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: [Exception, KeyError], 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, KeyError], 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: [Exception, KeyError], 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
-
==, Callback, EventType, FdCallback, FdWatcher, HttpConnect, HttpDelete, HttpGet, HttpHead, HttpMethod, HttpOptions, HttpPatch, HttpPost, HttpPut, HttpTrace, Loop, Observer, ObserverCallback, Platform, PlatformEvent, TimerCallback, TimerId, TlsState, acquireBuf, add, addCleanup, addIdle, addInterval, addTimer, addTurnEndHook, cancelObserver, cancelTimer, close, close, deferCall, ensureCapacity, init, iouEnabled, isRunning, modify, modify, monoMs, newLoop, observe, pauseTimer, poll, poll, pollNowMs, pollWallSec, postToLoop, register, releaseBuf, remove, removeIdle, resumeTimer, run, runOnce, stop, timerCount, unregister, unregisterFd, wake, Connection, ConnState, MaxBufPoolSize, MaxConnPoolSize, OnAccept, OnClose, OnData, OnError, TcpServer, acquireBuf, adoptListenFd, cancelSendFile, close, close, closeAfterDrain, closeAfterSend, closeAndRelease, connect, connect, connectHe, connectHe, connectUnix, connectUnix, continueSendFile, createListenSocket, driveHandshake, finCloseNow, flushWriteBuffer, formatIp, getClientIp, getClientSockAddr, getLoop, getLoop, injectFd, ktlsRxOffloaded, ktlsTxOffloaded, listen, listenUnix, maxBufPoolSize, maxConnPoolSize, maxWriteBufferSize, newConnection, newTcpServer, newTcpServer, refreshKtlsState, releaseBuf, run, run, send, send, sendFile, sendFileActive, sendv, shutdown, start, startUnix, stop, stop, tlsFree, tlsRead, tlsWrite, ==, Callback, EventType, FdCallback, HttpConnect, HttpDelete, HttpGet, HttpHead, HttpMethod, HttpOptions, HttpPatch, HttpPost, HttpPut, HttpTrace, ObserverCallback, TimerCallback, TimerId, TlsState, iouEnabled