swift-nio-extras/Sources/NIOResumableUpload/HTTPResumableUploadContext.swift
Guoye Zhang fde9d65d2e
Import HTTP resumable upload sample code (#203)
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>
2024-12-11 07:39:13 +00:00

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]
}
}
}