@@ -25,13 +25,24 @@ import CRT
25
25
#endif
26
26
27
27
public protocol HTTPClientProtocol {
28
- func execute( _ request: HTTPClientRequest , callback: @escaping ( Result < HTTPClientResponse , Error > ) -> Void )
28
+ typealias ProgressHandler = ( _ bytesReceived: Int64 , _ totalBytes: Int64 ? ) -> Void
29
+ typealias CompletionHandler = ( Result < HTTPClientResponse , Error > ) -> Void
30
+
31
+ /// Execute an HTTP request asynchronously
32
+ ///
33
+ /// - Parameters:
34
+ /// - request: The `HTTPClientRequest` to perform.
35
+ /// - callback: A closure to be notified of the completion of the request.
36
+ func execute( _ request: HTTPClientRequest ,
37
+ progress: ProgressHandler ? ,
38
+ completion: @escaping CompletionHandler )
29
39
}
30
40
31
41
public enum HTTPClientError : Error , Equatable {
32
42
case invalidResponse
33
43
case badResponseStatusCode( Int )
34
44
case circuitBreakerTriggered
45
+ case responseTooLarge( Int64 )
35
46
}
36
47
37
48
// MARK: - HTTPClient
@@ -40,7 +51,7 @@ public struct HTTPClient: HTTPClientProtocol {
40
51
public typealias Configuration = HTTPClientConfiguration
41
52
public typealias Request = HTTPClientRequest
42
53
public typealias Response = HTTPClientResponse
43
- public typealias Handler = ( Request , @escaping ( Result < Response , Error > ) -> Void ) -> Void
54
+ public typealias Handler = ( Request , ProgressHandler ? , @escaping ( Result < Response , Error > ) -> Void ) -> Void
44
55
45
56
public var configuration : HTTPClientConfiguration
46
57
private let diagnosticsEngine : DiagnosticsEngine ?
@@ -57,7 +68,7 @@ public struct HTTPClient: HTTPClientProtocol {
57
68
self . underlying = handler ?? URLSessionHTTPClient ( ) . execute
58
69
}
59
70
60
- public func execute( _ request: Request , callback : @escaping ( Result < Response , Error > ) -> Void ) {
71
+ public func execute( _ request: Request , progress : ProgressHandler ? = nil , completion : @escaping CompletionHandler ) {
61
72
// merge configuration
62
73
var request = request
63
74
if request. options. callbackQueue == nil {
@@ -72,6 +83,9 @@ public struct HTTPClient: HTTPClientProtocol {
72
83
if request. options. timeout == nil {
73
84
request. options. timeout = self . configuration. requestTimeout
74
85
}
86
+ if request. options. authorizationProvider == nil {
87
+ request. options. authorizationProvider = self . configuration. authorizationProvider
88
+ }
75
89
// add additional headers
76
90
if let additionalHeaders = self . configuration. requestHeaders {
77
91
additionalHeaders. forEach {
@@ -81,43 +95,68 @@ public struct HTTPClient: HTTPClientProtocol {
81
95
if request. options. addUserAgent, !request. headers. contains ( " User-Agent " ) {
82
96
request. headers. add ( name: " User-Agent " , value: " SwiftPackageManager/ \( SwiftVersion . currentVersion. displayString) " )
83
97
}
98
+ if let authorization = request. options. authorizationProvider ? ( request. url) , !request. headers. contains ( " Authorization " ) {
99
+ request. headers. add ( name: " Authorization " , value: authorization)
100
+ }
84
101
// execute
85
- self . _execute ( request: request, requestNumber: 0 ) { result in
86
- let callbackQueue = request. options. callbackQueue ?? self . configuration. callbackQueue
87
- callbackQueue. async {
88
- callback ( result)
102
+ let callbackQueue = request. options. callbackQueue ?? self . configuration. callbackQueue
103
+ self . _execute (
104
+ request: request, requestNumber: 0 ,
105
+ progress: progress. map { handler in
106
+ { received, expected in
107
+ callbackQueue. async {
108
+ handler ( received, expected)
109
+ }
110
+ }
111
+ } ,
112
+ completion: { result in
113
+ callbackQueue. async {
114
+ completion ( result)
115
+ }
89
116
}
90
- }
117
+ )
91
118
}
92
119
93
- private func _execute( request: Request , requestNumber: Int , callback : @escaping ( Result < Response , Error > ) -> Void ) {
120
+ private func _execute( request: Request , requestNumber: Int , progress : ProgressHandler ? , completion : @escaping CompletionHandler ) {
94
121
if self . shouldCircuitBreak ( request: request) {
95
122
diagnosticsEngine? . emit ( warning: " Circuit breaker triggered for \( request. url) " )
96
- return callback ( . failure( HTTPClientError . circuitBreakerTriggered) )
123
+ return completion ( . failure( HTTPClientError . circuitBreakerTriggered) )
97
124
}
98
125
99
- self . underlying ( request) { result in
100
- switch result {
101
- case . failure( let error) :
102
- callback ( . failure( error) )
103
- case . success( let response) :
104
- // record host errors for circuit breaker
105
- self . recordErrorIfNecessary ( response: response, request: request)
106
- // handle retry strategy
107
- if let retryDelay = self . shouldRetry ( response: response, request: request, requestNumber: requestNumber) {
108
- self . diagnosticsEngine? . emit ( warning: " \( request. url) failed, retrying in \( retryDelay) " )
109
- // TODO: dedicated retry queue?
110
- return self . configuration. callbackQueue. asyncAfter ( deadline: . now( ) + retryDelay) {
111
- self . _execute ( request: request, requestNumber: requestNumber + 1 , callback: callback)
126
+ self . underlying (
127
+ request,
128
+ { received, expected in
129
+ if let max = request. options. maximumResponseSizeInBytes {
130
+ guard received < max else {
131
+ // FIXME: cancel the request?
132
+ return completion ( . failure( HTTPClientError . responseTooLarge ( received) ) )
112
133
}
113
134
}
114
- // check for valid response codes
115
- if let validResponseCodes = request. options. validResponseCodes, !validResponseCodes. contains ( response. statusCode) {
116
- return callback ( . failure( HTTPClientError . badResponseStatusCode ( response. statusCode) ) )
135
+ progress ? ( received, expected)
136
+ } ,
137
+ { result in
138
+ switch result {
139
+ case . failure( let error) :
140
+ completion ( . failure( error) )
141
+ case . success( let response) :
142
+ // record host errors for circuit breaker
143
+ self . recordErrorIfNecessary ( response: response, request: request)
144
+ // handle retry strategy
145
+ if let retryDelay = self . shouldRetry ( response: response, request: request, requestNumber: requestNumber) {
146
+ self . diagnosticsEngine? . emit ( warning: " \( request. url) failed, retrying in \( retryDelay) " )
147
+ // TODO: dedicated retry queue?
148
+ return self . configuration. callbackQueue. asyncAfter ( deadline: . now( ) + retryDelay) {
149
+ self . _execute ( request: request, requestNumber: requestNumber + 1 , progress: progress, completion: completion)
150
+ }
151
+ }
152
+ // check for valid response codes
153
+ if let validResponseCodes = request. options. validResponseCodes, !validResponseCodes. contains ( response. statusCode) {
154
+ return completion ( . failure( HTTPClientError . badResponseStatusCode ( response. statusCode) ) )
155
+ }
156
+ completion ( . success( response) )
117
157
}
118
- callback ( . success( response) )
119
158
}
120
- }
159
+ )
121
160
}
122
161
123
162
private func shouldRetry( response: Response , request: Request , requestNumber: Int ) -> DispatchTimeInterval ? {
@@ -179,39 +218,43 @@ public struct HTTPClient: HTTPClientProtocol {
179
218
}
180
219
181
220
public extension HTTPClient {
182
- func head( _ url: URL , headers: HTTPClientHeaders = . init( ) , options: Request . Options = . init( ) , callback : @escaping ( Result < Response , Error > ) -> Void ) {
183
- self . execute ( Request ( method: . head, url: url, headers: headers, body: nil , options: options) , callback : callback )
221
+ func head( _ url: URL , headers: HTTPClientHeaders = . init( ) , options: Request . Options = . init( ) , completion : @escaping ( Result < Response , Error > ) -> Void ) {
222
+ self . execute ( Request ( method: . head, url: url, headers: headers, body: nil , options: options) , completion : completion )
184
223
}
185
224
186
- func get( _ url: URL , headers: HTTPClientHeaders = . init( ) , options: Request . Options = . init( ) , callback : @escaping ( Result < Response , Error > ) -> Void ) {
187
- self . execute ( Request ( method: . get, url: url, headers: headers, body: nil , options: options) , callback : callback )
225
+ func get( _ url: URL , headers: HTTPClientHeaders = . init( ) , options: Request . Options = . init( ) , completion : @escaping ( Result < Response , Error > ) -> Void ) {
226
+ self . execute ( Request ( method: . get, url: url, headers: headers, body: nil , options: options) , completion : completion )
188
227
}
189
228
190
- func put( _ url: URL , body: Data ? , headers: HTTPClientHeaders = . init( ) , options: Request . Options = . init( ) , callback : @escaping ( Result < Response , Error > ) -> Void ) {
191
- self . execute ( Request ( method: . put, url: url, headers: headers, body: body, options: options) , callback : callback )
229
+ func put( _ url: URL , body: Data ? , headers: HTTPClientHeaders = . init( ) , options: Request . Options = . init( ) , completion : @escaping ( Result < Response , Error > ) -> Void ) {
230
+ self . execute ( Request ( method: . put, url: url, headers: headers, body: body, options: options) , completion : completion )
192
231
}
193
232
194
- func post( _ url: URL , body: Data ? , headers: HTTPClientHeaders = . init( ) , options: Request . Options = . init( ) , callback : @escaping ( Result < Response , Error > ) -> Void ) {
195
- self . execute ( Request ( method: . post, url: url, headers: headers, body: body, options: options) , callback : callback )
233
+ func post( _ url: URL , body: Data ? , headers: HTTPClientHeaders = . init( ) , options: Request . Options = . init( ) , completion : @escaping ( Result < Response , Error > ) -> Void ) {
234
+ self . execute ( Request ( method: . post, url: url, headers: headers, body: body, options: options) , completion : completion )
196
235
}
197
236
198
- func delete( _ url: URL , headers: HTTPClientHeaders = . init( ) , options: Request . Options = . init( ) , callback : @escaping ( Result < Response , Error > ) -> Void ) {
199
- self . execute ( Request ( method: . delete, url: url, headers: headers, body: nil , options: options) , callback : callback )
237
+ func delete( _ url: URL , headers: HTTPClientHeaders = . init( ) , options: Request . Options = . init( ) , completion : @escaping ( Result < Response , Error > ) -> Void ) {
238
+ self . execute ( Request ( method: . delete, url: url, headers: headers, body: nil , options: options) , completion : completion )
200
239
}
201
240
}
202
241
203
242
// MARK: - HTTPClientConfiguration
204
243
244
+ public typealias HTTPClientAuthorizationProvider = ( URL ) -> String ?
245
+
205
246
public struct HTTPClientConfiguration {
206
247
public var requestHeaders : HTTPClientHeaders ?
207
248
public var requestTimeout : DispatchTimeInterval ?
249
+ public var authorizationProvider : HTTPClientAuthorizationProvider ?
208
250
public var retryStrategy : HTTPClientRetryStrategy ?
209
251
public var circuitBreakerStrategy : HTTPClientCircuitBreakerStrategy ?
210
252
public var callbackQueue : DispatchQueue
211
253
212
254
public init ( ) {
213
255
self . requestHeaders = . none
214
256
self . requestTimeout = . none
257
+ self . authorizationProvider = . none
215
258
self . retryStrategy = . none
216
259
self . circuitBreakerStrategy = . none
217
260
self . callbackQueue = . global( )
@@ -259,6 +302,8 @@ public struct HTTPClientRequest {
259
302
public var addUserAgent : Bool
260
303
public var validResponseCodes : [ Int ] ?
261
304
public var timeout : DispatchTimeInterval ?
305
+ public var maximumResponseSizeInBytes : Int64 ?
306
+ public var authorizationProvider : HTTPClientAuthorizationProvider ?
262
307
public var retryStrategy : HTTPClientRetryStrategy ?
263
308
public var circuitBreakerStrategy : HTTPClientCircuitBreakerStrategy ?
264
309
public var callbackQueue : DispatchQueue ?
@@ -267,6 +312,8 @@ public struct HTTPClientRequest {
267
312
self . addUserAgent = true
268
313
self . validResponseCodes = . none
269
314
self . timeout = . none
315
+ self . maximumResponseSizeInBytes = . none
316
+ self . authorizationProvider = . none
270
317
self . retryStrategy = . none
271
318
self . circuitBreakerStrategy = . none
272
319
self . callbackQueue = . none
0 commit comments