HttpRelay is an iOS app that acts as an HTTP CONNECT proxy server. It allows a Windows machine to route HTTP/HTTPS traffic through the iOS device, which is useful for scenarios where the iOS device has access to a network (like enterprise VPN via 飞连) that the Windows machine cannot directly access.
- HTTP CONNECT proxy server on configurable port (default 10808)
- Real-time TX/RX byte counting
- Connection count tracking
- Local IP address display
- Keep device awake while proxy is running
- Log display with connection status
- Purpose: HTTP CONNECT proxy server using NWListener
- Key Properties:
port: UInt16- listening port (default 10808)listener: NWListener?- the TCP listeneractiveTunnels: [String: TunnelManager]- dictionary of active tunnels, keyed by"\(host):\(port):\(ObjectIdentifier(clientConnection))"tunnelsLock: NSLock- thread-safe access to tunnels dictionarylocalIP: String- iOS device's WiFi IP addressonLocalIPReady: ((String) -> Void)?- callback when IP is determined
- Key Methods:
start()- starts NWListener on specified portstop()- cancels listenerhandleNewConnection(_:)- handles incoming proxy connectionsreceiveHTTPRequest(_:)- receives data, checks for tunnel or parses HTTPprocessRequest(_:connection:)- parses HTTP CONNECT requestestablishTunnel(host:port:clientConnection:)- creates TunnelManager for CONNECTsendSuccessResponse(_:)- sends "HTTP/1.1 200 Connection Established"sendErrorResponse(_:code:)- sends HTTP error responsefindTunnelKey(for:)- finds tunnel by client connection referenceforwardToTunnel(connection:data:)- forwards binary data to existing tunnelgetLocalIPAddress()- queries WiFi interface IP via getifaddrs()
- Purpose: Manages bidirectional data forwarding between client and target server
- Key Properties:
host: String- target server hostport: Int- target server portlogStore: LogStore- for logging and byte countingserverConnection: NWConnection?- connection to target serverclientConnection: NWConnection?- connection to proxy clientqueue: DispatchQueue- serial queue for connection operationsonConnected,onClose,onError- callbacksclientConnectionRef: NWConnection?- exposes client connection for tunnel lookup
- Key Methods:
start(clientConnection:)- initiates connection to target serverstartForwarding()- sets up bidirectional receive handlersforwardToServer(client:server:)- recursive client→server forwarderforwardToClient(client:server:)- recursive server→client forwarderreceiveClientData(_:)- handles data forwarded from ProxyServer (binary HTTPS data)close()- cancels both connections
- Byte Counting: Every data forward calls
logStore.addTxBytes()orlogStore.addRxBytes()viaTask { @MainActor in }
- Purpose: Central state management for logs and statistics
- Annotations:
@Observable,@MainActor(thread-safe) - Key Properties:
entries: [LogEntry]- limited to 100 entriesactiveConnections: Int- current connection counttotalTxBytes: Int64- total bytes sent to servertotalRxBytes: Int64- total bytes received from server
- Key Methods:
log(host:port:status:)- adds new log entryaddTxBytes(_:),addRxBytes(_:)- updates byte countersincrementConnections(),decrementConnections()- connection lifecycleclear()- resets all state
- Purpose: Single log entry data model
- Properties:
id,timestamp,host,port,status: LogStatus - LogStatus enum:
connect,connected,closed,error - Display:
HH:mm:ss host:port statusformat
- Purpose: Main SwiftUI UI
- State Variables:
isRunning: Bool- proxy running statelogStore: LogStore- shared log storeproxyServer: ProxyServer?- current proxy instanceconnectionCount: Int- derived from logStore.activeConnectionstxBytes: Int64,rxBytes: Int64- derived from logStore totalslocalIP: String- updated via onLocalIPReady callbackportString: String- editable port setting
- UI Layout (two columns):
- Left: Status (colored green/red), IP, Port (editable)
- Right: TX, RX (byte formatted), Conn count
- Features:
- Toggle to start/stop proxy
- Port field (disabled while running)
- Idle timer disabled while proxy running (
UIApplication.shared.isIdleTimerDisabled) - Logs display with ScrollView + LazyVStack (avoid ForEach crash)
- Clear logs button (disabled while running)
-
HTTP CONNECT Phase:
Windows → ProxyServer → TunnelManager → Target Server Windows ← ProxyServer ← TunnelManager ← Target Server -
Binary HTTPS Data Phase (after CONNECT established):
Windows → ProxyServer.receiveHTTPRequest() → findTunnelKey() → forwardToTunnel() Windows → TunnelManager.receiveClientData() → server.send() Windows ← ProxyServer ← TunnelManager ← server.receive() ← Target Server -
Tunnel Key Format:
"\(host):\(port):\(ObjectIdentifier(clientConnection as AnyObject))"- Unique per connection because ObjectIdentifier is unique per object instance
-
NWListener: Uses
NWParameters.tcpwithallowLocalEndpointReuse = true -
Receive Strategy:
- Client receive:
minimumIncompleteLength: 0, maximumLength: 65536 - Server receive:
minimumIncompleteLength: 1, maximumLength: 65536 - Changed from default to handle partial data immediately
- Client receive:
-
Thread Safety:
LogStoreis@MainActorfor SwiftUI thread safetyProxyServerusesNSLockfor tunnel dictionary accessTunnelManagerusesTask { @MainActor in }for LogStore updates
-
SwiftUI Crash Prevention (ForEach crash):
- Use
ScrollView + LazyVStackinstead ofList - LogStore uses private setters
- Limited to 100 log entries
- Use
-
IP Address Detection: Uses
getifaddrs()to queryen0oren1interface for IPv4 address -
Keep Awake:
UIApplication.shared.isIdleTimerDisabled = truewhen running
-
ForEach Crash: SwiftUI ForEach crash with Identifiable - fixed by using ScrollView+LazyVStack and proper Equatable
-
Binary HTTPS Data Not Forwarded: Original code only processed UTF-8 strings, binary data was ignored - fixed by adding tunnel lookup for existing tunnels and
receiveClientData()method -
Connection Stalling:
minimumIncompleteLength: 1blocked partial receives - changed to0 -
Thread Safety: Dictionary access from multiple callbacks - added NSLock
-
Tunnel Identification: Multiple tunnels to same host:port needed unique keys - added ObjectIdentifier
xcodebuild -project HttpRelay.xcodeproj -scheme HttpRelay -configuration Debug -destination 'platform=iOS Simulator,name=iPhone 17 Pro' build- Configure Windows proxy: IP of iOS device, port 10808
- Set up HTTP CONNECT tunnel in browser or system proxy
- Check logs for connection status
- Monitor TX/RX bytes incrementing
HttpRelay/
├── HttpRelayApp.swift # @main entry point
├── ContentView.swift # Main UI (SwiftUI)
├── ProxyServer.swift # HTTP CONNECT proxy server (Network framework)
├── TunnelManager.swift # Connection bridging
├── LogStore.swift # State management (@Observable)
├── LogEntry.swift # Data model
├── Info.plist
└── ...test files