-
-
Save fideloper/6ada632650d8677ba23963ab4eae6b48 to your computer and use it in GitHub Desktop.
<?php | |
/************************************************************************* | |
* Get File Information | |
*/ | |
// Assuming these come from some data source in your app | |
$s3FileKey = 's3/key/path/to/file.ext'; | |
$fileName = 'file.ext'; | |
// Create temporary download link and redirect | |
$adapter = Storage::disk('s3')->getAdapter(); | |
$client = $adapter->getClient(); | |
$client->registerStreamWrapper(); | |
$object = $client->headObject([ | |
'Bucket' => $adapter->getBucket(), | |
'Key' => /*$adapter->getPathPrefix() . */$s3FileKey, | |
]); | |
/************************************************************************* | |
* Set headers to allow browser to force a download | |
*/ | |
header('Last-Modified: '.$object['LastModified']); | |
// header('Etag: '.$object['ETag']); # We are not implementing validation caching here, but we could! | |
header('Accept-Ranges: '.$object['AcceptRanges']); | |
header('Content-Length: '.$object['ContentLength']); | |
header('Content-Type: '.$object['ContentType']); | |
header('Content-Disposition: attachment; filename='.$fileName); | |
/************************************************************************* | |
* Stream file to the browser | |
*/ | |
// Open a stream in read-only mode | |
if (!($stream = fopen("s3://{$adapter->getBucket()}/{$s3FileKey}", 'r'))) { | |
throw new \Exception('Could not open stream for reading file: ['.$s3FileKey.']'); | |
} | |
// Check if the stream has more data to read | |
while (!feof($stream)) { | |
// Read 1024 bytes from the stream | |
echo fread($stream, 1024); | |
} | |
// Be sure to close the stream resource when you're done with it | |
fclose($stream); |
Hey @fideloper, I bought your Scaling Laravel course and slowly but surely implementing all that good stuff into my app! I've just implemented your S3FileStream
class, but not 100% sure how to test (or specially how to mock S3). Can you share any ideas your have re: testing against that class?
hey @fideloper . There's an extra paranthesis at the end of
header('Content-Disposition: attachment; filename='.$fileName));
causing a syntax error
Fideloper Update: Fixed that, thanks!
@fideloper thanks so much, saved me hours.
@fideloper thanks for this
this is such a good script, works with Lumen (https://gist.github.com/digitalkreativ/17cd94db914e6cb21b5dd9e675dd9abe) , while S3FileStream.php does not (many classes missing)
tnx for sharing
Why is this being downloaded for me instead of being displayed?
@lovecoding-git comment this line:
header('Content-Disposition: attachment; filename='.$fileName);
This header tells the browser to force download instead of just displaying when the it knows the content type.
Why not to use Storage::disk('s3')->readStream($path)
, it's not the same?
Now you can just use
Storage::disk('s3')->download($path);
@justsanjit no you can't, this will first download the entire file from s3, then serve it back as a response, it seems. What I am sure of, once you send the get request it will wait and wait for response, then give you the file at once (if it doesn't timeout). The idea here is to force the browser to start a download and stream content from s3, trough the server, to the client as the client is first asked where to store the file, then observe the download progress like downloading any other public file from the server.
@reppair Which version of Laravel are you using?
Download method under the hood using StreamResponse
Laravel Framework 10.20.0
Thing is, the s3 files are private. Can't be public. I need to authorize the downloads on the server and stream them back. I've been digging into this for hours now, nothing seems to do an actual stream from s3, trough the server back to the browser apart from this:
$size = Storage::disk('s3')->size($path);
$fileUrl = Storage::disk('s3')->temporaryUrl($path, now()->addMinutes(3));
$headers = [
'Content-Type' => 'audio/wav',
'Content-Length' => $size,
];
return response()->streamDownload(function () use ($fileUrl, $filename, $size) {
if (! ($stream = fopen($fileUrl, 'r'))) {
throw new \Exception("'Could not open stream for reading file: ['.$filename.']'");
}
while (! feof($stream)) {
echo fread($stream, 1024);
}
fclose($stream);
}, $filename, $headers);
As you can see, the problem here is, I am creating a publicly accessible URL to the file. Which I want to avoid.
Here is how I've done it without creating temporary public URL with the S3 custom stream wrapper as outline here.
Edit: Later realized I've done basically the same as show in this gist, just overlooked it at first. 😸
$size = Storage::disk('s3')->size($path);
$client = Storage::disk('s3')->getClient();
$client->registerStreamWrapper();
$buket = config('filesystems.disks.s3.bucket');
$fileUrl = "s3://$buket/$path";
$headers = [
'Content-Type' => 'audio/wav',
'Content-Length' => $size,
];
return response()->streamDownload(function () use ($fileUrl, $filename) {
if (! ($stream = fopen($fileUrl, 'r'))) {
throw new \Exception("'Could not open stream for reading file: ['.$filename.']'");
}
while (! feof($stream)) {
echo fread($stream, 1024);
}
fclose($stream);
}, $filename, $headers);
I think this does a streamed response right?
return Storage::disk('s3.protected')->response($document->url);
or download response
return Storage::disk('s3.protected')->download($document->url);
I think this does a streamed response right?
return Storage::disk('s3.protected')->response($document->url);
or download response
return Storage::disk('s3.protected')->download($document->url);
In my case, the download does not start immediately as a streamed response should be.
It first waits until the file has been downloaded from S3 to the local server, and then the output begins.
Any tips?
I think this does a streamed response right?
return Storage::disk('s3.protected')->response($document->url);
or download response
return Storage::disk('s3.protected')->download($document->url);
In my case, the download does not start immediately as a streamed response should be. It first waits until the file has been downloaded from S3 to the local server, and then the output begins.
Any tips?
Maybe try this instead @moerphie
Also keep in mind streaming works off buffers in chunks based on byte size so if your file is smaller than the buffer size it'll always appear like that. Test a large file as well. You can probably dig through the sdk for the file driver and find it's streaming functionality
Reference:
Docs on HeadObject S3 API call:
http://docs.aws.amazon.com/aws-sdk-php/v3/api/api-s3-2006-03-01.html#headobject
Example streaming file and explanation on "$client->registerStreamWrapper()" method:
https://aws.amazon.com/blogs/developer/amazon-s3-php-stream-wrapper/
Force file download via
Content-Disposition
header:https://stackoverflow.com/questions/8485886/force-file-download-with-php-using-header