actix-web::client - how to get body of response?

Created on 9 Oct 2018  路  13Comments  路  Source: actix/actix-web

My issue is that I cannot find a good example of an actix-web client that shows how to retrieve the body of the response.

The following doesn't display the body.

    pub fn start(request: &str) {

        actix::run(
            || actix_web::client::get(&request)   // <- Create request builder
                .header("User-Agent", "Actix-web")
                .finish().unwrap()
                .send()
                .conn_timeout(Duration::from_secs(10))// <- Send http request
                .map_err(|Error::from| ())
                .and_then(handle_response )
        );

    }


fn handle_response(response: ClientResponse) -> Result<(), ()> {

    let fn_name = "handle_response";
    println!("{}: Response: {:?}", fn_name, &response);

    let body = response.body();

        match body
            .limit(8048)
            .and_then(|bytes: Bytes| {  // <- complete body
                println!("==== BODY ==== {:?}", bytes);
                Ok(())
            }).poll() {
            Ok(_value) => {
                println!("value {:?}", _value);
            },
            Err(_err) => {
                println!("error {:?}", _err);
            }
        }

    Ok(())
}

What I get is the following:

handle_response: Response:

ClientResponse HTTP/1.1 200 OK

headers:
"content-type": "application/json; charset=\"UTF-8\""
"content-length": "3658"
value NotReady

That makes perfect sense, because when dealing with a socket, one would typically need to keep reading to get the next chunk of data. However, I thought that the .poll() would probably handle that.

However, I cannot find any examples on how to setup a future to be able to gather all of the chunks into the final response. Note that I posted on reddit 18 days ago and never received a response.

I suspect that the Response needs to be defined as a future, but I cannot seem to find a syntax that will work with the APIs that I am using.

I think I need to call something like the following, but how do I feed this to actix::run()? Do I have to convert it into an Actor message?

    pub fn _query(req: &str) -> Box<Future<Item=String, Error=Error>> {
        Box::new(
            client::get(&req)
                .finish().unwrap()
                .send()
                .map_err(Error::from)
                .and_then(
                    |resp|resp.body()
                        .from_err()
                        .and_then(|body| {
                            let resp_data = str::from_utf8(&body).unwrap();
                            println!("{}", resp_data);
                            future::ok(resp_data.to_string())
                        })
                )
        )
    }

Any help would be appreciated. A pointer to a comprehensive example would be useful.

Thanks,
-Dave

Most helpful comment

Im late to the party... But this works for me:

let mut response = client
        .get("https://www.url.com/api")
        .send()
        .await
        .unwrap()
        .body()
        .await;

Not sure if this is the best practice, but is my first time with Rust. And if just in case, the toml has to be:

actix-web = {version="2.0.0", features=["openssl"]}
openssl = "0.10.29"

If you are sending the request to a secure url

All 13 comments

Sorry, I guess I should have opened this here:
https://github.com/actix/examples/issues

Can it be moved, or do I need to close this and create a new one there?

@crusty-dave

I found some code on the internet. It works like this:

