Pingora

Language: Rust · View source on GitHub

Dockerfile

FROM rust:1-slim AS build
RUN apt-get update && apt-get install -y pkg-config libssl-dev cmake g++ && rm -rf /var/lib/apt/lists/*
WORKDIR /src

# Cache dependencies with dummy main
COPY src/Servers/PingoraServer/Cargo.toml .
RUN mkdir src && echo "fn main() {}" > src/main.rs && cargo build --release && rm -rf src target/release/.fingerprint/pingora-server-*

COPY src/Servers/PingoraServer/src/ src/
RUN cargo build --release

FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y libssl3 && rm -rf /var/lib/apt/lists/*
COPY --from=build /src/target/release/pingora-server /usr/local/bin/
ENTRYPOINT ["pingora-server", "8080"]

Source

use async_trait::async_trait;
use bytes::Bytes;
use pingora::http::ResponseHeader;
use pingora::prelude::*;
use pingora::proxy::{http_proxy_service, ProxyHttp, Session};

struct OkProxy;

#[async_trait]
impl ProxyHttp for OkProxy {
    type CTX = ();

    fn new_ctx(&self) -> Self::CTX {}

    async fn request_filter(
        &self,
        session: &mut Session,
        _ctx: &mut Self::CTX,
    ) -> Result<bool> {
        let is_cookie = session.req_header().uri.path() == "/cookie";
        if is_cookie {
            let mut body_str = String::new();
            if let Some(raw) = session.req_header().headers.get("cookie").and_then(|v| v.to_str().ok()) {
                for pair in raw.split(';') {
                    let trimmed = pair.trim_start();
                    if let Some(eq) = trimmed.find('=') {
                        body_str.push_str(&format!("{}={}\n", &trimmed[..eq], &trimmed[eq+1..]));
                    }
                }
            }
            let body = Bytes::from(body_str);
            let mut header = ResponseHeader::build(200, None)?;
            header.insert_header("Content-Type", "text/plain")?;
            header.insert_header("Content-Length", &body.len().to_string())?;
            session
                .write_response_header(Box::new(header), false)
                .await?;
            session
                .write_response_body(Some(body), true)
                .await?;
            return Ok(true);
        }

        let is_echo = session.req_header().uri.path() == "/echo";
        if is_echo {
            let mut body_str = String::new();
            for (name, value) in session.req_header().headers.iter() {
                body_str.push_str(&format!("{}: {}\n", name, value.to_str().unwrap_or("")));
            }
            let body = Bytes::from(body_str);
            let mut header = ResponseHeader::build(200, None)?;
            header.insert_header("Content-Type", "text/plain")?;
            header.insert_header("Content-Length", &body.len().to_string())?;
            session
                .write_response_header(Box::new(header), false)
                .await?;
            session
                .write_response_body(Some(body), true)
                .await?;
            return Ok(true);
        }

        let is_post = session.req_header().method == pingora::http::Method::POST;
        let body = if is_post {
            let mut buf = Vec::new();
            while let Some(chunk) = session.read_request_body().await? {
                buf.extend_from_slice(&chunk);
            }
            Bytes::from(buf)
        } else {
            Bytes::from_static(b"OK")
        };
        let mut header = ResponseHeader::build(200, None)?;
        header.insert_header("Content-Type", "text/plain")?;
        header.insert_header("Content-Length", &body.len().to_string())?;
        session
            .write_response_header(Box::new(header), false)
            .await?;
        session
            .write_response_body(Some(body), true)
            .await?;
        Ok(true)
    }

    async fn upstream_peer(
        &self,
        _session: &mut Session,
        _ctx: &mut Self::CTX,
    ) -> Result<Box<HttpPeer>> {
        // Never reached — request_filter always handles the request
        unreachable!()
    }
}

fn main() {
    let port: u16 = std::env::args()
        .nth(1)
        .and_then(|s| s.parse().ok())
        .unwrap_or(9011);

    let mut server = Server::new(None).unwrap();
    server.bootstrap();

    let mut proxy = http_proxy_service(&server.configuration, OkProxy);
    proxy.add_tcp(&format!("0.0.0.0:{port}"));
    server.add_service(proxy);

    server.run_forever();
}

Test Results

Loading results...

Compliance

Smuggling

Malformed Input

Caching

Cookies