iOS HTTP traffic use SwiftNIO

Hello, i am using SwiftNIO as local proxy server to traffic HTTP. I have two problem right now:

1 By using the official example ConnecteHandler to handle HTTPS, I implement ProxyHandler to handle HTTP, I encountered the following error, it seem that i have received the respond, but error in type conversion

NIOCore/NIOAny.swift:200: Fatal error: tried to decode as type HTTPPart<HTTPResponseHead, IOData> but found HTTPPart<HTTPResponseHead, ByteBuffer> with contents other(NIOHTTP1.HTTPPart<NIOHTTP1.HTTPResponseHead, NIOCore.ByteBuffer>.head(HTTPResponseHead .....

here is the code of ProxyHandler, the GlueHandler use the official example:

final class HTTPConnectHandler: ChannelDuplexHandler, RemovableChannelHandler {
    func removeHandler(context: ChannelHandlerContext, removalToken: ChannelHandlerContext.RemovalToken) {

        if case let .pendingConnection(head) = self.state {
            self.state = .connected
            context.fireChannelRead(self.wrapInboundOut(.head(head)))
            
            if let bufferedBody = self.bufferedBody {
                context.fireChannelRead(self.wrapInboundOut(.body(.byteBuffer(bufferedBody))))
                self.bufferedBody = nil
            }
            
            if let bufferedEnd = self.bufferedEnd {
                context.fireChannelRead(self.wrapInboundOut(.end(bufferedEnd)))
                self.bufferedEnd = nil
            }
                        
            context.fireChannelReadComplete()
        }
        
        context.leavePipeline(removalToken: removalToken)
    }
    
    enum State {
        case idle
        case pendingConnection(head: HTTPRequestHead)
        case connected
    }
    
    enum ConnectError: Error {
        case invalidURL
        case wrongScheme
        case wrongHost
    }

    typealias InboundIn = HTTPServerRequestPart
    typealias InboundOut = HTTPClientRequestPart
    typealias OutboundIn = HTTPClientResponsePart
    typealias OutboundOut = HTTPServerResponsePart
    
    private var state = State.idle
    
    private var bufferedBody: ByteBuffer?
    private var bufferedEnd: HTTPHeaders?
    
    func channelRead(context: ChannelHandlerContext, data: NIOAny) {
        
        guard case .head(var head) = self.unwrapInboundIn(data) else {
            let unwrapped = self.unwrapInboundIn(data)
            
            switch unwrapped {
            case .body(let buffer):
                
                switch state {
                case .connected:
                    context.fireChannelRead(self.wrapInboundOut(.body(.byteBuffer(buffer))))
                case .pendingConnection(_):
                    self.bufferedBody = buffer
                default:
                    // shouldnt happen
                    break
                }
                
            case .end(let headers):
                switch state {
                case .connected:
                    context.fireChannelRead(self.wrapInboundOut(.end(headers)))
                case .pendingConnection(_):
                    self.bufferedEnd = headers
                default:
                    // shouldnt happen
                    break
                }
                
            case .head(_):
                assertionFailure("Not possible")
                break
            }
            return
        }
        
        guard let parsedUrl = URL(string: head.uri) else {
            context.fireErrorCaught(ConnectError.invalidURL)
            return
        }
        
        guard parsedUrl.scheme == "http" else {
            context.fireErrorCaught(ConnectError.wrongScheme)
            return
        }
        
        guard let host = head.headers.first(name: "Host"), host == parsedUrl.host else {
            context.fireErrorCaught(ConnectError.wrongHost)
            return
        }
        
        var targetUrl = parsedUrl.path
        
        if let query = parsedUrl.query {
            targetUrl += "?\(query)"
        }
        
        head.uri = targetUrl
        
        switch state {
        case .idle:
            state = .pendingConnection(head: head)
            connectTo(host: host, port: 80, context: context)
        case .pendingConnection(_):
            break
        case .connected:
            context.fireChannelRead(self.wrapInboundOut(.head(head)))
        }
        
    }
    
    
    func write(context: ChannelHandlerContext, data: NIOAny, promise: EventLoopPromise<Void>?) {
        switch self.unwrapOutboundIn(data) {
        case .head(let head):
            context.write(self.wrapOutboundOut(.head(head)), promise: nil)
        case .body(let body):
            context.write(self.wrapOutboundOut(.body(.byteBuffer(body))), promise: nil)
        case .end(let trailers):
            context.write(self.wrapOutboundOut(.end(trailers)), promise: nil)
        }
    }
    
    private func connectTo(host: String, port: Int, context: ChannelHandlerContext) {
        let channelFuture = ClientBootstrap(group: context.eventLoop)
            .channelInitializer { channel in
                channel.pipeline.addHandler(HTTPRequestEncoder()).flatMap {
                    channel.pipeline.addHandler(ByteToMessageHandler(HTTPResponseDecoder()))
                }
            }
            .connect(host: host, port: port)
        
        

        channelFuture.whenSuccess { channel in
            self.connectSucceeded(channel: channel, context: context)
        }
        channelFuture.whenFailure { error in
            self.connectFailed(error: error, context: context)
        }
    }
    
    private func connectSucceeded(channel: Channel, context: ChannelHandlerContext) {
        self.glue(channel, context: context)
    }

    private func connectFailed(error: Error, context: ChannelHandlerContext) {
        context.fireErrorCaught(error)
    }
    
    private func glue(_ peerChannel: Channel, context: ChannelHandlerContext) {

        self.removeEncoder(context: context)

        let (localGlue, peerGlue) = GlueHandler.matchedPair()
        context.channel.pipeline.addHandler(localGlue).and(peerChannel.pipeline.addHandler(peerGlue)).whenComplete { result in
            switch result {
            case .success(_):
                context.pipeline.removeHandler(self, promise: nil)
            case .failure(_):
                peerChannel.close(mode: .all, promise: nil)
                context.close(promise: nil)
            }
        }
    }
    
    private func removeEncoder(context: ChannelHandlerContext) {
        context.pipeline.context(handlerType: HTTPResponseEncoder.self).whenSuccess {_ in 
            context.pipeline.removeHandler(context: $0, promise: nil)
        }
    }
}

2 How to configure ServerBootstrap so that https is processed using connecHandler and http is processed using proxyhandler

I am a new user of swiftNIO and hope to get your feedback.
Thanks :grinning:

Do you have the full call stack for that error?

This is the stack stack in xcode

it seems that we have got the respond from server

Do you have any suggestions for me to solve this problem? Thank you

This doesn't appear to be passing through your connect handler at all, so you need a translation handler for the plaintext HTTP case.

This is an HTTP get request. I think it does not need to be processed by the connect handler? Am I right?

this is the request:

GET http://sns-img-bd.xhscdn.com/1040g2sg3101r9oed60005pevugrh93hnr2kugd8?imageView2/2/w/540/format/jpg/q/75%7CimageMogr2/strip&redImage/frame/0 HTTP/1.1

Host: sns-img-bd.xhscdn.com
Connection: keep-alive
Accept: image/*,*/*;q=0.8
X-XHS-TraceId: c26ea633ef6340dfbe284de5af79fa76
X-Net-Core: crn
User-Agent: discover/8.26 (iPhone; iOS 16.0; Scale/2.00)
Referer: https://app.xhs.cn/
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en

That's fine, but you need to have a different handler that does the type translation that HTTPConnectHandler does:

    typealias InboundIn = HTTPServerRequestPart
    typealias InboundOut = HTTPClientRequestPart
    typealias OutboundIn = HTTPClientResponsePart
    typealias OutboundOut = HTTPServerResponsePart