Правильный способ обслуживания двоичных данных и частичного содержимого с помощью Zend Framework 2


Я хочу разрешить использование двоичных файлов с каким-либо контролем доступа. Поскольку управление довольно сложное, я не могу просто позволить Apache обслуживать файлы, я должен обслуживать их через PHP, используя мое приложение Zend Framework 2. Действие происходит следующим образом:

public function sendAction() {
        $filename = /* database action */;
        $size = filesize($filename);
        $response = $this->getResponse();

        if($this->getRequest()->getHeaders()->has('Range')) {
            list($unit, $range) = explode('=', $this->getRequest()->getHeaders()->get('Range')->toString());
            $ranges = explode(',', $range);
            $ranges = explode('-', $ranges[0]);

            $start = (int)$ranges[0];
            $end = (int)(isset($ranges[1]) ? $ranges[1] : $size - 1);
            $length = $start - $end;

            $response->getHeaders()->addHeaders(array('Content-Type' => 'audio/mpeg', 'Accept-Ranges' => 'bytes', 'Content-Length' => $length - 1));
            $response->setStatusCode(206);

            $f = fopen($filename, 'r');
            if($start) fseek($f, $start);
            $out = '';
            while($length) {
                $read = ($length > 8192) ? 8192 : $length;
                $length -= $read;
                $out .= fread($fp,$read);
            }
            fclose($f);

            $response->setContent($out);
        } else {
            $response
                 ->setContent(file_get_contents($filename))
                 ->getHeaders()->addHeaders(array('Content-Type' => 'audio/mpeg', 'Accept-Ranges' => 'bytes'));
        }

        return $this->getResponse();
}

Ну, во-первых, я уверен, что это очень неэффективно, так как файлы всегда загружаются в оперативную память полностью для этого перед подачей.

Однако, похоже, это не работает. Когда я пытаюсь получить доступ к файлу, я получаю исправьте audio/mpeg плеер в Chrome, но затем браузер отменяет запросы и останавливается. Я вообще не могу воспроизводить звук.

Я не смог найти в Интернете никаких подсказок о том, как правильно реализовать ответ 206 в Zend 2, возможно, кто-нибудь сможет мне здесь помочь.

Author: Lanbo, 2013-07-23

2 answers

Вам следует использовать stream.

Пример кода из моего приложения

public function documentAction()
{
    $name = $this->params('name');
    try {

        if (!$this->getServiceLocator()->get('AuthStorage')->hasIdentity()) {
            throw new \Exception('You must login.');
        }

        $file = getcwd().'/data/uploads/'.pathinfo($name)['basename'];
        if (file_exists($file)) {
            $response = new \Zend\Http\Response\Stream();
            $headers = new \Zend\Http\Headers();
            $headers->addHeaderLine('Content-type', 'application/pdf');
            $response->setHeaders($headers);
            $response->setStream(fopen($file, 'r'));
            return $response;
        } else {
            throw new \Exception('File not exist');
        }
    }
    catch (\Exception $e) {
        $this->flashMessenger()->setNamespace('error')->addMessage('404');
        return $this->redirect()->toUrl('/');
    }
}    
 2
Author: Tomek Kobyliński, 2013-07-24 11:51:10

Это работает для рендеринга pdf в браузере (например, PDFJS), потоковой передачи видео в формате mp4 или загрузки файла.

$headers = new \Zend\Http\Headers();
$headers->addHeaderLine('Content-Type', $type)
        ->addHeaderLine('Cache-Control', 'must-revalidate')
        ->addHeaderLine('Pragma', 'public');
$response = new \Zend\Http\Response();

$downloadFile = false | true; // <-- Change this

if($downloadFile) {
    $headers->addHeaderLine('Content-Disposition', 'attachment; filename="' . $fileName . '"')
            ->addHeaderLine('Content-Length', $size);
    // ->addHeaderLine('Set-Cookie', 'fileDownload=true; path=/');

    $response = new \Zend\Http\Response\Stream();
    $response->setStream(fopen($pathFile, 'r'));

    $response->setHeaders($headers);

    return $response;
}

$size  = filesize($pathFile);
$start = 0;
$end   = $size - 1;
$buffer = 1024000;

// Get the 'Range' header if one was sent
if (isset($_SERVER['HTTP_RANGE'])) {
    $range = $_SERVER['HTTP_RANGE']; // IIS/Some Apache versions
} else if ($apache = apache_request_headers()) { // Try Apache again
    $arrHeaders = array();
    foreach ($apache as $header => $val) {
        $arrHeaders[strtolower($header)] = $val;
    }
    if (isset($arrHeaders['range'])) {
        $range = $arrHeaders['range'];
    } else {
        $range = false; // We can't get the header/there isn't one set
    }
} else {
    $range = false; // We can't get the header/there isn't one set
}

// Get the data range requested (if any)
$size = filesize($pathFile);
if ($range) {
    $partial = true;
    list($param,$range) = explode('=',$range);
    if (strtolower(trim($param)) != 'bytes') { // Bad request - range unit is not 'bytes'
        $response->setStatusCode(400);  // 400 Invalid Request
        exit;
    }
    $range = explode(',',$range);
    $range = explode('-',$range[0]); // We only deal with the first requested range
    if (count($range) != 2) { // Bad request - 'bytes' parameter is not valid
        $response->setStatusCode(400);  // 400 Invalid Request
        exit;
    }
    if ($range[0] === '') { // First number missing, return last $range[1] bytes
        $end = $size - 1;
        $start = $end - intval($range[1]);
    } else if ($range[1] === '') { // Second number missing, return from byte $range[0] to end
        $start = intval($range[0]);
        $end = $size - 1;
    } else { // Both numbers present, return specific range
        $start = intval($range[0]);
        $end = intval($range[1]);
        if ($end >= $size || (!$start && (!$end || $end == ($size - 1)))) {
            $partial = false; // Invalid range/whole file specified, return whole file
        }
    }
    $length = $end - $start + 1;
} else {
    $partial = false; // No range requested
    $length = $size;
}
// error_log(var_export(array($range, $partial, $length), true));

// Send standard headers
$headers->addHeaderLine("Content-Length", $length);
$headers->addHeaderLine("Accept-Ranges", "bytes");

// if requested, send extra headers and part of file...
if ($partial) {
    $response->setStatusCode(206);  // 206 (Partial Content)
    $headers->addHeaderLine("Content-Range", "bytes $start-$end/".$size);
    if (!$fp = fopen($pathFile, 'r')) { // Error out if we can't read the file
        $response->setStatusCode(500);  // 500 Internal Server Error
        exit;
    }
    if ($start) {
        fseek($fp,$start);
    }
    $out = '';
    while ($length) { // Read in blocks of 8KB so we don't chew up memory on the server
        $read = ($length > 8192) ? 8192 : $length;
        $length -= $read;
        $out .= fread($fp,$read);
    }
    fclose($fp);
} else {
    $out = readfile($pathFile); // ...otherwise just send the whole file
}

$response->setContent($out);

$response->setHeaders($headers);

return $response;
 0
Author: clab, 2017-10-23 00:14:42