Skip to content

Commit dc1b10e

Browse files
committed
add request/response history to HTTPClient.Response, and worry about "Sendability"
1 parent da621ce commit dc1b10e

File tree

2 files changed

+91
-3
lines changed

2 files changed

+91
-3
lines changed

Sources/AsyncHTTPClient/HTTPHandler.swift

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,13 @@ extension HTTPClient {
377377
public var headers: HTTPHeaders
378378
/// Response body.
379379
public var body: ByteBuffer?
380+
/// The history of all requests and responses in redirect order.
381+
public var history: [RequestResponse]
382+
383+
/// The target URL (after redirects) of the response.
384+
public var url: URL? {
385+
self.history.last?.request.url
386+
}
380387

381388
/// Create HTTP `Response`.
382389
///
@@ -392,6 +399,7 @@ extension HTTPClient {
392399
self.version = HTTPVersion(major: 1, minor: 1)
393400
self.headers = headers
394401
self.body = body
402+
self.history = []
395403
}
396404

397405
/// Create HTTP `Response`.
@@ -402,18 +410,21 @@ extension HTTPClient {
402410
/// - version: Response HTTP version.
403411
/// - headers: Reponse HTTP headers.
404412
/// - body: Response body.
413+
/// - history: History of all requests and responses in redirect order.
405414
public init(
406415
host: String,
407416
status: HTTPResponseStatus,
408417
version: HTTPVersion,
409418
headers: HTTPHeaders,
410-
body: ByteBuffer?
419+
body: ByteBuffer?,
420+
history: [RequestResponse]
411421
) {
412422
self.host = host
413423
self.status = status
414424
self.version = version
415425
self.headers = headers
416426
self.body = body
427+
self.history = history
417428
}
418429
}
419430

@@ -457,6 +468,16 @@ extension HTTPClient {
457468
}
458469
}
459470
}
471+
472+
public struct RequestResponse {
473+
public var request: Request
474+
public var responseHead: HTTPResponseHead
475+
476+
public init(request: Request, responseHead: HTTPResponseHead) {
477+
self.request = request
478+
self.responseHead = responseHead
479+
}
480+
}
460481
}
461482

462483
/// The default ``HTTPClientResponseDelegate``.
@@ -485,6 +506,7 @@ public final class ResponseAccumulator: HTTPClientResponseDelegate {
485506
}
486507
}
487508

509+
var history = [HTTPClient.RequestResponse]()
488510
var state = State.idle
489511
let requestMethod: HTTPMethod
490512
let requestHost: String
@@ -521,6 +543,14 @@ public final class ResponseAccumulator: HTTPClientResponseDelegate {
521543
self.maxBodySize = maxBodySize
522544
}
523545

