我个人反对将重定向到S3 URL的想法。我通过Laravel php包装器在服务器端遮盖所有的URL。如果其他人遇到类似问题,这就是我用来解决问题的代码。此代码编写用于使用Laravel 5.6从S3流式传输视频(并包括HTTP_RANGE支持,因此它也可以在iOS上工作)。
我使用下面的类,放置在App/Http/Responses中。要使用此类,请创建一个执行此操作的方法(这类似于getFile方法):
$filestream = new \App\Http\Responses\S3FileStream('file_path_and_name_within_bucket', 'disk_bucket_name', 'output_file_name_when_downloaded');
return $filestream->output();
好运的话,您应该很快就可以开始流式传输了(而不会揭示S3 URL)!
S3FileStream.php:<?php
namespace Http\Responses;
use Exception;
use Illuminate\Filesystem\FilesystemAdapter;
use Illuminate\Http\Response;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Storage;
use Symfony\Component\HttpFoundation\StreamedResponse;
class S3FileStream
{
private $adapter;
private $adapterName;
private $disk;
private $end;
private $filePath;
private $humanName;
private $isRange = false;
private $length = null;
private $returnHeaders = [];
private $size;
private $start;
public function __construct(string $filePath, string $adapter = 's3', ?string $humanName = null)
{
$this->filePath = $filePath;
$this->adapterName = $adapter;
$this->disk = Storage::disk($this->adapterName);
$this->adapter = $this->disk->getAdapter();
$this->humanName = $humanName;
$this->start = 0;
$this->size = 0;
$this->end = 0;
}
public function output()
{
return $this->setHeadersAndStream();
}
protected function setHeadersAndStream()
{
if (!$this->disk->exists($this->filePath)) {
report(new Exception('S3 File Not Found in S3FileStream - ' . $this->adapterName . ' - ' . $this->disk->path($this->filePath)));
return response('File Not Found', 404);
}
$this->start = 0;
$this->size = $this->disk->size($this->filePath);
$this->end = $this->size - 1;
$this->length = $this->size;
$this->isRange = false;
$this->returnHeaders = [
'Last-Modified' => $this->disk->lastModified($this->filePath),
'Accept-Ranges' => 'bytes',
'Content-Type' => $this->disk->mimeType($this->filePath),
'Content-Disposition' => 'inline; filename=' . ($this->humanName ?? basename($this->filePath) . '.' . Arr::last(explode('.', $this->filePath))),
'Content-Length' => $this->length,
];
if (!is_null(request()->server('HTTP_RANGE'))) {
$cStart = $this->start;
$cEnd = $this->end;
$range = Str::after(request()->server('HTTP_RANGE'), '=');
if (strpos($range, ',') !== false) {
return response('416 Requested Range Not Satisfiable', 416, [
'Content-Range' => 'bytes */' . $this->size,
]);
}
if (substr($range, 0, 1) == '-') {
$cStart = $this->size - intval(substr($range, 1)) - 1;
} else {
$range = explode('-', $range);
$cStart = intval($range[0]);
$cEnd = (isset($range[1]) && is_numeric($range[1])) ? intval($range[1]) : $cEnd;
}
$cEnd = min($cEnd, $this->size - 1);
if ($cStart > $cEnd || $cStart > $this->size - 1) {
return response('416 Requested Range Not Satisfiable', 416, [
'Content-Range' => 'bytes */' . $this->size,
]);
}
$this->start = intval($cStart);
$this->end = intval($cEnd);
$this->length = min($this->end - $this->start + 1, $this->size);
$this->returnHeaders['Content-Length'] = $this->length;
$this->returnHeaders['Content-Range'] = 'bytes ' . $this->start . '-' . $this->end . '/' . $this->size;
$this->isRange = true;
}
return $this->stream();
}
protected function stream(): StreamedResponse
{
$this->adapter->getClient()->registerStreamWrapper();
$context = stream_context_create([
's3' => [
'seekable' => true,
],
]);
if (!($stream = fopen("s3://{$this->adapter->getBucket()}/{$this->filePath}", 'rb', false, $context))) {
throw new Exception('Could not open stream for reading export [' . $this->filePath . ']');
}
if (isset($this->start) && $this->start > 0) {
fseek($stream, $this->start, SEEK_SET);
}
$remainingBytes = $this->length ?? $this->size;
$chunkSize = 100;
$video = response()->stream(
function () use ($stream, $remainingBytes, $chunkSize) {
while (!feof($stream) && $remainingBytes > 0) {
$toGrab = min($chunkSize, $remainingBytes);
echo fread($stream, $toGrab);
$remainingBytes -= $toGrab;
flush();
}
fclose($stream);
},
($this->isRange ? 206 : 200),
$this->returnHeaders
);
return $video;
}
}