powpow/proto/proxyserver

Search:
Group by:

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.