Download Stream

The DownloadStream is a response helper that is able to create a streamed download responseopen in new window for an attachment. Whenever a Range is requested, the helper will create either of the following stream download responses:

This chapter briefly highlights a few areas that you should be familiar with, before using the helper.


Create Streamed Download Response

By using the for() method, you can create a new download stream directly for your Resource Content.

use Illuminate\Support\Facades\Route;
use Aedart\ETags\Preconditions\Responses\DownloadStream;

Route::get('/downloads/{file}', function (DownloadFileRequest $request) {

    return DownloadStream::for($request->resource)
        ->setName($request->route('file'));
});

For a more complete example, please review the Files and Range Support section.

Attachment Types

Internally, the DownloadStream attempts to open a File Stream for the given attachment (e.g. for the ResourceContext data). As such, the following types of "attachments" are supported:

If the attachment type is not supported, then a RuntimeException is thrown, when the helper attempts to create a response.

Custom

Should the default provided attachment types not cover your needs, then you can specify a callback to resolve a file stream. This can be useful when you have the contents of a file (e.g. from database), but not an actual physical file.

Use setResolveStreamCallback() to specify a custom resolve method. The callback MUST return a valid FileStream instance.

use Aedart\Streams\FileStream;

$response = DownloadStream::for($request->resource)
        ->setResolveStreamCallback(function(mixed $data){
            return FileStream::openTemporary()
                ->append($data)
                ->positionToStart();
        });

Content Disposition

The Content-Disposition is by default set to "attachment" with a filename. This means that browser clients are forced to download the file. If you wish to allow browsers to display the contents of the file directly in the website, then you can do so via the asInlineDisposition() method.

$response = DownloadStream::for($request->resource)
    ->asInlineDisposition();

See Mozilla's documentationopen in new window for additional information about Content-Disposition.

Headers

To set custom headers, you can use the withHeaders() or header() methods.

$response = DownloadStream::for($request->resource)
    ->withHeaders([
        'X-Foo' => 'bar',
        'X-Author' => 'Charlotte Kennedy'
    ])

    // Or,...
    ->header('X-Contact', 'charlotte.kennedy@exmaple.org');

Accept Ranges

By default, bytes is set as the Accept-Rangesopen in new window header value. Consequently, the DownloadStream helper, as well as If-Range and Range preconditions, will process an attachment's bytes, when one or more ranges are requested via the Range header.

Other Units

Recommendation !

Despite the possibility to specify other values for Accept-Ranges header, it is highly discouraged to use any other value than bytes. You risk confusing Http clients and possibly cause response processing conflicts.

Only if you are 100% in control of your clients, e.g. in a closed system, and in full control of how attachments are requested, then you could use a different kind of unit.

To specify a different unit as acceptable range, use withAcceptRanges(). The implementation supports all units that can be converted to and from bytes. See Memory Util for details.

$response = DownloadStream::for($request->resource)
    ->withAcceptRanges('kilobytes');

From the above example, if a single range is requested, then the streamed download response could look something like this:

HTTP/1.1 206 Partial Content
Date: Fri, 05 Feb 2023 10:05:24 GMT
Last-Modified: Mon, 30 Jan 2023 11:06:13 GMT
Content-Range: kilobytes 0-2/6
Content-Length: 3000
Content-Type: plain/text
Content-Disposition: attachment; filename=contacts.txt

(3000 bytes of partial text file... not shown here)



 
 




Notice that the Content-Range is specified in kilobytes, whereas the Content-Length is in bytes! According to RFC 9110open in new window:

[...] Content-Length is used for message delimitation in HTTP/1.1, its field value can impact how the message is parsed by downstream recipients [...] If the message is forwarded by a downstream intermediary, a Content-Length field value that is inconsistent with the received message framing might cause a security failure due to request smuggling or response splitting [...]

In other words, the Content-Length does not contain any information about what unit type the value represents. But it bares significance for clients. According to Mozilla's documentationopen in new window, the value is always specified in bytes. As such, it would be dangerous to convert the Content-Length to any other value than bytes.

Onward

For more information about the DownloadStream, please review the helper's source code.