Help us shape the future of Go! Take the 2026 Go Developer Survey

web

package
v1.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 8, 2026 License: MIT Imports: 34 Imported by: 0

Documentation

Overview

Package web is the HTTP driving adapter: routes, middleware, handlers and server lifecycle.

Index

Constants

This section is empty.

Variables

View Source
var ErrUnknownPage = errors.New("unknown page")

ErrUnknownPage is returned when no template exists for a requested page.

Functions

func Chain

func Chain(h http.Handler, mws ...Middleware) http.Handler

Chain applies mws to h. The first middleware is the outermost.

func Handler

func Handler(cfg config.Config, log *slog.Logger, routes http.Handler) (http.Handler, error)

Handler wraps routes with the standard middleware, outermost first: request ID, access log, panic recovery, security headers, deadlines, the cross-origin check (CSRF protection), body size limit.

The access log sits outside recovery so it records the 500 that recovery writes. The security headers sit outside the two layers that refuse a request, the cross-origin check and the body limit, so a refusal carries them. The deadlines sit inside the security headers and outside those two layers, so a client cannot hold the connection open by being refused slowly; they also have to see the body before the body limit wraps it, because a wrapped body no longer says that a request has none. The cross-origin check comes before the body limit, so a request from another site is refused as such whatever it carries. The order of recovery, security headers and deadlines among themselves changes nothing that can be seen.

Pattern: Ordered Middleware Chain

func RequestIDFrom

func RequestIDFrom(ctx context.Context) string

RequestIDFrom returns the request ID stored by RequestID, or "".

func UserFrom

func UserFrom(ctx context.Context) (domain.User, bool)

UserFrom returns the signed-in user of the request, if any.

Types

type App

type App struct {
	// contains filtered or unexported fields
}

App owns the shared dependencies of the handlers and builds the route table. It is the only place that knows about both HTTP and the use cases; wiring the use cases to their adapters happens outside, in internal/app.

func NewApp

func NewApp(log *slog.Logger, files fs.FS, svc Services, opts Options) (*App, error)

NewApp parses templates and loads static files from files, which must hold templates/ and static/ (see package assets).

func (*App) EndStreams

func (a *App) EndStreams()

EndStreams ends every running reply: each is saved as stopped with the text so far, and its browser is told. Streams that start afterwards are sent away to try again, so a reply is never stopped before it began. Call it when the server starts shutting down.

func (*App) Routes

func (a *App) Routes() http.Handler

Routes returns the route table behind the authentication check. Wrap it with Handler for the rest of the middleware.

type Middleware

type Middleware func(http.Handler) http.Handler

Middleware wraps a handler.

func AccessLog

func AccessLog(log *slog.Logger) Middleware

AccessLog logs one line per request. Probes and static assets log at debug.

The line has seven named attributes and no more: request_id, method, path, status, bytes, duration and remote. The path is logged without its query, and no header, cookie or form value is logged, except the X-Request-ID that RequestID took for the request id.

Pattern: Log Allow-List

func CrossOrigin

func CrossOrigin(publicOrigin string) (Middleware, error)

CrossOrigin rejects unsafe cross-origin browser requests (CSRF) using Sec-Fetch-Site and Origin. publicOrigin is trusted so the app keeps working behind a reverse proxy that rewrites Host.

func Deadlines

func Deadlines(body, response time.Duration) Middleware

Deadlines bounds how long a client can hold a connection by being slow. Once the headers are in, net/http puts no limit on the rest: a client could send a body one byte a minute, or ask for a page and never read it, and each would keep a goroutine and a file descriptor for as long as it liked.

A body has body to arrive. net/http lifts that deadline itself when the body has been read, so a handler may take as long as it needs after that, and the request is not cancelled behind its back; a test holds it to that. A response has response to be taken; the event stream sets its own deadline for each write, after this one. A global ReadTimeout would do neither: it would cut every event stream.

Zero turns a limit off. Only code that builds its own config.HTTP or calls Deadlines itself can pass one: config.Load refuses zero for both timeouts, so a server started from the settings has both limits.

Pattern: Layered Deadlines

func MaxBody

func MaxBody(limit int64) Middleware

MaxBody caps request bodies. A declared length over the limit is refused immediately; chunked bodies fail with *http.MaxBytesError while being read (see writeBodyError).

func Recover

