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 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 IsJSONMediaType(media string) bool
- func IsMessageTooLarge(err error) bool
- func MaxMultipartBodyBytes() int64
- func MediaType(ct string) string
- func MultipartParseError(err error, tooLarge bool) error
- func NotFound(problem Problem, cause ...error) error
- func PayloadTooLarge(problem Problem, cause ...error) error
- func ProblemResponse(err error) (status int, body []byte, ok 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 ServeSocket[In, Out any](conn MessageConn, opts SocketOptions, fn func(*Socket[In, Out]) error) error
- 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 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 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 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 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 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 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 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 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 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
// 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 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.