Skip to content

Commit a244717

Browse files
committed
add request/response history to async/await HTTPClientResponse
1 parent 80cdab5 commit a244717

File tree

3 files changed

+59
-5
lines changed

3 files changed

+59
-5
lines changed

Sources/AsyncHTTPClient/AsyncAwait/HTTPClient+execute.swift

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,29 @@ extension HTTPClient {
8585
) async throws -> HTTPClientResponse {
8686
var currentRequest = request
8787
var currentRedirectState = redirectState
88+
var history: [HTTPClientRequestResponse] = []
8889

8990
// this loop is there to follow potential redirects
9091
while true {
9192
let preparedRequest = try HTTPClientRequest.Prepared(currentRequest, dnsOverride: configuration.dnsOverride)
92-
let response = try await self.executeCancellable(preparedRequest, deadline: deadline, logger: logger)
93+
let response = try await {
94+
var response = try await self.executeCancellable(preparedRequest, deadline: deadline, logger: logger)
95+
96+
history.append(
97+
.init(
98+
request: currentRequest,
99+
responseHead: .init(
100+
version: response.version,
101+
status: response.status,
102+
headers: response.headers
103+
)
104+
)
105+
)
106+
107+
response.history = history
108+
109+
return response
110+
}()
93111

94112
guard var redirectState = currentRedirectState else {
95113
// a `nil` redirectState means we should not follow redirects

Sources/AsyncHTTPClient/AsyncAwait/HTTPClientResponse.swift

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
import NIOCore
1616
import NIOHTTP1
1717

18+
import struct Foundation.URL
19+
1820
/// A representation of an HTTP response for the Swift Concurrency HTTPClient API.
1921
///
2022
/// This object is similar to ``HTTPClient/Response``, but used for the Swift Concurrency API.
@@ -32,24 +34,39 @@ public struct HTTPClientResponse: Sendable {
3234
/// The body of this HTTP response.
3335
public var body: Body
3436

37+
/// The history of all requests and responses in redirect order.
38+
public var history: [HTTPClientRequestResponse]
39+
40+
/// The target URL (after redirects) of the response.
41+
public var url: URL? {
42+
guard let lastRequestURL = self.history.last?.request.url else {
43+
return nil
44+
}
45+
46+
return URL(string: lastRequestURL)
47+
}
48+
3549
@inlinable public init(
3650
version: HTTPVersion = .http1_1,
3751
status: HTTPResponseStatus = .ok,
3852
headers: HTTPHeaders = [:],
39-
body: Body = Body()
53+
body: Body = Body(),
54+
history: [HTTPClientRequestResponse] = []
4055
) {
4156
self.version = version
4257
self.status = status
4358
self.headers = headers
4459
self.body = body
60+
self.history = history
4561
}
4662

4763
init(
4864
requestMethod: HTTPMethod,
4965
version: HTTPVersion,
5066
status: HTTPResponseStatus,
5167
headers: HTTPHeaders,
52-
body: TransactionBody
68+
body: TransactionBody,
69+
history: [HTTPClientRequestResponse] = []
5370
) {
5471
self.init(
5572
version: version,
@@ -64,11 +81,23 @@ public struct HTTPClientResponse: Sendable {
6481
status: status
6582
)
6683
)
67-
)
84+
),
85+
history: history
6886
)
6987
}
7088
}
7189

90+
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
91+
public struct HTTPClientRequestResponse: Sendable {
92+
public var request: HTTPClientRequest
93+
public var responseHead: HTTPResponseHead
94+
95+
public init(request: HTTPClientRequest, responseHead: HTTPResponseHead) {
96+
self.request = request
97+
self.responseHead = responseHead
98+
}
99+
}
100+
72101
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
73102
extension HTTPClientResponse {
74103
/// A representation of the response body for an HTTP response.

Tests/AsyncHTTPClientTests/AsyncAwaitEndToEndTests.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ final class AsyncAwaitEndToEndTests: XCTestCase {
7676
return
7777
}
7878

79+
XCTAssertEqual(response.url?.absoluteString, request.url)
80+
XCTAssertEqual(response.history.map(\.request.url), [request.url])
7981
XCTAssertEqual(response.status, .ok)
8082
XCTAssertEqual(response.version, .http2)
8183
}
@@ -98,6 +100,8 @@ final class AsyncAwaitEndToEndTests: XCTestCase {
98100
return
99101
}
100102

103+
XCTAssertEqual(response.url?.absoluteString, request.url)
104+
XCTAssertEqual(response.history.map(\.request.url), [request.url])
101105
XCTAssertEqual(response.status, .ok)
102106
XCTAssertEqual(response.version, .http2)
103107
}
@@ -734,9 +738,10 @@ final class AsyncAwaitEndToEndTests: XCTestCase {
734738
defer { XCTAssertNoThrow(try client.syncShutdown()) }
735739
let logger = Logger(label: "HTTPClient", factory: StreamLogHandler.standardOutput(label:))
736740
var request = HTTPClientRequest(url: "https://127.0.0.1:\(bin.port)/redirect/target")
741+
let redirectURL = "https://localhost:\(bin.port)/echohostheader"
737742
request.headers.replaceOrAdd(
738743
name: "X-Target-Redirect-URL",
739-
value: "https://localhost:\(bin.port)/echohostheader"
744+
value: redirectURL
740745
)
741746

742747
guard
@@ -753,6 +758,8 @@ final class AsyncAwaitEndToEndTests: XCTestCase {
753758
XCTAssertNoThrow(maybeRequestInfo = try JSONDecoder().decode(RequestInfo.self, from: body))
754759
guard let requestInfo = maybeRequestInfo else { return }
755760

761+
XCTAssertEqual(response.url?.absoluteString, redirectURL)
762+
XCTAssertEqual(response.history.map(\.request.url), [request.url, redirectURL])
756763
XCTAssertEqual(response.status, .ok)
757764
XCTAssertEqual(response.version, .http2)
758765
XCTAssertEqual(requestInfo.data, "localhost:\(bin.port)")

0 commit comments

Comments
 (0)