1

Edit 25.07.2018: As Pawnesh Kumar said in the answer, this seems to be a browser issue. If I hit the button multiple times in Firefox the below script will only send one POST request, but if I do the same in Chrome I get a POST request for each click.

However, I can replicate the problem in the video at 01:00. This means when I install Laravel with Authentication, then if I click the submit button in the login form twice, Firefox will send 2 requests.

Why is Firefox sometimes sending multiple POST request and sometimes only one, when clicking multiple times on the button?


I have a user table

id | name
 1 | John

where the field id is a primary, integer, auto-incremet key. When I submit a dummy form that only has one button, then this will insert a new record with name John. Now this is what I observed:

  • If I submit the form once, go back in the browser submit it again, then I find two new rows in the DB.

  • If I submit the form by clicking twice (or twenty times) on the Add button, then there is only a single new row in the DB.

Why is that? I would expect that if I hit the submit button multiple times, then the form will send multiple requests - and inserts multiple rows.

Thats my form:

<form action="/test.php" method="POST">
  <input type="submit" value="Add">
</form>

which submits to test.php:

<?php
$servername = "localhost";
$username = "adam";
$password = "password";
$dbname = "test-db";

$conn = new mysqli($servername, $username, $password, $dbname);       
$sql = "INSERT INTO user (name) VALUES ('John')";
if ($conn->query($sql) === TRUE) {
    echo "New record created successfully";
}     
$conn->close();

sleep(4);

Because of the sleep part, I can click on the Add button multiple times in a row. However, no matter how often I click the Add button while loading, there is only one new row in the DB.

In my access.log file I also find only one GET and POST request after clicking the button twenty times:

2001:****:****:4400:****:****:****:**** - - [25/Jul/2018:11:30:03 +0200] "GET /test/form.php HTTP/1.1" 200 301 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:57.0) Gecko/20100101 Firefox/57.0" "*******.net"

2001:****:****:4400:****:****:***:**** - - [25/Jul/2018:11:30:34 +0200] "POST /test/test.php HTTP/1.1" 200 31 "http://********.net/test/form.php" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:57.0) Gecko/20100101 Firefox/57.0" "********.net"


Remark:

I have read about techniques to prevent multiple submissions either in the backend or frontend. Thus I think it should be possible to submit the form multiple times by hitting the button multiple times.

I also read in the wiki article Post/Redirect/Get that this pattern can not prevent if a user sumbits a form multiple times too quick:

If a web user refreshes before the initial submission has completed because of server lag, resulting in a duplicate POST request in certain user agents.

Also in this video at 1:00 someone double clicks on a button and gets an error because he submitted twice.

Adam
  • 25,960
  • 22
  • 158
  • 247
  • so, you have dig pretty deep, just want to be sure, does `{{ csrf_field() }}` actually changed on page reload? – Bagus Tesa Jul 22 '18 at 02:57
  • @Eakethet I edited the question, hope its more clear now – Adam Jul 23 '18 at 12:56
  • 2
    @Adam much more, great. Dunno how in firefox, but it should be same as in chrome. Open network monitor and look how events are fired, when you insanely clicking twenty times on submit button. The event gets cancelled and it takes only the last event. Clicking many times would work with - 1) lag, 2) ajax form submission – Eakethet Jul 23 '18 at 13:00
  • @Eakethet if you hit insanely the submit button you say only the last event is taken. But if you consider the video I mentioned at the remarks then you find that the user clicked at 1:00 two times the submit button and got an error - this was because the first post refreshed the token in the session and on the second submit the token wasn't the same as the token from the hidden field in the form. So in this case, the first post wasn't canceled. – Adam Jul 23 '18 at 13:59
  • Which version of PHP are you using? – delboy1978uk Jul 23 '18 at 14:43
  • @delboy1978uk PHP 7.2.5 – Adam Jul 23 '18 at 15:35
  • @Adam havent seen video, he can use ajax submit, so this will happen... – Eakethet Jul 23 '18 at 16:18
  • @Eakethet its without ajax, I have installed Laravel and can reproduce this. – Adam Jul 23 '18 at 16:35

4 Answers4

3

Note: I'm no expert, so I might be wrong. This is just my opinion.

To me, this seems like a browser quirk. It seems like Firefox intentionally ignores all the extra requests.

Chrome

In case of Chrome, if you click on the add button multiple times, multiple requests are sent to the server. Initially, all the extra requests are canceled (you can learn more about canceled here) since the server is not responding. However, once the server responds (i-e, once the sleep time is up), all the requests are processed immediately. I don't know why, but the server only sleeps for the first request. The rest of the requests are processed immediately.

Chrome

Edge

Edge also sends multiple requests if you click the button multiple times.

Edge

Firefox

Firefox is a little weird. It just sends one request no matter how many times you click the button. All the extra requests are ignored.

Firefox

Wololo
  • 841
  • 8
  • 20
  • Yes but even Firefox can send multiple post request on multiple clicks. This is the missing part that I don't understand. – Adam Jul 25 '18 at 20:26
1
  1. How do you know that your form was not submitted twice or more times?

The fact, that you do not have multiply rows in database doesn't proof that. Maybe column "name" is unique in your table?

  1. Check logs of your webserver. Example for apache: sudo cat /var/log/apache2/access.log

There you will find info of how many request you recieved.

YanDatsiuk
  • 1,885
  • 2
  • 18
  • 30
  • Thank you for mentioning the access.log! I checked it and it confirm that there was only a single request (See edited question). The name filed of the DB is not unique. Before analyzing the access.log I belived that there was only a single request, because when I submitted the form, go back, and submit again, I found two rows in the DB (See point 1. in my observations from my question). – Adam Jul 25 '18 at 09:38
