【问题标题】:Correct way to serve binary data and partial content with Zend Framework 2使用 Zend Framework 2 提供二进制数据和部分内容的正确方法
【发布时间】:2013-07-23 08:59:54
【问题描述】:

我想允许提供具有某种访问控制的二进制文件。由于控件相当复杂,我不能简单地让 Apache 提供文件,我必须使用我的 Zend Framework 2 应用程序通过 PHP 提供它们。动作是这样的:

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();
}

首先,我确信这是非常低效的,因为文件总是在被提供之前完全加载到 RAM 中。

但是,这似乎不起作用。当我尝试访问该文件时,我在 Chrome 中获得了正确的 audio/mpeg 播放器,但随后浏览器取消了请求并停止。我根本无法播放音频。

我在网上找不到任何关于如何在 Zend 2 中以正确方式实现 206 响应的提示,也许有人可以在这里帮助我。

【问题讨论】:

    标签: php http-headers zend-framework2 download


    【解决方案1】:

    你应该使用流。

    来自我的应用程序的示例代码

    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('/');
        }
    }    
    

    【讨论】:

    • 这也处理206 Partial Content答案?
    【解决方案2】:

    这适用于浏览器中的 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;
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-06-09
      • 2010-10-01
      • 1970-01-01
      • 2015-09-23
      • 1970-01-01
      • 2013-09-30
      相关资源
      最近更新 更多