25

我注意到我的 LAMP 环境中有一个奇怪的现象。
在前端,我使用 jQuery 执行 AJAX 发布请求,如下所示:

$.post('save.php', {data1: d1, data2: d2, [...],  dataN: dN})

预先使用 jQuery 从网站(例如,从文本输入、文本区域、复选框等)收集d1变量。dN

该文件在一个查询save.php中将发布参数保存data1dataN数据库中。

除非我在请求期间更改页面(例如通过单击链接),否则该请求大约需要 500 毫秒并且可以正常工作。

通常,我希望请求被中止并被忽略(这很好)但是(这是奇怪的行为)请求似乎已经完成,但只有部分数据被传输并因此被保存。

这意味着例如,php 脚本只保存data1data5并设置data6dataN空。
该问题似乎是由 AJAX 请求(不是 php 脚本)引起的,因为在这种情况下$_POST['data6']$_POST['dataN']未在 php 中设置字段。

所以我的问题是:
为什么会发生这种情况(这是预期的行为)?
我怎样才能避免它?

更新
问题既不是 jQuery 也不是 php。jQuery 正确收集值并尝试将它们发布到 php.ini。我刚刚验证了它 - 它有效。另一方面,php 脚本按预期处理它所获得的一切——它只是没有接收到整个请求。
所以问题一定是被中断的请求本身。不像我期望它不会中止或失败,它仍然会传输所有数据,直到被切断。
然后 php 获取此帖子数据并开始处理它 - 显然缺少一些信息。

更新 2我通过在之后
添加一个参数并检查它是否在 php.ini 中设置来解决了这个问题。这样我就可以确定整个请求都被传输了。 尽管如此,这并不能解决我仍然不明白的问题的根源。有什么帮助吗?eofdataN

4

13 回答 13

6

Try the following actions to debug the problem:

  1. Check post_max_size in your php settings and compare it with the data size you are posting.

  2. User HTTP request builder, i.e. Use Fiddler to make an http request and check what it returns.

  3. Use print_r($_POST); on the top of the save.php, to check what you are getting in it.

  4. Use tool like Firebug to check what jQuery has posted.

  5. You should also verify the json object on client side that you are posting. i.e. JSON.stringify(some_object);

  6. Try posting some basic sample data { "data1":1, "data2":2, "data3":3, "data4":4, "data5":5 , "data6":6 }

Most probably you are sending to much data or likely data is invalid!

Edits: Very Foolish act but lets say you posted count as well. so directly check isset($_POST['data'.$_POST['count']] )

于 2013-06-07T11:32:00.190 回答
5

我认为我们可以排除服务器站点的问题(除非它是一些异国情调或自制的服务器守护程序),因为没有人会发送“数据结束”参数来HTTP POST请求确保所有数据都已真正发送。这是由HTTP它自己处理的(参见例如Detect end of HTTP request body)。此外,我认为您在向服务器发送数据Content-Length时不必检查标头POST,这仅仅是因为从来没有人这样做过。至少在您所描述的完全常见的情况下(Ajax POST通过发送jQuery)。

所以我想这会jQuery发送一个语法正确POST的,但它被切断了。我的猜测是,如果您通过导航到另一个页面来中断此数据收集,则从它能够收集的数据中jQuery构建一个请求,并向您的服务器发送语法正确的请求,但数据被截断。AjaxPOST

由于您正在使用Firebug,请转到其net选项卡并激活persist,因此导航到另一个页面时不会丢失流量数据。然后触发您的Ajax POST,导航到另一个页面(从而“中断”Ajax呼叫)并通过打开所有请求并检查选项卡(在此内部,选项卡)来检查Firebug实际发送到服务器的数据.POSTHeadersRequest Headers

我的猜测是可能会发生以下两种情况之一:

  1. 您将看到发送到服务器的数据已在Firebug'snet选项卡中显示给您的标头中被截断,并且Content-Length根据数据的实际(截断)长度正确计算POST。否则,我确信服务器会Bad Request整体拒绝请求。
  2. 您会看到有多个POST请求,其中一些(可能带有完整的、未截断的数据)实际上被中断,因此从未到达服务器,但至少POST触发了一个请求(同样,带有截断的数据)通过您的其他一些机制Javascript,即不是您认为的触发器,而是通过导航到另一个页面,Ajax可能会触发更多和其他请求(只是猜测,因为我不知道您的源代码)。

