Documentation
¶
Overview ¶
Package http is the request and routing layer.
It is a thin shell over net/http: Middleware is the standard func(http.Handler) http.Handler, so every middleware written for the Go ecosystem works here unchanged.
This package is a bridge. It is removed in v1.0.0; import github.com/arandu-io/hesape/http directly.
The components moved to github.com/arandu-io/hesape, under new names, and this package is now the old names pointing at them. It is the widest split in the collection: one package here answers to three there, and which one a symbol went to depends on the symbol.
hesape/http Context, Renderer, State, Redirect, Refuse, Reject, Back hesape/routing Router, Route, Routes, the resource controller interfaces hesape/pipeline Middleware and Chain, generified over the handler type
The death date above is what keeps this from being a second way to import one type. Nothing here holds an implementation: where the name and the signature survived the move it is a Go alias, and where the design diverged it is an envelope that translates and nothing more.
The three envelopes, and what diverged:
Router hesape/routing.Router holds no request state, takes a Group struct
rather than a prefix and a variadic, and has neither Action nor a
method-shaped Resource
Routes Routes.URL was renamed Routes.Route, and a method cannot be
declared on another package's type
Resource the seven action interfaces gained a type parameter, so each one
is an alias to an INSTANTIATED generic rather than to a plain type
One symbol was deleted rather than bridged: Context.Validate. It had no caller outside its own test and it was the second way to validate a request.
Index ¶
- func Back(r *http.Request) string
- func Chain(h http.Handler, mws ...Middleware) http.Handler
- func Redirect(w http.ResponseWriter, r *http.Request, to string)
- func Refuse(w http.ResponseWriter, r *http.Request, status int, message string)
- func Reject(w http.ResponseWriter, r *http.Request, f *security.Flash, ...)
- func WithState(parent context.Context, s State) context.Context
- type Context
- type Creator
- type Destroyer
- type Editor
- type Indexer
- type Middleware
- type Renderer
- type Route
- type Router
- func (r *Router) Action(method, pattern string, h func(*Context) error, mws ...Middleware) *Route
- func (r *Router) Delete(pattern string, h http.HandlerFunc, mws ...Middleware) *Route
- func (r *Router) ForModule(name string) *Router
- func (r *Router) Get(pattern string, h http.HandlerFunc, mws ...Middleware) *Route
- func (r *Router) Group(prefix string, mws ...Middleware) *Router
- func (r *Router) Patch(pattern string, h http.HandlerFunc, mws ...Middleware) *Route
- func (r *Router) Post(pattern string, h http.HandlerFunc, mws ...Middleware) *Route
- func (r *Router) Put(pattern string, h http.HandlerFunc, mws ...Middleware) *Route
- func (r *Router) Resource(name string, controller any) []*Route
- func (r *Router) Routes() []*Route
- func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request)
- func (r *Router) Table() *Routes
- func (r *Router) WithFlash(f *security.Flash) *Router
- func (r *Router) WithRenderer(rd Renderer) *Router
- type Routes
- type Shower
- type State
- type Storer
- type Updater
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Back ¶
Back is the address a rejected request is sent to: where it came from, or "/".
It is exported because a handler that answers a rejection itself -- one that has a domain reason rather than a rule failure -- needs the same address, and a second reading of the Referer header is a second place for the open-redirect check to be missing.
It answers "/" for everything it cannot prove is ours: no Referer, an unparseable one, one on another host, or a path LocalPath refuses.
func Chain ¶
func Chain(h http.Handler, mws ...Middleware) http.Handler
Chain composes middlewares. The first in the list is the outermost.
A wrapper and not an alias: Go has no alias form for a generic function, and pipeline.Chain is generic over the handler type.
func Redirect ¶
func Redirect(w http.ResponseWriter, r *http.Request, to string)
Redirect is Context.Redirect for the code that holds a raw ResponseWriter: a middleware, or a handler registered with Get rather than Action.
An HTMX request gets HX-Redirect and 204, because a 302 would be followed inside the fragment and nest the whole page in a div. Everything else gets 303, which after a POST is what tells the browser to GET the next address instead of posting the body to it again.
A wrapper and not a var: a package-level function variable is reassignable at init by any package in the build, and this is the one decision every redirect in the framework goes through.
func Refuse ¶
Refuse answers a refusal that the person in front of the browser can see.
The status and the sentence are the caller's and are unchanged: 403 stays 403 and 419 stays 419. What it adds is HX-Refresh, because htmx swaps no 4xx -- without it a guard's 403 reached somebody as a button that did nothing.
func Reject ¶
func Reject(w http.ResponseWriter, r *http.Request, f *security.Flash, errs validation.Errors)
Reject answers a request whose input failed the rules: back where it came from, with the messages and with what was typed still in the boxes.
The answer to a rejected form is a redirect, not a body. A 422 carrying the messages in the markup is thrown away by htmx unless the layout has reconfigured its response handling, and even where it was swapped in a reload re-posted the form.
The errs parameter keeps the framework's validation.Errors rather than naming hesape's, so the signature every caller is written against is unchanged. The conversion below is what makes that safe whichever way the validation bridge goes: it is identity while framework/validation.Errors is an alias for hesape's, and it is still correct if it ever goes back to being a defined type over the same map[string][]string.
security.Flash is already an alias for hesape/session.Flash, so the flash a caller holds is the value hesape/http.Reject expects.
Types ¶
type Context ¶
Context is what a controller action receives.
It is everything a controller action gets, and no more: the request, the response, and helpers that answer. There is no database handle here, no repository, no Grant -- a controller that could reach the data layer would be a controller that skipped the service, and therefore the policy.
The alias is what keeps it one type rather than two. It also carries the accessors the move added -- Header, Path, Method, IP, BearerToken, Cookie, IsHTMX, WantsJSON, User, RedirectRoute -- which is a widening of the surface and not a change to any signature that already existed.
Context.Validate is the one name that did not survive: it had no caller outside its own test and it was the second way to validate. See the package comment.
type Middleware ¶
type Middleware = pipeline.Middleware[http.Handler]
Middleware is the standard net/http signature. We do not invent our own type: that is what keeps the whole Go ecosystem compatible with the framework.
The "=" is load-bearing and not a shorthand. It is what lets hesape/session, hesape/cookie and hesape/exception return a pipeline.Middleware[http.Handler] that IS an http.Middleware without importing this package -- with a defined type each of them would have to import the layer above, which is the cycle this reorganisation exists to remove.
hesape/http.Middleware is the same type by the same alias, so a middleware written against either name satisfies both.
type Renderer ¶
Renderer draws a named view with typed data.
It is an interface, and implemented in the view package, for one reason: the view package imports this layer to register its route, so this layer importing the view package back would be a cycle. The kernel wires the concrete one at boot.
type Route ¶
Route is metadata, used by `aru routes` and by the error page.
The alias holds because the three exported fields, RouteName and Name are the same on both sides. What the move added -- Where, Middleware, Defaults, Domain, the parameter accessors -- is a widening, not a change.
type Router ¶
type Router struct {
// contains filtered or unexported fields
}
Router is a thin shell over http.ServeMux, which since Go 1.22 already handles methods and path parameters. It exists for groups, per-group middleware and route metadata -- the metadata is what lets the CLI generate typed URL helpers and what the error page uses to show the matched route.
It is an envelope over hesape/routing.Router and not an alias, for three reasons, any one of which would be enough:
- Group takes a prefix and a variadic here, and a routing.Group struct there;
- the renderer and the flash are fields here, and hesape/routing deliberately holds neither;
- Action and Resource do not exist there in this shape, because turning a func(*Context) error into an http.Handler is the request layer's job and hesape/routing takes that as a parameter.
It stores no routes of its own. Every registration goes straight through to the hesape router, whose table is shared by every sub-router exactly as it was before.
func (*Router) Action ¶
Action registers one controller action, for a route outside a resource.
Route.Action("GET", "/dashboard", dashboard.Index).Name("dashboard")
func (*Router) Delete ¶
func (r *Router) Delete(pattern string, h http.HandlerFunc, mws ...Middleware) *Route
Delete registers a DELETE route.
func (*Router) ForModule ¶
ForModule returns a sub-router that tags its routes with the module name, so `aru routes` can group them. The Kernel calls it for each module.
func (*Router) Get ¶
func (r *Router) Get(pattern string, h http.HandlerFunc, mws ...Middleware) *Route
Get registers a GET route.
func (*Router) Group ¶
func (r *Router) Group(prefix string, mws ...Middleware) *Router
Group returns a sub-router with the prefix appended and the middleware inherited. The route table is shared with the parent.
The prefix and the middleware are what hesape/routing takes as a Group struct. The struct's third field, Name, has no counterpart in this signature and is deliberately left unset: adding it would be a new way to name a route alongside Route.Name, and this package is being removed rather than grown.
func (*Router) Patch ¶
func (r *Router) Patch(pattern string, h http.HandlerFunc, mws ...Middleware) *Route
Patch registers a PATCH route.
func (*Router) Post ¶
func (r *Router) Post(pattern string, h http.HandlerFunc, mws ...Middleware) *Route
Post registers a POST route.
func (*Router) Put ¶
func (r *Router) Put(pattern string, h http.HandlerFunc, mws ...Middleware) *Route
Put registers a PUT route.
func (*Router) Resource ¶
Resource registers the REST routes a controller implements.
Route.Resource("invoices", InvoiceController{})
The seven, in the conventional order and with the conventional names:
GET /invoices index invoices.index
GET /invoices/create create invoices.create
POST /invoices store invoices.store
GET /invoices/{id} show invoices.show
GET /invoices/{id}/edit edit invoices.edit
PUT /invoices/{id} update invoices.update
PATCH /invoices/{id} update invoices.update
DELETE /invoices/{id} destroy invoices.destroy
A controller implementing none of the seven registers nothing and returns zero routes, which is a wiring mistake worth seeing in `aru routes`.
It stays a method here and is a function there -- routing.Resource takes the router first, because a Go method cannot take a type parameter and C has to come from somewhere. The type parameter and the adapter are supplied by this line, so no caller of Route.Resource changes.
func (*Router) ServeHTTP ¶
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request)
ServeHTTP dispatches to the underlying mux.
func (*Router) WithFlash ¶
WithFlash returns a router whose handlers can answer a rejected request.
The kernel calls it at boot, the way it calls WithRenderer, so no application wires it and none can forget to. Without it a handler that returns validation.Errors reaches the panic path, which is the honest answer: a rejection that cannot be flashed is a rejection nobody will see, and a page that fails loudly beats a form that silently comes back blank.
func (*Router) WithRenderer ¶
WithRenderer returns a router whose handlers can render views.
The kernel calls it at boot with the view module. Without it, Context.View returns an error naming the missing line in bootstrap/app.go rather than panicking.
type Routes ¶
type Routes struct {
// contains filtered or unexported fields
}
Routes is the table of registered routes, and the index by name.
It is an envelope over hesape/routing.Routes and not an alias, for one reason: the method that builds a path from a name was renamed URL -> Route, and Go forbids declaring a method on another package's type. An alias here would drop Routes.URL from the framework's surface, and a bridge that drops a method is not a bridge.
It forwards and holds nothing. The routes are hesape's table, shared by every sub-router the way it always was, so a route registered through a group is in the same table the root reads.
func (*Routes) Must ¶
Must is URL for the places that cannot handle an error -- a template helper, mostly. It returns the message as the href, so a broken link says what is wrong instead of pointing at "/".
func (*Routes) URL ¶
URL builds the path of a named route, filling the parameters in order.
URL("home") -> "/"
URL("invoices.show", "42") -> "/invoices/42"
A hardcoded "/invoices/"+id compiles and keeps compiling after the route moves. This does not: an unknown name or a wrong number of parameters is an error the caller sees, not a 404 the user sees.
It is hesape/routing.Routes.Route under the name this package has always used for it. Only this spelling is offered here -- exposing both would be two ways to ask one question, and the new one is reached by importing hesape/routing, which the death date says to do anyway.
type State ¶
State is what the framework knows about a request before the handler runs.
Today it is one thing: what the request that redirected here failed on. It is a struct rather than that one thing so the next piece of per-request framework state -- there will be one -- does not add a second context key, a second middleware and a second accessor that every page has to be taught about.
It is put on the request context by middleware.Flash and read by view.New. Nothing else writes it, and no handler ever has to.
Its Errors field is hesape/validation.Errors, which has the same underlying map as the framework's. A framework validation.Errors converts into it and a plain map[string][]string assigns straight to it.
func StateFrom ¶
StateFrom returns the state on a request context, or the zero State.
The zero value is the answer for every request that was not redirected here from a rejection, which is nearly all of them: no errors and no old input is a page that draws neither, not a page that has to check first.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package middleware holds the mandatory request pipeline.
|
Package middleware holds the mandatory request pipeline. |