cloudflare / pingora

A library for building fast, reliable and evolvable network services.
Apache License 2.0
20.21k stars 1.1k forks source link

Proxy to local service #288

Closed JosiahParry closed 2 weeks ago

JosiahParry commented 2 weeks ago

I'm working with Caddy right now but I'd like to move to a rust based solution that allows me to spawn new services as i need. I think pingora is the solution but am having a lot of trouble getting a minimal example running.

I'm trying to adapt the quickstart reverse proxy to work with a local service but am having no luck.

For example i have a local app running at 127.0.0.1:9000

image

I've modified the LoadBalancer so that it only calls the local service.

fn main() {
    let mut my_server = Server::new(None).unwrap();
    my_server.bootstrap();
    let r = SocketAddr::from_str("127.0.0.1:9000").unwrap();
    let upstreams = LoadBalancer::try_from_iter([r]).unwrap();
    let mut lb = http_proxy_service(&my_server.configuration, LB(Arc::new(upstreams)));
    lb.add_tcp("0.0.0.0:6188");
    my_server.add_service(lb);
    my_server.run_forever();
}

However, when I run this I get a 502 in the browser and there is no messaging being printed to stdout as to why this might be.

image

Are there any examples that illustrate proxying to a local service?

Ideally, I'd have a struct that can represent the app / service that can spawn new instances based on demand on random ports by, I think, implementing the Service trait. but before that, i'd need to prove that pingora can even proxy to a local app.

adammakowskidev commented 2 weeks ago

@JosiahParry Maybe try pingap? It is built on a pingora. https://github.com/vicanso/pingap

JosiahParry commented 2 weeks ago

No, that's not what I'm after. I'd like to define services and have pingora spin them up and down as needed as illustrated by the user guide.

But before I can do that I'd like to take baby steps to get there. The first of which would be showing that I can proxy to a local service

luizfonseca commented 2 weeks ago

@JosiahParry ~Not sure you can just append the LB there~ nvm, you can -- you just missing the impl HttpProxy for it.

Make sure you are following this guide: https://github.com/cloudflare/pingora/blob/main/docs/quick_start.md#create-a-pingora-server

TL;DR: afaik you are missing the impl HttpProxy that allows you to select a backend

JosiahParry commented 2 weeks ago

Thanks @luizfonseca! This is exactly the quickstart except changing the upstream to instead be a local service served at 127.0.0.1:9000 which is why i'm a bit confused why its not working. Using the one-one-one example works. But using a local address does not work

```rs use async_trait::async_trait; use pingora::prelude::*; use std::sync::Arc; use std::{net::SocketAddr, str::FromStr}; pub struct LB(Arc>); #[async_trait] impl ProxyHttp for LB { /// For this small example, we don't need context storage type CTX = (); fn new_ctx(&self) -> () { () } async fn upstream_peer(&self, _session: &mut Session, _ctx: &mut ()) -> Result> { let upstream = self .0 .select(b"", 256) // hash doesn't matter for round robin .unwrap(); // Set SNI to one.one.one.one let peer = Box::new(HttpPeer::new(upstream, true, "one.one.one.one".to_string())); Ok(peer) } async fn upstream_request_filter( &self, _session: &mut Session, upstream_request: &mut RequestHeader, _ctx: &mut Self::CTX, ) -> Result<()> { upstream_request .insert_header("Host", "one.one.one.one") .unwrap(); Ok(()) } } fn main() { let mut my_server = Server::new(None).unwrap(); my_server.bootstrap(); let r = SocketAddr::from_str("127.0.0.1:9000").unwrap(); let upstreams = LoadBalancer::try_from_iter([r]).unwrap(); let mut lb = http_proxy_service(&my_server.configuration, LB(Arc::new(upstreams))); lb.add_tcp("0.0.0.0:6188"); my_server.add_service(lb); my_server.run_forever(); } ```
luizfonseca commented 2 weeks ago

@JosiahParry Ah, gotcha!

This line here in your impl HttpProxy:

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

The true there is configuring this backend connection to use tls -- but since you are running against a local/non-ssl service you can change that to false.

let peer = Box::new(HttpPeer::new(upstream, false, "one.one.one.one".to_string()));

(you can also just issue your own certs locally and use https:// in your :9000 service to keep the true there, but that's another config step)

EDIT: it works in the example because it is targetting cloudflare's 1.1.1.1 (which uses tls)

JosiahParry commented 2 weeks ago

@luizfonseca thank you! That worked. Simple enough :) I'll close the issue.

For my continued exploration, I'd like to spawn services automatically.

I have a struct that spawns a service at a random port that I'd like to proxy to. Do you know which part of the docs / examples should I explore to have pingora spawn/close/manage the service and proxy to it?

Thank you so much that has been a huge help!

luizfonseca commented 2 weeks ago

@JosiahParry Interesting, it might be that documentation is sparse in that case, but:

What you can explore:

The other point I wanted to make is that you could also just group everything in 1 listener with multiple (worker) threads that has access to some sort of routing state (dashmap, arcswap etc) for better connection reuse, but that's only useful if it fits your use case anyway.

EDIT: Some other points you can look at: