Correct. If you want to setup a pipeline that supports both HTTP/1.1 and HTTP/2 then something has to do the protocol negotiation for you. This is mostly done via TLS as you discovered. RFC 9113 also explains how to start an HTTP/2 connection with prior knowledge.
If you are up for the latest async APIs that we are working on, this setups a connection that handles both HTTP/1.1 and HTTP/2 and provides you our new NIOAsyncChannel
based interfaces to handle incoming connections. (Omitted the sslContext creation)
let channel = try await ServerBootstrap(group: eventLoopGroup)
.bind(host: "127.0.0.1", port: 8080) { channel in
return channel.pipeline.addHandler(NIOSSLServerHandler(context: sslContext))
.flatMap {
channel.configureAsyncHTTPServerPipeline { http1ConnectionChannel in
http1ConnectionChannel.eventLoop.makeCompletedFuture {
try http1ConnectionChannel.pipeline.syncOperations.addHandler(HTTP1ToHTTPServerCodec(secure: false))
return try NIOAsyncChannel(
synchronouslyWrapping: http1ConnectionChannel,
configuration: .init(
inboundType: HTTPTypeRequestPart.self,
outboundType: HTTPTypeResponsePart.self
)
)
}
} http2ConnectionInitializer: { http2ConnectionChannel in
http2ConnectionChannel.eventLoop.makeCompletedFuture {
try NIOAsyncChannel(
synchronouslyWrapping: http2ConnectionChannel,
configuration: .init(
inboundType: HTTP2Frame.self,
outboundType: HTTP2Frame.self
)
)
}
} http2InboundStreamInitializer: { http2StreamChannel in
http2StreamChannel.eventLoop.makeCompletedFuture {
try http2StreamChannel.pipeline.syncOperations.addHandler(HTTP2FramePayloadToHTTPServerCodec())
return try NIOAsyncChannel(
synchronouslyWrapping: http2StreamChannel,
configuration: .init(
inboundType: HTTPTypeRequestPart.self,
outboundType: HTTPTypeResponsePart.self
)
)
}
}
}
}