aboutsummaryrefslogtreecommitdiffhomepage

How to return errors

For easy error handling, the pingora-error crate exports a custom Result type used throughout other Pingora crates.

The Error struct used in this Result's error variant is a wrapper around arbitrary error types. It allows the user to tag the source of the underlying error and attach other custom context info.

Users will often need to return errors by propagating an existing error or creating a wholly new one. pingora-error makes this easy with its error building functions.

Examples

For example, one could return an error when an expected header is not present:

fn validate_req_header(req: &RequestHeader) -> Result<()> {
    // validate that the `host` header exists
    req.headers()
        .get(http::header::HOST)
        .ok_or_else(|| Error::explain(InvalidHTTPHeader, "No host header detected"))
}

impl MyServer {
    pub async fn handle_request_filter(
        &self,
        http_session: &mut Session,
        ctx: &mut CTX,
    ) -> Result<bool> {
        validate_req_header(session.req_header()?).or_err(HTTPStatus(400), "Missing required headers")?;
        Ok(true)
    }
}

validate_req_header returns an Error if the host header is not found, using Error::explain to create a new Error along with an associated type (InvalidHTTPHeader) and helpful context that may be logged in an error log.

This error will eventually propagate to the request filter, where it is returned as a new HTTPStatus error using or_err. (As part of the default pingora-proxy fail_to_proxy() phase, not only will this error be logged, but it will result in sending a 400 Bad Request response downstream.)

Note that the original causing error will be visible in the error logs as well. or_err wraps the original causing error in a new one with additional context, but Error's Display implementation also prints the chain of causing errors.

Guidelines

An error has a type (e.g. ConnectionClosed), a source (e.g. Upstream, Downstream, Internal), and optionally, a cause (another wrapped error) and a context (arbitrary user-provided string details).

A minimal error can be created using functions like new_in / new_up / new_down, each of which specifies a source and asks the user to provide a type.

Generally speaking: * To create a new error, without a direct cause but with more context, use Error::explain. You can also use explain_err on a Result to replace the potential error inside it with a new one. * To wrap a causing error in a new one with more context, use Error::because. You can also use or_err on a Result to replace the potential error inside it by wrapping the original one.

Retry

Errors can be "retry-able." If the error is retry-able, pingora-proxy will be allowed to retry the upstream request. Some errors are only retry-able on reused connections, e.g. to handle situations where the remote end has dropped a connection we attempted to reuse.

By default a newly created Error either takes on its direct causing error's retry status, or, if left unspecified, is considered not retry-able.