diff --git a/Package.swift b/Package.swift index b77b90b1..38b0ade8 100644 --- a/Package.swift +++ b/Package.swift @@ -323,7 +323,7 @@ let package = Package( .library(name: "NIOCertificateHelpers", targets: ["NIOCertificateHelpers"]), ], dependencies: [ - .package(url: "https://github.com/apple/swift-nio.git", from: "2.81.0"), + .package(url: "https://github.com/apple/swift-nio.git", from: "2.94.0"), .package(url: "https://github.com/apple/swift-nio-http2.git", from: "1.27.0"), .package(url: "https://github.com/apple/swift-http-types.git", from: "1.3.0"), .package(url: "https://github.com/apple/swift-http-structured-headers.git", from: "1.2.0"), diff --git a/Sources/NIOResumableUpload/HTTPResumableUploadChannel.swift b/Sources/NIOResumableUpload/HTTPResumableUploadChannel.swift index 73a28ed1..434c3418 100644 --- a/Sources/NIOResumableUpload/HTTPResumableUploadChannel.swift +++ b/Sources/NIOResumableUpload/HTTPResumableUploadChannel.swift @@ -77,9 +77,9 @@ final class HTTPResumableUploadChannel: Channel, ChannelCore, @unchecked Sendabl self.allocator = parent.allocator self.closePromise = parent.eventLoop.makePromise() self.eventLoop = parent.eventLoop - // Only support Channels that implement sync options - let autoRead = try! parent.syncOptions!.getOption(ChannelOptions.autoRead) - self.autoRead = NIOLoopBound(autoRead, eventLoop: eventLoop) + // Only support Channels that implement sync options, but catch errors if e.g. parent is closed already. + let autoRead = try? parent.syncOptions!.getOption(ChannelOptions.autoRead) + self.autoRead = NIOLoopBound(autoRead ?? false, eventLoop: eventLoop) self._pipeline = ChannelPipeline(channel: self) channelConfigurator(self) } diff --git a/Tests/NIOResumableUploadTests/NIOResumableUploadTests.swift b/Tests/NIOResumableUploadTests/NIOResumableUploadTests.swift index 8c1df99a..ed766d49 100644 --- a/Tests/NIOResumableUploadTests/NIOResumableUploadTests.swift +++ b/Tests/NIOResumableUploadTests/NIOResumableUploadTests.swift @@ -639,6 +639,35 @@ final class NIOResumableUploadTests: XCTestCase { XCTAssertTrue(try channel3.finish().isClean) XCTAssertTrue(try channel.finish().isClean) } + + func testChannelInitHandlesParentThrowingOnOptionsRead() throws { + let channel = EmbeddedChannel() + let recorder = InboundRecorder() + + // Close the channel before we even get going. + try channel.close().wait() + + // Opt into the throwing behaviour (similar to socket-based channels). + channel.allowOptionsWhenClosed = false + + let context = HTTPResumableUploadContext(origin: "https://example.com") + try channel.pipeline.syncOperations.addHandler( + HTTPResumableUploadHandler(context: context, handlers: [recorder]) + ) + + // Send a request. + let request = HTTPRequest(method: .get, scheme: "https", authority: "example.com", path: "/") + try channel.writeInbound(HTTPRequestPart.head(request)) + try channel.writeInbound(HTTPRequestPart.end(nil)) + + // Check recorder was called, which means the outer channel handler was created and called. + XCTAssertEqual(recorder.receivedFrames.count, 2) + XCTAssertEqual(recorder.receivedFrames[0], HTTPRequestPart.head(request)) + XCTAssertEqual(recorder.receivedFrames[1], HTTPRequestPart.end(nil)) + + // Tolerate the channel being already closed -- we closed it :) + XCTAssertTrue(try channel.finish(acceptAlreadyClosed: true).isClean) + } } extension HTTPField.Name {