I have the below code:
#[tokio::main]
async fn main() {
let app = Router::new()
.route("/", get(handler))
.route("/custom/*key", get(proxy).post(proxy));
// ...
}
pub async fn proxy(mut req: Request<Body>) -> Result<Response<Body>, StatusCode> {
let path_query = req
.uri()
.path_and_query()
.map_or_else(|| req.uri().path(), |v| v.as_str());
let uri = format!("http://localhost:9000/{}", path_query);
*req.uri_mut() = Uri::try_from(uri).unwrap();
let http_client = reqwest::Client::new();
let res = http_client.execute(req).await.unwrap()
if res.status().is_server_error() {
return Err(StatusCode::INTERNAL_SERVER_ERROR);
}
Ok(res)
}
But obviously this doesn't work:
error[E0308]: mismatched types
|
35 | let res = http_client.execute(req).await.unwrap();
| ------- ^^^ expected `Request`, found `Request<Incoming>`
| |
| arguments to this method are incorrect
|
= note: `Request<Incoming>` and `reqwest::Request` have similar names, but are actually distinct types
note: `Request<Incoming>` is defined in crate `http`
--> C:\Users\Fred\.cargo\registry\src\index.crates.io-6f17d22bba15001f\http-1.1.0\src\request.rs:158:1
|
158 | pub struct Request<T> {
| ^^^^^^^^^^^^^^^^^^^^^
note: `reqwest::Request` is defined in crate `reqwest`
--> C:\Users\Fred\.cargo\registry\src\index.crates.io-6f17d22bba15001f\reqwest-0.12.4\src\async_impl\request.rs:22:1
|
22 | pub struct Request {
| ^^^^^^^^^^^^^^^^^^
note: method defined here
--> C:\Users\Fred\.cargo\registry\src\index.crates.io-6f17d22bba15001f\reqwest-0.12.4\src\async_impl\client.rs:1965:12
|
1965 | pub fn execute(
| ^^^^^^^
and:
error[E0308]: mismatched types
|
43 | Ok(res)
| -- ^^^ expected `Response<Incoming>`, found `Response`
| |
| arguments to this enum variant are incorrect
|
= note: `reqwest::Response` and `Response<Incoming>` have similar names, but are actually distinct types
note: `reqwest::Response` is defined in crate `reqwest`
--> C:\Users\Fred\.cargo\registry\src\index.crates.io-6f17d22bba15001f\reqwest-0.12.4\src\async_impl\response.rs:29:1
I'm using axum 0.7.5
and reqwest 0.12
(both on http 1
).
Is it possible to make it work?
As the error says, a reqwest Request
is not the same as an axum Request
which is an alias to Request
from the http crate. However, they are trying to express the same information so we just need to convert one to the other.
There is a TryFrom
implementation for a reqwest Request
to be built from an http Request
however there is a snag that the axum Body
is not convertible to a reqwest Body
. But there is a .map()
function on http Request
that can transform the body type ourselves.
We can avoid buffering the full request body by mapping them by byte streams like this:
let req = req.map(|body| reqwest::Body::wrap_stream(body.into_data_stream()));
However, yet again there is a snag: in the versions you are asking about .into_data_stream()
from the axum body doesn't implement Sync
that reqwest's Body::wrap_stream
requires. Fortunately, there is a workaround - previous versions of reqwest didn't require Sync
but were able to keep their Body
implementation Sync
by using a SyncStream
from the sync-wrapper crate (read their docs if you want to know how it works).
So the full request conversion looks like this:
use sync_wrapper::SyncStream;
let req = req.map(|body| reqwest::Body::wrap_stream(SyncStream::new(body.into_data_stream())));
let req = reqwest::Request::try_from(req).unwrap();
let res = http_client.execute(req).await.unwrap();
The try_from
call can theoretically fail due to re-parsing the URL from a http Uri
to a reqwest Url
(aliased from the url crate).
Converting from the reqwest Response
back into an axum/http Response
isn't as bad since reqwest Body
implements the http Body
trait and axum's Body
can be constructed by anything implementing the Body
trait. And neither of these conversions can fail. Here's how that looks:
let res = axum::http::Response::from(res);
let res = res.map(|body| Body::new(body));