Asynchronous 始终返回Ok HttpResponse,然后在actix web处理程序中执行操作

Asynchronous 始终返回Ok HttpResponse,然后在actix web处理程序中执行操作,asynchronous,rust,rust-diesel,actix-web,Asynchronous,Rust,Rust Diesel,Actix Web,我有一个处理程序来启动密码重置。它总是返回一个成功的200状态代码,因此攻击者无法使用该代码找出数据库中存储了哪些电子邮件地址。问题是,如果数据库中有电子邮件,则需要一段时间才能完成请求(阻止用户查找并使用重置令牌发送实际电子邮件)。如果用户不在数据库中,请求会很快返回,因此被攻击的用户会知道电子邮件不在数据库中 在后台处理请求时,如何立即返回HTTP响应 pub async fn forgot_password_handler( email_from_path: web::Path&l

我有一个处理程序来启动密码重置。它总是返回一个成功的200状态代码,因此攻击者无法使用该代码找出数据库中存储了哪些电子邮件地址。问题是,如果数据库中有电子邮件,则需要一段时间才能完成请求(阻止用户查找并使用重置令牌发送实际电子邮件)。如果用户不在数据库中,请求会很快返回,因此被攻击的用户会知道电子邮件不在数据库中

在后台处理请求时,如何立即返回HTTP响应

pub async fn forgot_password_handler(
    email_from_path: web::Path<String>,
    pool: web::Data<Pool>,
    redis_client: web::Data<redis::Client>,
) -> HttpResponse {
    let conn: &PgConnection = &pool.get().unwrap();
    let email_address = &email_from_path.into_inner();
    // search for user with email address in users table
    match users.filter(email.eq(email_address)).first::<User>(conn) {
        Ok(user) => {
            // some stuff omitted.. this is what happens:
            // create random token for user and store a hash of it in redis (it'll expire after some time)
            // send email with password reset link and token (not hashed) to client
            // then return with 
            HttpResponse::Ok().finish(),
        }
        _ => HttpResponse::Ok().finish(),
    }   
}
pub异步fn忘记密码\u处理程序(
来自以下路径的电子邮件路径:web::path,
池:web::Data,
redis_客户端:web::Data,
)->HttpResponse{
let conn:&PgConnection=&pool.get().unwrap();
让email_address=&将_从_path.发送到_inner();
//在用户表中搜索具有电子邮件地址的用户
匹配users.filter(email.eq(email_地址))。第一个::(conn){
Ok(用户)=>{
//有些东西遗漏了……事情就是这样发生的:
//为用户创建随机令牌并将其散列存储在redis中(过一段时间就会过期)
//向客户端发送带有密码重置链接和令牌(未散列)的电子邮件
//然后带着
HttpResponse::Ok().finish(),
}
_=>HttpResponse::Ok().finish(),
}   
}

您可以使用Actix
仲裁器来安排异步任务:

use actix::Arbiter;

async fn do_the_database_stuff(
    email: String,
    pool: web::Data<Pool>,
    redis_client: web::Data<redis::Client>)
{
    // async database code here
}

pub async fn forgot_password_handler(
    email_from_path: web::Path<String>,
    pool: web::Data<Pool>,
    redis_client: web::Data<redis::Client>,
) -> HttpResponse {

    let email = email_from_path.clone();
    Arbiter::spawn(async {
        do_the_database_stuff(
            email,
            pool,
            redis_client
        );
    });

    HttpResponse::Ok().finish()
}
这可能需要更多的工作,因为
Pool
redis::Client
在线程之间共享不太安全,所以您也必须解决这个问题。这就是为什么我没有在示例代码中包含它们

最好使用
Arbiter
s,而不要试图用
std::thread
生成一个新的本机线程。如果混合使用这两种方法,可能会意外地包含使工作人员出错的代码。例如,在
async
上下文中使用
std::thread::sleep
,将暂停恰好安排在同一工作进程上的无关任务,甚至可能不会对您想要的任务产生任何影响


最后,您也可以考虑架构更改。如果您将数据库繁重的任务考虑到它们自己的微服务中,您将自动解决这个问题。然后,web处理程序可以只发送一条消息(Kafka、RabbitMQ、ZMQ、HTTP或您选择的任何内容)并立即返回。这将允许您独立于web服务器扩展微服务-如果密码重置服务只需要一个实例,那么10x web服务器实例并不一定意味着10x数据库连接

fn do_the_database_stuff(email: String) {
    // blocking database code here
}

pub async fn forgot_password_handler(email_from_path: String) -> HttpResponse {
    let email = email_from_path.clone();
    Arbiter::new().exec_fn(move || { 
        async move {               
            do_the_database_stuff(email).await; 
        };
    });

    HttpResponse::Ok().finish()
}