Documentation
¶
Overview ¶
Package htmlupdate serves HTML templates that can update themselves in place.
One URL answers two ways. Without the render header a request gets the ordinary complete document, so a browser without the runtime, a crawler, and curl are all unaffected. With it, the response carries only the boundaries whose markup actually changed.
The transport concerns live here rather than in htmlbind, because that package stays free of net/http so generated template code keeps working on TinyGo and WebAssembly targets.
What each entry reads from a request lives in internal/updatecore, so the second transport runtime answers the same wire contract rather than agreeing with this one. This package is the net/http half: it wraps a *http.Request in the reader that half takes, and owns everything that writes — the document and delta renders, the record streams, and the runtime asset handler.
Index ¶
- Constants
- Variables
- func ApplyTo(header http.Header, w http.ResponseWriter)
- func DecodeManifest(encoded string) delta.Manifest
- func EncodeManifest(manifest delta.Manifest) string
- func QueryBool(values url.Values, name string, target *bool) error
- func QueryFloat(values url.Values, name string, target *float64) error
- func QueryInt(values url.Values, name string, target *int) error
- func QueryOptional[T any](values url.Values, name string, target **T) error
- func QueryString[T ~string](values url.Values, name string, target *T) error
- func QueryTime(values url.Values, name string, target *time.Time) error
- func QueryURL(values url.Values, name string, target *url.URL) error
- func Redirect(w http.ResponseWriter, r *http.Request, url string, status int)
- func RuntimeSource() []byte
- func RuntimeVersion() string
- func SetStreamErrorHandler(fn func(error))
- type Asset
- type DeltaStream
- type Failure
- type FailureKind
- type ManifestEntry
- type Mode
- type Negotiated
- type Options
- func (o Options) CSRFToken(r *http.Request) string
- func (o Options) Headers(r *http.Request, wrappers []htmlbind.Wrapper, leaf htmlbind.Fragment) http.Header
- func (o Options) LiveHeaders(r *http.Request, wrappers []htmlbind.Wrapper, leaf htmlbind.Fragment) http.Header
- func (o Options) Mount(router Router)
- func (o Options) Negotiate(r *http.Request) Negotiated
- func (o Options) Redraw(r *http.Request, reg *Registry, options ...htmlbind.Option) (Response, bool)
- func (o Options) RedrawHeaders(r *http.Request) http.Header
- func (o Options) Render(w http.ResponseWriter, r *http.Request, wrappers []htmlbind.Wrapper, ...) error
- func (o Options) RenderLiveStream(ctx context.Context, w http.ResponseWriter, r *http.Request, ...) error
- func (o Options) RenderStream(w http.ResponseWriter, r *http.Request, wrappers []htmlbind.Wrapper, ...) error
- func (o Options) RenderStreamAsync(ctx context.Context, w http.ResponseWriter, r *http.Request, ...) error
- func (o Options) RuntimeAsset() Asset
- func (o Options) RuntimeConfig() RuntimeConfig
- func (o Options) RuntimeConfigFor(csrfToken string) RuntimeConfig
- func (o Options) RuntimeHandler() http.Handler
- func (o Options) RuntimePath() string
- func (o Options) ScriptTag() string
- func (o Options) ScriptTagFor(csrfToken string) string
- func (o Options) Sequence(r *http.Request) (Response, bool)
- func (o Options) StreamHeaders(r *http.Request, wrappers []htmlbind.Wrapper, leaf htmlbind.Fragment) http.Header
- func (o Options) Validate() error
- func (o Options) VerifyCSRF(r *http.Request, expected string) error
- func (o Options) WantsUpdate(r *http.Request) bool
- func (o Options) WriteLiveStream(w http.ResponseWriter, r *http.Request, head []string, ...)
- func (o Options) WriteNavigate(url string) (Response, error)
- func (o Options) WriteStream(w http.ResponseWriter, r *http.Request, head []string, ...)
- func (o Options) WriteUpdate(r *http.Request, updates []Update, options ...htmlbind.Option) (Response, error)
- func (o Options) WriteUpdateStatus(r *http.Request, status int, updates []Update, options ...htmlbind.Option) (Response, error)
- type QueryError
- type Registry
- type Reloadable
- type Response
- type Router
- type RuntimeConfig
- type StreamPlan
- type Update
Constants ¶
const ( // ModeDocument is the complete HTML document. It is what a request without // a usable render header gets, including one from an incompatible client. ModeDocument = updatecore.ModeDocument ModeNavigation = updatecore.ModeNavigation // ModeLive returns the deliveries of the same route's live boundaries, on a // response held open for as long as the subscriptions live. ModeLive = updatecore.ModeLive // ModeRedraw returns one registered component's subtree, addressed by the // kind and instance headers rather than by the URL path. ModeRedraw = updatecore.ModeRedraw // ModeSequence returns the static half of one fragment, addressed by a // digest of its own content. ModeSequence = updatecore.ModeSequence )
const ( // FailureMalformedRequest is a redraw that named no component. FailureMalformedRequest = updatecore.FailureMalformedRequest // FailureUnknownComponent is a kind this deployment does not publish. FailureUnknownComponent = updatecore.FailureUnknownComponent // FailureArgumentsTooLarge is a query past the configured bound. FailureArgumentsTooLarge = updatecore.FailureArgumentsTooLarge // FailureInvalidArguments is a query the generated decoder refused. FailureInvalidArguments = updatecore.FailureInvalidArguments // FailureRenderFailed is a component that could not render. FailureRenderFailed = updatecore.FailureRenderFailed )
const ( // DefaultHeaderPrefix names the request and response headers. DefaultHeaderPrefix = updatecore.DefaultHeaderPrefix // DefaultPathPrefix is the URL namespace of the endpoints this package owns. DefaultPathPrefix = updatecore.DefaultPathPrefix // DefaultDataAttributePrefix names the attributes the protocol puts in a // document. DefaultDataAttributePrefix = updatecore.DefaultDataAttributePrefix // DefaultGlobalName is what the browser runtime is installed under. DefaultGlobalName = updatecore.DefaultGlobalName // DefaultRuntimeFileName names the served runtime file. DefaultRuntimeFileName = updatecore.DefaultRuntimeFileName // DefaultCSRFFieldName is the hidden field generated forms carry. DefaultCSRFFieldName = updatecore.DefaultCSRFFieldName // DefaultCSRFHeaderName is where the runtime puts the token. DefaultCSRFHeaderName = updatecore.DefaultCSRFHeaderName // DefaultMaxManifestBytes bounds the validators a request may carry. DefaultMaxManifestBytes = updatecore.DefaultMaxManifestBytes // DefaultMaxQueryBytes bounds the arguments a redraw may carry. DefaultMaxQueryBytes = updatecore.DefaultMaxQueryBytes // DefaultStreamContentType marks a delta delivered as a record stream. DefaultStreamContentType = updatecore.DefaultStreamContentType )
The naming defaults. Each is shared with the generator, which writes what these read back.
Variables ¶
var BuildID = updatecore.BuildID
BuildID identifies the running binary, so anything that could change rendering invalidates client state.
var ErrCSRFMismatch = updatecore.ErrCSRFMismatch
ErrCSRFMismatch reports a token that is not the session's.
var ErrCSRFMissing = updatecore.ErrCSRFMissing
ErrCSRFMissing reports an unsafe request carrying no token at all.
Functions ¶
func ApplyTo ¶ added in v0.4.7
func ApplyTo(header http.Header, w http.ResponseWriter)
ApplyTo copies a computed header set onto a response, adding rather than replacing so a caller's own values survive.
func DecodeManifest ¶
DecodeManifest reads the compact validator list a client sends back.
func EncodeManifest ¶
EncodeManifest renders the validator list a client sends back. It exists so a test, and any non-browser client, can produce exactly what the runtime does.
func QueryFloat ¶
QueryFloat decodes one floating-point value.
func QueryOptional ¶
QueryOptional decodes a value that may be absent, leaving the target nil when it is.
func QueryString ¶
QueryString decodes one string-kinded value.
func Redirect ¶ added in v0.5.1
Redirect sends the browser somewhere else, for the branch WantsUpdate exists to create.
A form submission that cannot apply an update response has to be answered with a redirect, which is what keeps a page working without JavaScript, so the redirect is not an aside — it is the other half of every action handler this package serves.
It is here rather than left to net/http because http.Redirect has no transportable spelling: the other backend redirects through a method on its context, and the transform rewrites argument lists rather than turning a function call into a method call. Declaring the same name over both is what lets one handler body compile on either.
It is http.Redirect and nothing more. The relative-path resolution, the hyperlink body on a GET, and the status handling are the standard library's, because a second implementation of those would be a second set of bugs.
func RuntimeSource ¶ added in v0.3.1
func RuntimeSource() []byte
RuntimeSource is the browser runtime this package implements.
The bytes are the point. A framework that already ships a runtime cannot put two on one document — that would be two boundary id spaces, two build identities, and two script tags with nothing deciding which owns a region — so it merges ours into its own asset. Without readable bytes, merging means keeping a copy, and a copy is not a version-pinned dependency: it drifts on upgrade with nothing in the build failing, and a drifted browser runtime is a silently dead page rather than a compile error.
The bytes carry no naming choice. The runtime reads its attribute prefix, header namespace, endpoint prefix, and installed name from the configuration it is given, so one asset serves every deployment and merging it needs no build step. RuntimeConfig produces that configuration; the file installs createPartialUpdateRuntime for a caller constructing an instance directly.
func RuntimeVersion ¶
func RuntimeVersion() string
RuntimeVersion is the content identity of the browser runtime. It appears in the runtime path so a new build gets a new URL.
func SetStreamErrorHandler ¶ added in v0.5.1
func SetStreamErrorHandler(fn func(error))
SetStreamErrorHandler installs the destination for stream failures raised after the response committed.
A stream that has written its head record cannot answer with a status, so an error from a producer has nowhere to go but a log. It is installed once for the process and covers both transports.
Types ¶
type Asset ¶ added in v0.3.1
type Asset = updatecore.Asset
Asset is one static file this package requires a page to load.
type DeltaStream ¶
type DeltaStream = updatecore.DeltaStream
DeltaStream is an open record stream a producer writes boundary completions to as they settle.
It is the same type on both transports, deliberately. A wrapper renaming so much as one method would make the two handler bodies differ by more than their signature line, and the source transform rewrites signatures and argument lists — not method names.
type Failure ¶ added in v0.3.1
type Failure = updatecore.Failure
Failure is one request an update endpoint could not answer. It satisfies error and unwraps to the cause, so it goes straight to a logger or a span.
type FailureKind ¶ added in v0.3.1
type FailureKind = updatecore.FailureKind
FailureKind names why an update endpoint could not answer.
type ManifestEntry ¶ added in v0.4.6
type ManifestEntry = updatecore.ManifestEntry
ManifestEntry is what a client stores for one instance beside its markup: the validator of the region's own bytes, the digest of its nested boundary ids, and the boundary enclosing it.
type Negotiated ¶
type Negotiated = updatecore.Negotiated
Negotiated is what a request asked for, after validation.
type Options ¶
type Options struct {
// Key authenticates validators. Two renders that are to be compared must
// use the same key; rotating it forces complete documents, which is the
// intended effect of a rotation.
//
// An unkeyed digest of low entropy content lets anyone confirm a guess by
// comparing digests, so a deployment serving non-public pages must set it.
Key []byte
// HeaderPrefix overrides the header namespace. Empty uses
// DefaultHeaderPrefix. The runtime reads it from its configuration, so
// overriding it needs no rebuilt runtime.
HeaderPrefix string
// DataAttributePrefix overrides the attribute namespace. Empty uses
// DefaultDataAttributePrefix.
//
// It must match the generator's own DataAttributePrefix, because that is
// what wrote the instance attributes into the markup this runtime reads.
// It also names the preserve and ignore markers an application author
// writes by hand, which is why a framework needs to own it: those are the
// author's surface, not a wire detail.
DataAttributePrefix string
// GlobalName overrides the name the browser runtime is installed under.
// Empty uses DefaultGlobalName.
//
// A framework sets it so its users call the framework's own name rather
// than a dependency's.
GlobalName string
// RuntimeFileName overrides the base name of the served runtime asset.
// Empty uses DefaultRuntimeFileName. The content digest and the .js suffix
// are appended either way, so the URL stays immutably cacheable.
RuntimeFileName string
// CallerOwnsRuntime says the caller ships its own browser runtime, so this
// package serves and references none: Mount registers no asset route and
// ScriptTag returns nothing.
//
// Usually that runtime is this one, merged into a larger asset from
// RuntimeSource. Two runtimes on one document would mean two boundary id
// spaces and two build identities, so a framework that already has one takes
// this rather than adding a second.
CallerOwnsRuntime bool
// ServeRuntime asks this package to serve the reference browser runtime at a
// content-hashed URL and to write the script tag that loads it.
//
// It is off by default, which is the whole of the difference from earlier
// versions: the browser half belongs to the caller, so serving one is
// something a deployment asks for rather than something it inherits.
//
// Exactly one of this and CallerOwnsRuntime must be set, and Validate says so
// at startup. A build that set neither would compile and then serve pages
// that silently stop updating, which is the worst failure shape available
// here: nothing fails, the page is just quietly dead.
ServeRuntime bool
// CSRFFieldName is the hidden field generated forms carry. Empty uses
// DefaultCSRFFieldName.
//
// It must match the generator's own CSRFFieldName, because that is what
// wrote the field into the markup this reads back: one writes it and the
// other reads it, and nothing links the two at compile time.
CSRFFieldName string
// CSRFHeaderName overrides the header the browser runtime puts the CSRF
// token in. Empty uses DefaultCSRFHeaderName.
//
// It does not follow HeaderPrefix on purpose: the render, manifest, and
// build headers name this protocol, and this one names a convention every
// framework's middleware already looks for.
CSRFHeaderName string
// PathPrefix overrides the URL namespace of every framework endpoint.
// Empty uses DefaultPathPrefix. Unlike the header names, the runtime learns
// this one at load time, so overriding it needs no rebuilt runtime.
PathPrefix string
// BuildID overrides the identity of the running binary. Empty uses
// BuildID(), which is the version control revision the binary was stamped
// with, or a per-process value when the tree was dirty or unstamped.
//
// A page rendered by a different build has client state this binary cannot
// vouch for, so it is served a complete document instead of a delta.
BuildID string
// MaxManifestBytes caps the manifest header a request may carry. Zero uses
// DefaultMaxManifestBytes. An oversized manifest is ignored rather than
// rejected, so the response is a larger delta instead of an error.
MaxManifestBytes int
// MaxQueryBytes caps the arguments a redraw may carry. Zero uses
// DefaultMaxQueryBytes. Unlike an oversized manifest an oversized query is
// rejected, because the arguments are the request rather than a hint.
MaxQueryBytes int
// StreamContentType overrides the media type of a streamed delta. Empty
// uses DefaultStreamContentType.
//
// This one names the wire format rather than a limit, so overriding it is a
// framing choice a client has to agree with, not a tuning knob.
StreamContentType string
// OnFailure receives every request an endpoint of this package could not
// answer. It observes rather than answers.
//
// Every entry returns the response it computed, with its Failure field set,
// so a caller with its own error pages substitutes them by sending something
// else instead of what it was handed. This hook is for the log line and the
// span, which a caller wants on every refusal whether or not it changes the
// answer — and which are otherwise lost, since a status alone cannot say
// whether a page was stale or a render failed.
//
// It takes the request's context rather than the request. A log line and a
// span both want the trace, the deadline, and whatever the caller's own
// middleware put there, and none of them want the transport; taking the
// narrower value is also what lets this field mean the same thing on a
// backend whose request type is not *http.Request.
OnFailure func(ctx context.Context, failure Failure)
}
Options configure one set of update endpoints.
func (Options) CSRFToken ¶ added in v0.3.3
CSRFToken reads the token a request carries, header first and form body second.
The order is not arbitrary. The header is what the runtime sends and the only channel a non-form body has; the field is the fallback for a submission made without script. Reading the header first also means an ordinary fetch never pays for parsing a body it does not have.
Reading the field consumes the request body through ParseForm, as any handler reading a form does.
func (Options) Headers ¶ added in v0.4.7
func (o Options) Headers(r *http.Request, wrappers []htmlbind.Wrapper, leaf htmlbind.Fragment) http.Header
Headers is what a response to this request must carry, for the entries that write their body directly and therefore need their headers set first.
It is computable before anything renders: the Vary axes come from which request headers this package reads, the content type from the mode negotiated, and the live marker from the composition. Pass the wrappers and leaf a render entry will be given; pass none and the live marker is omitted.
func (Options) LiveHeaders ¶ added in v0.4.7
func (o Options) LiveHeaders(r *http.Request, wrappers []htmlbind.Wrapper, leaf htmlbind.Fragment) http.Header
LiveHeaders is StreamHeaders for the entry that does hold subscriptions open, so a live request keeps the live mode rather than being downgraded.
func (Options) Mount ¶
Mount registers every endpoint this package owns under the configured path prefix, which is now the runtime asset and nothing else.
It takes no registry, because a redraw is no longer an endpoint this package mounts: the caller answers one from its own handler with Options.Redraw, at whatever URL it chooses. That is the whole point of the change — an endpoint the caller routes, protects, and logs should have an address the caller picked.
The asset is registered only when this build serves it; see Options.ServeRuntime.
func (Options) Negotiate ¶
func (o Options) Negotiate(r *http.Request) Negotiated
Negotiate resolves how a request must be answered.
Anything unrecognized resolves to ModeDocument rather than to an error: a stale client, a truncated header, and a proxy that dropped a header must all still produce a working page.
func (Options) Redraw ¶ added in v0.3.5
func (o Options) Redraw(r *http.Request, reg *Registry, options ...htmlbind.Option) (Response, bool)
Redraw answers a request for one registered component, and reports whether it did.
It is the entry a caller branches on inside its own handler, so the address a client redraws at is the caller's to choose and this package mounts nothing. Usually that is the page the component sits on, where the redraw inherits the page's own authorization rather than needing a second path pattern kept in step with the one protecting the page.
options reach the component's render, which is what a caller uses when a component renders one way inside its page and another in the response that replaces it — and one containing an unsafe form does not render at all, since htmlbind.Builder.CSRFField needs a token. The boundary prefix and the build identity are supplied from these Options and do not need passing.
func (Options) RedrawHeaders ¶ added in v0.4.7
RedrawHeaders names the Vary axes a URL that answers redraws depends on, whichever way this request turns out.
A page handler declares them before it branches: a page and the redraws of the components on it share one URL, so a cache that learned only the page would answer a redraw from it, and one that learned only one component's redraw would answer another component with it. The axes have to be on the response whether or not this request was a redraw, which is why they are computable without deciding.
func (Options) Render ¶
func (o Options) Render(w http.ResponseWriter, r *http.Request, wrappers []htmlbind.Wrapper, leaf htmlbind.Fragment, options ...htmlbind.Option) error
Render answers one request with either a complete document or a delta.
It always sets Vary, because a cache that served a delta body to a document request would hand a browser a page of JSON. The caller keeps every other response concern, as elsewhere in this module.
It buffers, so it holds nothing open and reports every failure as an ordinary error. A live request reaching it is answered with the document, which is the same fallback every unrecognized condition takes and leaves the client with a working page rather than an error.
func (Options) RenderLiveStream ¶
func (o Options) RenderLiveStream(ctx context.Context, w http.ResponseWriter, r *http.Request, wrappers []htmlbind.Wrapper, leaf htmlbind.Fragment, options ...htmlbind.Option) error
RenderLiveStream answers a live request by holding the response open for as long as the composition's subscriptions live.
It is the only entry that keeps subscriptions open. Everything else that reaches a live request answers it as a navigation and terminates, so a client learns at once that this route delivers nothing.
func (Options) RenderStream ¶
func (o Options) RenderStream(w http.ResponseWriter, r *http.Request, wrappers []htmlbind.Wrapper, leaf htmlbind.Fragment, options ...htmlbind.Option) error
RenderStream answers a navigation with a record stream instead of one buffered body, so each region applies as soon as it is written.
Everything that could change the status is decided before the first record, because writing it commits the response. After that a failure can only be reported in band.
func (Options) RenderStreamAsync ¶
func (o Options) RenderStreamAsync(ctx context.Context, w http.ResponseWriter, r *http.Request, wrappers []htmlbind.Wrapper, leaf htmlbind.Fragment, options ...htmlbind.Option) error
RenderStreamAsync answers a navigation with a record stream that also carries await boundaries as they settle.
Each region reaches the browser with its fallback in place and is replaced when its dependency finishes, so a slow one delays only itself. A chain with no await boundary produces exactly what RenderStream does.
This entry serves the document and navigation modes. A live request reaching it is answered as a navigation and terminated final, so a client that opened a live connection to a route this caller does not serve live learns so at once instead of holding a connection that will never deliver.
func (Options) RuntimeAsset ¶ added in v0.3.1
RuntimeAsset is the browser runtime as a static asset, for a caller that serves its own files.
func (Options) RuntimeConfig ¶ added in v0.3.1
func (o Options) RuntimeConfig() RuntimeConfig
RuntimeConfig is the configuration matching these options, so the server and the browser cannot disagree about a name.
It carries no CSRF token: a token belongs to a session and these options belong to the process. Use RuntimeConfigFor to add one.
func (Options) RuntimeConfigFor ¶ added in v0.3.3
func (o Options) RuntimeConfigFor(csrfToken string) RuntimeConfig
RuntimeConfigFor is RuntimeConfig carrying this session's CSRF token, so the runtime sends it on every request it issues.
The header is the channel for anything the runtime fetches; the hidden field generated into each form is the channel for a submission with no script. They carry the same value, which is what a header carrying exactly one value requires of the token: one per session.
func (Options) RuntimeHandler ¶
RuntimeHandler serves the browser runtime.
A caller owning the runtime serves its own asset and never calls this; see Options.CallerOwnsRuntime and RuntimeSource.
func (Options) RuntimePath ¶
RuntimePath is the URL the browser runtime is served from. The version segment makes the response immutable, which is why the handler may set a long max-age.
func (Options) ScriptTag ¶
ScriptTag is the element loading the runtime, ready to place at the end of a document body.
The caller injects it, because this milestone has no document shell bootstrap. The tag carries the whole runtime configuration, so one shared asset works for any set of names without being rebuilt.
A caller owning the runtime gets an empty string: a tag pointing at an asset this build does not serve is worse than no tag at all.
func (Options) ScriptTagFor ¶ added in v0.3.3
ScriptTagFor is ScriptTag carrying this session's CSRF token, which is what a handler that renders forms uses.
func (Options) Sequence ¶ added in v0.4.4
Sequence answers a request for one sequence tree, and reports whether it did.
It is the entry a caller branches on inside its own handler, exactly as Redraw is, so the address a client asks at is the caller's to choose and this package mounts nothing:
func page(w http.ResponseWriter, r *http.Request) {
if answer, ok := options.Sequence(r); ok {
w.Header().Set("Cache-Control", "public, max-age=31536000, immutable")
_, _ = answer.WriteTo(w)
return
}
// ordinary page render
}
The cache policy is the caller's, and a sequence is the one answer here that may be public and held forever: it is addressed by a digest of its own content, so a template edit produces a new address rather than a new body at the old one, and nothing needs invalidating.
An address this process has never rendered is answered 404, and a client falls back to asking for the assembled form it can always be sent instead. That is the whole recovery path: a sequence is an optimisation over markup that is still available, never a thing a screen depends on.
func (Options) StreamHeaders ¶ added in v0.4.7
func (o Options) StreamHeaders(r *http.Request, wrappers []htmlbind.Wrapper, leaf htmlbind.Fragment) http.Header
StreamHeaders is Headers for a streamed navigation, whose body is a record stream rather than one of the buffered shapes.
A live request reaching an entry that does not hold subscriptions open is answered as a navigation and terminated, so the echo says navigation: what a response claims to be has to be what it is, or a proxy substitution stops being detectable.
func (Options) Validate ¶ added in v0.3.1
Validate reports a configuration this package cannot serve.
func (Options) VerifyCSRF ¶ added in v0.3.3
VerifyCSRF compares what a request carries against the session's token.
expected is the caller's to produce, from wherever its session lives. An empty expected is refused rather than treated as "nothing to check": a session lookup that quietly returned nothing would otherwise disable the whole control for exactly the requests that most need it.
This is a token check and nothing else. Origin and Fetch Metadata validation belong to middleware that sees the request before any of this — Go's own http.CrossOriginProtection is what wraps a handler with them — and they are worth having: the two defenses fail for unrelated reasons, which is the point of running both.
func (Options) WantsUpdate ¶
WantsUpdate reports whether this request can be answered with the regions an action changed, rather than with a redirect.
An ordinary form submission cannot, so a handler branches on this and redirects instead, which is what keeps a page working without JavaScript.
func (Options) WriteLiveStream ¶ added in v0.5.1
func (o Options) WriteLiveStream(w http.ResponseWriter, r *http.Request, head []string, fn func(*DeltaStream) error)
WriteLiveStream is WriteStream for a delivery stream: the same records on the same framing, in the live mode rather than the navigation one.
The difference a caller sees is the ending. A navigation stream closes final, having described the route; a live stream closes done when every source finished, or retry when the server closed a healthy response at a lifetime bound. A client keys its retry policy on that rather than on the fact that the stream ended.
func (Options) WriteNavigate ¶
WriteNavigate tells the browser to leave the page, which is how an action that changed where the user belongs stays correct without guessing which regions to rewrite.
func (Options) WriteStream ¶ added in v0.5.1
func (o Options) WriteStream(w http.ResponseWriter, r *http.Request, head []string, fn func(*DeltaStream) error)
WriteStream opens a record stream, runs fn against it, and closes it.
The stream is closed whether or not fn returns an error, so a producer cannot leave a client holding a truncated response. fn's error is reported in band through the terminator, because the response committed when the head record went out and the status can no longer change.
head is the merged head of the composition, written as the first record so a stylesheet lands before the markup that needs it. The response headers are the caller's: take them from Options.StreamHeaders before calling.
func (Options) WriteUpdate ¶
func (o Options) WriteUpdate(r *http.Request, updates []Update, options ...htmlbind.Option) (Response, error)
WriteUpdate answers a mutating request with the regions it changed, so one round trip both performs the action and refreshes the page.
The body is the same shape a redraw returns, so the browser applies it with the same code. Unlike a redraw this request is not idempotent: it carries ambient credentials, so it needs CSRF protection, and its response is never cacheable.
options reach every region's render. The token one matters most here: a region holding an unsafe form emits a CSRF field, and without a token that render fails outright — which is this entry's own headline case, since rewriting a form with its validation errors is what it exists for.
func (Options) WriteUpdateStatus ¶
func (o Options) WriteUpdateStatus(r *http.Request, status int, updates []Update, options ...htmlbind.Option) (Response, error)
WriteUpdateStatus is WriteUpdate with an explicit status, so a failed validation can return 422 and still rewrite the form region with its errors.
The browser applies an update response whatever the status says, because rendering the failure is the point.
type QueryError ¶ added in v0.4.4
type QueryError = updatecore.QueryError
QueryError is a redraw argument the generated decoder refused.
type Registry ¶
type Registry = updatecore.Registry
Registry holds the components a deployment publishes for redraw.
type Reloadable ¶
type Reloadable = updatecore.Reloadable
Reloadable is one component published as a redraw endpoint.
type Response ¶ added in v0.4.7
type Response struct {
// Status is what this answer means. A caller may serve another, and an
// update response is applied by a client whatever the status says, which is
// how a rejected form returns 422 and still rewrites its own region.
Status int
// Header is what the response has to carry: the Vary axes it depends on, its
// content type, the served mode echoed back, and an entity tag where the
// answer has one. It carries no cache policy, which is the caller's.
Header http.Header
// Body is the bytes to write.
Body []byte
// Failure is why this answer is a refusal, or nil for an ordinary one.
//
// It travels here rather than through a hook that writes: a caller that only
// wants to observe logs it and sends the response as it stands, and one that
// wants its own error page reads the kind and writes that instead. Either way
// nothing is written until the caller writes it, which is the whole of this
// package's position on responses.
Failure *Failure
}
Response is a complete answer this package computed and did not send.
It exists for the entries whose headers depend on the body — a redraw digests what it rendered — because such an entry cannot both write the body and leave the headers alone. The buffered entries return one; the streaming entries take their headers from Headers instead, since a stream commits before its first record.
func FailureResponse ¶ added in v0.4.7
FailureResponse is the refusal this package computes, exported so a caller raising one of its own — a redraw it declined before this package saw it — answers in the same shape rather than reimplementing five status codes.
The body is RFC 9457 problem details, which is this module's documented error format everywhere else; the update endpoints were the only paths writing plain text. The media type is what tells the two apart on the wire: application/json is an update to apply, including a non-2xx one, and application/problem+json is a request that produced no update at all.
The status still directs. A client's rule — any non-2xx falls back to an ordinary navigation — is unchanged, so a client that cannot read the body still lands correctly; the body adds diagnosis rather than direction.
Nothing is sent until a caller sends it: Response.WriteTo does that, and a caller with its own error page sends that instead.
It carries no Vary, having no Options and no request to compute one from. The refusals this package produces get theirs added; a caller raising one of its own adds them from Options.RedrawHeaders or Options.Headers, and a cacheable status makes that matter.
func (Response) NotModified ¶ added in v0.4.7
NotModified reports whether the request already holds this answer, by comparing its If-None-Match against the entity tag this response carries.
Answering it is the caller's: a 304 is a cache policy decision, and this package no longer makes one. A response with no entity tag is never a match.
func (Response) WriteTo ¶ added in v0.4.7
func (resp Response) WriteTo(w http.ResponseWriter) (int64, error)
WriteTo sends the response: its headers, its status, and its body.
It is here so the common case is one call and an omission is a decision. A caller wanting its own policy sets it on w before calling, or copies Header itself and writes Body.
type Router ¶ added in v0.3.1
Router is what Mount installs on.
It names the one method Mount uses, so a framework with its own router passes it directly instead of losing the call. *http.ServeMux satisfies it, which is what keeps every existing call site compiling: naming a concrete type here made the convenience uncallable from exactly the callers who most wanted the whole surface installed by one rule.
type RuntimeConfig ¶ added in v0.3.1
type RuntimeConfig = updatecore.RuntimeConfig
RuntimeConfig is what the browser runtime reads to learn its own names.
type StreamPlan ¶ added in v0.5.1
type StreamPlan = updatecore.StreamPlan
StreamPlan is what a stream entry decided before it committed. A caller sees one only when it drives the delivery itself.