Это то, что меня раздражало какое-то время. Я создаю API RESTful, который должен получать файлы в некоторых случаях.
При использовании HTTP POST
мы можем читать data from $_POST
и files from $_FILES
.
При использовании HTTP GET
мы можем читать data from $_GET
и files from $_FILES
.
Однако при использовании HTTP PUT
AFAIK единственным способом чтения данных является использование php://input stream
.
Все хорошо и хорошо, пока я не хочу отправить файл через HTTP PUT. Теперь поток ввода php: // работает не так, как ожидалось, так как в нем есть файл.
Вот как я сейчас читаю данные по запросу PUT:
(который отлично работает, пока нет файлов)
$handle = fopen('php://input', 'r'); $rawData = ''; while ($chunk = fread($handle, 1024)) { $rawData .= $chunk; } parse_str($rawData, $data);
Когда я выдаю rawData, он показывает
-----ZENDHTTPCLIENT-44cf242ea3173cfa0b97f80c68608c4c Content-Disposition: form-data; name="image_01"; filename="lorem-ipsum.png" Content-Type: image/png; charset=binary PNG ...etc etc... , -----ZENDHTTPCLIENT-8e4c65a6678d3ef287a07eb1da6a5380 Content-Disposition: form-data; name="testkey" testvalue -----ZENDHTTPCLIENT-8e4c65a6678d3ef287a07eb1da6a5380 Content-Disposition: form-data; name="otherkey" othervalue
Кто-нибудь знает, как правильно получать файлы через HTTP PUT или как разбирать файлы из потока ввода php: //?
===== UPDATE # 1 =====
Я пробовал только этот метод, на самом деле не знаю, что я могу сделать иначе.
У меня нет ошибок с помощью этого метода, кроме того, я не получаю желаемого результата от опубликованных данных и файлов.
===== UPDATE # 2 =====
Я отправляю этот тестовый запрос, используя Zend_Http_Client, следующим образом: (до сих пор не было проблем с Zend_Http_Client)
$client = new Zend_Http_Client(); $client->setConfig(array( 'strict' => false, 'maxredirects' => 0, 'timeout' => 30) ); $client->setUri( 'http://...' ); $client->setMethod(Zend_Http_Client::PUT); $client->setFileUpload( dirname(__FILE__) . '/files/lorem-ipsum.png', 'image_01'); $client->setParameterPost(array('testkey' => 'testvalue', 'otherkey' => 'othervalue'); $client->setHeaders(array( 'api_key' => '...', 'identity' => '...', 'credential' => '...' ));
===== РЕШЕНИЕ =====
Оказывается, я сделал некоторые неправильные предположения, в основном, что HTTP PUT будет похож на HTTP POST. Как вы можете прочитать ниже, DaveRandom объяснил мне, что HTTP PUT не предназначен для передачи нескольких файлов по одному и тому же запросу.
Теперь я переместил передачу formdata из тела в строку запроса. Тело теперь содержит содержимое одного файла.
Для получения дополнительной информации прочитайте ответ DaveRandom. Это эпично.
Данные, которые вы показываете, не отображают действительный орган запроса PUT (ну, может , но я очень сомневаюсь в этом). То, что он показывает, это тело запроса multipart/form-data
– тип MIME, используемый при загрузке файлов через HTTP POST через HTML-форму.
Запросы PUT должны точно дополнять ответ на запрос GET – они отправляют вам содержимое файла в теле сообщения и ничего больше.
По сути, я говорю, что это не ваш код, чтобы получить файл, который является неправильным, это код, который делает запрос – неверный код клиента, а не код, который вы здесь показываете (хотя parse_str()
это бессмысленное упражнение).
Если вы объясните, что такое клиент (браузер, сценарий на другом сервере и т. Д.), Я могу помочь вам это сделать дальше. Как бы то ни было, соответствующий метод запроса для тела запроса, который вы изображаете, является POST, а не PUT.
Давайте сделаем шаг назад от проблемы и рассмотрим HTTP-протокол в целом – в частности, клиентскую сторону – надеюсь, это поможет вам понять, как все это должно работать. Во-первых, небольшая история (если вас это не интересует, не стесняйтесь пропустить этот раздел).
история
HTTP был первоначально разработан как механизм для извлечения HTML-документов с удаленных серверов. Сначала он эффективно поддерживал только метод GET, посредством которого клиент запрашивал документ по имени, а сервер возвращал его клиенту. Первая публичная спецификация для HTTP, обозначенная как HTTP 0.9, появилась в 1991 году, и если вам интересно, вы можете прочитать ее здесь .
Спецификация HTTP 1.0 (формализованная в 1996 году с RFC 1945 ) значительно расширила возможности протокола, добавив методы HEAD и POST. Он не был обратно совместим с HTTP 0.9 из-за изменения формата ответа – добавлен код ответа, а также возможность включать метаданные для возвращаемого документа в виде заголовков формата MIME – данные ключа / значения пар. HTTP 1.0 также абстрагировал протокол от HTML, позволяя передавать файлы и данные в других форматах.
HTTP 1.1, форма протокола, которая почти исключительно используется сегодня, построена поверх HTTP 1.0 и была разработана для обратной совместимости с реализациями HTTP 1.0. Он был стандартизирован в 1999 году с RFC 2616 . Если вы разработчик, работающий с HTTP, ознакомьтесь с этим документом – это ваша Библия. Понимание этого полностью даст вам значительное преимущество перед вашими сверстниками, которые этого не делают.
Подходите к делу уже
HTTP работает в архитектуре запроса-ответа – клиент отправляет на сервер сообщение с запросом, сервер возвращает ответное сообщение клиенту.
Сообщение запроса включает в себя МЕТОД, URI и, возможно, несколько HEADERS. МЕТОД запроса – это то, к чему относится этот вопрос, поэтому я расскажу о нем наиболее подробно здесь, но сначала важно понять, что мы имеем в виду, когда говорим о URI запроса.
URI – это местоположение на сервере ресурса, который мы запрашиваем. В общем, это состоит из компонента пути и, необязательно, строки запроса . Существуют обстоятельства, при которых могут присутствовать и другие компоненты, но в целях простоты мы будем их игнорировать пока.
Представим себе, что вы вводите http://server.domain.tld/path/to/document.ext?key=value
в адресную строку вашего браузера. Браузер демонтирует эту строку и определяет, что ей необходимо подключиться к HTTP-серверу на server.domain.tld
и запросить документ по адресу /path/to/document.ext?key=value
.
Сгенерированный запрос HTTP 1.1 будет выглядеть (как минимум) следующим образом:
GET /path/to/document.ext?key=value HTTP/1.1 Host: server.domain.tld
Первая часть запроса – это слово GET
– это МЕТОД запроса. Следующая часть – это путь к файлу, который мы запрашиваем, – это URI запроса. В конце этой первой строки есть идентификатор, указывающий используемую версию протокола. В следующей строке вы можете увидеть заголовок в формате MIME, называемый Host
. HTTP 1.1 указывает, что заголовок Host:
должен быть включен с каждым запросом. Это единственный заголовок, который является истинным.
URI запроса разбит на две части – все слева от вопросительного знака ?
это путь , все справа от него – строка запроса .
Методы запроса
RFC 2616 (HTTP / 1.1) определяет 8 методов запроса .
OPTIONS
Метод OPTIONS редко используется. Он предназначен как механизм для определения того, какие функции поддерживает сервер, прежде чем пытаться использовать службу, которую может предоставить сервер.
В верхней части моей головы единственное место в довольно распространенном использовании, которое я могу придумать, где это используется, – это открытие документов в Microsoft Office непосредственно через HTTP из Internet Explorer. Office отправит на сервер запрос OPTIONS, чтобы определить, поддерживает метод PUT для конкретного URI, и если он это сделает, он откроет документ таким образом, чтобы пользователь мог сохранить свои изменения в документе непосредственно на удаленном сервере. Эта функциональность тесно интегрирована в эти конкретные приложения Microsoft.
GET
Это самый распространенный метод в повседневном использовании. Каждый раз, когда вы загружаете обычный документ в свой веб-браузер, это будет запрос GET.
Метод GET запрашивает, чтобы сервер возвращал определенный документ. Единственными данными, которые должны быть переданы на сервер, является информация, которую серверу необходимо определить, какой документ должен быть возвращен. Это может включать информацию, которую сервер может использовать для динамического создания документа, который отправляется в форме заголовков и / или строки запроса в URI запроса. Пока мы по теме – Cookies отправляются в заголовки запроса.
HEAD
Этот метод идентичен методу GET с разницей – сервер не вернет запрошенный документ, если только вернет заголовки, которые будут включены в ответ. Это полезно для определения, например, наличия конкретного документа без переноса и обработки всего документа.
POST
Это второй наиболее часто используемый метод и, возможно, самый сложный. Запросы метода POST почти исключительно используются для вызова некоторых действий на сервере, которые могут изменить его состояние.
Запрос POST, в отличие от GET и HEAD, может (и обычно) включать некоторые данные в тело сообщения запроса. Эти данные могут быть в любом формате, но чаще всего это строка запроса (в том же формате, что и в URI запроса), или многостраничное сообщение, которое может связывать пары ключ / значение вместе с файловыми вложениями.
Многие формы HTML используют метод POST. Чтобы загрузить файлы из браузера, вам нужно будет использовать метод POST для вашей формы.
Метод POST семантически несовместим с API RESTful, поскольку он не является идемпотентным . Другими словами, второй идентичный запрос POST может привести к дальнейшему изменению состояния сервера. Это противоречит «безстоящему» ограничению REST.
PUT
Это напрямую дополняет GET. В тех случаях, когда запросы GET указывают, что сервер должен вернуть документ в местоположении, указанном в URI запроса, в методе PUT указывается, что сервер должен хранить данные в теле запроса в местоположении, указанном в URI запроса.
DELETE
Это указывает на то, что сервер должен уничтожить документ в месте, указанном URI запроса. Очень мало интернет-приложений, реализующих HTTP-сервер, будут выполнять любые действия, когда они получат запрос DELETE, по довольно очевидным причинам.
TRACE
Это обеспечивает механизм уровня уровня приложения, позволяющий клиентам проверять отправленный запрос, как он выглядит к моменту достижения целевого сервера. Это в основном полезно для определения эффекта, которое могут иметь любые прокси-серверы между клиентом и целевым сервером в сообщении запроса.
CONNECT
HTTP 1.1 резервирует имя для метода CONNECT, но не определяет его использование или даже его назначение. С тех пор некоторые реализации прокси-сервера использовали метод CONNECT для упрощения туннелирования HTTP.
Я никогда не пробовал использовать PUT (GET POST и FILES были достаточны для моих нужд), но этот пример из документов php, чтобы он мог помочь вам (http://php.net/manual/en/features.file-upload. пут-method.php):
<?php /* PUT data comes in on the stdin stream */ $putdata = fopen("php://input", "r"); /* Open a file for writing */ $fp = fopen("myputfile.ext", "w"); /* Read the data 1 KB at a time and write to the file */ while ($data = fread($putdata, 1024)) fwrite($fp, $data); /* Close the streams */ fclose($fp); fclose($putdata); ?>
Вот решение, которое я нашел наиболее полезным.
$put = array(); parse_str(file_get_contents('php://input'), $put);
$put
будет массивом, так же, как вы привыкли видеть в $_POST
, за исключением того, что теперь вы можете следовать истинному протоколу HTTP REST.
Просто следуйте тому, что он говорит в DOC :
<?php /* PUT data comes in on the stdin stream */ $putdata = fopen("php://input", "r"); /* Open a file for writing */ $fp = fopen("myputfile.ext", "w"); /* Read the data 1 KB at a time and write to the file */ while ($data = fread($putdata, 1024)) fwrite($fp, $data); /* Close the streams */ fclose($fp); fclose($putdata); ?>
Это должно прочитать весь файл, который находится в потоке PUT, и сохранить его локально, тогда вы можете делать то, что хотите.
Используйте POST и включите заголовок X, чтобы указать фактический метод (PUT в этом случае). Обычно это то, как один работает вокруг брандмауэра, который не позволяет использовать методы, отличные от GET и POST. Просто объявляйте PHP багги (поскольку он отказывается обрабатывать многостраничные полезные данные PUT, он глючит), и относитесь к нему так же, как к устаревшему / драконовскому брандмауэру.
Мнения относительно того, что означает PUT в отношении GET, – это просто мнения, мнения. HTTP не требует такого требования. Он просто утверждает «эквивалент». Дизайнер должен определить, что означает «эквивалент». Если ваш проект может принять многостраничную загрузку PUT и создать «эквивалентное» представление для последующего GET для одного и того же ресурса, это просто отлично и денди, как технически, так и философски, с спецификациями HTTP.