Documentation
¶
Overview ¶
Package proxy provides NewReverseProxy — the one-liner that turns a session-affine process pool into an HTTP gateway.
Why this exists ¶
Pool.Acquire gives you a *Session. But in the common case — proxying HTTP traffic to a subprocess — you still need to write the reverse-proxy loop yourself: acquire a session, build a *httputil.ReverseProxy pointing at session.Worker.Address(), forward the request, handle errors. That's 30 lines every user copy-pastes. NewReverseProxy collapses it to one call.
Usage ¶
pool, _ := herd.New(herd.NewProcessFactory("./my-binary", "--port", "{{.Port}}"))
proxy := proxy.NewReverseProxy(pool, func(r *http.Request) string {
return r.Header.Get("X-Session-ID")
})
http.ListenAndServe(":8080", proxy)
Session lifecycle ¶
NewReverseProxy acquires a session at the START of each HTTP request and releases it at the END (after the response is written). This means a single HTTP request holds a worker exclusively for its duration — appropriate for request-scoped work (a browser API call, an LLM inference call, a REPL eval).
For long-lived sessions where the same sessionID should stay pinned across many requests — e.g. a stateful REPL session that must keep the same process — callers should call Pool.Acquire / Session.Release directly and store the *Session in their own state (e.g. an HTTP session cookie → in-memory map).
Error handling ¶
If extractSessionID returns an empty string, ServeHTTP returns 400. If Pool.Acquire fails (timeout, all workers crashed), ServeHTTP returns 503. If the upstream subprocess returns a non-2xx, it is forwarded as-is — the proxy does not interfere with application-level error codes.
File layout ¶
proxy/proxy.go — NewReverseProxy + ReverseProxy.ServeHTTP (THIS FILE)
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type ReverseProxy ¶
type ReverseProxy[C any] struct { LookupOnly bool // If true, only routing to existing sessions; won't Acquire/Scale // contains filtered or unexported fields }
ReverseProxy is an http.Handler that acquires a session from pool, proxies the request to the worker's address, and releases the session when done.
C is the worker client type — for ProcessFactory this is *http.Client. ReverseProxy does not use C directly; it proxies via the worker's Address().
func NewReverseProxy ¶
func NewReverseProxy[C any]( pool *herd.Pool[C], extractSessionID func(*http.Request) string, ) *ReverseProxy[C]
NewReverseProxy returns an http.Handler that:
- Calls extractSessionID(r) to determine which session this request belongs to.
- Calls pool.Acquire(ctx, sessionID) to get (or create) the pinned worker.
- Reverse-proxies the request to worker.Address().
- Calls session.Release() after the response is written.
extractSessionID may inspect any part of the request — a header, a cookie, a path prefix, or a query parameter. It must return a non-empty string.
Example: route by X-Session-ID header:
proxy := proxy.NewReverseProxy(pool, func(r *http.Request) string {
return r.Header.Get("X-Session-ID")
})
func (*ReverseProxy[C]) ServeHTTP ¶
func (rp *ReverseProxy[C]) ServeHTTP(w http.ResponseWriter, r *http.Request)
ServeHTTP implements http.Handler.
Steps:
- Extract sessionID — return 400 if empty.
- Acquire/Lookup session — return 404/503 if unavailable.
- Parse worker address into *url.URL.
- Build a per-request httputil.ReverseProxy targeting that URL.
- Forward request; on response write, release the session.
func (*ReverseProxy[C]) WithLookupOnly ¶
func (rp *ReverseProxy[C]) WithLookupOnly() *ReverseProxy[C]
WithLookupOnly configures the proxy to only route to existing sessions. It will not trigger worker allocation or singleflight slow paths. Useful for stateless Data Plane proxies (Daemon Mode).