我在LAMP环境中发现了一个奇怪的现象 在前端,我用jQuery执行一个AJAX post请求:
$.post('save.php', {data1: d1, data2: d2, [...], dataN: dN})
预先使用jQuery从网站收集变量d1
到dN
(例如,从文本输入,textareas,复选框等)。
文件save.php
将帖子参数data1
带到dataN
,并在一次查询中将它们保存在数据库中。
请求大约需要500毫秒,并且除非我在请求期间更改了页面(例如,通过点击链接)。
通常情况下,我希望请求被中止并被忽略(这没关系)但是(这是奇怪的行为)请求似乎已经完成但只有部分数据传播并因此得救。
这意味着,例如,php脚本仅将data1
保存到data5
,并将data6
设置为dataN
以清空。
问题似乎是由AJAX请求(不是php脚本)引起的,因为在这种情况下,字段$_POST['data6']
到$_POST['dataN']
没有在php中设置。
所以我的问题:
为什么会发生这种情况(这是预期的行为)?
我该如何避免呢?
更新
问题既不是jQuery也不是php。 jQuery正确收集值并尝试将它们发布到php。我刚刚验证了它 - 它有效。
另一方面,php脚本处理它按预期获得的所有内容 - 它只是没有收到整个请求
所以问题必然是被中断的请求本身。与我预期它不会中止或失败不同,它仍会传输所有数据直到中断
然后php获取这个帖子数据并开始处理它 - 显然缺少一些信息。
更新2
我通过在eof
之后添加参数dataN
并检查它是否在php中设置来解决问题。通过这种方式,我可以确定整个请求已被传输
然而,这并不能解决我仍然不理解的问题根源
任何人都可以帮忙吗?
答案 0 :(得分:5)
尝试以下操作来调试问题:
检查您的php设置中的post_max_size
,并将其与您发布的数据大小进行比较。
用户HTTP请求构建器,即使用Fiddler
发出http请求并检查它返回的内容。
使用print_r($_POST);
顶部的save.php
来检查您的内容。
使用Firebug
之类的工具查看jQuery
发布的内容。
您还应该验证您要发布的客户端上的json对象。即JSON.stringify(some_object);
尝试发布一些基本样本数据{ "data1":1, "data2":2, "data3":3, "data4":4, "data5":5 , "data6":6 }
很可能您发送的数据很多,或者数据可能无效!
<强>编辑:强> 非常愚蠢的行为,但是让我们说你也发布了计数。所以直接检查isset($ _ POST ['data'。$ _ POST ['count']])
答案 1 :(得分:4)
您可以通过PHP验证收到的Content-Length
标题的值
在运行POST查询时,此值应已在客户端计算。如果它不匹配,那就是你的错误。这就是您需要的所有诊断 - 如果Content-Length与POST数据不匹配,请将POST拒绝为无效;不需要额外的参数(计算POST数据长度可能会很麻烦)。此外,你可能想调查为什么PHP,虽然解码POST ,因此能够验证它的长度,但似乎接受了错误的长度(也许检测错误所需的信息在某处$_SERVER
变量?)。
如果 匹配但仍然数据未到达(即Content-Length
更小,并正确描述了截止POST ),然后证明在截止后检查了,因此错误是在浏览器中(或者,不太可能,在jQuery中)或者浏览器和服务器(代理?)正在接收不完整的查询(内容长度&gt;实际长度)并且错误地重写它,使其对服务器显得“正确”,而不是拒绝它手。
执行摘要:前者错了,但后者显然是正确的。请参阅下面的代码,了解适用于我的测试系统的示例(Linux OpenSuSE 12.3,Apache)。
我认为错误Content-Length
的请求会被400 Bad Request
拒绝。我错了。似乎至少我的Apache 很多更宽松。
我使用这个简单的PHP代码来访问我感兴趣的关键变量
<?php
$f = file_get_contents("php://input");
print $_SERVER['CONTENT_LENGTH'];
print "\nLen: " . strlen($f) . "\n";
?>
然后我准备了一个错误Content-Length
的请求,使用nc
将其发送出去:
POST /p.php HTTP/1.0
Host: localhost
Content-Length: 666
answer=42
......而且,nc localhost 80 < request
没有产生400错误:
HTTP/1.1 200 OK
Date: Fri, 14 Jun 2013 20:56:07 GMT
Server: Apache/2.2.22 (Linux/SUSE)
X-Powered-By: PHP/5.3.17
Vary: Accept-Encoding
Content-Length: 12
Content-Type: text/html
666
Len: 10
在我看来,如果请求以回车结束,并且什么回车 - LF,那么内容长度可能会减少一到两个? CRLF?但是,当我添加简单的HTML以便能够从浏览器发布它时
<form method="post" action="?"><input type="text" name="key" /><input type="submit" value="go" /></form>
我能够在Firefox(最新),IE8,Chrome(最新)中验证所有在XP Pro SP3上运行, Content-Length的值与strlen
的{{1}}相同php://input
强>
除非请求被切断,即。
唯一的问题是php://input
is not always available即使对于POST
数据也是如此。
这让我们陷入了困境:
如果错误在网络级别,即POST已准备好并提供正确的内容长度,但中断使得整个数据被截断为此评论霍伦似乎表示:
所以只有前几个帖子参数到达,有时一个参数的值甚至在中间被中断
然后真的检查Content-Length
将阻止PHP处理不完整的请求:
<?php
if ('POST' == $_SERVER['SERVER_PROTOCOL'])
{
if (!isset($_SERVER['Content-Length']))
{
header($_SERVER['SERVER_PROTOCOL'] . ' 400 Bad Request', True, 400);
die();
}
if (strlen(file_get_contents('php://input'))!=(int)($_SERVER['Content-Length']))
{
header($_SERVER['SERVER_PROTOCOL'] . ' 400 Bad Request', True, 400);
die();
}
}
// ... go on
?>
另一只手如果问题出现在jQuery中,即以某种方式中断阻止jQuery从汇编完整的POST,然而POST已经组成,内容-Length计算了不完整的数据,并且发送了数据包 - 然后我的解决方法不可能工作,并且必须使用“telltale”额外字段,或者......也许jQuery中的.post
函数可能扩展到包括CRC字段?
答案 2 :(得分:4)
我认为我们可以排除服务器站点的问题(除非它是一些异国情调或自制的服务器守护程序),因为没有人发送&#34;数据结束&#34; - 参数HTTP POST
请求以确保所有数据都已真正发送。这由HTTP
本身处理(参见例如Detect end of HTTP request body)。此外,我不认为在Content-Length
数据发送到您的服务器时必须检查POST
标题,这完全是因为没有人这样做。至少不会像你描述它们那样完全常见的情况(通过Ajax
发送POST
jQuery
)。
所以我认为jQuery
发送语法正确的POST
,但它已被切断。我的猜测是,如果您通过导航到另一个页面来中断此数据收集,jQuery
会从其能够收集的数据中构建Ajax
请求,并将语法正确的POST
发送到你的服务器,但有数据中断。
由于您正在使用Firebug
,请转到其net
标签并激活persist
,以便在导航到其他网页时不会丢失流量数据。然后触发您的Ajax POST
,导航到另一个页面(从而&#34;中断&#34; Ajax
来电)并在Firebug
的网络标签中查看哪些数据有实际上是通过打开所有 POST
个请求并检查Headers
标签(以及Request Headers
标签内)来发送到服务器。
我的猜测是可能发生以下两种情况之一:
Firebug
net
标签中显示的标题中已被切断,并且Content-Length
已计算根据{{1}}数据的实际(截止)长度正确。否则,我确定服务器会拒绝该请求为POST
整体。Bad Request
请求,其中一些(可能包含完整的非切断数据)实际上已中断,因此从未到达服务器,但至少有一个POST
请求(再次,使用截断数据)由POST
中的其他机制触发,即不是您想到的触发器,而是导航到另一个页面,更多和其他{可能会触发{1}}请求(因为我不知道您的源代码,所以只是一个猜测)。在任何一种情况下,我认为你会发现这个问题与客户端有关,服务器只处理客户端发送到的(不完整的,但是(在Javascript
)语法上有效的数据)它
从那时起,您可以调试Ajax
并实施一些防止向服务器发送不完整数据的机制。同样,由于我不知道你的其他源代码,但很难说清楚该做什么,但也许在收集数据时会有一些重大的动作,你可能会确保只有在真正收集到所有数据时才会发生HTTP
。或者,也许您可以阻止导航,直到Javascript
请求完成或此类事情。
有趣的是,如果所有这些都没有意义,那就是查看更多的源代码,特别是如何触发POST
以及是否还有其他事件等等,如果你导航到另一个页面。您发送的示例数据也很有趣。
编辑:我还想指出,使用Ajax
输出数据可能会产生误导,因为它并不能保证这是实际发送的数据,它是&#39 ; s只是一个日志,它在调用Ajax POST
的确切时间评估给定输出。这就是为什么我建议嗅探网络流量的原因,因为那时(并且只有这样)你可以确定发送(和接收)真正的是什么。
尽管如此,如果您不习惯这种情况,这有点棘手(如果您使用加密流量,例如使用console.log()
)则不可能,因此console.log()
HTTPS
标签可能是一个很好的妥协。
答案 3 :(得分:3)
Post
数据看起来就像GET
:
Header1: somedata1\r\n
Header2: somedata2\r\n
...
HeaderN: somedataN\r\n
\r\n
data1=1&data2=2&...&dataN=N
当请求中止时,在某些情况下,最后一行可能只是部分传递。所以,这里有一些可能的解决方案:
Content-Length
和strlen($HTTP_RAW_POST_DATA)
答案 4 :(得分:3)
我尝试使用触发器手动重新创建此问题,更改服务器设置,尽我所能#$%&amp;事情,使用不同的数据大小,但我从来没有得到PHP的一半请求。只是因为apache不会调用PHP直到请求完全完成。见this question about Reading “chunked” POST data in PHP
所以唯一可能出错的是Jquery只收集部分数据然后发出POST请求。正如您所提到的那样只使用$.post('save.php', data)
,这不会发生。它要么正在收集数据,要么等待服务器的响应。
如果您在聚会期间切换网站,则不会有请求。如果您在请求完成后切换并快速离开,则在传输所有数据之前,apache服务器会将其视为请求的一半,并且不会调用PHP。
一些建议:
您是否有可能使用单独的页面来获取成功和部分请求?因为PHP只将前1000个元素添加到$_POST
,并且失败的请求可能包含更多data1000=data
个元素?所以不会有EOF参数。
您是否有可能在javascript中的全局var中收集数据,并且还有一个onbeforeunload方法也可以发送数据?因为那时POST中可能只有一半的数据。
您可以分享一些有关您正在发送的数据的信息吗?有很多小元素(比如data1到data10000)还是一些大的元素?
它总是与你上次收到的元素相同吗?像你提到的那样总是像data6一样?因为如果是这样,尝试失败的可能性总是在完全相同的dataN字段上会非常小。
答案 5 :(得分:2)
您可以在
查看主机日志吗? /var/log/messages
上次我在php上“丢失”了帖子变量我发现我发送了空的ASCII字符,而服务器(CentOS)正在考虑它是一种攻击,然后丢弃那些特定的变量......花了我一个星期才算出来出来了!这是服务器日志响应:
suhosin[1173]: ALERT - ASCII-NUL chars not allowed within request variables - dropped variable 'data3' (attacker '192.168.0.37', file '/var/www/upload_reader.php')
如果这是您的问题,请使用js压缩变量,使用base64对它们进行编码。用ajax发布它们,然后在php接收,解码64然后解压缩!这解决了我;)
答案 6 :(得分:1)
为了更多地了解正在发生的事情,为什么不使用jQuery的ajax函数正确编码ajax请求。使用所有回调函数来跟踪您的通话或回来的内容?元素type
设置为POST,元素data
包含您喜欢的任何对象结构{ ... }
。
$.ajax({
url : "save.php",
type : "POST",
data : {
"ajax_call" : "SOME_CUSTOM_AJAX_REQUEST_REFERENCE",
"data1" : data1,
"data2" : data2,
"data2" : data2,
"dataN" : dataN
},
//dataType : "html", contentType: "text/html; charset=utf-8",
dataType : "json", contentType: "application/json; charset=utf-8",
beforeSend: function () {
//alert('before send...');
},
dataFilter: function () {
//alert('data filter...');
},
success: function(data, textStatus, jqXHR) {
//alert('success...');
var response = JSON.parse(jqXHR.responseText, true);
if (undefined != response.data) {
my_error_function();
}
my_response_function(response.data);
},
error: function(jqXHR, textStatus, errorThrown) {
//alert('error...');
},
complete: function (xhr, status) {
//alert('end of call...');
my_continuation_function();
}
});
答案 7 :(得分:1)
在发送请求之前,为文档设置“onbeforepageunload”处理程序(禁止转换到另一个页面),并在成功后取消绑定。
例如:
$(document).on('unload', function(e){
e.preventDefault();
// Here you can display a message, you need to wait a bit
return false;
});
答案 8 :(得分:1)
猜测 - 你的Ubuntu / Debian服务器上的suhosin函数会导致字段被切断吗?
答案 9 :(得分:1)
我的问题是我的一个帖子对象中有太多变量。 PHP有一个max_input_vars变量,默认设置为1000。
我将此行添加到我的.htaccess文件中(因为我无法访问php.ini文件):
php_value max_input_vars 5000
问题解决了!
答案 10 :(得分:0)
我有同样的问题。 POST大小约为650KB,如果在ajax的情况下关闭浏览器窗口或刷新页面,则在帖子完成之前会损坏。 ajax.success()未被触发,但部分数据以200 OK状态发布到“save.php”。 $ _SERVER的内容长度与建议的elswhere没有用,因为它与部分数据的实际内容长度相匹配。
我想出了两种克服这种方法的方法:
如果post被中断,则不会调用.success(),所以这应该有用。
我认为这是一个jquery错误,向apache发送错误的(非常小的)内容长度,并且apache被迫假设发布请求已经完成,但我不确定。
答案 11 :(得分:0)
当帖子中的某些变量丢失时,也在寻找这种奇怪行为的原因,并且在这个问题中解释了非常相似的行为 ,因为Web客户端发送POST请求的连接速度很慢with multipart / form-data 。
以下是提到的问题:
当连接速度较慢的远程Web客户端时,我遇到了问题 无法使用multipart / form-data内容发送完整的POST请求 但PHP仍然使用部分接收的数据来填充$ _POST数组。 因此,$ _POST数组中的一个值可能不完整而且更多 值可能会丢失。
请参阅How to check for incomplete POST request in PHP
在那里你也可以找到推荐的解决方案。尽管如此,您已经提出了相同的解决方案。
您可以添加字段
<input type="hidden" name="complete">
(例如) 作为最后参数。在PHP
中首先检查此参数是否为 从客户发送。如果发送此参数 - 您可以确定 获得了整个数据。