为啥两次点击按钮时这个表单没有提交两次?

Posted

技术标签:

【中文标题】为啥两次点击按钮时这个表单没有提交两次?【英文标题】:Why is this form not submitted twice when hitting the button twice?为什么两次点击按钮时这个表单没有提交两次? 【发布时间】:2018-12-23 03:51:31 【问题描述】:

编辑 25.07.2018: 正如 Pawnesh Kumar 在回答中所说,这似乎是浏览器问题。如果我在 Firefox 中多次点击该按钮,下面的脚本只会发送一个 POST 请求,但如果我在 Chrome 中执行相同操作,则每次点击都会收到一个 POST 请求。

但是,我可以在 01:00 在video 中复制该问题。这意味着当我使用Authentication 安装 Laravel 时,如果我点击登录表单中的提交按钮两次,Firefox 将发送 2 个请求。

为什么在多次单击按钮时,Firefox 有时会发送多个 POST 请求,有时只发送一个?


我有一个用户

id | name
 1 | John

其中字段id 是一个主、整数、自动递增键。当我提交一个只有一个按钮的虚拟表单时,这将插入一条名为 John 的新记录。现在这是我观察到的:

如果我提交一次表单,返回浏览器再次提交,然后 我在数据库中发现了两个新行。

如果我通过在Add 上单击两次(或二十次)提交表单 按钮,那么数据库中只有一个新行。

这是为什么呢?我希望如果我多次点击提交按钮,那么表单将发送多个请求 - 并插入多行。

这是我的表格:

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

提交给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);

由于sleep 部分,我可以连续多次单击Add 按钮。但是,无论我在加载时单击Add 按钮的频率如何,数据库中都只有一个新行。

在我的 access.log 文件中,单击按钮二十次后,我还发现只有一个 GETPOST 请求:

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"


备注:

我已经阅读过有关防止多次提交的技术 在backend 或frontend 中。因此我认为应该可以通过多次点击按钮来多次提交表单。

我还在 wiki 文章 Post/Redirect/Get 中读到,如果用户过快地多次提交表单,这种模式无法阻止:

如果由于服务器延迟导致网络用户在初始提交完成之前刷新,导致某些用户代理中出现重复的 POST 请求。

同样在video 1:00 有人双击一个按钮并收到错误,因为他提交了两次。

【问题讨论】:

所以,您已经深入挖掘了,只是想确定一下, csrf_field() 是否真的在页面重新加载时发生了变化? @Eakethet 我编辑了这个问题,希望现在更清楚 @Adam 更多,太好了。不知道在 Firefox 中如何,但它应该与在 chrome 中相同。当您疯狂地点击提交按钮二十次时,打开网络监视器并查看事件是如何触发的。该事件被取消,它只需要最后一个事件。多次单击将适用于 - 1) 滞后,2) ajax 表单提交 @Eakethet 如果您疯狂地点击提交按钮,您会说只接受最后一个事件。但是,如果您考虑我在评论中提到的视频,那么您会发现用户在 1:00 单击了两次提交按钮并出现错误 - 这是因为第一个帖子刷新了会话中的令牌,第二个提交了令牌与表单中隐藏字段中的令牌不同。所以在这种情况下,第一个帖子没有被取消。 您使用的是哪个版本的 PHP? 【参考方案1】:

注意:我不是专家,所以我可能是错的。这只是我的看法。

对我来说,这似乎是浏览器的怪癖。 Firefox 似乎故意忽略了所有额外的请求。

对于 Chrome,如果您多次单击添加按钮,则会向服务器发送多个请求。最初,由于服务器没有响应,所有额外的请求都被取消(您可以了解更多关于 canceled here 的信息)。但是,一旦服务器响应(即,一旦睡眠时间结束),所有请求都会立即处理。我不知道为什么,但服务器只为第一个请求休眠。其余请求将立即处理。

边缘

如果您多次单击该按钮,Edge 也会发送多个请求。

火狐

Firefox 有点奇怪。无论您单击按钮多少次,它都只会发送一个请求。所有额外的请求都会被忽略。

【讨论】:

是的,但即使是 Firefox 也可以通过多次点击发送多个 post 请求。这是我不明白的缺失部分。【参考方案2】:

这是一个浏览器的事情。

我相信您说的是用户多次单击按钮时的情况。在后台页面开始重新加载。

当我们点击提交按钮时,浏览器将请求发送到服务器。直到响应收到一些浏览器显示旧的(过期)页面,同时一些简单的清理视图。

在 Firefox 中,即使点击了很多次,它也不会触发任何事件。其中Chrome每次用户点击时都会向服务器提交请求。

因此,最重要的是浏览器处理事物的方式。你的日志也显示了你使用 Firefox 这样的情况。

【讨论】:

