Actix-web middleware accessing x-www-form-urlencoded submitted data

Hi,

Please help with the following issue: how to access x-www-form-urlencoded submitted data in actix-web middleware.

I'm certain the submitted data does come to the middleware, since the &ServiceRequest.request() shows the following:

HttpRequest HTTP/1.1 POST:/api/login
  headers:
    "cache-control": "max-age=0"
    "upgrade-insecure-requests": "1"
    "accept-encoding": "gzip, deflate, br"
    "content-type": "application/x-www-form-urlencoded"
    "content-length": "59"
    "sec-ch-ua-mobile": "?0"
    "sec-fetch-dest": "document"
    "sec-fetch-user": "?1"
    "sec-ch-ua": "\"Opera\";v=\"105\", \"Chromium\";v=\"119\", \"Not?A_Brand\";v=\"24\""
    "host": "localhost:5000"
    "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7"
    "sec-fetch-site": "same-origin"
    "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 OPR/105.0.0.0"
    "sec-ch-ua-platform": "\"Windows\""
    "sec-fetch-mode": "navigate"
    "referer": "http://localhost:5000/ui/login"
    "connection": "keep-alive"
    "origin": "http://localhost:5000"
    "accept-language": "en-GB,en-US;q=0.9,en;q=0.8"

The submitted data is: email=chirstian.koblick.10004@gmail.com;password=password. The length matches "content-length": "59" reported above.

I have no problem accessing this data in endpoint handler. I have just to pass in web::Form<Login>; where Login is:

#[derive(FromRow, Serialize, Deserialize, Debug)]
pub struct Login {
    pub email: String,
    pub password: String,
}

Thank you and best regards,

...behai.

Can you try this? I'm on the mobile, and it's impossible for me to verify if the code is correct:

fn call(&self, req: ServiceRequest) -> Self::Future {
  let form_data = req.extract::<Form<Login>>().await?;

Hi @moy2010,

Thank you very much for your prompt help. It seems this is something I am looking for. But, the compiler complains that:

error[E0728]: `await` is only allowed inside `async` functions and blocks
   --> src\redirect.rs:104:60
    |
102 | /     fn call(&self, request: ServiceRequest) -> Self::Future {
103 | |
104 | |         let res = request.extract::<Form<Login>>().await?;
    | |                                                    ^^^^^ only allowed inside `async` functions and blocks
105 | |
...   |
171 | |         }
172 | |     }
    | |_____- this is not `async`

The documentation for extract reads:

Derives a type from this request using an extractor.

Returns the T extractor’s Future type which can be awaited. This is particularly handy when you want to use an extractor in a middleware implementation.

I think I would need to implement FromRequest for Login.

I will try this.

Thank you.

...behai.

Ah, yes. Of course :laughing:. If you want to run async code within the call method, you would do it like this:

fn call(&self, req: ServiceRequest) -> Self::Future {
  Box::pin(async move {
    let form_data = req.extract::<Form<Login>>().await.expect("There was no login form data in the request");
    // Do something with form_data
2 Likes

Hi @moy2010,

Thank you for looking into this... I did try what you've suggested above yesterday.

We must change request to mut:

fn call(&self, mut request: ServiceRequest) -> Self::Future

It is happy with this:

        Box::pin(async move {
            let form_data = request.extract::<Form<EmployeeLogin>>().await.expect("There was no login form data in the request");
            // Do something with form_data
        });

But accessing request again after this call, results in the following error:

error[E0382]: use of moved value: `request`
  --> src\redirect.rs:68:37
   |
60 |       fn call(&self, mut request: ServiceRequest) -> Self::Future {
   |                      ----------- move occurs because `request` has type `ServiceRequest`, which does not implement the `Copy` trait
...
63 |           Box::pin(async move {
   |  __________________-
64 | |             let form_data = request.extract::<Form<Login>>().await.expect("There was no login form data in the request");
   | |                             ------- variable moved due to use in generator
65 | |             // Do something with form_data
66 | |         });
   | |_________- value moved here
67 |
68 |           let res = self.service.call(request);
   |                                       ^^^^^^^ value used here after move

I understand what E0382 means, but I don't know how to work this case.

-- I looked into this, too. But all examples I came across seem to to be about the https://docs.rs/actix-web/latest/actix_web/struct.HttpRequest.html#method.extensions, not for extracting data form HTML form submission.

Thank you and best regards,

...behai.

You will need to consume the payload and reconstruct the request. Check out this answer in SO that shows how to do it: rust - How to modify request data in actix-web middleware? - Stack Overflow.

1 Like

Dear @moy2010,

Thank you for all your helps -- you are very helpful, and you do point me to the right direction. My knowledge of Rust and actix-web are not up to speed to follow your suggestion as I have "new problems" ( ha ha ha ) pop up trying your suggestion.

There is actually an official actix-web middleware example on this issue, I had seen it before posting this help, it did not register and so I forgot it:

-- actix-web middle example | read_request_body.rs

I just copied it over to my project, and it just works. Its output is:

request body (middleware): b"email=chirstian.koblick.10004%40gmail.com&password=password"
response: HeaderMap { inner: {"access-control-allow-origin": Value { inner: ["http://localhost:5000"] }, "access-control-allow-credentials": Value { inner: ["true"] }, "vary": Value { inner: ["Origin, Access-Control-Request-Method, Access-Control-Request-Headers"] }} }

which is exactly what I look for. And the code, essentially, is what you have suggested.

Thank you very much for your helps. I do appreciate your times and your kindness.

Best regards,

...behai.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.