将actix_web响应主体提取为字符串的正确方法是什么?

时间:2019-01-07 01:07:37

标签: rust actor rust-actix

我正在尝试使用actix_web来获取和显示网页的内容。 HTTP请求成功完成,我可以查看网页,但是我想将正文读入String进行打印。

我尝试了let my_ip: String = response.body().into();,但收到一条错误消息

error[E0277]: the trait bound `std::string::String: std::convert::From<actix_web::httpmessage::MessageBody<actix_web::client::response::ClientResponse>>` is not satisfied
  --> src/main.rs:16:53
   |
16 |                 let my_ip: String = response.body().into();
   |                                                     ^^^^ the trait `std::convert::From<actix_web::httpmessage::MessageBody<actix_web::client::response::ClientResponse>>` is not implemented for `std::string::String`
   |
   = help: the following implementations were found:
             <std::string::String as std::convert::From<&'a str>>
             <std::string::String as std::convert::From<std::borrow::Cow<'a, str>>>
             <std::string::String as std::convert::From<std::boxed::Box<str>>>
             <std::string::String as std::convert::From<trust_dns_proto::error::ProtoError>>
   = note: required because of the requirements on the impl of `std::convert::Into<std::string::String>` for `actix_web::httpmessage::MessageBody<actix_web::client::response::ClientResponse>`

这是我到目前为止所拥有的:

use actix;
use actix_web::{client, HttpMessage};
use futures::future::Future;

fn main() {
    actix::run(|| {
        client::get("http://ipv4.canhasip.com/")
            .header("User-Agent", "Actix-web")
            .finish()
            .unwrap()
            .send()
            .map_err(|_| ())
            .and_then(|response| {
                println!("Response: {:?}", response);
                // error occurs here
                let my_ip: String = response.body().into();
                Ok(())
            })
    });
}

相关的依赖版本:

[dependencies]
actix-web = "0.7"
actix = "0.7"
futures = "0.1"

3 个答案:

答案 0 :(得分:3)

In order to keep the response object you have while also extracting the body, we're going to take advantage of the fact that, unlike a few other frameworks, you can extract the body without destructuring the entire object. Code is below:

actix::run(|| {

    client::get("http://localhost/")
        .header("User-Agent", "Actix-web")
        .finish()
        .unwrap()
        .send()
        .map_err(|e| {
          Error::new(ErrorKind::AddrInUse, "Request error", e)
        })
        .and_then(|response| {
          println!("Got response");
          response.body().map(move |body_out| {
            (response, body_out)
          }).map_err(|e| Error::new(ErrorKind::InvalidData, "Payload error", e))
        }).and_then(|(response, body)| {
          println!("Response: {:?}, Body: {:?}", response, body);
          Ok(())
      }).map_err(|_| ())
});

In order:

  • everything in there now uses std::io::Error for ease of use. Since all actix error types implement Error, it is also possible to preserve the original types
  • and_then() allows me to extract the body. When this is resolved, a map (with move making sure we're taking response with us) then returns a tuple of (response, body)
  • From there, you can freely use either the response or the body as you see fit

Do note that I replaced your hostname with localhost for testing purposes as ipv4.canhasip.com does not currently resolve to anything on the outside.


Initial answer:

You really should've provided more context on this one. actix has more than one request type.

Your initial object (response) is a ClientResponse. calling body() on it returns a MessageBody struct, which is the start of the rabbit hole you fell into. This is NOT the actual body, merely an object implementing the Future trait and which will, once it has run its course, yield what you are after.

You'll need to do this in a less hacky way, but for now and to convince yourself that this is the source of the issue, instead of your line of code, try this:

println!("{:?}", response.body().wait())

Calling wait() on a future blocks the current thread, which is why I am saying it is a temporary, hacky way to show you where the issue is. Depending on what you have at your disposal (if you have an executor-like object somewhere, or are able to return a future for execution), your actual solution will differ.

答案 1 :(得分:1)

Complementing Sébastien's answer, you can also resolve the MessageBody future:

.and_then(|response| {
    response.body().map_err(|_| ()).and_then(|bytes| {
        println!("{:?}", bytes);
        Ok(())
    })
})

答案 2 :(得分:1)

actix::run(|| {
    client::get("http://ipv4.canhasip.com/")
        .header("User-Agent", "Actix-web")
        .finish()
        .unwrap()
        .send()
        .map_err(drop)
        .and_then(|response| response.body().map_err(drop))
        .map(|body| body.to_vec())
        .map(|body| String::from_utf8(body).unwrap())
        .map(drop) // Do *something* with the string, presumably
});

send的结果为SendRequest。这是一个可以解决ClientResponse的未来。 ClientResponse实现了HttpMessage,该方法具有 HttpMessage::body。这将返回一个解析为Bytes的未来。可以通过常用的Rust方法将其转换为String

另请参阅: