web-dev-qa-db-ja.com

PHPでHTTP PUT経由でファイルを受信する方法

これはしばらくの間私を悩ませてきたものです。私は、時々ファイルを受信しなければならないRESTful APIを構築しています。

HTTP POSTを使用すると、data from $_POSTおよびfiles from $_FILESを読み取ることができます。

HTTP GETを使用すると、data from $_GETおよびfiles from $_FILESを読み取ることができます。

ただし、HTTP PUTを使用する場合、データを読み取る唯一の方法はphp://input streamを使用することです。

HTTP PUT経由でファイルを送信するまでは、すべて順調です。 php:// inputストリームにはファイルが含まれているため、期待どおりに動作しなくなりました。

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:// inputストリームからファイルを解析する方法を知っている人はいますか?

===== 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の転送を本文からURLクエリ文字列に移動しました。本文には、単一のファイルの内容が保持されるようになりました。

詳細については、DaveRandomの回答をご覧ください。それは壮大です。

30
Maurice

表示するデータは、有効なPUTリクエストの本文を示していません(まあ、それはcouldですが、非常に疑わしいです)。それが示すのは_multipart/form-data_リクエストボディ-HTTP POST= HTMLフォームを介してファイルをアップロードするときに使用されるMIMEタイプです。

PUTリクエストは、GETリクエストへの応答を正確に補完する必要があります-ファイル本文をメッセージ本文で送信し、それ以外は何も送信しません。

本質的に私が言っているのは、間違ったファイルを受け取るのはあなたのコードではなく、リクエストを行っているコードであるということです-ここに表示するコードではなく、クライアントコードが間違っています(parse_str()呼び出しは無意味な運動です)。

クライアントが何であるか(ブラウザ、他のサーバーのスクリプトなど)を説明していただければ、これをさらに進めることができます。現状では、描写するリクエストボディに適切なリクエストメソッドはPUTではなくPOSTです。


問題から戻って、HTTPプロトコル全般、特にクライアントリクエスト側を見てみましょう。これがすべての仕組みを理解するのに役立つことを願っています。最初に、少し歴史を書きます(これに興味がない場合は、このセクションをスキップしてください)。

History

HTTPは元々、リモートサーバーからHTMLドキュメントを取得するためのメカニズムとして設計されました。最初は、GETメソッドのみを効果的にサポートしました。これにより、クライアントは名前でドキュメントを要求し、サーバーはドキュメントをクライアントに返します。 HTTP 0.9とラベル付けされたHTTPの最初の公開仕様は1991年に登場しました。興味がある場合は、それを読むことができます here

HTTP 1.0仕様(1996年に RFC 1945 で正式化)は、プロトコルの機能を大幅に拡張し、HEADおよびPOSTメソッド。HTTP0.9との後方互換性はありませんでした。応答の形式が変更されたためです-応答コードが追加され、返されたドキュメントのメタデータをMIME形式ヘッダーの形式で含める機能-key /値データのペアHTTP 1.0は、HTMLからプロトコルを抽象化し、他の形式のファイルとデータの転送を可能にしました。

現在ほとんど独占的に使用されているプロトコルの形式であるHTTP 1.1は、HTTP 1.0の上に構築されており、HTTP 1.0実装との下位互換性を保つように設計されています。 1999年に RFC 2616 で標準化されました。 HTTPを使用する開発者の方は、このドキュメントをご覧ください-それはあなたの聖書です。それを完全に理解することは、そうでない仲間よりもかなり有利になります。

すでにポイントに到達

HTTPは要求/応答アーキテクチャで動作します。クライアントはサーバーに要求メッセージを送信し、サーバーはクライアントに応答メッセージを返します。

要求メッセージには、METHOD、URI、およびオプションで複数のHEADERSが含まれます。リクエストMETHODはこの質問が関係するものであるため、ここで最も詳細に説明しますが、最初にリクエストURIについて話すときの意味を正確に理解することが重要です。

URIは、リクエストしているリソースのサーバー上の場所です。一般に、これはpathコンポーネントと、オプションでクエリ文字列で構成されます。他のコンポーネントも存在する場合がありますが、簡単にするために、現時点ではそれらを無視します。

ブラウザのアドレスバーに_http://server.domain.tld/path/to/document.ext?key=value_と入力したとします。ブラウザはこの文字列を分解し、_server.domain.tld_でHTTPサーバーに接続する必要があると判断し、_/path/to/document.ext?key=value_でドキュメントを要求します。

生成されたHTTP 1.1リクエストは(少なくとも)次のようになります。

_GET /path/to/document.ext?key=value HTTP/1.1
Host: server.domain.tld
_

リクエストの最初の部分はWord GETです-これはリクエストMETHODです。次の部分は、リクエストしているファイルへのパスです-これはリクエストURIです。この最初の行の最後には、使用中のプロトコルバージョンを示す識別子があります。次の行には、Hostと呼ばれるMIME形式のヘッダーがあります。 HTTP 1.1では、_Host:_ヘッダーをすべてのリクエストに含めることが義務付けられています。これが真である唯一のヘッダーです。

