Как проверить наличие неполного запроса на публикацию в PHP


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

Вот мой пример запроса на публикацию:

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

Author: spatar, 2013-11-28

13 answers

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

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

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

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

 3
Author: MeNa, 2013-12-04 09:41:48

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

1) Если вы можете изменить Content-Type (например, на application/x-www-form-urlencoded), вы можете прочитать php://input, который будет содержать исходное содержимое запроса. Размер php://input должен соответствовать Content-Length (при условии, что значение Content-Length правильное). Если есть совпадение, вы все равно можете использовать $_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% гарантией, что содержимое совпадает с тем, что отправил клиент.

 1
Author: Jasper N. Brouwer, 2013-12-09 09:10:43

Если вы можете изменить тип кода на

multipart/form-data-alternate

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

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

Против

$_SERVER['CONTENT_LENGTH']
 1
Author: corretge, 2013-12-10 10:58:12

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

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

Php.ini

post_max_size=20M
upload_max_filesize=20M

.htaccess

php_value post_max_size 20M
php_value upload_max_filesize 20M
 0
Author: vokx, 2013-11-28 00:05:41

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

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

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

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

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

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

Ссылка: Проверка размера загружаемого файла JavaScript

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

 0
Author: nightowl, 2017-05-23 10:30:51

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

// 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
}
 0
Author: andy.why, 2013-12-05 14:58:59

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

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

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

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

Но серьезно, используйте хеширование. хэшируйте СООБЩЕНИЕ на клиенте, проверьте длина первого хэша на сервере. (некоторые?) хэши имеют фиксированную длину, поэтому, если длина не совпадает, это неправильно. Затем хэшируйте полученное СООБЩЕНИЕ и сравните с ПОСТ-хэшем. Если вы делаете это в течение всего ПОСТА, в указанном порядке (поэтому любое изменение порядка отменяется), накладные расходы минимальны.

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

 0
Author: puredevotion, 2013-12-08 20:02:55

Я думаю, что то, что вы ищете, это $ HTTP_RAW_ПОСТ_ДАННЫЕ, это даст вам реальную длину ЗАПИСИ, а затем вы сможете сравнить ее с $_SERVER['CONTENT_LENGTH'].

 0
Author: Pilingo, 2013-12-09 23:03:47

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

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

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

 0
Author: Ghigo, 2013-12-10 11:12:32

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

set_time_limit(0);

И вы будете уверены, что данные о дыре будут отправлены.

 0
Author: ventsi.slav, 2013-12-10 16:27:43

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

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

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

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

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


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

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

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

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

POST /script.php
headers

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

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

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

Моменты, которых следует опасаться:

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

Дальнейшее чтение:

 0
Author: Denis de Bernardy, 2013-12-10 18:09:41

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

 0
Author: mp_de, 2017-12-12 15:15:48

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

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

 -1
Author: Rajiv Charan Tej K, 2013-12-10 18:14:08