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