Documentation
¶
Overview ¶
Package http is the request and response layer a controller action works with: the incoming Request, the Response types a handler can build, and the Context an action is called with.
Sub-packages:
http/client is an HTTP client for calling other services. http/exceptions holds the exception types a response can carry. http/middleware is the standard middleware every application wires. http/resources transforms data into response shapes. http/testing helps a test build requests and inspect responses.
The tenant never comes from the request ¶
There is no method on Request that reads a tenant id out of a path parameter, a query string, a header or a body field, and adding one would be the most direct route to a cross-tenant leak. The tenant is on the auth.Grant the policy mints, and the repository reads it from there.
If a method here seems to offer tenant access -- Server, Header, Input -- it does not. They read what the browser sent; the tenant is what the Grant authorises, never what the request carries.
Net/http and the package name ¶
The package is named http because it is the HTTP layer, and the directory is the component name. When it collides with net/http, the cost of the alias is on the caller:
import (
"net/http"
hhttp "github.com/arandu-io/hesape/http"
)
Inside this package, net/http is imported as stdhttp.
Index ¶
- Constants
- Variables
- func Back(r *stdhttp.Request) string
- func File(c *Context, field string) (filesystem.Upload, error)
- func ForwardedForFrom(ctx context.Context) []string
- func LocalPath(raw string) (string, bool)
- func MatchesType(actual, typ string) bool
- func Redirect(w stdhttp.ResponseWriter, r *stdhttp.Request, to string)
- func Refuse(w stdhttp.ResponseWriter, r *stdhttp.Request, status int, message string)
- func Reject(w stdhttp.ResponseWriter, r *stdhttp.Request, f *session.Flash, ...)
- func WithForwardedFor(parent context.Context, chain []string) context.Context
- func WithState(parent context.Context, s State) context.Context
- type Arrayable
- type BaseFile
- func (f *BaseFile) Dimensions() (int, int, bool)
- func (f *BaseFile) Extension() string
- func (f *BaseFile) GetMimeType() string
- func (f *BaseFile) GetPathname() string
- func (f *BaseFile) GetRealPath() string
- func (f *BaseFile) GetSize() int64
- func (f *BaseFile) GuessExtension() string
- func (f *BaseFile) HashName(dir ...string) string
- func (f *BaseFile) Path() string
- type Context
- func (c *Context) BearerToken() string
- func (c *Context) Cookie(name string) string
- func (c *Context) Ctx() context.Context
- func (c *Context) Fragment(status int, name string, data any) error
- func (c *Context) FullURL() string
- func (c *Context) Header(name string) string
- func (c *Context) IP() string
- func (c *Context) Input(name string) string
- func (c *Context) IsHTMX() bool
- func (c *Context) JSON(status int, resource JsonResource) error
- func (c *Context) Method() string
- func (c *Context) Old(field string) string
- func (c *Context) Param(name string) string
- func (c *Context) Path() string
- func (c *Context) Query(name string) string
- func (c *Context) Redirect(to string) error
- func (c *Context) RedirectRoute(name string, params ...string) error
- func (c *Context) State() State
- func (c *Context) Status(code int) error
- func (c *Context) URL(name string, params ...string) string
- func (c *Context) User() (auth.Subject, bool)
- func (c *Context) View(name string, data any) error
- func (c *Context) WantsJSON() bool
- func (c *Context) WithCookie(cookie *stdhttp.Cookie) *Context
- type ExceptionHandler
- type Intended
- type JsonResource
- type JsonResponse
- func (r *JsonResponse) Cookie(cookie *stdhttp.Cookie) *JsonResponse
- func (r *JsonResponse) GetData(args ...any) (any, error)
- func (r *JsonResponse) GetEncodingOptions() int
- func (r *JsonResponse) HasEncodingOption(option int) bool
- func (r *JsonResponse) Header(key string, values ...any) *JsonResponse
- func (r *JsonResponse) SetCallback(callback string) *JsonResponse
- func (r *JsonResponse) SetData(data any) (*JsonResponse, error)
- func (r *JsonResponse) SetEncodingOptions(options int) (*JsonResponse, error)
- func (r *JsonResponse) SetStatusCode(code int) *JsonResponse
- func (r *JsonResponse) WithCallback(callback string) *JsonResponse
- func (r *JsonResponse) WithCookie(cookie *stdhttp.Cookie) *JsonResponse
- func (r *JsonResponse) WithException(err error) *JsonResponse
- func (r *JsonResponse) WithHeaders(headers stdhttp.Header) *JsonResponse
- func (r *JsonResponse) WithoutCookie(name string, args ...string) *JsonResponse
- func (r *JsonResponse) WithoutHeader(keys ...string) *JsonResponse
- type JsonSerializable
- type Middleware
- type RedirectResponse
- func (r *RedirectResponse) Cookie(cookie *stdhttp.Cookie) *RedirectResponse
- func (r *RedirectResponse) EnforceSameOrigin(fallback string, args ...bool) *RedirectResponse
- func (r *RedirectResponse) ExceptInput(keys ...string) *RedirectResponse
- func (r *RedirectResponse) GetOriginalContent() any
- func (r *RedirectResponse) GetRequest() *Request
- func (r *RedirectResponse) GetSession() *session.Store
- func (r *RedirectResponse) GetTargetUrl() string
- func (r *RedirectResponse) Header(key string, values ...any) *RedirectResponse
- func (r *RedirectResponse) OnlyInput(keys ...string) *RedirectResponse
- func (r *RedirectResponse) SetRequest(request *Request) *RedirectResponse
- func (r *RedirectResponse) SetSession(store *session.Store) *RedirectResponse
- func (r *RedirectResponse) SetTargetUrl(to string) *RedirectResponse
- func (r *RedirectResponse) With(key any, value ...any) *RedirectResponse
- func (r *RedirectResponse) WithCookie(cookie *stdhttp.Cookie) *RedirectResponse
- func (r *RedirectResponse) WithCookies(cookies []*stdhttp.Cookie) *RedirectResponse
- func (r *RedirectResponse) WithErrors(provider any, key ...string) *RedirectResponse
- func (r *RedirectResponse) WithException(err error) *RedirectResponse
- func (r *RedirectResponse) WithFragment(fragment string) *RedirectResponse
- func (r *RedirectResponse) WithHeaders(headers stdhttp.Header) *RedirectResponse
- func (r *RedirectResponse) WithInput(input ...map[string]any) *RedirectResponse
- func (r *RedirectResponse) WithoutCookie(name string, args ...string) *RedirectResponse
- func (r *RedirectResponse) WithoutFragment() *RedirectResponse
- func (r *RedirectResponse) WithoutHeader(keys ...string) *RedirectResponse
- type Renderable
- type Renderer
- type Request
- func (r *Request) Accepts(contentTypes ...string) bool
- func (r *Request) AcceptsAnyContentType() bool
- func (r *Request) AcceptsHTML() bool
- func (r *Request) AcceptsJSON() bool
- func (r *Request) Ajax() bool
- func (r *Request) All(keys ...string) map[string]any
- func (r *Request) AllFiles() map[string]any
- func (r *Request) AnyFilled(keys ...string) bool
- func (r *Request) Array(keys ...string) []any
- func (r *Request) BearerToken() string
- func (r *Request) Boolean(key string, def ...bool) bool
- func (r *Request) Collect(keys ...string) []any
- func (r *Request) Cookie(name string, def ...string) string
- func (r *Request) Date(key string, format ...string) (time.Time, bool)
- func (r *Request) DecodedPath() string
- func (r *Request) Duplicate(query, post, cookies, server map[string]any) *Request
- func (r *Request) Enum(key string, tryFrom func(string) (any, bool)) any
- func (r *Request) Except(keys ...string) map[string]any
- func (r *Request) ExpectsJSON() bool
- func (r *Request) File(key string) any
- func (r *Request) Filled(keys ...string) bool
- func (r *Request) FilterPrecognitiveRules(rules map[string]any) map[string]any
- func (r *Request) Fingerprint() string
- func (r *Request) Flash()
- func (r *Request) FlashExcept(keys ...string)
- func (r *Request) FlashOnly(keys ...string)
- func (r *Request) Float(key string, def ...float64) float64
- func (r *Request) Fluent(key string, def ...map[string]any) map[string]any
- func (r *Request) Flush()
- func (r *Request) Format(def ...string) string
- func (r *Request) FullURL() string
- func (r *Request) FullURLIs(patterns ...string) bool
- func (r *Request) FullURLWithQuery(query map[string]string) string
- func (r *Request) FullURLWithoutQuery(keys ...string) string
- func (r *Request) GetAcceptableContentTypes() []string
- func (r *Request) GetRouteResolver() func() Route
- func (r *Request) GetSession() *session.Store
- func (r *Request) GetUserResolver() func(guard string) any
- func (r *Request) HTTPHost() string
- func (r *Request) Has(keys ...string) bool
- func (r *Request) HasAny(keys ...string) bool
- func (r *Request) HasCookie(name string) bool
- func (r *Request) HasFile(key string) bool
- func (r *Request) HasHeader(name string) bool
- func (r *Request) HasSession() bool
- func (r *Request) Header(name string, def ...string) string
- func (r *Request) Host() string
- func (r *Request) IP() string
- func (r *Request) IPs() []string
- func (r *Request) Input(key string, def ...any) any
- func (r *Request) Instance() *Request
- func (r *Request) Integer(key string, def ...int64) int64
- func (r *Request) Is(patterns ...string) bool
- func (r *Request) IsAttemptingPrecognition() bool
- func (r *Request) IsJSON() bool
- func (r *Request) IsMethod(method string) bool
- func (r *Request) IsNotFilled(keys ...string) bool
- func (r *Request) IsPrecognitive() bool
- func (r *Request) Json(key string, def ...any) any
- func (r *Request) Keys() []string
- func (r *Request) Merge(input map[string]any) *Request
- func (r *Request) MergeIfMissing(input map[string]any) *Request
- func (r *Request) Method() string
- func (r *Request) Missing(keys ...string) bool
- func (r *Request) Old(key string, def ...any) any
- func (r *Request) Only(keys ...string) map[string]any
- func (r *Request) PJAX() bool
- func (r *Request) Path() string
- func (r *Request) Post(key string, def ...string) string
- func (r *Request) PreferSafeContent() bool
- func (r *Request) Prefers(contentTypes ...string) string
- func (r *Request) Prefetch() bool
- func (r *Request) Query(key string, def ...string) string
- func (r *Request) RawRequest() *stdhttp.Request
- func (r *Request) Replace(input map[string]any) *Request
- func (r *Request) Root() string
- func (r *Request) Route(args ...any) any
- func (r *Request) RouteIs(patterns ...string) bool
- func (r *Request) SchemeAndHttpHost() string
- func (r *Request) Secure() bool
- func (r *Request) Segment(index int, def ...string) string
- func (r *Request) Segments() []string
- func (r *Request) Server(key string, def ...string) string
- func (r *Request) Session() *session.Store
- func (r *Request) SetDefaultRequestLocale(locale string) *Request
- func (r *Request) SetJson(payload map[string]any) *Request
- func (r *Request) SetLaravelSession(s *session.Store) *Request
- func (r *Request) SetPrecognitive() *Request
- func (r *Request) SetRequestLocale(locale string) *Request
- func (r *Request) SetRouteResolver(resolver func() Route) *Request
- func (r *Request) SetSession(s *session.Store) *Request
- func (r *Request) SetUserResolver(resolver func(guard string) any) *Request
- func (r *Request) Str(key string, def ...string) string
- func (r *Request) String(key string, def ...string) string
- func (r *Request) ToArray() map[string]any
- func (r *Request) URI() string
- func (r *Request) URL() string
- func (r *Request) User(guard ...string) any
- func (r *Request) UserAgent() string
- func (r *Request) Validate(rules *validation.Set, opts ...validation.ValidatorOption) (validation.Input, error)
- func (r *Request) ValidateWithBag(bag string, rules *validation.Set, opts ...validation.ValidatorOption) (validation.Input, error)
- func (r *Request) WantsJSON() bool
- func (r *Request) WhenFilled(key string, callback func(value any) any, def ...func() any) any
- func (r *Request) WhenHas(key string, callback func(value any) any, def ...func() any) any
- func (r *Request) WhenMissing(key string, callback func(value any) any, def ...func() any) any
- type Response
- func (r *Response) Content() string
- func (r *Response) Cookie(cookie *stdhttp.Cookie) *Response
- func (r *Response) Cookies() []*stdhttp.Cookie
- func (r *Response) Exception() error
- func (r *Response) GetCallback() string
- func (r *Response) GetContent() string
- func (r *Response) GetOriginalContent() any
- func (r *Response) GetProtocolVersion() string
- func (r *Response) GetStatusCode() int
- func (r *Response) Header(key string, values ...any) *Response
- func (r *Response) Headers() stdhttp.Header
- func (r *Response) Send(w stdhttp.ResponseWriter) error
- func (r *Response) ServeHTTP(w stdhttp.ResponseWriter, _ *stdhttp.Request)
- func (r *Response) SetContent(content any) (*Response, error)
- func (r *Response) SetProtocolVersion(version string) *Response
- func (r *Response) SetStatusCode(code int) *Response
- func (r *Response) Status() int
- func (r *Response) StatusText() string
- func (r *Response) ThrowResponse() error
- func (r *Response) WithCookie(cookie *stdhttp.Cookie) *Response
- func (r *Response) WithException(err error) *Response
- func (r *Response) WithHeaders(headers stdhttp.Header) *Response
- func (r *Response) WithoutCookie(name string, args ...string) *Response
- func (r *Response) WithoutHeader(keys ...string) *Response
- type Route
- type State
- type StoreOptions
- type StreamedEvent
- type URLGenerator
- type UploadedFile
- func (f *UploadedFile) ClientExtension() string
- func (f *UploadedFile) Extension() string
- func (f *UploadedFile) Get() ([]byte, error)
- func (f *UploadedFile) GetClientMimeType() string
- func (f *UploadedFile) GetClientOriginalExtension() string
- func (f *UploadedFile) GetClientOriginalName() string
- func (f *UploadedFile) GetError() int
- func (f *UploadedFile) GetSize() int64
- func (f *UploadedFile) GuessClientExtension() string
- func (f *UploadedFile) GuessExtension() string
- func (f *UploadedFile) HashName(dir ...string) string
- func (f *UploadedFile) IsValid() bool
- func (f *UploadedFile) Open() (io.ReadCloser, error)
- func (f *UploadedFile) Store(ctx context.Context, g auth.Grant, directory string, options StoreOptions) (string, error)
- func (f *UploadedFile) StoreAs(ctx context.Context, g auth.Grant, directory, name string, ...) (string, error)
- func (f *UploadedFile) StorePublicly(ctx context.Context, g auth.Grant, directory string, options StoreOptions) (string, error)
- func (f *UploadedFile) StorePubliclyAs(ctx context.Context, g auth.Grant, directory, name string, ...) (string, error)
- func (f *UploadedFile) Upload(field string) filesystem.Upload
Constants ¶
const ( // JSONHexTag is the "hex tag" encoding flag: escape "<" and ">". JSONHexTag = 1 // JSONHexAmp is the "hex amp" encoding flag: escape "&". JSONHexAmp = 2 // JSONHexApos is the "hex apos" encoding flag: escape "'". JSONHexApos = 4 // JSONHexQuot is the "hex quot" encoding flag: escape the double quote. JSONHexQuot = 8 // JSONForceObject forces an empty value or a map to encode as a JSON // object rather than an array. JSONForceObject = 16 // JSONNumericCheck encodes a numeric string as a number. JSONNumericCheck = 32 // JSONUnescapedSlashes and JSONUnescapedUnicode both mean "stop // rewriting characters that were already valid", which in encoding/json // is the one switch SetEscapeHTML: setting either flag turns it off. JSONUnescapedSlashes = 64 // JSONPrettyPrint indents the encoded output. JSONPrettyPrint = 128 // JSONUnescapedUnicode is documented on JSONUnescapedSlashes. JSONUnescapedUnicode = 256 // JSONPartialOutputOnError falls back to an empty object instead of // failing when the value cannot be encoded. SetData reads it. JSONPartialOutputOnError = 512 // JSONPreserveZeroFraction keeps a trailing ".0" on a float that has no // fractional part. JSONPreserveZeroFraction = 1024 // JSONThrowOnError is accepted for compatibility; an encoding failure is // always reported through the returned error here. JSONThrowOnError = 4194304 )
The encoding flags are bit values JsonResponse takes as its options argument, and HasEncodingOption tests a caller's payload against. The numbers are fixed: a caller reads back a flag it set through HasEncodingOption, and renumbering the constants would answer a different question for anyone who already depends on the current values.
encoding/json has no flag word, so only the flags that change what Go can emit are honoured: JSONPrettyPrint indents, JSONUnescapedSlashes and JSONUnescapedUnicode turn off Go's HTML escaping, and JSONPartialOutputOnError is read by SetData when deciding whether an encoding failure is fatal. The rest are accepted, stored and reported by HasEncodingOption, and otherwise change nothing.
const IntendedCookieName = "arandu_intended"
IntendedCookieName carries the address somebody was going to, between the request a guard turned away and the sign-in that follows it.
Fixed, for the reason session.CookieName is fixed: the guard writes it and the sign-in handler reads it, and two parts of a project that disagree about the name is a person who signs in and lands on the front page with no explanation -- which is the thing this exists to remove.
It is a cookie and not a row: there is no session yet at the moment the guard fires, which is exactly why it fires, so there is nowhere on the server to put it that is keyed to this browser.
const IntendedLifetime = 10 * time.Minute
IntendedLifetime is how long the address is worth keeping.
Long enough to find a password in a manager and type it, and short enough that it does not survive to an unrelated sign-in: on a shared machine, an address kept for a day sends the next person who signs in to the page the previous one was refused.
Variables ¶
var ErrFileNotFound = errors.New("http: the uploaded file is not readable")
ErrFileNotFound is returned when the file behind an upload is gone.
Functions ¶
func Back ¶
Back is the address a rejected request is sent to: where it came from, or "/".
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. Landing on the front page is a bad answer; landing on somebody else's site carrying the fact that you just submitted a form is a worse one.
func File ¶
func File(c *Context, field string) (filesystem.Upload, error)
File is the one file that arrived in a form field.
It is the whole of what this package knows about multipart, and it exists so that the knowing happens once: hesape/filesystem.FromMultipart is waiting for a *multipart.FileHeader and had nothing handing it one, so every project would have written this call itself, and the ones that wrote it slightly differently would have differed on what a filename is.
It is a function taking a Context rather than a method for the reason filesystem.Upload is where it is: an upload is checked against filesystem.UploadRules and stored with a Disk, and none of that belongs on the request. The returned Upload carries nothing but a name, a size, an announced type and a way to open the bytes -- all of it the client's except the size.
The size limit is not here ¶
The body is read into memory or into a temporary file before this returns, so the thing that stops a four gigabyte upload is middleware.LimitBodySize on the way in, not a rule checked on the way out. The temporary file, when there is one, is removed by net/http when the request ends -- so the bytes must be read during the request, which is what Disk.Put does.
func ForwardedForFrom ¶
ForwardedForFrom returns the chain of client addresses on a request context, or nil when no trusted proxy put one there.
func LocalPath ¶
LocalPath reports whether an address stays inside this application, and returns it when it does.
It is the open-redirect defence, and it is one function in the collection rather than one per caller: Reject calls it on the address a rejected form is sent back to, which comes off the Referer header and is therefore the visitor's to choose, and Intended calls it twice around a signed cookie. Two copies of this would be two lists of refused shapes, and the second list is always the shorter one.
A destination is accepted only as an absolute path on this origin. That is narrow on purpose: "is this URL one of ours" answered by comparing hosts is a question with a decade of published bypasses in it, and the only answer that has none is not to accept a host at all.
The four shapes it refuses, and what each one does:
- anything not starting with "/": "https://evil.example/login" and "javascript:..." are both Location headers a browser will act on.
- "//evil.example/x": a protocol-relative URL. It arrives here looking like a path -- net/http parses a request target beginning with "//" into URL.Path, host and all -- and a browser resolves it to another origin.
- "/\evil.example/x": browsers normalise a backslash to a slash in the authority position, so this is the line above with one character changed, and it is the form that gets past a check written as "starts with a single slash".
- a control character or a space anywhere in it: header injection, and the browsers that strip such characters before resolving, which turns a rejected address into an accepted one.
func MatchesType ¶
MatchesType reports whether two content types match, accounting for the "+suffix" vendor syntax. Accepts and Prefers both rely on it.
func Redirect ¶
func Redirect(w stdhttp.ResponseWriter, r *stdhttp.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.
It is one function and not one per caller. The branch below existed in three copies -- here, in the auth module's handlers, and in the route guards -- and the middle one had already been wrong once, answering HX-Redirect with 200 to every client including the ones that do not read the header, which left a browser with scripts off on a blank page after signing in. Three copies of a six-line decision is three chances to be the copy that is wrong.
303 and not 302: after a POST, 303 is what tells the browser to GET the next address instead of posting the body to it again.
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, so logs, monitoring and every non-HTMX client see exactly what they saw before.
What it adds is the HTMX half. htmx does not swap a 4xx -- its response handling is configured `{code:"[45]..", swap:false, error:true}` in the copy this framework embeds -- so a guard's 403 and an expired CSRF token both arrived as a fired event and a blank screen: the person clicked, nothing at all happened, and the sentence explaining why was in a response body that was thrown away. HX-Refresh makes the browser reload the page as an ordinary navigation, and that request is not an HTMX one, so the same middleware answers the same refusal and the browser renders it. Nothing loops: a reload is a GET, which CSRF verification does not check, and which a role guard answers as a plain page.
What a reload can and cannot show ¶
It reloads the page the person is ON, which is not always the address that was refused. When the two are the same -- an expired token on a form, a fragment of the page itself -- the refusal comes back as a full page and is read. When they differ -- a boosted link into an area this account may not open -- the person gets their own page back, correct and unexplained, and learns only that the link does nothing.
That half is deliberately not solved by sending them to the refused address instead: the address would come off the request, and a request target beginning with "//" is parsed into URL.Path host and all, so handing it to location.href is an open redirect built out of a refusal. Something visible and incomplete beats something invisible, and beats a hole.
It is one function and not a branch in each middleware, for the reason Redirect above is one function: the last time this decision existed in three copies, one of them was wrong.
func Reject ¶
func Reject(w stdhttp.ResponseWriter, r *stdhttp.Request, f *session.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.
It composes what answering a rejected form needs: a redirect back to where the request came from, carrying the input that was typed and the validation messages, so the page that follows the redirect can show them. The shape is deliberate: the answer to a rejected form is a redirect, not a body. A body was tried first and is the failure that produced this file. A 422 carrying the messages in the markup is thrown away by htmx unless the layout has reconfigured its response handling, so the person saw the form they submitted, unchanged, with nothing on it; and even where it was swapped in, a reload re-posted the form. A redirect is read by every client there is, and the page that follows it is a page.
Why it takes the flash rather than reaching for one ¶
RedirectResponse's WithInput and WithErrors need a session, and this package requires hesape/session for exactly that reason -- the dependency runs Http -> Session and never back. The Flash is a parameter and not a package-level default because a process serving two applications has two keys.
What "back" means, and why it is checked ¶
The Referer, and "/" when there is none or it is not ours. That address ends up in a Location header, so it is chosen by whoever sent the request: "https://evil.example/login" is a Referer somebody can send, and answering it turns every form in the application into an open redirect. LocalPath is the check, and it is the same one the intended-destination cookie has used since it existed -- one definition of "is this address ours", carrying its four documented bypasses, rather than a second one written here that knows about three of them.
What goes in the flash ¶
The posted form, minus the fields session.Flash never carries back, and the messages. The query string is deliberately not included: it is already in the address being returned to, and adding it would put the same values on the page twice with a chance of the two disagreeing.
func WithForwardedFor ¶
WithForwardedFor returns a context carrying the chain of client addresses the trust-proxies middleware worked out, nearest hop first.
The middleware calls it, because it is the only thing that knows which proxies are ours: the chain cannot be recovered from the request afterwards, which is why Request.IPs could not answer with one. It is exported for that, and for the test that drives a request past the middleware.
Only the first entry is verified. Everything to the left of it was written by whoever that is and can say anything at all, which is why Request.IP -- what a rate limit, a throttle or a log line is keyed by -- is the first and never the last.
Types ¶
type Arrayable ¶
Arrayable is a value that knows how to present itself as a map. SetContent turns one into JSON, which is the whole reason it is named here.
type BaseFile ¶
type BaseFile struct {
// contains filtered or unexported fields
}
BaseFile is a file on disk.
It is BaseFile and not File because File is already the function that pulls an upload off a Context, and a package cannot have both.
func NewBaseFile ¶
NewBaseFile builds a BaseFile at a path.
func (*BaseFile) Dimensions ¶
Dimensions is the width and height of the image, and whether it is one.
Returns (width, height, ok): ok is false when the file is not a decodable image.
func (*BaseFile) GetMimeType ¶
GetMimeType is the MIME type guessed from the extension.
func (*BaseFile) GetPathname ¶
GetPathname is an alias for Path.
func (*BaseFile) GetRealPath ¶
GetRealPath resolves symlinks in the path; a path that cannot be resolved is returned as it stands, because the caller wanted a path and not an error about one.
func (*BaseFile) GuessExtension ¶
GuessExtension is the extension guessed from the file's name, without the dot, empty when nothing is known. It guesses from the name because Go has no built-in content-sniffing equivalent to libmagic, and the name is what a person sees.
type Context ¶
type Context struct {
// Response and Request are exported: a handler that needs the standard
// library reaches for it directly instead of waiting for a wrapper.
Response stdhttp.ResponseWriter
Request *stdhttp.Request
// contains filtered or unexported fields
}
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. Nothing more -- and the "nothing more" is the point. 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 that guards it.
It is a struct rather than an interface because it has no second implementation and never will. One way to do one thing.
func NewContext ¶
func NewContext(w stdhttp.ResponseWriter, r *stdhttp.Request, render Renderer, urls URLGenerator) *Context
NewContext builds the Context an action is called with.
It is exported because the thing that calls actions -- the router -- is no longer in this package, and the two fields it fills are unexported so that nothing else can swap a renderer or a URL table mid-request. hesape/routing calls it once per request; a test that drives an action directly calls it with nil for whichever of the two that action does not use.
func (*Context) BearerToken ¶
BearerToken is the token of an Authorization: Bearer header, or empty.
The scheme is compared case-insensitively because RFC 9110 says it is, and the value is trimmed because a client that sends two spaces is a client, not an attack.
func (*Context) Cookie ¶
Cookie is the value of a cookie, or empty when the browser sent none.
Empty rather than (string, bool): every caller of the two-value form in the framework threw the bool away, and a cookie that is present and empty means the same thing to all of them as a cookie that is absent.
func (*Context) Ctx ¶
Ctx returns the request context, which carries the Collector, the logger and the request id.
func (*Context) Fragment ¶
Fragment renders a partial with a status, for HTMX.
The status matters: a form that failed validation answers 422 with the form fragment, so the browser and the logs agree with each other. Answering 200 would make both of them believe it worked.
The layout has to let htmx swap it, and by default htmx does not ¶
This comment used to end with "and HTMX swaps it in", stated as fact. It does not. htmx's default response handling is
[{code:"204", swap:false}, {code:"[23]..", swap:true}, {code:"[45]..", swap:false, error:true}]
in the copy this framework embeds (2.0.4), and a 422 matches the third entry. The fragment is fetched, the status is right, the body is correct -- and it is thrown away. The person sees the form they submitted, unchanged, with no message on it: the same failure that made a guard's 403 invisible, on the answer every form in an application gives.
Neither HX-Retarget nor HX-Reswap rescues it. Both are read after shouldSwap has already been decided from the table above; they change where and how, not whether. What decides whether is the configuration, and htmx reads it from the document without any script running:
<meta name="htmx-config" content='{"responseHandling":[
{"code":"204","swap":false},
{"code":"422","swap":true},
{"code":"[23]..","swap":true},
{"code":"[45]..","swap":false,"error":true}]}'>
422 before the catch-all, because htmx takes the first entry that matches. That line belongs in the application's layout, once -- it is the layout that decides what a fragment answer means, and a per-page opt-in would be a second way to answer a rejected form. A meta tag is not a script, so it costs nothing against a `script-src 'self'` policy and nothing in Node.
A refusal is a different thing and does not go through here: 403, 419 and 429 are not a form coming back with messages on it, and they answer through Refuse.
func (*Context) FullURL ¶
FullURL is the address this request was made to, scheme and host included.
It is what a mail template needs, and what nothing that answers HTML needs: a link inside a page is a path, so that the application keeps working behind a proxy, on a staging host and on somebody's laptop.
The scheme is the one the browser used, which behind a proxy is not the one this process is listening on. middleware.TrustProxies is what records it; with no proxy in front, r.TLS answers it directly. A request whose scheme neither of those two can prove is reported as http, because guessing https for a listener that is plain is how a mail link becomes unreachable.
func (*Context) IP ¶
IP is the address the request came from.
Behind a proxy it is whatever middleware.TrustProxies wrote onto RemoteAddr, and with no proxy it is the peer. Read it and do not parse a forwarding header here: which hop to believe is a deployment fact, it is decided once in that middleware, and a second reading of X-Forwarded-For is a second answer -- the one an attacker gets to choose.
It answers the address without the port, and tolerates a RemoteAddr that has none, because TrustProxies writes a bare address: a port that belonged to a connection this request did not arrive on is worse than no port.
func (*Context) Input ¶
Input reads a form field, from the body or the query string.
Named Input rather than Form because Input is the word the vocabulary already uses for it, and the vocabulary is the point.
func (*Context) IsHTMX ¶
IsHTMX reports whether htmx made this request.
It is the question that decides whether an answer is a fragment or a page, and it is asked in one place per decision: Redirect and Refuse already ask it for themselves, so a handler needs this only when the two shapes differ in what they render.
func (*Context) JSON ¶
func (c *Context) JSON(status int, resource JsonResource) error
JSON answers with JSON. It exists for the endpoints that are genuinely an API; a page answers with View.
It takes a resource and not a value, and the signature is the whole point of it. An encoder handed an entity answers with whatever fields the entity happens to have, including the ones somebody adds to it later without ever reading this handler: a password hash, an internal note, the identifier of the account a row belongs to. A resource cannot do that, because what leaves is a list somebody wrote.
The answer is the fields under a "data" key, and whatever With returns beside it. The key is fixed here and does not follow the resource layer's configurable wrapping: this is one answer with one shape, and an endpoint that needs another builds its body with the resource layer and writes it to Context.Response itself.
A field carrying a value that reports itself missing is left out, which is what a conditional field is for -- the field a person may not see is absent rather than present and empty.
The body is built before anything is written. An encoder writing straight into the response has already sent the status and half an object by the time it reports that it cannot marshal the rest, and neither can be taken back.
func (*Context) Old ¶
Old returns what was typed in a field on the request that was rejected, or empty.
A view reads it through view.Page.OldValue rather than through here -- the page has the state on it by the time a view runs. This is for the handler that needs the value in Go: re-deriving a select's options from what was chosen, for instance.
It is always empty for a password field, by construction. See session.Flash.
func (*Context) Redirect ¶
Redirect answers a redirect, and does the right thing under HTMX.
An HTMX request that gets a 302 follows it inside the fragment, so the whole page ends up nested in a div. HX-Redirect is the header that makes the browser navigate instead. Handling it here means no application has to remember.
func (*Context) RedirectRoute ¶
RedirectRoute redirects to a named route, with its parameters filled in order.
It is Redirect over URL, and it exists so that the address a person is sent to after a POST is looked up by name like every other address in the application. An unknown name is logged by URL and answered as a redirect to "/", because the alternative -- a Location header that is empty -- is a browser that stays where it is with no explanation.
func (*Context) URL ¶
URL is the path of a named route, with its parameters filled in order.
ctx.URL("posts.show", post.ID) -> "/posts/01J.../"
It is what a controller hands a view instead of building a path by hand. "/posts/"+id compiles and keeps compiling after the route moves; this stops working the moment the name is wrong, and says so.
An unknown name or a wrong number of parameters returns empty and is logged at ERROR with the name. Empty is what the views already treat as "there is no link here" -- a page with a missing button is recoverable, and a template renderer that panics takes the whole page down to report something a missing link would have said better.
func (*Context) User ¶
User is who is signed in, and whether anybody is.
It reads the subject the authentication middleware put on the context, and it is the only thing a controller may ask about identity. It is deliberately not a Grant: a Grant is minted by a policy for one action on one thing, and a controller that could produce one would be a controller that authorises itself.
func (*Context) View ¶
View renders a page. The data is a typed struct, never a map.
return ctx.View("invoices/index", IndexData{Invoices: list})
A map would compile and render blank on a typo, which is the failure this framework exists to make impossible.
func (*Context) WantsJSON ¶
WantsJSON reports whether this request asked for JSON rather than a page.
htmx is deliberately excluded even though it sends X-Requested-With: it swaps HTML, so answering it with JSON puts a JSON document inside a div.
hesape/exception states the same rule as the default of its Config.RenderJSONWhen, because it must answer a failure on a request that never reached a Context. The two are the same three lines and must not drift; folding them into one is waiting on the layer that wires the handler.
func (*Context) WithCookie ¶
WithCookie writes a cookie on the answer and returns the Context, so that the line that sets one reads as one line:
return ctx.WithCookie(pref).Redirect("/settings")
It is a plain stdhttp.Cookie and not a struct of this package's own: the standard library's has every attribute there is, and a wrapper would have to be taught each new one.
type ExceptionHandler ¶
type ExceptionHandler interface {
// Report records the error wherever failures are recorded. It is separate
// from Render because an error that was answered still has to be reported,
// and a report that only happens when a page is drawn misses every failure
// on a queued job.
Report(ctx context.Context, err error)
// Render writes the answer: the debug page in development, a status page
// anywhere else.
Render(w stdhttp.ResponseWriter, r *stdhttp.Request, err error)
}
ExceptionHandler turns an error that reached the edge into an answer.
Declared here and implemented in hesape/exception, which must not import this package: exception produces middleware through pipeline precisely so that the error path stays below the request path. The kernel wires the concrete one at boot, and hesape/routing calls it instead of panicking.
type Intended ¶
type Intended struct {
// contains filtered or unexported fields
}
Intended is where somebody was going when a guard turned them away.
It lives with the rest of what answers a request rather than with the session: the value it carries is an address, the whole of its correctness is LocalPath, and hesape/session never validates a URL.
It holds no state of its own -- the address is in a signed cookie in the browser -- so one value serves every request, and it is wired once at boot beside the Flash and the session store, over the same application key.
func NewIntended ¶
NewIntended returns an Intended over the application key.
The same key as the session, the flash and the signed links, because they are the same secret: an attacker who has it does not need four. Pass secure=false only in development -- without the Secure attribute the cookie travels over plain HTTP, and it carries where somebody was going.
func (*Intended) Clear ¶
func (i *Intended) Clear(w stdhttp.ResponseWriter)
Clear tells the browser to drop the address.
One function and not two copies of a cookie literal, because the two copies have to agree on Path: a clearing cookie written for a different path does not replace the one that is there, and the address quietly survives the thing that was supposed to spend it.
Intended.Take calls it, which spends the address. It is exported for the other caller, which is signing out: the address outlives a session by up to IntendedLifetime, and a shared machine changes hands at exactly that moment -- the next person to sign in would be carried to the page the previous one was refused. A sign-out handler calls it beside session.Invalidate.
func (*Intended) Remember ¶
func (i *Intended) Remember(w stdhttp.ResponseWriter, r *stdhttp.Request)
Remember records where this request was going, so that the sign-in screen it is about to be sent to can finish the journey.
The guard is the only thing that knows what the person was reaching for, and by the time they have typed a password that request is gone. Without it every sign-in ends at the front page, and somebody who followed a link to one invoice has to find it again.
Why it is signed rather than merely same-site ¶
The value decides where a browser goes immediately after authenticating, so whoever can write it can choose where every person in the application lands. SameSite=Strict stops another SITE from setting it, and does not stop another HOST on the same registrable domain: a cookie set on ".example.com" by anything holding a subdomain -- a customer's CNAME, a forgotten staging box, a vendor's status page -- arrives here indistinguishable from ours. An HMAC does stop it, because the attacker does not have the application key, and it comes with the expiry signed into the same bytes so a stale address cannot be replayed by keeping the cookie alive.
The destination is checked for being local anyway, on the way in and on the way out, because a signature only proves that WE wrote the value.
What it declines to remember ¶
Only a GET, because the address is replayed by a browser navigation: a POST remembered here is a form submission turned into a link, which either answers 405 or, on a route that also accepts GET, performs something the person did not ask for a second time.
Only a whole page, never an HTMX fragment. A hx-get that is refused would otherwise be remembered as "/inbox/rows?page=2", and after signing in the person lands on that partial: no layout, no navigation, and it reads as a broken deploy. A boosted navigation is a page and is kept -- hx-boost is how most links in this stack are followed, so dropping it would drop nearly everything.
It writes nothing when there is nothing worth writing, so a caller has no branch to get wrong.
func (*Intended) Take ¶
Take returns the address Remember stored, and clears it.
It is meant to be the whole of a sign-in handler's last line:
return ctx.Redirect(intended.Take(ctx.Response, ctx.Request, "/"))
The fallback is a parameter rather than a constant because it is the one part that genuinely differs -- a blog sends people to the front page, an application to its dashboard -- and taking it here means no caller has to write the branch for "there was nowhere in particular".
It answers the fallback for anything it cannot prove ¶
A forged or foreign signature, an expired one, a value that is not a local address: all of them are somebody's redirect that is not ours, and none of them is worth telling the person about at the moment they have just signed in. The check that the address is local is what keeps this from being an open redirect -- "sign in and then continue to https://evil.example/login" is the oldest phishing link there is, and it belongs here rather than in each project's handler precisely because every project would otherwise have to remember it.
It is consumed, not read: the cookie is cleared whatever it said, so the address is used once. An address left standing is one a person meets again at the next sign-in from this browser, weeks later, with no idea why.
type JsonResource ¶ added in v0.7.0
type JsonResource interface {
// ToArray returns the fields that may leave, by name.
ToArray() map[string]any
// With returns what goes beside them at the top level of the response:
// metadata about the answer rather than the thing being answered with.
With() map[string]any
}
JsonResource is what Context.JSON takes: the fields a type is allowed to answer with, declared once beside the type rather than remembered at each place it is answered from.
It is declared here rather than imported from the resource layer, which declares the same contract: that layer builds a response and imports this package to do it, so an import back would be a cycle. Go compares an interface by its methods, so a resource satisfies this one by being what it already is, and the two names are one contract rather than two.
type JsonResponse ¶
type JsonResponse struct {
Response
// contains filtered or unexported fields
}
JsonResponse is a Response whose body is the JSON encoding of a value, with the encoding flags kept so that SetEncodingOptions can re-encode what is already there.
It embeds Response, so Status, Header, WithCookie, ThrowResponse and the rest are the same methods on the same fields.
func FromJsonString ¶
func FromJsonString(data string, args ...any) (*JsonResponse, error)
FromJsonString builds a response around a string that is already encoded JSON.
The variadic arguments are status (200) and headers (none).
func NewJsonResponse ¶
func NewJsonResponse(data any, args ...any) (*JsonResponse, error)
NewJsonResponse builds a JsonResponse.
The variadic arguments are, in order: status (200), headers (none), options (0) and json (false -- whether data is already an encoded JSON string).
Returns (*JsonResponse, error): the error is set when data cannot be encoded.
func (*JsonResponse) Cookie ¶
func (r *JsonResponse) Cookie(cookie *stdhttp.Cookie) *JsonResponse
Cookie is Response.Cookie, typed for the chain.
func (*JsonResponse) GetData ¶
func (r *JsonResponse) GetData(args ...any) (any, error)
GetData is the payload decoded back out of the encoded body.
The variadic args are accepted and ignored: they exist for a caller passing a decoding mode and a depth limit, neither of which encoding/json needs -- it always decodes into map[string]any and has no depth limit. An empty body decodes as (nil, nil).
func (*JsonResponse) GetEncodingOptions ¶
func (r *JsonResponse) GetEncodingOptions() int
GetEncodingOptions is the encoding flags currently set.
func (*JsonResponse) HasEncodingOption ¶
func (r *JsonResponse) HasEncodingOption(option int) bool
HasEncodingOption reports whether a flag is set in the encoding options.
func (*JsonResponse) Header ¶
func (r *JsonResponse) Header(key string, values ...any) *JsonResponse
Header is Response.Header, typed for the chain.
func (*JsonResponse) SetCallback ¶
func (r *JsonResponse) SetCallback(callback string) *JsonResponse
SetCallback sets the JSONP callback and rebuilds the body. WithCallback calls it.
func (*JsonResponse) SetData ¶
func (r *JsonResponse) SetData(data any) (*JsonResponse, error)
SetData encodes the value and makes it the body. It keeps the value on original, which is what GetOriginalContent returns.
Returns an error when encoding fails, unless JSONPartialOutputOnError is set, in which case it falls back to an empty object instead.
func (*JsonResponse) SetEncodingOptions ¶
func (r *JsonResponse) SetEncodingOptions(options int) (*JsonResponse, error)
SetEncodingOptions changes the flags and re-encodes what is already there.
func (*JsonResponse) SetStatusCode ¶
func (r *JsonResponse) SetStatusCode(code int) *JsonResponse
SetStatusCode is Response.SetStatusCode, typed for the chain.
func (*JsonResponse) WithCallback ¶
func (r *JsonResponse) WithCallback(callback string) *JsonResponse
WithCallback sets the JSONP callback.
func (*JsonResponse) WithCookie ¶
func (r *JsonResponse) WithCookie(cookie *stdhttp.Cookie) *JsonResponse
WithCookie is Response.WithCookie, typed for the chain.
func (*JsonResponse) WithException ¶
func (r *JsonResponse) WithException(err error) *JsonResponse
WithException is Response.WithException, typed for the chain.
func (*JsonResponse) WithHeaders ¶
func (r *JsonResponse) WithHeaders(headers stdhttp.Header) *JsonResponse
WithHeaders is Response.WithHeaders, typed for the chain.
func (*JsonResponse) WithoutCookie ¶
func (r *JsonResponse) WithoutCookie(name string, args ...string) *JsonResponse
WithoutCookie is Response.WithoutCookie, typed for the chain.
func (*JsonResponse) WithoutHeader ¶
func (r *JsonResponse) WithoutHeader(keys ...string) *JsonResponse
WithoutHeader is Response.WithoutHeader, typed for the chain.
type JsonSerializable ¶
type JsonSerializable interface {
// JsonSerialize is the value that gets encoded in this one's place.
JsonSerialize() any
}
JsonSerializable is a value that names what of itself is encoded. Go's encoding/json reaches the same end through json.Marshaler, which is checked too.
type Middleware ¶
type Middleware = pipeline.Middleware[stdhttp.Handler]
Middleware is the standard net/http signature, named.
It is an alias and not a defined type, and the = is the whole point: hesape/session, hesape/cookie and hesape/exception all produce middleware, and none of them may import this package -- exception in particular is what answers a failure that never reached a Context. With an alias they return pipeline.Middleware[stdhttp.Handler] and it IS an http.Middleware; with a defined type each of them would have to import the layer that calls it, which is the cycle this collection is being reorganised to remove.
Being the standard func(stdhttp.Handler) stdhttp.Handler underneath is what keeps every middleware written for the Go ecosystem usable here unchanged. Note the one place that is not free: a []func(stdhttp.Handler) stdhttp.Handler is not assignable to a []Middleware, because a slice of an aliased element type converts and a slice type does not.
Compose with pipeline.Chain. There is no Chain here: a second one, differing only in being non-generic, is a second way to do the same thing.
type RedirectResponse ¶
type RedirectResponse struct {
Response
// contains filtered or unexported fields
}
RedirectResponse is a Response whose whole content is a Location header, plus the four things a redirect carries across the request boundary -- flashed data, old input, errors and cookies.
It embeds Response, so its own methods and Response's are on the same fields.
The tenant is never flashed and never read back: WithInput carries what the browser typed, and what a repository is allowed to see comes from the auth.Grant a policy minted, not from a value that survived a redirect.
func NewRedirectResponse ¶
func NewRedirectResponse(to string, args ...any) *RedirectResponse
NewRedirectResponse builds a RedirectResponse.
The variadic arguments are status (302) and headers (none).
func (*RedirectResponse) Cookie ¶
func (r *RedirectResponse) Cookie(cookie *stdhttp.Cookie) *RedirectResponse
Cookie is Response.Cookie, typed for the chain.
func (*RedirectResponse) EnforceSameOrigin ¶
func (r *RedirectResponse) EnforceSameOrigin(fallback string, args ...bool) *RedirectResponse
EnforceSameOrigin replaces the target with the fallback unless it is on the same origin as the request.
It is the open-redirect defence on the Response side, and it answers the same question LocalPath answers on the Request side -- by comparing hosts rather than by refusing a host at all. A framework redirect built from a path should still go through LocalPath; this is for the target that genuinely carries a host.
The variadic arguments are validateScheme and validatePort, both defaulting to true.
func (*RedirectResponse) ExceptInput ¶
func (r *RedirectResponse) ExceptInput(keys ...string) *RedirectResponse
ExceptInput flashes everything except the named keys.
The keys are the form's own reason to drop a field. A password, a one-time code or a request token does not need naming here: the session store drops the secret fields whatever the caller passes.
func (*RedirectResponse) GetOriginalContent ¶
func (r *RedirectResponse) GetOriginalContent() any
GetOriginalContent returns nil: a redirect has no body to have had an original.
func (*RedirectResponse) GetRequest ¶
func (r *RedirectResponse) GetRequest() *Request
GetRequest is the request this redirect answers.
func (*RedirectResponse) GetSession ¶
func (r *RedirectResponse) GetSession() *session.Store
GetSession is the session store this redirect flashes into.
func (*RedirectResponse) GetTargetUrl ¶
func (r *RedirectResponse) GetTargetUrl() string
GetTargetUrl is where the browser is being sent.
func (*RedirectResponse) Header ¶
func (r *RedirectResponse) Header(key string, values ...any) *RedirectResponse
Header is Response.Header, typed for the chain.
func (*RedirectResponse) OnlyInput ¶
func (r *RedirectResponse) OnlyInput(keys ...string) *RedirectResponse
OnlyInput flashes only the named keys.
func (*RedirectResponse) SetRequest ¶
func (r *RedirectResponse) SetRequest(request *Request) *RedirectResponse
SetRequest sets the request WithInput and OnlyInput read from.
func (*RedirectResponse) SetSession ¶
func (r *RedirectResponse) SetSession(store *session.Store) *RedirectResponse
SetSession sets the session store With, WithInput and WithErrors flash into.
func (*RedirectResponse) SetTargetUrl ¶
func (r *RedirectResponse) SetTargetUrl(to string) *RedirectResponse
SetTargetUrl sets where the browser is being sent. WithFragment and EnforceSameOrigin both call it.
func (*RedirectResponse) With ¶
func (r *RedirectResponse) With(key any, value ...any) *RedirectResponse
With flashes a piece of data to the session, so the page that follows the redirect can read it once.
The key may be a map or a single key: a map flashes every pair in it, and a single key flashes with value. The value is variadic, defaulting to nil.
func (*RedirectResponse) WithCookie ¶
func (r *RedirectResponse) WithCookie(cookie *stdhttp.Cookie) *RedirectResponse
WithCookie is Response.WithCookie, typed for the chain.
func (*RedirectResponse) WithCookies ¶
func (r *RedirectResponse) WithCookies(cookies []*stdhttp.Cookie) *RedirectResponse
WithCookies adds several cookies at once.
func (*RedirectResponse) WithErrors ¶
func (r *RedirectResponse) WithErrors(provider any, key ...string) *RedirectResponse
WithErrors flashes a container of messages into the session's error bag, so the page that follows draws them beside the fields they belong to.
provider takes any of several shapes: a MessageProvider, a *MessageBag, a map, a slice of strings, a single string, or validation.Errors, which is what this module's validator produces. The variadic key names the bag, defaulting to "default" -- which is what lets two forms on one page keep their messages apart.
func (*RedirectResponse) WithException ¶
func (r *RedirectResponse) WithException(err error) *RedirectResponse
WithException is Response.WithException, typed for the chain.
func (*RedirectResponse) WithFragment ¶
func (r *RedirectResponse) WithFragment(fragment string) *RedirectResponse
WithFragment puts a "#anchor" on the target, replacing one that is already there.
func (*RedirectResponse) WithHeaders ¶
func (r *RedirectResponse) WithHeaders(headers stdhttp.Header) *RedirectResponse
WithHeaders is Response.WithHeaders, typed for the chain.
func (*RedirectResponse) WithInput ¶
func (r *RedirectResponse) WithInput(input ...map[string]any) *RedirectResponse
WithInput flashes the input to the session so the form that was rejected comes back with the boxes filled.
It is one half of a pair with Request.Old, which reads what this writes. If the two diverge, the form comes back blank, which is the bug the pair exists to not have.
Two things never reach the session: an uploaded file, which is not something to put back in a text box, and a secret field, which the session store drops whatever the caller passes.
The variadic argument is the optional input to flash; with none, the request's own input is used.
func (*RedirectResponse) WithoutCookie ¶
func (r *RedirectResponse) WithoutCookie(name string, args ...string) *RedirectResponse
WithoutCookie is Response.WithoutCookie, typed for the chain.
func (*RedirectResponse) WithoutFragment ¶
func (r *RedirectResponse) WithoutFragment() *RedirectResponse
WithoutFragment drops any "#anchor" from the target.
func (*RedirectResponse) WithoutHeader ¶
func (r *RedirectResponse) WithoutHeader(keys ...string) *RedirectResponse
WithoutHeader is Response.WithoutHeader, typed for the chain.
type Renderable ¶
type Renderable interface {
// Render draws the value and returns the result.
Render() (string, error)
}
Renderable is a value that draws itself, a view being the one that matters. SetContent calls it rather than casting to string, so a failure inside a template is a failure and not an empty page.
Render returns (string, error): a rendering failure is reported through the error rather than by panicking.
type Renderer ¶
type Renderer interface {
Render(ctx context.Context, w stdhttp.ResponseWriter, status int, name string, data any) error
}
Renderer draws a named view with typed data.
It is an interface here, and implemented in the view package, for one reason: the view package imports http, so http importing the view package back would be a cycle. The kernel wires the concrete one at boot.
type Request ¶
type Request struct {
// contains filtered or unexported fields
}
Request wraps a *net/http.Request and provides the methods a controller action reaches for: the input the body and the query string carried, the headers, the cookies, the files, the session that survived the redirect, and the route that matched.
The tenant NEVER comes from the request. There is no method here that reads a tenant id out of a path parameter, a query string, a header or a body field, and adding one would be the most direct route to a cross-tenant leak. The tenant is on the auth.Grant the policy mints, and the repository reads it from there. If a method here seems to offer tenant access, it does not.
A note on names: method names use the initial uppercase Go requires. Input is Input, never Get. Old is Old, never Previous. Initialisms are upper case: FullURL, IsJSON. Where a failure can happen, the method returns (T, error).
func CreateFrom ¶
CreateFrom is a new Request built from another, sharing its query, body, headers, session and resolvers. It is what a middleware that needs a modified copy of the request uses.
func CreateFromBase ¶
CreateFromBase returns NewRequest called on the given *http.Request.
func NewRequest ¶
NewRequest wraps a *net/http.Request. It is the constructor a handler or a test calls; the router calls it once per request.
func (*Request) Accepts ¶
Accepts reports whether the request accepts any of the given content types. An empty Accept header accepts everything.
func (*Request) AcceptsAnyContentType ¶
AcceptsAnyContentType reports whether the request accepts any content type (empty Accept, "*/*" or "*").
func (*Request) AcceptsHTML ¶
AcceptsHTML reports whether the request accepts "text/html".
func (*Request) AcceptsJSON ¶
AcceptsJSON reports whether the request accepts "application/json".
func (*Request) Ajax ¶
Ajax reports whether the request was made by XMLHttpRequest. htmx sends this header too; see WantsJSON for why htmx is deliberately excluded there.
func (*Request) Array ¶
Array is the value as a []any. When more than one key is given, returns Only for those keys.
func (*Request) BearerToken ¶
BearerToken is the token of an Authorization: Bearer header, or empty. The scheme is compared case-insensitively (RFC 9110) and the value is trimmed.
func (*Request) Boolean ¶
Boolean is the value as a bool. Returns true for "1", "true", "on", "yes" (case-insensitive).
func (*Request) Collect ¶
Collect is the value as a []any, which is the shape a hesape/collections.Collection is built from. With more than one key, returns Only for those keys as a slice.
func (*Request) Date ¶
Date is the value parsed as a time.Time. Without a format, parses as RFC3339; with a format, parses against it. Returns the zero time when the key is not filled or the value does not parse.
func (*Request) DecodedPath ¶
DecodedPath is the path, URL-decoded.
func (*Request) Duplicate ¶
Duplicate is a copy of the request with optional overrides for query, post, cookies and server. Nil arguments keep the original.
func (*Request) Enum ¶
Enum is the value as an enum, via a tryFrom function the caller supplies. Returns nil when the key is not filled or the value does not match any enum case.
The caller passes the function that turns a string into the enum value and reports whether it matched. A Go enum that implements a TryFrom method can hand it directly:
status, ok := req.Enum("status", StatusTryFrom)
func (*Request) ExpectsJSON ¶
ExpectsJSON reports whether the request probably expects a JSON response. True when the request is AJAX (not PJAX) and accepts any content type, or when it explicitly wants JSON.
func (*Request) File ¶
File is the uploaded file(s) at the key, or nil. Returns *multipart.FileHeader, which UploadedFile wraps into the fuller file surface.
func (*Request) Filled ¶
Filled reports whether every given key has a non-empty value. A boolean true and a non-empty list count as filled; an empty string or a string of whitespace does not.
func (*Request) FilterPrecognitiveRules ¶
FilterPrecognitiveRules returns only the rules whose attribute matches one of the Precognition-Validate-Only header's patterns, when that header is present; otherwise it returns the rules unchanged.
func (*Request) Fingerprint ¶
Fingerprint is a unique hash of the route methods, domain, URI and client IP. Panics when no route is set.
func (*Request) Flash ¶
func (r *Request) Flash()
Flash flashes the request's input to the session, so the next request can read it as old input.
Panics when no session is set. A handler that calls this is inside a flow that started with a session, and a missing one there is a wiring error the caller should see immediately.
func (*Request) FlashExcept ¶
FlashExcept flashes the input except the given keys.
The keys are the form's own reason to drop a field -- a long body already saved elsewhere, a step the next page recomputes. A credential is not that reason and does not need naming here: the session store drops the secret fields whatever the caller passes.
func (*Request) Fluent ¶
Fluent is the input as a map that reads with dot notation. It is the input source merged with the query, and a read on a missing key returns nil rather than panicking.
func (*Request) Flush ¶
func (r *Request) Flush()
Flush removes all of the old input from the session.
func (*Request) Format ¶
Format is the response format the request prefers, derived from the Accept header. Returns the default ("html") when nothing matches.
func (*Request) FullURLIs ¶
FullURLIs reports whether the full URL matches any of the patterns. "*" is the only wildcard.
func (*Request) FullURLWithQuery ¶
FullURLWithQuery is the full URL with the given query parameters merged into the existing ones.
func (*Request) FullURLWithoutQuery ¶
FullURLWithoutQuery is the full URL without the given query parameters.
func (*Request) GetAcceptableContentTypes ¶
GetAcceptableContentTypes is the content types the Accept header lists, in priority order, without quality parameters.
func (*Request) GetRouteResolver ¶
GetRouteResolver is the installed resolver, or a no-op when none was set.
func (*Request) GetSession ¶
GetSession is the session store. Panics when none is set. It is an alias for Session.
func (*Request) GetUserResolver ¶
GetUserResolver is the installed resolver, or a no-op when none was set.
func (*Request) HTTPHost ¶
HTTPHost is the host as the browser sent it, port included when the browser sent one.
func (*Request) HasHeader ¶
HasHeader reports whether a header is present, including when its value is empty.
func (*Request) HasSession ¶
HasSession reports whether a session store was set.
func (*Request) Header ¶
Header is the first value of a header, or default when the header is absent. The name is canonicalised by net/http.
func (*Request) IP ¶
IP is the client address, without the port. Behind a proxy it is whatever the trust-proxies middleware wrote onto RemoteAddr.
func (*Request) IPs ¶
IPs is the chain of client addresses, nearest hop first. With no proxy in front, this is a list of one.
It is the X-Forwarded-For entries plus the address the request actually came from, with our own proxies removed, reversed so that IPs()[0] is IP(). It used to return the one address and no chain, so behind a proxy sending "X-Forwarded-For: 1.1.1.1, 2.2.2.2" it answered with a single element instead of two: the doc said "the chain of client addresses" and there was never a chain.
Only the first entry is trustworthy. See WithForwardedFor.
func (*Request) Input ¶
Input is a value from the request's input source merged with the query string, read with dot notation. The input source is the JSON body for JSON requests, the post form for POST/PUT/PATCH/DELETE, and the query string for GET/HEAD. The input source takes precedence over the query.
A JSON body is read the same way as form input: Input("user.name") descends by dot through the decoded payload, so a nested JSON field is found the same way a flattened form field is.
With no key, returns the whole merged map.
func (*Request) Is ¶
Is reports whether the decoded path matches any of the patterns. "*" is the only wildcard.
func (*Request) IsAttemptingPrecognition ¶
IsAttemptingPrecognition reports whether the Precognition header is "true".
func (*Request) IsJSON ¶
IsJSON reports whether the Content-Type header indicates JSON. The check is for "/json" or "+json" anywhere in the content type, so "application/json", "application/vnd.api+json" and "application/problem+json" all match.
func (*Request) IsNotFilled ¶
IsNotFilled reports whether every given key is empty.
func (*Request) IsPrecognitive ¶
IsPrecognitive reports whether the request was marked as a precognitive validation request.
func (*Request) Json ¶
Json is the decoded JSON body. With a key, returns the value at the dotted path; without, returns the whole payload.
func (*Request) Merge ¶
Merge merges the given input into the request's input source, overwriting existing keys. Returns the request, so calls can chain.
func (*Request) MergeIfMissing ¶
MergeIfMissing merges the given input only for keys that are missing from the request.
func (*Request) Old ¶
Old is the value that was typed into the form that was rejected, so the page it was sent back to can fill the boxes again. With no key, returns all of the old input; with a key, returns the value at that key or the default.
It is what makes a form come back pre-filled after a rejection, and its pair with RedirectResponse.WithInput is the two ends of one thing: WithInput writes into the session's old input, and Old reads it. If the two diverge, the form comes back blank, which is the bug this exists to not have.
Returns the default when no session is set.
func (*Request) Path ¶
Path is the path without leading or trailing slashes, "/" when the path is empty.
func (*Request) Post ¶
Post is a post body parameter, or a default when it is absent. With an empty key, returns empty.
func (*Request) PreferSafeContent ¶
PreferSafeContent reports whether the Prefer header asks for safe content.
func (*Request) Prefers ¶
Prefers is the most suitable content type among the given ones, based on content negotiation. Returns empty when none match.
func (*Request) Query ¶
Query is a query string parameter, or a default when it is absent. With an empty key, returns empty.
func (*Request) RawRequest ¶
RawRequest returns the *net/http.Request this wraps, for the rare case that needs something Request's own methods do not cover. It is not the common path: the methods on Request are what a controller should reach for.
func (*Request) Route ¶
Route is the matched route, or a specific parameter from it. With no arguments, returns the Route. With a string argument, returns the parameter of that name from the route. With a default, returns the default when the parameter is absent.
Returns nil when no resolver is set.
func (*Request) RouteIs ¶
RouteIs reports whether the route name matches any of the patterns. Returns false when no route is set.
func (*Request) SchemeAndHttpHost ¶
SchemeAndHttpHost is scheme + "://" + host.
func (*Request) Segment ¶
Segment is the nth segment of the path (1-based), or default when the index is out of range.
func (*Request) Server ¶
Server is a server variable, or default. The common CGI-style keys are mapped from the request; an unknown HTTP_* key falls back to the corresponding header.
func (*Request) SetDefaultRequestLocale ¶
SetDefaultRequestLocale stores the default locale on the request, for the view layer to fall back to.
func (*Request) SetLaravelSession ¶
SetLaravelSession is an alias for SetSession.
func (*Request) SetPrecognitive ¶
SetPrecognitive marks the request as precognitive, which IsPrecognitive then reports.
func (*Request) SetRequestLocale ¶
SetRequestLocale stores the locale on the request for the view layer to read. It is a string because Go has no locale type, and the view layer reads it through the same string.
func (*Request) SetRouteResolver ¶
SetRouteResolver sets the resolver Route calls.
func (*Request) SetSession ¶
SetSession sets the session store.
func (*Request) SetUserResolver ¶
SetUserResolver sets the resolver User calls.
func (*Request) URL ¶
URL is the request URL without the query string. It is the address a link inside the page uses, so that the application keeps working behind a proxy, on a staging host and on somebody's laptop.
*http.Request carries scheme and host in URL when it was parsed from a full target, and in Host when it arrived over the wire. Both are checked, so a test that builds a request from "http://example.com/path" and a server that builds one from "/path" both get the right answer.
func (*Request) User ¶
User is the authenticated user, via the resolver the auth middleware installed. Returns nil when no resolver is set.
func (*Request) Validate ¶
func (r *Request) Validate(rules *validation.Set, opts ...validation.ValidatorOption) (validation.Input, error)
Validate runs the rules against the request's input and returns the validated values, or the error the failures make.
The rules are a compiled *validation.Set, built at boot with validation.MustCompile. The options carry the context, the Grant and the collaborators the rules that leave the process need.
func (*Request) ValidateWithBag ¶
func (r *Request) ValidateWithBag(bag string, rules *validation.Set, opts ...validation.ValidatorOption) (validation.Input, error)
ValidateWithBag is Validate with the failures named, so that two forms on one page do not draw each other's errors.
func (*Request) WhenFilled ¶
WhenFilled calls the callback with the value when the key is filled, otherwise calls the default.
type Response ¶
type Response struct {
// contains filtered or unexported fields
}
Response is a status, a set of headers, a set of cookies and a body, built before anything is written to the wire.
It is a value and not a stdhttp.ResponseWriter: a controller returns it, middleware may still add a header to it, and Response.Send is the one place it meets the standard library.
func NewResponse ¶
NewResponse builds a Response.
Returns (*Response, error): the error is set when the content cannot be encoded. The variadic arguments are status 200 and no headers.
func (*Response) Cookies ¶
Cookies returns the cookies this response carries. Response.Send writes them.
func (*Response) GetCallback ¶
GetCallback is the JSONP callback, empty when there is none. Only a JsonResponse ever sets one.
func (*Response) GetContent ¶
GetContent is the encoded body, empty when there is none.
func (*Response) GetOriginalContent ¶
GetOriginalContent is what SetContent was handed, before it became JSON or HTML. A Response wrapping a Response unwraps.
func (*Response) GetProtocolVersion ¶
GetProtocolVersion is the HTTP protocol version string.
func (*Response) GetStatusCode ¶
GetStatusCode is an alias for Status.
func (*Response) Headers ¶
Headers is the response's headers. It is a getter because a Go field would let a caller swap the whole map.
func (*Response) Send ¶
func (r *Response) Send(w stdhttp.ResponseWriter) error
Send writes the status, the headers, the cookies and the body to the wire.
It is the one place this type meets stdhttp.ResponseWriter, which is the point of building a Response at all: everything before this is a value that middleware may still change.
func (*Response) ServeHTTP ¶
func (r *Response) ServeHTTP(w stdhttp.ResponseWriter, _ *stdhttp.Request)
ServeHTTP makes a Response a stdhttp.Handler, so a route may answer with one directly. It is Send with the arguments a handler is given.
func (*Response) SetContent ¶
SetContent sets the body, encoding as needed.
It keeps what it was handed on original: a value that is "JSONable" -- Arrayable, Jsonable, JsonSerializable, json.Marshaler, a map or a slice -- becomes JSON and sets the Content-Type; a Renderable is rendered; anything else is cast to a string.
Returns (*Response, error): the error is set when JSON encoding fails.
func (*Response) SetProtocolVersion ¶
SetProtocolVersion sets the HTTP protocol version string. NewResponse calls it with "1.0".
func (*Response) SetStatusCode ¶
SetStatusCode sets the status code.
func (*Response) StatusText ¶
StatusText is the reason phrase.
func (*Response) ThrowResponse ¶
ThrowResponse wraps this response in an HttpResponseException so the layer above sends it instead of rendering.
The error is returned rather than thrown; the caller returns it in turn.
func (*Response) WithCookie ¶
WithCookie adds a cookie.
func (*Response) WithException ¶
WithException records the error that produced this answer.
func (*Response) WithHeaders ¶
WithHeaders adds several headers at once.
func (*Response) WithoutCookie ¶
WithoutCookie expires a cookie when the response is sent. MaxAge -1 is what tells a browser to delete it immediately.
The variadic arguments are an optional path and domain.
func (*Response) WithoutHeader ¶
WithoutHeader removes headers.
type Route ¶
type Route interface {
RouteName() string
Parameter(name string, def ...any) any
Parameters() map[string]any
Methods() []string
Domain() string
URI() string
}
Route is the minimal view of the matched route the request needs.
The concrete type is hesape/routing.Route. The interface is declared here so that hesape/http does not import hesape/routing, which would be a cycle: routing builds the request, and the request needs the route's name and parameters, not the router itself.
type State ¶
type State struct {
// Errors is what the request that redirected here failed validation on,
// keyed by the name of the form input.
Errors validation.Errors
// Old is what was typed on that request, minus every password field. See
// session.Flash for what "every password field" means and why the messages
// for those fields survive when their values do not.
Old url.Values
}
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 the middleware that spends the flash cookie, in hesape/session, and read by view.New. Nothing else writes it, and no handler ever has to: a handler that had to carry the errors from the middleware to the page would be a handler that can forget to, and forgetting is invisible -- the form comes back blank, exactly as it did before any of this existed.
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.
type StoreOptions ¶
type StoreOptions struct {
// Disk is the disk to write to. The zero value is the default disk.
Disk *filesystem.Disk
// Visibility sets the stored file's visibility. StorePublicly and
// StorePubliclyAs set it to "public".
Visibility string
}
StoreOptions is what Store and its three siblings take to say where and how to store a file: a struct says this without a caller having to know a set of map keys by name.
type StreamedEvent ¶
type StreamedEvent struct {
// Event is the name of the event.
Event string
// Data is the payload.
Data any
}
StreamedEvent is one named event on a server-sent event stream.
func NewStreamedEvent ¶
func NewStreamedEvent(event string, data any) *StreamedEvent
NewStreamedEvent builds a StreamedEvent.
func (*StreamedEvent) String ¶
func (e *StreamedEvent) String() string
String formats the event the way the server-sent event wire format wants it: an "event:" line, one "data:" line per line of payload, and a blank line.
It is here and not in a writer of its own because StreamedEvent is two fields and nothing else, and the framework that reads it needs the bytes.
type URLGenerator ¶
URLGenerator turns the name of a route into its path.
It is an interface here for the reason Renderer is: the route table lives in hesape/routing, which imports this package to build a Context, so naming its type here would be a cycle. hesape/routing.Routes satisfies it.
The method is Route and not URL because that is what the table calls it, and because Context.URL is the caller-facing name -- one word per side, and neither of them shadows net/url.
type UploadedFile ¶
type UploadedFile struct {
BaseFile
// contains filtered or unexported fields
}
UploadedFile is one file that arrived in a form field, with the file-info methods and the four store variants.
It embeds BaseFile, so the two share the same methods on the same fields.
Storing one needs a Grant ¶
The store methods take a context and an auth.Grant, because filesystem.Disk.PutFileAs does: a file is written under a tenant, the tenant comes off the Grant a policy minted, and there is no path from a form field to a storage prefix.
func NewUploadedFile ¶
func NewUploadedFile(header *multipart.FileHeader, field string) *UploadedFile
NewUploadedFile builds an UploadedFile from the multipart part a browser sent.
This is the one constructor: a second package-level CreateFromBase for this type would collide with Request's, since Go namespaces by package rather than by type.
func NewUploadedFileFromPath ¶
func NewUploadedFileFromPath(pathname, originalName, mimeType string, test bool) *UploadedFile
NewUploadedFileFromPath builds an UploadedFile standing in for one that arrived, from a file that is already on disk -- the shape a testing factory uses. The test flag is what IsValid checks instead of looking for a real multipart part.
func (*UploadedFile) ClientExtension ¶
func (f *UploadedFile) ClientExtension() string
ClientExtension is an alias for GuessClientExtension.
func (*UploadedFile) Extension ¶
func (f *UploadedFile) Extension() string
Extension is an alias for GuessExtension.
func (*UploadedFile) Get ¶
func (f *UploadedFile) Get() ([]byte, error)
Get is the contents of the uploaded file.
Returns ([]byte, error): the error unwraps to ErrFileNotFound when the file is not valid.
func (*UploadedFile) GetClientMimeType ¶
func (f *UploadedFile) GetClientMimeType() string
GetClientMimeType is the type the client announced, which is a header somebody wrote.
func (*UploadedFile) GetClientOriginalExtension ¶
func (f *UploadedFile) GetClientOriginalExtension() string
GetClientOriginalExtension is the extension of the name the client announced.
func (*UploadedFile) GetClientOriginalName ¶
func (f *UploadedFile) GetClientOriginalName() string
GetClientOriginalName is the name the client announced.
func (*UploadedFile) GetError ¶
func (f *UploadedFile) GetError() int
GetError is the upload error code, 0 when there is none.
func (*UploadedFile) GetSize ¶
func (f *UploadedFile) GetSize() int64
GetSize is the byte count the server counted as the body arrived, which is the one value on an upload that did not come from the client.
func (*UploadedFile) GuessClientExtension ¶
func (f *UploadedFile) GuessClientExtension() string
GuessClientExtension is the extension guessed from the type the client announced.
func (*UploadedFile) GuessExtension ¶
func (f *UploadedFile) GuessExtension() string
GuessExtension is the extension guessed from the announced name, since the pathname of an upload is that name.
func (*UploadedFile) HashName ¶
func (f *UploadedFile) HashName(dir ...string) string
HashName is redeclared here so that the extension comes from the upload's own GuessExtension rather than the embedded BaseFile's.
func (*UploadedFile) IsValid ¶
func (f *UploadedFile) IsValid() bool
IsValid reports whether the upload completed and there are bytes behind it.
func (*UploadedFile) Open ¶
func (f *UploadedFile) Open() (io.ReadCloser, error)
Open returns a reader over the bytes. It is what Get and the store methods read through, and it may be called more than once.
func (*UploadedFile) Store ¶
func (f *UploadedFile) Store(ctx context.Context, g auth.Grant, directory string, options StoreOptions) (string, error)
Store writes the file under a directory with a random name, and returns the key it landed on.
Returns (string, error). The context and the Grant are what filesystem.Disk.PutFileAs requires: a file is stored under the tenant a policy authorised, never under one read off the request.
func (*UploadedFile) StoreAs ¶
func (f *UploadedFile) StoreAs(ctx context.Context, g auth.Grant, directory, name string, options StoreOptions) (string, error)
StoreAs writes the file under a directory with the name given, and returns the key it landed on.
Returns (string, error).
func (*UploadedFile) StorePublicly ¶
func (f *UploadedFile) StorePublicly(ctx context.Context, g auth.Grant, directory string, options StoreOptions) (string, error)
StorePublicly is Store with the visibility set to public.
func (*UploadedFile) StorePubliclyAs ¶
func (f *UploadedFile) StorePubliclyAs(ctx context.Context, g auth.Grant, directory, name string, options StoreOptions) (string, error)
StorePubliclyAs is StoreAs with the visibility set to public.
func (*UploadedFile) Upload ¶
func (f *UploadedFile) Upload(field string) filesystem.Upload
Upload converts this file into the filesystem.Upload the storage layer takes. It is the one place the two vocabularies meet, so that the conversion is written once.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package client is an HTTP client wrapping net/http.Client, with a testing surface built in: stub callbacks (Fake), response sequences, request recording, and assertions on what was sent.
|
Package client is an HTTP client wrapping net/http.Client, with a testing surface built in: stub callbacks (Fake), response sequences, request recording, and assertions on what was sent. |
|
concerns
Package concerns is intentionally empty.
|
Package concerns is intentionally empty. |
|
events
Package events holds the events dispatched during an HTTP client request lifecycle: RequestSending, ResponseReceived and ConnectionFailed.
|
Package events holds the events dispatched during an HTTP client request lifecycle: RequestSending, ResponseReceived and ConnectionFailed. |
|
promises
Package promises provides the two promise-like types the HTTP client returns from an asynchronous request: FluentPromise and LazyPromise.
|
Package promises provides the two promise-like types the HTTP client returns from an asynchronous request: FluentPromise and LazyPromise. |
|
Package concerns is intentionally empty.
|
Package concerns is intentionally empty. |
|
Package exceptions holds the exception types an HTTP response can carry: HttpResponseException, MalformedUrlException, OriginMismatchException, PostTooLargeException and ThrottleRequestsException.
|
Package exceptions holds the exception types an HTTP response can carry: HttpResponseException, MalformedUrlException, OriginMismatchException, PostTooLargeException and ThrottleRequestsException. |
|
Package middleware holds the standard middleware every application wires: CORS, security headers, body-size limits, host and proxy trust, and cache validation.
|
Package middleware holds the standard middleware every application wires: CORS, security headers, body-size limits, host and proxy trust, and cache validation. |
|
Package resources transforms data into the shapes a JSON response takes: conditional fields, wrapping, pagination metadata and resource collections.
|
Package resources transforms data into the shapes a JSON response takes: conditional fields, wrapping, pagination metadata and resource collections. |
|
json
Package json holds the concrete builders and response types that compose with the interfaces in the parent package hesape/http/resources.
|
Package json holds the concrete builders and response types that compose with the interfaces in the parent package hesape/http/resources. |
|
jsonapi
Package jsonapi provides the resource types and helpers for building JSON:API-compliant responses, including sparse fieldsets, included resources, and relationship resolution.
|
Package jsonapi provides the resource types and helpers for building JSON:API-compliant responses, including sparse fieldsets, included resources, and relationship resolution. |
|
Package testing helps a test build an UploadedFile without a real browser: a fake file of a given size or with given content, a fake image, and the MIME type lookups uploads are validated against.
|
Package testing helps a test build an UploadedFile without a real browser: a fake file of a given size or with given content, a fake image, and the MIME type lookups uploads are validated against. |