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 ?

2 Likes

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

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.