Отправка POST запросов с помощью php. Часть 1

Эта небольшая статья будет об отправке запросов POST на сервер. Далеко не все программисты знают как отправлять такие запросы. На самом деле есть целых три способа. Я расскажу обо всех трех.

Сокеты прежде всего

Самый первый и самый правильный, на мой взгляд, способ – это способ с сокетами.

Для начала разберемся с теорией. Когда пользователь заполняет форму на сайте и нажимает на submit-кнопку формы, происходит следующее:

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

Именно в таком порядке происходит отправка формы с пост-запросом. Это и нужно повторить, только уже программными инструментами на php. Теперь давайте разберемся со всеми пунктами по порядку. Для того, чтобы открыть соединение с сервером, нужно использовать функцию fsockopen(). Вот ее сигнатура:

resource fsockopen ( string $target [, int $port [, int &$errno
[, string &$errstr [, float $timeout]]]] )

В функцию fsockopen передаются следующие параметры:

  • $target – это адрес (в нашем случае URL), к которому нужно открыть сокет;
  • $port – номер порта (обычно для протокола HTTP это 80 или 8080);
  • $errno – в эту переменную будет записан номер ошибки, если она произойдет. Заметьте символ & перед переменной в сигнатуре. В свое время я долго не мог понять, что значит такая запись. После изучения технологии ссылок все стало на свои места: на месте $errno в вызове функции должна быть передана переменная (значение не подойдет). Именно в эту переменную и будет записан номер ошибки;
  • $errstr – в эту переменную будет записана строка с ошибкой, если она произойдет;
  • $timeout – время ожидания соединении (таймаут) в секундах.

Функция вернет ресурс соединения или FALSE в случае ошибки. Если произошла ошибка, то в переменных $errno и $errstr можно посмотреть номер и текст ошибки соответственно.

После открытия соединения, нужно указать серверу метод запроса (POST), URI запроса, а также хост, с которого идет запрос.

Указывать будем с помощью обычной функции записи в файл fwrite().

int fwrite ( resource $handle, string $string [, int $length] )

Параметры:

  • $handle – указатель на открытый файл или соединение;
  • $string – строка, которую нужно записать в файл;
  • $length – размер записываемых данных. Если этот параметр опущен, то запишется вся строка $string

Функция вернет количество записанных байт или FALSE в случае ошибки.

Теперь разберемся с заголовками. Что такое заголовки? Если в двух словах – это специальный текст, который указывает серверу то или иное значение некоторых параметров. Не очень понятно, да? Тогда вот пример.

При посылке запроса обязательно нужно указать серверу тип передаваемых данных. В post-запросах тип имеет название application/x-www-form-urlencoded. Для того чтобы указать серверу тип, есть заголовок Content-type. В комплексе, заголовок и значение заголовка записываются через двоеточие, вот так: Content-type: application/x-www-form-urlencoded.

После каждого заголовка ОБЯЗАТЕЛЬНО должен быть перевод строки.

Для посылки обычного post-запроса, понадобятся следующие заголовки:

  • Content-type – заголовок, указывающий тип данных;
  • Content-length – заголовок, указывающий размер отправляемых данных
  • Accept – типы данных, которые может принимать браузер;
  • User-agent – имя браузера.

После отправки всех заголовков, ставится еще один перевод строки.

Данные, которые отправляются серверу достаточно просты по виду: varname1=value1&varname2=value2&varname3=value3. Да, именно такие как при отправке GET-запроса. После отправки данных, ставятся два перевода строк.

Прочитать ответ сервера можно функцией fgets(). Сигнатура ее проста и наверное всем известна:

string fgets ( resource $handle [, int $length] )

Параметры:

  • $handle – указатель на открытый файл или соединение;
  • $length – количество байт, которые нужно прочитать из ресурса $handle.

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

bool feof ( resource $handle )

Параметры:

  • $handle – указатель на открытый файл или соединение.

Вернет TRUE, если достигнут конец файла или ресурса и FALSE если конец еще не достигнут.

С помощью этих двух функций мы сможем прочитать весь ответ примерно таким образом:

while(!feof($handle)){
 
$body.=fgets($handle, 4096);
 
}

Закрыть соединение с сервером можно с помощью функции fclose().

bool fclose ( resource $handle )

Параметры:

  • $handle – указатель на открытый файл или соединение.

