How to Get Response Body in middleware
I want to impl a middleware that can log request and response body
#[async_trait]
impl Middleware for LoggingMiddlware {
async fn handle(
&self,
req: Request,
extensions: &mut Extensions,
next: Next<'_>,
) -> ReqResult<Response> {
req.body().clone()
.and_then(|b| b.as_bytes())
.and_then(|v| {
println!("Request: {:?}", std::str::from_utf8(v).unwrap());
Some(())
});
let res = next.run(req, extensions).await;
// since response is a future
// how can I get its response body without changing it?
res
}
}
Any updates on this one? I had the same scenario, trying to log the response body within the logger middleware but the current design just doesn't support this. In the end, I needed to pull out my logging logic out of the middleware and wrap around HTTP client's execute method in order to invoke the .bytes() method.
I guess the culprit is that body streaming methods such as .bytes() are consuming the whole Response object (self) so it becomes unusable after it is invoked.
I am interested in adding a body trace to my OTLP span but that seems even more difficult as fn on_request_end ofReqwestOtelSpanBackend is not even async.
https://docs.rs/reqwest-tracing/latest/reqwest_tracing/
Did anyone find a workaround for this?
Hi, I managed to get body by consuming the response and rebuilding it back
Note: This code seems to work for me but I can't guarantee correctness in all scenarios
async fn collect_body(body: reqwest::Body) -> Result<Bytes, reqwest::Error> {
// impl from `reqwest::Response::bytes()`
// allows to collect without consuming whole response (w/ parts)
use http_body_util::BodyExt;
BodyExt::collect(body).await.map(|buf| buf.to_bytes())
}
fn insert_response_url_extension(parts: &mut http::response::Parts, resp_url: Url) {
// while https://github.com/seanmonstar/reqwest/pull/2798 is not merged
// do this hack/fix manually
use reqwest::ResponseBuilderExt;
let extensions_with_response_url = http::response::Builder::new()
.url(resp_url)
.extensions_ref()
.expect("New builder + infallible operation")
.clone();
parts.extensions.extend(extensions_with_response_url);
}
async fn clone_reqwest_response(resp: reqwest::Response) -> Result<(reqwest::Response, reqwest::Response), reqwest::Error> {
let resp_url = resp.url().clone();
let resp = http::Response::<reqwest::Body>::from(resp);
let (mut parts, body) = resp.into_parts();
// prevent losing response url while executing `reqwest::Response::from`
insert_response_url_extension(&mut parts, resp_url);
let b: Bytes = collect_body(body).await?;
let reqwest_1 = reqwest::Response::from(http::Response::from_parts(parts.clone(), b.clone()));
let reqwest_2 = reqwest::Response::from(http::Response::from_parts(parts, b));
Ok((reqwest_1, reqwest_2))
}
Also had to make insert_response_url_extension hack because response url is for some reason overridden from extensions when converting back to reqwest::Response and it's not fixed yet.
Depending on your needs you can avoid cloning the whole response, but this was my use case :)