HTTP website needs a refresh in order to load

Hi,

Im using NIO proxy server in order to forward all the traffic from my NEPacketTunnelProvider in iOS.
HTTPS works fine all the apps and websites load good, however when I try to load HTTP website, it doesn't load and I need to refresh the website in order to load it.

this is my handler:

//
//  ConnectionHandler.swift

import Foundation


import NIOCore
import NIOPosix
import NIOHTTP1
import Common

final class ConnectHandler {
    private var upgradeState: State
    
    
    init() {
        self.upgradeState = .idle
    }
}


extension ConnectHandler {
    fileprivate enum State {
        case idle
        case beganConnecting
        case awaitingEnd(connectResult: Channel)
        case awaitingConnection(pendingBytes: [NIOAny])
        case upgradeComplete(pendingBytes: [NIOAny])
        case upgradeFailed
    }
}


extension ConnectHandler: ChannelInboundHandler {
    typealias InboundIn = HTTPServerRequestPart
    typealias OutboundOut = HTTPServerResponsePart
    
    func channelRead(context: ChannelHandlerContext, data: NIOAny) {
        Logger.networkInfo("channelRead")
        switch self.upgradeState {
        case .idle:
            self.handleInitialMessage(context: context, data: self.unwrapInboundIn(data))
            
        case .beganConnecting:
            // We got .end, we're still waiting on the connection
            if case .end = self.unwrapInboundIn(data) {
                self.upgradeState = .awaitingConnection(pendingBytes: [])
                self.removeDecoder(context: context)
            }
            
        case .awaitingEnd(let peerChannel):
            if case .end = self.unwrapInboundIn(data) {
                // Upgrade has completed!
                self.upgradeState = .upgradeComplete(pendingBytes: [])
                self.removeDecoder(context: context)
                self.glue(peerChannel, context: context)
            } else if case .head(let head) = self.unwrapInboundIn(data) {
                // Forward the GET request to the server
                forwardRequestToServer(requestHead: head, context: context)
            }
            
        case .awaitingConnection(var pendingBytes):
            // We've seen end, this must not be HTTP anymore. Danger, Will Robinson! Do not unwrap.
            self.upgradeState = .awaitingConnection(pendingBytes: [])
            pendingBytes.append(data)
            self.upgradeState = .awaitingConnection(pendingBytes: pendingBytes)
            
        case .upgradeComplete(pendingBytes: var pendingBytes):
            // We're currently delivering data, keep doing so.
            self.upgradeState = .upgradeComplete(pendingBytes: [])
            pendingBytes.append(data)
            self.upgradeState = .upgradeComplete(pendingBytes: pendingBytes)
            
        case .upgradeFailed:
            break
        }
    }
    
    func handlerAdded(context: ChannelHandlerContext) {
        // Add logger metadata.
    }
}


extension ConnectHandler: RemovableChannelHandler {
    func removeHandler(context: ChannelHandlerContext, removalToken: ChannelHandlerContext.RemovalToken) {
        var didRead = false
        Logger.networkInfo("removeHandler")
        // We are being removed, and need to deliver any pending bytes we may have if we're upgrading.
        while case .upgradeComplete(var pendingBytes) = self.upgradeState, pendingBytes.count > 0 {
            // Avoid a CoW while we pull some data out.
            self.upgradeState = .upgradeComplete(pendingBytes: [])
            let nextRead = pendingBytes.removeFirst()
            self.upgradeState = .upgradeComplete(pendingBytes: pendingBytes)
            
            context.fireChannelRead(nextRead)
            didRead = true
        }
        
        if didRead {
            context.fireChannelReadComplete()
        }
        
        Logger.networkInfo("Removing \(self) from pipeline")
        context.leavePipeline(removalToken: removalToken)
    }
}

extension ConnectHandler {
    private func handleInitialMessage(context: ChannelHandlerContext, data: InboundIn) {
        Logger.networkInfo("handleInitialMessage")

        guard case .head(let head) = data else {
            Logger.networkInfo("Invalid HTTP message type \(data)")
            self.httpErrorAndClose(context: context)
            return
        }
        
        Logger.networkInfo("\(head.method) \(head.uri) \(head.version)")
        guard let url = URL(string: head.uri) else {
            return
            
        }
        let components = head.uri.split(separator: ":", maxSplits: 1, omittingEmptySubsequences: false)
        var host:String = String(components.first!)  // There will always be a first.
        var port = components.last.flatMap { Int($0, radix: 10) } ?? 80  // Port 80 if not specified
        if head.uri.hasPrefix("http://"){
            host = url.host!
            port = 80
        }
//        if host.hasPrefix("www."){
//            host.trimPrefix("www.")
//        }
//        if head.method == .CONNECT {
//            self.upgradeState = .beganConnecting
//            self.connectTo(host: String(host), port: port, context: context)
//        } else {
//            Logger.networkInfo("Invalid HTTP method: \(head.method)")
////            self.httpErrorAndClose(context: context)
//        }
        
        
        self.upgradeState = .beganConnecting
        self.connectTo(host: String(host), port: port, context: context)
        
    }
    
    private func forwardRequestToServer(requestHead: HTTPRequestHead, context: ChannelHandlerContext) {
        Logger.log("forwardRequestToServer")

        guard let host = requestHead.headers["Host"].first else {
            // Handle error: Host header is missing
            return
        }
        
        let channelFuture = ClientBootstrap(group: context.eventLoop)
            .connect(host: host, port: 80)
        
        channelFuture.whenSuccess { channel in
            // Create a new response head based on the request head
            let responseHead = HTTPResponseHead(version: requestHead.version, status: .ok, headers: requestHead.headers)
            
            // Write the response head to the server channel
            channel.write(self.wrapOutboundOut(.head(responseHead)), promise: nil)
            channel.writeAndFlush(self.wrapOutboundOut(.end(nil)), promise: nil)
            
            // Set the upgrade state to handle the server response
            self.upgradeState = .awaitingEnd(connectResult: channel)
        }
        
        channelFuture.whenFailure { error in
            // Handle connection failure
            self.connectFailed(error: error, context: context)
        }
    }
    
