Не так давно, столкнулся с задачей отправки формы, содержащей поля типа file на сервер методом post. Поля типа “файл” (это я хорошо сказал :) ) - чтобы было понятно, это форма, содержащая, среди прочих, теги file. И все бы ничего, но пришлось немного покопаться в мануалах и пресловутых RFC, собственно, результатами своих творческих изысканий в документации я и хочу сегодня поделиться.


Несколько вводных замечаний:

1. Я буду показывать пример с использованием библиотеки cURL. Если по какой-то причине вы ее не используете, то, исходя из общих принципов изложенных здесь, сможете переделать код под использование на сокетах.

2. Полезностью в плане общевебпрограммерской эрудиции, в нашем случае, будет чтение раздела 7.2 RFC 1341, который описывается Multipart Content-Type и используется при передаче файлов из формы методом POST.

Итак, теперь по сути вопроса.

Как говорит нам php manual раздел V.38 Handling file uploads, чтобы форма корректно передавала данные на сервер, в атрибутах тега form обязательно должен быть указан enctype="multipart/form-data" , или другими словами нам необходимо применить тип кодирования данных формы multipart/form-data. Кто может быть не в курсе, по умолчанию данные формы, при передаче на сервер кодируются как application/x-www-form-urlencoded, т.е. мы, как правило, не указываем никакого enctype, и все отлично передается именно как application/x-www-form-urlencoded.

Не вдаваясь в подробности application/x-www-form-urlencoded, это все та же строка вида:

<параметр1>=<значение1>&<параметр2>=<значение2>&<параметр3>=<значение3> и т.д.,

которая передается и в GET запросе, только, в отличие от GET, уже не ограничена 255 символами. Подчеркиваю, строка - это именно строковые данные или данные, которые могут быть приведены к этому виду.


А что же такое multipart/form-data?

Как говорит нам вышеупомянутый RFC 1341, этот тип кодирования используется, когда сообщение (у нас это POST запрос) объединяет разнородные данные в едином теле (в нашем случае в контентной части нашего запроса). При этом тело сообщения (контентная часть POST запроса) должно содержать одну или более частей, которые отделены друг от друга заданным ограничителем. Каждая такая часть сообщения начинается с ограничителя, за которым идут поля заголовка именно этой части и собственно ее тело. Для определения конца всего сообщения используется все тот же заданный ограничитель. Это, вобщем, мой довольно свободный перевод обще-вводной части раздела RFC, посвященного Multipart Content-Type. Я постарался описать это понятным языком, но читатель вправе вынести свое суждение и не согласится со моим трактованием документа. :)

Теперь собственно практический пример, того о чем я писал выше.

// сабмитим форму загрузки файла

// определяем разделитель
$boundary = &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;---------------------------&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;.mktime();

// формируем тело запроса (контентную часть)
// будем передавать два поля: file1 - это собственно наш файл и comment - поле простого текстового коментария
// поле файла
$data = &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;--&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;.$boundary.&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;\r\n&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;;
$data .= &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;Content-Disposition: form-data; name=\&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;file1\&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;; filename=\&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;yourfile.txt\&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;\r\n
Content-Type: text/plain; charset=windows-1251\r\n\r\n&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;;
$data .= file_get_contents(&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;yourfile.txt&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;).&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;\r\n&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;;
$data .= &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;--&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;.$boundary.&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;\r\n&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;;

// поле комментария
$data .= &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;Content-Disposition: form-data; name=\&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;comment\&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;\r\n\r\n&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;;
$data .= &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;Ваш текстовый комментарий к файлу&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;.&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;\r\n&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;;
$data .= &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;--&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;.$boundary.&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;--\r\n&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;;

// массив полей заголовка запроса
$header_fields = array(	&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;Content-Type: multipart/form-data; boundary=$boundary&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;,
&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;Content-Length: &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;.strlen($data));
// url скрипта который принимает данные формы (поле action тега form)
$form_url = &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;http://yourdomain/fileupload.php&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;;

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $form_url);
curl_setopt($ch, CURLOPT_HEADER, 1);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HTTPHEADER, $header_fields);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
$res = curl_exec($ch);

Как вы видите из приведенного примера, сначала мы определяем разделитель (переменная $boundary), затем формируем контентную часть нашего запроса (переменная $data). Контент в нашем случае будет состоять из двух частей (проще говоря переменных). Первая file1 - это наш файл, который мы посылаем на сервер, и который будет обрабатываться в php через массив $_FILES. Вторая comment - поле простого текстового комментария (например, оформленного тегами input или textarea), и которая будет видна в массиве $_POST. Не трудно заметить, что каждая часть, как и говорилось выше, начинается с разделителя, представленного переменной $boundary, за которым идут поля заголовка данной части.

В нашем примере, в заголовках для отправляемого файла мы указали:
Content-Disposition: form-data; name=file1; filename=yourfile.txt
Content-Type: text/plain; charset=windows-1251

т.е. в поле заголовка Content-Disposition указываем, что это у нас form-data и собственно имя поля формы name=file1, а также имя нашего файла filename=yourfile.txt, которое и будет доступно по ключу в массиве $_FILES['file1']['name']

А для поля comment мы указали лишь:
Content-Disposition: form-data; name=comment
Т.е. это лишь определение имени поля формы, значение которого будет доступно по ключу в массиве $_POST['comment']. При этом следует отметить, что согласно RFC 822 и описанию данному в разделе 4 The Content-Type Header Field RFC 1341, если не указано иного, то по умолчанию используется Content-type: text/plain; charset=us-ascii.

А после заголовка каждого поля в контентной части идет непосредственно содержимое.
В нашем примере, для поля file1 вычитываем содержимое файла через file_get_contents, которая, кстати сказать, является безопасной по отношению к бинарным данным, т.е. этой функцией можно читать все файлы.

А для поля comment указываем в качестве содержимого строку "Ваш текстовый комментарий к файлу"


Далее мы указываем некоторые поля общего заголовка нашего запроса как:
Content-Type: multipart/form-data; boundary=$boundary
Content-Length: strlen($data)
и собственно отправляем POST запрос функцией библиотеки CURL

На принимающей стороне скрипт http://yourdomain/fileupload.php увидит наши данные в полях массивов $_FILES['file1'] и $_POST['comment']

Вот собственно и вся техника, спасибо за внимание и до новых встреч в эфире. :)

google.com bobrdobr.ru del.icio.us technorati.com linkstore.ru news2.ru rumarkz.ru memori.ru moemesto.ru

Понравился пост подпишись по RSS 

Оставить комментарий