S3:putObject()一个通过POST接收的流

时间:2016-12-13 16:48:00

标签: php amazon-web-services amazon-s3

我正在接收文件(最多4 GB):文件内容在POST请求正文中流式传输给我。 我想将此流直接上传到s3存储桶,而不是先将其保存在本地。 已经尝试过不同的方法,但由于不同的原因而失败。

我目前的做法:

use GuzzleHttp\Psr7\Stream;
use Aws\S3\S3Client;

$s3 = new \Aws\S3\S3Client([
    'version' => 'latest',
    'region' => 'eu-west-1',
    'credentials' => [
        'key' => 'abc',
        'secret' => '123'
    ]
]);

$stream = new \GuzzleHttp\Psr7\Stream(fopen('php://input', 'r'));

$result = $s3->putObject(array(
    'Bucket' => $bucket,
    'Key' => $keyname,
    'ContentLength' => (int)$_SERVER['CONTENT_LENGTH'],
    'Body' => $stream->getContents(),
    'ACL' => 'private',
    'StorageClass' => 'STANDARD_IA',
));

尝试流式传输80 MB文件时发生以下错误:

PHP message: PHP Fatal error:  Allowed memory size of 134217728 bytes exhausted (tried to allocate 78847383 bytes) in /var/www/slimapi/vendor/slim/slim/Slim/Http/Stream.php on line 403

Stream.php的第403行是:

if (!$this->isReadable() || ($contents = stream_get_contents($this->stream)) === false) {

因此,错误可能是由于将流的整个内容加载到字符串中而导致超出内存限制。 (令人恼火的是为什么在Slim / Stream中出现错误,因为我正在尝试使用guzzle \ Stream。)

所以我的问题是: 如何在不缓冲导致内存问题的问题的情况下将传入的POST数据直接流式传输到s3存储桶?

我已经尝试过:

  • $ stream = Psr7 \ stream_for(fopen('php:// input','r'));
  • $流 = fopen('php:// input','r');
  • 在putObject()中:'Body'=> Stream :: factory(fopen('php:// input','r')),

2 个答案:

答案 0 :(得分:0)

PHP SDK调用不支持直接读取流。所以对我来说似乎正在发生的事情是PHP正在耗尽内存,因为它在将实际调用SDK以将该数据字符串输出到对象之前将整个对象从该流加载到变量中。

您想要考虑使用S3 Stream Wrapper

此示例似乎最合适,但您需要在两个流之间传递数据。虽然S3 Stream Wrapper似乎支持从本地文件创建流,但我没有看到将现有流传递给它的直接示例。

在这个例子中,如果可用,我们从源读取4096个字节(如果4096不可用则读取更少,如果返回的值非空,则我们将其写入S3对象。我们继续这样做直到源到达EOF(在此示例中,源必须支持和EOF)。

$client = new Aws\S3\S3Client([/** options **/]);

// Register the stream wrapper from an S3Client object
$client->registerStreamWrapper();

$stream = fopen('s3://bucket/key', 'w');
while (!$stream_source->stream_eof()) {
    $string = $stream_source->stream_read (4096)
    if (!empty($string)) {
        fwrite($stream, $string);
    }
}
fclose($stream);

答案 1 :(得分:0)

我知道这是个老话题,但是没有标记为已解决,所以...

如您在SDK规范(https://docs.aws.amazon.com/aws-sdk-php/v3/api/api-s3-2006-03-01.html#putobject)中所见,PHP SDK确实支持流源-参见参数语法:

$result = $client->putObject([
    // ...
    'Body' => <string || resource || Psr\Http\Message\StreamInterface>,
    // ...
]);

这意味着您的代码几乎可以用,唯一的事情是您应该传递$stream而不是$stream->getContents()

$stream = new \GuzzleHttp\Psr7\Stream(fopen('php://input', 'r'));

$result = $s3->putObject(array(
    'Bucket' => $bucket,
    'Key' => $keyname,
    'ContentLength' => (int)$_SERVER['CONTENT_LENGTH'],
    'Body' => $stream,
    'ACL' => 'private',
    'StorageClass' => 'STANDARD_IA',
));

就这么简单。