4
votes

I am writing a web service with Rust 2018 Stable and Actix-Web. Using Reqwest, I am making an HTTP request to a diffent site from within one route handler function. Simplyfied it looks like this

extern crate reqwest;
use actix_web;
use reqwest::Url;

pub fn testing(req: actix_web::HttpRequest) -> actix_web::Result<actix_web::HttpResponse> {
    println!(">>> testing request begin");
    let url = Url::parse("https://example.com/").unwrap();
    println!(">>> testing url built");
    let req = reqwest::Client::new().post(url);
    println!(">>> testing req prepared");
    let res_struct = req.send();
    println!(">>> testing res_struct received");
    let res = res_struct.unwrap();
    println!(">>> testing res unwrapped");
    Ok(format!("done.").into())
}

That doesn't work, and I am getting the following error message (the error is printed 8 times, "worker:1" to "worker:8", despite calling the function only once):

thread 'actix-rt:worker:1' panicked at 'called `Result::unwrap()` 
on an `Err` value: Error(BlockingClientInFutureContext, 
"https://www.example.com/")', src/libcore/result.rs:999:5
Panic in Arbiter thread, shutting down system.

Google didn't find anything useful on "BlockingClientInFutureContext", but I am guessing it is somehow related to async/await or maybe Tokio's own futures?

Thanks for any pointers about what to read up on. Also, I am new to Rust.

The handler function is called from the Actix-Web HTttpServer:

HttpServer::new(|| App::new().service(
    web::resource("/testing").route(
        web::get().to(views::testing)
    )
)).bind("127.0.0.1:8001")?.run()
4
I would not think that tokio or async/await would call 'unwrap()'. In you code: .post(Url::parse(API_ENDPOINT).unwrap()), you are calling unwrap(), which will result in a panic if the unwrap() fails. Have you tried changing this to a call that does not use unwrap()?Gardener
Thanks for the idea, I updated the question. It panics when unwrapping the reqwest Response.C14L
I think it would help if you could submit a minimal reproducible example, with a main fn so that members can copy and paste your example into their IDE.Gardener
I updated the question with a full example. Almost sure this can be solved with actix_web::web::block() somehow.C14L

4 Answers

5
votes

I had a similar issue. The resolution for me was to lock the Reqwest crate version at 0.9.17 in your cargo file then rebuild.

reqwest = "=0.9.17"

It appears that newer version of Reqwest are broken with Actix-web unless you're using the async functionality on both. For reference: https://github.com/seanmonstar/reqwest/issues/541

3
votes

Turns out, actix_web::web::block() was the correct guess. Using it makes it possible to make blocking calls. block() returns a Future that resolves once the network request returns data. Very close to Promises in JS, plus the .from_err() in there.

pub fn testing(_req: actix_web::HttpRequest)
    -> impl Future<Item = HttpResponse, Error = Error>
{
    println!(">>> testing request begin");
    let url = Url::parse("https://example.com/").unwrap();
    println!(">>> testing url built");
    let req = reqwest::Client::new().get(url);
    println!(">>> testing req prepared");

    actix_web::web::block(move || {
        println!(">>> testing res received");
        req.send()
    })
    .from_err()
    .and_then(|res| {
        println!(">>> testing res: {:?}", &res);
        HttpResponse::Ok().content_type("text/html").body("Hello!")
    })
}

Additionally, in main.rs the route must be called using .to_async() instead of simply .to():

HttpServer::new(|| App::new().service(
    web::resource("/testing").route(
        web::get().to_async(views::testing)
    )
)).bind("127.0.0.1:8001")?.run()
1
votes

TL;DR: Upgrade to reqwest 0.9.22 or newer.

The error is indicating that you're attempting to make a blocking network call from inside an asynchronous context (Actix Web handlers are called asynchronously). This is not supported in reqwest versions 0.9.17 - 0.9.21.

As of 0.9.22, the author has removed this error in favor of a warning. For more information:

https://github.com/seanmonstar/reqwest/pull/670

https://github.com/seanmonstar/reqwest/issues/541

0
votes

The call to unwrap() is failing because an error is returned. It is best to avoid unwrap() in production code because it usually means we are trying to look at the 'desired' value (often called the 'happy path') while ignoring the error path.

This code works:

use actix_web;
use reqwest::Url;

fn main()  {
    println!(">>> testing request begin");
    let url = Url::parse("http:/example.com/").unwrap();
    println!(">>> testing url built");
    let req = reqwest::Client::new().post(url);
    println!(">>> testing req prepared");
    let res_struct = req.send();
    println!(">>> testing res_struct received");
    match res_struct {
        Ok(r)=> println!("response: {:?}", r),
        Err(e)=> println!("error: {}", e),
    }
//    let res = res_struct.unwrap();
    println!("done.");
}

The output is:

Finished dev [unoptimized + debuginfo] target(s) in 2.63s
     Running `target/debug/untitled`
>>> testing request begin
>>> testing url built
>>> testing req prepared
>>> testing res_struct received
error: http://example.com/: error trying to connect: failed to lookup address information: nodename nor servname provided, or not known
>>> testing res unwrapped
done.

The above code works without a panic, but the server at example.com is not providing a good response. If I re-run this using a valid URL, e.g. https://cisco.com, I get no errors:

>>> testing request begin
>>> testing url built
>>> testing req prepared
>>> testing res_struct received
response: Response { url: "https://www.cisco.com/", status: 200, headers: {"server": "Apache", "etag": "\"1732e-59058880c8465\"", "accept-ranges": "bytes", "strict-transport-security": "max-age=31536000", "cdchost": "wemxweb-publish-prod2-02", "x-xss-protection": "1; mode=block", "x-test-debug": "nURL=www.cisco.com,realm=0,isRealm=0,realmDomain=0,shortrealm=0", "content-security-policy": "upgrade-insecure-requests; frame-ancestors *.cisco.com *.jasper.com *.ciscospark.com *.ciscolive.com  http://cisco.lookbookhq.com https://cisco.lookbookhq.com testcisco.marketing.adobe.com cisco.marketing.adobe.com ciscosales.my.salesforce.com test.salesforce.com zedo.com hindustantimes.com economictimes.indiatimes.com *.webex.com *.cdw.com *.cdwg.com *.cdw.ca *.meraki-go.com http://ciscopartners.lookbookhq.com https://ciscopartners.lookbookhq.com ciscolearningsystem.com ciscocustomer.lookbookhq.com cisco.lookbookhq.com;", "content-type": "text/html", "expires": "Sun, 18 Aug 2019 12:10:23 GMT", "cache-control": "max-age=0, no-cache, no-store", "pragma": "no-cache", "date": "Sun, 18 Aug 2019 12:10:23 GMT", "connection": "keep-alive", "vary": "Accept-Encoding"} }
>>> testing res unwrapped
done.