Вернет TRUE, если файл или соединение закрыты и FALSE, если закрыть не удалось.

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

test.php

<?php
 
echo 'Name:'.$_POST['name'],"<br>",'Surname:'.$_POST['surname'];
 
?>

Этот код выведет переменные name и surname, которые были переданные по методу POST.

Теперь собственно код отправки запроса.

socket.php

<?php
//открываем сокет к http://www.example.loc на 80-й порт с таймаутом в 30 секунд
$socket = fsockopen('www.example.loc', 80, $errno, $errstr, 30);
 
//если fsockopen вернула false, то завершаем работу скрипта и выводим текст и номер ошибки
if(!$socket)die("$errstr($errno)");
 
//собираем данные
$data = "name=".urlencode("John")."&surname=".urlencode("Smith");
 
//пишем в сокет метод, URI и протокол 
fwrite($socket, "POST /post/test.php HTTP/1.1\r\n");
//а также имя хоста
fwrite($socket, "Host: www.example.loc\r\n");
 
//теперь отправляем заголовки
//Content-type должен быть applicaion/x-www-form-urlencoded
fwrite($socket,"Content-type: application/x-www-form-urlencoded\r\n");
//размер передаваемых данных передаем в заголовке Content-length
fwrite($socket,"Content-length:".strlen($data)."\r\n");
//типы принимаемых данных. */* означает, что принимаем все типы данных
fwrite($socket,"Accept:*/*\r\n");
//представимся оперой
fwrite($socket,"User-agent:Opera 10.00\r\n");
fwrite($socket,"Connection:Close\r\n");
fwrite($socket,"\r\n");
 
//теперь передаем данные
fwrite($socket,"$data\r\n");
fwrite($socket,"\r\n");
 
//теперь читаем и выводим ответ
$answer = '';
while(!feof($socket)){
	$answer.= fgets($socket, 4096);
}
echo $answer;
 
//закрываем сокет
fclose($socket);
?>

Теперь если обратиться к файлу socket.php, то сервер выведет, приблизительно такой ответ

HTTP/1.1 200 OK
Date: Mon, 19 Oct 2009 09:26:53 GMT
Server: Apache/2.2.9 (Win32) PHP/5.2.11
X-Powered-By: PHP/5.2.11
Content-Length: 26
Content-Type: text/html

Name:John<br>Surname:Smith

Сервер также ответил с заголовками.
HTTP/1.1 200 OK – здесь указана версия протокола, код ответа (200) и текст ответа (ОК)
Date: Mon, 19 Oct 2009 09:26:53 GMT – дата
Server: Apache/2.2.9 (Win32) PHP/5.2.11 – имя сервера
X-Powered-By: PHP/5.2.11 – эту строчку добавил php
Content-Length: 26 – размер передаваемых данных в байтах
Content-Type: text/html – тип данных

Далее пустая строка и тело ответа.

На сегодня все. В следующий раз разберем отправку запросов с помощью библиотеки curl.

Читайте все статьи цикла.
В первой части рассматривается работа с сокетами.
Во второй части описывается библиотека cURL.
В третьей части можно почитать о работе с Stream Function.
В четвертой части описывается отправка файла через POST.
В пятой части описывается отправка POST-запросов в Zend Framework.

Популярность: 40%


Интересное из других блогов:

2leep.com

И не забывайте комментировать статью.

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

