SwiftNIO proxyService combine HTTP/HTTPS handler Error

Create combine HTTP/HTTPS handler for proxy service, as in (Option to make Connect Proxy work with HTTP traffic?), but during the tests, get error, which I can't understand.

NIO/NIOAny.swift:200: Fatal error: tried to decode as type HTTPPart<HTTPRequestHead, ByteBuffer> but found IOData with contents ioData(IOData { ByteBuffer { readerIndex: 1104, writerIndex: 1917, readableBytes: 813, capacity: 2048, slice: _ByteBufferSlice { 0..<2048 }, storage: 0x0000000107131400 (2048 bytes) } })

Sometimes it appeared in HttpHttpsHandler func channelRead(), on trying to unwrap input data

stack trace
Crashed: NIO-ELT-#2
0 libswiftCore.dylib 0x3a514 assertionFailure(::file:line:flags:) + 304
1 cora-tunnel 0xc964 specialized NIOAny.forceAsOther(type:) + 4344695140
2 cora-tunnel 0xc604 specialized NIOAny.forceAs
(type:) + 4344694276
3 cora-tunnel 0x6ad8 HttpHttpsConnectHandler.channelRead(context:data:) + 81 (HttpHttpsConnectHandler.swift:81)
4 NIO 0x28934 ChannelHandlerContext.invokeChannelRead(
:) + 1332 (ChannelPipeline.swift:1332)
5 NIO 0x2895c ChannelHandlerContext.invokeChannelRead(:) + 1330 (ChannelPipeline.swift:1330)
6 NIO 0x25458 ChannelHandlerContext.fireChannelRead(
:) + 1141 (ChannelPipeline.swift:1141)
7 NIOHTTP1 0x7c04 HTTPDecoder.decodeLast(context:buffer:seenEOF:) + 617 (HTTPDecoder.swift:617)
8 NIOHTTP1 0x7d0c protocol witness for ByteToMessageDecoder.decodeLast(context:buffer:seenEOF:) in conformance HTTPDecoder<A, B> + 4353162508 (:4353162508)
9 NIO 0x38238 ByteToMessageHandler.decodeLoop(context:decodeMode:) + 456 (Codec.swift:456)
10 NIO 0x37c98 ByteToMessageHandler.processLeftovers(context:) + 427 (Codec.swift:427)
11 NIO 0x38b2c ByteToMessageHandler.channelInactive(context:) + 1136 (ChannelPipeline.swift:1136)
12 NIO 0x28844 ChannelHandlerContext.invokeChannelInactive() + 1322 (ChannelPipeline.swift:1322)

Another pleace is GlueHandlers partnerWrite(_:)

NIO/NIOAny.swift:200: Fatal error: tried to decode as type HTTPPart<HTTPRequestHead, IOData> but found HTTPPart<HTTPRequestHead, ByteBuffer> with contents other(NIOHTTP1.HTTPPart<NIOHTTP1.HTTPRequestHead, NIO.ByteBuffer>.head(HTTPRequestHead { method: GET, uri: "http://testingmcafeesites.com/favicon.ico", version: HTTP/1.1, headers: [(name: "Host", value: "testingmcafeesites.com"), (name: "Proxy-Connection", value: "keep-alive"), (name: "Accept", value: "image/*;q=0.8"), (name: "User-Agent", value: "Mozilla/5.0 (iPhone; CPU iPhone OS 15_2_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) FxiOS/96.0 Mobile/15E148 Safari/605.1.15"), (name: "Accept-Language", value: "en-GB,en;q=0.9"), (name: "Accept-Encoding", value: "gzip, deflate"), (name: "Connection", value: "keep-alive")] }))

