Skip to content

Commit 15ec951

Browse files
authored
Fix pipeline bootstrapping (#13)
The current bootstrapping of the HTTP pipeline isn't fully correct. We're currently always setting up the pipeline using secure upgrade. This means that we need to wait for the SSL handshake to conclude to finish negotiating the protocol we're using. However, if we are setting up an insecure channel (i.e. without TLS) we never add a TLS handler to the pipeline, causing the protocol negotiation handler to wait forever for the handshake to finish. This means that insecure H1 requests currently don't work. This PR changes this to set up the pipeline for H1 if TLS is not being used. We could consider adding h2c support.
1 parent 5ffc0fe commit 15ec951

File tree

1 file changed

+134
-119
lines changed

1 file changed

+134
-119
lines changed

Sources/HTTPServer/HTTPServer.swift

Lines changed: 134 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -156,115 +156,161 @@ public final class Server<RequestHandler: HTTPServerRequestHandler> {
156156
configuration: HTTPServerConfiguration,
157157
handler: RequestHandler
158158
) async throws {
159-
let serverChannel = try await Self.bind(bindTarget: configuration.bindTarget) {
160-
(channel) -> EventLoopFuture<
161-
EventLoopFuture<
162-
NIONegotiatedHTTPVersion<
163-
NIOAsyncChannel<HTTPRequestPart, HTTPResponsePart>,
164-
(
165-
Void,
166-
NIOHTTP2Handler.AsyncStreamMultiplexer<NIOAsyncChannel<HTTPRequestPart, HTTPResponsePart>>
159+
switch configuration.tlSConfiguration.backing {
160+
case .insecure:
161+
try await Self.serveInsecureHTTP1_1(
162+
bindTarget: configuration.bindTarget,
163+
handler: handler,
164+
logger: logger
165+
)
166+
167+
case .certificateChainAndPrivateKey(let certificateChain, let privateKey):
168+
try await Self.serveSecureUpgrade(
169+
bindTarget: configuration.bindTarget,
170+
certificateChain: certificateChain,
171+
privateKey: privateKey,
172+
handler: handler,
173+
logger: logger
174+
)
175+
}
176+
}
177+
178+
private static func serveInsecureHTTP1_1(
179+
bindTarget: HTTPServerConfiguration.BindTarget,
180+
handler: RequestHandler,
181+
logger: Logger
182+
) async throws {
183+
switch bindTarget.backing {
184+
case .hostAndPort(let host, let port):
185+
let serverChannel = try await ServerBootstrap(group: .singletonMultiThreadedEventLoopGroup)
186+
.serverChannelOption(.socketOption(.so_reuseaddr), value: 1)
187+
.bind(host: host, port: port) { channel in
188+
channel.pipeline.configureHTTPServerPipeline().flatMapThrowing {
189+
try channel.pipeline.syncOperations.addHandler(HTTP1ToHTTPServerCodec(secure: false))
190+
return try NIOAsyncChannel<HTTPRequestPart, HTTPResponsePart>(
191+
wrappingChannelSynchronously: channel,
192+
configuration: .init(isOutboundHalfClosureEnabled: true)
167193
)
168-
>
169-
>
170-
> in
171-
channel.eventLoop.makeCompletedFuture {
172-
switch configuration.tlSConfiguration.backing {
173-
case .insecure:
174-
break
194+
}
195+
}
175196

176-
case .certificateChainAndPrivateKey(let certificateChain, let privateKey):
177-
let certificateChain =
178-
try certificateChain
179-
.map {
180-
try NIOSSLCertificate(
181-
bytes: $0.serializeAsPEM().derBytes,
182-
format: .der
197+
try await withThrowingDiscardingTaskGroup { group in
198+
try await serverChannel.executeThenClose { inbound in
199+
for try await http1Channel in inbound {
200+
group.addTask {
201+
await Self.handleRequestChannel(
202+
logger: logger,
203+
channel: http1Channel,
204+
handler: handler
183205
)
184206
}
185-
.map { NIOSSLCertificateSource.certificate($0) }
186-
let privateKey = NIOSSLPrivateKeySource.privateKey(
187-
try NIOSSLPrivateKey(
188-
bytes: privateKey.serializeAsPEM().derBytes,
189-
format: .der
207+
}
208+
}
209+
}
210+
}
211+
}
212+
213+
private static func serveSecureUpgrade(
214+
bindTarget: HTTPServerConfiguration.BindTarget,
215+
certificateChain: [Certificate],
216+
privateKey: Certificate.PrivateKey,
217+
handler: RequestHandler,
218+
logger: Logger
219+
) async throws {
220+
switch bindTarget.backing {
221+
case .hostAndPort(let host, let port):
222+
let serverChannel = try await ServerBootstrap(group: .singletonMultiThreadedEventLoopGroup)
223+
.serverChannelOption(.socketOption(.so_reuseaddr), value: 1)
224+
.bind(host: host, port: port) { channel in
225+
channel.eventLoop.makeCompletedFuture {
226+
let certificateChain = try certificateChain
227+
.map {
228+
try NIOSSLCertificate(
229+
bytes: $0.serializeAsPEM().derBytes,
230+
format: .der
231+
)
232+
}
233+
.map { NIOSSLCertificateSource.certificate($0) }
234+
let privateKey = NIOSSLPrivateKeySource.privateKey(
235+
try NIOSSLPrivateKey(
236+
bytes: privateKey.serializeAsPEM().derBytes,
237+
format: .der
238+
)
190239
)
191-
)
192240

193-
try channel.pipeline.syncOperations
194-
.addHandler(
195-
NIOSSLServerHandler(
196-
context: .init(
197-
configuration:
198-
.makeServerConfiguration(
241+
try channel.pipeline.syncOperations
242+
.addHandler(
243+
NIOSSLServerHandler(
244+
context: .init(
245+
configuration: .makeServerConfiguration(
199246
certificateChain: certificateChain,
200247
privateKey: privateKey
201248
)
249+
)
202250
)
203251
)
204-
)
205-
}
206-
}.flatMap {
207-
channel
208-
.configureAsyncHTTPServerPipeline { channel in
209-
channel.eventLoop.makeCompletedFuture {
210-
try channel.pipeline.syncOperations.addHandler(HTTP1ToHTTPServerCodec(secure: false))
252+
}.flatMap {
253+
channel.configureAsyncHTTPServerPipeline { channel in
254+
channel.eventLoop.makeCompletedFuture {
255+
try channel.pipeline.syncOperations.addHandler(HTTP1ToHTTPServerCodec(secure: true))
211256

212-
return try NIOAsyncChannel<HTTPRequestPart, HTTPResponsePart>(
213-
wrappingChannelSynchronously: channel,
214-
configuration: .init(isOutboundHalfClosureEnabled: true)
215-
)
216-
}
217-
} http2ConnectionInitializer: { channel in
218-
channel.eventLoop.makeSucceededVoidFuture()
219-
} http2StreamInitializer: { channel in
220-
channel.eventLoop.makeCompletedFuture {
221-
try channel.pipeline.syncOperations
222-
.addHandler(
223-
HTTP2FramePayloadToHTTPServerCodec()
257+
return try NIOAsyncChannel<HTTPRequestPart, HTTPResponsePart>(
258+
wrappingChannelSynchronously: channel,
259+
configuration: .init(isOutboundHalfClosureEnabled: true)
224260
)
261+
}
262+
} http2ConnectionInitializer: { channel in
263+
channel.eventLoop.makeSucceededVoidFuture()
264+
} http2StreamInitializer: { channel in
265+
channel.eventLoop.makeCompletedFuture {
266+
try channel.pipeline.syncOperations
267+
.addHandler(
268+
HTTP2FramePayloadToHTTPServerCodec()
269+
)
225270

226-
return try NIOAsyncChannel<HTTPRequestPart, HTTPResponsePart>(
227-
wrappingChannelSynchronously: channel,
228-
configuration: .init(isOutboundHalfClosureEnabled: true)
229-
)
271+
return try NIOAsyncChannel<HTTPRequestPart, HTTPResponsePart>(
272+
wrappingChannelSynchronously: channel,
273+
configuration: .init(isOutboundHalfClosureEnabled: true)
274+
)
275+
}
230276
}
231277
}
232-
}
233-
}
278+
}
234279

235-
try await withThrowingDiscardingTaskGroup { group in
236-
try await serverChannel.executeThenClose { inbound in
237-
for try await upgradeResult in inbound {
238-
group.addTask {
239-
do {
240-
try await withThrowingDiscardingTaskGroup { connectionGroup in
241-
switch try await upgradeResult.get() {
242-
case .http1_1(let http1Channel):
243-
connectionGroup.addTask {
244-
await Self.handleRequestChannel(
245-
logger: logger,
246-
channel: http1Channel,
247-
handler: handler
248-
)
249-
}
250-
case .http2((_, let http2Multiplexer)):
251-
do {
252-
for try await http2StreamChannel in http2Multiplexer.inbound {
253-
connectionGroup.addTask {
254-
await Self.handleRequestChannel(
255-
logger: logger,
256-
channel: http2StreamChannel,
257-
handler: handler
258-
)
280+
try await withThrowingDiscardingTaskGroup { group in
281+
try await serverChannel.executeThenClose { inbound in
282+
for try await upgradeResult in inbound {
283+
group.addTask {
284+
do {
285+
try await withThrowingDiscardingTaskGroup { connectionGroup in
286+
switch try await upgradeResult.get() {
287+
case .http1_1(let http1Channel):
288+
connectionGroup.addTask {
289+
await Self.handleRequestChannel(
290+
logger: logger,
291+
channel: http1Channel,
292+
handler: handler
293+
)
294+
}
295+
case .http2((_, let http2Multiplexer)):
296+
do {
297+
for try await http2StreamChannel in http2Multiplexer.inbound {
298+
connectionGroup.addTask {
299+
await Self.handleRequestChannel(
300+
logger: logger,
301+
channel: http2StreamChannel,
302+
handler: handler
303+
)
304+
}
259305
}
306+
} catch {
307+
logger.debug("HTTP2 connection closed: \(error)")
260308
}
261-
} catch {
262-
logger.debug("HTTP2 connection closed: \(error)")
263309
}
264310
}
311+
} catch {
312+
logger.debug("Negotiating ALPN failed: \(error)")
265313
}
266-
} catch {
267-
logger.debug("Negotiating ALPN failed: \(error)")
268314
}
269315
}
270316
}
@@ -312,39 +358,8 @@ public final class Server<RequestHandler: HTTPServerRequestHandler> {
312358
// TODO: We need to send a response head here potentially
313359
}
314360
} catch {
315-
logger.debug("Error thrown while handling connection")
361+
logger.debug("Error thrown while handling connection: \(error)")
316362
// TODO: We need to send a response head here potentially
317363
}
318364
}
319-
320-
private static func bind(
321-
bindTarget: HTTPServerConfiguration.BindTarget,
322-
childChannelInitializer: @escaping @Sendable (any Channel) -> EventLoopFuture<
323-
EventLoopFuture<
324-
NIONegotiatedHTTPVersion<
325-
NIOAsyncChannel<HTTPRequestPart, HTTPResponsePart>,
326-
(Void, NIOHTTP2Handler.AsyncStreamMultiplexer<NIOAsyncChannel<HTTPRequestPart, HTTPResponsePart>>)
327-
>
328-
>
329-
>
330-
) async throws -> NIOAsyncChannel<
331-
EventLoopFuture<
332-
NIONegotiatedHTTPVersion<
333-
NIOAsyncChannel<HTTPRequestPart, HTTPResponsePart>,
334-
(Void, NIOHTTP2Handler.AsyncStreamMultiplexer<NIOAsyncChannel<HTTPRequestPart, HTTPResponsePart>>)
335-
>
336-
>, Never
337-
> {
338-
switch bindTarget.backing {
339-
case .hostAndPort(let host, let port):
340-
return try await ServerBootstrap(group: .singletonMultiThreadedEventLoopGroup)
341-
.serverChannelOption(.socketOption(.so_reuseaddr), value: 1)
342-
.bind(
343-
host: host,
344-
port: port,
345-
childChannelInitializer: childChannelInitializer
346-
)
347-
}
348-
349-
}
350365
}

0 commit comments

Comments
 (0)