Skip to content

Commit be7759e

Browse files
committed
Support informational response heads
1 parent d49602f commit be7759e

File tree

5 files changed

+166
-2
lines changed

5 files changed

+166
-2
lines changed

Diff for: Package.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ let package = Package(
2121
.library(name: "AsyncHTTPClient", targets: ["AsyncHTTPClient"]),
2222
],
2323
dependencies: [
24-
.package(url: "https://github.com/apple/swift-nio.git", from: "2.32.0"),
24+
.package(url: "https://github.com/apple/swift-nio.git", .branch("main")),
2525
.package(url: "https://github.com/apple/swift-nio-ssl.git", from: "2.14.1"),
2626
.package(url: "https://github.com/apple/swift-nio-http2.git", from: "1.18.2"),
2727
.package(url: "https://github.com/apple/swift-nio-extras.git", from: "1.10.0"),

Diff for: Sources/AsyncHTTPClient/ConnectionPool/HTTP1.1/HTTP1Connection.swift

+10-1
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,16 @@ final class HTTP1Connection {
116116

117117
do {
118118
let sync = self.channel.pipeline.syncOperations
119-
try sync.addHTTPClientHandlers()
119+
120+
// We can not use `sync.addHTTPClientHandlers()`, as we want to explicitly set the
121+
// `.informationalResponseStrategy` for the decoder.
122+
let requestEncoder = HTTPRequestEncoder()
123+
let responseDecoder = HTTPResponseDecoder(
124+
leftOverBytesStrategy: .dropBytes,
125+
informationalResponseStrategy: .forward
126+
)
127+
try sync.addHandler(requestEncoder)
128+
try sync.addHandler(ByteToMessageHandler(responseDecoder))
120129

121130
if case .enabled(let limit) = configuration.decompression {
122131
let decompressHandler = NIOHTTPResponseDecompressor(limit: limit)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the AsyncHTTPClient open source project
4+
//
5+
// Copyright (c) 2018-2019 Apple Inc. and the AsyncHTTPClient project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of AsyncHTTPClient project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
//
15+
// HTTPClientInformationalResponsesTests+XCTest.swift
16+
//
17+
import XCTest
18+
19+
///
20+
/// NOTE: This file was generated by generate_linux_tests.rb
21+
///
22+
/// Do NOT edit this file directly as it will be regenerated automatically when needed.
23+
///
24+
25+
extension HTTPClientReproTests {
26+
static var allTests: [(String, (HTTPClientReproTests) -> () throws -> Void)] {
27+
return [
28+
("testServerSends100ContinueFirst", testServerSends100ContinueFirst),
29+
("testServerSendsSwitchingProtocols", testServerSendsSwitchingProtocols),
30+
]
31+
}
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the AsyncHTTPClient open source project
4+
//
5+
// Copyright (c) 2021 Apple Inc. and the AsyncHTTPClient project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of AsyncHTTPClient project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import AsyncHTTPClient
16+
import Logging
17+
import NIOCore
18+
import NIOHTTP1
19+
import XCTest
20+
21+
final class HTTPClientReproTests: XCTestCase {
22+
func testServerSends100ContinueFirst() {
23+
final class HTTPInformationalResponseHandler: ChannelInboundHandler {
24+
typealias InboundIn = HTTPServerRequestPart
25+
typealias OutboundOut = HTTPServerResponsePart
26+
27+
func channelRead(context: ChannelHandlerContext, data: NIOAny) {
28+
switch self.unwrapInboundIn(data) {
29+
case .head:
30+
context.writeAndFlush(self.wrapOutboundOut(.head(.init(version: .http1_1, status: .continue))), promise: nil)
31+
case .body:
32+
break
33+
case .end:
34+
context.write(self.wrapOutboundOut(.head(.init(version: .http1_1, status: .ok))), promise: nil)
35+
context.writeAndFlush(self.wrapOutboundOut(.end(nil)), promise: nil)
36+
}
37+
}
38+
}
39+
40+
let client = HTTPClient(eventLoopGroupProvider: .createNew)
41+
defer { XCTAssertNoThrow(try client.syncShutdown()) }
42+
43+
let httpBin = HTTPBin(.http1_1(ssl: false, compress: false)) { _ in
44+
HTTPInformationalResponseHandler()
45+
}
46+
47+
let body = #"{"foo": "bar"}"#
48+
49+
var maybeRequest: HTTPClient.Request?
50+
XCTAssertNoThrow(maybeRequest = try HTTPClient.Request(
51+
url: "http://localhost:\(httpBin.port)/",
52+
method: .POST,
53+
headers: [
54+
"Content-Type": "application/json",
55+
],
56+
body: .string(body)
57+
))
58+
guard let request = maybeRequest else { return XCTFail("Expected to have a request here") }
59+
60+
var logger = Logger(label: "test")
61+
logger.logLevel = .trace
62+
63+
var response: HTTPClient.Response?
64+
XCTAssertNoThrow(response = try client.execute(request: request, logger: logger).wait())
65+
XCTAssertEqual(response?.status, .ok)
66+
}
67+
68+
func testServerSendsSwitchingProtocols() {
69+
final class HTTPInformationalResponseHandler: ChannelInboundHandler {
70+
typealias InboundIn = HTTPServerRequestPart
71+
typealias OutboundOut = HTTPServerResponsePart
72+
73+
func channelRead(context: ChannelHandlerContext, data: NIOAny) {
74+
switch self.unwrapInboundIn(data) {
75+
case .head:
76+
let head = HTTPResponseHead(version: .http1_1, status: .switchingProtocols, headers: [
77+
"Connection": "Upgrade",
78+
"Upgrade": "Websocket",
79+
])
80+
let body = context.channel.allocator.buffer(string: "foo bar")
81+
82+
context.write(self.wrapOutboundOut(.head(head)), promise: nil)
83+
context.write(self.wrapOutboundOut(.body(.byteBuffer(body))), promise: nil)
84+
// we purposefully don't send an `.end` here.
85+
context.flush()
86+
case .body:
87+
break
88+
case .end:
89+
break
90+
}
91+
}
92+
}
93+
94+
let client = HTTPClient(eventLoopGroupProvider: .createNew)
95+
defer { XCTAssertNoThrow(try client.syncShutdown()) }
96+
97+
let httpBin = HTTPBin(.http1_1(ssl: false, compress: false)) { _ in
98+
HTTPInformationalResponseHandler()
99+
}
100+
101+
let body = #"{"foo": "bar"}"#
102+
103+
var maybeRequest: HTTPClient.Request?
104+
XCTAssertNoThrow(maybeRequest = try HTTPClient.Request(
105+
url: "http://localhost:\(httpBin.port)/",
106+
method: .POST,
107+
headers: [
108+
"Content-Type": "application/json",
109+
],
110+
body: .string(body)
111+
))
112+
guard let request = maybeRequest else { return XCTFail("Expected to have a request here") }
113+
114+
var logger = Logger(label: "test")
115+
logger.logLevel = .trace
116+
117+
var response: HTTPClient.Response?
118+
XCTAssertNoThrow(response = try client.execute(request: request, logger: logger).wait())
119+
XCTAssertEqual(response?.status, .switchingProtocols)
120+
XCTAssertNil(response?.body)
121+
}
122+
}

Diff for: Tests/LinuxMain.swift

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import XCTest
3636
testCase(HTTPClientCookieTests.allTests),
3737
testCase(HTTPClientInternalTests.allTests),
3838
testCase(HTTPClientNIOTSTests.allTests),
39+
testCase(HTTPClientReproTests.allTests),
3940
testCase(HTTPClientSOCKSTests.allTests),
4041
testCase(HTTPClientTests.allTests),
4142
testCase(HTTPConnectionPoolTests.allTests),

0 commit comments

Comments
 (0)