Отзывов: 37 на «Отправка POST запросов с помощью php. Часть 1»

  1. Автор: Slaffko, 21 января 2010 в 17:40

    использует Firefox 3.5.7 Firefox 3.5.7 на Windows XP Windows XP

    Спасибо за статью. У меня такой вопрос: как вывести только Name:JohnSurname:Smith без заголовков?

    • Автор: web-junior, 21 января 2010 в 17:55

      использует Firefox 3.5.7 Firefox 3.5.7 на Windows XP Windows XP

      Дело в том, что заголовки отделены от тела ответа пустой строкой. Поэтому можно поискать в ответе пустую строку. Вместо
      //теперь читаем и выводим ответ
      $answer = ”;
      while(!feof($socket)){
      $answer.= fgets($socket, 4096);
      }
      echo $answer;

      можно написать вот так
      //теперь читаем и выводим ответ
      $answer = ”;$headers = ”;
      while(!feof($socket)){
      $tmp = ”;
      $tmp = fgets($socket, 4096);
      if(strpos($headers,”\r\n\r\n”)==false){
      $headers.=$tmp;
      }else{
      $answer.=$tmp;
      }

      }
      echo $answer;

      теперь в $headers будут все заголовки, а в $answer – тело ответа

      • Автор: Slaffko, 21 января 2010 в 23:22

        использует Firefox 3.5.7 Firefox 3.5.7 на Windows XP Windows XP

        Спасибо за ответ.
        Еще вопрос: можно ли после отправки POST сделать редирект на адрес, на который был отправлен запрос?

        • Автор: web-junior, 22 января 2010 в 09:54

          использует Firefox 3.5.7 Firefox 3.5.7 на Windows XP Windows XP

          Можно.
          Для этого нужно отдать пользователю заголовок Location.
          Например:
          //отправляем запрос и получаем данные

          //теперь перенаправляем
          header(“Location:http://www.web-junior.net“);

          после этого пользователь пойдет на http://www.web-junior.net. Но есть одно условие: до отправки заголовка не должно быть никакого вывода, т.е. ничего нельзя выводить пользователю в браузе с помощью echo, print и т.д. Также нельзя выводить что-либо вне тегов < ?php ?>, поскольку при этом будут отсылаться автоматом другие заголовки и при попытке отправки любого заголовка, php будет отвечать ,что заголовки уже посланы.

          • Автор: VIPruless, 15 июня 2010 в 10:56

            использует Opera 9.27 Opera 9.27 на Windows XP Windows XP

            Скажите, а как отправить POST “вместе” с редиректом? Если следовать Вашему совету выше:

            Например:
            //отправляем запрос и получаем данные

            //теперь перенаправляем
            header(“Location:http://www.web-junior.net”);

            , то на новый адрес приходит пустой POST.

            Суть в чем? Я не хочу светить в форме некоторые данные. Поэтому есть необходимость отправить данные на другой сайт программно с одновременным редиректом. На том (другом) сайте от пользователя будут требоваться дополнительные телодвижения. Поэтому просто передать данные и получить ответ – не годится. Нужно именно отправить данные с одновременным редиректом (как браузер).

            Думаю, этот способ не годится (или я ошибаюсь?). Тогда какой?

            Спасибо.

          • Автор: web-junior, 15 июня 2010 в 14:49

            использует Firefox 3.5.9 Firefox 3.5.9 на Windows XP Windows XP

            Перенаправить пользователя вместе с постом наверное не получится. Поскольку POST – это запрос, а перенаправление (Location или коды 3xx) – это ответ.

            Если нужно добавить поля, которые пользователь не должен видеть, то можете использовать скрытый input в html-форме. Если нужна еще большая защита от ботов, то JS+base64+Ваша фантазия помогут решить это дело на стороне клиента.

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

  2. Автор: hrafn, 1 марта 2010 в 13:58

    использует Opera 9.26 Opera 9.26 на Windows XP Windows XP

    Доброго времени суток, у меня вопрос:
    почему скрипт ругается на 400 ошибку?

    в чем может быть косяк ?

    • Автор: web-junior, 1 марта 2010 в 14:16

      использует Firefox 3.6 Firefox 3.6 на Windows XP Windows XP

      Добрый день!
      Все ошибки, которые начинаются с 4 (400, 401, 402 и т.д.) – это ошибки клиента. В этом случае сервер указывает на какую-то ошибку клиента.
      В данном случае (400-я ошибка), сервер говорит о том, что был неверно сформирован запрос. Возможно наличие каких-то синтаксических ошибок, возможно недостает какого-то важного заголовка в запросе и т.п.
      Нужно изменить запрос и попробовать снова.

  3. Автор: hrafn, 3 марта 2010 в 12:45

    использует Opera 9.26 Opera 9.26 на Windows XP Windows XP

    Путем диких прыганий сбумном фсе заработало, вопрос не втему, как с помощь этого фсего принять xml ответ ибо он выводится не весь и без тегов, что посоветует?

    • Автор: web-junior, 3 марта 2010 в 13:21

      использует Firefox 3.6 Firefox 3.6 на Windows XP Windows XP

      Хотел бы узнать, а в чем была проблема, каких заголовков не хватало серверу в вашем случае?

      xml-ответ судя по всему должен быть на месте

      Name:John<br>Surname:Smith

      чтобы он вывелся полностью и xml-теги не интерпретировались браузером стоит перед выводом использовать функцию htmlspecialchars()
      если вы это имеете в виду.

      • Автор: hrafn, 3 марта 2010 в 13:25

        использует Opera 9.26 Opera 9.26 на Windows XP Windows XP

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

        За ответ спасибо все помогло)))

        Очень информативная статья)))

  4. Автор: hrafn, 3 марта 2010 в 12:56

    использует Opera 9.26 Opera 9.26 на Windows XP Windows XP

    в догонку снифер показал что сервер отпраил точно фсе как надо и ответ дошел полностью

  5. Автор: hrafn, 4 марта 2010 в 11:20

    использует Opera 9.26 Opera 9.26 на Windows XP Windows XP

    Ну чтож у меня еще вопрос но уже чуть другого характера)))
    Вылезает ошибка: “Fatal error: Maximum execution time of 30 seconds exceeded in”
    пробывал баловатся с такой функцией:stream_set_timeout($sock, 10); фсе равно время ожидания закрытия не меняется.
    Собственно сам вопрос: Как узнать кончился файл или если сервер не закрывает соединение на сокет?

    • Автор: web-junior, 5 марта 2010 в 10:13

      использует Firefox 3.6 Firefox 3.6 на Windows XP Windows XP

      Максимальное время ожидания (таймаут) задается функцией fsockopen при открытии сокета. Самый последний параметр float $timeout устанавливает время таймаута (см. сигнатуру функции выше в статье).
      А stream_set_timeout судя по всему предназначена не для срабатывания таймаута, а для проверки, произошел ли таймаут или нет.
      К сожалению, узнать про конец файла кроме как функцией feof, я не знаю. Хотя, если найдете, пожалуйста, расскажите мне об этом.

  6. Автор: Alex, 18 марта 2010 в 13:08

    использует Firefox 3.6 Firefox 3.6 на Windows XP Windows XP

    А как быть если надо отправить данные по HTTPS ?
    Какой будет номер порта и как указать https?

    • Автор: gosha, 23 марта 2010 в 01:09

      использует Firefox 3.5.8 Firefox 3.5.8 на Windows XP Windows XP

      $socket = fsockopen(‘www.example.loc’, 443, $errno, $errstr, 30); // 443 – это и будет номер порта для https

      fwrite($socket, “POST https://www.example.loc/post/test.php HTTP/1.1\r\n”); // а так укажешь https

  7. Автор: gosha, 23 марта 2010 в 01:05

    использует Firefox 3.5.8 Firefox 3.5.8 на Windows XP Windows XP

    Можно ли передать 2 post-запроса не закрывая соединения (закрывая сокета)? При этом считывая ответ от сервера?

    • Автор: web-junior, 23 марта 2010 в 11:14

      использует Firefox 3.5.8 Firefox 3.5.8 на Windows XP Windows XP

      передать два пост-запроса не закрывая сокета, можно. Если передавать оба запроса по порядку одному и тому же серверу (тому, к которому открывали сокет), то сервер без проблем примет, обработает и ответит на оба запроса.
      К сожалению, при этом невозможно будет считывать ответ после отсылки каждого запроса. Это связано с тем, что при считывании ответа сокет достигает конца (как файл) и больше с ним ничего не получается сделать: ни записать в него, ни прочитать.
      Поэтому считать ответы на оба запроса можно будет после отсылки обоих запросов.

  8. Автор: Rustyle, 24 марта 2010 в 20:47

    использует Google Chrome 4.1.249.1036 Google Chrome 4.1.249.1036 на Windows 7 Windows 7

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

    Вопрос: почему большая задержка(таймаут регулировал) и почему код работает только при отправке на мои страницы.

    • Автор: web-junior, 24 марта 2010 в 21:39

      использует Firefox 3.5.8 Firefox 3.5.8 на Windows XP Windows XP

      Если я все правильно понял, то может быть в этом случае сервер ставит какие-либо куки или сессионные данные, которые не отправляет скрипт и отправляет браузер.
      Попробуйте разобраться с этим моментом – он порой бывает очень важен.

RSS-лента комментариев. Адрес для трекбека

Ваш отзыв

Вы можете использовать следующие теги: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="" highlight="">

Нажимая на кнопку "Добавить" вы принимаете правила комментирования