...
client.request(request).and_then(|response| {
    match response.status().as_u16() {
        200 => match response.into_body().concat2().wait() {
            Ok(data) => ...

I forgot the type of data :-D You might find it in futures crate's docs.

But I think wait() is not good in asynchronous context. Please do your research about that call. For me, I was too lazy, and is still using this technique in production code. :-(

@hkcorac

The syntax that you provided didn't work, the best I could come up with was the following, but I still don't get the body of the response:

fn _handle_response2(response: client::ClientResponse) -> Result<(), ()> {

    let fn_name = "handle_response";
    println!("{}: Response: {:?}", fn_name, &response);

    match response.status() {
        StatusCode::OK => {
            match response.body().wait() {
                Ok(body) => {
                    let _foo = body.to_vec();
                    println!("==== BODY ==== {:?}", _foo);
                    Ok(())
                },
                Err(_err) => {
                    println!("error {:?}", _err);
                    Err(())
                }
            }
        },
        _ => {
            println!("error {:?}", response.status());
            Err(())
        }
    }
}

It seems like the wait would need to be outside of the handler, but I couldn't get that to compile:

    pub fn start(request: &str) {

        actix::run(
            || client::get(&jims_request)   // <- Create request builder
                .header("User-Agent", "Actix-web")
                .finish().unwrap()
                .send()
                //.conn_timeout(Duration::from_secs(30))// <- Send http request
                .map_err(|_| () )
                .and_then(handle_response ).wait()
        );
    }

error[E0277]: the trait bound std::result::Result<(), ()>: futures::Future is not satisfied
--> src\srx_sim.rs:78:9
|
78 | actix::run(
| ^^^^^^^^^^ the trait futures::Future is not implemented for std::result::Result<(), ()>
|
= note: required by actix::run

The following is the version from Cargo.toml:

actix = "0.7.4"
actix-web = { version="0.7.7", features=["flate2-rust"], default-features = false }

Thanks for trying to help,
-Dave

Please learn a bit more about how to handle futures

response.body() returns future as you can see, but you cannot just wait on it.

Instead I'd suggest to do following:

and_then(|rsp| rsp.body()).and_then|body| { println!("Handle body"); Ok(()) }

The best way to work with futures is to change them

For further questions feel free to visit our gitter

@DoumanAsh I've tried to help because I faced the same problem like his before. Personally I think Issues section can be used for question like this. Again, it's just my opinion. It's your policy about Issues section, which I _personally_ think that it is too hard.

@hkcorac I'd like to encourage people to use gitter for questions and leave issues with bugs/enhancements

We may need to add example of various client usages in examples repo though

@crusty-dave I guess you might find the answer above. In my opinion, reddit is not a good place to ask for questions about coding snippets. Next time you might want to go straight to the project repository (and check the README file first).

My repository, before I dropped actix-web client might of use as example for client only code https://github.com/DoumanAsh/fie/tree/f6b6ef530a1594df242306451d82f637e5bd2b5d

Thanks @DoumanAsh , I understand. Your answer did help me too.

Actix is a good place to start learning about futures IMO,
For reference:
https://github.com/actix/examples/blob/master/http-proxy/src/main.rs
E.g.

use futures::future::lazy;
use actix_rt::System;
use actix_web::client::Client;

fn main() {
    System::new("test").block_on(lazy(|| {
        let client = Client::default();
        client.get("https://www.rust-lang.org") // <- Create request builder
            .header("User-Agent", "Actix-web")
            .send()                             // <- Send http request
            .map_err(|_| ())
            .and_then(|mut response| {          // <- server http response

                println!("Response: {:?}", response);

                response.body().and_then(move |bytes| {
                    let s = std::str::from_utf8(&bytes).expect("utf8 parse error)");
                    println!("html: {:?}", s);

                    // Ok(())
                    // Or return something else...
                    Ok(HttpResponse::Ok()
                        .content_type("text/html")
                        .body(format!("{}", s)))
                })
                .map_err(|_| ()) // <- Handle error properly
                // .wait()       // blocking...

            })
    }));
}

This worked for me:

  let mut res = client
    .get("http://example.com")
    .await?;

  let body = res.body().await?; // Bytes
  let utf8 = std::str::from_utf8(body.as_ref()).unwrap();  // .as_ref converts Bytes to [u8]

I believe it only works in my case because .await? (from res.body().await?) guarantees Bytes is the final value when .as_ref is called. I am a beginner with Rust but that's what I understood searching for a solution.

Im late to the party... But this works for me:

let mut response = client
        .get("https://www.url.com/api")
        .send()
        .await
        .unwrap()
        .body()
        .await;

Not sure if this is the best practice, but is my first time with Rust. And if just in case, the toml has to be:

actix-web = {version="2.0.0", features=["openssl"]}
openssl = "0.10.29"

If you are sending the request to a secure url

Was this page helpful?
0 / 5 - 0 ratings

Related issues

mcelkys picture mcelkys  路  4Comments

naturallymitchell picture naturallymitchell  路  4Comments

zhaobingss picture zhaobingss  路  4Comments

bedax picture bedax  路  3Comments

Dadibom picture Dadibom  路  4Comments