http

package
v0.31.0 Latest Latest
Warning

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

Go to latest
Published: Aug 19, 2026 License: MIT Imports: 9 Imported by: 0

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

Constants

This section is empty.

Variables

This section is empty.

Functions

func Back

func Back(r *http.Request) string

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

func Refuse(w http.ResponseWriter, r *http.Request, status int, message string)

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

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.

func WithState

func WithState(parent context.Context, s State) context.Context

WithState returns a context carrying the framework's per-request state.

middleware.Flash calls it. It is exported for the test that drives a request past the middleware, and for nothing else.

Types

type Context

type Context = hhttp.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 Creator

type Creator = routing.Creator[hhttp.Context]

Creator answers GET /thing/create -- the empty form.

type Destroyer

type Destroyer = routing.Destroyer[hhttp.Context]

Destroyer answers DELETE /thing/{id}.

type Editor

type Editor = routing.Editor[hhttp.Context]

Editor answers GET /thing/{id}/edit -- the filled form.

type Indexer

type Indexer = routing.Indexer[hhttp.Context]

Indexer answers GET /thing -- the list.

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

type Renderer = hhttp.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

type Route = routing.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 NewRouter

func NewRouter() *Router

NewRouter returns an empty router.

func (*Router) Action

func (r *Router) Action(method, pattern string, h func(*Context) error, mws ...Middleware) *Route

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

func (r *Router) ForModule(name string) *Router

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

func (r *Router) Resource(name string, controller any) []*Route

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) Routes

func (r *Router) Routes() []*Route

Routes returns the registered routes, in registration order.

func (*Router) ServeHTTP

func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request)

ServeHTTP dispatches to the underlying mux.

func (*Router) Table

func (r *Router) Table() *Routes

Table returns the route table, for URL generation and for `aru routes`.

func (*Router) WithFlash

func (r *Router) WithFlash(f *security.Flash) *Router

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

func (r *Router) WithRenderer(rd Renderer) *Router

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) All

func (t *Routes) All() []*Route

All returns the routes in registration order, for `aru routes`.

func (*Routes) Must

func (t *Routes) Must(name string, params ...string) string

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

func (t *Routes) URL(name string, params ...string) (string, error)

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 Shower

type Shower = routing.Shower[hhttp.Context]

Shower answers GET /thing/{id} -- one record.

type State

type State = hhttp.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

func StateFrom(ctx context.Context) State

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.

type Storer

type Storer = routing.Storer[hhttp.Context]

Storer answers POST /thing -- the form submission.

type Updater

type Updater = routing.Updater[hhttp.Context]

Updater answers PUT and PATCH /thing/{id}.

Directories

Path Synopsis
Package middleware holds the mandatory request pipeline.
Package middleware holds the mandatory request pipeline.

Jump to

Keyboard shortcuts

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