Skip to content

Instantly share code, notes, and snippets.

@eghojansu
Created October 26, 2021 09:22
Show Gist options
  • Save eghojansu/4cf5fccaa73118ff4703ff9c810eb79c to your computer and use it in GitHub Desktop.
Save eghojansu/4cf5fccaa73118ff4703ff9c810eb79c to your computer and use it in GitHub Desktop.
Symfony static file server
<?php
namespace App\Service;
use App\Utils;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
final class FileServer
{
public function createResponse(Request $request, string $root, string $path, string $prefix = '', array $headers = null): Response
{
$file = $root . $path;
if (!is_readable($file) || false === strpos(Utils::fixslashes(realpath($file)), Utils::fixslashes($root))) {
throw new NotFoundHttpException(sprintf("File not found: '%s'", $prefix . $path));
}
$response = new Response('', 200, ($headers ?? array()) + array(
'Last-Modified' => gmdate('D, d M Y H:i:s', filemtime($file)) . ' GMT',
));
if ($response->isNotModified($request)) {
return $response;
}
$size = filesize($file);
$range = $this->getRequestRange($request, $size);
if (!$range) {
$response->setStatusCode(416);
$response->headers->set('Content-Range', sprintf('bytes */%u', $size));
return $response;
}
return $this->createStreamedResponse($response, $file, $size, $range);
}
private function createStreamedResponse(Response $base, string $file, int $size, array $range): StreamedResponse
{
list($min, $max, $isPartial) = $range + array(2 => false);
$len = $max - $min + 1;
$code = $base->getStatusCode();
$headers = array(
'Accept-Ranges' => 'bytes',
'Content-Length' => $len,
'Content-Type' => Utils::getMimeType($file),
) + $base->headers->allPreserveCase();
if ($isPartial) {
$code = 206;
$headers['Content-Range'] = sprintf('bytes %u-%u/%u', $min, $max, $size);
}
return new StreamedResponse(static function () use ($file, $len, $min) {
$sent = 0;
$fp = fopen($file, 'rb');
if ($min > 0) {
fseek($fp, $min);
}
while (!feof($fp) && (connection_status() === CONNECTION_NORMAL)) {
$readingSize = $len - $sent;
$readingSize = min($readingSize, 512 * 1024);
if ($readingSize <= 0) {
break;
}
$data = fread($fp, $readingSize);
if(!$data) {
break;
}
$sent += strlen($data);
echo $data;
flush();
}
fclose($fp);
}, $code, $headers);
}
private function getRequestRange(Request $request, int $size): ?array
{
$max = $size - 1;
if (!$request->headers->has('Range')) {
return array(0, $max);
}
$requestRange = $request->headers->get('Range');
if ('bytes=' !== substr($requestRange, 0, 6)) {
return null;
}
$ranges = explode(',', substr($requestRange, 6));
$range = explode('-', $ranges[0]);
if ($range[0] === '') {
$range[0] = 0;
}
if ($range[1] === '') {
$range[1] = $max;
}
$isPartial = ($range[0] >= 0) && ($range[1] <= $max) && ($range[0] <= $range[1]);
if (!$isPartial) {
return null;
}
$range[2] = $isPartial;
return $range;
}
}
<?php
namespace App;
use Symfony\Component\Mime\MimeTypes;
final class Utils
{
public static function fixslashes(string $str): string
{
return strtr($str, '\\', '/');
}
public static function getMimeType(string $filename): string
{
$mimeTypes = new MimeTypes();
$extension = pathinfo($filename, PATHINFO_EXTENSION);
$types = $mimeTypes->getMimeTypes($extension);
if ($types) {
return reset($types);
}
return $mimeTypes->guessMimeType($filename) ?? '';
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment