CURL. Отправка файла на сервер методом POST
Не так давно, столкнулся с задачей отправки формы, содержащей поля типа 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. Я постарался описать это понятным языком, но читатель вправе вынести свое суждение и не согласится со моим трактованием документа. :) Теперь собственно практический пример, того о чем я писал выше. [php] // сабмитим форму загрузки файла // определяем разделитель $boundary = "---------------------------".mktime(); // формируем тело запроса (контентную часть) // будем передавать два поля: file1 - это собственно наш файл и comment - поле простого текстового коментария // поле файла $data = "--".$boundary."\r\n" $data .= "Content-Disposition: form-data; name=\"file1\" filename=\"yourfile.txt\"\r\n Content-Type: text/plain; charset=windows-1251\r\n\r\n" $data .= file_get_contents("yourfile.txt")."\r\n" $data .= "--".$boundary."\r\n" // поле комментария $data .= &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;quot;comment\&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;quot;; $data .= &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;Ваш текстовый комментарий к файлу&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;.&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;\r\n&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;; $data .= &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;--&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;.$boundary.&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;--\r\n&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;; // массив полей заголовка запроса $header_fields = array( &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;quot;, &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;quot;Content-Length: &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;quot;http://yourdomain/fileupload.php&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); [/php] Как вы видите из приведенного примера, сначала мы определяем разделитель (переменная $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'] Вот собственно и вся техника, спасибо за внимание и до новых встреч в эфире. :)
