rest

package
v1.29.1 Latest Latest
Warning

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

Go to latest
Published: Nov 9, 2026 License: MIT Imports: 21 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Configure added in v1.29.0

func Configure(e *echo.Echo, log zerolog.Logger, opts ...Option)

Configure sets e's logger and registers compose's middleware on e, in this order:

  • the request id: every request gets a new UUID, whatever X-Request-Id the client sent, as X-Request-Id on the response and on the request and as the ID of the request requests.New builds, so the response header, the request log line, a handler's request and the errors answered for it all carry the same id (see assignRequestID);
  • the request logger, one line per request with the request's id (see requestLogger);
  • Echo's Recover, which turns a panic into a 500 error the request logger logs with its stack;
  • CORS, exposing Session-Token and X-Request-Id to cross-origin clients;
  • the body limit, when WithBodyLimit is passed;
  • RejectNUL, unconditionally, after CORS and the body limit; see RejectNUL for what it inspects and its memory use.

It uses WithAllowedOrigins, WithCredentialedCORS and WithBodyLimit and ignores the other options, so an app passes it the same options as Serve. Without WithAllowedOrigins every origin is allowed, and without WithBodyLimit request bodies are unbounded, so production callers should pass both.

The request log and Echo's own lines (the listening address, net/http server errors, an app's c.Logger() calls) are written to log at info level and above whatever level log has, as lecho did, so a logger set to warn still logs requests.

Configure needs no listener, so the configured stack can be served through httptest in tests, or by an app that runs its own server. Middleware registered with e.Use after Configure runs inside the stack, after RejectNUL and so inside Recover (a Sentry middleware that re-panics, for one); middleware registered before Configure runs outside it. An app calls either Start, or Configure then Serve, never both Configure and Start, which would register everything twice. ErrorHandler answers, in compose's error shape, the errors this stack logs; Configure does not install it.

func ErrorHandler added in v1.29.0

func ErrorHandler() echo.HTTPErrorHandler

ErrorHandler is an Echo HTTPErrorHandler that answers the errors that never reach handler.ManageError (unknown routes, 405, recovered panics, errors a handler or middleware returns straight to Echo) in compose's handler.ErrorResponse shape. An app installs it with e.HTTPErrorHandler = rest.ErrorHandler(); Configure and Start do not.

The status is the non-zero StatusCode of an echo.HTTPStatusCoder in the error chain, else 500, as Echo's default handler and the request log have it. The code is ez.HTTPStatusToError of that status. The message is the non-empty Message of an *echo.HTTPError in the chain, else http.StatusText of the status; it is never the error's own text or its wrapped cause, so no internal error or panic stack reaches the client. An *echo.BindingError embeds the HTTPError without being one, so a typed binder's error returned straight to Echo gets the status text, the way handler.BindedJSONResponse keeps binding detail out of its answer too. The request_id is the X-Request-Id of the response: under Configure the request's id, the one the request log line carries, and otherwise whatever the app's own middleware set there, which Echo's middleware.RequestID copies from the client. The request's own X-Request-Id is never read. A HEAD request gets the status with no body. A response already committed is left alone.

It logs nothing about the error, because the request logger that Configure (and so Start) installs logs every error raised inside its stack at error level under the same id, including the stack of a recovered panic, which Echo's *middleware.PanicStackError carries in its Error text. An error from middleware outside that stack, registered with e.Pre or with e.Use before Configure, which under Start is all of an app's middleware, is answered with an empty request_id and logged by nothing; Configure then Serve, with the app's e.Use calls in between, puts that middleware inside. In an app that runs neither Configure nor Start nothing logs a 500, so adopt the two together. Only a failure to write the answer is logged, through c.Logger(), as Echo's default handler does.

func RejectNUL added in v1.26.1

func RejectNUL() echo.MiddlewareFunc

RejectNUL refuses, with 400 ez.EINVALID and before any handler runs, a request that carries a NUL byte (0x00) where a handler would read it. Postgres cannot store a NUL byte in text, varchar or jsonb, and bun v1.3.0 refuses to render a string holding one into SQL, so such a string would fail its query with a 500 (ez.EINTERNAL); no legitimate client sends one.

It checks four places, in order:

  • the path parameters, as c.Param returns them;
  • the keys and values of the query string;
  • a JSON body (media type application/json, or one ending in +json): a raw NUL byte, or the escape \u0000 when its backslash is not itself escaped. The six-character text \u0000, written "\\u0000" in JSON, passes;
  • a form body (application/x-www-form-urlencoded or multipart/form-data): the keys and values of its fields and the names of its files. File contents are not inspected, since files such as images hold NUL bytes.

Media types match in any letter case, as net/http's form parsing does. The body is chosen by Content-Type, not by how the handler reads it, so a JSON body a handler reads raw, such as a webhook whose signature it checks over the bytes, is scanned and refused before the handler runs. An app that must accept such a payload on a route exempts the route by wrapping the middleware with a check on c.Path(), or by registering it on route groups instead of with e.Use. XML bodies are not checked, since encoding/xml already rejects a NUL byte, and nor are other content types. Headers need no check: net/http refuses a header holding a NUL byte before Echo runs.

It reads a JSON, urlencoded or multipart body of up to 32 MB into memory (two to three times that while reading and parsing) and puts it back, so handlers can still bind, read or stream it; a larger body passes through uninspected and intact, so it holds at most 32 MB per request whatever the body limit. Registered with e.Use it runs on every route, matched or not. Register it after CORS, so a preflight is answered first and its 400 carries the CORS headers; after any request-decompression middleware, since compressed bytes hold NUL bytes; and after a body limit such as middleware.BodyLimit, so a body over the limit gets the limit's 413 without being read first. A chunked body over the limit is read up to the limit, and the limit's error then goes to Echo's HTTPErrorHandler as Echo's own error, which Echo's default handler answers with its status.

