diff --git a/Sources/HTTPServer/HTTPServer.swift b/Sources/HTTPServer/HTTPServer.swift index 81f905e..2f8391e 100644 --- a/Sources/HTTPServer/HTTPServer.swift +++ b/Sources/HTTPServer/HTTPServer.swift @@ -156,115 +156,161 @@ public final class Server { configuration: HTTPServerConfiguration, handler: RequestHandler ) async throws { - let serverChannel = try await Self.bind(bindTarget: configuration.bindTarget) { - (channel) -> EventLoopFuture< - EventLoopFuture< - NIONegotiatedHTTPVersion< - NIOAsyncChannel, - ( - Void, - NIOHTTP2Handler.AsyncStreamMultiplexer> + 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( + 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( - 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( + 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( - wrappingChannelSynchronously: channel, - configuration: .init(isOutboundHalfClosureEnabled: true) - ) + return try NIOAsyncChannel( + 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)") } } } @@ -312,39 +358,8 @@ public final class Server { // 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)") // 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, - (Void, NIOHTTP2Handler.AsyncStreamMultiplexer>) - > - > - > - ) async throws -> NIOAsyncChannel< - EventLoopFuture< - NIONegotiatedHTTPVersion< - NIOAsyncChannel, - (Void, NIOHTTP2Handler.AsyncStreamMultiplexer>) - > - >, 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 - ) - } - - } }