Documentation
¶
Overview ¶
Package bindcore holds the declarations both transport runtimes need and neither should own: the error model, the uploaded-file value, and the check helpers. Keeping them here is what lets an error cross between the net/http and fasthttp surfaces and still match, and what keeps a model struct naming File from pulling a transport in behind it.
It imports no transport package.
Index ¶
- Constants
- Variables
- func AcceptsCBOR(accept string) bool
- func AppendFileJSON(dst []byte, f File) []byte
- func BadRequest(problem Problem, cause ...error) error
- func BindError(field, location, message string) error
- func CheckDate(s string) bool
- func CheckDateTime(s string) bool
- func CheckEmail(s string) bool
- func CheckOriginDefault(origin, host string) bool
- func CheckTime(s string) bool
- func CheckUUID(s string) bool
- func Conflict(problem Problem, cause ...error) error
- func Forbidden(problem Problem, cause ...error) error
- func Internal(err error) error
- func IsCBORMediaType(media string) bool
- func IsJSONMediaType(media string) bool
- func IsMessageTooLarge(err error) bool
- func IsNormalClose(err error) bool
- func JSONBodyError(err error) error
- func JSONBodyNotObject() error
- func MatchQueryPair(rawKey, rawValue, key string) (value string, ok, matched bool)
- func MaxCBORBodyBytes() int64
- func MaxMultipartBodyBytes() int64
- func MediaType(ct string) string
- func MultipartParseError(err error, tooLarge bool) error
- func NotFound(problem Problem, cause ...error) error
- func ParseBytes(s string) ([]byte, error)
- func PayloadTooLarge(problem Problem, cause ...error) error
- func ProblemResponse(err error) (status int, body []byte, ok bool)
- func QueryNeedsUnescape(s string) bool
- func Redirect(target string, status ...int) error
- func RedirectTarget(err error) (string, int, bool)
- func RegisterNormalClose(fn func(error) bool)
- func ReportStreamError(err error)
- func RestFormAny(formBody map[string]string, exclude []string) map[string]any
- func RestFormRaw(formBody map[string]string, exclude []string) map[string]json.RawMessage
- func ScanQuery(raw, key string) (string, bool)
- func ServeSocket[In, Out any](conn MessageConn, opts SocketOptions, fn func(*Socket[In, Out]) error) error
- func SetMaxCBORBodyBytes(n int64)
- func SetMaxMultipartBodyBytes(n int64)
- func SetSocketDefaults(opts SocketOptions)
- func SetStreamErrorHandler(fn func(error))
- func SocketHandshakeError(status int, reason error) error
- func StatusText(status int) string
- func Unauthorized(problem Problem, cause ...error) error
- func ValidateSocketOptions(opts SocketOptions) error
- func Validation(fields ...FieldError) error
- type FieldError
- type File
- type HTTPError
- type MessageConn
- type Problem
- type QueryValues
- type Socket
- type SocketOptions
- type Stream
- type StreamFormat
- type StreamHeader
Constants ¶
const ( DefaultSocketReadLimit int64 = 1 << 20 DefaultSocketIdleTimeout = 60 * time.Second DefaultSocketPingInterval = 54 * time.Second DefaultSocketWriteTimeout = 10 * time.Second DefaultSocketBufferSize = 4096 )
Socket option defaults. Every one is non-zero: an unset limit is an allocation the peer chooses, and an unset read deadline is a read nothing can interrupt under TinyGo.
const CBORContentType = "application/cbor"
CBORContentType is the media type CBOR bodies are read and written under.
const DefaultMaxCBORBodyBytes int64 = 1 << 20
DefaultMaxCBORBodyBytes is the default cap for CBOR body reads (1 MiB), matching the JSON default so switching a client's encoding does not change how much it may send.
const DefaultMaxMultipartBodyBytes int64 = 1 << 20
DefaultMaxMultipartBodyBytes is the default cap on multipart request bodies (1 MiB). Override with SetMaxMultipartBodyBytes.
const DefaultMultipartMaxMemory int64 = 32 << 20
DefaultMultipartMaxMemory is how much of a multipart form stays in RAM before file parts spill to temp files. This is not a body size cap; see DefaultMaxMultipartBodyBytes.
const ProblemContentType = "application/problem+json"
ProblemContentType is the media type of an RFC 9457 error document.
Variables ¶
var ( ErrNilSocket = errors.New("httpbind: nil socket") ErrSocketClosed = errors.New("httpbind: socket closed") // ErrBinaryMessage reports a binary frame on a socket carrying JSON. ErrBinaryMessage = errors.New("httpbind: binary message on a JSON socket") )
var ( ErrNilStream = errors.New("httpbind: nil stream") ErrStreamClosed = errors.New("httpbind: stream closed") )
var ErrFileTooLarge = errors.New("httpbind: multipart file too large")
ErrFileTooLarge is returned when a single file part exceeds the limit.
var ErrNoHijacker = errors.New("httpbind: ResponseWriter is not an http.Hijacker; serve through tinygodriver/httpserver")
ErrNoHijacker reports a ResponseWriter that cannot hand over the connection, which under TinyGo means the server is not the one an upgrade needs.
Functions ¶
func AcceptsCBOR ¶ added in v0.5.19
AcceptsCBOR reports whether an Accept header value asks for a CBOR response.
Only an explicit application/cbor entry counts. Wildcards do not: a browser sends */* on every navigation, and answering it with CBOR would switch the default response format on clients that never asked. A q=0 entry is a refusal and does not count either. Relative preference between CBOR and JSON beyond that is not weighed — an explicit ask wins.
func AppendFileJSON ¶
AppendFileJSON appends an uploaded file the way encoding/json rendered it before generated encoders stopped going through reflection: exported fields in declaration order, with the content base64-encoded.
func BadRequest ¶
BadRequest returns a 400 Bad Request error.
func CheckDateTime ¶
CheckDateTime reports whether s is RFC3339 (fractional seconds accepted).
func CheckEmail ¶
CheckEmail reports whether s is a pragmatic (non-RFC5322) email. Empty string returns false; callers skip empty optional fields before calling.
func CheckOriginDefault ¶ added in v0.5.4
CheckOriginDefault refuses a handshake whose Origin names a host other than the one the request was addressed to, and admits one carrying no Origin.
A socket accepting any origin is cross-site request forgery with a persistent connection, so this is the default rather than the opt-in.
func CheckUUID ¶
CheckUUID reports whether s is a UUID string (8-4-4-4-12 hex with dashes). Version/variant bits are not enforced.
func IsCBORMediaType ¶ added in v0.5.19
IsCBORMediaType reports whether media is CBOR or a +cbor structured syntax suffix type (RFC 6839), e.g. application/cbor, application/senml+cbor.
func IsJSONMediaType ¶
IsJSONMediaType reports whether media is JSON or a +json structured syntax suffix type (RFC 6839), e.g. application/json, application/problem+json, application/vnd.api+json. text/json is also accepted.
func IsMessageTooLarge ¶
IsMessageTooLarge reports the transport-neutral size-limit failures, without errors.As so TinyGo does not need reflect.AssignableTo. A caller adds its own transport's error type before consulting this.
func IsNormalClose ¶ added in v0.5.27
IsNormalClose reports whether err is the ordinary end of a socket rather than a failure — the peer's normal or going-away close, an already-sent close, or EOF. A handler's read loop tests it to end without routing the close to the error handler, and it answers alike on both transports where a driver's own IsCloseError does not.
func JSONBodyError ¶ added in v0.5.21
JSONBodyError wraps a structural failure from a binder's inline JSON body walk in the same 400 problems the pre-inline Object split produced, so moving the parse into generated code changed no response body.
func JSONBodyNotObject ¶ added in v0.5.21
func JSONBodyNotObject() error
JSONBodyNotObject is the 400 a binder answers when the body decodes to something other than an object — required whenever payload:"*" rest maps are in play, and long the rule for every bound body.
func MatchQueryPair ¶ added in v0.5.27
MatchQueryPair decides one raw pair against a wanted key. matched reports that the pair answered the lookup; a pair whose escapes do not decode is treated the way url.ParseQuery treats it — as if it were not there.
func MaxCBORBodyBytes ¶ added in v0.5.19
func MaxCBORBodyBytes() int64
MaxCBORBodyBytes returns the effective global CBOR body limit.
func MaxMultipartBodyBytes ¶
func MaxMultipartBodyBytes() int64
MaxMultipartBodyBytes returns the effective global multipart body limit.
func MediaType ¶
MediaType returns the lowercase type/subtype of a Content-Type header value (parameters after ';' are stripped).
func MultipartParseError ¶
MultipartParseError maps a parse failure to 413 or 400. The caller decides tooLarge, because detecting it needs a transport-specific error type on top of the shared cases IsMessageTooLarge covers.
func ParseBytes ¶ added in v0.5.24
ParseBytes decodes a base64 value carried by a query, path, header or cookie into a byte field.
Both base64 alphabets are accepted, padded or not, because a value source is not a document. Two things force that: a query value carrying + arrives as a space unless the client percent-encoded it, and a client putting a blob in a URL will have reached for the URL-safe alphabet anyway. Refusing either would mean the same blob binds from a body and 400s from a query, which is a difference the field never asked for.
The writing side has no such choice to make. A byte field is written as padded standard base64 wherever it is written, so being permissive here widens what is accepted without widening what is produced.
An empty value is an empty blob rather than an error, matching the empty string a byte field encodes to.
func PayloadTooLarge ¶
PayloadTooLarge returns a 413 Payload Too Large error.
func ProblemResponse ¶
ProblemResponse derives the status and body of the RFC 9457 document for err. ok is false when err is nil and nothing should be written.
Both transport runtimes call this rather than deriving the document themselves, which is what makes their error bytes identical by construction instead of by two implementations agreeing.
func QueryNeedsUnescape ¶ added in v0.5.27
QueryNeedsUnescape reports whether a raw span decodes to something other than itself. The common query — unescaped ASCII keys and values — skips the decode and its allocation entirely.
func Redirect ¶ added in v0.5.11
Redirect returns a value that sends the browser to target. It travels the error return because a caller that returns values rather than holding a ResponseWriter has no other channel, and because a redirect and an error both end the normal response before it starts.
The value is an ordinary error: nothing panics and no control-flow exception is thrown, which is the difference from how a server-function ecosystem built on exceptions expresses this.
status defaults to 303, which is what a page wants after a POST. Pass one of 301, 302, 307, or 308 to choose another; anything else is refused here rather than emitted as a status no client will follow.
func RedirectTarget ¶ added in v0.5.11
RedirectTarget reports the location a redirect value carries.
func RegisterNormalClose ¶ added in v0.5.27
RegisterNormalClose adds a transport's normal-close rule. Each transport package calls it once from init, so IsNormalClose answers the same on both surfaces for the callback body they share.
func ReportStreamError ¶
func ReportStreamError(err error)
ReportStreamError hands err to the installed handler, if any.
func RestFormAny ¶
RestFormAny builds map[string]any from leftover form keys not in exclude.
func RestFormRaw ¶
RestFormRaw builds map[string]json.RawMessage from leftover form keys.
func ScanQuery ¶ added in v0.5.27
ScanQuery resolves one key straight off the raw query, for the callers that want a single value and would waste a full split on it.
func ServeSocket ¶ added in v0.5.4
func ServeSocket[In, Out any](conn MessageConn, opts SocketOptions, fn func(*Socket[In, Out]) error) error
ServeSocket applies the lifecycle to conn, runs fn, and closes the socket whatever fn returns.
Both surfaces call this, so the limits, the pong accounting, the ping cadence and the close handshake are one implementation rather than two. Closing the connection stays with the caller: fasthttp closes it when its upgrade callback returns, and net/http has to be told.
func SetMaxCBORBodyBytes ¶ added in v0.5.19
func SetMaxCBORBodyBytes(n int64)
SetMaxCBORBodyBytes sets the global CBOR body size limit.
n > 0 → use n bytes n <= 0 → restore DefaultMaxCBORBodyBytes (1 MiB)
func SetMaxMultipartBodyBytes ¶
func SetMaxMultipartBodyBytes(n int64)
SetMaxMultipartBodyBytes sets the global multipart body size limit.
n > 0 → use n bytes n <= 0 → restore DefaultMaxMultipartBodyBytes (1 MiB)
func SetSocketDefaults ¶ added in v0.5.4
func SetSocketDefaults(opts SocketOptions)
SetSocketDefaults installs the process-wide socket options. It is shared with the fasthttp runtime, so installing them once covers both.
func SetStreamErrorHandler ¶
func SetStreamErrorHandler(fn func(error))
SetStreamErrorHandler installs the destination for post-commit stream failures. Passing nil discards them, which is the default: a runtime logging on its own would write to a destination the caller did not choose.
func SocketHandshakeError ¶ added in v0.5.4
SocketHandshakeError shapes an upgrader's refusal as a Problem-carrying error, so a refused socket looks like every other refusal in the application rather than like the driver's own plain text. Both surfaces install it on their upgrader's Error hook, which is the only place a refusal can be reshaped: the driver writes the response itself, before any 101.
The 500 case keeps its named code for the returned error and the log only; ProblemResponse hides a 5xx code from the body, which is what should happen to a server misconfiguration.
func StatusText ¶
StatusText mirrors net/http.StatusText for the codes this package can produce and the ordinary ones beside them, and returns "" for anything else exactly as the standard library does. It exists so the error model links no transport: every constructor below sets Title, so this is reached only by a hand-built HTTPError.
func Unauthorized ¶
Unauthorized returns a 401 Unauthorized error.
func ValidateSocketOptions ¶ added in v0.5.4
func ValidateSocketOptions(opts SocketOptions) error
ValidateSocketOptions rejects a combination that would serve a socket which dies on schedule.
func Validation ¶
func Validation(fields ...FieldError) error
Validation returns a 400 Bad Request validation error with field details.
Types ¶
type FieldError ¶
FieldError describes a single field-level validation failure.
func Field ¶
func Field(field, location, message string) FieldError
Field builds a field-level validation error.
type File ¶
File is an uploaded file bound from a multipart/form-data part. After a successful bind, Filename, ContentType (when the client sent one), Size, and Content are populated from the named file part.
func FileFromHeader ¶
func FileFromHeader(fh *multipart.FileHeader, limit int64) (File, error)
FileFromHeader reads one multipart part into a File, bounded by limit. Both transports reach this: fasthttp's MultipartForm also yields *multipart.FileHeader, so the part-reading rule is written once.
type HTTPError ¶
type HTTPError struct {
Status int
Title string
Problem Problem
Fields []FieldError
// Location carries a redirect target. It is empty for every ordinary error,
// and set only by Redirect, whose value travels the error return because a
// redirect and an error both end the normal response before it starts.
Location string
// contains filtered or unexported fields
}
HTTPError is an HTTP-mapped error with optional RFC 9457 details and cause.
func AsHTTPError ¶
AsHTTPError extracts *HTTPError from err if present. Implemented without errors.As so TinyGo does not require reflect.AssignableTo (unimplemented for interfaces in TinyGo 0.40), which otherwise panics when Bind's json.RawMessage path is also linked into the same binary.
type MessageConn ¶ added in v0.5.4
type MessageConn interface {
ReadMessage() (messageType int, p []byte, err error)
NextWriter(messageType int) (io.WriteCloser, error)
WriteControl(messageType int, data []byte, deadline time.Time) error
SetReadLimit(limit int64)
SetReadDeadline(t time.Time) error
SetWriteDeadline(t time.Time) error
SetPongHandler(h func(appData string) error)
Subprotocol() string
Close() error
}
MessageConn is the part of a driver WebSocket connection a typed socket uses. Both driver Conn types satisfy it structurally, with no adapter.
type QueryValues ¶ added in v0.5.27
type QueryValues struct {
// contains filtered or unexported fields
}
QueryValues is a request's query string split once into raw key=value spans, in wire order.
Generated binders only ever ask for a key's first value, so the url.Values map — one allocation per key plus an unescaped copy of every member — buys random access nobody uses. Splitting into spans costs one slice, and a span is unescaped only when it is actually looked up and actually escaped: the same trade the binders' inline JSON walk makes for body members.
func ParseQuery ¶ added in v0.5.27
func ParseQuery(raw string) QueryValues
ParseQuery splits raw into the pairs a lookup may answer from.
raw must be a string the caller owns. The spans alias it and every value handed back is cut from it, which is what lets the fasthttp surface convert the pooled query bytes once here rather than per field.
func (QueryValues) Lookup ¶ added in v0.5.27
func (q QueryValues) Lookup(key string) (string, bool)
Lookup returns the first value for key.
func (QueryValues) LookupAll ¶ added in v0.5.27
func (q QueryValues) LookupAll(key string) []string
LookupAll returns every value for key, in the order the URL wrote them.
A repeated key is the array spelling a browser produces: an urlencoded form writes one pair per successful control, so a checkbox group named tag submits tag=a&tag=b. Nothing else is an array here — brackets are ordinary key characters, and a comma is an ordinary value character.
An empty value contributes nothing. A blank control submits its key with no value, so counting one would turn an untouched filter field into an element no user chose, and tag= and a bare tag are indistinguishable anyway.
type Socket ¶ added in v0.5.4
type Socket[In, Out any] struct { // contains filtered or unexported fields }
Socket is a typed WebSocket connection carrying one JSON value per message.
Read must be called from one goroutine. Write may be called from any: it takes a lock the control frames share, so a broadcast goroutine cannot interleave its frame with a message or with a lifecycle ping.
func NewSocket ¶ added in v0.5.4
func NewSocket[In, Out any](conn MessageConn, opts SocketOptions) *Socket[In, Out]
NewSocket builds a typed socket over conn. ServeSocket is the entry that applies the lifecycle; this exists for tests and for a caller assembling one by hand.
func (*Socket[In, Out]) Close ¶ added in v0.5.4
Close ends the socket with a normal-closure handshake. Idempotent.
It does not close the underlying connection: the entry that opened it does that, so the close frame is always sent before the transport goes away.
A goroutine calling this to end someone else's read loop does not interrupt a read already blocked — nothing can, under TinyGo. The reader returns when the peer answers the close frame, or when its own deadline expires.
func (*Socket[In, Out]) Read ¶ added in v0.5.4
Read returns the next message decoded into In.
The read deadline is set here rather than once at setup: netdev takes a deadline by value when a read begins, so the bound has to be in place before the call rather than pushed into it afterwards.
A decode failure is returned without closing the socket, because a message this application cannot read is the application's to answer.
func (*Socket[In, Out]) Subprotocol ¶ added in v0.5.4
Subprotocol returns the subprotocol the handshake negotiated, or "".
type SocketOptions ¶ added in v0.5.4
type SocketOptions struct {
ReadLimit int64
IdleTimeout time.Duration
PingInterval time.Duration
WriteTimeout time.Duration
ReadBufferSize int
WriteBufferSize int
// Subprotocols are offered in preference order; the negotiated one is
// readable from the socket.
Subprotocols []string
// EnableCompression asks for permessage-deflate. It is off by default:
// it pulls flate into the binary, and only no-context-takeover mode is
// supported, so the saving is smaller than the cost.
EnableCompression bool
// CheckOrigin decides whether a handshake is allowed, from the Origin and
// Host header values. It takes two strings rather than a request so one
// policy serves both transports, the same reason stream negotiation does.
// A nil value takes CheckOriginDefault.
CheckOrigin func(origin, host string) bool
}
SocketOptions configures one socket. A zero field takes the process default installed with SetSocketDefaults, and a process default left zero takes the constant above, so nothing reaches the driver as zero.
func ResolveSocketOptions ¶ added in v0.5.4
func ResolveSocketOptions(opts SocketOptions) SocketOptions
ResolveSocketOptions fills opts from the process defaults, then from the constants. Both surfaces call it so one options value means the same thing on either.
func SocketDefaults ¶ added in v0.5.4
func SocketDefaults() SocketOptions
SocketDefaults returns the installed process defaults, with every unset field resolved to its constant.
type Stream ¶
type Stream[T any] struct { // contains filtered or unexported fields }
Stream is a typed incremental response stream over any writer.
func NewStream ¶
func NewStream[T any](w io.Writer, format StreamFormat) *Stream[T]
NewStream builds a stream over w. The caller has already sent the headers StreamHeaders lists and the 200 status.
func (*Stream[T]) Close ¶
Close marks the stream finished. Idempotent. For JSON array format, Close writes the trailing ']' (or "[]" if no Write). SSE and NDJSON do not require a special trailer.
func (*Stream[T]) Format ¶
func (s *Stream[T]) Format() StreamFormat
Format returns the negotiated stream format (sse | ndjson | json-array).
type StreamFormat ¶
type StreamFormat string
StreamFormat is the negotiated on-the-wire format for Stream[T].
const ( // StreamSSE is text/event-stream (data: <json>\n\n). StreamSSE StreamFormat = "sse" // StreamNDJSON is application/x-ndjson (one JSON object per line). // Same family as JSONL / NDJSON; not a single JSON array document. StreamNDJSON StreamFormat = "ndjson" // StreamJSONArray is application/json as one JSON array document: // [obj1,obj2,...] with items appended incrementally and closed by Close. StreamJSONArray StreamFormat = "json-array" )
func NegotiateStream ¶
func NegotiateStream(streamQuery, accept, userAgent string) StreamFormat
NegotiateStream selects SSE, NDJSON, or JSON array using:
- the ?stream= query value
- Accept
- User-Agent heuristics
- default NDJSON
It takes the three values as strings so each transport reads them its own way and the decision itself stays in one place.
Note: NDJSON/JSONL (line-delimited objects) is distinct from JSON array (a single [...] document). application/json selects the array form; application/x-ndjson / application/jsonl select NDJSON.
type StreamHeader ¶
type StreamHeader struct{ Name, Value string }
StreamHeader is one response header a format requires.
func StreamHeaders ¶
func StreamHeaders(format StreamFormat) []StreamHeader
StreamHeaders lists the headers a format's response opens with, in order, so both surfaces send the same set. The returned slice is shared and read-only.