Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit ac2025f

Browse files
authoredMar 7, 2025··
Merge branch 'main' into update_manifest_swiftSettings_targets
2 parents cefdcbd + 31122ea commit ac2025f

11 files changed

+263
-33
lines changed
 

‎.github/workflows/main.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,5 @@ jobs:
1414
linux_5_9_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error"
1515
linux_5_10_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error"
1616
linux_6_0_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable"
17-
linux_nightly_6_1_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable"
17+
linux_nightly_next_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable"
1818
linux_nightly_main_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable"

‎.github/workflows/pull_request.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
linux_5_9_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error"
1818
linux_5_10_arguments_override: "-Xswiftc -warnings-as-errors --explicit-target-dependency-import-check error"
1919
linux_6_0_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable"
20-
linux_nightly_6_1_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable"
20+
linux_nightly_next_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable"
2121
linux_nightly_main_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable"
2222

2323
cxx-interop:

‎Sources/AsyncHTTPClient/AsyncAwait/HTTPClient+execute.swift

+19-1
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

+44-2
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,6 +34,18 @@ 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,
@@ -42,14 +56,30 @@ public struct HTTPClientResponse: Sendable {
4256
self.status = status
4357
self.headers = headers
4458
self.body = body
59+
self.history = []
60+
}
61+
62+
@inlinable public init(
63+
version: HTTPVersion = .http1_1,
64+
status: HTTPResponseStatus = .ok,
65+
headers: HTTPHeaders = [:],
66+
body: Body = Body(),
67+
history: [HTTPClientRequestResponse] = []
68+
) {
69+
self.version = version
70+
self.status = status
71+
self.headers = headers
72+
self.body = body
73+
self.history = history
4574
}
4675

4776
init(
4877
requestMethod: HTTPMethod,
4978
version: HTTPVersion,
5079
status: HTTPResponseStatus,
5180
headers: HTTPHeaders,
52-
body: TransactionBody
81+
body: TransactionBody,
82+
history: [HTTPClientRequestResponse]
5383
) {
5484
self.init(
5585
version: version,
@@ -64,11 +94,23 @@ public struct HTTPClientResponse: Sendable {
6494
status: status
6595
)
6696
)
67-
)
97+
),
98+
history: history
6899
)
69100
}
70101
}
71102

103+
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
104+
public struct HTTPClientRequestResponse: Sendable {
105+
public var request: HTTPClientRequest
106+
public var responseHead: HTTPResponseHead
107+
108+
public init(request: HTTPClientRequest, responseHead: HTTPResponseHead) {
109+
self.request = request
110+
self.responseHead = responseHead
111+
}
112+
}
113+
72114
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
73115
extension HTTPClientResponse {
74116
/// A representation of the response body for an HTTP response.

‎Sources/AsyncHTTPClient/AsyncAwait/Transaction.swift

+2-1
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,8 @@ extension Transaction: HTTPExecutableRequest {
242242
version: head.version,
243243
status: head.status,
244244
headers: head.headers,
245-
body: body
245+
body: body,
246+
history: []
246247
)
247248
continuation.resume(returning: response)
248249
}

‎Sources/AsyncHTTPClient/FileDownloadDelegate.swift

+16-2
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,25 @@ import NIOCore
1616
import NIOHTTP1
1717
import NIOPosix
1818

