Отправка 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И не забывайте комментировать статью.
Добавляйся в группу во вконтакте, чтобы самым первым узнавать все новости сайта
Автор: Slaffko, 21 января 2010 в 17:40
используетСпасибо за статью. У меня такой вопрос: как вывести только Name:JohnSurname:Smith без заголовков?
Автор: web-junior, 21 января 2010 в 17:55
используетДело в том, что заголовки отделены от тела ответа пустой строкой. Поэтому можно поискать в ответе пустую строку. Вместо
//теперь читаем и выводим ответ
$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
используетСпасибо за ответ.
Еще вопрос: можно ли после отправки POST сделать редирект на адрес, на который был отправлен запрос?
Автор: web-junior, 22 января 2010 в 09:54
используетМожно.
Для этого нужно отдать пользователю заголовок Location.
Например:
//отправляем запрос и получаем данные
…
//теперь перенаправляем
header(“Location:http://www.web-junior.net“);
после этого пользователь пойдет на http://www.web-junior.net. Но есть одно условие: до отправки заголовка не должно быть никакого вывода, т.е. ничего нельзя выводить пользователю в браузе с помощью echo, print и т.д. Также нельзя выводить что-либо вне тегов < ?php ?>, поскольку при этом будут отсылаться автоматом другие заголовки и при попытке отправки любого заголовка, php будет отвечать ,что заголовки уже посланы.
Автор: VIPruless, 15 июня 2010 в 10:56
используетСкажите, а как отправить POST “вместе” с редиректом? Если следовать Вашему совету выше:
Например:
//отправляем запрос и получаем данные
…
//теперь перенаправляем
header(“Location:http://www.web-junior.net”);
, то на новый адрес приходит пустой POST.
Суть в чем? Я не хочу светить в форме некоторые данные. Поэтому есть необходимость отправить данные на другой сайт программно с одновременным редиректом. На том (другом) сайте от пользователя будут требоваться дополнительные телодвижения. Поэтому просто передать данные и получить ответ – не годится. Нужно именно отправить данные с одновременным редиректом (как браузер).
Думаю, этот способ не годится (или я ошибаюсь?). Тогда какой?
Спасибо.
Автор: web-junior, 15 июня 2010 в 14:49
используетПеренаправить пользователя вместе с постом наверное не получится. Поскольку POST – это запрос, а перенаправление (Location или коды 3xx) – это ответ.
Если нужно добавить поля, которые пользователь не должен видеть, то можете использовать скрытый input в html-форме. Если нужна еще большая защита от ботов, то JS+base64+Ваша фантазия помогут решить это дело на стороне клиента.
Если же нет уверенности в надежности JS, то можно сделать скрипт который добавит к присланным пост-данным дополнительные данные, отошлет их дальше, прочтет ответ и выведет этот ответ клиенту. Т.е. можно реализовать по способу проксирования.
Автор: hrafn, 1 марта 2010 в 13:58
используетДоброго времени суток, у меня вопрос:
почему скрипт ругается на 400 ошибку?
в чем может быть косяк ?
Автор: web-junior, 1 марта 2010 в 14:16
используетДобрый день!
Все ошибки, которые начинаются с 4 (400, 401, 402 и т.д.) – это ошибки клиента. В этом случае сервер указывает на какую-то ошибку клиента.
В данном случае (400-я ошибка), сервер говорит о том, что был неверно сформирован запрос. Возможно наличие каких-то синтаксических ошибок, возможно недостает какого-то важного заголовка в запросе и т.п.
Нужно изменить запрос и попробовать снова.
Автор: hrafn, 3 марта 2010 в 12:45
используетПутем диких прыганий сбумном фсе заработало, вопрос не втему, как с помощь этого фсего принять xml ответ ибо он выводится не весь и без тегов, что посоветует?
Автор: web-junior, 3 марта 2010 в 13:21
используетХотел бы узнать, а в чем была проблема, каких заголовков не хватало серверу в вашем случае?
xml-ответ судя по всему должен быть на месте
чтобы он вывелся полностью и xml-теги не интерпретировались браузером стоит перед выводом использовать функцию htmlspecialchars()
если вы это имеете в виду.
Автор: hrafn, 3 марта 2010 в 13:25
используетЧестно говоря уже не помню что и где делал, менял. Но основной косяк это был промежуточный прокси сервер. Но и пара ошибок в коде дико извеняюсь но уже не помню какие и где(((
За ответ спасибо все помогло)))
Очень информативная статья)))
Автор: web-junior, 3 марта 2010 в 13:29
используетзаходите ещё =)
Автор: hrafn, 3 марта 2010 в 12:56
используетв догонку снифер показал что сервер отпраил точно фсе как надо и ответ дошел полностью
Автор: hrafn, 4 марта 2010 в 11:20
используетНу чтож у меня еще вопрос но уже чуть другого характера)))
Вылезает ошибка: “Fatal error: Maximum execution time of 30 seconds exceeded in”
пробывал баловатся с такой функцией:stream_set_timeout($sock, 10); фсе равно время ожидания закрытия не меняется.
Собственно сам вопрос: Как узнать кончился файл или если сервер не закрывает соединение на сокет?
Автор: web-junior, 5 марта 2010 в 10:13
используетМаксимальное время ожидания (таймаут) задается функцией fsockopen при открытии сокета. Самый последний параметр float $timeout устанавливает время таймаута (см. сигнатуру функции выше в статье).
А stream_set_timeout судя по всему предназначена не для срабатывания таймаута, а для проверки, произошел ли таймаут или нет.
К сожалению, узнать про конец файла кроме как функцией feof, я не знаю. Хотя, если найдете, пожалуйста, расскажите мне об этом.
Автор: Alex, 18 марта 2010 в 13:08
используетА как быть если надо отправить данные по HTTPS ?
Какой будет номер порта и как указать https?
Автор: gosha, 23 марта 2010 в 01:09
использует$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
Автор: gosha, 23 марта 2010 в 01:05
используетМожно ли передать 2 post-запроса не закрывая соединения (закрывая сокета)? При этом считывая ответ от сервера?
Автор: web-junior, 23 марта 2010 в 11:14
используетпередать два пост-запроса не закрывая сокета, можно. Если передавать оба запроса по порядку одному и тому же серверу (тому, к которому открывали сокет), то сервер без проблем примет, обработает и ответит на оба запроса.
К сожалению, при этом невозможно будет считывать ответ после отсылки каждого запроса. Это связано с тем, что при считывании ответа сокет достигает конца (как файл) и больше с ним ничего не получается сделать: ни записать в него, ни прочитать.
Поэтому считать ответы на оба запроса можно будет после отсылки обоих запросов.
Автор: Rustyle, 24 марта 2010 в 20:47
используетУ меня очень странная ситуация, отправляю пост запросы по истечении секунд 50-60 веб страница не доступна, при этом сайт рабочий(проверил через обычную форму грузит быстро).
Тоже самое отправляю на свой сервер , с вышеуказанной задержкой но работает только не совсем корректно (тоесть некоторые ссылки на странице ответа стали зачеркнутыми)
Вопрос: почему большая задержка(таймаут регулировал) и почему код работает только при отправке на мои страницы.
Автор: web-junior, 24 марта 2010 в 21:39
используетЕсли я все правильно понял, то может быть в этом случае сервер ставит какие-либо куки или сессионные данные, которые не отправляет скрипт и отправляет браузер.
Попробуйте разобраться с этим моментом – он порой бывает очень важен.