Подтвердить что ты не робот

Запрос запроса jQuery прерывается: появляется только половина параметров сообщения

Я заметил странное явление в моей среде LAMP.
По интерфейсу я выполняю запрос AJAX post с jQuery следующим образом:

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

Переменные d1 to dN собираются с веб-сайта (например, из текстовых входов, текстовых полей, флажков и т.д.) с помощью jQuery заранее.

Файл save.php принимает параметры сообщения data1 до dataN и сохраняет их в базе данных в одном запросе.

Запрос занимает около 500 мс и работает без проблем, если я не изменю страницы (например, щелкнув ссылку) во время запроса.

Обычно я ожидаю, что запрос будет прерван и проигнорирован (что будет хорошо), но (и это странное поведение), запрос, кажется, завершен, но только с частью переданных и, таким образом, сохраненных данных.

Это означает, например, что PHP скрипт сохраняет только data1 до data5 и устанавливает data6 в dataN на пустую.
Проблема, похоже, вызвана запросом AJAX уже (а не php script), так как поля $_POST['data6'] до $_POST['dataN'] не установлены в php в этом сценарии.

Итак, мои вопросы:
Почему это происходит (это ожидаемое поведение)?
Как я могу избежать этого?

Обновление
Проблема заключается не только в jQuery и php. jQuery правильно собирает значения и пытается отправить их в php. Я просто подтвердил это - он работает. С другой стороны, PHP скрипт обрабатывает все, что он получает, как ожидалось, - он просто не получает весь запрос.
Таким образом, проблема должна заключаться в прерванном запросе. В отличие от того, что я ожидал, что он не прервется или не сработает, он все равно передает все данные до отключения.
Затем php получает эти данные и начинает обрабатывать их - очевидно, отсутствует какая-либо информация.

Обновление 2
Я исправил проблему, добавив параметр eof после dataN и проверив, был ли он установлен в php. Таким образом, я могу быть уверен, что весь запрос был передан.
Тем не менее это не фиксирует источник проблемы, который я до сих пор не понимаю.
Любая помощь кому-нибудь?

4b9b3361

Ответ 1

Попробуйте выполнить следующие действия для отладки проблемы:

  • Проверьте post_max_size в настройках php и сравните его с размером данных, которое вы публикуете.

  • Пользовательский конструктор запросов 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']])

Ответ 2

Вы можете проверить значение заголовка Content-Length, получаемого PHP.

Это значение должно быть рассчитано на стороне клиента при запуске запроса 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, чтобы иметь возможность POST его из браузера

<form method="post" action="?"><input type="text" name="key" /><input type="submit" value="go" /></form>

Мне удалось проверить, что в Firefox (последний), IE8, Chrome (последний), все работает на XP Pro SP3, значение Content-Length совпадает с значением strlen php://input.

За исключением случаев, когда запрос отключен, то есть.

Единственная проблема заключается в том, что php://input не всегда доступен даже для POST данных.

Это оставляет нас еще в затруднительном положении:

ЕСЛИ ОШИБКА НА УРОВНЕ СЕТИ, т.е. POST подготовлен и снабжен правильной длиной контента, но прерывание делает так, что все данные обрезаются как этот комментарий Horen, кажется, указывает:

Таким образом, появилась только первая пара параметров сообщения, иногда значение одного параметра было даже прервано в середине

тогда действительно проверка 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 составлен, длина контента рассчитана на неполные данные, и пакет отправлен - тогда мое обходное решение не может работать, и нужно использовать дополнительное поле "telltale", или... возможно, функция .post в jQuery может быть расширена, чтобы включить поле CRC?

Ответ 3

Я думаю, что мы можем исключить проблемы на сервере (если это не какой-то экзотический или самодельный серверный демон), потому что никто никогда не посылал "end-of-data" -параметры с запросом HTTP POST, чтобы убедиться, что все данные действительно отправлены. Это обрабатывается самим HTTP (см., Например, Обнаружение конца тела HTTP-запроса). Более того, я не думаю, что вам нужно проверять заголовок Content-Length, когда POST данные на ваш сервер просто из-за того, что никто этого не делает. По крайней мере, не в таких общих обстоятельствах, как вы их описываете (отправка Ajax POST через jQuery).

Поэтому я полагаю, что jQuery отправляет синтаксически правильный POST, но он отключается. Я предполагаю, что если вы прервите сбор этих данных, перейдя на другую страницу, jQuery создаст запрос Ajax из данных, которые он смог собрать и отправил синтаксически правильный POST на ваш сервер, но с разрезом выключения.

Поскольку вы используете Firebug, перейдите на вкладку net и активируйте persist, поэтому данные трафика не будут потеряны при переходе на другую страницу. Затем запустите свой Ajax POST, перейдите на другую страницу (и тем самым "прервите" вызов Ajax) и зайдите в вкладку Firebug net, какие данные были фактически отправлены на сервер, открыв ВСЕ POST запрашивает и проверяет вкладку Headers (и внутри нее вкладку Request Headers).

Я предполагаю, что может произойти одна из двух вещей:

  • Вы увидите, что данные, отправленные на сервер, обрезаются уже в заголовках, представленных вам на вкладке Firebug net, а Content-Length вычисляется правильно в соответствии с фактической (срезанной) длиной данные POST. В противном случае, я уверен, что сервер отклонил запрос как Bad Request в целом.
  • Вы увидите, что существует несколько запросов POST, некоторые из них (возможно, с полными, не обрезанными данными) фактически прерываются и, следовательно, никогда не доходят до сервера, но по крайней мере один запрос POST (опять же, с отключенными данными), который был вызван некоторым другим механизмом в вашем Javascript, т.е. не вызванным вами триггером, но, перейдя на другую страницу, могут быть вызваны другие и другие запросы Ajax (просто предположение, так как я не знаю вашего исходного кода).

В любом случае, я думаю, вы узнаете, что эта проблема связана с клиентом, а сервер просто обрабатывает (неполные, но (в терминах HTTP) синтаксически допустимые) данные, отправленные клиенту на него.

С этого момента вы можете отладить Javascript и реализовать механизм, который предотвращает отправку неполных данных на ваш сервер. Опять же, сложно сказать, что делать, поскольку я не знаю остальной части исходного кода, но, возможно, в сборе данных происходит какое-то тяжелое действие, и вы можете убедиться, что POST произойдет только тогда, когда все данные действительно собраны. Или, возможно, вы можете предотвратить навигацию, пока не будет завершен запрос Ajax или такие вещи.

Что может быть интересно, если все это не имеет смысла, было бы лучше посмотреть на исходный код, особенно, как срабатывает Ajax POST, и если есть какие-либо другие события, и если вы перейдите на другую страницу. Образцы данных, которые вы отправляете, также могут быть интересными.

EDIT: Я также хотел бы указать, что вывод данных с помощью console.log() может вводить в заблуждение, поскольку он никоим образом не гарантирует, что это данные, которые действительно отправляются, это просто логарифм, который оценивает данный вывод на точное время, когда вызывается console.log(). Поэтому я предложил обнюхивать сетевой трафик, потому что тогда (и только тогда) вы можете быть уверены, что отправлено (и получено) действительно. Тем не менее, это немного сложно, если вы не привыкли к этому (и невозможно, если вы используете зашифрованный трафик, например, с помощью HTTPS), поэтому вкладка Firebug net может быть хорошим компромиссом.

Ответ 4

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)
  • Проверка входных данных
  • Передавать не так много данных за один раз

Ответ 5

Я попытался воссоздать эту проблему, используя триггеры, вручную, изменив настройки сервера, сделав все возможное, чтобы # $% & вещи, используя разные размеры данных, но у меня никогда не было только половины запроса на PHP. Просто потому, что apache не будет вызывать PHP до тех пор, пока запрос не будет полностью выполнен. См. этот вопрос о чтении "chunked" POST-данных в PHP

Итак, единственное, что может пойти не так, это то, что JQuery только собирает часть данных, а затем делает запрос POST. Используя только $.post('save.php', data), как вы упомянули, этого не произойдет. Он либо работает над сбором данных, либо ожидает ответа от сервера.

Если вы переключаете сайты во время сбора, не будет запроса. И если вы переключитесь после того, как запрос был сделан, и вы быстро уходите, прежде чем все данные будут переданы, сервер apache увидит его как половину запроса и не вызовет PHP.

Некоторые предложения:

Возможно ли, что вы используете отдельные страницы для успешных и частичных запросов? Поскольку PHP добавляет только первые 1000 элементов к $_POST, и, возможно, неудавшиеся запросы имеют более чем data1000=data элементов? Таким образом, не будет параметра EOF.

Возможно ли, что вы собираете данные в глобальном var в javascript и используете метод onbeforeunload, который также отправляет данные? Потому что тогда в POST может быть только половина данных.

Можете ли вы поделиться некоторой информацией о данных, которые вы делаете? Есть ли много мелких элементов (например, data1 до data10000) или несколько больших один раз?

Это тот же самый элемент, который вы получили последним? Как всегда, данные6, как вы упоминаете? Потому что, если это так, вероятность неудачной попытки всегда в том же самом поле dataN будет очень тонкой.

Ответ 6

Вы можете проверить журнал хоста на

/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')

Если это ваша проблема, tyr to, с js, сжимайте переменные, закодируйте их с помощью base64. Отправьте их с помощью ajax, затем получите затем на php, decode64, затем распакуйте! Это решило для меня;)

Ответ 7

Чтобы узнать больше о том, что происходит, почему бы вам не правильно запрограммировать запрос ajax с помощью функции jQuery 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();
    }
});

Ответ 8

Перед отправкой запроса установите обработчик "onbeforepage" для документа (чтобы запретить переход на другую страницу) и отмените привязку после успеха.

Пример:

$(document).on('unload', function(e){
    e.preventDefault();
    // Here you can display a message, you need to wait a bit
    return false;
});

Ответ 9

Догадка - функция suhosin на вашем сервере Ubuntu/Debian приводит к отключению поля?

Ответ 10

Моя проблема заключалась в том, что в одном из моих объектов сообщения было слишком много переменных. PHP имеет переменную max_input_vars, которая по умолчанию установлена ​​в 1000.

Я добавил эту строку в мой .htaccess файл (так как у меня нет доступа к файлу php.ini):

php_value max_input_vars 5000

Проблема решена!

Ответ 11

У меня та же проблема. Размер POST составляет около 650 КБ и повреждается, если закрыть окно браузера или обновить страницу в случае ajax, прежде чем сообщение будет завершено. ajax.success() не запускается, но частичные данные отправляются на "save.php" с 200 OK. $_SERVER Контент-длина бесполезна, как предложено в elswhere, поскольку она соответствует фактической длине содержимого частичных данных.

Я выяснил два способа преодоления этого:

  • как вы предлагаете, добавьте переменную post в конце. Кажется, это работает, хотя кажется немного рискованным.
  • создайте резервную копию save.php script во временном столбце db, а затем используйте ajax.success() для вызова другого PHP скрипт, скажем savefinal.php, без передачи каких-либо данных, которые передают данные от столбца temp до конечного столбца (или просто помещает эти данные как действительные). Таким образом, если сообщение прервано, данные будут находиться только в столбце temp в базе данных (или не будут помечены как действительные).

Ошибка .success() не вызывается, если сообщение прерывается, поэтому это должно работать.

Я предполагаю, что ошибка jquery отправляет неверную (очень маленькую) длину контента в apache, и apache вынужден предположить, что почтовый запрос завершен, но я не уверен.

Ответ 12

Решила проблему, увеличив max_input_vars в моем файле php.ini сервера

Поскольку в массиве было более 1000 переменных, только часть из них была получена сервером!

Надеюсь, это поможет кому-то!

Ответ 13

Почему это происходит (это ожидаемое поведение)?

Также искал причину этого странного поведения, когда некоторые переменные из сообщения отсутствовали, и пришел к этому вопросу, где объясняется очень подобное поведение при медленном подключении веб-клиента, отправляющего запрос POST с помощью multipart/форм-данные.

Здесь указан упомянутый вопрос:

Я столкнулся с проблемой, когда удаленный веб-клиент с медленным подключением не удается отправить полный запрос POST с использованием контента multipart/form-data но PHP все еще использует частично полученные данные для заполнения массива $_POST. В результате одно значение в массиве $_POST может быть неполным и более значения могут отсутствовать.

См. Как проверить неполный запрос POST в PHP

Как я могу избежать этого?

Там вы также найдете рекомендуемое решение. Тем не менее, то же решение уже было предложено вами.

Вы можете добавить поле <input type="hidden" name="complete"> (например) как параметр last. в PHP проверьте, был ли этот параметр отправлено от клиента. если этот параметр отправлен - вы можете быть уверены, что вы получил все данные.