无论哪种情况,我想您都会发现这个问题与客户端有关,服务器只处理HTTP客户端发送给它的(不完整,但(就)语法有效的)数据。

从那时起,您可以调试Javascript并实现一些机制来防止将不完整的数据发送到您的服务器。同样,由于我不知道您的源代码的其余部分,因此很难确切说明该做什么,但是在收集数据时可能正在进行一些繁重的操作,您可以确保POST只有在所有数据都发生的情况下才会发生真是收藏了。或者,也许您可​​以阻止导航,直到Ajax请求完成或类似的事情。

有趣的是,如果所有这些都没有意义,那就是查看更多的源代码,特别是如何Ajax POST触发以及是否有任何其他事件,如果您导航到另一个页面。您发送的示例数据也可能很有趣。

编辑:我还想指出,console.log()console.log(). 这就是为什么我建议嗅探网络流量的原因,因为那时(并且只有那时)您可以确定真正发送(和接收)的内容。尽管如此,如果您不习惯,这有点棘手(如果您使用加密流量,例如使用 ,则不可能HTTPS),因此Firebug net选项卡可能是一个很好的折衷方案。

于 2013-06-13T11:23:07.670 回答
4

您可以验证PHP接收Content-Length到的标头的值。

该值应该在运行 POST 查询时在客户端计算。如果它不匹配,那就是你的错误。这就是您需要的所有诊断 - 如果 Content-Length 与 POST 数据不匹配,则拒绝 POST 为无效;不需要额外的参数(但计算 POST 数据长度可能很麻烦)。此外,您可能想调查为什么 PHP 在解码 POST并因此能够验证其长度时,似乎接受了错误的长度(也许检测错误所需的信息在$_SERVER变量中的某处?)。

如果它确实匹配,并且仍然没有到达数据(即,Content-Length较小,并且正确描述了截止 POST),则证明 POST在截止后被检查,因此要么错误在浏览器中(或者,不太可能,在 jQuery 中),或者浏览器和服务器(代理?)之间存在接收不完整查询(内容长度>实际长度)并且错误地重写它的东西,使其在服务器看来是“正确的”,而不是立即拒绝它。

对理论和解决方法的一些测试

内容提要:我把前者弄错了,但后者显然是对的。有关在我的测试系统(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 的值strlenphp://input.

除非请求被切断,否则。

唯一的问题是即使对于数据php://input 也不总是可用的。POST

这让我们仍然陷入困境:

如果错误发生在网络级别,即 POST 已准备好并提供了正确的 Content-Length,但中断导致整个数据被切断,因为 Horen 的评论似乎表明:

所以只有前几个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 是由不完整的数据计算得出的 Content-Length 以及发送的数据包 - 那么我的解决方法不可能工作,并且必须使用“telltale”额外字段,或者......也许.postjQuery中的函数可能会扩展为包含CRC字段?

于 2013-06-12T22:54:06.767 回答
3

Post数据看起来就像GET

Header1: somedata1\r\n
Header2: somedata2\r\n
...
HeaderN: somedataN\r\n
\r\n
data1=1&data2=2&...&dataN=N

当请求被中止时,在某些情况下,最后一行可能仅部分通过。所以,这里有一些可能的解决方案:

  1. 比较Content-Lengthstrlen($HTTP_RAW_POST_DATA)
  2. 验证输入数据
  3. 一次传递的数据不多
于 2013-06-12T23:01:04.877 回答
3

我曾尝试使用触发器手动重新创建此问题,更改服务器设置,尽我最大的努力 #$%& 使用不同的数据大小,但我从未在 PHP 中收到过一半的请求。仅仅是因为 apache 在请求完全完成之前不会调用 PHP。请参阅有关在 PHP 中读取“分块”POST 数据的问题

所以唯一可能出错的是 Jquery 只收集部分数据然后发出 POST 请求。$.post('save.php', data)就像你提到的那样使用,这不会发生。它要么正在收集数据,要么正在等待服务器的响应。

如果您在聚会期间切换站点,则不会有请求。如果你在请求发出后切换并迅速离开,在所有数据传输之前,apache 服务器会将其视为半个请求,不会调用 PHP。

一些建议:

您是否有可能对成功和部分请求使用单独的页面?因为 PHP 只添加了前 1000 个元素,$_POST而且失败的请求可能有更多的data1000=data元素?所以不会有EOF参数。

您是否有可能在 javascript 的全局变量中收集数据,并且有一个 onbeforeunload 方法也可以发送数据?因为那时 POST 中可能只有一半的数据。

你能分享一些关于你正在处理的数据的信息吗?是否有很多小元素(如 data1 到 data10000)或一些大元素?

你最后收到的元素总是相同的吗?像你提到的那样总是data6?因为如果是这样,总是在完全相同的 dataN 字段上尝试失败的机会将非常渺茫。

于 2013-06-13T12:22:50.410 回答
2

我的问题是我的一个帖子对象中有太多变量。PHP 有一个 max_input_vars 变量,默认设置为 1000。

我将此行添加到我的 .htaccess 文件中(因为我无权访问 php.ini 文件):

php_value max_input_vars 5000

问题解决了!

于 2014-07-18T09:05:59.090 回答
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中接收,decode64然后解压缩!这为我解决了;)

