import Foundation import NIOCore import NIOPosix import NIOHTTP1 public final class ProxyServer: Sendable { private let host: String private let port: Int private let group: EventLoopGroup private let trafficRepo: TrafficRepository nonisolated(unsafe) private var channel: Channel? public init( host: String = ProxyConstants.proxyHost, port: Int = ProxyConstants.proxyPort, trafficRepo: TrafficRepository = TrafficRepository() ) { self.host = host self.port = port self.group = MultiThreadedEventLoopGroup(numberOfThreads: 1) self.trafficRepo = trafficRepo ProxyLogger.proxy.info("ProxyServer init: \(host):\(port)") } public func start() async throws { let trafficRepo = self.trafficRepo ProxyLogger.proxy.info("ProxyServer binding to \(self.host):\(self.port)...") let bootstrap = ServerBootstrap(group: group) .serverChannelOption(.backlog, value: 256) .serverChannelOption(.socketOption(.so_reuseaddr), value: 1) .childChannelInitializer { channel in ProxyLogger.proxy.debug("New client connection from \(channel.remoteAddress?.description ?? "unknown")") return channel.pipeline.addHandler( ByteToMessageHandler(HTTPRequestDecoder(leftOverBytesStrategy: .forwardBytes)) ).flatMap { channel.pipeline.addHandler(HTTPResponseEncoder()) }.flatMap { channel.pipeline.addHandler(ConnectHandler(trafficRepo: trafficRepo)) } } .childChannelOption(.socketOption(.so_reuseaddr), value: 1) .childChannelOption(.maxMessagesPerRead, value: 16) channel = try await bootstrap.bind(host: host, port: port).get() ProxyLogger.proxy.info("ProxyServer LISTENING on \(self.host):\(self.port)") } public func stop() async { ProxyLogger.proxy.info("ProxyServer stopping...") do { try await channel?.close() try await group.shutdownGracefully() ProxyLogger.proxy.info("ProxyServer stopped cleanly") } catch { ProxyLogger.proxy.error("ProxyServer shutdown error: \(error.localizedDescription)") } } }