mirror of
https://github.com/apple/swift-nio-extras.git
synced 2025-05-14 00:42:41 +08:00
Strict concurrency for NIOResumableUpload (#265)
HTTPResumableUpload contains the core logic. It uses an event loop to synchronize its state internally. Some methods are safe to call from off of that event loop and have been moved to a new sendable view. The HTTPResumableUpload type is marked as explicitly not sendable. As such, most other types now hold on to the sendable view and use that as the interface to HTTPResumableUpload. HTTPResumableUploadChannel must be sendable (it's a Channel) and now uses safe abstractions (where possible).
This commit is contained in:
parent
b6b5e1133f
commit
a0189d045c
@ -214,7 +214,8 @@ var targets: [PackageDescription.Target] = [
|
|||||||
.product(name: "NIOCore", package: "swift-nio"),
|
.product(name: "NIOCore", package: "swift-nio"),
|
||||||
.product(name: "StructuredFieldValues", package: "swift-http-structured-headers"),
|
.product(name: "StructuredFieldValues", package: "swift-http-structured-headers"),
|
||||||
.product(name: "Atomics", package: "swift-atomics"),
|
.product(name: "Atomics", package: "swift-atomics"),
|
||||||
]
|
],
|
||||||
|
swiftSettings: strictConcurrencySettings
|
||||||
),
|
),
|
||||||
.executableTarget(
|
.executableTarget(
|
||||||
name: "NIOResumableUploadDemo",
|
name: "NIOResumableUploadDemo",
|
||||||
@ -224,14 +225,16 @@ var targets: [PackageDescription.Target] = [
|
|||||||
.product(name: "HTTPTypes", package: "swift-http-types"),
|
.product(name: "HTTPTypes", package: "swift-http-types"),
|
||||||
.product(name: "NIOCore", package: "swift-nio"),
|
.product(name: "NIOCore", package: "swift-nio"),
|
||||||
.product(name: "NIOPosix", package: "swift-nio"),
|
.product(name: "NIOPosix", package: "swift-nio"),
|
||||||
]
|
],
|
||||||
|
swiftSettings: strictConcurrencySettings
|
||||||
),
|
),
|
||||||
.testTarget(
|
.testTarget(
|
||||||
name: "NIOResumableUploadTests",
|
name: "NIOResumableUploadTests",
|
||||||
dependencies: [
|
dependencies: [
|
||||||
"NIOResumableUpload",
|
"NIOResumableUpload",
|
||||||
.product(name: "NIOEmbedded", package: "swift-nio"),
|
.product(name: "NIOEmbedded", package: "swift-nio"),
|
||||||
]
|
],
|
||||||
|
swiftSettings: strictConcurrencySettings
|
||||||
),
|
),
|
||||||
.target(
|
.target(
|
||||||
name: "NIOHTTPResponsiveness",
|
name: "NIOHTTPResponsiveness",
|
||||||
|
@ -63,7 +63,7 @@ final class HTTPResumableUpload {
|
|||||||
|
|
||||||
private func createChannel(handler: HTTPResumableUploadHandler, parent: Channel) -> HTTPResumableUploadChannel {
|
private func createChannel(handler: HTTPResumableUploadHandler, parent: Channel) -> HTTPResumableUploadChannel {
|
||||||
let channel = HTTPResumableUploadChannel(
|
let channel = HTTPResumableUploadChannel(
|
||||||
upload: self,
|
upload: self.sendableView,
|
||||||
parent: parent,
|
parent: parent,
|
||||||
channelConfigurator: self.channelConfigurator
|
channelConfigurator: self.channelConfigurator
|
||||||
)
|
)
|
||||||
@ -89,6 +89,86 @@ final class HTTPResumableUpload {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension HTTPResumableUpload {
|
||||||
|
var sendableView: SendableView {
|
||||||
|
SendableView(handler: self)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SendableView: Sendable {
|
||||||
|
let eventLoop: any EventLoop
|
||||||
|
let loopBoundUpload: NIOLoopBound<HTTPResumableUpload>
|
||||||
|
let uploadHandlerChannel: NIOLockedValueBox<Channel?>
|
||||||
|
|
||||||
|
init(handler: HTTPResumableUpload) {
|
||||||
|
self.eventLoop = handler.eventLoop
|
||||||
|
self.loopBoundUpload = NIOLoopBound(handler, eventLoop: eventLoop)
|
||||||
|
self.uploadHandlerChannel = handler.uploadHandlerChannel
|
||||||
|
}
|
||||||
|
|
||||||
|
var parentChannel: Channel? {
|
||||||
|
self.uploadHandlerChannel.withLockedValue { $0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
private func withHandlerOnEventLoop(
|
||||||
|
checkHandler: HTTPResumableUploadHandler,
|
||||||
|
_ work: @escaping @Sendable (HTTPResumableUpload) -> Void
|
||||||
|
) {
|
||||||
|
let id = ObjectIdentifier(checkHandler)
|
||||||
|
|
||||||
|
if self.eventLoop.inEventLoop {
|
||||||
|
let upload = self.loopBoundUpload.value
|
||||||
|
if let handler = upload.uploadHandler, ObjectIdentifier(handler) == id {
|
||||||
|
work(upload)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.eventLoop.execute {
|
||||||
|
let upload = self.loopBoundUpload.value
|
||||||
|
if let handler = upload.uploadHandler, ObjectIdentifier(handler) == id {
|
||||||
|
work(upload)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func receive(handler: HTTPResumableUploadHandler, channel: Channel, part: HTTPRequestPart) {
|
||||||
|
self.withHandlerOnEventLoop(checkHandler: handler) { upload in
|
||||||
|
switch part {
|
||||||
|
case .head(let request):
|
||||||
|
upload.receiveHead(handler: upload.uploadHandler!, channel: channel, request: request)
|
||||||
|
case .body(let body):
|
||||||
|
upload.receiveBody(handler: upload.uploadHandler!, body: body)
|
||||||
|
case .end(let trailers):
|
||||||
|
upload.receiveEnd(handler: upload.uploadHandler!, trailers: trailers)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func receiveComplete(handler: HTTPResumableUploadHandler) {
|
||||||
|
self.withHandlerOnEventLoop(checkHandler: handler) { upload in
|
||||||
|
upload.uploadChannel?.receiveComplete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func writabilityChanged(handler: HTTPResumableUploadHandler) {
|
||||||
|
self.withHandlerOnEventLoop(checkHandler: handler) { upload in
|
||||||
|
upload.uploadChannel?.writabilityChanged()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func end(handler: HTTPResumableUploadHandler, error: Error?) {
|
||||||
|
self.withHandlerOnEventLoop(checkHandler: handler) { upload in
|
||||||
|
if !upload.uploadComplete && upload.resumePath != nil {
|
||||||
|
upload.pendingError = error
|
||||||
|
upload.detachUploadHandler(close: false)
|
||||||
|
} else {
|
||||||
|
upload.destroyChannel(error: error)
|
||||||
|
upload.detachUploadHandler(close: false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// For `HTTPResumableUploadHandler`.
|
// For `HTTPResumableUploadHandler`.
|
||||||
extension HTTPResumableUpload {
|
extension HTTPResumableUpload {
|
||||||
/// `HTTPResumableUpload` runs on the same event loop as the initial upload handler that started the upload.
|
/// `HTTPResumableUpload` runs on the same event loop as the initial upload handler that started the upload.
|
||||||
@ -99,22 +179,6 @@ extension HTTPResumableUpload {
|
|||||||
self.eventLoop = eventLoop
|
self.eventLoop = eventLoop
|
||||||
}
|
}
|
||||||
|
|
||||||
private func runInEventLoop(_ work: @escaping () -> Void) {
|
|
||||||
if self.eventLoop.inEventLoop {
|
|
||||||
work()
|
|
||||||
} else {
|
|
||||||
self.eventLoop.execute(work)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func runInEventLoop(checkHandler handler: HTTPResumableUploadHandler, _ work: @escaping () -> Void) {
|
|
||||||
self.runInEventLoop {
|
|
||||||
if self.uploadHandler === handler {
|
|
||||||
work()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func attachUploadHandler(_ handler: HTTPResumableUploadHandler, channel: Channel) {
|
func attachUploadHandler(_ handler: HTTPResumableUploadHandler, channel: Channel) {
|
||||||
self.eventLoop.preconditionInEventLoop()
|
self.eventLoop.preconditionInEventLoop()
|
||||||
|
|
||||||
@ -146,7 +210,8 @@ extension HTTPResumableUpload {
|
|||||||
|
|
||||||
if self.uploadChannel != nil {
|
if self.uploadChannel != nil {
|
||||||
self.idleTimer?.cancel()
|
self.idleTimer?.cancel()
|
||||||
self.idleTimer = self.eventLoop.scheduleTask(in: self.context.timeout) {
|
// Unsafe unchecked is fine: there's a precondition on entering this function.
|
||||||
|
self.idleTimer = self.eventLoop.assumeIsolatedUnsafeUnchecked().scheduleTask(in: self.context.timeout) {
|
||||||
let error = self.pendingError ?? HTTPResumableUploadError.timeoutWaitingForResumption
|
let error = self.pendingError ?? HTTPResumableUploadError.timeoutWaitingForResumption
|
||||||
self.uploadChannel?.end(error: error)
|
self.uploadChannel?.end(error: error)
|
||||||
self.uploadChannel = nil
|
self.uploadChannel = nil
|
||||||
@ -159,15 +224,13 @@ extension HTTPResumableUpload {
|
|||||||
otherHandler: HTTPResumableUploadHandler,
|
otherHandler: HTTPResumableUploadHandler,
|
||||||
version: HTTPResumableUploadProtocol.InteropVersion
|
version: HTTPResumableUploadProtocol.InteropVersion
|
||||||
) {
|
) {
|
||||||
self.runInEventLoop {
|
self.detachUploadHandler(close: true)
|
||||||
self.detachUploadHandler(close: true)
|
let response = HTTPResumableUploadProtocol.offsetRetrievingResponse(
|
||||||
let response = HTTPResumableUploadProtocol.offsetRetrievingResponse(
|
offset: self.offset,
|
||||||
offset: self.offset,
|
complete: self.uploadComplete,
|
||||||
complete: self.uploadComplete,
|
version: version
|
||||||
version: version
|
)
|
||||||
)
|
self.respondAndDetach(response, handler: otherHandler)
|
||||||
self.respondAndDetach(response, handler: otherHandler)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func saveUploadLength(complete: Bool, contentLength: Int64?, uploadLength: Int64?) -> Bool {
|
private func saveUploadLength(complete: Bool, contentLength: Int64?, uploadLength: Int64?) -> Bool {
|
||||||
@ -198,40 +261,36 @@ extension HTTPResumableUpload {
|
|||||||
uploadLength: Int64?,
|
uploadLength: Int64?,
|
||||||
version: HTTPResumableUploadProtocol.InteropVersion
|
version: HTTPResumableUploadProtocol.InteropVersion
|
||||||
) {
|
) {
|
||||||
self.runInEventLoop {
|
let conflict: Bool
|
||||||
let conflict: Bool
|
if self.uploadHandler == nil && self.offset == offset && !self.responseStarted {
|
||||||
if self.uploadHandler == nil && self.offset == offset && !self.responseStarted {
|
conflict = !self.saveUploadLength(
|
||||||
conflict = !self.saveUploadLength(
|
complete: complete,
|
||||||
complete: complete,
|
contentLength: contentLength,
|
||||||
contentLength: contentLength,
|
uploadLength: uploadLength
|
||||||
uploadLength: uploadLength
|
)
|
||||||
)
|
} else {
|
||||||
} else {
|
conflict = true
|
||||||
conflict = true
|
|
||||||
}
|
|
||||||
guard !conflict else {
|
|
||||||
self.detachUploadHandler(close: true)
|
|
||||||
self.destroyChannel(error: HTTPResumableUploadError.badResumption)
|
|
||||||
let response = HTTPResumableUploadProtocol.conflictResponse(
|
|
||||||
offset: self.offset,
|
|
||||||
complete: self.uploadComplete,
|
|
||||||
version: version
|
|
||||||
)
|
|
||||||
self.respondAndDetach(response, handler: otherHandler)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
self.requestIsCreation = false
|
|
||||||
self.requestIsComplete = complete
|
|
||||||
self.interopVersion = version
|
|
||||||
self.attachUploadHandler(otherHandler, channel: channel)
|
|
||||||
}
|
}
|
||||||
|
guard !conflict else {
|
||||||
|
self.detachUploadHandler(close: true)
|
||||||
|
self.destroyChannel(error: HTTPResumableUploadError.badResumption)
|
||||||
|
let response = HTTPResumableUploadProtocol.conflictResponse(
|
||||||
|
offset: self.offset,
|
||||||
|
complete: self.uploadComplete,
|
||||||
|
version: version
|
||||||
|
)
|
||||||
|
self.respondAndDetach(response, handler: otherHandler)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.requestIsCreation = false
|
||||||
|
self.requestIsComplete = complete
|
||||||
|
self.interopVersion = version
|
||||||
|
self.attachUploadHandler(otherHandler, channel: channel)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func uploadCancellation() {
|
private func uploadCancellation() {
|
||||||
self.runInEventLoop {
|
self.detachUploadHandler(close: true)
|
||||||
self.detachUploadHandler(close: true)
|
self.destroyChannel(error: HTTPResumableUploadError.uploadCancelled)
|
||||||
self.destroyChannel(error: HTTPResumableUploadError.uploadCancelled)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func receiveHead(handler: HTTPResumableUploadHandler, channel: Channel, request: HTTPRequest) {
|
private func receiveHead(handler: HTTPResumableUploadHandler, channel: Channel, request: HTTPRequest) {
|
||||||
@ -277,7 +336,7 @@ extension HTTPResumableUpload {
|
|||||||
if let path = request.path, let upload = self.context.findUpload(path: path) {
|
if let path = request.path, let upload = self.context.findUpload(path: path) {
|
||||||
self.uploadHandler = nil
|
self.uploadHandler = nil
|
||||||
self.uploadHandlerChannel.withLockedValue { $0 = nil }
|
self.uploadHandlerChannel.withLockedValue { $0 = nil }
|
||||||
upload.offsetRetrieving(otherHandler: handler, version: version)
|
upload.loopBoundUpload.value.offsetRetrieving(otherHandler: handler, version: version)
|
||||||
} else {
|
} else {
|
||||||
let response = HTTPResumableUploadProtocol.notFoundResponse(version: version)
|
let response = HTTPResumableUploadProtocol.notFoundResponse(version: version)
|
||||||
self.respondAndDetach(response, handler: handler)
|
self.respondAndDetach(response, handler: handler)
|
||||||
@ -287,7 +346,7 @@ extension HTTPResumableUpload {
|
|||||||
handler.upload = upload
|
handler.upload = upload
|
||||||
self.uploadHandler = nil
|
self.uploadHandler = nil
|
||||||
self.uploadHandlerChannel.withLockedValue { $0 = nil }
|
self.uploadHandlerChannel.withLockedValue { $0 = nil }
|
||||||
upload.uploadAppending(
|
upload.loopBoundUpload.value.uploadAppending(
|
||||||
otherHandler: handler,
|
otherHandler: handler,
|
||||||
channel: channel,
|
channel: channel,
|
||||||
offset: offset,
|
offset: offset,
|
||||||
@ -302,7 +361,7 @@ extension HTTPResumableUpload {
|
|||||||
}
|
}
|
||||||
case .uploadCancellation:
|
case .uploadCancellation:
|
||||||
if let path = request.path, let upload = self.context.findUpload(path: path) {
|
if let path = request.path, let upload = self.context.findUpload(path: path) {
|
||||||
upload.uploadCancellation()
|
upload.loopBoundUpload.value.uploadCancellation()
|
||||||
let response = HTTPResumableUploadProtocol.cancelledResponse(version: version)
|
let response = HTTPResumableUploadProtocol.cancelledResponse(version: version)
|
||||||
self.respondAndDetach(response, handler: handler)
|
self.respondAndDetach(response, handler: handler)
|
||||||
} else {
|
} else {
|
||||||
@ -358,43 +417,6 @@ extension HTTPResumableUpload {
|
|||||||
self.uploadChannel?.receive(.end(trailers))
|
self.uploadChannel?.receive(.end(trailers))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func receive(handler: HTTPResumableUploadHandler, channel: Channel, part: HTTPRequestPart) {
|
|
||||||
self.runInEventLoop(checkHandler: handler) {
|
|
||||||
switch part {
|
|
||||||
case .head(let request):
|
|
||||||
self.receiveHead(handler: handler, channel: channel, request: request)
|
|
||||||
case .body(let body):
|
|
||||||
self.receiveBody(handler: handler, body: body)
|
|
||||||
case .end(let trailers):
|
|
||||||
self.receiveEnd(handler: handler, trailers: trailers)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func receiveComplete(handler: HTTPResumableUploadHandler) {
|
|
||||||
self.runInEventLoop(checkHandler: handler) {
|
|
||||||
self.uploadChannel?.receiveComplete()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func writabilityChanged(handler: HTTPResumableUploadHandler) {
|
|
||||||
self.runInEventLoop(checkHandler: handler) {
|
|
||||||
self.uploadChannel?.writabilityChanged()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func end(handler: HTTPResumableUploadHandler, error: Error?) {
|
|
||||||
self.runInEventLoop(checkHandler: handler) {
|
|
||||||
if !self.uploadComplete && self.resumePath != nil {
|
|
||||||
self.pendingError = error
|
|
||||||
self.detachUploadHandler(close: false)
|
|
||||||
} else {
|
|
||||||
self.destroyChannel(error: error)
|
|
||||||
self.detachUploadHandler(close: false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// For `HTTPResumableUploadChannel`.
|
// For `HTTPResumableUploadChannel`.
|
||||||
@ -477,6 +499,9 @@ extension HTTPResumableUpload {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable)
|
||||||
|
extension HTTPResumableUpload: Sendable {}
|
||||||
|
|
||||||
/// Errors produced by resumable upload.
|
/// Errors produced by resumable upload.
|
||||||
enum HTTPResumableUploadError: Error {
|
enum HTTPResumableUploadError: Error {
|
||||||
/// An upload cancelation request received.
|
/// An upload cancelation request received.
|
||||||
|
@ -18,8 +18,10 @@ import NIOHTTPTypes
|
|||||||
|
|
||||||
/// The child channel that persists across upload resumption attempts, delivering data as if it is
|
/// The child channel that persists across upload resumption attempts, delivering data as if it is
|
||||||
/// a single HTTP upload.
|
/// a single HTTP upload.
|
||||||
final class HTTPResumableUploadChannel: Channel, ChannelCore {
|
final class HTTPResumableUploadChannel: Channel, ChannelCore, @unchecked Sendable {
|
||||||
let upload: HTTPResumableUpload
|
// @unchecked because of '_pipeline' which is an IUO assigned during init.
|
||||||
|
|
||||||
|
let upload: HTTPResumableUpload.SendableView
|
||||||
|
|
||||||
let allocator: ByteBufferAllocator
|
let allocator: ByteBufferAllocator
|
||||||
|
|
||||||
@ -63,10 +65,10 @@ final class HTTPResumableUploadChannel: Channel, ChannelCore {
|
|||||||
|
|
||||||
let eventLoop: EventLoop
|
let eventLoop: EventLoop
|
||||||
|
|
||||||
private var autoRead: Bool
|
private var autoRead: NIOLoopBound<Bool>
|
||||||
|
|
||||||
init(
|
init(
|
||||||
upload: HTTPResumableUpload,
|
upload: HTTPResumableUpload.SendableView,
|
||||||
parent: Channel,
|
parent: Channel,
|
||||||
channelConfigurator: (Channel) -> Void
|
channelConfigurator: (Channel) -> Void
|
||||||
) {
|
) {
|
||||||
@ -75,7 +77,8 @@ final class HTTPResumableUploadChannel: Channel, ChannelCore {
|
|||||||
self.closePromise = parent.eventLoop.makePromise()
|
self.closePromise = parent.eventLoop.makePromise()
|
||||||
self.eventLoop = parent.eventLoop
|
self.eventLoop = parent.eventLoop
|
||||||
// Only support Channels that implement sync options
|
// Only support Channels that implement sync options
|
||||||
self.autoRead = try! parent.syncOptions!.getOption(ChannelOptions.autoRead)
|
let autoRead = try! parent.syncOptions!.getOption(ChannelOptions.autoRead)
|
||||||
|
self.autoRead = NIOLoopBound(autoRead, eventLoop: eventLoop)
|
||||||
self._pipeline = ChannelPipeline(channel: self)
|
self._pipeline = ChannelPipeline(channel: self)
|
||||||
channelConfigurator(self)
|
channelConfigurator(self)
|
||||||
}
|
}
|
||||||
@ -109,7 +112,7 @@ final class HTTPResumableUploadChannel: Channel, ChannelCore {
|
|||||||
|
|
||||||
switch option {
|
switch option {
|
||||||
case _ as ChannelOptions.Types.AutoReadOption:
|
case _ as ChannelOptions.Types.AutoReadOption:
|
||||||
self.autoRead = value as! Bool
|
self.autoRead.value = value as! Bool
|
||||||
default:
|
default:
|
||||||
if let parent = self.parent {
|
if let parent = self.parent {
|
||||||
// Only support Channels that implement sync options
|
// Only support Channels that implement sync options
|
||||||
@ -125,7 +128,7 @@ final class HTTPResumableUploadChannel: Channel, ChannelCore {
|
|||||||
|
|
||||||
switch option {
|
switch option {
|
||||||
case _ as ChannelOptions.Types.AutoReadOption:
|
case _ as ChannelOptions.Types.AutoReadOption:
|
||||||
return self.autoRead as! Option.Value
|
return self.autoRead.value as! Option.Value
|
||||||
default:
|
default:
|
||||||
if let parent = self.parent {
|
if let parent = self.parent {
|
||||||
// Only support Channels that implement sync options
|
// Only support Channels that implement sync options
|
||||||
@ -157,23 +160,19 @@ final class HTTPResumableUploadChannel: Channel, ChannelCore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func write0(_ data: NIOAny, promise: EventLoopPromise<Void>?) {
|
func write0(_ data: NIOAny, promise: EventLoopPromise<Void>?) {
|
||||||
self.eventLoop.preconditionInEventLoop()
|
self.upload.loopBoundUpload.value.write(unwrapData(data), promise: promise)
|
||||||
self.upload.write(unwrapData(data), promise: promise)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func flush0() {
|
func flush0() {
|
||||||
self.eventLoop.preconditionInEventLoop()
|
self.upload.loopBoundUpload.value.flush()
|
||||||
self.upload.flush()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func read0() {
|
func read0() {
|
||||||
self.eventLoop.preconditionInEventLoop()
|
self.upload.loopBoundUpload.value.read()
|
||||||
self.upload.read()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func close0(error: Error, mode: CloseMode, promise: EventLoopPromise<Void>?) {
|
func close0(error: Error, mode: CloseMode, promise: EventLoopPromise<Void>?) {
|
||||||
self.eventLoop.preconditionInEventLoop()
|
self.upload.loopBoundUpload.value.close(mode: mode, promise: promise)
|
||||||
self.upload.close(mode: mode, promise: promise)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func triggerUserOutboundEvent0(_ event: Any, promise: EventLoopPromise<Void>?) {
|
func triggerUserOutboundEvent0(_ event: Any, promise: EventLoopPromise<Void>?) {
|
||||||
@ -228,7 +227,7 @@ extension HTTPResumableUploadChannel {
|
|||||||
self.eventLoop.preconditionInEventLoop()
|
self.eventLoop.preconditionInEventLoop()
|
||||||
self.pipeline.fireChannelReadComplete()
|
self.pipeline.fireChannelReadComplete()
|
||||||
|
|
||||||
if self.autoRead {
|
if self.autoRead.value {
|
||||||
self.pipeline.read()
|
self.pipeline.read()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,11 +16,11 @@ import NIOConcurrencyHelpers
|
|||||||
import NIOCore
|
import NIOCore
|
||||||
|
|
||||||
/// `HTTPResumableUploadContext` manages ongoing uploads.
|
/// `HTTPResumableUploadContext` manages ongoing uploads.
|
||||||
public final class HTTPResumableUploadContext {
|
public final class HTTPResumableUploadContext: Sendable {
|
||||||
let origin: String
|
let origin: String
|
||||||
let path: String
|
let path: String
|
||||||
let timeout: TimeAmount
|
let timeout: TimeAmount
|
||||||
private let uploads: NIOLockedValueBox<[String: HTTPResumableUpload]> = .init([:])
|
private let uploads: NIOLockedValueBox<[String: HTTPResumableUpload.SendableView]> = .init([:])
|
||||||
|
|
||||||
/// Create an `HTTPResumableUploadContext` for use with `HTTPResumableUploadHandler`.
|
/// Create an `HTTPResumableUploadContext` for use with `HTTPResumableUploadHandler`.
|
||||||
/// - Parameters:
|
/// - Parameters:
|
||||||
@ -51,7 +51,7 @@ public final class HTTPResumableUploadContext {
|
|||||||
let token = "\(random.next())-\(random.next())"
|
let token = "\(random.next())-\(random.next())"
|
||||||
self.uploads.withLockedValue {
|
self.uploads.withLockedValue {
|
||||||
assert($0[token] == nil)
|
assert($0[token] == nil)
|
||||||
$0[token] = upload
|
$0[token] = upload.sendableView
|
||||||
}
|
}
|
||||||
return self.path(fromToken: token)
|
return self.path(fromToken: token)
|
||||||
}
|
}
|
||||||
@ -65,7 +65,7 @@ public final class HTTPResumableUploadContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func findUpload(path: String) -> HTTPResumableUpload? {
|
func findUpload(path: String) -> HTTPResumableUpload.SendableView? {
|
||||||
let token = token(fromPath: path)
|
let token = token(fromPath: path)
|
||||||
return self.uploads.withLockedValue {
|
return self.uploads.withLockedValue {
|
||||||
$0[token]
|
$0[token]
|
||||||
|
@ -24,7 +24,7 @@ public final class HTTPResumableUploadHandler: ChannelDuplexHandler {
|
|||||||
public typealias OutboundIn = Never
|
public typealias OutboundIn = Never
|
||||||
public typealias OutboundOut = HTTPResponsePart
|
public typealias OutboundOut = HTTPResponsePart
|
||||||
|
|
||||||
var upload: HTTPResumableUpload? = nil
|
var upload: HTTPResumableUpload.SendableView? = nil
|
||||||
let createUpload: () -> HTTPResumableUpload
|
let createUpload: () -> HTTPResumableUpload
|
||||||
var shouldReset: Bool = false
|
var shouldReset: Bool = false
|
||||||
|
|
||||||
@ -60,7 +60,7 @@ public final class HTTPResumableUploadHandler: ChannelDuplexHandler {
|
|||||||
self.createUpload = {
|
self.createUpload = {
|
||||||
HTTPResumableUpload(context: context) { channel in
|
HTTPResumableUpload(context: context) { channel in
|
||||||
if !handlers.isEmpty {
|
if !handlers.isEmpty {
|
||||||
_ = channel.pipeline.addHandlers(handlers)
|
try? channel.pipeline.syncOperations.addHandlers(handlers)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -73,7 +73,7 @@ public final class HTTPResumableUploadHandler: ChannelDuplexHandler {
|
|||||||
let upload = self.createUpload()
|
let upload = self.createUpload()
|
||||||
upload.scheduleOnEventLoop(self.eventLoop)
|
upload.scheduleOnEventLoop(self.eventLoop)
|
||||||
upload.attachUploadHandler(self, channel: context.channel)
|
upload.attachUploadHandler(self, channel: context.channel)
|
||||||
self.upload = upload
|
self.upload = upload.sendableView
|
||||||
self.shouldReset = false
|
self.shouldReset = false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,49 +124,32 @@ public final class HTTPResumableUploadHandler: ChannelDuplexHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@available(*, unavailable)
|
||||||
|
extension HTTPResumableUploadHandler: Sendable {}
|
||||||
|
|
||||||
// For `HTTPResumableUpload`.
|
// For `HTTPResumableUpload`.
|
||||||
extension HTTPResumableUploadHandler {
|
extension HTTPResumableUploadHandler {
|
||||||
private func runInEventLoop(_ work: @escaping () -> Void) {
|
|
||||||
if self.eventLoop.inEventLoop {
|
|
||||||
work()
|
|
||||||
} else {
|
|
||||||
self.eventLoop.execute(work)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func write(_ part: HTTPResponsePart, promise: EventLoopPromise<Void>?) {
|
func write(_ part: HTTPResponsePart, promise: EventLoopPromise<Void>?) {
|
||||||
self.runInEventLoop {
|
self.context?.write(self.wrapOutboundOut(part), promise: promise)
|
||||||
self.context?.write(self.wrapOutboundOut(part), promise: promise)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func flush() {
|
func flush() {
|
||||||
self.runInEventLoop {
|
self.context?.flush()
|
||||||
self.context?.flush()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeAndFlush(_ part: HTTPResponsePart, promise: EventLoopPromise<Void>?) {
|
func writeAndFlush(_ part: HTTPResponsePart, promise: EventLoopPromise<Void>?) {
|
||||||
self.runInEventLoop {
|
self.context?.writeAndFlush(self.wrapOutboundOut(part), promise: promise)
|
||||||
self.context?.writeAndFlush(self.wrapOutboundOut(part), promise: promise)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func read() {
|
func read() {
|
||||||
self.runInEventLoop {
|
self.context?.read()
|
||||||
self.context?.read()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func close(mode: CloseMode, promise: EventLoopPromise<Void>?) {
|
func close(mode: CloseMode, promise: EventLoopPromise<Void>?) {
|
||||||
self.runInEventLoop {
|
self.context?.close(mode: mode, promise: promise)
|
||||||
self.context?.close(mode: mode, promise: promise)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func detach() {
|
func detach() {
|
||||||
self.runInEventLoop {
|
self.context = nil
|
||||||
self.context = nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,9 @@ final class NIOResumableUploadTests: XCTestCase {
|
|||||||
let recorder = InboundRecorder<HTTPRequestPart, Never>()
|
let recorder = InboundRecorder<HTTPRequestPart, Never>()
|
||||||
|
|
||||||
let context = HTTPResumableUploadContext(origin: "https://example.com")
|
let context = HTTPResumableUploadContext(origin: "https://example.com")
|
||||||
try channel.pipeline.addHandler(HTTPResumableUploadHandler(context: context, handlers: [recorder])).wait()
|
try channel.pipeline.syncOperations.addHandler(
|
||||||
|
HTTPResumableUploadHandler(context: context, handlers: [recorder])
|
||||||
|
)
|
||||||
|
|
||||||
let request = HTTPRequest(method: .get, scheme: "https", authority: "example.com", path: "/")
|
let request = HTTPRequest(method: .get, scheme: "https", authority: "example.com", path: "/")
|
||||||
try channel.writeInbound(HTTPRequestPart.head(request))
|
try channel.writeInbound(HTTPRequestPart.head(request))
|
||||||
@ -66,7 +68,9 @@ final class NIOResumableUploadTests: XCTestCase {
|
|||||||
let recorder = InboundRecorder<HTTPRequestPart, Never>()
|
let recorder = InboundRecorder<HTTPRequestPart, Never>()
|
||||||
|
|
||||||
let context = HTTPResumableUploadContext(origin: "https://example.com")
|
let context = HTTPResumableUploadContext(origin: "https://example.com")
|
||||||
try channel.pipeline.addHandler(HTTPResumableUploadHandler(context: context, handlers: [recorder])).wait()
|
try channel.pipeline.syncOperations.addHandler(
|
||||||
|
HTTPResumableUploadHandler(context: context, handlers: [recorder])
|
||||||
|
)
|
||||||
|
|
||||||
let request = HTTPRequest(method: .post, scheme: "https", authority: "example.com", path: "/")
|
let request = HTTPRequest(method: .post, scheme: "https", authority: "example.com", path: "/")
|
||||||
try channel.writeInbound(HTTPRequestPart.head(request))
|
try channel.writeInbound(HTTPRequestPart.head(request))
|
||||||
@ -85,7 +89,9 @@ final class NIOResumableUploadTests: XCTestCase {
|
|||||||
let recorder = InboundRecorder<HTTPRequestPart, HTTPResponsePart>()
|
let recorder = InboundRecorder<HTTPRequestPart, HTTPResponsePart>()
|
||||||
|
|
||||||
let context = HTTPResumableUploadContext(origin: "https://example.com")
|
let context = HTTPResumableUploadContext(origin: "https://example.com")
|
||||||
try channel.pipeline.addHandler(HTTPResumableUploadHandler(context: context, handlers: [recorder])).wait()
|
try channel.pipeline.syncOperations.addHandler(
|
||||||
|
HTTPResumableUploadHandler(context: context, handlers: [recorder])
|
||||||
|
)
|
||||||
|
|
||||||
var request = HTTPRequest(method: .options, scheme: "https", authority: "example.com", path: "/")
|
var request = HTTPRequest(method: .options, scheme: "https", authority: "example.com", path: "/")
|
||||||
request.headerFields[.uploadDraftInteropVersion] = "6"
|
request.headerFields[.uploadDraftInteropVersion] = "6"
|
||||||
@ -118,7 +124,9 @@ final class NIOResumableUploadTests: XCTestCase {
|
|||||||
let recorder = InboundRecorder<HTTPRequestPart, Never>()
|
let recorder = InboundRecorder<HTTPRequestPart, Never>()
|
||||||
|
|
||||||
let context = HTTPResumableUploadContext(origin: "https://example.com")
|
let context = HTTPResumableUploadContext(origin: "https://example.com")
|
||||||
try channel.pipeline.addHandler(HTTPResumableUploadHandler(context: context, handlers: [recorder])).wait()
|
try channel.pipeline.syncOperations.addHandler(
|
||||||
|
HTTPResumableUploadHandler(context: context, handlers: [recorder])
|
||||||
|
)
|
||||||
|
|
||||||
var request = HTTPRequest(method: .post, scheme: "https", authority: "example.com", path: "/")
|
var request = HTTPRequest(method: .post, scheme: "https", authority: "example.com", path: "/")
|
||||||
request.headerFields[.uploadDraftInteropVersion] = "3"
|
request.headerFields[.uploadDraftInteropVersion] = "3"
|
||||||
@ -150,7 +158,9 @@ final class NIOResumableUploadTests: XCTestCase {
|
|||||||
let recorder = InboundRecorder<HTTPRequestPart, Never>()
|
let recorder = InboundRecorder<HTTPRequestPart, Never>()
|
||||||
|
|
||||||
let context = HTTPResumableUploadContext(origin: "https://example.com")
|
let context = HTTPResumableUploadContext(origin: "https://example.com")
|
||||||
try channel.pipeline.addHandler(HTTPResumableUploadHandler(context: context, handlers: [recorder])).wait()
|
try channel.pipeline.syncOperations.addHandler(
|
||||||
|
HTTPResumableUploadHandler(context: context, handlers: [recorder])
|
||||||
|
)
|
||||||
|
|
||||||
var request = HTTPRequest(method: .post, scheme: "https", authority: "example.com", path: "/")
|
var request = HTTPRequest(method: .post, scheme: "https", authority: "example.com", path: "/")
|
||||||
request.headerFields[.uploadDraftInteropVersion] = "5"
|
request.headerFields[.uploadDraftInteropVersion] = "5"
|
||||||
@ -182,7 +192,9 @@ final class NIOResumableUploadTests: XCTestCase {
|
|||||||
let recorder = InboundRecorder<HTTPRequestPart, Never>()
|
let recorder = InboundRecorder<HTTPRequestPart, Never>()
|
||||||
|
|
||||||
let context = HTTPResumableUploadContext(origin: "https://example.com")
|
let context = HTTPResumableUploadContext(origin: "https://example.com")
|
||||||
try channel.pipeline.addHandler(HTTPResumableUploadHandler(context: context, handlers: [recorder])).wait()
|
try channel.pipeline.syncOperations.addHandler(
|
||||||
|
HTTPResumableUploadHandler(context: context, handlers: [recorder])
|
||||||
|
)
|
||||||
|
|
||||||
var request = HTTPRequest(method: .post, scheme: "https", authority: "example.com", path: "/")
|
var request = HTTPRequest(method: .post, scheme: "https", authority: "example.com", path: "/")
|
||||||
request.headerFields[.uploadDraftInteropVersion] = "6"
|
request.headerFields[.uploadDraftInteropVersion] = "6"
|
||||||
@ -215,7 +227,9 @@ final class NIOResumableUploadTests: XCTestCase {
|
|||||||
let recorder = InboundRecorder<HTTPRequestPart, Never>()
|
let recorder = InboundRecorder<HTTPRequestPart, Never>()
|
||||||
|
|
||||||
let context = HTTPResumableUploadContext(origin: "https://example.com")
|
let context = HTTPResumableUploadContext(origin: "https://example.com")
|
||||||
try channel.pipeline.addHandler(HTTPResumableUploadHandler(context: context, handlers: [recorder])).wait()
|
try channel.pipeline.syncOperations.addHandler(
|
||||||
|
HTTPResumableUploadHandler(context: context, handlers: [recorder])
|
||||||
|
)
|
||||||
|
|
||||||
var request = HTTPRequest(method: .post, scheme: "https", authority: "example.com", path: "/")
|
var request = HTTPRequest(method: .post, scheme: "https", authority: "example.com", path: "/")
|
||||||
request.headerFields[.uploadDraftInteropVersion] = "3"
|
request.headerFields[.uploadDraftInteropVersion] = "3"
|
||||||
@ -235,7 +249,7 @@ final class NIOResumableUploadTests: XCTestCase {
|
|||||||
let resumptionPath = try XCTUnwrap(URLComponents(string: location)?.path)
|
let resumptionPath = try XCTUnwrap(URLComponents(string: location)?.path)
|
||||||
|
|
||||||
let channel2 = EmbeddedChannel()
|
let channel2 = EmbeddedChannel()
|
||||||
try channel2.pipeline.addHandler(HTTPResumableUploadHandler(context: context, handlers: [])).wait()
|
try channel2.pipeline.syncOperations.addHandler(HTTPResumableUploadHandler(context: context, handlers: []))
|
||||||
var request2 = HTTPRequest(method: .head, scheme: "https", authority: "example.com", path: resumptionPath)
|
var request2 = HTTPRequest(method: .head, scheme: "https", authority: "example.com", path: resumptionPath)
|
||||||
request2.headerFields[.uploadDraftInteropVersion] = "3"
|
request2.headerFields[.uploadDraftInteropVersion] = "3"
|
||||||
try channel2.writeInbound(HTTPRequestPart.head(request2))
|
try channel2.writeInbound(HTTPRequestPart.head(request2))
|
||||||
@ -251,7 +265,7 @@ final class NIOResumableUploadTests: XCTestCase {
|
|||||||
XCTAssertTrue(try channel2.finish().isClean)
|
XCTAssertTrue(try channel2.finish().isClean)
|
||||||
|
|
||||||
let channel3 = EmbeddedChannel()
|
let channel3 = EmbeddedChannel()
|
||||||
try channel3.pipeline.addHandler(HTTPResumableUploadHandler(context: context, handlers: [])).wait()
|
try channel3.pipeline.syncOperations.addHandler(HTTPResumableUploadHandler(context: context, handlers: []))
|
||||||
var request3 = HTTPRequest(method: .patch, scheme: "https", authority: "example.com", path: resumptionPath)
|
var request3 = HTTPRequest(method: .patch, scheme: "https", authority: "example.com", path: resumptionPath)
|
||||||
request3.headerFields[.uploadDraftInteropVersion] = "3"
|
request3.headerFields[.uploadDraftInteropVersion] = "3"
|
||||||
request3.headerFields[.uploadIncomplete] = "?0"
|
request3.headerFields[.uploadIncomplete] = "?0"
|
||||||
@ -277,7 +291,9 @@ final class NIOResumableUploadTests: XCTestCase {
|
|||||||
let recorder = InboundRecorder<HTTPRequestPart, Never>()
|
let recorder = InboundRecorder<HTTPRequestPart, Never>()
|
||||||
|
|
||||||
let context = HTTPResumableUploadContext(origin: "https://example.com")
|
let context = HTTPResumableUploadContext(origin: "https://example.com")
|
||||||
try channel.pipeline.addHandler(HTTPResumableUploadHandler(context: context, handlers: [recorder])).wait()
|
try channel.pipeline.syncOperations.addHandler(
|
||||||
|
HTTPResumableUploadHandler(context: context, handlers: [recorder])
|
||||||
|
)
|
||||||
|
|
||||||
var request = HTTPRequest(method: .post, scheme: "https", authority: "example.com", path: "/")
|
var request = HTTPRequest(method: .post, scheme: "https", authority: "example.com", path: "/")
|
||||||
request.headerFields[.uploadDraftInteropVersion] = "5"
|
request.headerFields[.uploadDraftInteropVersion] = "5"
|
||||||
@ -298,7 +314,7 @@ final class NIOResumableUploadTests: XCTestCase {
|
|||||||
let resumptionPath = try XCTUnwrap(URLComponents(string: location)?.path)
|
let resumptionPath = try XCTUnwrap(URLComponents(string: location)?.path)
|
||||||
|
|
||||||
let channel2 = EmbeddedChannel()
|
let channel2 = EmbeddedChannel()
|
||||||
try channel2.pipeline.addHandler(HTTPResumableUploadHandler(context: context, handlers: [])).wait()
|
try channel2.pipeline.syncOperations.addHandler(HTTPResumableUploadHandler(context: context, handlers: []))
|
||||||
var request2 = HTTPRequest(method: .head, scheme: "https", authority: "example.com", path: resumptionPath)
|
var request2 = HTTPRequest(method: .head, scheme: "https", authority: "example.com", path: resumptionPath)
|
||||||
request2.headerFields[.uploadDraftInteropVersion] = "3"
|
request2.headerFields[.uploadDraftInteropVersion] = "3"
|
||||||
try channel2.writeInbound(HTTPRequestPart.head(request2))
|
try channel2.writeInbound(HTTPRequestPart.head(request2))
|
||||||
@ -314,7 +330,7 @@ final class NIOResumableUploadTests: XCTestCase {
|
|||||||
XCTAssertTrue(try channel2.finish().isClean)
|
XCTAssertTrue(try channel2.finish().isClean)
|
||||||
|
|
||||||
let channel3 = EmbeddedChannel()
|
let channel3 = EmbeddedChannel()
|
||||||
try channel3.pipeline.addHandler(HTTPResumableUploadHandler(context: context, handlers: [])).wait()
|
try channel3.pipeline.syncOperations.addHandler(HTTPResumableUploadHandler(context: context, handlers: []))
|
||||||
var request3 = HTTPRequest(method: .patch, scheme: "https", authority: "example.com", path: resumptionPath)
|
var request3 = HTTPRequest(method: .patch, scheme: "https", authority: "example.com", path: resumptionPath)
|
||||||
request3.headerFields[.uploadDraftInteropVersion] = "5"
|
request3.headerFields[.uploadDraftInteropVersion] = "5"
|
||||||
request3.headerFields[.uploadComplete] = "?1"
|
request3.headerFields[.uploadComplete] = "?1"
|
||||||
@ -341,7 +357,9 @@ final class NIOResumableUploadTests: XCTestCase {
|
|||||||
let recorder = InboundRecorder<HTTPRequestPart, Never>()
|
let recorder = InboundRecorder<HTTPRequestPart, Never>()
|
||||||
|
|
||||||
let context = HTTPResumableUploadContext(origin: "https://example.com")
|
let context = HTTPResumableUploadContext(origin: "https://example.com")
|
||||||
try channel.pipeline.addHandler(HTTPResumableUploadHandler(context: context, handlers: [recorder])).wait()
|
try channel.pipeline.syncOperations.addHandler(
|
||||||
|
HTTPResumableUploadHandler(context: context, handlers: [recorder])
|
||||||
|
)
|
||||||
|
|
||||||
var request = HTTPRequest(method: .post, scheme: "https", authority: "example.com", path: "/")
|
var request = HTTPRequest(method: .post, scheme: "https", authority: "example.com", path: "/")
|
||||||
request.headerFields[.uploadDraftInteropVersion] = "6"
|
request.headerFields[.uploadDraftInteropVersion] = "6"
|
||||||
@ -362,7 +380,7 @@ final class NIOResumableUploadTests: XCTestCase {
|
|||||||
let resumptionPath = try XCTUnwrap(URLComponents(string: location)?.path)
|
let resumptionPath = try XCTUnwrap(URLComponents(string: location)?.path)
|
||||||
|
|
||||||
let channel2 = EmbeddedChannel()
|
let channel2 = EmbeddedChannel()
|
||||||
try channel2.pipeline.addHandler(HTTPResumableUploadHandler(context: context, handlers: [])).wait()
|
try channel2.pipeline.syncOperations.addHandler(HTTPResumableUploadHandler(context: context, handlers: []))
|
||||||
var request2 = HTTPRequest(method: .head, scheme: "https", authority: "example.com", path: resumptionPath)
|
var request2 = HTTPRequest(method: .head, scheme: "https", authority: "example.com", path: resumptionPath)
|
||||||
request2.headerFields[.uploadDraftInteropVersion] = "3"
|
request2.headerFields[.uploadDraftInteropVersion] = "3"
|
||||||
try channel2.writeInbound(HTTPRequestPart.head(request2))
|
try channel2.writeInbound(HTTPRequestPart.head(request2))
|
||||||
@ -378,7 +396,7 @@ final class NIOResumableUploadTests: XCTestCase {
|
|||||||
XCTAssertTrue(try channel2.finish().isClean)
|
XCTAssertTrue(try channel2.finish().isClean)
|
||||||
|
|
||||||
let channel3 = EmbeddedChannel()
|
let channel3 = EmbeddedChannel()
|
||||||
try channel3.pipeline.addHandler(HTTPResumableUploadHandler(context: context, handlers: [])).wait()
|
try channel3.pipeline.syncOperations.addHandler(HTTPResumableUploadHandler(context: context, handlers: []))
|
||||||
var request3 = HTTPRequest(method: .patch, scheme: "https", authority: "example.com", path: resumptionPath)
|
var request3 = HTTPRequest(method: .patch, scheme: "https", authority: "example.com", path: resumptionPath)
|
||||||
request3.headerFields[.uploadDraftInteropVersion] = "6"
|
request3.headerFields[.uploadDraftInteropVersion] = "6"
|
||||||
request3.headerFields[.uploadComplete] = "?1"
|
request3.headerFields[.uploadComplete] = "?1"
|
||||||
@ -406,7 +424,9 @@ final class NIOResumableUploadTests: XCTestCase {
|
|||||||
let recorder = InboundRecorder<HTTPRequestPart, Never>()
|
let recorder = InboundRecorder<HTTPRequestPart, Never>()
|
||||||
|
|
||||||
let context = HTTPResumableUploadContext(origin: "https://example.com")
|
let context = HTTPResumableUploadContext(origin: "https://example.com")
|
||||||
try channel.pipeline.addHandler(HTTPResumableUploadHandler(context: context, handlers: [recorder])).wait()
|
try channel.pipeline.syncOperations.addHandler(
|
||||||
|
HTTPResumableUploadHandler(context: context, handlers: [recorder])
|
||||||
|
)
|
||||||
|
|
||||||
var request = HTTPRequest(method: .post, scheme: "https", authority: "example.com", path: "/")
|
var request = HTTPRequest(method: .post, scheme: "https", authority: "example.com", path: "/")
|
||||||
request.headerFields[.uploadDraftInteropVersion] = "3"
|
request.headerFields[.uploadDraftInteropVersion] = "3"
|
||||||
@ -434,7 +454,7 @@ final class NIOResumableUploadTests: XCTestCase {
|
|||||||
XCTAssertEqual(try channel.readOutbound(as: HTTPResponsePart.self), .end(nil))
|
XCTAssertEqual(try channel.readOutbound(as: HTTPResponsePart.self), .end(nil))
|
||||||
|
|
||||||
let channel2 = EmbeddedChannel()
|
let channel2 = EmbeddedChannel()
|
||||||
try channel2.pipeline.addHandler(HTTPResumableUploadHandler(context: context, handlers: [])).wait()
|
try channel2.pipeline.syncOperations.addHandler(HTTPResumableUploadHandler(context: context, handlers: []))
|
||||||
var request2 = HTTPRequest(method: .head, scheme: "https", authority: "example.com", path: resumptionPath)
|
var request2 = HTTPRequest(method: .head, scheme: "https", authority: "example.com", path: resumptionPath)
|
||||||
request2.headerFields[.uploadDraftInteropVersion] = "3"
|
request2.headerFields[.uploadDraftInteropVersion] = "3"
|
||||||
try channel2.writeInbound(HTTPRequestPart.head(request2))
|
try channel2.writeInbound(HTTPRequestPart.head(request2))
|
||||||
@ -450,7 +470,7 @@ final class NIOResumableUploadTests: XCTestCase {
|
|||||||
XCTAssertTrue(try channel2.finish().isClean)
|
XCTAssertTrue(try channel2.finish().isClean)
|
||||||
|
|
||||||
let channel3 = EmbeddedChannel()
|
let channel3 = EmbeddedChannel()
|
||||||
try channel3.pipeline.addHandler(HTTPResumableUploadHandler(context: context, handlers: [])).wait()
|
try channel3.pipeline.syncOperations.addHandler(HTTPResumableUploadHandler(context: context, handlers: []))
|
||||||
var request3 = HTTPRequest(method: .patch, scheme: "https", authority: "example.com", path: resumptionPath)
|
var request3 = HTTPRequest(method: .patch, scheme: "https", authority: "example.com", path: resumptionPath)
|
||||||
request3.headerFields[.uploadDraftInteropVersion] = "3"
|
request3.headerFields[.uploadDraftInteropVersion] = "3"
|
||||||
request3.headerFields[.uploadIncomplete] = "?0"
|
request3.headerFields[.uploadIncomplete] = "?0"
|
||||||
@ -476,7 +496,9 @@ final class NIOResumableUploadTests: XCTestCase {
|
|||||||
let recorder = InboundRecorder<HTTPRequestPart, Never>()
|
let recorder = InboundRecorder<HTTPRequestPart, Never>()
|
||||||
|
|
||||||
let context = HTTPResumableUploadContext(origin: "https://example.com")
|
let context = HTTPResumableUploadContext(origin: "https://example.com")
|
||||||
try channel.pipeline.addHandler(HTTPResumableUploadHandler(context: context, handlers: [recorder])).wait()
|
try channel.pipeline.syncOperations.addHandler(
|
||||||
|
HTTPResumableUploadHandler(context: context, handlers: [recorder])
|
||||||
|
)
|
||||||
|
|
||||||
var request = HTTPRequest(method: .post, scheme: "https", authority: "example.com", path: "/")
|
var request = HTTPRequest(method: .post, scheme: "https", authority: "example.com", path: "/")
|
||||||
request.headerFields[.uploadDraftInteropVersion] = "5"
|
request.headerFields[.uploadDraftInteropVersion] = "5"
|
||||||
@ -505,7 +527,7 @@ final class NIOResumableUploadTests: XCTestCase {
|
|||||||
XCTAssertEqual(try channel.readOutbound(as: HTTPResponsePart.self), .end(nil))
|
XCTAssertEqual(try channel.readOutbound(as: HTTPResponsePart.self), .end(nil))
|
||||||
|
|
||||||
let channel2 = EmbeddedChannel()
|
let channel2 = EmbeddedChannel()
|
||||||
try channel2.pipeline.addHandler(HTTPResumableUploadHandler(context: context, handlers: [])).wait()
|
try channel2.pipeline.syncOperations.addHandler(HTTPResumableUploadHandler(context: context, handlers: []))
|
||||||
var request2 = HTTPRequest(method: .head, scheme: "https", authority: "example.com", path: resumptionPath)
|
var request2 = HTTPRequest(method: .head, scheme: "https", authority: "example.com", path: resumptionPath)
|
||||||
request2.headerFields[.uploadDraftInteropVersion] = "5"
|
request2.headerFields[.uploadDraftInteropVersion] = "5"
|
||||||
try channel2.writeInbound(HTTPRequestPart.head(request2))
|
try channel2.writeInbound(HTTPRequestPart.head(request2))
|
||||||
@ -521,7 +543,7 @@ final class NIOResumableUploadTests: XCTestCase {
|
|||||||
XCTAssertTrue(try channel2.finish().isClean)
|
XCTAssertTrue(try channel2.finish().isClean)
|
||||||
|
|
||||||
let channel3 = EmbeddedChannel()
|
let channel3 = EmbeddedChannel()
|
||||||
try channel3.pipeline.addHandler(HTTPResumableUploadHandler(context: context, handlers: [])).wait()
|
try channel3.pipeline.syncOperations.addHandler(HTTPResumableUploadHandler(context: context, handlers: []))
|
||||||
var request3 = HTTPRequest(method: .patch, scheme: "https", authority: "example.com", path: resumptionPath)
|
var request3 = HTTPRequest(method: .patch, scheme: "https", authority: "example.com", path: resumptionPath)
|
||||||
request3.headerFields[.uploadDraftInteropVersion] = "5"
|
request3.headerFields[.uploadDraftInteropVersion] = "5"
|
||||||
request3.headerFields[.uploadComplete] = "?1"
|
request3.headerFields[.uploadComplete] = "?1"
|
||||||
@ -548,7 +570,9 @@ final class NIOResumableUploadTests: XCTestCase {
|
|||||||
let recorder = InboundRecorder<HTTPRequestPart, Never>()
|
let recorder = InboundRecorder<HTTPRequestPart, Never>()
|
||||||
|
|
||||||
let context = HTTPResumableUploadContext(origin: "https://example.com")
|
let context = HTTPResumableUploadContext(origin: "https://example.com")
|
||||||
try channel.pipeline.addHandler(HTTPResumableUploadHandler(context: context, handlers: [recorder])).wait()
|
try channel.pipeline.syncOperations.addHandler(
|
||||||
|
HTTPResumableUploadHandler(context: context, handlers: [recorder])
|
||||||
|
)
|
||||||
|
|
||||||
var request = HTTPRequest(method: .post, scheme: "https", authority: "example.com", path: "/")
|
var request = HTTPRequest(method: .post, scheme: "https", authority: "example.com", path: "/")
|
||||||
request.headerFields[.uploadDraftInteropVersion] = "6"
|
request.headerFields[.uploadDraftInteropVersion] = "6"
|
||||||
@ -577,7 +601,7 @@ final class NIOResumableUploadTests: XCTestCase {
|
|||||||
XCTAssertEqual(try channel.readOutbound(as: HTTPResponsePart.self), .end(nil))
|
XCTAssertEqual(try channel.readOutbound(as: HTTPResponsePart.self), .end(nil))
|
||||||
|
|
||||||
let channel2 = EmbeddedChannel()
|
let channel2 = EmbeddedChannel()
|
||||||
try channel2.pipeline.addHandler(HTTPResumableUploadHandler(context: context, handlers: [])).wait()
|
try channel2.pipeline.syncOperations.addHandler(HTTPResumableUploadHandler(context: context, handlers: []))
|
||||||
var request2 = HTTPRequest(method: .head, scheme: "https", authority: "example.com", path: resumptionPath)
|
var request2 = HTTPRequest(method: .head, scheme: "https", authority: "example.com", path: resumptionPath)
|
||||||
request2.headerFields[.uploadDraftInteropVersion] = "6"
|
request2.headerFields[.uploadDraftInteropVersion] = "6"
|
||||||
try channel2.writeInbound(HTTPRequestPart.head(request2))
|
try channel2.writeInbound(HTTPRequestPart.head(request2))
|
||||||
@ -593,7 +617,7 @@ final class NIOResumableUploadTests: XCTestCase {
|
|||||||
XCTAssertTrue(try channel2.finish().isClean)
|
XCTAssertTrue(try channel2.finish().isClean)
|
||||||
|
|
||||||
let channel3 = EmbeddedChannel()
|
let channel3 = EmbeddedChannel()
|
||||||
try channel3.pipeline.addHandler(HTTPResumableUploadHandler(context: context, handlers: [])).wait()
|
try channel3.pipeline.syncOperations.addHandler(HTTPResumableUploadHandler(context: context, handlers: []))
|
||||||
var request3 = HTTPRequest(method: .patch, scheme: "https", authority: "example.com", path: resumptionPath)
|
var request3 = HTTPRequest(method: .patch, scheme: "https", authority: "example.com", path: resumptionPath)
|
||||||
request3.headerFields[.uploadDraftInteropVersion] = "6"
|
request3.headerFields[.uploadDraftInteropVersion] = "6"
|
||||||
request3.headerFields[.uploadComplete] = "?1"
|
request3.headerFields[.uploadComplete] = "?1"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user