@@ -365,6 +365,131 @@ class HTTP1ConnectionTests: XCTestCase {
365365 XCTAssertEqual ( httpBin. activeConnections, 0 )
366366 }
367367
368+ func testConnectionIsClosedAfterSwitchingProtocols( ) {
369+ let embedded = EmbeddedChannel ( )
370+ let logger = Logger ( label: " test.http1.connection " )
371+
372+ XCTAssertNoThrow ( try embedded. connect ( to: SocketAddress ( ipAddress: " 127.0.0.1 " , port: 3000 ) ) . wait ( ) )
373+
374+ var maybeConnection : HTTP1Connection ?
375+ let connectionDelegate = MockConnectionDelegate ( )
376+ XCTAssertNoThrow ( maybeConnection = try HTTP1Connection . start (
377+ channel: embedded,
378+ connectionID: 0 ,
379+ delegate: connectionDelegate,
380+ configuration: . init( decompression: . enabled( limit: . ratio( 4 ) ) ) ,
381+ logger: logger
382+ ) )
383+ guard let connection = maybeConnection else { return XCTFail ( " Expected to have a connection at this point. " ) }
384+
385+ var maybeRequest : HTTPClient . Request ?
386+ XCTAssertNoThrow ( maybeRequest = try HTTPClient . Request ( url: " http://swift.org/ " ) )
387+ guard let request = maybeRequest else { return XCTFail ( " Expected to be able to create a request " ) }
388+
389+ let delegate = ResponseAccumulator ( request: request)
390+ var maybeRequestBag : RequestBag < ResponseAccumulator > ?
391+ XCTAssertNoThrow ( maybeRequestBag = try RequestBag (
392+ request: request,
393+ eventLoopPreference: . delegate( on: embedded. eventLoop) ,
394+ task: . init( eventLoop: embedded. eventLoop, logger: logger) ,
395+ redirectHandler: nil ,
396+ connectionDeadline: . now( ) + . seconds( 30 ) ,
397+ requestOptions: . forTests( ) ,
398+ delegate: delegate
399+ ) )
400+ guard let requestBag = maybeRequestBag else { return XCTFail ( " Expected to be able to create a request bag " ) }
401+
402+ connection. executeRequest ( requestBag)
403+
404+ XCTAssertNoThrow ( try embedded. readOutbound ( as: ByteBuffer . self) ) // head
405+ XCTAssertNoThrow ( try embedded. readOutbound ( as: ByteBuffer . self) ) // end
406+
407+ let responseString = """
408+ HTTP/1.1 101 Switching Protocols \r \n \
409+ Upgrade: websocket \r \n \
410+ Sec-WebSocket-Accept: xAMUK7/Il9bLRFJrikq6mm8CNZI= \r \n \
411+ Connection: upgrade \r \n \
412+ date: Mon, 27 Sep 2021 17:53:14 GMT \r \n \
413+ \r \n \
414+ \r \n foo bar baz
415+ """
416+
417+ XCTAssertTrue ( embedded. isActive)
418+ XCTAssertEqual ( connectionDelegate. hitConnectionClosed, 0 )
419+ XCTAssertEqual ( connectionDelegate. hitConnectionReleased, 0 )
420+ XCTAssertNoThrow ( try embedded. writeInbound ( ByteBuffer ( string: responseString) ) )
421+ XCTAssertFalse ( embedded. isActive)
422+ ( embedded. eventLoop as! EmbeddedEventLoop ) . run ( ) // tick once to run futures.
423+ XCTAssertEqual ( connectionDelegate. hitConnectionClosed, 1 )
424+ XCTAssertEqual ( connectionDelegate. hitConnectionReleased, 0 )
425+
426+ var response : HTTPClient . Response ?
427+ XCTAssertNoThrow ( response = try requestBag. task. futureResult. wait ( ) )
428+ XCTAssertEqual ( response? . status, . switchingProtocols)
429+ XCTAssertEqual ( response? . headers. count, 4 )
430+ XCTAssertEqual ( response? . body, nil )
431+ }
432+
433+ func testConnectionDoesntCrashAfterConnectionCloseAndEarlyHints( ) {
434+ let embedded = EmbeddedChannel ( )
435+ let logger = Logger ( label: " test.http1.connection " )
436+
437+ XCTAssertNoThrow ( try embedded. connect ( to: SocketAddress ( ipAddress: " 127.0.0.1 " , port: 3000 ) ) . wait ( ) )
438+
439+ var maybeConnection : HTTP1Connection ?
440+ let connectionDelegate = MockConnectionDelegate ( )
441+ XCTAssertNoThrow ( maybeConnection = try HTTP1Connection . start (
442+ channel: embedded,
443+ connectionID: 0 ,
444+ delegate: connectionDelegate,
445+ configuration: . init( decompression: . enabled( limit: . ratio( 4 ) ) ) ,
446+ logger: logger
447+ ) )
448+ guard let connection = maybeConnection else { return XCTFail ( " Expected to have a connection at this point. " ) }
449+
450+ var maybeRequest : HTTPClient . Request ?
451+ XCTAssertNoThrow ( maybeRequest = try HTTPClient . Request ( url: " http://swift.org/ " ) )
452+ guard let request = maybeRequest else { return XCTFail ( " Expected to be able to create a request " ) }
453+
454+ let delegate = ResponseAccumulator ( request: request)
455+ var maybeRequestBag : RequestBag < ResponseAccumulator > ?
456+ XCTAssertNoThrow ( maybeRequestBag = try RequestBag (
457+ request: request,
458+ eventLoopPreference: . delegate( on: embedded. eventLoop) ,
459+ task: . init( eventLoop: embedded. eventLoop, logger: logger) ,
460+ redirectHandler: nil ,
461+ connectionDeadline: . now( ) + . seconds( 30 ) ,
462+ requestOptions: . forTests( ) ,
463+ delegate: delegate
464+ ) )
465+ guard let requestBag = maybeRequestBag else { return XCTFail ( " Expected to be able to create a request bag " ) }
466+
467+ connection. executeRequest ( requestBag)
468+
469+ XCTAssertNoThrow ( try embedded. readOutbound ( as: ByteBuffer . self) ) // head
470+ XCTAssertNoThrow ( try embedded. readOutbound ( as: ByteBuffer . self) ) // end
471+
472+ let responseString = """
473+ HTTP/1.1 103 Early Hints \r \n \
474+ date: Mon, 27 Sep 2021 17:53:14 GMT \r \n \
475+ \r \n \
476+ \r \n
477+ """
478+
479+ XCTAssertTrue ( embedded. isActive)
480+ XCTAssertEqual ( connectionDelegate. hitConnectionClosed, 0 )
481+ XCTAssertEqual ( connectionDelegate. hitConnectionReleased, 0 )
482+ XCTAssertNoThrow ( try embedded. writeInbound ( ByteBuffer ( string: responseString) ) )
483+ XCTAssertFalse ( embedded. isActive)
484+ ( embedded. eventLoop as! EmbeddedEventLoop ) . run ( ) // tick once to run futures.
485+ XCTAssertEqual ( connectionDelegate. hitConnectionClosed, 1 )
486+ XCTAssertEqual ( connectionDelegate. hitConnectionReleased, 0 )
487+
488+ XCTAssertThrowsError ( try requestBag. task. futureResult. wait ( ) ) {
489+ XCTAssertEqual ( $0 as? HTTPClientError , . httpEndReceivedAfterHeadWith1xx)
490+ }
491+ }
492+
368493 // In order to test backpressure we need to make sure that reads will not happen
369494 // until the backpressure promise is succeeded. Since we cannot guarantee when
370495 // messages will be delivered to a client pipeline and we need this test to be
0 commit comments