Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Send error context to downstream client #457

Open
cetra3 opened this issue Nov 7, 2024 · 3 comments
Open

Send error context to downstream client #457

cetra3 opened this issue Nov 7, 2024 · 3 comments
Assignees
Labels
enhancement New feature or request WIP We are working on this feature internally

Comments

@cetra3
Copy link

cetra3 commented Nov 7, 2024

What is the problem your feature solves, or the need it fulfills?

At the moment the context of the error is logged, but that information isn't sent to the downstream client. We're using pingora for some internal proxying, and while we can get an error status back when things go wrong, the context is lost. It'd be great if there was a way to send an error back to the client.

Describe the solution you'd like

A way to specify that the error context is returned in the response body to clients.

Describe alternatives you've considered

As the error is happening within the upstream_peer of the ProxyHttp trait, I tried setting the session's downstream response body directly, but this doesn't appear to work. Instead no body is sent back to the client downstream.

I.e, doing something like this:

               // Send error to downstream client
                let error_msg = pingora_error
                    .context
                    .as_ref()
                    .map(|val| Bytes::from(val.to_string().into_bytes()));

                session.as_downstream_mut().write_response_body(error_msg.unwrap_or_default(), true).await?;

And also tried write_response_body directly:

                session.write_response_body(error_msg, true).await?;
@andrewhavck
Copy link
Contributor

Where are you trying to write the error? Ideally this is would happen in fail_to_proxy.

/// Users may write an error response to the downstream if the downstream is still writable.

@andrewhavck andrewhavck added the question Further information is requested label Nov 8, 2024
@cetra3
Copy link
Author

cetra3 commented Nov 8, 2024

Implementing the fail_to_proxy method doesn't work either:

    async fn fail_to_proxy(&self, session: &mut Session, e: &Error, _ctx: &mut Self::CTX) -> u16
    where
        Self::CTX: Send + Sync,
    {
        let server_session = session.as_mut();
        let code = match e.etype() {
            HTTPStatus(code) => *code,
            _ => {
                match e.esource() {
                    ErrorSource::Upstream => 502,
                    ErrorSource::Downstream => {
                        match e.etype() {
                            WriteError | ReadError | ConnectionClosed => {
                                /* conn already dead */
                                0
                            }
                            _ => 400,
                        }
                    }
                    ErrorSource::Internal | ErrorSource::Unset => 500,
                }
            }
        };
        if code > 0 {
            server_session.respond_error(code).await;
            if let Some(ref ctx) = e.context {
                // Nothing is written to the downstream client
                server_session.write_response_body(ctx.to_string().into(), true).await.ok();
            }
        }
        code
    }

@andrewhavck andrewhavck self-assigned this Nov 8, 2024
@andrewhavck
Copy link
Contributor

The respond_error() function uses gen_error_response() which has a default Content-Length: 0. so the body is being ignored when you try to write it.

You can either calculate the length of the error message and set the correct content length or use chunked encoding on the error response. Either way you'll need to construct your own error response headers.

IE:

fn gen_chunked_error_response(code: u16) -> ResponseHeader {
    let mut resp = ResponseHeader::build(code, Some(4)).unwrap();
    resp.insert_header(header::SERVER, &SERVER_NAME[..])
        .unwrap();
    resp.insert_header(header::DATE, "Sun, 06 Nov 1994 08:49:37 GMT")
        .unwrap(); // placeholder
    resp.insert_header(header::TRANSFER_ENCODING, "chunked")
        .unwrap();
    resp.insert_header(header::CACHE_CONTROL, "private, no-store")
        .unwrap();
    resp
}

Then use write_response_header() and write_response_body().

We actually have some code that handles error responses with bodies internally, will move this out so this is easier for others.

@andrewhavck andrewhavck added enhancement New feature or request WIP We are working on this feature internally and removed question Further information is requested labels Nov 8, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request WIP We are working on this feature internally
Projects
None yet
Development

No branches or pull requests

2 participants