Отправка POST запросов на PHP и прием файлов

Отправить POST запрос средствами PHP несколько сложнее, чем методом GET. Для решения этой задачи можно использовать несколько способов, например: сокеты, cURL, потоковые функции. В этой статье будет рассмотрена реализация выполнения задачи при помощи сокетов, с использованием встроенной PHP функции fsockopen().

Сокеты и fsockopen()

Предварительно, перед применением сокетов и функции fsockopen(), следует разобраться с заголовками, которые необходимо отправить серверу. С этим поможет справиться плагин для браузера Firefox: LiveHTTPHeaders. С помощью LiveHTTPHeaders можно просмотреть все отправляемые на сервер заголовки и что посылается в ответ. Нужно найти или создать любую форму, которая отправляет POST запрос и посмотреть заголовки, которыми обменивается сервер с браузером. Для примера возьмем форму авторизации на сайте gmail.com. Введя логин test и пароль pass браузер cгенерирует приблизительно такие, как в примере ниже, заголовки, и отправит их на сервер google:

POST /ServiceLoginAuth HTTP/1.1
Host: accounts.google.com
User-Agent: ИНФОРМАЦИЯ О БРАУЗЕРЕ
Accept-Language: ru
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 293

continue=http%3A%2F%2Fmail.google.com%2Fmail...Email=test&Passwd=pass&...

Последнюю строку можно привести в понятный человеку вид используя функцию PHP urldecode('строка').

Чтобы отправить POST запрос при помощи PHP необходимо инициировать соединение функцией fsockopen(), отослать сходные с примером заголовки на сервер, и прочитать, что он вернет в ответ. Сразу хотел бы отметить, что не стоит экспериментировать на популярных серверах с многомиллионной посещаемостью - там вполне вероятно установлена защита от роботов, и примеры, размещенные в этой статье могут вернуть не то, что предполагается. Лучше всего проводить испытания на собственноручно написанном скрипте, который будет возвращать данные, в зависимости от полученных переменных в POST. Пример такого скрипта:

if ($_POST['file'] === 'zip')
  {
  readfile('test.zip');
  exit();
  }

if ($_POST['name'] === 'fsockopen')
  echo 'fsockopen ';
if (array_key_exists('plus', $_POST))
  echo ($_POST['plus'] + $_POST['plus']).' ';
if (is_array($_POST['my_array']))
  {
  foreach ($_POST['my_array'] AS $key => $val)
    {
    echo $key.'=>'.$val.' ';
    }
  }

Предположим, что этот скрипт доступен по адресу www.example.com/server.php. В той же директории, где расположен скрипт server.php следует расположить любой zip-архив не большого размера с именем test.zip. Этот файл нужен для теста передачи файла по HTTP протоколу, в зависимости, от отосланной POST переменной.

Ниже, пример скрипта, который позволяет не только получить ответ от сервера, в зависимости от отправленных POST переменных, но и принять файл по протоколу HTTP. Передача файлов по HTTP будет полезна, если доступ к ним необходимо предоставить ограниченному кругу пользователей, список которых задается в PHP-скриптах. Также, в скрипте реализована отправка массива данных POST (my_array[0]=0&my_array[1]=var1&my_array[2]=vars2). Еще одна изюминка этого скрипта - защита от зависания функции feof(), что может произойти, если сервер, к которому идет подключение, не завершает соединение.


// Скрипт с сайта http://petrenco.com/php.php?txt=134

$serv_addr = 'www.example.com';
$serv_port = 80;
$serv_page = 'server.php';
$timelimit = 2; // Время ожидания ответа в сек. По умолчанию - 30 сек.

// Передаваемые POST переменные в формате: название переменной => значение
$post_data = array('name' => 'fsockopen',
                   'plus' => 5,
                   'my_array[0]' => 0,
                   'my_array[1]' => 'var1',
                   'my_array[2]' => 'vars2',
                   'file' => 'zip');

