发送二进制数据时覆盖响应标头


Response headers overridden when sending binary data

问题的背景我正在使用jPlayer在ZF2应用程序中播放一些音频文件。jPlayer使用ZF2操作作为音频源,效果良好。通过控制器操作,我使用response对象发回一个原始响应。简化的代码如下所示:

public function streamAction()
{
    // ... left out code to retrieve order and error handling stuff ... //
    $response = $this->getResponse();
    $path = $this->orderService->getFullPathToAudioFile($order);
    $response->setStatusCode(Response::STATUS_CODE_200);
    $response->getHeaders()->addHeaderLine('Content-Type', 'audio/mpeg')
                            ->addHeaderLine('Content-Length', filesize($path))
                            ->addHeaderLine('Expires', -1)
                            ->addHeaderLine('Cache-Control', 'no-store, no-cache, must-revalidate');
    $response->setContent(readfile($path));
    return $response;
}

在Chrome中,jPlayer的HTML5实现将其用作音频源没有问题。Firefox自动恢复了jPlayer的Flash实现,并且运行良好。Safari也使用HTML5实现,但有一个问题,但当我使用jPlayer的Flash实现时,效果很好。

这促使我研究响应中实际发送回的标头。我用萤火虫检查了一下。我在这里注意到了一些奇怪的事情:Content-Length标头没有被发回,Content-Type是"text/html",而不是我指定的"audio/mpeg"。

测试代码我做了一个小测试动作来演示:

public function testAction()
{
    $path = './data/audio/test.mp3';
    $response = $this->getResponse();
    $response->setStatusCode(Response::STATUS_CODE_200);
    $response->getHeaders()->addHeaderLine('Content-Type', 'audio/mpeg')
                           ->addHeaderLine('Content-Length', filesize($path))
                           ->addHeaderLine('Expires', -1)
                           ->addHeaderLine('Cache-Control', 'no-store, no-cache, must-revalidate');
    $response->setContent(readfile($path));
    return $response;
}

请注意我验证了可以找到音频文件。当我在浏览器中调用测试操作时,我也可以看到这一点。它显示mp3文件的内容。

情节变厚当我评论设置内容的行时:$response->setContent(readfile($path));我确实在响应中看到了正确的Content-Type标头和Content-Length标头。

此外,当我创建一个像下面这样的简单纯文本响应时,它也能正常工作。

public function testAction()
{
    $response = $this->getResponse();
    $response->setStatusCode(Response::STATUS_CODE_200);
    $response->getHeaders()->addHeaderLine('Content-Type', 'text/plain')
                           ->addHeaderLine('Content-Length', 10)
                           ->addHeaderLine('Expires', -1)
                           ->addHeaderLine('Cache-Control', 'no-store, no-cache, must-revalidate');
    $response->setContent('0123456789');
    return $response;
}

有什么想法吗?为什么会这样,我该如何解决它?

您的代码的主要问题是readfile()不会返回文件内容,而是立即将其输出到浏览器。因此,您对响应对象所做的更改没有任何效果,因为没有使用响应对象。如果您将其更改为file_get_contents(),它可能会如您所期望的那样工作。

除非您正在流式传输的文件很小(<10KB),否则我建议您查看XSendFile,它将以一种更节省内存的方式完成所有这些操作,并自动为您处理标头(包括范围标头,这将使人们跳过曲目)。