Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
253 changes: 134 additions & 119 deletions Sources/HTTPServer/HTTPServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -156,115 +156,161 @@ public final class Server<RequestHandler: HTTPServerRequestHandler> {
configuration: HTTPServerConfiguration,
handler: RequestHandler
) async throws {
let serverChannel = try await Self.bind(bindTarget: configuration.bindTarget) {
(channel) -> EventLoopFuture<
EventLoopFuture<
NIONegotiatedHTTPVersion<
NIOAsyncChannel<HTTPRequestPart, HTTPResponsePart>,
(
Void,
NIOHTTP2Handler.AsyncStreamMultiplexer<NIOAsyncChannel<HTTPRequestPart, HTTPResponsePart>>
switch configuration.tlSConfiguration.backing {
case .insecure:
try await Self.serveInsecureHTTP1_1(
bindTarget: configuration.bindTarget,
handler: handler,
logger: logger
)

case .certificateChainAndPrivateKey(let certificateChain, let privateKey):
try await Self.serveSecureUpgrade(
bindTarget: configuration.bindTarget,
certificateChain: certificateChain,
privateKey: privateKey,
handler: handler,
logger: logger
)
}
}

private static func serveInsecureHTTP1_1(
bindTarget: HTTPServerConfiguration.BindTarget,
handler: RequestHandler,
logger: Logger
) async throws {
switch bindTarget.backing {
case .hostAndPort(let host, let port):
let serverChannel = try await ServerBootstrap(group: .singletonMultiThreadedEventLoopGroup)
.serverChannelOption(.socketOption(.so_reuseaddr), value: 1)
.bind(host: host, port: port) { channel in
channel.pipeline.configureHTTPServerPipeline().flatMapThrowing {
try channel.pipeline.syncOperations.addHandler(HTTP1ToHTTPServerCodec(secure: false))
return try NIOAsyncChannel<HTTPRequestPart, HTTPResponsePart>(
wrappingChannelSynchronously: channel,
configuration: .init(isOutboundHalfClosureEnabled: true)
)
>
>
> in
channel.eventLoop.makeCompletedFuture {
switch configuration.tlSConfiguration.backing {
case .insecure:
break
}
}

case .certificateChainAndPrivateKey(let certificateChain, let privateKey):
let certificateChain =
try certificateChain
.map {
try NIOSSLCertificate(
bytes: $0.serializeAsPEM().derBytes,
format: .der
try await withThrowingDiscardingTaskGroup { group in
try await serverChannel.executeThenClose { inbound in
for try await http1Channel in inbound {
group.addTask {
await Self.handleRequestChannel(
logger: logger,
channel: http1Channel,
handler: handler
)
}
.map { NIOSSLCertificateSource.certificate($0) }
let privateKey = NIOSSLPrivateKeySource.privateKey(
try NIOSSLPrivateKey(
bytes: privateKey.serializeAsPEM().derBytes,
format: .der
}
}
}
}
}

private static func serveSecureUpgrade(
bindTarget: HTTPServerConfiguration.BindTarget,
certificateChain: [Certificate],
privateKey: Certificate.PrivateKey,
handler: RequestHandler,
logger: Logger
) async throws {
switch bindTarget.backing {
case .hostAndPort(let host, let port):
let serverChannel = try await ServerBootstrap(group: .singletonMultiThreadedEventLoopGroup)
.serverChannelOption(.socketOption(.so_reuseaddr), value: 1)
.bind(host: host, port: port) { channel in
channel.eventLoop.makeCompletedFuture {
let certificateChain = try certificateChain
.map {
try NIOSSLCertificate(
bytes: $0.serializeAsPEM().derBytes,
format: .der
)
}
.map { NIOSSLCertificateSource.certificate($0) }
let privateKey = NIOSSLPrivateKeySource.privateKey(
try NIOSSLPrivateKey(
bytes: privateKey.serializeAsPEM().derBytes,
format: .der
)
)
)

try channel.pipeline.syncOperations
.addHandler(
NIOSSLServerHandler(
context: .init(
configuration:
.makeServerConfiguration(
try channel.pipeline.syncOperations
.addHandler(
NIOSSLServerHandler(
context: .init(
configuration: .makeServerConfiguration(
certificateChain: certificateChain,
privateKey: privateKey
)
)
)
)
)
}
}.flatMap {
channel
.configureAsyncHTTPServerPipeline { channel in
channel.eventLoop.makeCompletedFuture {
try channel.pipeline.syncOperations.addHandler(HTTP1ToHTTPServerCodec(secure: false))
}.flatMap {
channel.configureAsyncHTTPServerPipeline { channel in
channel.eventLoop.makeCompletedFuture {
try channel.pipeline.syncOperations.addHandler(HTTP1ToHTTPServerCodec(secure: true))

return try NIOAsyncChannel<HTTPRequestPart, HTTPResponsePart>(
wrappingChannelSynchronously: channel,
configuration: .init(isOutboundHalfClosureEnabled: true)
)
}
} http2ConnectionInitializer: { channel in
channel.eventLoop.makeSucceededVoidFuture()
} http2StreamInitializer: { channel in
channel.eventLoop.makeCompletedFuture {
try channel.pipeline.syncOperations
.addHandler(
HTTP2FramePayloadToHTTPServerCodec()
return try NIOAsyncChannel<HTTPRequestPart, HTTPResponsePart>(
wrappingChannelSynchronously: channel,
configuration: .init(isOutboundHalfClosureEnabled: true)
)
}
} http2ConnectionInitializer: { channel in
channel.eventLoop.makeSucceededVoidFuture()
} http2StreamInitializer: { channel in
channel.eventLoop.makeCompletedFuture {
try channel.pipeline.syncOperations
.addHandler(
HTTP2FramePayloadToHTTPServerCodec()
)

return try NIOAsyncChannel<HTTPRequestPart, HTTPResponsePart>(
wrappingChannelSynchronously: channel,
configuration: .init(isOutboundHalfClosureEnabled: true)
)
return try NIOAsyncChannel<HTTPRequestPart, HTTPResponsePart>(
wrappingChannelSynchronously: channel,
configuration: .init(isOutboundHalfClosureEnabled: true)
)
}
}
}
}
}
}

try await withThrowingDiscardingTaskGroup { group in
try await serverChannel.executeThenClose { inbound in
for try await upgradeResult in inbound {
group.addTask {
do {
try await withThrowingDiscardingTaskGroup { connectionGroup in
switch try await upgradeResult.get() {
case .http1_1(let http1Channel):
connectionGroup.addTask {
await Self.handleRequestChannel(
logger: logger,
channel: http1Channel,
handler: handler
)
}
case .http2((_, let http2Multiplexer)):
do {
for try await http2StreamChannel in http2Multiplexer.inbound {
connectionGroup.addTask {
await Self.handleRequestChannel(
logger: logger,
channel: http2StreamChannel,
handler: handler
)
try await withThrowingDiscardingTaskGroup { group in
try await serverChannel.executeThenClose { inbound in
for try await upgradeResult in inbound {
group.addTask {
do {
try await withThrowingDiscardingTaskGroup { connectionGroup in
switch try await upgradeResult.get() {
case .http1_1(let http1Channel):
connectionGroup.addTask {
await Self.handleRequestChannel(
logger: logger,
channel: http1Channel,
handler: handler
)
}
case .http2((_, let http2Multiplexer)):
do {
for try await http2StreamChannel in http2Multiplexer.inbound {
connectionGroup.addTask {
await Self.handleRequestChannel(
logger: logger,
channel: http2StreamChannel,
handler: handler
)
}
}
} catch {
logger.debug("HTTP2 connection closed: \(error)")
}
} catch {
logger.debug("HTTP2 connection closed: \(error)")
}
}
} catch {
logger.debug("Negotiating ALPN failed: \(error)")
}
} catch {
logger.debug("Negotiating ALPN failed: \(error)")
}
}
}
Expand Down Expand Up @@ -312,39 +358,8 @@ public final class Server<RequestHandler: HTTPServerRequestHandler> {
// TODO: We need to send a response head here potentially
}
} catch {
logger.debug("Error thrown while handling connection")
logger.debug("Error thrown while handling connection: \(error)")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to revisit the logs to not interpolate in the future

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I can open a PR to do this - should we include the error as metadata instead?

// TODO: We need to send a response head here potentially
}
}

private static func bind(
bindTarget: HTTPServerConfiguration.BindTarget,
childChannelInitializer: @escaping @Sendable (any Channel) -> EventLoopFuture<
EventLoopFuture<
NIONegotiatedHTTPVersion<
NIOAsyncChannel<HTTPRequestPart, HTTPResponsePart>,
(Void, NIOHTTP2Handler.AsyncStreamMultiplexer<NIOAsyncChannel<HTTPRequestPart, HTTPResponsePart>>)
>
>
>
) async throws -> NIOAsyncChannel<
EventLoopFuture<
NIONegotiatedHTTPVersion<
NIOAsyncChannel<HTTPRequestPart, HTTPResponsePart>,
(Void, NIOHTTP2Handler.AsyncStreamMultiplexer<NIOAsyncChannel<HTTPRequestPart, HTTPResponsePart>>)
>
>, Never
> {
switch bindTarget.backing {
case .hostAndPort(let host, let port):
return try await ServerBootstrap(group: .singletonMultiThreadedEventLoopGroup)
.serverChannelOption(.socketOption(.so_reuseaddr), value: 1)
.bind(
host: host,
port: port,
childChannelInitializer: childChannelInitializer
)
}

}
}
Loading