// Генерируем строку с POST запросом
$post_data_text = '';
foreach ($post_data AS $key => $val)
  $post_data_text .= $key.'='.urlencode($val).'&';
 
// Убираем последний символ & из строки $post_data_text
$post_data_text = substr($post_data_text, 0, -1);
 
// Прописываем заголовки, для передачи на сервер
// Последний заголовок должен быть обязательно пустым,
// так как тело запросов отделяется от заголовков пустой строкой (символом перевода каретки "\r\n")
$headers = array('POST /'.$serv_page.' HTTP/1.1',
                 'Host: '.$serv_addr,
                 'Content-type: application/x-www-form-urlencoded',
                 'Content-length: '.strlen($post_data_text),
                 'Accept: */*',
                 'Connection: Close',
                 '');
                 
// Создание строки заголовков
$headers_txt = '';
foreach ($headers AS $val)
  {
  $headers_txt .= $val.chr(13).chr(10);
  }
 
// Создание общего запроса (заголовки и тело запроса)
// chr(13).chr(10) равно "\r\n" - перевод каретки
$request_body = $headers_txt.$post_data_text.chr(13).chr(10).chr(13).chr(10);

// Открытие сокета
$sp = fsockopen($serv_addr, $serv_port, $errno, $errstr, $timelimit);
 
if (!$sp)
  exit('Error: '.$errstr.' #'.$errno);

// Передача заголовков и POST запросов за один раз
fwrite($sp, $request_body);

$server_answer = '';

// Если соединение, открытое fsockopen() не было закрыто сервером
// код while(!feof($sp)) { ... } приведет к зависанию скрипта
// В коде ниже - эта проблема решена
$start = microtime(true);
$header_flag = 1;
while(!feof($sp) && (microtime(true) - $start) < $timelimit)
  {
  if ($header_flag == 1)
    {
    $content = fgets($sp, 4096);
    if ($content === chr(13).chr(10))
      $header_flag = 0;
    else
      $server_header .= $content;
    }
  else
    {
    $server_answer .= fread($sp, 4096);
    }
  }

fclose($sp);  
 
echo $server_header.chr(13).chr(10).chr(13).chr(10).chr(13).chr(10).$server_answer.' ';

// Запись ответа сервера в файл
// Если в ответе сервера будет только содержимое zip-файла
// файл test.zip - будет являться zip-архивом
$fp = fopen('test.zip', 'wb+');
fwrite($fp, $server_answer, strlen($server_answer));
fclose($fp);

Опубликовано: 2012/05/05
HTML-код ссылки на эту страницу:
<a href="https://petrenco.com/php.php?txt=134" target="_blank">Отправка POST запросов на PHP и прием файлов.</a>
55158
Комментарии
Вот это вообще щедеврально:
$post_data_text = substr($post_data_text, 0, -1);

А если чуть выше вот так?:
$post_data_text .= (($post_data_text != '')?'&':'') . $key . '=' . urlencode($val);

И chr(13).chr(10) в "\r\n" тоже проще будет :))

Ваш вариант, тоже вполне приемлем, но неужели, если кто-то пишет не так "шедаврально" как Вы, это значить плохо?! Чем Ваш вариант лучше, кроме того, что Вам он нравиться больше? :) Да и если поставить условие в цикл оно будет нагружать систему тем больше, чем длиннее будет становиться $post_data_text.
$headers_txt = implode("\r\n", $headers);
Согласен, можно и так вместо foreach.
Можно также записывать в массив с последующим импладом
foreach ($post_data AS $key => $val)
$ar[] = $key.'='.urlencode($val);
$post_data_text = implode('&', $ar);
Хорошо написано все понятно, я похоже пишу, даже приятно стало, что нет всяких извращенств.
Добавить комментарий
Ваш e-mail: (не виден посетителям сайта)
Ваше имя:
Комментарий:
Символы с картинки:
Только выделенные поля формы добавления комментариев обязательны к заполнению.