总是返回 Ok HttpResponse 然后在 actix-web 处理程序中工作

Posted

技术标签:

【中文标题】总是返回 Ok HttpResponse 然后在 actix-web 处理程序中工作【英文标题】:Always return Ok HttpResponse then do work in actix-web handler 【发布时间】:2020-08-22 07:36:50 【问题描述】:

我有一个处理程序来启动密码重置。它总是返回一个成功的 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(),
       

【问题讨论】:

【参考方案1】:

您可以使用 Actix Arbiter 来安排异步任务:

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


如果您的数据库代码阻塞,为了防止占用长期存在的 Actix 工作线程,您可以创建一个新的 Arbiter,并使用它自己的线程:

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

这可能需要更多的工作,因为Poolredis::Client 在线程之间共享不太可能是安全的,所以你也必须解决这个问题。这就是我没有将它们包含在示例代码中的原因。

最好使用Arbiters,而不是尝试使用std::thread 生成新的本机线程。如果您将两者混合使用,您最终可能会意外地包含使工作人员混乱的代码。例如,在 async 上下文中使用 std::thread::sleep 会暂停恰好安排在同一工作人员上的不相关任务,甚至可能对您预期的任务没有任何影响。


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

【讨论】:

以上是关于总是返回 Ok HttpResponse 然后在 actix-web 处理程序中工作的主要内容,如果未能解决你的问题,请参考以下文章

Django基础必会套装

URLDownloadToFile 总是返回 S_OK

View 没有返回 HttpResponse 对象。它返回 None 而不是

“视图没有返回 HttpResponse 对象。而是返回了 None。”

ValueError - 视图没有返回 HttpResponse 对象。它返回 None 而不是

在调用不同 API 的 APIController 方法中返回 HttpResponse