我不认为它是浏览器的事情。因为在某些脚本上,当我多次单击该按钮时会发送多个请求。例如,如果我安装了一个 Laravel 应用程序并尝试在 01:00 像在https://www.youtube.com/watch?v=gJRv2ahMzEg 中那样单击登录按钮两次登录,那么我会收到相同的错误消息。因此,在 Laravel 示例中,单击按钮两次会发送两个请求,而在我上面的小示例中则不会。我不知道它是什么,但不可能只是因为浏览器。 好的,我用 Chrome 和你的权利测试了它。在 Chrome 中,它使用上面的示例脚本为每次点击发送一个 POST 请求。但是为什么我可以从Firefox中的视频中重现双重提交的问题? 我在 Firefox 中没有找到。我做的和视频中的完全一样,但不是那个错误,而是我在表单字段中得到一个正常的验证错误。 您需要先注册一个带有姓名和密码的电子邮件(在标签注册处)。然后在登录表单中使用相同的电子邮件和密码。双击登录按钮应该会给你 Firefox 中的错误。 我找到了解决方案!在 Laravel 中,重定向到 home 部分。这意味着当您第一次按下按钮时,会直接发送 POST 请求,然后是 GET 请求。现在,如果您再次按下该按钮,Firefox 将接受下一个 POST 请求,因为在最后一个 POST 之间是一个 get。什么是重温的理解。谢谢!【参考方案3】:
    您怎么知道您的表单没有提交两次或更多次?

您在数据库中没有多行这一事实并不能证明这一点。也许列“名称”在您的表中是唯一的?

    检查您的网络服务器的日志。 Apache 示例:sudo cat /var/log/apache2/access.log

在那里你会找到你收到多少请求的信息。

【讨论】:

感谢您提及 access.log!我检查了它并确认只有一个请求(请参阅编辑的问题)。数据库的名称不是唯一的。在分析 access.log 之前,我相信只有一个请求,因为当我提交表单时,返回并再次提交时,我在数据库中发现了两行(请参阅我对问题的观察中的第 1 点)。 【参考方案4】:

这可能是因为您使用flock 专门锁定了一个文件LOCK_EX,然后尝试使用file_put_contents 写入它,这在后台打开了一个带有fopen 的新指针,因为您已经锁定了该文件完全无法写入您的文件。

尝试将file_put_contents 替换为fwrite($fp, $current, strlen($current)),并在fopen 中添加一个附加标志,例如fopen($file, a+)

这会重现多次写入的问题吗?

更新

由于 Laravel 的 CSRF 保护,您提供的视频中的令牌异常不匹配发生。

Laravel 中的每个表单都需要有一个 csrf_field() ,或者您需要在 AJAX 请求的 Header 中提供 crsf_token

表单隐藏字段中的 csrf_token 与 Laravel 的用户会话中存储的值相同。当您在表单上双击快速提交时,第一个请求与第一个 csrf_token 一起发送,当它到达 Laravel 时,将比较表单请求中发送的令牌和会话中存储的令牌,如果它们相等则请求通过,并在会话中生成一个新的csrf_token

由于您没有重新加载视图,因此新令牌无法在隐藏表单字段中呈现,因此当第二个请求命中时,我们在会话中有一个新的 csrf_token,而在第二个表单中是旧的请求,因此抛出异常。

更新 2

正如我所说的flock,试试这个代码,多重提交问题就可以解决

<?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>

【讨论】:

你是对的,file_put_contents 创建了另一个锁。但这不是这里的问题。如果那会是个问题,那么一开始就不可能写任何东西来罚款。但正如我在评论中所说 - 如果我“只是”在数据库中插入一行或发送邮件,这个问题也存在。它与锁无关。 等一下file_put_content默认不创建LOCK:php.net/manual/de/function.file-put-contents.php 但是你在调用file_put_contents之前已经锁定了文件flock($fp, LOCK_EX),所以你不能写入文件,存在排他锁,file_put_contents打开另一个指针背景,无法写入,因为您已经在上面锁定了一行。 首先,我真的很感谢您在这里为我提供了如此多的帮助。我真的希望你是对的,因为我真的很想知道这里发生了什么。但是,我认为您错过了有关羊群的一些事情。您可以始终使用fwritefopenfile_put_content - 无论文件是否被锁定,都可以独立使用。 flock 只是一个咨询锁定。如果你的论点是正确的,那么就根本不可能用我的脚本向文件中写入一些东西,但显然它是。 我从 Update 2 尝试了您的脚本,如果我多次单击提交按钮,文件中仍然只添加了一个 +。这次我将再次使用数据库更改我的问题,因此flock 的事情将不再让人们感到困惑。不过,感谢您的帮助!

以上是关于为啥两次点击按钮时这个表单没有提交两次?的主要内容,如果未能解决你的问题,请参考以下文章

表单提交两次mvc

为啥我的表单提交两次?

多次点击提交后如何只提交一次表单?

为啥在表单操作页面中结构键值显示两次像“提交,提交”?

强制点击提交按钮两次

当用户双击提交按钮时,需要避免向服务器提交两次表单