Skip to content

Commit

Permalink
Response: support range requests for files
Browse files Browse the repository at this point in the history
  • Loading branch information
distantnative committed Apr 19, 2023
1 parent 8174267 commit 12f19bc
Show file tree
Hide file tree
Showing 2 changed files with 107 additions and 9 deletions.
33 changes: 33 additions & 0 deletions src/Filesystem/F.php
Original file line number Diff line number Diff line change
Expand Up @@ -783,6 +783,39 @@ public static function size(string|array $file): int
}
}

/**
* Continously outputs the file between the provided range
*/
public static function stream(
string $file,
int $start = 0,
int $end = null
): void {
$size = static::size($file);
$end ??= $size;

if ($end === $size) {
echo static::read($file);
return;
}

$handle = fopen($file, 'rb');
fseek($handle, $start);

if (!feof($handle)) {
throw new Exception('Invalid file handle');
}

while ($start < $end) {
$chunk = fread($handle, min(8 * 1024, $end - $start));
$start += strlen($chunk);
echo $chunk;
flush();
}

fclose($handle);
}

/**
* Categorize the file
*
Expand Down
83 changes: 74 additions & 9 deletions src/Http/Response.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Closure;
use Exception;
use Kirby\Cms\App;
use Kirby\Exception\LogicException;
use Kirby\Filesystem\F;
use Throwable;
Expand All @@ -30,7 +31,7 @@ class Response
/**
* The response body
*/
protected string $body;
protected string|Closure $body;

/**
* The HTTP response code
Expand All @@ -51,7 +52,7 @@ class Response
* Creates a new response object
*/
public function __construct(
string|array $body = '',
string|Closure|array $body = '',
string|null $type = null,
int|null $code = null,
array|null $headers = null,
Expand Down Expand Up @@ -107,6 +108,10 @@ public function __toString(): string
*/
public function body(): string
{
if (is_callable($this->body) === true) {
return call_user_func($this->body);
}

return $this->body;
}

Expand Down Expand Up @@ -169,14 +174,27 @@ public static function download(
*
* @param array $props Custom overrides for response props (e.g. headers)
*/
public static function file(string $file, array $props = []): static
{
$props = array_merge([
'body' => F::read($file),
'type' => F::extensionToMime(F::extension($file))
], $props);
public static function file(
string $file,
array $props = [],
string|false|null $range = null
): static {
// if no range is specified, lazily check request for HTTP_RANGE header
$range ??= App::instance(null, true)?->request()->header('range');

return new static($props);
if (is_string($range) === true) {
if ($response = static::range($file, $range, $props)) {
return $response;
}
}

return new static(array_merge([
'body' => F::read($file),
'type' => F::extensionToMime(F::extension($file)),
'headers' => [
'Content-Length' => F::size($file),
]
], $props));
}


Expand Down Expand Up @@ -250,6 +268,53 @@ public static function json(
]);
}

/**
* Creates a bytes range response for a file
* based on the passed range string
*/
protected static function range(
string $file,
string $range,
array $props = []
): static|null {
preg_match("/^bytes=(\d*)-(\d*)/", $range, $matches);

if ($matches === false) {
return null;
}

$size = F::size($file);
$start = $matches[1];
$end = $matches[2];

if ($start === '') {
$start = $size - (int)$end;
$end = $size;
}

if ($end === '') {
$end = $size;
}

$start = (int)$start;
$end = (int)$end;

// range out of bounds: provide specific response
if ($start < 0 || $start >= $end || $end > $size) {
return new static(['code' => 416]);
}

return new static(array_merge([
'code' => 206,
'body' => fn () => F::stream($file, $start, $end),
'type' => F::extensionToMime(F::extension($file)),
'headers' => [
'Content-Length' => $size,
'Content-Range' => 'bytes ' . $start . '-' . $end . '/' . $size,
]
], $props));
}

/**
* Creates a redirect response,
* which will send the visitor to the
Expand Down

0 comments on commit 12f19bc

Please sign in to comment.