Stacktrace
Crashed: NIO-ELT-#1
0 libswiftCore.dylib 0x3a514 assertionFailure(::file:line:flags:) + 304
1 NIO 0xb47f0 $s3NIO6NIOAnyV12forceAsOther4typexxm_tlF + 1784
2 NIO 0xb4af4 $s3NIO6NIOAnyV7forceAs4typexxm_tlF + 660
3 NIO 0x108b2c $s3NIO22ChannelOutboundHandlerPAAE06unwrapC2Iny0cF0QzAA6NIOAnyVF + 112
4 NIO 0x669c4 $s3NIO20ByteToMessageHandlerCA2A014WriteObservingbcD7DecoderRzrlE5write7context4data7promiseyAA07ChannelE7ContextC_AA6NIOAnyVAA16EventLoopPromiseVyytGSgtF + 724
5 NIO 0x66dc8 $s3NIO20ByteToMessageHandlerCyxGAA016_ChannelOutboundE0A2A014WriteObservingbcD7DecoderRzrlAaEP5write7context4data7promiseyAA0fE7ContextC_AA6NIOAnyVAA16EventLoopPromiseVyytGSgtFTW + 16
6 NIO 0x4ba5c $s3NIO21ChannelHandlerContextC11invokeWrite33_EEC863903996E9F191EBAFEB0FB0DFDDLL_7promiseyAA6NIOAnyV_AA16EventLoopPromiseVyytGSgtF + 1036
7 NIO 0x50cec $s3NIO21ChannelHandlerContextC5write_7promiseyAA6NIOAnyV_AA16EventLoopPromiseVyytGSgtF + 156
8 cora-tunnel 0x2e4a4 GlueHandler.partnerWrite(
:) + 56 (GlueHandler.swift:56)
9 cora-tunnel 0x31a24 GlueHandler.channelRead(context:data:) + 127 (GlueHandler.swift:127)

Why is this error can occurred, and what are ways to handle it?
reference to HttpHttpsHandler - GitHub - YuriyNess/SwiftNIO-Http-Https-handler: Http/Https-handler

@lukasa Can you look at my question, will be very thankful

Hi @YuriyNess,

the error you are seeing is happening because of the ChannelHandlers, that you are using have a type mismatch. Would you mind copy and pasting your current setup code for your channel handler pipeline here, for us to figure out the issue together? I suspect that you have an issue in the order of your ChannelHandlers.

Please remember that a ChannelHandler's InboundIn must match the previous ChannelHandler InboundOut for the read side. For the write side the OutboundIn must match the OutboundOut of the next ChannelHandler.

1 Like

Hi @fabianfett , this is pipeline

bootstrap = ServerBootstrap(group: groupWorker)
            .serverChannelOption(ChannelOptions.socket(SOL_SOCKET, SO_REUSEADDR), value: 1)
            .childChannelOption(ChannelOptions.socket(SOL_SOCKET, SO_REUSEADDR), value: 1)
            .childChannelInitializer { channel in
                channel.pipeline.addHandler(ByteToMessageHandler(HTTPRequestDecoder(leftOverBytesStrategy: .forwardBytes))).flatMap {
                    channel.pipeline.addHandler(HTTPResponseEncoder()).flatMap {
                        channel.pipeline.addHandler(HttpHttpsConnectHandler())
                    }
                }
            }

Hi @YuriyNess,

the problem in your code is, that the encoder must be before the decoder in the channel pipeline, as the decoder inspects the outgoing http parts (and requires them to be of type HTTPServerResponsePart). Just swapping adding the encoder and adding the decoder line should fix your issue.

Nonetheless I would strongly recommend to use the predefined methods in NIOHTTP1: channel.pipeline.configureHTTPServerPipeline(), as this already does what you want.

Hope that helps.

@fabianfett Thanks for reply. I though that when the data is "outgoing" it goes straigtn to encoder (and than dont go to decoder because, it is only for inbound events). Am I miss-understand this, and HTTPDecoder also take a part in outbound events?