リクエストURIは2つの部分に分かれています-疑問符_?_の左側はすべてpathで、右側はすべてクエリ文字列

リクエストメソッド

RFC 2616(HTTP/1.1)は、 8リクエストメソッド を定義しています。

OPTIONS

OPTIONSメソッドはめったに使用されません。これは、サーバーが提供するサービスを消費しようとする前に、サーバーがサポートする機能の種類を決定するメカニズムとして意図されています。

私の頭の一番上で、これがどこで使用されるか考えることができるかなり一般的な使用法の唯一の場所は、Microsoft OfficeでInternet ExplorerからHTTP経由で直接ドキュメントを開くときです-OfficeはサーバーにOPTIONSリクエストを送信して特定のURIのPUTメソッドをサポートします。サポートしている場合、ユーザーはドキュメントへの変更をリモートサーバーに直接保存できる方法でドキュメントを開きます。この機能は、これらの特定のMicrosoftアプリケーションに緊密に統合されています。

GET

これは、毎日の使用で最も一般的な方法です。 Webブラウザに通常のドキュメントを読み込むたびに、GETリクエストになります。

GETメソッドは、サーバーが特定のドキュメントを返すことを要求します。サーバーに送信する必要があるデータは、サーバーが返すドキュメントを決定するために必要な情報のみです。これには、サーバーがドキュメントを動的に生成するために使用できる情報を含めることができます。この情報は、リクエストURIのヘッダーやクエリ文字列の形式で送信されます。この件については、リクエストヘッダーでCookieが送信されます。

HEAD

このメソッドはGETメソッドと同じですが、1つ違いがあります。サーバーは、応答に含まれるヘッダーのみを返す場合、要求されたドキュメントを返しません。これは、たとえば、ドキュメント全体を転送および処理する必要なく特定のドキュメントが存在するかどうかを判断するのに役立ちます。

POST

これは2番目に一般的に使用される方法であり、おそらく最も複雑な方法です。 POSTメソッド要求は、サーバーの状態を変更する可能性のあるいくつかのアクションを呼び出すためにほぼ排他的に使用されます。

POSTリクエストは、GETやHEADとは異なり、リクエストメッセージの本文にデータを含めることができます(通常はそうします)。このデータは任意の形式にできますが、最も一般的なのはクエリです文字列(要求URIに表示されるのと同じ形式)またはキーと値のペアを添付ファイルと通信できるマルチパートメッセージ。

多くのHTMLフォームはPOSTメソッドを使用します。ブラウザからファイルをアップロードするには、フォームにPOSTメソッドを使用する必要があります。

POSTメソッドは、 idempotent ではないため、RESTful APIと意味的に互換性がありません。つまり、2番目の同一のPOST=リクエストサーバーの状態がさらに変更される可能性がありますが、これはRESTの「ステートレス」制約と矛盾します。

PUT

これはGETを直接補完します。 GETリクエストが、サーバーが応答本文のリクエストURIで指定された場所でドキュメントを返す必要があることを示している場合、PUTメソッドは、サーバーがリクエストURIで指定された場所のリクエスト本文にデータを格納することを示します。

DELETE

これは、サーバーがリクエストURIで指定された場所にあるドキュメントを破棄する必要があることを示しています。 HTTPサーバーの実装に直面しているインターネットは、かなり明白な理由により、DELETE要求を受信したときにアクションを実行することはほとんどありません。

TRACE

これにより、アプリケーション層レベルのメカニズムが提供され、クライアントは、送信先サーバーに到達するまでに送信したリクエストを検査できます。これは、クライアントと宛先サーバー間のプロキシサーバーが要求メッセージに与える影響を判断するのに最も役立ちます。

CONNECT

HTTP 1.1はCONNECTメソッドの名前を予約していますが、その使用法や目的さえも定義していません。プロキシサーバーの実装の中には、CONNECTメソッドを使用してHTTPトンネリングを促進するものがあります。

42
DaveRandom

PUTを使用したことは一度もありません(GET POSTおよびFILESは私のニーズに十分でした)。 /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);
?>
6
kjurkovic

これが私が最も有用であることがわかった解決策です。

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

$putは、あなたが$_POST。ただし、true REST HTTPプロトコルに従うことができるようになりました。

3
Daniel Sikes

POSTを使用し、実際のメソッド(この場合はPUT)を示すX-ヘッダーを含めます。通常、これは、GETおよびPOST以外のメソッドを許可しないファイアウォールを回避する方法です。 PHPバギー(マルチパートPUTペイロードの処理を拒否するため、ISバギー)であり、古い/厳しいファイアウォールと同様に扱います。

GETに関してPUTが何を意味するかについての意見は、まさにその意見です。 HTTPではそのような要件はありません。それは単に「同等」と述べているだけです。「同等」の意味を決定するのは設計者次第です。デザインが複数ファイルのアップロードPUTを受け入れ、同じリソースの後続のGETに対して「同等の」表現を生成できる場合、HTTP仕様では技術的にも哲学的にもうまくいきます。

1
user4157069

[〜#〜] 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);
?>

これはshouldPUTストリーム上にあるファイル全体を読み取り、ローカルに保存します。

0
Neal