于 2013-06-13T13:22:08.623 回答
1

在发送请求之前,为文档设置“onbeforepageunload”处理程序(禁止转换到另一个页面),成功后解除绑定。

例如:

$(document).on('unload', function(e){
    e.preventDefault();
    // Here you can display a message, you need to wait a bit
    return false;
});
于 2013-06-07T07:31:03.453 回答
1

一个猜测 - 你的 Ubuntu/Debian 服务器上的 suhosin 函数导致该字段被切断?

于 2013-06-12T22:56:52.310 回答
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();
    }
});
于 2013-06-05T14:37:33.723 回答
1

通过增加我服务器的 php.ini 文件中的 max_input_vars 解决了这个问题

由于我在数组中有超过 1000 个变量,因此只有部分变量被服务器接收!

希望这对某人有帮助!

于 2015-12-21T09:13:13.997 回答
0

我也有同样的问题。POST 大小约为 650KB,如果在 ajax 的情况下关闭浏览器窗口或刷新页面,则在 post 完成之前会损坏。ajax.success() 未触发,但部分数据以 200 OK 状态发布到“save.php”。$_SERVER 的 Content-length 与 elswhere 建议的一样没有用,因为它与部分数据的实际内容长度匹配。

我想出了两种克服这个问题的方法:

  1. 按照您的建议,在末尾附加一个 post 变量。似乎工作,虽然它似乎有点冒险。
  2. 使您的“save.php”脚本保存到临时 db 列,然后使用 ajax.success() 调用另一个 php 脚本,例如 savefinal.php,而不传递任何数据,它将数据从 temp 列传输到 final列(或只是将此数据标记为有效)。这样,如果 post 被中断,数据将仅驻留在数据库的 temp 列上(或不会被标记为有效)。

如果发布中断,则不会调用 .success() ,因此这应该可以工作。

我认为这是一个 jquery 错误,向 apache 发送了错误的(非常小的)内容长度,并且 apache 被迫假设发布请求已经完成,但我不太确定。

于 2014-01-06T15:50:32.203 回答
0

为什么会发生这种情况(这是预期的行为)?

当 post 中的一些变量丢失时,也在寻找这种奇怪行为的原因,并提出了这个问题,其中非常相似的行为被解释为 web 客户端使用 multipart/form-data 发送 POST 请求的缓慢连接

这里是提到的问题:

当连接速度较慢的远程 Web 客户端无法发送包含多部分/表单数据内容的完整 POST 请求但 PHP 仍然使用部分接收的数据来填充 $_POST 数组时,我遇到了一个问题。因此,$_POST 数组中的一个值可能不完整,并且可能会丢失更多值。

请参阅如何在 PHP 中检查不完整的 POST 请求

我怎样才能避免它?

在那里您也可以找到推荐的解决方案。尽管如此,您已经提出了相同的解决方案。

您可以添加字段<input type="hidden" name="complete">(例如)作为最后一个参数。首先检查此参数是否是PHP从客户端发送的。如果发送了这个参数 - 你可以确定你得到了整个数据。

于 2017-12-06T12:13:23.343 回答