cloudflare / cloudflare/pingora

Examples not reproducible

Open
#460 4 comments 3 reactions 1 assignee Claimed by @johnhurt View on GitHub
documentation
Dominant language
Rust
Stars
27.4k
Forks
1.7k
Avg merge
6h 22m
Merged PRs (30d)
3

Description

## Describe the bug

The examples are not very useful for individuals trying to get started. The first challenge is getting the examples to run locally in their own crate.

None of the examples use `use pingora::prelude::*;` as suggested in the Quick Start guide.

After swapping each individual crate to `pingora::` e.g. `pingora_http::RequestHeader` to `pingora::http::RequestHeader` each of the examples always result in the following error

```
[2024-11-09T19:47:04Z ERROR pingora_proxy] Fail to proxy: Upstream ConnectionClosed context: Peer: addr: 1.1.1.1:443, scheme: HTTPS,sni: one.one.one.one, cause: context: while reading response headers, bytes already read: 0, status: 502, tries: 1, retry: false, GET /, Host: localhost:6190
```

I suspect this is because there is not a TSL cert.

Using `cargo example` can show you the output of the example but it cannot get you started with a standalone example.

## Pingora info

Please include the following information about your environment:

**Pingora version**: "0.4.0", source = "registry+https://github.com/rust-lang/crates.io-index", checksum = "79c9fc7098dc3e7d09d2d1647921005be9301cf68536826195dc5369e05124bd"
**Rust version**: rustc 1.83.0-nightly (adf8d168a 2024-09-08)
**Operating system version**: e.g. Apple M1 macOS 15.0.1

## Steps to reproduce

Please provide step-by-step instructions to reproduce the issue. Include any relevant code
snippets.

```shell
cargo new test-pingora
cd test-pingora
cargo add pingora
```

Copy and past the gateway example from https://github.com/cloudflare/pingora/blob/main/pingora-proxy/examples/gateway.rs

Notice you need prometheus, clap, and log

```shell
cargo add clap log prometheus
```

```shell
cargo check
```

Replace all instances of `pingora_{crate}` with the appropriate `pingora::`. This gets you a main.rs that contains:

```rs
use async_trait::async_trait;
use clap::Parser;
use log::info;
use pingora::{http::ResponseHeader, prelude::*};
use prometheus::register_int_counter;

fn check_login(req: &RequestHeader) -> bool {
// implement you logic check logic here
req.headers.get("Authorization").map(|v| v.as_bytes()) == Some(b"password")
}

pub struct MyGateway {
req_metric: prometheus::IntCounter,
}

#[async_trait]
impl ProxyHttp for MyGateway {
type CTX = ();
fn new_ctx(&self) -> Self::CTX {}

async fn request_filter(&self, session: &mut Session, _ctx: &mut Self::CTX) -> Result {
if session.req_header().uri.path().starts_with("/login")
&& !check_login(session.req_header())
{
let _ = session.respond_error(403).await;
// true: early return as the response is already written
return Ok(true);
}
Ok(false)
}

async fn upstream_peer(
&self,
session: &mut Session,
_ctx: &mut Self::CTX,
) -> Result> {
let addr = if session.req_header().uri.path().starts_with("/family") {
("1.0.0.1", 443)
} else {
("1.1.1.1", 443)
};

info!("connecting to {addr:?}");

let peer = Box::new(HttpPeer::new(addr, true, "one.one.one.one".to_string()));
Ok(peer)
}

async fn response_filter(
&self,
_session: &mut Session,
upstream_response: &mut ResponseHeader,
_ctx: &mut Self::CTX,
) -> Result<()>
where
Self::CTX: Send + Sync,
{
// replace existing header if any
upstream_response
.insert_header("Server", "MyGateway")
.unwrap();
// because we don't support h3
upstream_response.remove_header("alt-svc");

Ok(())
}

async fn logging(
&self,
session: &mut Session,
_e: Option<&pingora::Error>,
ctx: &mut Self::CTX,
) {
let response_code = session
.response_written()
.map_or(0, |resp| resp.status.as_u16());
info!(
"{} response code: {response_code}",
self.request_summary(session, ctx)
);

self.req_metric.inc();
}
}

fn main() {
env_logger::init();

// read command line arguments
let opt = Opt::parse();
let mut my_server = Server::new(Some(opt)).unwrap();
my_server.bootstrap();

let mut my_proxy = pingora::proxy::http_proxy_service(
&my_server.configuration,
MyGateway {
req_metric: register_int_counter!("req_counter", "Number of requests").unwrap(),
},
);
my_proxy.add_tcp("0.0.0.0:6191");
my_server.add_service(my_proxy);

let mut prometheus_service_http =
pingora::services::listening::Service::prometheus_http_service();
prometheus_service_http.add_tcp("127.0.0.1:6192");
my_server.add_service(prometheus_service_http);

my_server.run_forever();
}
```

Now, `Opt::parse()` doesn't work. According to RA there may be an alternative option here:

```
help: trait `Parser` which provides `parse` is implemented but not in scope; perhaps you want to import it
|
1 + use clap::derive::Parser;
|
help: there is an associated function `try_parse` with a similar name
|
90 | let opt = Opt::try_parse();
| ~~~~~~~~~
```

No combination of `clap` traits can work. Discover that `pingora::prelude::Opt` has an associated `parse_args()` method. After changing `let opt = Opt::parse();` to `let opt = Opt::parse_args();` we can compile correctly.

Run

```shell
cargo run
```

Now test it

```shell
# returns Couldn't connect to server
curl 127.0.0.1:6190/family/ -H "Host: one.one.one.one"

# returns bad gateway
curl 127.0.0.1:6191/login/ -H "Host: one.one.one.one" -I -H "Authorization: password"

# returns error as expected
curl 127.0.0.1:6191/login/ -H "Host: one.one.one.one" -I -H "Authorization: bad"
```

From a browser navigate to `localhost:6190/family` and expect to be proxied to `https://one.one.one.one/`

## Expected results

I expected the proxy to successfully work.

## Observed results

Each example fails to with a 502 or similar error including the quick start.

## Additional context

At the 0.1.0 release, the quickstart and the load balancer examples worked. They no longer do.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.