Routing
Handlers
AsHandler registers a handler for a ServeMux pattern: [METHOD ][HOST]/[PATH], with {name} wildcards read with r.PathValue("name").
A handler is either:
- a value: an
http.Handler, or afunc(http.ResponseWriter, *http.Request), - or a constructor returning one, whose parameters are injected by fx.
type GetUserHandler struct { repository *UserRepository}
func NewGetUserHandler(repository *UserRepository) *GetUserHandler { return &GetUserHandler{repository: repository}}
func (h *GetUserHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { user, err := h.repository.Find(r.Context(), r.PathValue("id")) if err != nil { http.Error(w, "user not found", http.StatusNotFound)
return }
_ = json.NewEncoder(w).Encode(user)}
var Module = fx.Options( fx.Provide(NewUserRepository), httpserver.AsHandler("GET /users/{id}", NewGetUserHandler), httpserver.AsHandler("GET /ping", func(w http.ResponseWriter, _ *http.Request) { _, _ = io.WriteString(w, "pong") }),)Useful patterns:
| Pattern | Matches |
|---|---|
GET /users/{id} |
GET (and HEAD) /users/42 |
POST /users |
POST /users only |
/files/{path...} |
Any method, /files/a/b/c, with path = a/b/c |
GET /{$} |
GET / only, not every path |
GET /static/ |
GET on every path under /static/ |
The application fails to start if a pattern is invalid, or conflicts with another one.
Middlewares
A middleware is either:
- a value: a
func(http.Handler) http.Handler(the usual Go middleware signature, so existing middlewares work as is), or anhttpserver.Middleware(an interface withWrap(next http.Handler) http.Handler), - or a constructor returning one, whose parameters are injected by fx.
AsMiddleware registers global middlewares, applied to every handler. Middlewares can also be given to a single handler, after its pattern and handler:
func NewAuthMiddleware(verifier *TokenVerifier) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if !verifier.Verify(r) { http.Error(w, "unauthorized", http.StatusUnauthorized)
return }
next.ServeHTTP(w, r) }) }}
var Module = fx.Options( httpserver.AsMiddleware(NewAuthMiddleware), httpserver.AsHandler("DELETE /users/{id}", NewDeleteUserHandler, NewAuditMiddleware),)Middlewares run in registration order, the first one being the outermost:
- global middlewares,
- group middlewares, outer groups first,
- handler middlewares.
They run once the request is routed: r.Pattern and r.PathValue() are available. Requests matching no route (404, 405) do not go through them.
Handler groups
AsHandlerGroup registers handlers sharing a path prefix and middlewares. Groups can be nested with WithGroup.
httpserver.AsHandlerGroup("/api/v1", httpserver.WithMiddlewares(NewAuthMiddleware), httpserver.WithHandler("GET /users", NewListUsersHandler), httpserver.WithHandler("POST /users", NewCreateUserHandler, NewAuditMiddleware), httpserver.WithGroup("/admin", httpserver.WithMiddlewares(NewAdminMiddleware), httpserver.WithHandler("GET /stats", NewStatsHandler), ),)| Request | Middlewares, in order |
|---|---|
GET /api/v1/users |
global, auth |
POST /api/v1/users |
global, auth, audit |
GET /api/v1/admin/stats |
global, auth, admin |
| Option | Description |
|---|---|
WithHandler(pattern, handler, middlewares...) |
Adds a handler, with its own middlewares |
WithMiddlewares(middlewares...) |
Adds middlewares to all the handlers of the group and its sub groups, wherever it is placed |
WithGroup(prefix, options...) |
Adds a sub group, whose prefix is relative to the group one |
- The group patterns are relative to the prefix:
GET /usersin the group/api/v1servesGET /api/v1/users. They cannot have a host. - A group middleware is built once, and shared by the group handlers.
