I'd like to implement a simple reverse HTTP proxy using hyper as both server and client. Since all content will be served from a single origin, I'd like to share an upstream connection pool to that origin between all of the downstream client requests. I believe I need to share a single instance of Client<HttpConnector>
between all of the hyper Services since each client connection has a single Service.
When I look at the hyper docs, it suggests using a custom MakeService which will hold the shared state (i.e. the shared Client). OK, let's try it! Inside my custom MakeService I will call service_fn
to create a new service for each connection.
This snippet needs http, tokio, and hyper.
use http::uri;
use hyper::service::{Service, service_fn};
use hyper::{Body, Client, Request, Response, Server};
use std::net::SocketAddr;
use std::env;
use std::future::Future;
use std::convert::Infallible;
use std::task::{Context, Poll};
type HttpClient = Client<hyper::client::HttpConnector>;
async fn shutdown_signal() {
tokio::signal::ctrl_c()
.await
.expect("failed to install CTRL+C signal handler");
}
#[tokio::main]
async fn main() {
let client = HttpClient::new();
let proxy_authority = format!(
"{}:{}",
env::var("PROXY_HOST").unwrap(),
env::var("PROXY_PORT").unwrap()
).parse::<uri::Authority>().unwrap();
let svc = ProxyMakeService {
authority: proxy_authority,
client: client,
};
let addr = SocketAddr::from(([127, 0, 0, 1], 8080));
let server = Server::bind(&addr);
let server = server.serve(svc)
.with_graceful_shutdown(shutdown_signal());
if let Err(e) = server.await {
panic!(e)
}
}
struct ProxyMakeService {
authority: uri::Authority,
client: HttpClient,
}
impl<Target, Svc, Ret> Service<Target> for ProxyMakeService
where
Svc: Service<Request<Body>, Response = Response<Body>, Error = hyper::Error>,
Ret: Future<Output = Result<Svc, Infallible>>,
{
type Response = Svc;
type Error = Infallible;
type Future = Ret;
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Infallible>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, _t: &Target) -> Self::Future {
async {
Ok(service_fn(|req| {
proxy(&self.authority, &self.client, req)
}))
}
}
}
async fn proxy(
proxy_authority: &uri::Authority,
client: &HttpClient,
mut req: Request<Body>,
) -> Result<Response<Body>, hyper::Error> {
let mut parts = req.uri().clone().into_parts();
parts.authority = Some(proxy_authority.to_owned());
parts.scheme = Some(uri::Scheme::HTTP);
let new_uri = hyper::Uri::from_parts(parts).unwrap();
*req.uri_mut() = new_uri;
Ok(client.request(req).await?)
}
When I try to build this program, I get this error:
Compiling hyper_basic_proxy v0.1.0 (/Users/<redacted>/Code/demos/hyper_basic_proxy)
error[E0284]: type annotations needed
--> src/main.rs:35:25
|
35 | let server = server.serve(svc)
| ^^^^^ cannot infer type
|
= note: cannot resolve `<_ as hyper::service::http::HttpService<hyper::body::body::Body>>::Future == _`
error: aborting due to previous error
For more information about this error, try `rustc --explain E0284`.
error: could not compile `hyper_basic_proxy`.
It seems that there's a problem inferring the type of the Future that my function will return. I've tried a few different annotations and even tried implementing a custom Service without any luck.
What's the right way to do this? Thanks!