Return different body type in hyper::Response

I am following this example to create a HTTP reverse proxy using hyper, version 1.0.0-rc.1.

// This is the `Service` that will handle the connection.
// `service_fn` is a helper to convert a function that
// returns a Response into a `Service`.
let service = service_fn(move |req| {

  let backend_routine_option = cloned_callback(&req);
  
  async move {
      
      if let Some(backend_routine) = backend_routine_option {
        let primary_backend = backend_routine.primary();
        let client_stream = TcpStream::connect(primary_backend.address()).await.unwrap();

        let (mut sender, conn) =
            hyper::client::conn::http1::handshake(client_stream).await?;

        tokio::task::spawn(async move {
            if let Err(err) = conn.await {
                warn!("Connection failed: {:?}", err);
            }
        });

        sender.send_request(req).await.and_then(|mut resp| {
          // here the return type is `Response<Incoming>`
          Ok(resp)
        })
      } else {

        let response = Response::builder()
          .status(http::StatusCode::BAD_GATEWAY)
          .body(())
          .unwrap();
        return Ok(response);  // here the return type is `Response<()>`
      }
  }
});

In normal case, the response is a type of Response<Incoming> instance which is from upstream.
But when there is no upstream available,it should return a HTTP 502 error instantly, in this case the type of response is Response<()>

Because the return type of two branches are different, the compilation failed.

mismatched types
expected enum Result<hyper::Response<()>, _>
found enum Result<hyper::Response<hyper::body::Incoming>, hyper::Error>

Please how should I fix this problem?
Maybe I need return Response<Box<dyn hyper::Body>> ? but have no idea how to convert Response<hyper::body::Incoming> into this type.


UPDATE:

I have found a solution by myself.

First define an enum to cover both types of response body.


enum GatewayBody {
  Incoming(hyper::body::Incoming),
  Empty,
}


impl hyper::body::Body for GatewayBody
where
{
    type Data = hyper::body::Bytes;
    type Error = hyper::Error;

    fn poll_frame(
        self: Pin<&mut Self>,
        cx: &mut Context<'_>,
    ) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> {
      match &mut *self.get_mut(){
        Self::Incoming( incoming) => {
          Pin::new( incoming).poll_frame(cx)
        },
        Self::Empty => {
          Poll::Ready(None)
        }
      }
      
    }
}

Then copy the headers from upstream response into my response to client.

let service = service_fn(move |req| {
 
  let backend_routine_option = cloned_callback(&req);
 
  async move {
      
      if let Some(backend_routine) = backend_routine_option {
        let primary_backend = backend_routine.primary();
        let client_stream = TcpStream::connect(primary_backend.address()).await.unwrap();

        let (mut sender, conn) =
            hyper::client::conn::http1::handshake(client_stream).await?;
        tokio::task::spawn(async move {
            if let Err(err) = conn.await {
                warn!("Connection failed: {:?}", err);
            }
        });

        sender.send_request(req).await.and_then(|upstream_resp| {
          

          let mut res = Response::builder().status(upstream_resp.status());
          {
            let headers = res.headers_mut().unwrap();
            headers.clone_from(upstream_resp.headers());
          }
          Ok ( res.body(GatewayBody::Incoming(upstream_resp.into_body())).unwrap() )
        })
      } else {

        let res = Response::builder()
          .status(http::StatusCode::BAD_GATEWAY)
          .body(GatewayBody::Empty)
          .unwrap();
        Ok(res)
      }
  }
});

It works but maybe there is a better approach ?

1 Like

Which version of hyper are you using? There's no hyper::body::Incoming in the newest version.

Normally, I would consider the simplest approach to just always use hyper::body::Body. It has a method for creating an empty body that you can use instead of ().

v1.0 is going to be released in a few days, I am coding using its RC1

Body in hyper::body - Rust hyper::body::Body now is a trait instead of struct

Is there any none clone way to do this? clearly we just need to wrap the body