1

It is a browser thing.

I believe you are talking about the situation when a user clicks multiple times on the button. While in the background the page start reloading.

When we hit the submit button browser send the request to server. Until the response receive some browser show the old(expired) page while some simple clean the view.

In Firefox, it won't fire any event in spite of clicking many times. Wherein Chrome it submit the request to the server every time user click.

So, the bottom line it is the way the browser handles things. Your logs also showing you were using Firefox so that the case.

Pawnesh Kumar
  • 484
  • 2
  • 10
  • I don't think its a browser thing. Because on some scripts multiple requests are send when I click the button multiple times. If I install for instance a Laravel Application and try to login with clicking the login button twice as in `https://www.youtube.com/watch?v=gJRv2ahMzEg` at 01:00, then I get the same error message. Thus in the Laravel example clicking the button twice sends two request, while in my small example above it does not. I don't know what it is, but it can't be only because of the browser. – Adam Jul 25 '18 at 10:22
  • Okay I tested it with Chrome and your right. In Chrome it sends a POST request for each click with my sample script above. But why can I reproduce the problem with the double submit from the video in firefox? – Adam Jul 25 '18 at 10:32
  • 1
    I did not find that in Firefox. I did exactly as in the video but not that error instead I get a normal validation error in the form field. – Pawnesh Kumar Jul 25 '18 at 16:46
  • You need to register an email with name and passwort first (at the tab register). Then use the same email and password at the login form. A double click on login button should give you the error in Firefox. – Adam Jul 25 '18 at 17:27
  • 2
    I found the solution! In Laravel the redirect to the home section. This means when you press the button the first time a `POST` request is send directly followed up by a `GET` request. Now if you press the button again, Firefox accepts the next `POST` request because inbetween the last `POST` was a get. WHAT A RELIVE TO UNDERSTAND THAT. Thank you! – Adam Jul 26 '18 at 01:00
0

It might be because you lock a file exclusively LOCK_EX with flock and then try to write to it with file_put_contents which under the hood opens a new pointer with fopen, and since you already locked the file exclusively it can't write to your file.

Try replacing the file_put_contents with fwrite($fp, $current, strlen($current)), and add an append flag to your fopen like this fopen($file, a+)

Does this reproduce the issue of multiple writes?

UPDATE

The token exception mismatch from the video you provided happens because of the Laravel's CSRF protection.

Every form in Laravel needs to have a {{ csrf_field() }} or you need to provide the crsf_token in the Header for AJAX requests.

That csrf_token in the form's hidden field has that same value stored in the Laravel's user session. When you double click submit fast on a form, the first request is sent with the first csrf_token, when it hits Laravel the token sent in the form request and the token stored in the session are compared, if they are equal the request passes, and a new csrf_token is generated in the session.

Since you did not reload the view the new token couldn't be rendered in the hidden form field, so by the time the second request hits we have a new csrf_token in the session and the old one in the second form request and thus the exception is thrown.

UPDATE 2

As I said it was the flock, try this code and the multiple submit issue will work

<?php

    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
       $file = 'file.txt';
       $fp = fopen($file, 'a+');

       if (flock($fp, LOCK_EX)) {
           fwrite($fp, "+", 1);
           flock($fp, LOCK_UN);
       }
       sleep(2);
    }
 ?>
<!DOCTYPE html>
<html lang="en">
    <head>

    </head>
    <body>
        <form action="/" method="POST">
            <button type="submit">Submit</button>
        </form>
    </body>
</html>
Sasa Blagojevic
  • 2,110
  • 17
  • 22
  • You are right, `file_put_contents` creates another lock. But this is not the issue here. If that would be a problem, then it would not be possible to write anything to the fine in the first place. But as I said in the remarks - the issue also holds if I "just" insert a row in DB or send a mail. It has nothing to do with the lock. – Adam Jul 23 '18 at 18:30
  • Wait acutally `file_put_content` does not create a LOCK by default: http://php.net/manual/de/function.file-put-contents.php – Adam Jul 23 '18 at 18:43
  • But you have already locked the file `flock($fp, LOCK_EX)` before you call the `file_put_contents`, and that's why you can't write to the file, an exclusive lock exists, `file_put_contents` opens another pointer in the background and can't write to it because you already locked it a line above. – Sasa Blagojevic Jul 23 '18 at 19:08
  • First of all I really appreciate that you try to help me here so much. I actually wish that you were right because I really want to know whats going on here. However, I think you missed some things about flock. You can use `fwrite` or `fopen` or `file_put_content` always - independently if a file is locked or not. flock is only an **advisory locking**. If your argument would be correct, then it would not be possible at all to write something to the file with my script, but clearly it is. – Adam Jul 23 '18 at 23:56
  • I tried your script from **Update 2** and if I click the submit button multiple times, there is still only one `+` added to the file. I will change my question once again this time with a database, so the `flock` thing will stop confusing people. Still, thanks for your help! – Adam Jul 23 '18 at 23:56
  • In my example, multiple "+" signs were added to the file and I was able to recreate what you are describing with your database example. So I'm not quite sure what you are doing? – Sasa Blagojevic Jul 24 '18 at 11:44
  • I think its best to discuss this at https://chat.stackoverflow.com/rooms/176653/why-is-this-form-not-submitted-twice – Adam Jul 24 '18 at 13:43
  • I have created a video at https://youtu.be/OSe3qCt1Rgw where I used your script, clicked a couple of times on the submit button but only a single `+` was added to the file. I only changed the sleep duration from 2 to 5 and the action url. – Adam Jul 25 '18 at 10:13