A refusal is logged at warn level as "Request Error" with the method, the route path and the request id, never the URI, query or body. The middleware writes the 400 itself, in the handler.ErrorResponse shape, so neither an app ErrorTranslator nor Echo's HTTPErrorHandler sees it. Its request_id, and the id of the log line, is the X-Request-Id of the response, as ErrorHandler has it: under Configure the request's id, otherwise what the app's own middleware set there; the request's X-Request-Id is never read. It is empty, and the log line has no id, when nothing set that header, as when RejectNUL is used without Configure and without a request ID middleware.

func Serve added in v1.29.0

func Serve(ctx context.Context, e *echo.Echo, log zerolog.Logger, port string, opts ...Option)

Serve serves e on port with server timeouts. It registers no middleware; Configure does that. It uses the timeout options and ignores the others, so an app passes it the same options as Configure. It always sets a 10s read-header timeout and a 120s idle timeout. The read and write timeouts are unset (unlimited) unless WithReadTimeout or WithWriteTimeout is passed.

Serve blocks until ctx ends, then stops accepting connections, waits up to 10s for the requests in flight, closes the connections of any still running and returns. The caller owns the shutdown: signal.NotifyContext for SIGINT and SIGTERM gives the usual behaviour, and closing the database or flushing logs comes after Serve returns. A handler still running at that point has lost its connection, so its reads and writes fail and c.Request().Context() ends once net/http notices, but it runs on until it returns or its own timeout ends (the context requests.New builds does not derive from the connection's and ends after 15s by default), so it can hit what the caller tears down.

With a ctx that has already ended Serve returns at once. If the server cannot listen or fails while serving, Serve logs the error to log at fatal level and exits the process.

func Start

func Start(ctx context.Context, e *echo.Echo, log zerolog.Logger, port string, opts ...Option)

Start configures e with Configure and serves it on port with Serve, passing both the same opts; see both for what they do. An app that needs the configured stack without Start's server, or middleware of its own inside the stack, calls Configure and then Serve instead of Start, never Configure and Start, which would register everything twice.

Types

type Option added in v1.16.0

type Option func(*config)

Option configures Configure, Serve and Start. The options are one set: Configure uses WithAllowedOrigins, WithCredentialedCORS and WithBodyLimit, Serve uses the four timeout options, and each ignores the options that are not its own, so an app passes the same options to both.

func WithAllowedOrigins added in v1.16.0

func WithAllowedOrigins(origins ...string) Option

WithAllowedOrigins restricts CORS to the given origins. Without it, with no origins, or with "*" among them, every origin is allowed, and the other origins are neither used nor checked. An origin is a scheme and host with an optional port, like "https://app.example.com", matched whole ignoring case and sent back to the browser as written, so write it in lowercase as browsers send it. A pattern such as "https://*.example.com" matches nothing. An empty or padded origin (as from splitting an unset or spaced variable), one without a scheme, or one with a path, trailing slash, non-empty query or fragment makes Configure, and so Start, panic.

func WithBodyLimit added in v1.16.0

func WithBodyLimit(limit int64) Option

WithBodyLimit caps request bodies at limit bytes using echo's BodyLimit middleware, e.g. 10 << 20 for 10 MiB. Larger bodies get 413. Without it, or with a zero or negative limit, bodies are unbounded.

func WithCredentialedCORS added in v1.18.0

func WithCredentialedCORS() Option

WithCredentialedCORS sends Access-Control-Allow-Credentials: true, so a browser app on another origin can send and receive cookies. It is needed for cookie-based session auth from another origin. It only takes effect together with WithAllowedOrigins naming the origins: browsers reject credentials with a wildcard origin, so without allowed origins, or with "*" among them, it is ignored and Configure logs a warning. The cookie path also needs the origin checks of requests.WithCookie (the CSRF rule in docs/auth.md), since CORS alone does not stop cross-site writes.

func WithIdleTimeout added in v1.16.0

func WithIdleTimeout(d time.Duration) Option

WithIdleTimeout overrides the default 120s keep-alive idle timeout.

func WithReadHeaderTimeout added in v1.16.0

func WithReadHeaderTimeout(d time.Duration) Option

WithReadHeaderTimeout overrides the default 10s read-header timeout.

func WithReadTimeout added in v1.17.0

func WithReadTimeout(d time.Duration) Option

WithReadTimeout caps the time to read a whole request, headers and body, so a client trickling body bytes cannot hold a connection open. It is off by default (zero means unlimited). Uploads that take longer than d are cut off, so size it for the slowest upload the service accepts.

func WithWriteTimeout added in v1.17.0

func WithWriteTimeout(d time.Duration) Option

WithWriteTimeout caps the time from the end of reading the request headers to the end of writing the response. It is off by default (zero means unlimited). Long-running handlers, large downloads and streaming or SSE responses that take longer than d are cut off, so only set it on services that do none of those.

Directories

Path Synopsis
Package generator derives an OpenAPI 3.0 document from the source of an Echo application built on compose's rest handlers.
Package generator derives an OpenAPI 3.0 document from the source of an Echo application built on compose's rest handlers.
Package reference renders an OpenAPI 3.x document as a self-hosted API reference page: an alternative to Scalar and Redoc that ships inside the backend, with no CDN assets.
Package reference renders an OpenAPI 3.x document as a self-hosted API reference page: an alternative to Scalar and Redoc that ships inside the backend, with no CDN assets.

Jump to

Keyboard shortcuts

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