Отправка POST запросов с помощью php. Отправка файлов

curl-refinedНемного раньше мы рассмотрели отправку пост запросов в PHP с помощью сокетов, с помощью библиотеки curl и с помощью врапперов потока . В этих статьях были приведены конкретные примеры программной отправки переменных по методу POST. Но иногда возникает ситуация, когда программно нужно отправить не только переменные, но и еще отправить произвольный файл по протоколу HTTP. Именно эту конкретную ситуацию мы и рассмотрим сегодня.
В протоколе HTTP предусмотрены два способа отправки файлов. По методам PUT и POST. Сегодня речь пойдет об отправке файлов по методу POST.

Пример
Для начала напишем тестовый пример, который выведет все данные, которые мы будем отправлять программно.
test.php

<?php
if(isset($_FILES['my_file']['name'])&&$_FILES['my_file']['size']>0
&&$_FILES['my_file']['error']==0){
echo 'Name:'.$_POST['name'].' Surname:'.$_POST['surname'].'<br/>';
readfile($_FILES['my_file']['tmp_name']);
}else{
echo 'Ошибка: не был получен файл';
}
?>

Сначала проверяем, был ли закачан файл посредством проверки данных в массиве $_FILES. Если файл успешно закачан, то выводим пост-переменные name и surname, после чего выводим содержимое загруженного файла.
Файл я расположил на локальном сервере по адресу http://www.example.loc/test.php. Еще для тестов я взял jpeg-файл, test_file.jpg, который положил рядом.

Форма
Чтобы уяснить некоторые нюансы отправки файлов по методу post, давайте напишем небольшую html-форму, которая смогла бы справиться с загрузкой файла и установки двух переменных. Вот код формы.
form.html

<form action= "http://www.example.loc/test.php" 
name= "upload" method="post" 
enctype="multipart/form-data">
Name:<input type="text" /><br/>
Surname:<input type="text" /><br/>
File:<input type="file" /><br/>
<input type="submit" value="Submit" />
</form>

Эта форма вполне может справиться с отправкой данных и файла за счет браузера. Прошу обратить внимание на параметр enctype тега form. Этот параметр по умолчанию имеет значение application/x-www-form-urlencoded и обычно не пишется. Для отправки файла этот параметр нужно установить в значение multipart/form-data. Этот параметр на самом деле указывает браузеру на тип данных, которые будут переданы. В соответствии с этим параметром устанавливается заголовок Content-type (подробнее про заголовки см. в первой статье по отправке пост запросов).

Структура
Когда мы пытаемся отправить файл, то структура тела запроса меняется. Теперь это не просто query-string (имя_переменной1=значение&имя_переменной2=значение…). Все тело запроса разделяется на несколько частей. Одна часть – это файл, вторая часть – это первая переменная, третья часть – это вторая переменная и т.д.
Каждая часть состоит из заголовков и тела. Заголовки отделяются от тела пустой строкой. Каждый заголовок написан на отдельной строке. Первым заголовком здесь является Content-Disposition. Записывается так:
Content-Disposition:form-data;name=”имя_переменной
Если в этой части находится файл, то через точку с запятой ; после name в Content-Disposition записывается имя файла вот так
Content-Disposition:form-data;name=”file”;filename=”file.jpg”
Если в этой части находится файл, то добавляются заголовки Content-type и Content-Transfer-Encoding. Последний устанавливается в binary.
В начале каждой части на отдельной строке вписывается специальный разделитель. Разделитель начинается с --, а затем без пробелов какой-либо уникальный текст. В php он обычно генерируется так

$boundary = md5(uniqid(time()));

Этот код генерирует такого рода уникальный разделитель.
ce77f5f41b001bd35088f9a3fd6c957a
После последней части на отдельной строке пишется разделитель, перед и после которого добавляется --, вот так:
--ce77f5f41b001bd35088f9a3fd6c957a--
Этот разделитель указывается в заголовке Content-type через точку с запятой,вот так
Content-type:multipart/form-data;boundary= ce77f5f41b001bd35088f9a3fd6c957a
Теперь, ознакомившись с основами, можно приступать к конкретным примерам.