19+
import struct Foundation.URL
20+
1921
/// Handles a streaming download to a given file path, allowing headers and progress to be reported.
2022
public final class FileDownloadDelegate: HTTPClientResponseDelegate {
2123
/// The response type for this delegate: the total count of bytes as reported by the response
22-
/// "Content-Length" header (if available), the count of bytes downloaded, and the
23-
/// response head.
24+
/// "Content-Length" header (if available), the count of bytes downloaded, the
25+
/// response head, and a history of requests and responses.
2426
public struct Progress: Sendable {
2527
public var totalBytes: Int?
2628
public var receivedBytes: Int
2729

30+
/// The history of all requests and responses in redirect order.
31+
public var history: [HTTPClient.RequestResponse] = []
32+
33+
/// The target URL (after redirects) of the response.
34+
public var url: URL? {
35+
self.history.last?.request.url
36+
}
37+
2838
public var head: HTTPResponseHead {
2939
get {
3040
assert(self._head != nil)
@@ -150,6 +160,10 @@ public final class FileDownloadDelegate: HTTPClientResponseDelegate {
150160
)
151161
}
152162

163+
public func didVisitURL(task: HTTPClient.Task<Progress>, _ request: HTTPClient.Request, _ head: HTTPResponseHead) {
164+
self.progress.history.append(.init(request: request, responseHead: head))
165+
}
166+
153167
public func didReceiveHead(
154168
task: HTTPClient.Task<Response>,
155169
_ head: HTTPResponseHead

‎Sources/AsyncHTTPClient/HTTPHandler.swift

+80-6
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,22 @@
1313
//===----------------------------------------------------------------------===//
1414

1515
import Algorithms
16-
import Foundation
1716
import Logging
1817
import NIOConcurrencyHelpers
1918
import NIOCore
2019
import NIOHTTP1
2120
import NIOPosix
2221
import NIOSSL
2322

23+
#if compiler(>=6.0)
24+
import Foundation
25+
#else
26+
@preconcurrency import Foundation
27+
#endif
28+
2429
extension HTTPClient {
2530
/// A request body.
26-
public struct Body {
31+
public struct Body: Sendable {
2732
/// A streaming uploader.
2833
///
2934
/// ``StreamWriter`` abstracts
@@ -209,7 +214,7 @@ extension HTTPClient {
209214
}
210215

211216
/// Represents an HTTP request.
212-
public struct Request {
217+
public struct Request: Sendable {
213218
/// Request HTTP method, defaults to `GET`.
214219
public let method: HTTPMethod
215220
/// Remote URL.
@@ -377,6 +382,13 @@ extension HTTPClient {
377382
public var headers: HTTPHeaders
378383
/// Response body.
379384
public var body: ByteBuffer?
385+
/// The history of all requests and responses in redirect order.
386+
public var history: [RequestResponse]
387+
388+
/// The target URL (after redirects) of the response.
389+
public var url: URL? {
390+
self.history.last?.request.url
391+
}
380392

381393
/// Create HTTP `Response`.
382394
///
@@ -392,6 +404,7 @@ extension HTTPClient {
392404
self.version = HTTPVersion(major: 1, minor: 1)
393405
self.headers = headers
394406
self.body = body
407+
self.history = []
395408
}
396409

397410
/// Create HTTP `Response`.
@@ -414,6 +427,32 @@ extension HTTPClient {
414427
self.version = version
415428
self.headers = headers
416429
self.body = body
430+
self.history = []
431+
}
432+
433+
/// Create HTTP `Response`.
434+
///
435+
/// - parameters:
436+
/// - host: Remote host of the request.
437+
/// - status: Response HTTP status.
438+
/// - version: Response HTTP version.
439+
/// - headers: Reponse HTTP headers.
440+
/// - body: Response body.
441+
/// - history: History of all requests and responses in redirect order.
442+
public init(
443+
host: String,
444+
status: HTTPResponseStatus,
445+
version: HTTPVersion,
446+
headers: HTTPHeaders,
447+
body: ByteBuffer?,
448+
history: [RequestResponse]
449+
) {
450+
self.host = host
451+
self.status = status
452+
self.version = version
453+
self.headers = headers
454+
self.body = body
455+
self.history = history
417456
}
418457
}
419458

@@ -457,6 +496,16 @@ extension HTTPClient {
457496
}
458497
}
459498
}
499+
500+
public struct RequestResponse: Sendable {
501+
public var request: Request
502+
public var responseHead: HTTPResponseHead
503+
504+
public init(request: Request, responseHead: HTTPResponseHead) {
505+
self.request = request
506+
self.responseHead = responseHead
507+
}
508+
}
460509
}
461510

462511
/// The default ``HTTPClientResponseDelegate``.
@@ -485,6 +534,7 @@ public final class ResponseAccumulator: HTTPClientResponseDelegate {
485534
}
486535
}
487536

537+
var history = [HTTPClient.RequestResponse]()
488538
var state = State.idle
489539
let requestMethod: HTTPMethod
490540
let requestHost: String
@@ -521,6 +571,14 @@ public final class ResponseAccumulator: HTTPClientResponseDelegate {
521571
self.maxBodySize = maxBodySize
522572
}
523573

574+
public func didVisitURL(
575+
task: HTTPClient.Task<HTTPClient.Response>,
576+
_ request: HTTPClient.Request,
577+
_ head: HTTPResponseHead
578+
) {
579+
self.history.append(.init(request: request, responseHead: head))
580+
}
581+
524582
public func didReceiveHead(task: HTTPClient.Task<Response>, _ head: HTTPResponseHead) -> EventLoopFuture<Void> {
525583
switch self.state {
526584
case .idle:
@@ -596,15 +654,17 @@ public final class ResponseAccumulator: HTTPClientResponseDelegate {
596654
status: head.status,
597655
version: head.version,
598656
headers: head.headers,
599-
body: nil
657+
body: nil,
658+
history: self.history
600659
)
601660
case .body(let head, let body):
602661
return Response(
603662
host: self.requestHost,
604663
status: head.status,
605664
version: head.version,
606665
headers: head.headers,
607-
body: body
666+
body: body,
667+
history: self.history
608668
)
609669
case .end:
610670
preconditionFailure("request already processed")
@@ -668,7 +728,16 @@ public protocol HTTPClientResponseDelegate: AnyObject {
668728
/// - task: Current request context.
669729
func didSendRequest(task: HTTPClient.Task<Response>)
670730

671-
/// Called when response head is received. Will be called once.
731+
/// Called each time a response head is received (including redirects), and always called before ``HTTPClientResponseDelegate/didReceiveHead(task:_:)-9r4xd``.
732+
/// You can use this method to keep an entire history of the request/response chain.
733+
///
734+
/// - parameters:
735+
/// - task: Current request context.
736+
/// - request: The request that was sent.
737+
/// - head: Received response head.
738+
func didVisitURL(task: HTTPClient.Task<Response>, _ request: HTTPClient.Request, _ head: HTTPResponseHead)
739+
740+
/// Called when the final response head is received (after redirects).
672741
/// You must return an `EventLoopFuture<Void>` that you complete when you have finished processing the body part.
673742
/// You can create an already succeeded future by calling `task.eventLoop.makeSucceededFuture(())`.
674743
///
@@ -734,6 +803,11 @@ extension HTTPClientResponseDelegate {
734803
/// By default, this does nothing.
735804
public func didSendRequest(task: HTTPClient.Task<Response>) {}
736805

806+
/// Default implementation of ``HTTPClientResponseDelegate/didVisitURL(task:_:_:)-2el9y``.
807+
///
808+
/// By default, this does nothing.
809+
public func didVisitURL(task: HTTPClient.Task<Response>, _: HTTPClient.Request, _: HTTPResponseHead) {}
810+
737811
/// Default implementation of ``HTTPClientResponseDelegate/didReceiveHead(task:_:)-9r4xd``.
738812
///
739813
/// By default, this does nothing.

‎Sources/AsyncHTTPClient/RequestBag.swift

+2
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,8 @@ final class RequestBag<Delegate: HTTPClientResponseDelegate> {
228228
private func receiveResponseHead0(_ head: HTTPResponseHead) {
229229
self.task.eventLoop.assertInEventLoop()
230230

231+
self.delegate.didVisitURL(task: self.task, self.request, head)
232+
231233
// runs most likely on channel eventLoop
232234
switch self.state.receiveResponseHead(head) {
233235
case .none:

0 commit comments

Comments
 (0)
Please sign in to comment.