Always return Ok HttpResponse 然后在 actix-web 处理程序中工作
Always return Ok HttpResponse then do work in actix-web handler
我有一个处理程序来启动密码重置。它总是 returns 一个成功的 200 状态代码,这样攻击者就无法使用它来找出数据库中存储了哪些电子邮件地址。问题是,如果电子邮件在数据库中,则需要一段时间才能完成请求(阻止用户查找并发送带有重置令牌的实际电子邮件)。如果用户不在数据库中,请求 returns 非常快,因此受到攻击的人会知道电子邮件不存在。
如何在后台处理请求时立即返回 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()
}
这可能需要多做一些工作,因为 Pool
和 redis::Client
在线程之间共享不太可能是安全的,所以您也必须解决这个问题。这就是我没有将它们包含在示例代码中的原因。
最好使用 Arbiter
s 而不是试图用 std::thread
产生一个新的本机线程。如果将两者混用,最终可能会不小心包含使工作程序混乱的代码。例如,在 async
上下文中使用 std::thread::sleep
会暂停恰好安排在同一工作人员上的不相关任务,甚至可能不会对您想要的任务产生任何影响。
最后,您还可以考虑架构更改。如果将数据库繁重的任务分解为它们自己的微服务,您将自动解决此问题。然后 Web 处理程序可以立即发送一条消息(Kafka、RabbitMQ、ZMQ、HTTP 或任何您选择的消息)return。这将使您能够独立于 Web 服务器扩展微服务 - 如果密码重置服务只需要一个实例,那么 10 个 Web 服务器实例并不一定意味着 10 个数据库连接。
我有一个处理程序来启动密码重置。它总是 returns 一个成功的 200 状态代码,这样攻击者就无法使用它来找出数据库中存储了哪些电子邮件地址。问题是,如果电子邮件在数据库中,则需要一段时间才能完成请求(阻止用户查找并发送带有重置令牌的实际电子邮件)。如果用户不在数据库中,请求 returns 非常快,因此受到攻击的人会知道电子邮件不存在。
如何在后台处理请求时立即返回 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()
}
这可能需要多做一些工作,因为 Pool
和 redis::Client
在线程之间共享不太可能是安全的,所以您也必须解决这个问题。这就是我没有将它们包含在示例代码中的原因。
最好使用 Arbiter
s 而不是试图用 std::thread
产生一个新的本机线程。如果将两者混用,最终可能会不小心包含使工作程序混乱的代码。例如,在 async
上下文中使用 std::thread::sleep
会暂停恰好安排在同一工作人员上的不相关任务,甚至可能不会对您想要的任务产生任何影响。
最后,您还可以考虑架构更改。如果将数据库繁重的任务分解为它们自己的微服务,您将自动解决此问题。然后 Web 处理程序可以立即发送一条消息(Kafka、RabbitMQ、ZMQ、HTTP 或任何您选择的消息)return。这将使您能够独立于 Web 服务器扩展微服务 - 如果密码重置服务只需要一个实例,那么 10 个 Web 服务器实例并不一定意味着 10 个数据库连接。