Ошибка BlockingClientInFutureContext при попытке сделать запрос из функции обработчика маршрута actix-web

Я пишу веб-сервис на Rust 2018 Stable и Actix-Web. Используя Reqwest, я выполняю HTTP-запрос к другому сайту из одного обработчика маршрута. Simplyfied это выглядит так

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

Это не работает, и я получаю следующее сообщение об ошибке (ошибка печатается 8 раз, от «worker: 1» до «worker: 8», несмотря на то, что функция вызывается только один раз):

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 не нашел ничего полезного в "BlockingClientInFutureContext", но я предполагаю, что это как-то связано с async / await или, может быть, с собственным фьючерсом Tokio?

Спасибо за любые указатели о том, что читать. Кроме того, я новичок в Rust.

Функция обработчика вызывается из Actix-Web HTttpServer:

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

person C14L    schedule 17.08.2019    source источник
comment
Я бы не подумал, что tokio или async / await вызовут unwrap (). В вашем коде: .post(Url::parse(API_ENDPOINT).unwrap()) вы вызываете unwrap (), что приведет к панике, если unwrap () завершится с ошибкой. Вы пробовали изменить это на вызов, который не использует unwrap ()?   -  person Gardener    schedule 18.08.2019
comment
Спасибо за идею, обновил вопрос. Паникает при разворачивании ответа reqwest.   -  person C14L    schedule 18.08.2019
comment
Я думаю, что было бы полезно, если бы вы могли отправить минимальный воспроизводимый пример с основным fn, чтобы участники могли копировать и вставьте свой пример в их IDE.   -  person Gardener    schedule 18.08.2019
comment
Я обновил вопрос полным примером. Почти уверен, что это можно как-то решить с помощью actix_web::web::block().   -  person C14L    schedule 18.08.2019


Ответы (4)


У меня была аналогичная проблема. Решением для меня было заблокировать версию ящика Reqwest на уровне 0.9.17 в вашем грузовом файле, а затем перестроить.

reqwest = "=0.9.17"

Похоже, что более новая версия Reqwest не работает с Actix-web, если вы не используете функцию async на обоих. Для справки: https://github.com/seanmonstar/reqwest/issues/541.

person LoganSmith    schedule 20.08.2019

Оказывается, actix_web::web::block() было правильным предположением. С его помощью можно совершать блокирующие звонки. block() возвращает Future, которое разрешается, когда сетевой запрос возвращает данные. Очень близко к Promises в JS, плюс там .from_err().

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!")
    })
}

Кроме того, в main.rs маршрут должен вызываться с использованием .to_async() вместо простого .to():

HttpServer::new(|| App::new().service(
    web::resource("/testing").route(
        web::get().to_async(views::testing)
    )
)).bind("127.0.0.1:8001")?.run()
person C14L    schedule 19.08.2019
comment
Спасибо, я почти потратил день на эту проблему, но это решение не работает с std :: future, не так ли? так как будущий ящик выдернули, я не знаю, как решить эту проблему - person Ejonas GGgg; 22.08.2019

TL; DR: обновитесь до reqwest 0.9.22 или новее.

Ошибка указывает на то, что вы пытаетесь выполнить блокирующий сетевой вызов из асинхронного контекста (веб-обработчики Actix вызываются асинхронно). Это не поддерживается в версиях reqwest 0.9.17 - 0.9.21.

Начиная с версии 0.9.22, автор удалил эту ошибку в пользу предупреждения. Для дополнительной информации:

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

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

person B. Reynolds    schedule 10.10.2019

Вызов unwrap() завершается ошибкой, поскольку возвращается ошибка. Лучше избегать unwrap() в производственном коде, потому что это обычно означает, что мы пытаемся посмотреть на «желаемое» значение (часто называемое «счастливым путем»), игнорируя путь ошибки.

Этот код работает:

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.");
}

Результат:

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.

Приведенный выше код работает без паники, но сервер по адресу example.com не дает хорошего ответа. Если я перезапущу это, используя действительный URL-адрес, например https://cisco.com, ошибок нет:

>>> 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.
person Gardener    schedule 18.08.2019
comment
Спасибо, но я все равно получаю ту же ошибку: error: https://cisco.com/: blocking Client used inside a Future context. Это выполняется в обработчике запросов, вызываемом из маршрутизатора Actix-Web, а не просто из обычной main() функции. - person C14L; 18.08.2019
comment
Думаю, ваш минимальный воспроизводимый пример нужно немного расширить :-). - person Gardener; 19.08.2019
comment
Я несколько раз упоминал, что это функция обработчика запросов Actix-Web. И я даже опубликовал, как настроить стандартный веб-маршрутизатор Actix на HttpServer. Как я могу сделать это более понятным? Именно по этой причине вопрос отмечен тегом Actix-Web. - person C14L; 19.08.2019