C14L
C14L

Reputation: 12548

Error "BlockingClientInFutureContext" when trying to make a request from within an actix-web route handler function

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()

Upvotes: 4

Views: 973

Answers (4)

B. Reynolds
B. Reynolds

Reputation: 21

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

Upvotes: 1

LoganSmith
LoganSmith

Reputation: 116

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

Upvotes: 5

C14L
C14L

Reputation: 12548

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()

Upvotes: 3

Gardener
Gardener

Reputation: 2660

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.

Upvotes: 0

Related Questions