2016-07-04 4 views
1

PHPアプリケーションから作業する必要があるAPIがあります。 1つのエンドポイントはアップロードするファイルをPOST要求の本体として受け取ります。アップロードされたファイルはかなり大きい(最大25GB)ことがあります。エンドポイントは、200 OKまたはその他のステータスコードが異なる単純なJSONコンテンツを返します。PHP 5.5のためにbody内の大きなファイルでPOSTリクエストを作成する

例要求は次のようになります。

POST /api/upload HTTP/1.1 
Host: <hostname> 
Content-Type: application/octet-stream 
Content-Length: 26843545600 
Connection: close 

<raw file data up to 25 GB> 

は基本的に、私は、サーバーを殺すことなく、このような要求を実行するメソッドを記述する必要があります。

Iは、任意の合理的な実装を見つけることを試みたが、私が見ることができるものから、カール及び非カール(stream_context_create)方法の両方は、サーバのメモリを使い果たすことができるストリング要求体を、必要とします。

a separate socket transport layerを書かずにこれを達成する簡単な方法はありますか?

+1

は、ストリームをオープンし、そのストリームに小さなチャンクでファイルを養います。 –

+0

ftpを使用できませんか? –

+0

@MarcBこれを 'stream_context_create(['http'])'と組み合わせて、送信/解析ヘッダなどでプロトコルを再実装することはできませんか? – VisioN

答えて

0

良いオプションが見つからなかったので、私はfsockopenという既定の解決策に行きました。

ここでは、メモリ消費量の少ないHTTP要求を実行するユーティリティ関数の完全なソースコードを示します。 dataパラメータとして、string,arrayおよびSplFileInfoオブジェクトを受け入れることができます。

/** 
* Performs memory-safe HTTP request. 
* 
* @param string $url  Request URL, e.g. "https://example.com:23986/api/upload". 
* @param string $method Request method, e.g. "GET", "POST", "PATCH", etc. 
* @param mixed $data  [optional] Data to pass with the request. 
* @param array $headers [optional] Additional headers. 
* 
* @return string   Response body. 
* 
* @throws Exception 
*/ 
function request($url, $method, $data = null, array &$headers = []) { 
    static $schemes = [ 
     'https' => ['ssl://', 443], 
     'http' => ['', 80], 
    ]; 

    $u = parse_url($url); 
    if (!isset($u['host']) || !isset($u['scheme']) || !isset($schemes[$u['scheme']])) { 
     throw new Exception('URL parameter must be a valid URL.'); 
    } 

    $scheme = $schemes[$u['scheme']]; 
    if (isset($u['port'])) { 
     $scheme[1] = $u['port']; 
    } 

    $fp = @fsockopen($scheme[0] . $u['host'], $scheme[1], $errno, $errstr); 
    if ($fp === false) { 
     throw new Exception($errstr, $errno); 
    } 

    $uri = isset($u['path']) ? $u['path'] : '/'; 
    if (isset($u['query'])) { 
     $uri .= '?' . $u['query']; 
    } 

    if (is_array($data)) { 
     $data = http_build_query($data); 
     $headers['Content-Type'] = 'application/x-www-form-urlencoded'; 
     $headers['Content-Length'] = strlen($data); 
    } elseif ($data instanceof SplFileInfo) { 
     $headers['Content-Length'] = $data->getSize(); 
    } 

    $headers['Host'] = $this->host; 
    $headers['Connection'] = 'close'; 

    fwrite($fp, sprintf("%s /api%s HTTP/1.1\r\n", $method, $uri)); 
    foreach ($headers as $header => $value) { 
     fwrite($fp, $header . ': ' . $value . "\r\n"); 
    } 
    fwrite($fp, "\r\n"); 

    if ($data instanceof SplFileInfo) { 
     $fh = fopen($data->getPathname(), 'rb'); 
     while ($chunk = fread($fh, 4096)) { 
      fwrite($fp, $chunk); 
     } 
     fclose($fh); 
    } else { 
     fwrite($fp, $data); 
    } 

    $response = ''; 
    while (!feof($fp)) { 
     $response .= fread($fp, 1024); 
    } 
    fclose($fp); 

    if (false === $pos = strpos($response, "\r\n\r\n")) { 
     throw new Exception('Bad server response body.'); 
    } 

    $headers = explode("\r\n", substr($response, 0, $pos)); 
    if (!isset($headers[0]) || strpos($headers[0], 'HTTP/1.1 ')) { 
     throw new Exception('Bad server response headers.'); 
    } 

    return substr($response, $pos + 4); 
} 

使用例:

$file = new SplFileObject('/path/to/file', 'rb'); 
$contents = request('https://example.com/api/upload', 'POST', $file, $headers); 
if ($headers[0] == 'HTTP/1.1 200 OK') { 
    print $contents; 
} 
+0

共有いただきありがとうございます。なぜあなたはOKステータスのチェックでHTTPバージョンをハードコードしていますか?(例:$ headers [0] == 'HTTP/1.1 200 OK')複数のバージョンがあるので、文字列を解析してレスポンスコードを抽出するほうがよいでしょうか?ところで、おそらく '===' – BeetleJuice

+0

@BeetleJuiceを使用します。私はこのコードを例として提供しました。もちろん、プロトコルのバージョンと応答ステータスを正しく取得するために、最初の応答ヘッダーのより高度な解析を追加することができます。 '==='に関しては、空でない文字列を比較すると、厳密な同一比較は異なる結果をもたらさないので、あまり重要ではないと思います。 – VisioN

関連する問題