func Recover(log *slog.Logger) Middleware

Recover turns a handler panic into a logged 500 so one bad request cannot take the server down. http.ErrAbortHandler is passed through, as net/http documents.

func RequestID

func RequestID() Middleware

RequestID keeps a well-formed incoming X-Request-ID or generates one, stores it in the request context, and echoes it on the response.

func SecurityHeaders

func SecurityHeaders(hsts bool) Middleware

SecurityHeaders sets the standard browser hardening headers on every response. hsts adds Strict-Transport-Security and should be on only when the app is served over https.

Every answer is "no-store" unless its route says otherwise (static files, the event stream): a page holds one person's conversations, and the next person at the same computer must not get it back with the Back button.

type Options

type Options struct {
	// SecureCookies adds the Secure attribute to the session cookie (https).
	SecureCookies bool
	// FlushInterval is the shortest time between streamed updates.
	FlushInterval time.Duration
	// RateLimitRPS and RateLimitBurst set the token bucket for sending messages
	// (per user) and signing in (per client address). Zero in either turns
	// limiting off. Only code that builds Options itself can pass one:
	// config.Load refuses zero for both settings, so a server started from the
	// settings always limits.
	RateLimitRPS   float64
	RateLimitBurst int
	// ReadinessChecks are what GET /readyz looks at, in the order they are
	// reported.
	ReadinessChecks []ReadinessCheck
}

Options are the settings the handlers need.

type ReadinessCheck

type ReadinessCheck struct {
	Name  string
	Check func(ctx context.Context) error
}

ReadinessCheck is one dependency /readyz looks at.

type Renderer

type Renderer struct {
	// contains filtered or unexported fields
}

Renderer renders html/template pages. Each page is the base layout, all partials, and one page file; pages define "page:title" and "page:main". Every request for a page gets the whole document: what htmx swaps in by itself are the partials (see Block).

func NewRenderer

func NewRenderer(fsys fs.FS) (*Renderer, error)

NewRenderer parses every page under templates/pages in fsys at startup, so a broken template fails the boot instead of a request.

func (*Renderer) Block

func (rd *Renderer) Block(name string, data any) (string, error)

Block renders one named partial to a string, for responses that are fragments by nature (server-sent events, htmx swaps).

func (*Renderer) Page

func (rd *Renderer) Page(w http.ResponseWriter, r *http.Request, status int, page string, data any) error

Page renders the named page with status. Output is buffered, so a template error leaves the response untouched and the caller can answer with a 500.

type Server

type Server struct {
	// contains filtered or unexported fields
}

Server runs the HTTP handler and shuts it down gracefully.

It sets no global WriteTimeout on purpose: a server-wide write deadline would cut long-lived SSE streams. Streams manage their own deadlines.

func NewServer

func NewServer(cfg config.Config, log *slog.Logger, h http.Handler, onShutdown ...func()) *Server

NewServer wires h into a server that honors cfg.App.ShutdownGrace. Each onShutdown function runs as shutdown begins, before the server waits for requests to finish: that is the moment to end streams (see App.EndStreams), which would otherwise keep the server waiting until the grace period is up.

Pattern: Shutdown Hook Before Wait

func (*Server) Serve

func (s *Server) Serve(ctx context.Context, ln net.Listener) error

Serve accepts connections on ln until ctx is canceled, then waits up to the grace period for in-flight requests before closing them.

type Services

type Services struct {
	Auth          service.Auth
	Chat          service.Chat
	Conversations service.Conversation
}

Services are the use cases the handlers call.

type Static

type Static struct {
	// contains filtered or unexported fields
}

Static serves the embedded static/ directory from memory. Vendored files live in version-named directories, so they are cached as immutable; our own files are revalidated with an ETag on every load. Text files are sent gzipped to a browser that takes it: a first load of about 400 KB of text is under 100 KB.

func NewStatic

func NewStatic(fsys fs.FS) (*Static, error)

NewStatic reads every file under static/ in fsys.

func (*Static) ServeHTTP

func (s *Static) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP serves the file named by the {path...} wildcard.

Directories

Path Synopsis
Package render turns an agent's Markdown into safe HTML.
Package render turns an agent's Markdown into safe HTML.
cmd/chromacss command
Command chromacss writes the syntax-highlighting stylesheet.
Command chromacss writes the syntax-highlighting stylesheet.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL