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

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

вот мой пример запроса POST:

POST /test.php HTTP/1.0
Connection: close
Content-Length: 10000
Content-Type: multipart/form-data; boundary=ABCDEF

--ABCDEF
Content-Disposition: form-data; name="a"

A
--ABCDEF

видно, что Content-Length is 10000 байт, но я отправляю только один var a=A.

скрипт PHP:

<?php print_r($_REQUEST); ?>

веб-сервер ждет около 10 секунд для остальной части моего запроса (но я ничего не отправляю), а затем возвращает этот ответ:

HTTP/1.1 200 OK
Date: Wed, 27 Nov 2013 19:42:20 GMT
Server: Apache/2.2.22 (Debian)
X-Powered-By: PHP/5.4.4-14+deb7u3
Vary: Accept-Encoding
Content-Length: 23
Connection: close
Content-Type: text/html

Array
(
     [a] => A
)

Итак, вот мой вопрос: Как я могу проверить в PHP, что запрос post был получен полностью? $_SERVER['CONTENT_LENGTH'] будет шоу 10000 из заголовка запроса, но есть ли способ проверить реальную длину полученного контента?

13 ответов


Я думаю, что удаленный клиент на самом деле является браузером с HTML-страницей. в противном случае дайте мне знать, и я попытаюсь адаптировать свое решение.

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

Теперь я не уверен, что порядок параметров должен быть сохранен в соответствии с RFC (обоих, HTML и HTTP). но я попробовал некоторые вариации и увидел, что порядок действительно соблюден.

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


насколько я знаю, нет способа проверить, соответствует ли размер полученного контента значению Content-Length заголовка при использовании multipart/form-data as Content-Type, потому что вы не можете получить сырой контент.

1) Если вы можете изменить Content-Type (to application/x-www-form-urlencoded например) вы можете узнать php://input, который будет содержать исходное содержимое запроса. Размер php://input должны соответствовать Content-Length (предполагая, что значение Content-Length это правильно). Если есть совпадение, вы все равно можете использовать $_POST получить обработанный контент (регулярные данные post). Читайте о php://input здесь.

2) или вы можете сериализовать данные на клиенте и отправить его как text/plain. Сервер может проверить размер так же, как описано выше. Сервер нужно восстановить полученный контент, чтобы иметь возможность работать с ним. И если клиент генерирует хэш сериализованных данных и отправляет его в заголовке (X-Content-Hash например), сервер также может генерировать хэш и проверять, если он совпадает с тем, что в заголовке. Вам не нужно будет проверять хэш и может быть на 100% уверен, что содержимое правильно.

3) Если вы не можете изменить Content-Type, вам понадобится что-то отличное от размера, чтобы проверить содержимое. Клиент может использовать дополнительный заголовок (что-то вроде X-Form-Data-Fields), чтобы суммировать поля / ключи / имена контента, который вы отправляете. Затем сервер может проверить, присутствуют ли в содержимом все поля, упомянутые в заголовке.

4) другое решение будет для клиента иметь предопределенный ключ / значение как последние запись в содержание. Что-то вроде:

--boundary
Content-Disposition: form-data; name="_final_field_"

TRUE
--boundary--

сервер может проверить, присутствует ли это поле в содержимом, если оно должно быть полным.

обновление

когда вам нужно передать двоичные данные, вы не можете использовать Вариант 1, но все еще можете использовать Вариант 2:

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

это немного больше работы, чем просто с помощью multipart/form-data, но сервер может проверить со 100% гарантией, что содержимое совпадает с тем, что отправил клиент.


Если вы можете изменить enctype к

multipart/form-data-alternate

вы можете проверить

strlen(file_get_contents('php://input'))

и

$_SERVER['CONTENT_LENGTH']

они, вероятно, получают ограничение по ограничениям в Apache или PHP. Я считаю, что Apache также имеет переменную конфигурации для этого.

вот настройки PHP;

php.ini

post_max_size=20M
upload_max_filesize=20M

.реврайт

php_value post_max_size 20M
php_value upload_max_filesize 20M

Что касается значений формы, которые полностью отсутствуют из-за проблем с подключением, вы можете просто проверить, установлены ли они:

if(isset($_POST['key']){
    //value is set
}else{
    //connection was interrupted
}

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

$_FILES['key']['size']

простое решение может использовать JavaScript для вычисления размера файла на стороне клиента и добавления этого значения в форму в качестве скрытого ввода при отправке формы. Вы получите размер файла в JS, используя что-то вроде

var filesize = input.files[0].size;

ссылки: проверка размера загрузки файла JavaScript

затем при загрузке файла, если значение скрытой формы ввода соответствует размеру загруженного файла, запрос не был прерван проблемами сетевого подключения.


возможно, вы можете проверить с помощью допустимой переменной, но не длины, например:

// client
$clientVars = array('var1' => 'val1', 'otherVar' => 'some value');
ksort($clientVars);  // dictionary sorted
$validVar = md5(implode('', $clientVars));
$values = 'var1=val1&otherVar=some value&validVar=' . $validVar;
httpRequest($url, values);

// server
$validVar = $_POST['validVar'];
unset($_POST['validVar']);
ksort($_POST);  // dictionary sorted
if (md5(implode('', $_POST)) == $validVar) {
    // completed POST, do something
} else {
    // not completed POST, log error and do something
}

Я также собирался рекомендовать использовать hidden значение или хэширование, как упоминает MeNa. (проблема в том, что некоторые алгоритмы по-разному реализованы на платформах, поэтому ваш CRC32 в js может отличаться от CRC32 в PHP. Но с некоторым тестированием вы сможете найти совместимый)

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

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

Если вы не можете расшифровать сообщение (или он дает искаженный беспорядок), чем сообщение было неполным.

а если серьезно используйте хэширование. хэш-сообщение на клиенте, проверьте длину первого хэша на сервере. (некоторые?) хэши-фиксированная длина, поэтому, если длина не совпадает, это неправильно. Затем хэш полученного сообщения и сравнить с POST-hash. Если вы делаете это по полной должности, в указанном порядке (поэтому любой переупорядочивание отменяется) накладные расходы минимальны.

все это предполагает, что вы просто не можете проверить сообщение post, чтобы увидеть, отсутствуют ли поля и is_set==True, length > 0 , !пустой.)(..


Я думаю, что вы ищете $HTTP_RAW_POST_DATA, это даст вам реальные длина сообщения, а затем вы можете сравнить его с $_SERVER ['CONTENT_LENGTH'].


Я не думаю, что можно вычислить исходный размер контента из $_REQUEST superglobal, по крайней мере, для запросов multipart/form-data.

Я бы добавил пользовательский заголовок к вашему http-запросу со всем хэшем parameter=value, чтобы проверить серверную сторону. Заголовки прибудут наверняка, поэтому ваш хэш-заголовок всегда там. Обязательно соединяйте параметры в одном порядке, иначе хэш будет отличаться. Также обратите внимание на кодирование, должно быть одинаковым на клиенте и сервер.

Если вы можете настроить Apache, вы можете добавить vhost с mod_proxy, настроенный для прокси на другом vhost на том же сервере. Это должно фильтр uncomplete запросы. Обратите внимание, что вы тратите 2 сокета на запрос таким образом, поэтому следите за использованием ресурсов, если вы думаете пойти этим путем.


другое решение, которое может быть полезным... Если соединение с другой стороны медленное, просто удалите предел для выполнения сообщения.

set_time_limit(0);

и вы будете уверены, что данные сообщения отверстия будут отправлены.


если вычисление длины контента не является разумным, вы, вероятно, могли бы уйти с подписанием данных, отправленных клиентом.

используя javascript, сериализуйте данные формы в строку json или эквивалент разумным образом (т. е. сортируйте их по мере необходимости) перед отправкой. Хэш этой строки с использованием одного или двух достаточно быстрых алгоритмов (например, crc32, md5, sha1) и добавьте эти дополнительные хэш-данные к тому, что будет отправлено в качестве подписи.

на сервере Снимите это дополнительные хэш-данные из запроса $_POST, а затем повторить ту же работу в PHP. Сравните хэши соответственно: ничего не потерялось в переводе, если хэши совпадают. (Используйте два хэша, если вы хотите аннулировать незначительный риск получения ложных срабатываний.)

Я бы поспорил, что есть разумные средства сделать что-то подобное для файлов, например, получение их имени и размера в JS и добавление этой дополнительной информации к данным, которые подписываются.

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


оригинальный ответ:

насколько мне известно, разница между отправкой GET или POST-запроса более или менее на суммы, отправляющие что-то вроде:

GET /script.php?var1=foo&var2=bar
headers

vs отправка чего-то вроде:

POST /script.php
headers

var1=foo&var2=bar              <— content length is the length of this chunk

так для каждой части, вы можете рассчитать длину и проверить, что против длины, объявленной заголовком content-length.

  • $_FILES записи имеют удобное поле размера, которое вы можете использовать напрямую.
  • For $_POST data, перестроить строку запроса, которая была отправлена и вычислить ее длину.

точки быть осторожными о:

  1. вам нужно знать, как ожидается, что данные будут отправлены в некоторых случаях, например var[]=foo&var[]=baz vs var[0]=foo&var[1]=baz
  2. ты работа с длиной c-строки, а не многобайтовой длиной в последнем случае. (Хотя я не удивлюсь, если узнаю, что странный браузер ведет себя непоследовательно здесь и там.)

читайте далее:


Это известная ошибка в PHP и должна быть исправлена там -https://bugs.php.net/bug.php?id=61471


попробуйте использовать буферизацию вывода с помощью ob_start (). В то время как буферизация вывода активна, вывод не отправляется из скрипта (кроме заголовков), вместо этого вывод сохраняется во внутреннем буфере.

содержание этого внутреннего буфера может быть скопировано в строковую переменную, используя ob_get_contents(). Для вывода, что хранится во внутреннем буфере, используйте ob_end_flush(). Кроме того, ob_end_clean() будет отбрасывать содержимое буфера.