# Request lifecycle

> How an HTTP request reaches a plugin handler: route collection, named middleware, constraints, body limits, CORS, recovery and request context values.

The `serve` command builds one `http.Handler` from every plugin's route declarations and serves it with the Go standard library. This page follows a request from the socket to your handler and back.

## Building the router

When `serve` starts, `surf.BuildRouter` collects the routes:

1. It registers the built-in middleware (`throttle`, `body.limit`, `locale.from-principal` and, when the admin is enabled, `backend`).
2. It registers the named middleware of every plugin that implements `pact.HasMiddleware`, `pact.HasMiddlewareFactories` or `pact.HasHouseMiddleware`, and the rate-limit buckets of every `surf.BucketProvider`.
3. It calls `pact.HasRoutes.Routes` on every plugin in activation order, passing a `pact.Router` that records groups, routes, middleware names and constraints.
4. It mounts the cabana admin API and checks every route.

`surf.Assemble` then compiles the routes onto a standard library `http.ServeMux`. A duplicate route, an unknown middleware name or a malformed `throttle` parameter fails here, at boot, and `serve` exits with the error instead of serving a broken router. `./bin/acme route:list` builds the same router without opening the database and prints the route table.

## Declaring routes

A plugin declares routes the way a WinterCMS `routes.php` file does, with groups that share a prefix and middleware:

```php
Route::group(['prefix' => 'api/blog', 'middleware' => ['auth']], function () {
    Route::get('posts/{id}', 'Acme\Blog\Http\Posts@show')->where('id', '[0-9]+');
});
```

In Go the same declaration is a `pact.HasRoutes` method. Paths use Go `http.ServeMux` patterns, `pact.Router.Where` and `pact.Router.WhereIn` constrain the last declared route, and middleware names are strings that must be registered by the time the router is built. The [surf](/docs/api/surf.md) reference has the full route builder with rate-limit buckets and per-route body limits.

## The wrapping order

surf wraps every non-raw route in the same layers. From the outside in:

1. CORS, only for the paths configured under `http.cors.paths`, including preflight requests.
2. Panic recovery. A panic becomes an opaque JSON 500 from [wire](/docs/api/wire.md), and because the response is buffered until the handler returns, the client never receives half a body.
3. The request locale, taken from the `Accept-Language` header and stored in the context.
4. The body limit: `http.body_limits.default_bytes`, or the route's own `body.limit:<bytes>`.
5. The route's middleware, in the order you listed them: group middleware first, then the route's own.
6. The path constraints. A request whose parameter fails `pact.Router.Where` or `pact.Router.WhereIn` gets a 404 before your handler runs.
7. Your handler.

Raw groups, declared with `pact.Router.GroupRaw`, are for webhooks and file streams. They skip the default body limit, refuse house middleware, and a panic in them returns a bare 500.

## Request context values

WinterCMS reads the current locale and user through facades. SummerCMS carries them on the request's `context.Context`. surf stores the locale with `towel.WithLocale`, the authentication middleware stores the signed-in principal, and your own middleware can add the organization or collection with `towel.WithOrganization` and `towel.WithCollection`. Any code that receives the context reads them back without a global lookup:

```go
// A middleware stores the organization once for the whole request.
withAcme := func(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		ctx := towel.WithOrganization(r.Context(), "acme")
		next.ServeHTTP(w, r.WithContext(ctx))
	})
}

// The handler reads the values back from its request context.
listPosts := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
	org, _ := towel.Organization(r.Context())
	locale, ok := towel.Locale(r.Context())
	if !ok {
		locale = "en"
	}
	fmt.Fprintf(w, "posts for %s in %s", org, locale)
})

// surf sets the locale from Accept-Language; here the test sets it.
req := httptest.NewRequest(http.MethodGet, "/api/blog/posts", nil)
req = req.WithContext(towel.WithLocale(req.Context(), "pl"))
rec := httptest.NewRecorder()
withAcme(listPosts).ServeHTTP(rec, req)
fmt.Println(rec.Body.String())
// Output: posts for acme in pl
```

## Writing responses

Handlers write JSON with `wire.WriteJSON`, which keeps bodies byte-compatible with a Laravel backend: HTML characters are not escaped and there is no trailing newline. `wire.Slice` turns a nil list into `[]`, `wire.Time` marshals timestamps in Carbon's `+00:00` form and `wire.TriBool` models a nullable boolean. Errors that should look like your API's error envelope go through the house middleware a plugin registers with `pact.HasHouseMiddleware`.