public final class HTTPDecoder<In, Out>: ByteToMessageDecoder, HTTPDecoderDelegate {
    public typealias InboundOut = In

Oh sorry, I'm stupid. The HTTPDecoder also looks at the outgoing bytes, but only in client usecases. You are in a server use case.

Your HttpHttpsConnectHandler InboundIn typealias seems to be HTTPClientRequestPart, but it needs to be HTTPServerRequestPart.

@fabianfett no, my InboundIn = HTTPServerRequestPart

extension HttpHttpsConnectHandler: ChannelDuplexHandler {
    typealias InboundIn = HTTPServerRequestPart
    typealias InboundOut = HTTPClientRequestPart
    typealias OutboundIn = HTTPClientResponsePart
    typealias OutboundOut = HTTPServerResponsePart

Can you look at the link on HttpHttpsHandler GitHub - YuriyNess/SwiftNIO-Http-Https-handler: Http/Https-handler? it seems to me that I have problem with upgraded state, and at some moment (in channelRead) get simple Bytebuffer, which I cant unwrap to InboundIn type.

@YuriyNess What I don't understand is: Why do you receive IOData on your inbound connection? Do you have any other custom handlers on your inbound pipeline side?

@fabianfett no other custom handlers in pipeline.

Why do you receive IOData on your inbound connection?

Its not clear for me, what you mean. As I see in standart flow I revceive data: NIOAny in channelRead.
Or you mean why am I craete ChannelDuplexHandler?

Hey folks, the original crash was in $(extension in NIO):NIO.ChannelOutboundHandler.unwrapOutboundIn(NIO.NIOAny) -> A.OutboundIn: that is, unwrapping OutboundIn. Don't worry about the inbound connection, this is not your issue. Your issue is the way you've glued your pipelines together.

Here's the full demangled stack:

0 libswiftCore.dylib 0x3a514 assertionFailure(::file:line:flags:) + 304
1 NIO 0xb47f0 NIO.NIOAny.forceAsOther<A>(type: A.Type) -> A + 1784
2 NIO 0xb4af4 NIO.NIOAny.forceAs<A>(type: A.Type) -> A + 660
3 NIO 0x108b2c (extension in NIO):NIO.ChannelOutboundHandler.unwrapOutboundIn(NIO.NIOAny) -> A.OutboundIn + 112
4 NIO 0x669c4 (extension in NIO):NIO.ByteToMessageHandler< where A: NIO.WriteObservingByteToMessageDecoder>.write(context: NIO.ChannelHandlerContext, data: NIO.NIOAny, promise: NIO.EventLoopPromise<()>?) -> () + 724
5 NIO 0x66dc8 protocol witness for NIO._ChannelOutboundHandler.write(context: NIO.ChannelHandlerContext, data: NIO.NIOAny, promise: NIO.EventLoopPromise<()>?) -> () in conformance < where A: NIO.WriteObservingByteToMessageDecoder> NIO.ByteToMessageHandler<A> : NIO._ChannelOutboundHandler in NIO + 16
6 NIO 0x4ba5c NIO.ChannelHandlerContext.(invokeWrite in _EEC863903996E9F191EBAFEB0FB0DFDD)(_: NIO.NIOAny, promise: NIO.EventLoopPromise<()>?) -> () + 1036
7 NIO 0x50cec NIO.ChannelHandlerContext.write(_: NIO.NIOAny, promise: NIO.EventLoopPromise<()>?) -> () + 156
8 cora-tunnel 0x2e4a4 GlueHandler.partnerWrite(:) + 56 (GlueHandler.swift:56)
9 cora-tunnel 0x31a24 GlueHandler.channelRead(context:data:) + 127 (GlueHandler.swift:127)

This provides the story. We have read a message from one channel, where that message is of type HTTPPart<HTTPRequestHead, ByteBuffer> (better known as HTTPServerRequestPart). We have forwarded it on to the partner channel as a write, where that channel has a handler that is expecting to receive a HTTPPart<HTTPRequestHead, IOData> (better known as a HTTPClientRequestPart). Given that the handler is a ByteToMessageHandler, we can assume that this is HTTPResponseDecoder in the partner pipeline, as set up here. As @fabianfett noted above, HTTPResponseDecoder needs access to the outbound messages, and it's not getting what it expects.

This is a wrinkle when gluing together two HTTP channels back-to-back: when we read them they can only have ByteBuffer body types, but when we write them they may have ByteBuffer or FileRegion types. These aren't considered to be the same by the Swift type system, so you get this kind of error.

Your HttpHttpsConnectHandler is probably the source of this issue in the form of removeHandler, which does not appropriately transform the buffered channelRead calls.

@lukasa Thanks for reply. I have a lot of blind moments in understanding of NIO proxy, but I try to figure out what flow we have.

ServerBootstrap pipeline contains:

