diff --git a/Sources/HTTPClient/URLSession/URLSessionRequestStreamBridge.swift b/Sources/HTTPClient/URLSession/URLSessionRequestStreamBridge.swift index a7e3152..731295f 100644 --- a/Sources/HTTPClient/URLSession/URLSessionRequestStreamBridge.swift +++ b/Sources/HTTPClient/URLSession/URLSessionRequestStreamBridge.swift @@ -105,8 +105,19 @@ final class URLSessionRequestStreamBridge: NSObject, StreamDelegate, Sendable { } } - func close() { + func close(trailerFields: HTTPFields?) { self.lockedState.withLock { state in + if let trailerFields { + var trailerNames: Set = [] + for field in trailerFields { + trailerNames.insert(field.name) + } + var trailerDictionary: [String: String] = [:] + for name in trailerNames { + trailerDictionary[name.rawName] = trailerFields[name] + } + state.inputStream.setProperty(trailerDictionary, forKey: .init("_kCFStreamPropertyHTTPTrailer")) + } state.outputStream.close() } } diff --git a/Sources/HTTPClient/URLSession/URLSessionTaskDelegateBridge+ConcludingAsyncReader.swift b/Sources/HTTPClient/URLSession/URLSessionTaskDelegateBridge+ConcludingAsyncReader.swift index ad6db14..de5fa2c 100644 --- a/Sources/HTTPClient/URLSession/URLSessionTaskDelegateBridge+ConcludingAsyncReader.swift +++ b/Sources/HTTPClient/URLSession/URLSessionTaskDelegateBridge+ConcludingAsyncReader.swift @@ -21,7 +21,7 @@ extension URLSessionTaskDelegateBridge: ConcludingAsyncReader { func consumeAndConclude( body: (consuming sending URLSessionTaskDelegateBridge) async throws(Failure) -> Return ) async throws(Failure) -> (Return, HTTPFields?) { - try await (body(self), nil) + try await (body(self), self.responseTrailerFields) } } diff --git a/Sources/HTTPClient/URLSession/URLSessionTaskDelegateBridge.swift b/Sources/HTTPClient/URLSession/URLSessionTaskDelegateBridge.swift index 1c90c7e..c1d2990 100644 --- a/Sources/HTTPClient/URLSession/URLSessionTaskDelegateBridge.swift +++ b/Sources/HTTPClient/URLSession/URLSessionTaskDelegateBridge.swift @@ -74,10 +74,15 @@ final class URLSessionTaskDelegateBridge: NSObject, Sendable, URLSessionDataDele } var state: State = .awaitingResponse var completionContinuation: CheckedContinuation? = nil + var responseTrailerFields: HTTPFields? } private let state: Mutex = .init(.init()) + var responseTrailerFields: HTTPFields? { + self.state.withLock { $0.responseTrailerFields } + } + func urlSession( _ session: URLSession, dataTask: URLSessionDataTask, @@ -153,6 +158,15 @@ final class URLSessionTaskDelegateBridge: NSObject, Sendable, URLSessionDataDele state.state = .awaitingConsumption(existingData, complete: true, error: error, suspendedTask: nil) } state.completionContinuation = nil + if let trailerDictionary = task.value(forKey: "_trailers") as? [String: String] { + var trailerFields = HTTPFields() + for (name, value) in trailerDictionary { + if let name = HTTPField.Name(name) { + trailerFields.append(.init(name: name, value: value)) + } + } + state.responseTrailerFields = trailerFields + } } return state } @@ -247,15 +261,16 @@ final class URLSessionTaskDelegateBridge: NSObject, Sendable, URLSessionDataDele let bridge = URLSessionRequestStreamBridge(task: task) completionHandler(bridge.inputStream) do { - _ = try await requestBody.produce(into: bridge) + let trailerFields = try await requestBody.produce(into: bridge) + bridge.close(trailerFields: trailerFields) } catch { if bridge.writeFailed { // Ignore error } else { self.requestBodyStreamFailed(with: error) } + bridge.close(trailerFields: nil) } - bridge.close() } } } @@ -277,15 +292,16 @@ final class URLSessionTaskDelegateBridge: NSObject, Sendable, URLSessionDataDele let bridge = URLSessionRequestStreamBridge(task: task) completionHandler(bridge.inputStream) do { - _ = try await requestBody.produce(offset: offset, into: bridge) + let trailerFields = try await requestBody.produce(offset: offset, into: bridge) + bridge.close(trailerFields: trailerFields) } catch { if bridge.writeFailed { // Ignore error } else { self.requestBodyStreamFailed(with: error) } + bridge.close(trailerFields: nil) } - bridge.close() } } }