Подтвердить что ты не робот

Как получить файл через HTTP PUT с PHP

Это то, что меня раздражало какое-то время. Я создаю RESTful API, который иногда должен получать файлы.

При использовании 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. Это эпос.

4b9b3361

Ответ 1

Данные, которые вы показываете, не отображают действительный орган запроса PUT (ну, возможно, но я очень сомневаюсь в этом). Это показывает тело запроса multipart/form-data - тип MIME, используемый при загрузке файлов через HTTP POST через HTML-форму.

Запросы PUT должны точно дополнять ответ на запрос GET - они отправляют вам содержимое файла в теле сообщения и ничего больше.

По сути, я говорю, что это не ваш код, чтобы получить неверный файл, это код, который делает запрос - неверный код клиента, а не код, который вы здесь показываете (хотя parse_str() вызов - бессмысленное упражнение).

Если вы объясните, что такое клиент (браузер, script на другом сервере и т.д.), я могу помочь вам это сделать дальше. Как бы то ни было, соответствующий метод запроса для тела запроса, который вы изображаете, является 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, потому что он не idempotent. Другими словами, второй идентичный запрос POST может привести к дальнейшему изменению состояния сервера. Это противоречит "безстоящему" ограничению REST.

PUT

Это напрямую дополняет GET. Если запросы GET указывают, что сервер должен вернуть документ в местоположении, указанном в URI запроса, в методе PUT указывается, что сервер должен хранить данные в теле запроса в местоположении, указанном в URI запроса.

DELETE

Это означает, что сервер должен уничтожить документ в месте, указанном URI запроса. Очень мало интернет-приложений, реализующих HTTP-сервер, будет выполнять любые действия, когда они получат запрос DELETE по довольно очевидным причинам.

TRACE

Это обеспечивает механизм уровня уровня приложения, позволяющий клиентам проверять отправленный запрос, как он выглядит к моменту достижения целевого сервера. Это в основном полезно для определения эффекта, который могут иметь любые прокси-серверы между клиентом и целевым сервером в сообщении запроса.

CONNECT

HTTP 1.1 резервирует имя для метода CONNECT, но не определяет его использование или даже его назначение. С тех пор некоторые реализации прокси-сервера использовали метод CONNECT для упрощения туннелирования HTTP.

Ответ 2

Я никогда не пробовал использовать PUT (GET POST и FILES были достаточны для моих нужд), но этот пример из документов php, чтобы он мог вам помочь (http://php.net/manual/en/features.file -upload.put-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);
?>

Ответ 3

Вот решение, которое оказалось наиболее полезным.

$put = array(); parse_str(file_get_contents('php://input'), $put);

$put будет массивом, так же, как вы привыкли видеть в $_POST, за исключением того, что теперь вы можете следовать истинному протоколу HTTP REST.

Ответ 4

Просто следуйте указаниям 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, и сохранять его локально, тогда вы можете делать с ним что угодно.

Ответ 5

Используйте POST и включите заголовок X, чтобы указать фактический метод (PUT в этом случае). Обычно это то, как один работает вокруг брандмауэра, который не позволяет использовать методы, отличные от GET и POST. Просто объявляйте PHP багги (поскольку он отказывается обрабатывать многостраничные PUT файлы, он глючит), и рассматривайте его как устаревший/драконовский брандмауэр.

Мнения о том, что означает PUT в отношении GET, - это просто мнения. HTTP не требует такого требования. Он просто утверждает "эквивалент". Дизайнер должен определить, что означает "эквивалент". Если ваш проект может принять многостраничную загрузку PUT и создать "эквивалентное" представление для последующего GET для одного и того же ресурса, это просто отлично и денди, как технически, так и философски, с спецификациями HTTP.