简体   繁体   English

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

[英]Always return Ok HttpResponse then do work in actix-web handler

I have a handler to initiate a password reset.我有一个处理程序来启动密码重置。 It always returns a successful 200 status code, so that an attacker cannot use it to find out which email addresses are stored in the database.它总是返回一个成功的 200 状态码,因此攻击者无法使用它来找出数据库中存储了哪些 email 地址。 The problem is, if an email is in the database, it'll take a while for the request to be fulfilled (blocking user lookup and sending the actual email with a reset token).问题是,如果 email 在数据库中,则需要一段时间才能完成请求(阻止用户查找并发送带有重置令牌的实际 email)。 If the user is not in the db, the request returns very quickly, so an attacked would know the email is not there.如果用户不在数据库中,请求会很快返回,因此被攻击者会知道 email 不存在。

How would I go about returning the HTTP response right away while processing the request in the background?在后台处理请求时,我将如何 go 关于立即返回 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(),
    }   
}

You can use an Actix Arbiter to schedule an asynchronous task:您可以使用 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()
}

If your database code is blocking, to prevent hogging the long-lived Actix worker threads, you could instead create a new Arbiter , with its own thread:如果您的数据库代码被阻塞,为了防止占用长期存在的 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()
}

This may be a bit more work because Pool and redis::Client are unlikely to be safe to share between threads, so you will have to solve that too.这可能需要更多的工作,因为Poolredis::Client在线程之间共享不太可能是安全的,所以你也必须解决这个问题。 That's why I didn't include them in the example code.这就是为什么我没有将它们包含在示例代码中。

It's better to use Arbiter s than be tempted to spawn a new native thread with std::thread .使用Arbiter比使用std::thread生成新的本地线程要好。 If you mix the two, you can end up accidentally including code that messes up the worker.如果您将两者混合使用,您最终可能会意外地包含使工作人员混乱的代码。 For example using std::thread::sleep in an async context would pause unrelated tasks that just happen to be scheduled on the same worker, and may not even have any effect on the task you intended.例如,在async上下文中使用std::thread::sleep会暂停恰好安排在同一个工作人员上的不相关任务,甚至可能对您想要的任务没有任何影响。


Finally, you might also consider an architectural change.最后,您还可以考虑架构更改。 If you factor database-heavy tasks into their own microservices, you would solve this problem automatically.如果您将数据库繁重的任务纳入他们自己的微服务中,您将自动解决此问题。 The web handler can then just send a message (Kafka, RabbitMQ, ZMQ, HTTP, or whatever you choose) and immediately return.然后,web 处理程序可以发送一条消息(Kafka、RabbitMQ、ZMQ、HTTP 或您选择的任何内容)并立即返回。 This will let you scale the microservices independently of the webserver - 10x web server instances doesn't have to mean 10x database connections, if you only need one instance for the password reset service.这将使您能够独立于 Web 服务器扩展微服务 - 如果您只需要一个用于密码重置服务的实例,那么 10 个 web 服务器实例不一定意味着 10 个数据库连接。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM