Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion docs/user_guide/modify_filter.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,32 @@ impl ProxyHttp for MyGateway {
Ok(false)
}
```

## Return a redirect

`request_filter()` can write a response without contacting an upstream. Return `Ok(true)` after the response is
written so that Pingora skips the remaining proxy phases.

The response still needs valid HTTP message framing. For a redirect with no body, `Content-Length: 0` tells HTTP/1
clients that the response is complete while allowing the connection to remain open. The `true` argument tells
response filters that no body chunks will follow; it does not add a framing header.

```Rust
async fn request_filter(&self, session: &mut Session, _ctx: &mut Self::CTX) -> Result<bool> {
if session.req_header().uri.path() == "/old-path" {
let mut response = ResponseHeader::build(302, Some(2))?;
response.insert_header("Location", "/new-path")?;
response.insert_header("Content-Length", "0")?;
session
.write_response_header(Box::new(response), true)
.await?;
return Ok(true);
}

Ok(false)
}
```

## Logging

Logging logic can be added to the `logging` phase of Pingora. The logging phase runs on every request right before Pingora proxy finish processing it. This phase runs for both successful and failed requests.
Expand Down Expand Up @@ -129,4 +155,4 @@ fn main() {

my_server.run_forever();
}
```
```
12 changes: 12 additions & 0 deletions pingora-proxy/examples/gateway.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

use async_trait::async_trait;
use bytes::Bytes;
use http::header::{CONTENT_LENGTH, LOCATION};
use log::info;
use prometheus::register_int_counter;

Expand All @@ -39,6 +40,16 @@ impl ProxyHttp for MyGateway {
fn new_ctx(&self) -> Self::CTX {}

async fn request_filter(&self, session: &mut Session, _ctx: &mut Self::CTX) -> Result<bool> {
if session.req_header().uri.path() == "/redirect" {
let mut response = ResponseHeader::build(302, Some(2))?;
response.insert_header(LOCATION, "/family/")?;
response.insert_header(CONTENT_LENGTH, "0")?;
session
.write_response_header(Box::new(response), true)
.await?;
return Ok(true);
}

if session.req_header().uri.path().starts_with("/login")
&& !check_login(session.req_header())
{
Expand Down Expand Up @@ -108,6 +119,7 @@ impl ProxyHttp for MyGateway {
// RUST_LOG=INFO cargo run --example gateway
// curl 127.0.0.1:6191 -H "Host: one.one.one.one"
// curl 127.0.0.1:6190/family/ -H "Host: one.one.one.one"
// curl --max-redirs 0 127.0.0.1:6191/redirect -H "Host: one.one.one.one" -v
// curl 127.0.0.1:6191/login/ -H "Host: one.one.one.one" -I -H "Authorization: password"
// curl 127.0.0.1:6191/login/ -H "Host: one.one.one.one" -I -H "Authorization: bad"
// For metrics
Expand Down
9 changes: 7 additions & 2 deletions pingora-proxy/src/proxy_trait.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,13 @@ pub trait ProxyHttp {
/// In this phase, users can parse, validate, rate limit, perform access control and/or
/// return a response for this request.
///
/// If the user already sent a response to this request, an `Ok(true)` should be returned so that
/// the proxy would exit. The proxy continues to the next phases when `Ok(false)` is returned.
/// After sending a response, return `Ok(true)` so that the proxy stops processing the request.
/// Return `Ok(false)` to continue to the remaining phases.
///
/// Returning `Ok(true)` and setting `end_of_stream` on a write do not add HTTP message framing.
/// Before returning, the response must indicate where its body ends. For a response with no
/// body, set `Content-Length: 0` or disable downstream keep-alive with
/// `session.set_keepalive(None)`.
///
/// By default this filter does nothing and returns `Ok(false)`.
async fn request_filter(&self, _session: &mut Session, _ctx: &mut Self::CTX) -> Result<bool>
Expand Down
Loading