为啥两次点击按钮时这个表单没有提交两次?
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 文件中,单击按钮二十次后,我还发现只有一个 GET
和 POST
请求:
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
打开另一个指针背景,无法写入,因为您已经在上面锁定了一行。
首先,我真的很感谢您在这里为我提供了如此多的帮助。我真的希望你是对的,因为我真的很想知道这里发生了什么。但是,我认为您错过了有关羊群的一些事情。您可以始终使用fwrite
或fopen
或file_put_content
- 无论文件是否被锁定,都可以独立使用。 flock 只是一个咨询锁定。如果你的论点是正确的,那么就根本不可能用我的脚本向文件中写入一些东西,但显然它是。
我从 Update 2 尝试了您的脚本,如果我多次单击提交按钮,文件中仍然只添加了一个 +
。这次我将再次使用数据库更改我的问题,因此flock
的事情将不再让人们感到困惑。不过,感谢您的帮助!以上是关于为啥两次点击按钮时这个表单没有提交两次?的主要内容,如果未能解决你的问题,请参考以下文章