1313//===----------------------------------------------------------------------===//
1414
1515import Algorithms
16- import Foundation
1716import Logging
1817import NIOConcurrencyHelpers
1918import NIOCore
2019import NIOHTTP1
2120import NIOPosix
2221import NIOSSL
2322
23+ #if compiler(>=6.0)
24+ import Foundation
25+ #else
26+ @preconcurrency import Foundation
27+ #endif
28+
2429extension 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.
0 commit comments