moudarir / downloader
Modern PHP download library with RFC 9110 support.
Requires
- php: ^8.4
- moudarir/file: ^1.0
Requires (Dev)
- phpunit/phpunit: ^13.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
A lightweight and modern PHP library for streaming files and in-memory data with support for HTTP conditional requests and byte range requests.
Features
- PHP 8.4+
- File downloads
- In-memory data downloads
- Bandwidth rate limiting
- HTTP Range requests (RFC 9110)
- Multipart byte ranges
- Configurable per-response content length limitation for inline partial responses
- Mandatory ETag generation
- Last-Modified support
- Conditional requests
- HEAD requests
- X-Sendfile support
- X-Accel-Redirect support
- UTF-8 filenames (RFC 6266)
Requirements
- PHP 8.4+
fileinfoextension for MIME type detectionzipextension for ZIP-related MIME type detection- (Optional) Apache
mod_xsendfilemodule forResponseAction::X_SEND_FILE
Installation
composer require moudarir/downloader
Download configuration
Download behavior is configured through DownloadConfig. It centralizes the response action, MIME type, content disposition, ETag strategy, streaming options and server-side delivery settings.
The configuration object can be customized through immutable with...() methods. Each method returns a new configuration instance, so the calls can be chained.
use Moudarir\Downloader\Download; use Moudarir\Downloader\DownloadConfig; use Moudarir\Downloader\Enums\ContentDisposition; use Moudarir\Downloader\Enums\ResponseAction; use Moudarir\File\Enum\MimeType; $config = new DownloadConfig() ->withMimeType(MimeType::MP4) ->withResponseAction(ResponseAction::PARTIAL) ->withContentDisposition(ContentDisposition::INLINE) ->withChunkSize(256 * 1024) ->withMaxRangeItems(5) ->withLimitRate(0) ->withContentLengthLimitation(1024 * 1024); $response = Download::fromFile( '/path/to/video.mp4', 'video.mp4', $config, ); $response->send();
Configuration options
| Method | Default | Description |
|---|---|---|
withResponseAction() |
ResponseAction::DEFAULT |
Selects the response mode. |
withMimeType() |
null |
Specifies the MIME type. For files, null allows MIME type detection. In-memory data defaults to application/octet-stream when no MIME type is specified. |
withContentDisposition() |
ContentDisposition::ATTACHMENT |
Controls whether the resource is presented as an attachment or inline. |
withETagStrategy() |
null |
Selects an ETag generation strategy. When omitted, a supported strategy is selected automatically. |
withXAccelRedirectUri() |
null |
Sets the internal URI used by Nginx with X-Accel-Redirect. |
withChunkSize() |
DownloadConfig::CHUNK_SIZE (128 KiB) |
Sets the stream read buffer size. |
withMaxRangeItems() |
DownloadConfig::MAX_RANGE_ITEMS (10) |
Sets the maximum number of byte ranges accepted in a single request. |
withLimitRate() |
DownloadConfig::BYTES_PER_SECOND (0 = unlimited) |
Sets the maximum download rate in bytes per second. |
withContentLengthLimitation() |
DownloadConfig::MAX_CONTENT_LENGTH (0 = unlimited) |
Limits the total body size of an applicable partial response. |
Invalid configuration values throw a DownloadException.
Content length limitation
withContentLengthLimitation() limits the size of an actual partial response when both of these settings apply:
ResponseAction::PARTIALContentDisposition::INLINE
For example, the following configuration limits an applicable HTTP Range response to 1 MiB:
$config = new DownloadConfig() ->withResponseAction(ResponseAction::PARTIAL) ->withContentDisposition(ContentDisposition::INLINE) ->withContentLengthLimitation(1024 * 1024);
The limit applies to each response, not cumulatively across multiple requests. A request without a processed Range does not have its full representation truncated to this limit. A value of 0 disables the limitation.
Usage
Download a file
use Moudarir\Downloader\Download; use Moudarir\Downloader\DownloadConfig; use Moudarir\File\Enum\MimeType; $config = new DownloadConfig()->withMimeType(MimeType::PDF); $response = Download::fromFile( '/path/to/file.pdf', config: $config, ); $response->send();
Download with a custom filename
$config = new DownloadConfig()->withMimeType(MimeType::PDF); $response = Download::fromFile( '/path/to/file.pdf', 'document.pdf', $config, ); $response->send();
Inline response
The default content disposition is ContentDisposition::ATTACHMENT. To display supported content inline in the browser, configure ContentDisposition::INLINE.
use Moudarir\Downloader\Download; use Moudarir\Downloader\DownloadConfig; use Moudarir\Downloader\Enums\ContentDisposition; use Moudarir\File\Enum\MimeType; $config = new DownloadConfig() ->withMimeType(MimeType::PDF) ->withContentDisposition(ContentDisposition::INLINE); $response = Download::fromFile( '/path/to/file.pdf', config: $config, ); $response->send();
Partial content and Range requests
For browser-based video playback, configure ResponseAction::PARTIAL together with ContentDisposition::INLINE.
use Moudarir\Downloader\Download; use Moudarir\Downloader\DownloadConfig; use Moudarir\Downloader\Enums\ContentDisposition; use Moudarir\Downloader\Enums\ResponseAction; use Moudarir\File\Enum\MimeType; $config = new DownloadConfig() ->withMimeType(MimeType::MP4) ->withResponseAction(ResponseAction::PARTIAL) ->withContentDisposition(ContentDisposition::INLINE) ->withContentLengthLimitation(1024 * 1024); $response = Download::fromFile( '/path/to/video.mp4', 'video.mp4', $config, ); $response->send();
ResponseAction::PARTIAL enables HTTP Range request handling, including:
- Full responses when no valid Range request is processed
- Single-range responses
- Multipart byte-range responses
- Unsatisfiable ranges (
416 Range Not Satisfiable)
The optional content length limitation caps applicable partial responses. It does not implement adaptive bitrate streaming: the client or browser still controls which requests it makes.
Download from in-memory data
Use Download::fromData() to send a string as a downloadable resource. Configure its MIME type explicitly when needed.
use Moudarir\Downloader\Download; use Moudarir\Downloader\DownloadConfig; use Moudarir\File\Enum\MimeType; $config = new DownloadConfig()->withMimeType(MimeType::OCTET_STREAM); $response = Download::fromData( $data, 'document.bin', $config, ); $response->send();
When no MIME type is configured for in-memory data, the type defaults to application/octet-stream.
Server-side file delivery
For file resources, delivery can be delegated to the web server using X-Sendfile or X-Accel-Redirect.
Apache — X-Sendfile
The server must have a compatible mod_xsendfile configuration.
use Moudarir\Downloader\Download; use Moudarir\Downloader\DownloadConfig; use Moudarir\Downloader\Enums\ResponseAction; $config = new DownloadConfig()->withResponseAction(ResponseAction::X_SEND_FILE); $response = Download::fromFile( '/path/to/file.pdf', config: $config, ); $response->send();
Nginx — X-Accel-Redirect
Configure the internal URI known to Nginx. This URI is not the physical filesystem path.
use Moudarir\Downloader\Download; use Moudarir\Downloader\DownloadConfig; use Moudarir\Downloader\Enums\ResponseAction; $config = new DownloadConfig() ->withResponseAction(ResponseAction::X_ACCEL_REDIRECT) ->withXAccelRedirectUri('/protected/path/to/file.pdf'); $response = Download::fromFile( '/path/to/file.pdf', config: $config, ); $response->send();
Server-side response actions are available only for file resources.
Response actions
DownloadConfig::withResponseAction() selects the response mode.
| Action | Description |
|---|---|
ResponseAction::DEFAULT |
Standard response. |
ResponseAction::PARTIAL |
Enables HTTP Range request handling. |
ResponseAction::X_SEND_FILE |
Delegates file delivery to a compatible web server using X-Sendfile. |
ResponseAction::X_ACCEL_REDIRECT |
Delegates file delivery to Nginx using X-Accel-Redirect. |
The returned DownloadResponse must be sent explicitly:
$response->send();
Cache-Control
Use DownloadResponse::addCacheControl() to customize the Cache-Control header for the response.
$response = Download::fromFile('/path/to/file.pdf')->addCacheControl('private, max-age=3600'); $response->send();
The default Cache-Control value is private, must-revalidate.
Response metadata
Download::fromFile() and Download::fromData() return a DownloadResponse instance. Response metadata can be inspected before sending the response.
use Moudarir\Downloader\Download; use Moudarir\Downloader\DownloadConfig; use Moudarir\Downloader\Enums\StatusCode; use Moudarir\File\Enum\MimeType; $config = new DownloadConfig()->withMimeType(MimeType::PDF); $response = Download::fromFile( '/path/to/file.pdf', config: $config, ); $metadata = $response->metadata(); echo $metadata->statusCode()->value; echo $metadata->contentLength(); echo $metadata->contentType(); echo $metadata->filename(); echo $metadata->etagValue(); if ($metadata->statusCode() === StatusCode::OK) { // ... } $response->send();
Available metadata includes:
- Resource information, including filepath, filename, filesize, MIME type and Last-Modified
- Response status, Content-Length and Content-Type
- The selected
ResponseAction - ETag value and weak/strong state
- Range information, including single and multipart ranges
statusCode() returns a StatusCode enum.
ETag
ETag generation is mandatory. The library automatically generates an ETag for each supported resource and uses it for HTTP conditional requests.
Available strategies include:
ETagStrategy::MTIMEETagStrategy::INODEETagStrategy::MD5ETagStrategy::SHA256ETagStrategy::SHA512
The selected strategy must be supported by the resource. Otherwise, the library throws a DownloadException.
To specify a strategy:
use Moudarir\Downloader\Download; use Moudarir\Downloader\DownloadConfig; use Moudarir\Downloader\Enums\ETagStrategy; $config = new DownloadConfig()->withETagStrategy(ETagStrategy::SHA256); $response = Download::fromFile( '/path/to/file.pdf', config: $config, ); $response->send();
If no strategy is specified, the library automatically selects a supported strategy.
HTTP features
The library supports:
- ETag
- Last-Modified
- If-Match
- If-None-Match
- If-Modified-Since
- If-Unmodified-Since
- Range
- If-Range
- HEAD
Conditional request evaluation follows the precedence defined by RFC 9110.
If-Range supports both strong ETag comparison and HTTP-date comparison. HTTP-date parsing supports the HTTP-date formats defined by RFC 9110.
HTTP responses
The library supports the following response status codes:
| Status code | Description |
|---|---|
StatusCode::OK |
200 OK |
StatusCode::PARTIAL_CONTENT |
206 Partial Content |
StatusCode::NOT_MODIFIED |
304 Not Modified |
StatusCode::PRECONDITION_FAILED |
412 Precondition Failed |
StatusCode::RANGE_NOT_SATISFIABLE |
416 Range Not Satisfiable |
304 Not Modified responses do not contain a message body.
In the current implementation, 412 Precondition Failed and 416 Range Not Satisfiable responses also do not contain a body and use Content-Length: 0. A 416 response includes Content-Range: bytes */<resource-size> when the requested range is unsatisfiable.
License
MIT