Type inference error trying to implement a custom MakeService with hyper

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!

When implementing Service for ProxyMakeService, you are defining the impl block like this:

impl<Target, Svc, Ret>

This means that Ret is a generic parameter, and it therefore means that the concrete type of Ret is chosen by the user of the trait implementation. However this isn't really what you intended — the call function in that trait is defined to return a certain async block, and this async block has a certain specific type (that implements future). What you want is for Self::Future to be the type of that async block, and letting the choice of Ret up to the user will not do that, so you will get an error from get about the return type not matching because the user might have chosen some other type for Ret.

Unfortunately the feature required to set Self::Future to be the async block does not exist at this time, and unless you want to return a pinned box, you it is likely simplest to use the service_fn and make_service_fn functions instead.

To actually use this trait without boxing the returned future, you would have to define a struct for your future and manually implement Future on that struct, which is not a simple task.

Thank you! This makes sense now. I tried type Future = impl Future<...>; and got an error stating it wasn't stabilized yet. Is that what you're referring to?

I guess the easiest way to avoid this is to curry the async fn that handles the proxying. I just need to figure out how to tie the lifetime of the HTTP client to the curried function, or maybe the Future that main() returns.

Yep, that's the missing feature.

Got it. I decided to just wrap up my Client, URL authority, and some other state in a read-only Config and pass that around with Arc. That gives me lower-cost copies so that the various futures that the nested make_service_fn( service_fn() ) call creates can separate their lifetimes from their parent closures. Thanks again for the help!

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.