Rust actix-web线程不安全移动

3

我正在尝试使用actix-web 1.0编写一个HTTP端点。我将函数简化,只返回传递给它的用户,但编译器仍然会报错。

extern crate actix_web;
extern crate chrono;
extern crate futures;
extern crate listenfd;
#[macro_use]
extern crate serde_derive;
extern crate dotenv;
use actix_web::{error, web, App, Error, HttpResponse, HttpServer};
use futures::future::Future;

#[derive(Debug, Deserialize, Serialize)]
pub struct LoginUser {
    pub username: String,
    pub password: String,
}

pub fn login(
    login_user: web::Json<LoginUser>,
) -> impl Future<Item = HttpResponse, Error = error::BlockingError<Error>> {
    web::block(move || {
        let login_user = login_user.into_inner();
        let user = LoginUser {
            username: login_user.username,
            password: login_user.password,
        };
        Ok(HttpResponse::Ok().json(user))
    })
}

pub fn router(cfg: &mut web::ServiceConfig) {
    cfg.service(web::scope("/").service(web::resource("").route(web::get().to(login))));
}

fn main() -> std::io::Result<()> {
    HttpServer::new(move || App::new().configure(router))
        .bind("127.0.0.1:3000")?
        .run()
}

这是我的 cargo.toml 文件。
[package]
name = "log"
version = "0.1.0"
authors = ["me@example.com"
edition = "2018"

[dependencies]
actix-files = "~0.1"
actix-web = "~1.0"
chrono = { version = "0.4.6", features = ["serde"] }
listenfd = "0.3"
diesel = {version = "1.4.1", features = ["postgres", "uuid", "r2d2", "chrono"]}
dotenv = "0.13"
failure = "0.1"
futures = "0.1"
scrypt = "0.2.0"
serde_derive="1.0"
serde_json="1.0"
serde="1.0"

我遇到了编译错误

|     web::block(move || {
|     ^^^^^^^^^^ `(dyn std::any::Any + 'static)` cannot be sent between threads safely

我认为与web::block中使用login_user有关,但从错误信息中很难看出。在Rust或actix中,异步使用请求参数的首选方式是什么,如何保证安全?


好的,我会把它缩小到一个小型游乐场。 - CallMeNorm
1
有可能 Playground 没有你需要的 crates(对于 Actix 很确定是这样),这种情况下,你应该使用相同的技术在本地创建一个 MCVE,然后发布你的 main.rs 文件。谢谢! - Shepmaster
1个回答

7
首先,HttpResponse没有实现Send。由于web::block()在线程池上运行闭包函数,这是一个问题。因此,您需要从web::block返回一个Send类型的值,然后使用例如and_then()创建一个HttpResponse
其次,在路由器中,您正在使用web::get().to(login)。如果要调用返回Future的函数,则需要使用web::get().to_async(login)
第三,web::block中的闭包函数需要返回Result类型。由于您从未返回错误值,编译器无法推断错误类型。您需要给编译器一个提示。通常情况下,std::io::Error就可以了,因此可以返回Ok::<_, std::io::Error>(...value...)
第四,web::block返回一个BlockingError<E>。您可以使用from_err()将其映射为可返回的内容。
因此,通过以上所有修改,您的代码应如下所示:
pub fn login(
    login_user: web::Json<LoginUser>,
) -> impl Future<Item = HttpResponse, Error = Error> {
    web::block(move || {
        let login_user = login_user.into_inner();
        let user = LoginUser {
            username: login_user.username,
            password: login_user.password,
        };
        Ok::<_, std::io::Error>(user)
    })
        .from_err()
        .and_then(|user| HttpResponse::Ok().json(user))
}

pub fn router(cfg: &mut web::ServiceConfig) {
    cfg.service(web::scope("/").service(web::resource("").route(web::get().to_async(login))));
}

我也想到了这一点,但CallMeNorm说:“我已经简化了函数,使其只返回传递给它的用户”。因此,我认为在原始代码中有一些实际的阻塞代码。 - miquels
@miquels 谢谢你的回答。你是怎么从错误中找到答案的呢?对我来说,问题的一部分是错误信息“std::any::Any”不能在线程之间传递,这让我很难搜索。 - CallMeNorm
1
啊是的。Rust编译器通常在错误消息方面非常有帮助,但在某些情况下 - 特别是在处理futures时 - 它们可能会非常复杂。我从以前的经验中知道,Actix-Web的设计与纯Tokio略有不同。它能够将请求负载均衡到多个线程上,但然后一个请求会在分配给它的一个线程上执行 - 且几种Actix类型不是'send'。它们不必这样做。 - miquels

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接