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 enumResult<hyper::Response<()>, _>
found enumResult<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 ?