- Adaptive iPhone/iPad layout with NavigationSplitView sidebar - Auto-detect SSL-pinned domains, fall back to passthrough - Certificate install via local HTTP server (Safari profile flow) - App Group-backed CA, per-domain leaf cert LRU cache - DB-backed config repository, Darwin notification throttling - Rules engine, breakpoint rules, pinned domain tracking - os.Logger instrumentation across tunnel/proxy/mitm/capture/cert/rules/db/ipc/ui - Fix dyld framework embed, race conditions, thread safety
60 lines
2.3 KiB
Swift
60 lines
2.3 KiB
Swift
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)")
|
|
}
|
|
}
|
|
}
|