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 :)