Skip to content

Commit af837ed

Browse files
authored
Add http/2 support to HTTPBin (#382)
- Add `swift-nio-http2` as a dependency to the Package.swift (only used in the test target so far). - Remove unused initialization options for `HTTPBin` - The initialization options `ssl: Bool = false, compress: Bool = false, refusesConnections: Bool = false` move into a `Mode` enum. The mode might be `.http1_1(ssl: Bool, compress: Bool)`, `.http2(compress: Bool)`, or `.refuse` - Added support for `http/2` (not used in any tests yet, however I verified the support with curl locally.) - Nearly all channel pipeline modifications in `HTTPBin` are synchronous now. - HTTPBin's request handler is configurable. This allows testing of more esoteric cases without having to create a new server every time. The default `HTTPBin` continues to use the `HTTPBinHandler`.
1 parent 102b7e4 commit af837ed

6 files changed

+349
-145
lines changed

Diff for: Package.swift

+2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ let package = Package(
2323
dependencies: [
2424
.package(url: "https://github.com/apple/swift-nio.git", from: "2.30.0"),
2525
.package(url: "https://github.com/apple/swift-nio-ssl.git", from: "2.14.0"),
26+
.package(url: "https://github.com/apple/swift-nio-http2.git", from: "1.18.0"),
2627
.package(url: "https://github.com/apple/swift-nio-extras.git", from: "1.10.0"),
2728
.package(url: "https://github.com/apple/swift-nio-transport-services.git", from: "1.11.0"),
2829
.package(url: "https://github.com/apple/swift-log.git", from: "1.4.0"),
@@ -48,6 +49,7 @@ let package = Package(
4849
.product(name: "NIO", package: "swift-nio"),
4950
.product(name: "NIOConcurrencyHelpers", package: "swift-nio"),
5051
.product(name: "NIOSSL", package: "swift-nio-ssl"),
52+
.product(name: "NIOHTTP2", package: "swift-nio-http2"),
5153
"AsyncHTTPClient",
5254
.product(name: "NIOFoundationCompat", package: "swift-nio"),
5355
.product(name: "NIOTestUtils", package: "swift-nio"),

Diff for: Tests/AsyncHTTPClientTests/HTTPClient+SOCKSTests.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ class HTTPClientSOCKSTests: XCTestCase {
2323

2424
var clientGroup: EventLoopGroup!
2525
var serverGroup: EventLoopGroup!
26-
var defaultHTTPBin: HTTPBin!
26+
var defaultHTTPBin: HTTPBin<HTTPBinHandler>!
2727
var defaultClient: HTTPClient!
2828
var backgroundLogStore: CollectEverythingLogHandler.LogStore!
2929

Diff for: Tests/AsyncHTTPClientTests/HTTPClientInternalTests.swift

+47-15
Original file line numberDiff line numberDiff line change
@@ -368,11 +368,49 @@ class HTTPClientInternalTests: XCTestCase {
368368
func didFinishRequest(task: HTTPClient.Task<Response>) throws {}
369369
}
370370

371+
final class WriteAfterFutureSucceedsHandler: ChannelInboundHandler {
372+
typealias InboundIn = HTTPServerRequestPart
373+
typealias OutboundOut = HTTPServerResponsePart
374+
375+
let bodyFuture: EventLoopFuture<Void>
376+
let endFuture: EventLoopFuture<Void>
377+
378+
init(bodyFuture: EventLoopFuture<Void>, endFuture: EventLoopFuture<Void>) {
379+
self.bodyFuture = bodyFuture
380+
self.endFuture = endFuture
381+
}
382+
383+
func channelRead(context: ChannelHandlerContext, data: NIOAny) {
384+
switch self.unwrapInboundIn(data) {
385+
case .head:
386+
let head = HTTPResponseHead(version: HTTPVersion(major: 1, minor: 1), status: .ok)
387+
context.writeAndFlush(wrapOutboundOut(.head(head)), promise: nil)
388+
case .body:
389+
// ignore
390+
break
391+
case .end:
392+
self.bodyFuture.hop(to: context.eventLoop).whenSuccess {
393+
let buffer = context.channel.allocator.buffer(string: "1234")
394+
context.writeAndFlush(self.wrapOutboundOut(.body(.byteBuffer(buffer))), promise: nil)
395+
}
396+
397+
self.endFuture.hop(to: context.eventLoop).whenSuccess {
398+
context.writeAndFlush(self.wrapOutboundOut(.end(nil)), promise: nil)
399+
}
400+
}
401+
}
402+
}
403+
371404
// cannot test with NIOTS as `maxMessagesPerRead` is not supported
372405
let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)
373406
let httpClient = HTTPClient(eventLoopGroupProvider: .shared(eventLoopGroup))
374-
let promise = httpClient.eventLoopGroup.next().makePromise(of: Channel.self)
375-
let httpBin = HTTPBin(channelPromise: promise)
407+
let delegate = BackpressureTestDelegate(eventLoop: httpClient.eventLoopGroup.next())
408+
let httpBin = HTTPBin { _ in
409+
WriteAfterFutureSucceedsHandler(
410+
bodyFuture: delegate.optionsApplied.futureResult,
411+
endFuture: delegate.backpressurePromise.futureResult
412+
)
413+
}
376414

377415
defer {
378416
XCTAssertNoThrow(try httpClient.syncShutdown(requiresCleanClose: true))
@@ -381,27 +419,21 @@ class HTTPClientInternalTests: XCTestCase {
381419
}
382420

383421
let request = try Request(url: "http://localhost:\(httpBin.port)/custom")
384-
let delegate = BackpressureTestDelegate(eventLoop: httpClient.eventLoopGroup.next())
385-
let future = httpClient.execute(request: request, delegate: delegate).futureResult
386422

387-
let channel = try promise.futureResult.wait()
423+
let requestFuture = httpClient.execute(request: request, delegate: delegate).futureResult
388424

389425
// We need to wait for channel options that limit NIO to sending only one byte at a time.
390426
try delegate.optionsApplied.futureResult.wait()
391427

392428
// Send 4 bytes, but only one should be received until the backpressure promise is succeeded.
393-
let buffer = channel.allocator.buffer(string: "1234")
394-
try channel.writeAndFlush(HTTPServerResponsePart.body(.byteBuffer(buffer))).wait()
395429

396430
// Now we wait until message is delivered to client channel pipeline
397431
try delegate.messageReceived.futureResult.wait()
398432
XCTAssertEqual(delegate.reads, 1)
399433

400434
// Succeed the backpressure promise.
401435
delegate.backpressurePromise.succeed(())
402-
403-
try channel.writeAndFlush(HTTPServerResponsePart.end(nil)).wait()
404-
try future.wait()
436+
try requestFuture.wait()
405437

406438
// At this point all other bytes should be delivered.
407439
XCTAssertEqual(delegate.reads, 4)
@@ -602,7 +634,7 @@ class HTTPClientInternalTests: XCTestCase {
602634
}
603635

604636
func testResponseConnectionCloseGet() throws {
605-
let httpBin = HTTPBin(ssl: false)
637+
let httpBin = HTTPBin(.http1_1())
606638
let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup),
607639
configuration: HTTPClient.Configuration(certificateVerification: .none))
608640
defer {
@@ -756,14 +788,14 @@ class HTTPClientInternalTests: XCTestCase {
756788
struct NoChannelError: Error {}
757789

758790
let client = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup))
759-
var maybeServersAndChannels: [(HTTPBin, Channel)]?
791+
var maybeServersAndChannels: [(HTTPBin<HTTPBinHandler>, Channel)]?
760792
XCTAssertNoThrow(maybeServersAndChannels = try (0..<10).map { _ in
761793
let web = HTTPBin()
762794
defer {
763795
XCTAssertNoThrow(try web.shutdown())
764796
}
765797

766-
let req = try! HTTPClient.Request(url: "http://localhost:\(web.serverChannel.localAddress!.port!)/get",
798+
let req = try! HTTPClient.Request(url: "http://localhost:\(web.port)/get",
767799
method: .GET,
768800
body: nil)
769801
var maybeConnection: Connection?
@@ -847,7 +879,7 @@ class HTTPClientInternalTests: XCTestCase {
847879
XCTAssertNoThrow(try client.syncShutdown())
848880
}
849881

850-
let req = try! HTTPClient.Request(url: "http://localhost:\(web.serverChannel.localAddress!.port!)/get",
882+
let req = try! HTTPClient.Request(url: "http://localhost:\(web.port)/get",
851883
method: .GET,
852884
body: nil)
853885

@@ -1083,7 +1115,7 @@ class HTTPClientInternalTests: XCTestCase {
10831115
let el1 = elg.next()
10841116
let el2 = elg.next()
10851117

1086-
let httpBin = HTTPBin(refusesConnections: true)
1118+
let httpBin = HTTPBin(.refuse)
10871119
let client = HTTPClient(eventLoopGroupProvider: .shared(elg))
10881120

10891121
defer {

Diff for: Tests/AsyncHTTPClientTests/HTTPClientNIOTSTests.swift

+3-3
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ class HTTPClientNIOTSTests: XCTestCase {
5252
func testTLSFailError() {
5353
guard isTestingNIOTS() else { return }
5454

55-
let httpBin = HTTPBin(ssl: true)
55+
let httpBin = HTTPBin(.http1_1(ssl: true))
5656
let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup))
5757
defer {
5858
XCTAssertNoThrow(try httpClient.syncShutdown(requiresCleanClose: true))
@@ -76,7 +76,7 @@ class HTTPClientNIOTSTests: XCTestCase {
7676

7777
func testConnectionFailError() {
7878
guard isTestingNIOTS() else { return }
79-
let httpBin = HTTPBin(ssl: true)
79+
let httpBin = HTTPBin(.http1_1(ssl: true))
8080
let httpClient = HTTPClient(eventLoopGroupProvider: .shared(self.clientGroup),
8181
configuration: .init(timeout: .init(connect: .milliseconds(100),
8282
read: .milliseconds(100))))
@@ -96,7 +96,7 @@ class HTTPClientNIOTSTests: XCTestCase {
9696
func testTLSVersionError() {
9797
guard isTestingNIOTS() else { return }
9898
#if canImport(Network)
99-
let httpBin = HTTPBin(ssl: true)
99+
let httpBin = HTTPBin(.http1_1(ssl: true))
100100
var tlsConfig = TLSConfiguration.makeClientConfiguration()
101101
tlsConfig.certificateVerification = .none
102102
tlsConfig.minimumTLSVersion = .tlsv11

0 commit comments

Comments
 (0)