<?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); |
@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
@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.