mirror of
https://github.com/apple/swift-nio-extras.git
synced 2025-06-03 03:26:23 +08:00
Support HTTP resumable upload. ### Motivation: Supporting HTTP resumable upload protocol defined in https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-resumable-upload-05 * Interop version 3: iOS 17.0, macOS 14.0 * Interop version 5: iOS 18.0, macOS 15.0 * Interop version 6: iOS 18.1, macOS 15.1 ### Modifications: 2 new public classes, `HTTPResumableUploadHandler` and `HTTPResumableUploadContext`, and a few other supporting objects to manage resumable uploads and translate them into regular uploads. --------- Co-authored-by: Jonathan Flat <jflat@apple.com> Co-authored-by: Cory Benfield <lukasa@apple.com>
75 lines
2.5 KiB
Swift
75 lines
2.5 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This source file is part of the SwiftNIO open source project
|
|
//
|
|
// Copyright (c) 2023-2024 Apple Inc. and the SwiftNIO project authors
|
|
// Licensed under Apache License v2.0
|
|
//
|
|
// See LICENSE.txt for license information
|
|
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
|
|
//
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
import NIOConcurrencyHelpers
|
|
import NIOCore
|
|
|
|
/// `HTTPResumableUploadContext` manages ongoing uploads.
|
|
public final class HTTPResumableUploadContext {
|
|
let origin: String
|
|
let path: String
|
|
let timeout: TimeAmount
|
|
private let uploads: NIOLockedValueBox<[String: HTTPResumableUpload]> = .init([:])
|
|
|
|
/// Create an `HTTPResumableUploadContext` for use with `HTTPResumableUploadHandler`.
|
|
/// - Parameters:
|
|
/// - origin: Scheme and authority of the upload server. For example, "https://www.example.com".
|
|
/// - path: Request path for resumption URLs. `HTTPResumableUploadHandler` intercepts all requests to this path.
|
|
/// - timeout: Time to wait before failure if the client didn't attempt an upload resumption.
|
|
public init(origin: String, path: String = "/resumable_upload/", timeout: TimeAmount = .hours(1)) {
|
|
self.origin = origin
|
|
self.path = path
|
|
self.timeout = timeout
|
|
}
|
|
|
|
func isResumption(path: String) -> Bool {
|
|
path.hasPrefix(self.path)
|
|
}
|
|
|
|
private func path(fromToken token: String) -> String {
|
|
"\(self.path)\(token)"
|
|
}
|
|
|
|
private func token(fromPath path: String) -> String {
|
|
assert(self.isResumption(path: path))
|
|
return String(path.dropFirst(self.path.count))
|
|
}
|
|
|
|
func startUpload(_ upload: HTTPResumableUpload) -> String {
|
|
var random = SystemRandomNumberGenerator()
|
|
let token = "\(random.next())-\(random.next())"
|
|
self.uploads.withLockedValue {
|
|
assert($0[token] == nil)
|
|
$0[token] = upload
|
|
}
|
|
return self.path(fromToken: token)
|
|
}
|
|
|
|
func stopUpload(_ upload: HTTPResumableUpload) {
|
|
if let path = upload.resumePath {
|
|
let token = token(fromPath: path)
|
|
self.uploads.withLockedValue {
|
|
$0[token] = nil
|
|
}
|
|
}
|
|
}
|
|
|
|
func findUpload(path: String) -> HTTPResumableUpload? {
|
|
let token = token(fromPath: path)
|
|
return self.uploads.withLockedValue {
|
|
$0[token]
|
|
}
|
|
}
|
|
}
|