546+
public func didVisitURL(
547+
task: HTTPClient.Task<HTTPClient.Response>,
548+
_ request: HTTPClient.Request,
549+
_ head: HTTPResponseHead
550+
) {
551+
self.history.append(.init(request: request, responseHead: head))
552+
}
553+
524554
public func didReceiveHead(task: HTTPClient.Task<Response>, _ head: HTTPResponseHead) -> EventLoopFuture<Void> {
525555
switch self.state {
526556
case .idle:
@@ -596,15 +626,17 @@ public final class ResponseAccumulator: HTTPClientResponseDelegate {
596626
status: head.status,
597627
version: head.version,
598628
headers: head.headers,
599-
body: nil
629+
body: nil,
630+
history: self.history
600631
)
601632
case .body(let head, let body):
602633
return Response(
603634
host: self.requestHost,
604635
status: head.status,
605636
version: head.version,
606637
headers: head.headers,
607-
body: body
638+
body: body,
639+
history: self.history
608640
)
609641
case .end:
610642
preconditionFailure("request already processed")

Tests/AsyncHTTPClientTests/HTTPClientTests.swift

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,14 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass {
469469

470470
var response = try localClient.get(url: self.defaultHTTPBinURLPrefix + "redirect/302").wait()
471471
XCTAssertEqual(response.status, .ok)
472+
XCTAssertEqual(response.url?.absoluteString, self.defaultHTTPBinURLPrefix + "ok")
473+
XCTAssertEqual(
474+
response.history.map(\.request.url.absoluteString),
475+
[
476+
self.defaultHTTPBinURLPrefix + "redirect/302",
477+
self.defaultHTTPBinURLPrefix + "ok",
478+
]
479+
)
472480

473481
response = try localClient.get(url: self.defaultHTTPBinURLPrefix + "redirect/https?port=\(httpsBin.port)")
474482
.wait()
@@ -501,6 +509,8 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass {
501509
var response = try localClient.execute(request: request).wait()
502510
XCTAssertEqual(response.status, .found)
503511
XCTAssertEqual(response.headers.first(name: "Location"), targetURL)
512+
XCTAssertEqual(response.url, request.url)
513+
XCTAssertEqual(response.history.map(\.request.url), [request.url])
504514

505515
request = try Request(
506516
url: "https://localhost:\(httpsBin.port)/redirect/target",
@@ -512,6 +522,8 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass {
512522
response = try localClient.execute(request: request).wait()
513523
XCTAssertEqual(response.status, .found)
514524
XCTAssertEqual(response.headers.first(name: "Location"), targetURL)
525+
XCTAssertEqual(response.url, request.url)
526+
XCTAssertEqual(response.history.map(\.request.url), [request.url])
515527

516528
// From HTTP or HTTPS to HTTPS+UNIX should also fail to redirect
517529
targetURL =
@@ -526,6 +538,8 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass {
526538
response = try localClient.execute(request: request).wait()
527539
XCTAssertEqual(response.status, .found)
528540
XCTAssertEqual(response.headers.first(name: "Location"), targetURL)
541+
XCTAssertEqual(response.url, request.url)
542+
XCTAssertEqual(response.history.map(\.request.url), [request.url])
529543

530544
request = try Request(
531545
url: "https://localhost:\(httpsBin.port)/redirect/target",
@@ -537,6 +551,8 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass {
537551
response = try localClient.execute(request: request).wait()
538552
XCTAssertEqual(response.status, .found)
539553
XCTAssertEqual(response.headers.first(name: "Location"), targetURL)
554+
XCTAssertEqual(response.url, request.url)
555+
XCTAssertEqual(response.history.map(\.request.url), [request.url])
540556

541557
// ... while HTTP+UNIX to HTTP, HTTPS, or HTTP(S)+UNIX should succeed
542558
targetURL = self.defaultHTTPBinURLPrefix + "ok"
@@ -550,6 +566,11 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass {
550566

551567
response = try localClient.execute(request: request).wait()
552568
XCTAssertEqual(response.status, .ok)
569+
XCTAssertEqual(response.url?.absoluteString, targetURL)
570+
XCTAssertEqual(
571+
response.history.map(\.request.url.absoluteString),
572+
[request.url.absoluteString, targetURL]
573+
)
553574

554575
targetURL = "https://localhost:\(httpsBin.port)/ok"
555576
request = try Request(
@@ -562,6 +583,11 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass {
562583

563584
response = try localClient.execute(request: request).wait()
564585
XCTAssertEqual(response.status, .ok)
586+
XCTAssertEqual(response.url?.absoluteString, targetURL)
587+
XCTAssertEqual(
588+
response.history.map(\.request.url.absoluteString),
589+
[request.url.absoluteString, targetURL]
590+
)
565591

566592
targetURL =
567593
"http+unix://\(httpSocketPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)/ok"
@@ -575,6 +601,11 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass {
575601

576602
response = try localClient.execute(request: request).wait()
577603
XCTAssertEqual(response.status, .ok)
604+
XCTAssertEqual(response.url?.absoluteString, targetURL)
605+
XCTAssertEqual(
606+
response.history.map(\.request.url.absoluteString),
607+
[request.url.absoluteString, targetURL]
608+
)
578609

579610
targetURL =
580611
"https+unix://\(httpsSocketPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)/ok"
@@ -588,6 +619,11 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass {
588619

589620
response = try localClient.execute(request: request).wait()
590621
XCTAssertEqual(response.status, .ok)
622+
XCTAssertEqual(response.url?.absoluteString, targetURL)
623+
XCTAssertEqual(
624+
response.history.map(\.request.url.absoluteString),
625+
[request.url.absoluteString, targetURL]
626+
)
591627

592628
// ... and HTTPS+UNIX to HTTP, HTTPS, or HTTP(S)+UNIX should succeed
593629
targetURL = self.defaultHTTPBinURLPrefix + "ok"
@@ -601,6 +637,11 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass {
601637

602638
response = try localClient.execute(request: request).wait()
603639
XCTAssertEqual(response.status, .ok)
640+
XCTAssertEqual(response.url?.absoluteString, targetURL)
641+
XCTAssertEqual(
642+
response.history.map(\.request.url.absoluteString),
643+
[request.url.absoluteString, targetURL]
644+
)
604645

605646
targetURL = "https://localhost:\(httpsBin.port)/ok"
606647
request = try Request(
@@ -613,6 +654,11 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass {
613654

614655
response = try localClient.execute(request: request).wait()
615656
XCTAssertEqual(response.status, .ok)
657+
XCTAssertEqual(response.url?.absoluteString, targetURL)
658+
XCTAssertEqual(
659+
response.history.map(\.request.url.absoluteString),
660+
[request.url.absoluteString, targetURL]
661+
)
616662

617663
targetURL =
618664
"http+unix://\(httpSocketPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)/ok"
@@ -626,6 +672,11 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass {
626672

627673
response = try localClient.execute(request: request).wait()
628674
XCTAssertEqual(response.status, .ok)
675+
XCTAssertEqual(response.url?.absoluteString, targetURL)
676+
XCTAssertEqual(
677+
response.history.map(\.request.url.absoluteString),
678+
[request.url.absoluteString, targetURL]
679+
)
629680

630681
targetURL =
631682
"https+unix://\(httpsSocketPath.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)/ok"
@@ -639,6 +690,11 @@ final class HTTPClientTests: XCTestCaseHTTPClientTestsBaseClass {
639690

640691
response = try localClient.execute(request: request).wait()
641692
XCTAssertEqual(response.status, .ok)
693+
XCTAssertEqual(response.url?.absoluteString, targetURL)
694+
XCTAssertEqual(
695+
response.history.map(\.request.url.absoluteString),
696+
[request.url.absoluteString, targetURL]
697+
)
642698
}
643699
)
644700
}

0 commit comments

Comments
 (0)