    private func connectTo(host: String, port: Int, context: ChannelHandlerContext) {
        Logger.networkInfo("Connecting to \(host):\(port)")
        
        let channelFuture = ClientBootstrap(group: context.eventLoop)
            .connect(host: String(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) {
        Logger.networkInfo("Connected to \(String(describing: channel.remoteAddress))")
        
        switch self.upgradeState {
        case .beganConnecting:
            Logger.networkInfo("connected to beganConnecting")
            // Ok, we have a channel, let's wait for end.
            self.upgradeState = .awaitingEnd(connectResult: channel)
            
        case .awaitingConnection(pendingBytes: let pendingBytes):
            Logger.networkInfo("connected to awaitingConnection")

            // Upgrade complete! Begin gluing the connection together.
            self.upgradeState = .upgradeComplete(pendingBytes: pendingBytes)
            self.glue(channel, context: context)
            
        case .awaitingEnd(let peerChannel):
            Logger.networkInfo("connected to awaitingEnd")

            // This case is a logic error, close already connected peer channel.
            peerChannel.close(mode: .all, promise: nil)
            context.close(promise: nil)
            
        case .idle, .upgradeFailed, .upgradeComplete:
            Logger.networkInfo("connected to idle")

            // These cases are logic errors, but let's be careful and just shut the connection.
            context.close(promise: nil)
        }
    }
    
    private func connectFailed(error: Error, context: ChannelHandlerContext) {
        Logger.networkInfo("Connect failed: \(error)")
        
        switch self.upgradeState {
        case .beganConnecting, .awaitingConnection:
            // We still have a somewhat active connection here in HTTP mode, and can report failure.
            self.httpErrorAndClose(context: context)
            
        case .awaitingEnd(let peerChannel):
            // This case is a logic error, close already connected peer channel.
            peerChannel.close(mode: .all, promise: nil)
            context.close(promise: nil)
            
        case .idle, .upgradeFailed, .upgradeComplete:
            // Most of these cases are logic errors, but let's be careful and just shut the connection.
            context.close(promise: nil)
        }
        
        context.fireErrorCaught(error)
    }
    
    private func glue(_ peerChannel: Channel, context: ChannelHandlerContext) {
        Logger.networkInfo("Gluing together \(ObjectIdentifier(context.channel)) and \(ObjectIdentifier(peerChannel))")
        
        // Ok, upgrade has completed! We now need to begin the upgrade process.
        // First, send the 200 message.
        // This content-length header is MUST NOT, but we need to workaround NIO's insistence that we set one.
        let headers = HTTPHeaders([("Content-Length", "0")])
        let head = HTTPResponseHead(version: .init(major: 1, minor: 1), status: .ok, headers: headers)
        context.write(self.wrapOutboundOut(.head(head)), promise: nil)
        context.writeAndFlush(self.wrapOutboundOut(.end(nil)), promise: nil)
        
        // Now remove the HTTP encoder.
        self.removeEncoder(context: context)
        
        // Now we need to glue our channel and the peer channel together.
        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(_):
                // Close connected peer channel before closing our channel.
                peerChannel.close(mode: .all, promise: nil)
                context.close(promise: nil)
            }
        }
    }
    
    private func httpErrorAndClose(context: ChannelHandlerContext) {
        Logger.networkInfo("httpErrorAndClose")
        self.upgradeState = .upgradeFailed
        
        let headers = HTTPHeaders([("Content-Length", "0"), ("Connection", "close")])
        let head = HTTPResponseHead(version: .init(major: 1, minor: 1), status: .badRequest, headers: headers)
        context.write(self.wrapOutboundOut(.head(head)), promise: nil)
        context.writeAndFlush(self.wrapOutboundOut(.end(nil))).whenComplete { (_: Result<Void, Error>) in
            context.close(mode: .output, promise: nil)
        }
    }
    
    private func removeDecoder(context: ChannelHandlerContext) {
        // We drop the future on the floor here as these handlers must all be in our own pipeline, and this should
        // therefore succeed fast.
        context.pipeline.context(handlerType: ByteToMessageHandler<HTTPRequestDecoder>.self).whenSuccess {
            context.pipeline.removeHandler(context: $0, promise: nil)
        }
    }
    
    private func removeEncoder(context: ChannelHandlerContext) {
        context.pipeline.context(handlerType: HTTPResponseEncoder.self).whenSuccess {
            context.pipeline.removeHandler(context: $0, promise: nil)
        }
    }
}

can someone explain me please why this is happening?
Thanks :)

Sorry, your description of what happens isn't entirely clear. Can you break down, in detail, what steps you take and what you observe each time?

Of course.

First of all just to be clear and you probably already saw - I'm using the github sample for the Connect Proxy Server that works great for HTTPS, however as you already answered in other posts here this sample doesn't support HTTP, and I tried to support HTTP as well ( I also followed other posts here about it - but failed to make it work).

So basically, my current situation is that HTTPS works fine, and when I load HTTP websites the only thing to happen is a blank screen, i can see in my logs that the connect worked for port 80, and glue success but as I said there is only a blank screen.

However, If i refresh the browser after the first attemp, the website loads!

So currently the only thing that is missing now is to make it load on the first attempt without a refresh.

Looking forward for your reply,
Thanks