Network Programming in Rust #1 - Pingora filtering header

Migo·2024년 5월 27일

pingora

목록 보기
2/4

Pingora provides extensive customizability to your network programming. For more information, read: the this article.

Common problem 1 - validation

Say, you want to enforce that user must provide certain kind of header, X-FORWARDED-FOR in this example. If not present, you want to drop the request before you forward them to the upstream backends.

This is done easily by this:

 async fn request_filter(&self, session: &mut Session, ctx: &mut Self::CTX) -> Result<bool> {
        if session
            .req_header()
            .headers
            .get("X-FORWARDED-FOR")
            .is_none()
        {
            return Err(Error::explain(HTTPStatus(400), "X-FORWARDED-FOR not given"));
        }

        Ok(false)
    }

So when you curl:

curl -H "X-FORWARDED-FOR" 127.0.0.1:{your-port}

It works as expected and when you don't present the header, it will return 404 as follows:

curl -o /dev/null -s -w "%{http_code}" 127.0.0.1:{your-port}

# response
400%


Common problem 2 - based on Path

More likely though, you don't indiscriminately validate the request like above. More often than not, you want to apply the validation based on the path. That could be achieved like this:

 async fn request_filter(&self, session: &mut Session, ctx: &mut Self::CTX) -> Result<bool> {
        let req_header = session.req_header();

        if req_header.uri.path().starts_with("/routing")
            && req_header.headers.get("X-FORWARDED-FOR").is_none()
        {
            return Err(Error::explain(HTTPStatus(400), "X-FORWARDED-FOR not given"));
        }

        Ok(false)
    }

Success case:

curl -H "X-FORWARDED-FOR" 127.0.0.1:{your-port}/routing

Fail case:

curl -o /dev/null -s -w "%{http_code}" 127.0.0.1:{your-port}/routing

# response
400%


Common problem 3 - Global counter

Say, you have a certain endpoint that could be accessed by either authenticated or anonymous user. But for anonymous user, you want them to allow up to 3.

Let's have global counter first, which you may replace with something else in production.

static REQ_COUNTER: AtomicUsize = AtomicUsize::new(0);

Rate limiting logic follows:

   async fn request_filter(&self, session: &mut Session, ctx: &mut Self::CTX) -> Result<bool> {
        let req_header = session.req_header();

        if req_header.headers.get("Authorization").is_none() {
            // if REQ_COUNTER is more than 10, return 429
            if REQ_COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed) > 2 {
                return Err(Error::explain(HTTPStatus(429), "Too many requests"));
            }
        }
        Ok(false)
    }

Test:

curl -H 127.0.0.1:{your-port} #OK
curl -H 127.0.0.1:{your-port} #OK
curl -H 127.0.0.1:{your-port} #OK 
curl -H 127.0.0.1:{your-port} #Failed with 429


profile
Dude with existential crisis

0개의 댓글