@@ -16,14 +16,24 @@ import struct Foundation.URL
16
16
import NIOCore
17
17
import NIOHTTP1
18
18
19
+ extension HTTPClient {
20
+ /// The maximum body size allowed, before a redirect response is cancelled. 3KB.
21
+ ///
22
+ /// Why 3KB? We feel like this is a good compromise between potentially reusing the
23
+ /// connection in HTTP/1.1 mode (if we load all data from the redirect response we can
24
+ /// reuse the connection) and not being to wasteful in the amount of data that is thrown
25
+ /// away being transferred.
26
+ fileprivate static let maxBodySizeRedirectResponse = 1024 * 3
27
+ }
28
+
19
29
extension RequestBag {
20
30
struct StateMachine {
21
31
fileprivate enum State {
22
32
case initialized
23
33
case queued( HTTPRequestScheduler )
24
34
case executing( HTTPRequestExecutor , RequestStreamState , ResponseStreamState )
25
35
case finished( error: Error ? )
26
- case redirected( HTTPResponseHead , URL )
36
+ case redirected( HTTPRequestExecutor , Int , HTTPResponseHead , URL )
27
37
case modifying
28
38
}
29
39
@@ -259,11 +269,18 @@ extension RequestBag.StateMachine {
259
269
}
260
270
}
261
271
272
+ enum ReceiveResponseHeadAction {
273
+ case none
274
+ case forwardResponseHead( HTTPResponseHead )
275
+ case signalBodyDemand( HTTPRequestExecutor )
276
+ case redirect( HTTPRequestExecutor , RedirectHandler < Delegate . Response > , HTTPResponseHead , URL )
277
+ }
278
+
262
279
/// The response head has been received.
263
280
///
264
281
/// - Parameter head: The response' head
265
282
/// - Returns: Whether the response should be forwarded to the delegate. Will be `false` if the request follows a redirect.
266
- mutating func receiveResponseHead( _ head: HTTPResponseHead ) -> Bool {
283
+ mutating func receiveResponseHead( _ head: HTTPResponseHead ) -> ReceiveResponseHeadAction {
267
284
switch self . state {
268
285
case . initialized, . queued:
269
286
preconditionFailure ( " How can we receive a response, if the request hasn't started yet. " )
@@ -276,24 +293,40 @@ extension RequestBag.StateMachine {
276
293
status: head. status,
277
294
responseHeaders: head. headers
278
295
) {
279
- self . state = . redirected( head, redirectURL)
280
- return false
296
+ // If we will redirect, we need to consume the response's body ASAP, to be able to
297
+ // reuse the existing connection. We will consume a response body, if the body is
298
+ // smaller than 3kb.
299
+ switch head. contentLength {
300
+ case . some( 0 ... ( HTTPClient . maxBodySizeRedirectResponse) ) , . none:
301
+ self . state = . redirected( executor, 0 , head, redirectURL)
302
+ return . signalBodyDemand( executor)
303
+ case . some:
304
+ self . state = . finished( error: HTTPClientError . cancelled)
305
+ return . redirect( executor, self . redirectHandler!, head, redirectURL)
306
+ }
281
307
} else {
282
308
self . state = . executing( executor, requestState, . buffering( . init( ) , next: . askExecutorForMore) )
283
- return true
309
+ return . forwardResponseHead ( head )
284
310
}
285
311
case . redirected:
286
312
preconditionFailure ( " This state can only be reached after we have received a HTTP head " )
287
313
case . finished( error: . some) :
288
- return false
314
+ return . none
289
315
case . finished( error: . none) :
290
316
preconditionFailure ( " How can the request be finished without error, before receiving response head? " )
291
317
case . modifying:
292
318
preconditionFailure ( " Invalid state: \( self . state) " )
293
319
}
294
320
}
295
321
296
- mutating func receiveResponseBodyParts( _ buffer: CircularBuffer < ByteBuffer > ) -> ByteBuffer ? {
322
+ enum ReceiveResponseBodyAction {
323
+ case none
324
+ case forwardResponsePart( ByteBuffer )
325
+ case signalBodyDemand( HTTPRequestExecutor )
326
+ case redirect( HTTPRequestExecutor , RedirectHandler < Delegate . Response > , HTTPResponseHead , URL )
327
+ }
328
+
329
+ mutating func receiveResponseBodyParts( _ buffer: CircularBuffer < ByteBuffer > ) -> ReceiveResponseBodyAction {
297
330
switch self . state {
298
331
case . initialized, . queued:
299
332
preconditionFailure ( " How can we receive a response body part, if the request hasn't started yet. " )
@@ -312,17 +345,26 @@ extension RequestBag.StateMachine {
312
345
currentBuffer. append ( contentsOf: buffer)
313
346
}
314
347
self . state = . executing( executor, requestState, . buffering( currentBuffer, next: next) )
315
- return nil
348
+ return . none
316
349
case . executing( let executor, let requestState, . waitingForRemote) :
317
350
var buffer = buffer
318
351
let first = buffer. removeFirst ( )
319
352
self . state = . executing( executor, requestState, . buffering( buffer, next: . askExecutorForMore) )
320
- return first
321
- case . redirected:
322
- // ignore body
323
- return nil
353
+ return . forwardResponsePart( first)
354
+ case . redirected( let executor, var receivedBytes, let head, let redirectURL) :
355
+ let partsLength = buffer. reduce ( into: 0 ) { $0 += $1. readableBytes }
356
+ receivedBytes += partsLength
357
+
358
+ if receivedBytes > HTTPClient . maxBodySizeRedirectResponse {
359
+ self . state = . finished( error: HTTPClientError . cancelled)
360
+ return . redirect( executor, self . redirectHandler!, head, redirectURL)
361
+ } else {
362
+ self . state = . redirected( executor, receivedBytes, head, redirectURL)
363
+ return . signalBodyDemand( executor)
364
+ }
365
+
324
366
case . finished( error: . some) :
325
- return nil
367
+ return . none
326
368
case . finished( error: . none) :
327
369
preconditionFailure ( " How can the request be finished without error, before receiving response head? " )
328
370
case . modifying:
@@ -368,7 +410,7 @@ extension RequestBag.StateMachine {
368
410
self . state = . executing( executor, requestState, . buffering( newChunks, next: . eof) )
369
411
return . consume( first)
370
412
371
- case . redirected( let head, let redirectURL) :
413
+ case . redirected( _ , _ , let head, let redirectURL) :
372
414
self . state = . finished( error: nil )
373
415
return . redirect( self . redirectHandler!, head, redirectURL)
374
416
@@ -529,3 +571,12 @@ extension RequestBag.StateMachine {
529
571
}
530
572
}
531
573
}
574
+
575
+ extension HTTPResponseHead {
576
+ var contentLength : Int ? {
577
+ guard let header = self . headers. first ( name: " content-length " ) else {
578
+ return nil
579
+ }
580
+ return Int ( header)
581
+ }
582
+ }
0 commit comments