powpow/proto/proxyserver.nim — High-level TCP reverse proxy.
A ProxyServer owns its Loop + seq[TcpServer] frontends + pair state, just like HttpServer. The low-level examples/tcp_proxy.nim hand-rolls newTcpServer + Loop.connect + Table[int,ProxyPair] + pending + teardown. This module hides that behind friendly proxy.onXxx setters.
Usage:
import powpow/proto/proxyserver let proxy = newProxyServer() # owns its Loop # or: let proxy = newProxyServer(loop) # use an existing Loop # or: let proxy = newProxyServer("127.0.0.1", 9001) proxy.onConnect(proc(pair: ProxyPair) {.gcsafe.} = echo "client ", pair.client.fd.int, " connected" ) proxy.onUpstreamConnect(proc(pair: ProxyPair) {.gcsafe.} = echo "proxied ", pair.client.fd.int, " -> ", pair.upstream.fd.int ) proxy.onData(proc(pair: ProxyPair, dir: ProxyDir, data: openArray[byte]) {.gcsafe.} = echo dir, " ", data.len, " bytes" ) proxy.onClose(proc(pair: ProxyPair, dir: ProxyDir) {.gcsafe.} = echo "closed from ", dir ) proxy.onError(proc(pair: ProxyPair, err: string) {.gcsafe.} = echo "error: ", err ) proxy.setUpstream("127.0.0.1", 9001) # static upstream proxy.listen("0.0.0.0", 9020) # additive, multi-port ready proxy.listen("0.0.0.0", 9021) proxy.start(Port(9020), Port(9021)) # blocks: listen + loop.run() # or: proxy.getLoop().run() after manual listen
The proxy is transparent: bytes flow both ways, buffered while the upstream connects, and closing either side tears down the pair. Callbacks are notifications — forwarding happens automatically before they fire. Use pair.client.send / pair.upstream.send inside callbacks for injection.
Types
OnProxyClose = proc (pair: ProxyPair; dir: ProxyDir) {....gcsafe.}
- Either side of pair closed. dir indicates which side initiated.
OnProxyConnect = proc (pair: ProxyPair) {....gcsafe.}
- Client accepted, before upstream connect is attempted.
OnProxyData = proc (pair: ProxyPair; dir: ProxyDir; data: openArray[byte]) {. ...gcsafe.}
- Data flowing in dir. Forwarding happens automatically; this is a notification hook (e.g. for logging / metrics / injection).
OnProxyError = proc (pair: ProxyPair; err: string) {....gcsafe.}
- Upstream connect / DNS error. pair always has client set.
OnProxyUpstreamConnect = proc (pair: ProxyPair) {....gcsafe.}
- Upstream successfully connected; pair.upstream is now set.
ProxyDir = enum FromClient, ## data flowing client -> upstream FromUpstream ## data flowing upstream -> client
ProxyPair = ref object id*: int client*: Connection upstream*: Connection pending*: seq[byte] ## client bytes buffered until upstream connects
- A proxied connection pair. Exposed to callbacks; both client and upstream are the raw Connection objects so callers can pair.client.getClientIp(), pair.client.send(...), etc.
ProxyServer = ref object upstreamHost*: string upstreamPort*: int upstreamUnixPath*: string useUnix*: bool maxPending*: int maxConnections*: int
Consts
DefaultMaxPending = 1048576
- 1 MiB per pair pending buffer cap
Procs
proc close(server: ProxyServer) {....raises: [KeyError, Exception], tags: [RootEffect], forbids: [].}
proc getLoop(server: ProxyServer): Loop {.inline, ...raises: [], tags: [], forbids: [].}
- The event loop owned (or borrowed) by this proxy. Prefer run() — it hides the loop entirely. getLoop is for advanced cases where you need to share the loop with another server.
proc listen(server: ProxyServer; address: string; port: int) {. ...raises: [NetError, KeyError, OSError], tags: [], forbids: [].}
proc listenUnix(server: ProxyServer; path: string; mode: int = 0o000000000660) {. ...raises: [NetError, KeyError, OSError], tags: [], forbids: [].}
- Listen on a Unix domain socket.
proc newProxyServer(): ProxyServer {....raises: [OSError], tags: [TimeEffect], forbids: [].}
- Create a proxy with its own event loop (like newHttpServer()).
proc newProxyServer(loop: Loop): ProxyServer {....raises: [], tags: [], forbids: [].}
- Create a proxy that uses loop. The loop is not owned; close will not close it, but stop will.
proc newProxyServer(upstreamHost: string; upstreamPort: int; loop: Loop = nil): ProxyServer {. ...raises: [OSError], tags: [TimeEffect], forbids: [].}
- Convenience: create a proxy already pointed at upstreamHost:upstreamPort.
proc onClientData(server: ProxyServer; cb: OnProxyData): ProxyServer {. discardable, ...raises: [], tags: [], forbids: [].}
- Sugar for onData filtered to FromClient.
proc onClose(server: ProxyServer; cb: OnProxyClose): ProxyServer {.discardable, ...raises: [], tags: [], forbids: [].}
- Either side of a pair closed; dir says which side.
proc onConnect(server: ProxyServer; cb: OnProxyConnect): ProxyServer {. discardable, ...raises: [], tags: [], forbids: [].}
- Called when a client is accepted, before the upstream connect.
proc onData(server: ProxyServer; cb: OnProxyData): ProxyServer {.discardable, ...raises: [], tags: [], forbids: [].}
- Data notification for both directions. dir is FromClient or FromUpstream. Forwarding is automatic before this fires.
proc onError(server: ProxyServer; cb: OnProxyError): ProxyServer {.discardable, ...raises: [], tags: [], forbids: [].}
- Upstream connect / DNS error, or pending overflow.
proc onUpstreamConnect(server: ProxyServer; cb: OnProxyUpstreamConnect): ProxyServer {. discardable, ...raises: [], tags: [], forbids: [].}
- Called when the upstream for a pair connects (pair.upstream is set).
proc onUpstreamData(server: ProxyServer; cb: OnProxyData): ProxyServer {. discardable, ...raises: [], tags: [], forbids: [].}
- Sugar for onData filtered to FromUpstream.
proc run(server: ProxyServer) {.inline, ...raises: [Exception], tags: [TimeEffect, RootEffect], forbids: [].}
- Run the owned event loop. Blocks until stop() or close() is called from a callback / signal. This is the high-level counterpart to HttpServer's loop.run() — the loop itself is never exposed.
proc setMaxConnections(server: ProxyServer; n: int) {....raises: [], tags: [], forbids: [].}
- Cap concurrent frontend connections (0 = unlimited). Applied to new frontends; existing frontends keep their current limit.
proc setMaxPending(server: ProxyServer; bytes: int) {....raises: [], tags: [], forbids: [].}
- Per-pair pending buffer cap before the client connects (default 1 MiB).
proc setUpstream(server: ProxyServer; host: string; port: int) {....raises: [], tags: [], forbids: [].}
- Set (or change) the static upstream. Takes effect for new pairs.
proc setUpstreamUnix(server: ProxyServer; path: string) {....raises: [], tags: [], forbids: [].}
- Use a Unix domain socket as upstream.
proc start(server: ProxyServer; address: string; ports: varargs[Port]) {. ...raises: [ValueError, NetError, KeyError, OSError, Exception], tags: [TimeEffect, RootEffect], forbids: [].}
- Multi-port on explicit address: proxy.start("127.0.0.1", Port(9020)).
proc start(server: ProxyServer; port: Port) {. ...raises: [NetError, KeyError, OSError, Exception], tags: [TimeEffect, RootEffect], forbids: [].}
- Shorthand: listen("0.0.0.0", port) then loop.run() (blocking).
proc start(server: ProxyServer; ports: varargs[Port]) {. ...raises: [ValueError, NetError, KeyError, OSError, Exception], tags: [TimeEffect, RootEffect], forbids: [].}
- Multi-port: proxy.start(Port(9020), Port(9021)) on 0.0.0.0.
proc stop(server: ProxyServer) {....raises: [KeyError, Exception], tags: [RootEffect], forbids: [].}
- close + close the owned loop (if any). No-op for a borrowed loop.
proc teardownByUpstreamFd(server: ProxyServer; upstreamFd: int): int {. ...raises: [Exception, KeyError], tags: [RootEffect], forbids: [].}
- Find the clientFd for an upstream fd and tear that pair down. Returns clientFd or -1.
Exports
-
addIdle, HttpConnect, pollWallSec, removeIdle, HttpGet, timerCount, releaseBuf, HttpPost, EventType, addCleanup, close, remove, pollNowMs, add, unregisterFd, HttpTrace, ==, deferCall, observe, addTimer, PlatformEvent, HttpOptions, TlsState, ensureCapacity, init, modify, FdWatcher, addInterval, runOnce, close, poll, wake, iouEnabled, Observer, unregister, TimerId, HttpPatch, monoMs, modify, acquireBuf, HttpHead, run, register, postToLoop, newLoop, HttpDelete, FdCallback, TimerCallback, poll, HttpMethod, HttpPut, resumeTimer, addTurnEndHook, pauseTimer, Loop, Platform, isRunning, cancelTimer, ObserverCallback, Callback, cancelObserver, stop, OnClose, closeAndRelease, maxWriteBufferSize, injectFd, formatIp, shutdown, connectUnix, MaxConnPoolSize, driveHandshake, MaxBufPoolSize, sendFile, cancelSendFile, newConnection, Connection, close, TcpServer, close, maxConnPoolSize, releaseBuf, closeAfterDrain, tlsFree, maxBufPoolSize, tlsWrite, sendFileActive, OnAccept, continueSendFile, ConnState, sendv, getClientIp, OnError, flushWriteBuffer, connect, OnData, connectHe, tlsRead, send, send, acquireBuf, listen, listenUnix, newTcpServer, closeAfterSend, getClientSockAddr, finCloseNow, ==, TimerCallback, HttpConnect, HttpPatch, HttpGet, HttpHead, EventType, FdCallback, TimerId, HttpPost, HttpTrace, HttpMethod, HttpPut, HttpOptions, TlsState, HttpDelete, ObserverCallback, Callback, iouEnabled