15
15
import Logging
16
16
import NIOCore
17
17
import NIOHTTP1
18
+ import ServiceContextModule
19
+ import Tracing
18
20
19
21
import struct Foundation. URL
20
22
@@ -34,18 +36,20 @@ extension HTTPClient {
34
36
public func execute(
35
37
_ request: HTTPClientRequest ,
36
38
deadline: NIODeadline ,
37
- logger: Logger ? = nil
39
+ logger: Logger ? = nil ,
40
+ context: ServiceContext ? = nil
38
41
) async throws -> HTTPClientResponse {
39
42
try await self . executeAndFollowRedirectsIfNeeded (
40
43
request,
41
44
deadline: deadline,
42
45
logger: logger ?? Self . loggingDisabled,
46
+ context: context ?? . current ?? . topLevel,
43
47
redirectState: RedirectState ( self . configuration. redirectConfiguration. mode, initialURL: request. url)
44
48
)
45
49
}
46
50
}
47
51
48
- // MARK: Connivence methods
52
+ // MARK: Convenience methods
49
53
50
54
@available ( macOS 10 . 15 , iOS 13 . 0 , watchOS 6 . 0 , tvOS 13 . 0 , * )
51
55
extension HTTPClient {
@@ -63,12 +67,14 @@ extension HTTPClient {
63
67
public func execute(
64
68
_ request: HTTPClientRequest ,
65
69
timeout: TimeAmount ,
66
- logger: Logger ? = nil
70
+ logger: Logger ? = nil ,
71
+ context: ServiceContext ? = nil
67
72
) async throws -> HTTPClientResponse {
68
73
try await self . execute (
69
74
request,
70
75
deadline: . now( ) + timeout,
71
- logger: logger
76
+ logger: logger,
77
+ context: context
72
78
)
73
79
}
74
80
}
@@ -81,6 +87,7 @@ extension HTTPClient {
81
87
_ request: HTTPClientRequest ,
82
88
deadline: NIODeadline ,
83
89
logger: Logger ,
90
+ context: ServiceContext ,
84
91
redirectState: RedirectState ?
85
92
) async throws -> HTTPClientResponse {
86
93
var currentRequest = request
@@ -91,7 +98,13 @@ extension HTTPClient {
91
98
while true {
92
99
let preparedRequest = try HTTPClientRequest . Prepared ( currentRequest, dnsOverride: configuration. dnsOverride)
93
100
let response = try await {
94
- var response = try await self . executeCancellable ( preparedRequest, deadline: deadline, logger: logger)
101
+ var response = try await self . executeCancellable (
102
+ preparedRequest,
103
+ deadline: deadline,
104
+ logger: logger,
105
+ context: context,
106
+ resendCount: history. isEmpty ? nil : history. count
107
+ )
95
108
96
109
history. append (
97
110
. init(
@@ -149,39 +162,68 @@ extension HTTPClient {
149
162
private func executeCancellable(
150
163
_ request: HTTPClientRequest . Prepared ,
151
164
deadline: NIODeadline ,
152
- logger: Logger
165
+ logger: Logger ,
166
+ context: ServiceContext ,
167
+ resendCount: Int ?
153
168
) async throws -> HTTPClientResponse {
154
169
let cancelHandler = TransactionCancelHandler ( )
155
170
156
- return try await withTaskCancellationHandler (
157
- operation: { ( ) async throws -> HTTPClientResponse in
158
- let eventLoop = self . eventLoopGroup. any ( )
159
- let deadlineTask = eventLoop. scheduleTask ( deadline: deadline) {
160
- cancelHandler. cancel ( reason: . deadlineExceeded)
161
- }
162
- defer {
163
- deadlineTask. cancel ( )
164
- }
165
- return try await withCheckedThrowingContinuation {
166
- ( continuation: CheckedContinuation < HTTPClientResponse , Swift . Error > ) -> Void in
167
- let transaction = Transaction (
168
- request: request,
169
- requestOptions: . fromClientConfiguration( self . configuration) ,
170
- logger: logger,
171
- connectionDeadline: . now( ) + ( self . configuration. timeout. connectionCreationTimeout) ,
172
- preferredEventLoop: eventLoop,
173
- responseContinuation: continuation
174
- )
171
+ return try await withSpan ( request. head. method. rawValue, context: context, ofKind: . client) { span in
172
+ var request = request
173
+ request. head. headers. propagate ( span. context)
174
+ span. updateAttributes { attributes in
175
+ attributes [ " http.request.method " ] = request. head. method. rawValue
176
+ attributes [ " server.address " ] = request. poolKey. connectionTarget. host
177
+ attributes [ " server.port " ] = request. poolKey. connectionTarget. port
178
+ attributes [ " url.full " ] = request. url. absoluteString
179
+ attributes [ " http.request.resend_count " ] = resendCount
180
+ }
181
+
182
+ do {
183
+ let response = try await withTaskCancellationHandler (
184
+ operation: { ( ) async throws -> HTTPClientResponse in
185
+ let eventLoop = self . eventLoopGroup. any ( )
186
+ let deadlineTask = eventLoop. scheduleTask ( deadline: deadline) {
187
+ cancelHandler. cancel ( reason: . deadlineExceeded)
188
+ }
189
+ defer {
190
+ deadlineTask. cancel ( )
191
+ }
192
+ let response = try await withCheckedThrowingContinuation {
193
+ ( continuation: CheckedContinuation < HTTPClientResponse , Swift . Error > ) -> Void in
194
+ let transaction = Transaction (
195
+ request: request,
196
+ requestOptions: . fromClientConfiguration( self . configuration) ,
197
+ logger: logger,
198
+ connectionDeadline: . now( ) + ( self . configuration. timeout. connectionCreationTimeout) ,
199
+ preferredEventLoop: eventLoop,
200
+ responseContinuation: continuation
201
+ )
175
202
176
- cancelHandler. registerTransaction ( transaction)
203
+ cancelHandler. registerTransaction ( transaction)
177
204
178
- self . poolManager. executeRequest ( transaction)
179
- }
180
- } ,
181
- onCancel: {
182
- cancelHandler. cancel ( reason: . taskCanceled)
205
+ self . poolManager. executeRequest ( transaction)
206
+ }
207
+ if response. status. code >= 400 {
208
+ span. setStatus ( . init( code: . error) )
209
+ span. attributes [ " error.type " ] = " \( response. status. code) "
210
+ }
211
+ span. attributes [ " http.response.status_code " ] = " \( response. status. code) "
212
+ return response
213
+ } ,
214
+ onCancel: {
215
+ span. setStatus ( . init( code: . error) )
216
+ span. attributes [ " error.type " ] = " \( CancellationError . self) "
217
+ cancelHandler. cancel ( reason: . taskCanceled)
218
+ }
219
+ )
220
+ return response
221
+ } catch {
222
+ span. setStatus ( . init( code: . error) )
223
+ span. attributes [ " error.type " ] = " \( type ( of: error) ) "
224
+ throw error
183
225
}
184
- )
226
+ }
185
227
}
186
228
}
187
229
0 commit comments