Сокеты
Для начала рассмотрим пример отправки файла с помощью сокетов. При отправке файла этим способом придется собрать все заголовки.
socket.php

<?php
//открываем сокет к www.example.loc на 80-й порт 
//с таймаутом в 30 секунд
$socket = fsockopen('www.example.loc', 80, $errno, $errstr, 30);
//если fsockopen вернула false, то завершаем работу 
//скрипта и выводим текст 
//и номер ошибки
if(!$socket)die("$errstr($errno)");
//разделитель
$boundary = md5(uniqid(time()));
/*собираем часть с файлом:
сначала разделитель\r\n
со следующей строки заголовки\r\n
потом пустая строка\r\n
после чего сам файл\r\n
*/
$file = "--$boundary\r\n".
"Content-Disposition: form-data; name=\"my_file\";".
" filename=\"test_file.jpg\"\r\n".
"Content-Type: image/jpeg\r\n".
"Content-Transfer-Encoding: binary\r\n\r\n";
$file.= file_get_contents(dirname(__FILE__).DIRECTORY_SEPARATOR.
'test_file.jpg');
$file.="\r\n";
/*
как и файл, собираем переменные
сначала разделитель\r\n
со следующей строки заголовки\r\n
пустая строка\r\n
потом значение переменной\r\n
*/
$var1 = "--$boundary\r\nContent-Disposition: '.
'form-data; name=\"name\"\r\n\r\n".
urlencode("John")."\r\n";
$var2 = "--$boundary\r\nContent-Disposition:'.
'form-data;name=\"surname\"\r\n\r\n".
urlencode("Smith")."\r\n";
//пишем в сокет метод, URI и протокол
fwrite($socket, "POST /test.php HTTP/1.1\r\n");
//а также имя хоста
fwrite($socket, "Host: www.example.loc\r\n");
//представимся оперой
fwrite($socket,"User-agent:Opera 10.00\r\n");
fwrite($socket, "Connection: close\r\n");
//теперь отправляем заголовки
//Content-type должен быть multipart/form-data, 
//также должен быть указан разделитель, 
//который мы сгенерировали выше
fwrite($socket,"Content-Type: '.
'multipart/form-data; boundary=$boundary\r\n");
//размер передаваемых данных передаем в заголовке 
//Content-length
fwrite($socket,"Content-length:".(strlen($file)+strlen($var1)+
strlen($var2))."\r\n");
//типы принимаемых данных. */* 
//означает, что принимаем все типы данных
fwrite($socket,"Accept:*/*\r\n");
fwrite($socket,"\r\n");
//теперь передаем данные
//передаем файл
fwrite($socket,"$file");
//и переменные
fwrite($socket, "$var1$var2");
//в конце разделитель
fwrite($socket,"--$boundary--\r\n");
//и пустая строка
fwrite($socket, "\r\n");
//теперь читаем и выводим ответ
$answer = '';
while(!feof($socket)){
$answer= fgets($socket, 4096);
echo $answer;
}
//закрываем сокет
fclose($socket);
?>

Сохранив его на локальном сервере и обратившись к нему из браузера можно увидеть приблизительно такую картину

HTTP/1.1 200 OK Date: Sun, 06 Dec 2009 19:02:01 GMT Server: Apache/2.2.9 (Win32) PHP/5.2.11 X-Powered-By: PHP/5.2.11 Connection: close Transfer-Encoding: chunked Content-Type: text/html Name:John Surname:Smith

╪ р

…………………….

Далее идет вывод jpeg-файла. Если бы не вывод заголовков и вывод пост-переменных, то отобразилась бы картинка.

Curl
В библиотеке curl проблем с отправкой пост-переменных нет. А вот как только задашься целью отправить файл, так тут и поджидает подвох. Как же быть с отправкой файла? Куда же нужно устанавливать файл?
Ответ очень прост. В CURLOPT_POSTFIELDS все данные передаются массивом, а в поле, в котором будет файл передается путь к файлу, а перед значением пути ставится @.
curl.php

<?php
//инициализируем сеанс
$curl = curl_init();
//уcтанавливаем урл, к которому обратимся
curl_setopt($curl, CURLOPT_URL, 
'http://www.example.loc/test.php');
//включаем вывод заголовков
curl_setopt($curl, CURLOPT_HEADER, 1);
//передаем данные по методу post
curl_setopt($curl, CURLOPT_POST, 1);
//теперь curl вернет нам ответ, а не выведет
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
/*переменные, которые будут переданные по методу post
Перед именем файла ставится @
*/
curl_setopt($curl, CURLOPT_POSTFIELDS,
array('name'=>urlencode('John'),
'surname'=>urlencode('Smith'),
'my_file'=>'@'.dirname(__FILE__).
DIRECTORY_SEPARATOR.'test_file.jpg'
)
);
//я не скрипт, я браузер опера
curl_setopt($curl, CURLOPT_USERAGENT, 'Opera 10.00');
$res = curl_exec($curl);
//проверяем, если ошибка, то получаем номер и сообщение
if(!$res){
$error = curl_error($curl).'('.curl_errno($curl).')';
echo $error;
}
//если не ошибка, то выводим результат
else{
echo $res;
}
curl_close($curl);
?>

В результате как и в прошлый раз выведет заголовки, значение пост-переменных и jpeg-файл
HTTP/1.1 100 Continue HTTP/1.1 200 OK Date: Sun, 06 Dec 2009 19:13:43 GMT Server: Apache/2.2.9 (Win32) PHP/5.2.11 X-Powered-By: PHP/5.2.11 Transfer-Encoding: chunked Content-Type: text/html Name:John Surname:Smith

╪ р

………………………

Как выяснилось, курл не умеет правильно устанавливать тип файла. Во всех случаях он ставит тип application/octet-stream. Поэтому в некоторых случаях сервер будет выдавать ошибку связанную с типом файла.

Stream
Теперь попробуем совладать с врапперами для потоков. Для врапперов нужно собирать почти все заголовки как и в случае с сокетами.
context.php

<?php
$boundary = md5(uniqid(time()));
$file = "--$boundary\r\nContent-Disposition:".
" form-data; name=\"my_file\";".
" filename=\"test_file.jpg\"\r\n"."Content-Type: image/jpeg\r\n".
"Content-Transfer-Encoding: binary\r\n\r\n";
$file.= file_get_contents(dirname(__FILE__).
DIRECTORY_SEPARATOR.'test_file.jpg');
$file.="\r\n";
//собираем переменные
$var1 = "--$boundary\r\nContent-Disposition:".
" form-data; name=\"name\"\r\n\r\n".
urlencode("John")."\r\n";
$var2 = "--$boundary\r\nContent-Disposition:".
"form-data;name=\"surname\"\r\n\r\n".
urlencode("Smith")."\r\n";
//опции для создания контекста
$c_options = array(
//имя враппера - http
'http'=>array(
//метод - POST
'method'=>'POST',
//заголовки
'header'=>"Content-type: ".
"multipart/form-data;boundary=$boundary\r\n".
"User-agent:Opera 10.00\r\nContent-length:".
(strlen($file)+strlen($var1)+strlen($var2)),
//данные
'content'=>$file.$var1.$var2."--$boundary--\r\n\r\n"
)
);
//создаем контекст
$context = stream_context_create($c_options);
//читаем файл с контекстом
$res = file_get_contents('http://www.example.loc/test.php',
false,$context);
//выводим результат
echo $res;
?>

В результате выведет
Name:John Surname:Smith

╪ р

……………………

Что собственно и ожидали от сервера.

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

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


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

2leep.com

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

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

Отзывов: 30 на «Отправка POST запросов с помощью php. Отправка файлов»

  1. Автор: Herkules, 22 января 2010 в 00:41

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

    Уже незнаю что мне делать, отправляю POST запрос multipart’овый. вот как он выглядит:
    $post = array(
    ‘a’ => ‘addfoto’,
    ‘s’ => ”,
    ‘d’ => ’1′,
    ‘to’ => ”,
    ‘addfile’ => ‘@’.dirname(__FILE__).DIRECTORY_SEPARATOR.’xxx.jpg’,
    ‘name’ => ‘Моя фотка’,
    ‘Submit’ => ‘Добавить фотографию’
    );
    Но после отправки мне на странице выбевает каждый раз вы не выбрали файл для отправки и поле пустое типа. Что это может быть? Куки все выловил, заголовки такие же самые получаются как и при оправке вручную. Полный капец.

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

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

      Не могли бы вы мне по секрету (email на страничке О блоге находится) рассказать куда отправляете, чтоб я мог просмотреть в чем проблема, так сказать, на месте.
      Кроме того, вы можете использовать другие способы отправки файлов: сокеты и врапперы потока тоже неплохо отправляют файлы.

      • Автор: Вова, 5 мая 2010 в 18:28

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

        Столкнулся с такой же проблемой. Если есть решение можно на емаил?

        • Автор: web-junior, 5 мая 2010 в 19:14

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

          там проблема была в том, что сервер проверял тип передаваемых данных и допускал только image/jpeg, image/jpg, image/png, image/gif. А curl, который был использован в этом случае всегда слал тип application/octet-stream. Поэтому сервер отказывал в загрузке файла такого типа.
          Для исправления этой ошибки нужно использовать другой способ отправки файла.

          • Автор: Вова, 6 мая 2010 в 06:41

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

            хм… а почему тогда если запускать тот же скрипт на денвере, то загрузка фотки проходит нормально?

          • Автор: web-junior, 6 мая 2010 в 10:34

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

            а не могли бы вы подробнее описать вашу проблему?

  2. Автор: Ninja, 24 января 2010 в 11:52

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

    Здравствуйте! Почему-то не выходит залить файл на сервер методом post через curl. На сайте стоит форма enctype multipart/form-data. Получаю в ответ странный заголовок –
    Authorization: Basic Og==
    Что вы можете посоветовать по этому поводу?

    • Автор: web-junior, 24 января 2010 в 12:06

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

      а какой код ответа присылает? Случайно не 401-й?

  3. Автор: Ninja, 24 января 2010 в 12:45

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

    Отправил вам письмо с описанием проблемы и самим кодом.
    Может там просто невозможно залить файл?!…..

  4. Автор: Ninja, 25 января 2010 в 18:12

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

    А вы можете сказать чем смотрите заголовки?
    Я использую плагины для FF такие как FireBug и HttpFox, но они оба показывают что там тип данных multipart/form-data.

  5. Автор: Ninja, 29 января 2010 в 15:25

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

    Добрый день! Ищу информацию и пока без результатов. Интересует работа с сокетами через прокси. В курле с этим попроще будет. Можете что-то подсказать по этому поводу?

    • Автор: web-junior, 29 января 2010 в 16:16

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

      Добрый день!
      С HTTP-прокси работать не сложно. Весь смысл работы через этот вид прокси заключается в отсылке HTTP-запроса к прокси-серверу. Прокси сам отсылает запрос куда нужно, получает результат и возвращает его клиенту. На php это будет выглядеть не сложно:
      //HTTP-запрос
      $msg = “GET / HTTP/1.1
      Host: http://www.ya.ru
      User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; ru; rv:1.9.1.2) Gecko/20090729 Firefox/3.5.2
      Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
      Accept-Language: ru,en-us;q=0.7,en;q=0.3
      Accept-Charset: windows-1251,utf-8;q=0.7,*;q=0.7
      Keep-Alive: 300
      Proxy-Connection: keep-alive\r\n\r\n”;

      //открываем соединение с прокси
      $fp = fsockopen(’118.96.121.141′,80,$erno, $errstr);
      //отправляем запрос
      fwrite($fp,$msg,strlen($msg));
      //читаем результат
      while(FALSE!=($r = fread($fp,1024))){
      echo $r;
      }
      fclose($fp);

      Пока прокси 118.96.121.141 работает мы получаем страничку Яндекса.

      С SOCKS-прокси работать сложнее. Этот вид прокси разрабатывали как универсальный-прокси, который будет работать и с текстовыми приложениями и двоичными приложениями. Поэтому он общается в двоичном режиме. Подробнее вы можете почитать мою статью по работе с SOCKS5-прокси.

      • Автор: hrafn, 7 марта 2010 в 20:56

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

        А как насчет отправки POST запросов через file_get_contents() и все это дело через прокси? у меня есть пример отправки через прокси но там GET а переделать что то не получается(((

        • Автор: web-junior, 7 марта 2010 в 21:14

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

          У меня без проблем пошел пост-запрос через прокси. Я вот такие опции передавал контексту

          //опции для создания контекста
          $c_options = array(
          //имя враппера – http
          ‘http’=>array(
          ‘proxy’ => ‘tcp://222.124.158.163:8080′,
          ‘request_fulluri’ => true,
          //метод – POST
          ‘method’=>’POST’,
          //заголовки
          ‘header’=>”Content-type: application/x-www-form-urlencoded\r\nUser-agent:Opera 10.00\r\nContent-length:”.strlen($post),
          //данные
          ‘content’=>$post
          )
          );
          ….
          далее так, как показано выше в статье

  6. Автор: Ninja, 30 января 2010 в 17:06

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

    Большое спасибо! Очень помогли!

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

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

      Пожалуйста. Обращайтесь еще – с удовольствием вам помогу.

  7. Автор: Terminator, 25 мая 2010 в 13:02

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

    Здравствуйте! А что делать если на сайте формы в формате XForms? Можно ли совладать с ними при помощи php?

    • Автор: web-junior, 25 мая 2010 в 17:54

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

      Ну XForms тоже ведь отсылают данные по протоколу HTTP. Другой альтернативы просто нет пока что.

  8. Автор: artbox, 15 декабря 2010 в 12:48

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

    Подскажите плиз 2 вопроса.. (по curl)
    1. передаются нормально только латинские символы, пробелы, собачки, русские – всякими значками. Подозреваю что проблема в кодировке но как быть не знаю. Оригинальная страница в UTF-8, свой php скрипт пробовал сохранять и в UTF-8 и Win 1251… все одно
    2. не совсем понял как правильно писать путь к файлу, если к примеру он у меня на сервере по пути /pic/img1.jpg (т.е. не понял вот это dirname(__FILE__).
    DIRECTORY_SEPARATOR.’test_file.jpg’)

    Буду признателен за совет. Спасибо!

    • Автор: web-junior, 16 декабря 2010 в 11:08

      использует Firefox 3.6.12 Firefox 3.6.12 на Windows 7 Windows 7

      Здравствуйте.
      1. Для корректной передачи по протоколу HTTP, данные кодируются в специальный формат с помощью функции urlencode(). Для раскодирования используйте функцию urldecode() вот так:

      var_dump(urldecode($_POST['name']));

      2. Нужен путь к файлу не тот, по которому он открывается в браузере. Нужен путь в файловой системе, поскольку скрипт работает на стороне сервера. dirname(__FILE__).DIRECTORY_SEPARATOR.’test_file.jpg’ – это всего-лишь один из способов обратиться к файлу test_file.jpg, расположенного рядом со скриптом, при этом не зная где именно (в какой папке) находится скрипт.

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="">

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