Read hyper::Body without modification to it

It is a reverse proxy and I want to read received hyper::Body without modication to it. because the body will be consumed by others later

The following code works.

async fn parse_uid(req: &mut Request<Body>) -> Result<String, hyper::Error> {
 if let Some(header_value) = req.headers().get(header::CONTENT_TYPE) {
    if let Ok(content_type) = header_value.to_str() {
        if content_type.eq_ignore_ascii_case("application/x-www-form-urlencoded") {

            let buffer : bytes::Bytes;
            let params : HashMap<String, String>;
            {
                let whole_body = hyper::body::aggregate(req.body_mut()).await?;
                buffer = bytes::Bytes::copy_from_slice(whole_body.bytes());
                params = form_urlencoded::parse(&buffer)
                    .into_owned()
                    .map(|mut p| { p.0.make_ascii_lowercase(); p } )
                    .collect::<HashMap<String, String>>();
            }
            let (mut tx, body) = Body::channel();
            *req.body_mut() = body;// reset body
            tx.send_data(buffer).await?;

            if let Some(uid) = params.get("userid") {
                if uid.len() > 0 {
                    return Ok(uid.to_string());
                }
            }
        }
    }
}

Ok("".to_string())

}

Is there some better way to optimize the code to avoid copy the body ?

Thank you

A Body is a stream of bytes, and the whole response is typically not loaded into memory at once. You must read the entire response into memory if you want to go through it twice.

Note that your tx.send_data(buffer).await? might deadlock as nothing is reading from the channel. To convert a Bytes into a Body, just do Body::from(buffer).

2 Likes

Thank you. Now is better.

let params : HashMap<String, String>;
let buffer : Bytes =
{
    let whole_body = hyper::body::aggregate(req.body_mut()).await?;
    Bytes::copy_from_slice(whole_body.bytes())
};
params = form_urlencoded::parse(&buffer)
    .into_owned()
    .map(|mut p| { p.0.make_ascii_lowercase(); p } )
    .collect::<HashMap<String, String>>();
*req.body_mut() = Body::from(buffer);

if let Some(uid) = params.get("userid") {
    if uid.len() > 0 {
        return Ok(uid.to_string());
    }
}

BTW, is it possible to avoid Bytes::copy_from_slice ? somehow if I can convert whole_body into bytes:Bytes

Your use of Bytes::copy_from_slice(whole_body.bytes()) is actually incorrect. From the documentation of Buf::bytes, see:

Note that this can return shorter slice (this allows non-continuous internal representation).

And the impl Buf returned by aggregate does in fact make use of this feature. I would do it like this:

use hyper::body::HttpBody;

let buffer: Bytes = {
    let mut body = req.body_mut();
    let mut buf = BytesMut::with_capacity(body.size_hint().lower() as usize);
    while let Some(chunk) = body.data().await {
        buf.extend_from_slice(&chunk?);
    }
    buf.freeze()
};

The extend_from_slice here cannot be avoided.

1 Like

Thanks again, @alice you let me learn a lot :heart:

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.