@@ -16,14 +16,24 @@ import struct Foundation.URL
1616import NIOCore
1717import NIOHTTP1
1818
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+
1929extension RequestBag {
2030 struct StateMachine {
2131 fileprivate enum State {
2232 case initialized
2333 case queued( HTTPRequestScheduler )
2434 case executing( HTTPRequestExecutor , RequestStreamState , ResponseStreamState )
2535 case finished( error: Error ? )
26- case redirected( HTTPResponseHead , URL )
36+ case redirected( HTTPRequestExecutor , Int , HTTPResponseHead , URL )
2737 case modifying
2838 }
2939
@@ -259,11 +269,18 @@ extension RequestBag.StateMachine {
259269 }
260270 }
261271
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+
262279 /// The response head has been received.
263280 ///
264281 /// - Parameter head: The response' head
265282 /// - 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 {
267284 switch self . state {
268285 case . initialized, . queued:
269286 preconditionFailure ( " How can we receive a response, if the request hasn't started yet. " )
@@ -276,24 +293,40 @@ extension RequestBag.StateMachine {
276293 status: head. status,
277294 responseHeaders: head. headers
278295 ) {
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+ }
281307 } else {
282308 self . state = . executing( executor, requestState, . buffering( . init( ) , next: . askExecutorForMore) )
283- return true
309+ return . forwardResponseHead ( head )
284310 }
285311 case . redirected:
286312 preconditionFailure ( " This state can only be reached after we have received a HTTP head " )
287313 case . finished( error: . some) :
288- return false
314+ return . none
289315 case . finished( error: . none) :
290316 preconditionFailure ( " How can the request be finished without error, before receiving response head? " )
291317 case . modifying:
292318 preconditionFailure ( " Invalid state: \( self . state) " )
293319 }
294320 }
295321
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 {
297330 switch self . state {
298331 case . initialized, . queued:
299332 preconditionFailure ( " How can we receive a response body part, if the request hasn't started yet. " )
@@ -312,17 +345,26 @@ extension RequestBag.StateMachine {
312345 currentBuffer. append ( contentsOf: buffer)
313346 }
314347 self . state = . executing( executor, requestState, . buffering( currentBuffer, next: next) )
315- return nil
348+ return . none
316349 case . executing( let executor, let requestState, . waitingForRemote) :
317350 var buffer = buffer
318351 let first = buffer. removeFirst ( )
319352 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+
324366 case . finished( error: . some) :
325- return nil
367+ return . none
326368 case . finished( error: . none) :
327369 preconditionFailure ( " How can the request be finished without error, before receiving response head? " )
328370 case . modifying:
@@ -368,7 +410,7 @@ extension RequestBag.StateMachine {
368410 self . state = . executing( executor, requestState, . buffering( newChunks, next: . eof) )
369411 return . consume( first)
370412
371- case . redirected( let head, let redirectURL) :
413+ case . redirected( _ , _ , let head, let redirectURL) :
372414 self . state = . finished( error: nil )
373415 return . redirect( self . redirectHandler!, head, redirectURL)
374416
@@ -529,3 +571,12 @@ extension RequestBag.StateMachine {
529571 }
530572 }
531573}
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