asynchronous - 始终返回 Ok HttpResponse 然后在 actix-web 处理程序中工作

标签 asynchronous rust rust-diesel actix-web

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

在后台处理请求时,如何立即返回 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(),
    }   
}

最佳答案

您可以使用 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 不太可能在线程之间安全共享,因此您也必须解决这个问题。这就是为什么我没有将它们包含在示例代码中。

最好使用Arbiter,而不是试图使用std::thread 生成新的 native 线程。如果将两者混合使用,最终可能会意外地包含使工作人员陷入困惑的代码。例如,在异步上下文中使用 std::thread::sleep 会暂停恰好在同一个工作线程上调度的不相关任务,甚至可能不会产生任何效果关于您想要的任务。


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

关于asynchronous - 始终返回 Ok HttpResponse 然后在 actix-web 处理程序中工作,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61650471/

相关文章:

casting - 将 Vec<u32> 就地转换为 Vec<u8> 且开销最小

postgresql - 带有 Postgres 的 Rust 柴油机库中的时间戳

rust - 为柴油模型定义结构时出现错误未定义类型或模块

iphone - iPhone/iOS 异步多人游戏框架?

swift - CLGeocoder geocodeAddressString() 达到极限

rust - 如何在实现特征时对类型施加特征约束?

postgresql - 使用 Rust Diesel 插入记录时处理 ID 的最佳方法是什么

安卓 : Retrofit Callback success and failure executed asynchronously?

c# - 使用异步绑定(bind)时如何避免闪烁

error-handling - 如何使用自定义结构实现错误