Documentation
¶
Index ¶
- func Configure(e *echo.Echo, log zerolog.Logger, opts ...Option)
- func ErrorHandler(opts ...Option) echo.HTTPErrorHandler
- func RejectNUL() echo.MiddlewareFunc
- func Serve(ctx context.Context, e *echo.Echo, log zerolog.Logger, port string, ...)
- func Start(ctx context.Context, e *echo.Echo, log zerolog.Logger, port string, ...)
- type Option
- func WithAllowedOrigins(origins ...string) Option
- func WithBodyLimit(limit int64) Option
- func WithCredentialedCORS() Option
- func WithErrorReporter(fn func(c *echo.Context, err error)) Option
- func WithErrorTranslator(fn func(c *echo.Context, err error) error) Option
- func WithIdleTimeout(d time.Duration) Option
- func WithReadHeaderTimeout(d time.Duration) Option
- func WithReadTimeout(d time.Duration) Option
- func WithWriteTimeout(d time.Duration) Option
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Configure ¶ added in v1.29.0
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, which has e.HTTPErrorHandler answer an error before logging it (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 and ErrorHandler. 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, and the request logger calls whatever e.HTTPErrorHandler is when the request runs.
func ErrorHandler ¶ added in v1.29.0
func ErrorHandler(opts ...Option) echo.HTTPErrorHandler
ErrorHandler is an Echo HTTPErrorHandler that answers every error returned to Echo in compose's handler.ErrorResponse shape: the errors of the api registry's operations, which return them rather than writing them, and the errors that never reach handler.ManageError (unknown routes, 405, recovered panics, errors a handler or middleware returns straight to Echo). An app installs it with e.HTTPErrorHandler = rest.ErrorHandler(opts...); Configure and Start do not, and the api registry's Ready panics while Echo's default handler is installed. It uses WithErrorReporter and WithErrorTranslator and ignores the other options, so an app passes it the same options as Configure and Serve.
The error is an ez error when it is an *ez.Error itself, or when an *ez.Error is in its chain and the chain carries no Echo status (echo.StatusCode is 0), the rule handler.ReportToSentry classifies by too. An ez error is answered with ez.ErrorToHTTPStatus, ez.ErrorCode and ez.ErrorMessage of the error, as handler.ManageError does; an ez error wrapped in another kind of error, such as a recovered panic of one, is therefore a 500 EINTERNAL with ez's generic message (a 499 or 504 when a cancelled or timed-out context is in its chain). An Echo error wrapping an ez error, as c.Bind makes when a field's UnmarshalJSON returns one and middleware.KeyAuth when its validator does, is not an ez error. Any other error with the non-zero StatusCode of an echo.HTTPStatusCoder in the error chain gets that status, as Echo's default handler has 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. An error with neither is classified with ez.ErrorCode, as handler.ManageError does: context.Canceled in its chain is a 499 ECANCELED and a timeout (context.DeadlineExceeded or os.ErrDeadlineExceeded in its chain, or an error whose own Timeout method reports true, such as an HTTP client's) a 504 ETIMEOUT, both with ez.ErrorMessage's fixed text, and anything else a 500 EINTERNAL with http.StatusText(500). The message is never such an 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.
An ez EINVALID error that carries the field errors of the api registry's request validation (validate.Fields) is answered with them as the fields list of the envelope, one {in, name, message} entry per failing field in the order the message joins them and at most validate.MaxFields of them, so a client can mark the field without parsing the message. The list is read from anywhere in the error's chain: an app that wraps such an error in an EINVALID of its own answers with its own message and the same list. Any other error has no fields key, an EINVALID from an app's Validate included unless the app built it with validate.NewFieldsError. WithErrorTranslator replaces the message only: the entries keep the registry's English text.
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, hooks included. That makes it run once per request under Configure, whose request logger calls it and then returns the error, so Echo calls it again on the answered response.
It logs nothing about the error, because the request logger that Configure (and so Start) installs has it answer every error raised inside its stack and then logs the error under the same id with the status sent, at warn level for a 4xx and error level for a 5xx, 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
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 and ErrorHandler. 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; an api registry operation's request is built on the connection's context and ends with it), 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 ¶
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, Start and ErrorHandler. The options are one set: Configure uses WithAllowedOrigins, WithCredentialedCORS and WithBodyLimit, Serve uses the four timeout options, ErrorHandler uses WithErrorTranslator and WithErrorReporter, and each ignores the options that are not its own, so an app passes the same options to all of them.
func WithAllowedOrigins ¶ added in v1.16.0
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
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 WithErrorReporter ¶ added in v1.31.0
WithErrorReporter has ErrorHandler call fn with every error it answers, ez or not, client or server error, before the answer is written; fn decides what to report. handler.ReportToSentry is one, for an app that reports to Sentry. The api registry's Ready also calls it once at boot, with an ez EINVALID error and handler.Request(c) nil, which handler.ReportToSentry skips as a client error. The context of the request a registry operation built (handler.Request(c).GetContext()) is already cancelled when fn runs: fn reads values from it and does I/O with c.Request().Context(). ErrorHandler option. Panics when fn is nil.
func WithErrorTranslator ¶ added in v1.31.0
WithErrorTranslator has ErrorHandler apply fn to every ez error right before writing it, so an app can localize error messages. Only the translated error's message is used; the code and status always come from the original error, and returning nil keeps it. Errors that are not ez errors are never translated. It is handler.BaseHandler.ErrorTranslator for the errors ErrorHandler answers. An app that localizes by the request's locale reads handler.Request(c), which is nil for an error raised outside a registry operation or handler.ManageError (an unknown route, 405, a panic before the handler, the ez EINVALID error the api registry's Ready answers once at boot). That request's context is already cancelled when fn runs, so fn reads values from it and does I/O with c.Request().Context(). ErrorHandler option. Panics when fn is nil.
func WithIdleTimeout ¶ added in v1.16.0
WithIdleTimeout overrides the default 120s keep-alive idle timeout.
func WithReadHeaderTimeout ¶ added in v1.16.0
WithReadHeaderTimeout overrides the default 10s read-header timeout.
func WithReadTimeout ¶ added in v1.17.0
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
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 api is compose's declarative REST route registry.
|
Package api is compose's declarative REST route registry. |
|
internal/fixture
Package fixture mirrors auditor3's runs module for api registry tests.
|
Package fixture mirrors auditor3's runs module for api registry tests. |
|
openapi
Package openapi turns Go request and response types into OpenAPI 3.1 (JSON Schema draft 2020-12) component schemas and parameters.
|
Package openapi turns Go request and response types into OpenAPI 3.1 (JSON Schema draft 2020-12) component schemas and parameters. |
|
openapi/internal/fixture
Package fixture mirrors auditor3's runs module for openapi reflector tests.
|
Package fixture mirrors auditor3's runs module for openapi reflector tests. |
|
openapi/internal/fixture/a/units
Package units collides by last path segment with fixture/b/units.
|
Package units collides by last path segment with fixture/b/units. |
|
openapi/internal/fixture/b/units
Package units collides by last path segment with fixture/a/units.
|
Package units collides by last path segment with fixture/a/units. |
|
openapi/internal/fixture/other
Package other holds a type whose name collides with fixture.Run, to prove package-qualified component names keep them apart.
|
Package other holds a type whose name collides with fixture.Run, to prove package-qualified component names keep them apart. |
|
validate
Package validate compiles OpenAPI 3.1 schemas with santhosh-tekuri/jsonschema and validates decoded requests against them.
|
Package validate compiles OpenAPI 3.1 schemas with santhosh-tekuri/jsonschema and validates decoded requests against them. |
|
Package apitest checks recorded responses against the contract of an api.Registry: the strict OpenAPI 3.1 document, where every response object component has additionalProperties:false so undocumented fields fail.
|
Package apitest checks recorded responses against the contract of an api.Registry: the strict OpenAPI 3.1 document, where every response object component has additionalProperties:false so undocumented fields fail. |
|
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. |