Skip to content

Go SDK

The Go SDK (github.com/dortanes/prox/sdk) provides a clean callback API for writing plugins. It handles all transport details — stdin/stdout JSON, Unix socket msgpack framing, and the ready handshake.

Installation

go get github.com/dortanes/prox/sdk

Quick Start

Auth Plugin

package main

import "github.com/dortanes/prox/sdk"

func main() {
    p := sdk.New()

    p.OnRequest(func(req *sdk.Request) *sdk.Response {
        token := req.Header("Authorization")
        if token == "" {
            return sdk.Deny(401, "Unauthorized")
        }
        return sdk.Allow(sdk.WithHeader("X-User-ID", "123"))
    })

    p.Run()
}

Target Discovery Plugin

package main

import "github.com/dortanes/prox/sdk"

func main() {
    p := sdk.New()

    p.OnConfigure(func(route sdk.Route) {
        go func() {
            targets := discoverTargets(route.Domain)
            p.SetTargets(route.ID, targets)
        }()
    })

    p.Run()
}

Hooks

OnConfigure(func(route Route))

Called when the plugin receives route configuration (on startup and reload).

The Route struct contains:

Field Type Description
ID string Stable identifier (service:routeIndex)
Domain string Domain pattern from config
Path string Path pattern from config

OnRequest(func(req *Request) *Response) — L7

Called for every HTTP request on the route. Returns a verdict:

  • sdk.Allow(opts...) — approve the request, optionally inject headers
  • sdk.Deny(status, body, opts...) — reject with an HTTP error response
  • sdk.Drop() — silently close the connection (no HTTP response)
p.OnRequest(func(req *sdk.Request) *sdk.Response {
    // req.Method, req.Path, req.Domain, req.RemoteAddr
    // req.Query, req.Host, req.Proto, req.ContentLength
    // req.Header("Authorization"), req.Headers, req.Body
    // req.QueryParam("token")
    // req.MatchDomain, req.MatchGlob, req.MatchPath, req.Vars
    return sdk.Allow(sdk.WithHeader("X-Verified", "true"))
})

OnResponse(func(req *Request, resp *UpstreamResponse) *ResponseMod) — L7

Called after the upstream responds, before headers are sent to the client. Modify or remove headers, override the status code.

p.OnResponse(func(req *sdk.Request, resp *sdk.UpstreamResponse) *sdk.ResponseMod {
    return sdk.ModifyResponse(
        sdk.WithResponseHeader("X-Frame-Options", "DENY"),
        sdk.RemoveResponseHeader("Server"),
        sdk.WithResponseStatus(200),
    )
})

OnConnect(func(conn *ConnRequest) *ConnResponse) — L4

Called for raw TCP connections on pass routes (before TLS relay). Only has access to SNI domain and remote address.

p.OnConnect(func(conn *sdk.ConnRequest) *sdk.ConnResponse {
    if isBlacklisted(conn.RemoteAddr) {
        return sdk.RejectConn()
    }
    return sdk.AcceptConn()
})

Request Fields

Field Type Description
RouteID string Route identifier (service:routeIndex)
Method string HTTP method (GET, POST, etc.)
Path string Request path (/api/users)
Query string Raw query string (foo=bar&baz=1)
Domain string Request host without port
Host string Full Host header including port
Proto string HTTP protocol (HTTP/1.1, HTTP/2.0)
RemoteAddr string Client IP:port
ContentLength int64 Request body size (-1 if unknown)
Headers map[string]string All request headers (first value only)
Body []byte Request body (capped at 64KB)
MatchDomain string Captured * wildcard value(s)
MatchGlob string Captured ** glob suffix
MatchPath string Path pattern from config
Vars map[string]string Route-level set variables

Helper Methods

  • req.Header(key) — get a request header value
  • req.QueryParam(key) — get a query parameter value

Response Helpers

Request Verdicts

sdk.Allow(opts...)                    // approve request
sdk.Deny(status, body, opts...)       // reject with HTTP response
sdk.Drop()                            // silently close connection

sdk.WithHeader(key, value)            // inject header (on allow: into request, on deny: into response)

Response Modifications

sdk.ModifyResponse(opts...)           // modify upstream response
sdk.NoResponseMod()                   // pass through unchanged

sdk.WithResponseHeader(key, value)    // add/override response header
sdk.RemoveResponseHeader(key)         // remove response header
sdk.WithResponseStatus(status)        // override status code

L4 Connection Verdicts

sdk.AcceptConn()                      // allow TCP connection
sdk.RejectConn()                      // close TCP connection

Push API

For target discovery, use the push methods (these go over stdin/stdout, no socket needed):

p.SetTargets(routeID, []string{"10.0.1.1:8080", "10.0.1.2:8080"})
p.SetGroupedTargets(routeID, map[string][]string{
    "de": {"de-1:8080", "de-2:8080"},
    "us": {"us-1:8080"},
})

Grouped Targets

With domain pattern *.**, a request to de.example.com captures de → the balancer picks from the "de" group only. Each group gets its own sub-balancer with the route's strategy.