Added new FILE page mode that allows sending files to the browser with these improvements:
Reads the file and outputs it in chunks rather than all at once, reducing the amount of memory needed to very little, even for very very large files. Supports http request ranges so that only parts of the file will be returned if requested. This allows in-browser video players to seek to arbitrary points in the video without needing to download the whole file. Makes use of flush during send to allow the browser to being receiving file data immediately, allowing streamable video formats to begin playing before the server has finished sending the data. This could also be used in the future to add a transmission rate limiter. Has early-disconnect detection, to terminate sending file data if the client browser has disconnected or aborted (for instance, a user starts a video, then seeks to near the middle, the first request of data will be terminated rather than continuing to process the file).
This commit is contained in:
parent
ff28f34088
commit
de6d6a0515
@ -31,6 +31,7 @@ abstract class PageMode
|
||||
const REDIRECT = 'redirect';
|
||||
const DATA = 'data';
|
||||
const PAGE = 'page';
|
||||
const FILE = 'file';
|
||||
}
|
||||
|
||||
/**
|
||||
@ -75,9 +76,14 @@ class Page
|
||||
/** @var string; public only for unit test */
|
||||
public $data = "";
|
||||
|
||||
/** @var string; */
|
||||
public $file = null;
|
||||
|
||||
/** @var string; public only for unit test */
|
||||
public $filename = null;
|
||||
|
||||
private $disposition = null;
|
||||
|
||||
/**
|
||||
* Set the raw data to be sent.
|
||||
*/
|
||||
@ -86,12 +92,18 @@ class Page
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
public function set_file(string $file): void
|
||||
{
|
||||
$this->file = $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the recommended download filename.
|
||||
*/
|
||||
public function set_filename(string $filename): void
|
||||
public function set_filename(string $filename, string $disposition = "attachment"): void
|
||||
{
|
||||
$this->filename = $filename;
|
||||
$this->disposition = $disposition;
|
||||
}
|
||||
|
||||
|
||||
@ -294,10 +306,79 @@ class Page
|
||||
case PageMode::DATA:
|
||||
header("Content-Length: " . strlen($this->data));
|
||||
if (!is_null($this->filename)) {
|
||||
header('Content-Disposition: attachment; filename='.$this->filename);
|
||||
header('Content-Disposition: ' . $this->disposition . '; filename=' . $this->filename);
|
||||
}
|
||||
print $this->data;
|
||||
break;
|
||||
case PageMode::FILE:
|
||||
if (!is_null($this->filename)) {
|
||||
header('Content-Disposition: ' . $this->disposition . '; filename=' . $this->filename);
|
||||
}
|
||||
|
||||
//https://gist.github.com/codler/3906826
|
||||
|
||||
$size = filesize($this->file); // File size
|
||||
$length = $size; // Content length
|
||||
$start = 0; // Start byte
|
||||
$end = $size - 1; // End byte
|
||||
|
||||
header("Content-Length: " . strlen($size));
|
||||
header('Accept-Ranges: bytes');
|
||||
|
||||
if (isset($_SERVER['HTTP_RANGE'])) {
|
||||
|
||||
$c_start = $start;
|
||||
$c_end = $end;
|
||||
list(, $range) = explode('=', $_SERVER['HTTP_RANGE'], 2);
|
||||
if (strpos($range, ',') !== false) {
|
||||
header('HTTP/1.1 416 Requested Range Not Satisfiable');
|
||||
header("Content-Range: bytes $start-$end/$size");
|
||||
break;
|
||||
}
|
||||
if ($range == '-') {
|
||||
$c_start = $size - substr($range, 1);
|
||||
} else {
|
||||
$range = explode('-', $range);
|
||||
$c_start = $range[0];
|
||||
$c_end = (isset($range[1]) && is_numeric($range[1])) ? $range[1] : $size;
|
||||
}
|
||||
$c_end = ($c_end > $end) ? $end : $c_end;
|
||||
if ($c_start > $c_end || $c_start > $size - 1 || $c_end >= $size) {
|
||||
header('HTTP/1.1 416 Requested Range Not Satisfiable');
|
||||
header("Content-Range: bytes $start-$end/$size");
|
||||
break;
|
||||
}
|
||||
$start = $c_start;
|
||||
$end = $c_end;
|
||||
$length = $end - $start + 1;
|
||||
header('HTTP/1.1 206 Partial Content');
|
||||
}
|
||||
header("Content-Range: bytes $start-$end/$size");
|
||||
header("Content-Length: " . $length);
|
||||
|
||||
|
||||
$fp = fopen($this->file, 'r');
|
||||
try {
|
||||
fseek($fp, $start);
|
||||
$buffer = 1024 * 64;
|
||||
while (!feof($fp) && ($p = ftell($fp)) <= $end) {
|
||||
if ($p + $buffer > $end) {
|
||||
$buffer = $end - $p + 1;
|
||||
}
|
||||
set_time_limit(0);
|
||||
echo fread($fp, $buffer);
|
||||
flush();
|
||||
|
||||
// After flush, we can tell if the client browser has disconnected.
|
||||
// This means we can start sending a large file, and if we detect they disappeared
|
||||
// then we can just stop and not waste any more resources or bandwidth.
|
||||
if (connection_status() != 0)
|
||||
break;
|
||||
}
|
||||
} finally {
|
||||
fclose($fp);
|
||||
}
|
||||
break;
|
||||
case PageMode::REDIRECT:
|
||||
header('Location: ' . $this->redirect);
|
||||
print 'You should be redirected to <a href="' . $this->redirect . '">' . $this->redirect . '</a>';
|
||||
|
@ -255,7 +255,6 @@ class ImageIO extends Extension
|
||||
|
||||
global $page;
|
||||
if (!is_null($image)) {
|
||||
$page->set_mode(PageMode::DATA);
|
||||
if ($type == "thumb") {
|
||||
$ext = $config->get_string("thumb_type");
|
||||
if (array_key_exists($ext, MIME_TYPE_MAP)) {
|
||||
@ -278,14 +277,17 @@ class ImageIO extends Extension
|
||||
$gmdate_mod = gmdate('D, d M Y H:i:s', filemtime($file)) . ' GMT';
|
||||
|
||||
if ($if_modified_since == $gmdate_mod) {
|
||||
$page->set_mode(PageMode::DATA);
|
||||
$page->set_code(304);
|
||||
$page->set_data("");
|
||||
} else {
|
||||
$page->set_mode(PageMode::FILE);
|
||||
$page->add_http_header("Last-Modified: $gmdate_mod");
|
||||
if ($type != "thumb") {
|
||||
$page->add_http_header("Content-Disposition: inline; filename=".$image->get_nice_image_name());
|
||||
$page->set_filename($image->get_nice_image_name(), 'inline');
|
||||
}
|
||||
$page->set_data(file_get_contents($file));
|
||||
|
||||
$page->set_file($file);
|
||||
|
||||
if ($config->get_int("image_expires")) {
|
||||
$expires = date(DATE_RFC1123, time() + $config->get_int("image_expires"));
|
||||
|
Loading…
x
Reference in New Issue
Block a user