Skip to content

The basics

HTTP context

The *web.Ctx passed to every handler wraps the request and response with typed helpers.

Last updated:

Every handler receives a *web.Ctx and returns an error:

Go
type HandlerFunc func(c *Ctx) error

*web.Ctx wraps http.ResponseWriter and *http.Request. It is per request: it must not be used after the handler returns or from other goroutines.

Request#

MethodReturns
Request(), SetRequest(r)The underlying *http.Request
Context()The request context.Context
Method(), Path()Request method and path
Param(name)Path parameter ({name} in the pattern)
ParamInt(name)Path parameter as int64, or a 404 error when not numeric
Query(name)Query string value
QueryInt(name, def)Query value as int, or def when absent or invalid
QueryBool(name)true for 1, t, true…
Header(name), Cookie(name)Request header, cookie value ("" when absent)
IP()Client IP; X-Forwarded-For honoured only from trusted proxies
IsJSON()The request body is JSON
WantsJSON()The client prefers a JSON response
IsHTMX()The request was made by htmx (HX-Request: true)
RouteName()Name of the matched route

Response#

MethodPurpose
Writer()The *web.ResponseWriter (records status, size, whether it was written; supports http.ResponseController)
SetHeader(name, value), SetCookie(ck)Response headers and cookies
Status(code)Sets the status; chainable
String, HTML, JSON, Blob, StreamBodies, see Responses
Redirect, RedirectRoute, BackRedirects
Problem(status, title, detail)RFC 9457 problem details

Services#

The router owns one *web.Services value shared by every Ctx:

Go
services := &web.Services{
	Log:            app.Log,
	Debug:          app.Config.Debug,          // detailed 5xx messages, never in production
	TrustedProxies: proxies,                   // []*net.IPNet for IP()
	ProblemBaseURL: "https://example.com/problems",
	// Errors: custom web.ErrorHandler; Views: a web.Renderer; URLs: set by the router
}

r := router.New(services)

c.Services() returns them and c.Log() returns the request logger.

Trusted proxies#

c.IP() returns the direct peer address unless that peer is listed in TrustedProxies. Only then is X-Forwarded-For read, right to left, skipping trusted hops, so clients cannot spoof their address.

In an app built by vukem.New (every starter), set them from the environment; nobody is trusted by default and an invalid entry stops the boot:

.env
TRUSTED_PROXIES=10.0.0.0/8,192.0.2.10
TRUST_FORWARDED_PROTO=true # X-Forwarded-Proto: https from those peers enables HSTS
CLIENT_IP_HEADER=X-Real-IP # optional: read this single-IP header instead of X-Forwarded-For

CLIENT_IP_HEADER (Services.ClientIPHeader) is for proxies that put the client address in one header of their own. It is read only from trusted peers and only when it holds exactly one IP; otherwise c.IP() returns the peer address. X-Forwarded-For is then ignored.

With a router you build yourself, set the field:

Go
_, lb, _ := net.ParseCIDR("10.0.0.0/8")
services.TrustedProxies = []*net.IPNet{lb}

Sessions, views and htmx#

HelperSee
c.Session(), c.Flash(key, v), c.FlashValue(key), c.Old(field), c.Errors()Sessions
c.Render(name, data, opts...), c.Fragment(name, block, data), c.View(v)Views
c.IsHTMX(), c.IsPartial(), c.HTMX()HTMX