  • 1 HTTPResponseEncoder (outbound IN/OUT OutboundIn = HTTPServerResponsePart OutboundOut = IOData)
  • 2 ByteToMessageHandler (InboundIn = ByteBuffer, InboundOut = Decoder.InboundOut, OutboundIn = Decoder.OutboundIn)
  • 3 HTTPRequestDecoder (InboundOut = HTTPServerRequestPart)
  • 4 HttpHttpsConnectHandler (duplex inbound IN, outbound IN/OUT)
  • 5 GlueHandler2 (duplex inbound IN, outbound IN/OUT - Bytebuffer)

HttpHttpsConnectHandler detecs is it HTTP scheme or method CONNECT and create appropriate ClientBootstrap.

For HTTP we create ClientBootstrap and it pipeline contains:

  • 1 HTTPRequestEncoder (OutboundIn = HTTPClientRequestPart, OutboundOut = IOData)

  • 2 ByteToMessageHandler (InboundIn = ByteBuffer, InboundOut = Decoder.InboundOut, OutboundIn = Decoder.OutboundIn)

  • 3 HTTPResponseDecoder (InboundOut = In HTTPClientResponsePart , OutboundIn = HTTPClientRequestPart)

  • 4 GlueHandler1 (duplex inbound IN, outbound IN/OUT - Bytebuffer)

After ClientBootstrap connected to remote server, we put our data to GlueHandler2 (in server bootstrap) and it makes partnerWrite -> which invokes clinetBootsrap handlers to work. HttpHttpsConnectHandler InboundOut = HTTPClientRequestPart, GlueHandler1 get is as NIOAny and just forward it to ByteToMessageHandler (which wraps HTTPResponseDecoder) HTTPResponseDecoder OutboundIn = HTTPClientRequestPart.

For this moment, as i see, types corrrect we send HTTPClientRequestPart and get this type in HTTPResponseDecoder.

I dont find that ByteToMessageHandler has OutboundOut type, probably it can be the same as OutboundIn.

The last HTTPRequestEncoder OutboundIn = HTTPClientRequestPart OutboundOut = IOData

As you mentioned above for some reasons Decoder gets HTTPServerRequestPart but want HTTPClientRequestPart.

You also write that sometimes GluHandler can get FileRegion type, why is this can happened? May be on try to get some special resources?

HttpHttpsConnectHandler for HTTP ClinetBootstrap doesnt remove any handlers from pipeline after connection success.

Does the problem in HTTP bottstrap? What bootstrap should be transformed by the (removeHandler)? May be smth wrong in my understanding of how flow should be?

And the one more crash with the same errro appeared in

HttpHttpsConnectHandler

func channelRead(context: ChannelHandlerContext, data: NIOAny) {
        switch self.upgradeState {
        case .idle, .pendingConnection(head: _), .connected:
            self.handleInitialMessage(context: context, data:self.unwrapInboundIn(data))

self.unwrapInboundIn(data))

Error NIO-ELT-#1 (4): Fatal error: tried to decode as type HTTPPart<HTTPRequestHead, ByteBuffer> but found IOData with contents ioData(IOData { ByteBuffer { readerIndex: 0, writerIndex: 813, readableBytes: 813, capacity: 2048, slice: _ByteBufferSlice { 0..<2048 }, storage: 0x0000000104846000 (2048 bytes) } })

The problem with this error removed after update NIO from 2 -> 2.42