Swift-基于 NIO 的代理在每个传入请求上重新配置管道

Swift-NIO based proxy reconfigures pipeline on every incoming request

我正在使用 Swift-NIO 为 iOS/tvOS 创建一个带 TLS 的 Http/2 代理。我的代理启动:

var tlsConfiguration = ...
tlsConfiguration.applicationProtocols = NIOHTTP2SupportedALPNProtocols

let bootstrap = NIOTSListenerBootstrap(group: loopGroup)
.serverChannelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1)
.serverChannelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEPORT), value: 1)
.childChannelInitializer { channel in
    let sslContext: NIOSSLContext
    let tlsHandler: NIOSSLServerHandler
    do {
         sslContext = try NIOSSLContext(configuration: tlsConfiguration)
         tlsHandler = NIOSSLServerHandler(context: sslContext)
    } catch {
         print("[HTTP2PROXY] Could not configure TLS")
         return channel.close(mode: .all)
    }
    
    return channel.pipeline.addHandler(tlsHandler, name: "TLS_Handler").flatMap {
        print("[HTTP2PROXY] TLSHandler added to pipeline")
        print("[HTTP2PROXY] Configuring pipeline for Http/1.1 and Http/2")
        return channel.configureCommonHTTPServerPipeline(h2ConnectionChannelConfigurator: nil) { streamChannel in
            return streamChannel.pipeline.addHandlers([DebugInboundEventsHandler(), DebugOutboundEventsHandler()]).flatMap {
                print("[HTTP2PROXY] Event debugger handlers added")
                return streamChannel.pipeline.addHandler(HTTPResponseCompressor(), name: "ResponseCompressor")
                }.flatMap {
                     print("[HTTP2PROXY] HTTPResponseCompressor added to pipeline")
                     return streamChannel.pipeline.addHandler(CustomHttp1Handler(hlsRequestHandler: self.hlsRequestHandler), name: "Custom_Http1")
                }.flatMap {
                     print("[HTTP2PROXY] Custom Http1Handler added to pipeline")
                     return streamChannel.pipeline.addHandler(ErrorHandler())
                }
            }
        }
    }
.childChannelOption(ChannelOptions.socket(IPPROTO_TCP, TCP_NODELAY), value: 1)
.childChannelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEADDR), value: 1)
.childChannelOption(ChannelOptions.socket(SocketOptionLevel(SOL_SOCKET), SO_REUSEPORT), value: 1)
do {
    let serverChannel = try bootstrap.bind(host: Http2Proxy.host, port: Http2Proxy.port).wait()
    print("[HTTP2PROXY] Server Channel bound to: \(serverChannel.localAddress!)")
}
catch {
    try! loopGroup.syncShutdownGracefully()
    print("[HTTP2PROXY] Failed to start channel: \(error)")
}

服务器工作并使用 Https over Http/2 正确处理请求,甚至在询问时 gzip 响应。那太好了。但在日志中,我看到对于每个传入请求,管道都在重新配置(即,用于附加处理程序的转义被一遍又一遍地调用)。这是它应该工作的方式吗? 据我所知,我不会在任何地方关闭 context/channel。这个代理是否为每个请求设置了一个新的管道,因此我是否错过了通过同一通道(和管道)发送大量请求的 Http/2 方法?或者这实际上是它应该工作的方式吗?感觉是 none 最佳结果...

日志:

10:21:15.760 [HTTP2PROXY] Server Channel bound to: [IPv4]127.0.0.1/127.0.0.1:50001
10:22:47.813 [HTTP2PROXY] TLSHandler added to pipeline
10:22:47.813 [HTTP2PROXY] Configuring pipeline for Http/1.1 and Http/2
10:22:47.837 [HTTP2PROXY] Event debugger handlers added
10:22:47.838 [HTTP2PROXY] HTTPResponseCompressor added to pipeline
10:22:47.838 [HTTP2PROXY] Custom Http1Handler added to pipeline
10:22:47.853 [HTTP2PROXY] Event debugger handlers added
10:22:47.853 [HTTP2PROXY] HTTPResponseCompressor added to pipeline
10:22:47.853 [HTTP2PROXY] Custom Http1Handler added to pipeline
10:22:47.854 [HTTP2PROXY] Event debugger handlers added
10:22:47.854 [HTTP2PROXY] HTTPResponseCompressor added to pipeline
10:22:47.854 [HTTP2PROXY] Custom Http1Handler added to pipeline
10:22:47.860 [HTTP2PROXY] Event debugger handlers added
10:22:47.860 [HTTP2PROXY] HTTPResponseCompressor added to pipeline
10:22:47.860 [HTTP2PROXY] Custom Http1Handler added to pipeline
10:22:47.861 [HTTP2PROXY] Event debugger handlers added
10:22:47.861 [HTTP2PROXY] HTTPResponseCompressor added to pipeline
10:22:47.861 [HTTP2PROXY] Custom Http1Handler added to pipeline
10:22:47.927 [HTTP2PROXY] Event debugger handlers added
10:22:47.927 [HTTP2PROXY] HTTPResponseCompressor added to pipeline
10:22:47.927 [HTTP2PROXY] Custom Http1Handler added to pipeline

记录 context.channel.pipeline.debugDescription 给出:

10:30:41.083 [HTTP2PROXY] Pipeline config: 
ChannelPipeline[ObjectIdentifier(0x00000002804d6fd0)]:
                                 [I] ↓↑ [O]
 HTTP2FramePayloadToHTTP1ServerCodec ↓↑ HTTP2FramePayloadToHTTP1ServerCodec [handler0]
              HTTPResponseCompressor ↓↑ HTTPResponseCompressor              [ResponseCompressor]
                  CustomHttp1Handler ↓↑                                     [Custom_Http1]
                        ErrorHandler ↓↑                                     [handler1]
10:30:41.087 [HTTP2PROXY] Pipeline config: 
ChannelPipeline[ObjectIdentifier(0x00000002804d7160)]:
                                 [I] ↓↑ [O]
 HTTP2FramePayloadToHTTP1ServerCodec ↓↑ HTTP2FramePayloadToHTTP1ServerCodec [handler0]
              HTTPResponseCompressor ↓↑ HTTPResponseCompressor              [ResponseCompressor]
                  CustomHttp1Handler ↓↑                                     [Custom_Http1]
                        ErrorHandler ↓↑                                     [handler1]
10:30:41.090 [HTTP2PROXY] Pipeline config: 
ChannelPipeline[ObjectIdentifier(0x00000002804d7610)]:
                                 [I] ↓↑ [O]
 HTTP2FramePayloadToHTTP1ServerCodec ↓↑ HTTP2FramePayloadToHTTP1ServerCodec [handler0]
              HTTPResponseCompressor ↓↑ HTTPResponseCompressor              [ResponseCompressor]
                  CustomHttp1Handler ↓↑                                     [Custom_Http1]
                        ErrorHandler ↓↑                                     [handler1]
10:30:41.100 [HTTP2PROXY] Pipeline config: 
ChannelPipeline[ObjectIdentifier(0x00000002804d71b0)]:
                                 [I] ↓↑ [O]
 HTTP2FramePayloadToHTTP1ServerCodec ↓↑ HTTP2FramePayloadToHTTP1ServerCodec [handler0]
              HTTPResponseCompressor ↓↑ HTTPResponseCompressor              [ResponseCompressor]
                  CustomHttp1Handler ↓↑                                     [Custom_Http1]
                        ErrorHandler ↓↑                                     [handler1]

所以管道的objectIdentifier每次都不一样...

此问题已在 swift 论坛上处理 https://forums.swift.org/t/swift-nio-based-proxy-reconfigures-pipeline-on-every-incoming-request/52043

卢卡萨的回答:

This is expected behaviour. HTTP/2 is multiplexed: this means you can run multiple request/response sequences over the same TCP connection. This manifests in SwiftNIO HTTP/2 in the form of the "stream channel initializer": this is called once per stream creation. The stream channel initializer here is the trailing closure being passed to configureCommonHTTPServerPipeline.

If you would like to create the handlers only once, you can do that. But now your handlers need to support being involved in multiple concurrent requests and responses.