Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
crusty-dave opened this issue Oct 9, 2018 · 13 comments
Closed

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

crusty-dave opened this issue Oct 9, 2018 · 13 comments

Comments

@crusty-dave
Copy link

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

@crusty-dave
Copy link
Author

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?

@ghost
Copy link

ghost commented Oct 9, 2018

@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. :-(

@crusty-dave
Copy link
Author

@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

@DoumanAsh
Copy link
Contributor

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

@DoumanAsh
Copy link
Contributor

For further questions feel free to visit our gitter

@ghost
Copy link

ghost commented Oct 9, 2018

@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.

@DoumanAsh
Copy link
Contributor

@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

@ghost
Copy link

ghost commented Oct 9, 2018

@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).

@DoumanAsh
Copy link
Contributor

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

@ghost
Copy link

ghost commented Oct 9, 2018

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

@peitalin
Copy link

peitalin commented May 31, 2019

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...

            })
    }));
}

@http-teapot
Copy link

http-teapot commented Jan 28, 2020

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.

@dfrojas
Copy link

dfrojas commented Jun 30, 2020

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants