首页 > 解决方案 > 基于 Swift-NIO 的代理在每个传入请求上重新配置管道

问题描述

我正在使用 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 响应。那太好了。但是在日志中,我看到对于每个传入的请求,都在重新配置管道(即一遍又一遍地调用附加处理程序的转义)。这是它应该工作的方式吗?据我所知,我不会在任何地方关闭上下文/频道。这个代理是否为每个请求设置了一个新的管道,因此我是否错过了通过同一通道(和管道)发送大量请求的 Http/2 方法?或者这实际上是它应该工作的方式?感觉不是最理想的结果...

日志:

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每次都不一样...

标签: iosswiftproxyhttp2swift-nio

解决方案


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

卢卡萨的回答:

这是预期的行为。HTTP/2 是多路复用的:这意味着您可以在同一个 TCP 连接上运行多个请求/响应序列。这在 SwiftNIO HTTP/2 中以“流通道初始化器”的形式体现:每个流创建都会调用一次。这里的流通道初始化器是传递给 configureCommonHTTPServerPipeline 的尾随闭包。

如果您只想创建一次处理程序,您可以这样做。但是现在您的处理程序需要支持参与多个并发请求和响应。


推荐阅读