118 lines
4.2 KiB
Markdown
118 lines
4.2 KiB
Markdown
|
---
|
||
|
title: Solving `app_data` or `ReqData` missing in requests for actix-web
|
||
|
date: 2022-03-26
|
||
|
---
|
||
|
|
||
|
> This post is day 5 of me taking part in the
|
||
|
> [#100DaysToOffload](https://100daystooffload.com/) challenge.
|
||
|
|
||
|
I'm using `actix-web` to set up a web server, and I've been hitting a small
|
||
|
problem that I think other people may come across too.
|
||
|
|
||
|
To explain the problem, let me talk a bit about my setup. I have a custom
|
||
|
middleware that checks if a user is authorized to access a route. It looks like
|
||
|
this:
|
||
|
|
||
|
```rust
|
||
|
impl<S: 'static, B> Service<ServiceRequest> for CheckLoginMiddleware<S>
|
||
|
where
|
||
|
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
|
||
|
S::Future: 'static,
|
||
|
{
|
||
|
type Response = ServiceResponse<EitherBody<B>>;
|
||
|
type Error = Error;
|
||
|
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
|
||
|
|
||
|
dev::forward_ready!(service);
|
||
|
|
||
|
fn call(&self, req: ServiceRequest) -> Self::Future {
|
||
|
let state = self.state.clone();
|
||
|
let (request, payload) = req.into_parts();
|
||
|
let service = self.service.clone();
|
||
|
|
||
|
let user_token = get_token_from_header(&request);
|
||
|
let path_token = if self.allow_path_tokens {
|
||
|
get_token_from_query(&request)
|
||
|
} else {
|
||
|
None
|
||
|
};
|
||
|
|
||
|
Box::pin(async move {
|
||
|
match verify_auth(state, user_token, path_token, request.path()).await {
|
||
|
Ok(authorized) => {
|
||
|
tracing::debug!("Request authorized, inserting authorization token");
|
||
|
// This is the "important bit" where we insert the authorization token into the request data
|
||
|
request.extensions_mut().insert(authorized);
|
||
|
let service_request =
|
||
|
service.call(ServiceRequest::from_parts(request, payload));
|
||
|
service_request
|
||
|
.await
|
||
|
.map(ServiceResponse::map_into_left_body)
|
||
|
}
|
||
|
Err(err) => {
|
||
|
let response = HttpResponse::Unauthorized().json(err).map_into_right_body();
|
||
|
|
||
|
Ok(ServiceResponse::new(request, response))
|
||
|
}
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
The `verify_auth` function is omitted, but the gist of it is that it returns an `Result<Authorized, Error>`.
|
||
|
If the user is authorized, the authorization token `verify_auth` returned is then attached to the request.
|
||
|
|
||
|
Then here's how I use it in a path:
|
||
|
|
||
|
```rust
|
||
|
#[delete("/{store}/{path:.*}")]
|
||
|
async fn delete_storage(
|
||
|
params: web::Path<(String, String)>,
|
||
|
// This parameter is automatically filled with the token
|
||
|
authorized: Option<ReqData<Authorized>>,
|
||
|
) -> Result<HttpResponse, StorageError> {
|
||
|
let (store, path) = params.as_ref();
|
||
|
|
||
|
let mut store_path = get_authorized_path(&authorized, store)?;
|
||
|
store_path.push(path);
|
||
|
if fs::metadata(&store_path).await?.is_file() {
|
||
|
tracing::debug!("Deleting file {:?}", store_path);
|
||
|
fs::remove_file(&store_path).await?;
|
||
|
} else {
|
||
|
tracing::debug!("Deleting folder {:?}", store_path);
|
||
|
fs::remove_dir(&store_path).await?;
|
||
|
}
|
||
|
Ok(HttpResponse::Ok().finish())
|
||
|
}
|
||
|
```
|
||
|
|
||
|
This setup worked for this path, but would absolutely not work for another path.
|
||
|
I inserted logs to track everything, and just found that the middleware would
|
||
|
insert the token, but the path would just get `None`. How‽ I tried to slowly
|
||
|
strip everything away from the non-functional path until it was identical to
|
||
|
this one, but it still would not work.
|
||
|
|
||
|
Well it turns out the solution was very simple, see this:
|
||
|
|
||
|
```rust
|
||
|
use my_package::storage::put_storage;
|
||
|
use crate::storage::delete_storage;
|
||
|
```
|
||
|
|
||
|
Ah! They are imported differently. I had set up my program as both a library and
|
||
|
a program for various reasons. However, it turns out importing the same thing
|
||
|
from `crate` is different from importing it from the library. Because of the
|
||
|
difference in import, Actix doesn't recognize that the types match, so the route
|
||
|
can't access the attached token.
|
||
|
|
||
|
The solution is normalizing the imports. I went with going through the library
|
||
|
for everything, because that's what `rust-analyzer`s automatic import seems to
|
||
|
prefer.
|
||
|
|
||
|
```rust
|
||
|
use my_package::storage::{put_storage, delete_storage};
|
||
|
```
|
||
|
|
||
|
Solved!
|