mod_xsendfile
我在使用(版本)的 Apache 服务器上提供大型音频文件>= 0.10
。当我使用 时,文件可以正常提供。header( 'HTTP/1.1 200 OK' );
但是,这些文件是完整提供的。因为我想允许访问者在音频文件中搜索,所以我正在接受Range
来自客户端的请求。
这是我遇到问题的地方。当我使用 时header( 'HTTP/1.1 206 Partial Content' );
,PHP 脚本会响应Content-Length: 0
。这对我来说很奇怪,因为在Content-Range
响应中,请求的范围和文件的总文件大小都正确提及了。例如:
Content-Length:0
Content-Range:bytes 0-139143856/139143857
我的 PHP 脚本用于发送这两个标头的标头是:
$filesize = filesize( $mix_file );
...
$start = calculated from HTTP_RANGE or 0;
$end = calculated from HTTP_RANGE or $filesize - 1;
...
header( 'Content-Length: ' . ( ( $end - $start ) + 1 ) );
header( 'Content-Range: bytes ' . $start . '-' . $end . '/' . $filesize );
Content-Range
仅当有请求时才会发送标头,Range
否则我的脚本会省略此标头。
当我明确设置了它的值时为什么还会Content-Length
返回?0
我尝试过的一些故障排除方法
stream.php?id=9966
使用请求加载Range
,这是预期的设置。脚本返回206 Partial Content
四次(见下面的截图和预期脚本)。它们都Content-Length
设置了错误地作为0
。stream.php?id=9966
直接在浏览器中加载。GET
无需请求即可发送Range
。脚本返回200 OK
整个文件内容。Content-Length
返回正确地. 文件开始在浏览器窗口中下载。206 Partial Content
请求时不要在脚本中设置标头Range
。导致脚本返回200 OK
标头。Content-Length
是正确地原样返回Content-Range
。- 强制
stream.php
始终返回200 OK
,即使返回响应也是如此Content-Range
。当GET
与请求绑定时Range
,将同时返回和Content-Length
Content-Range
正确地。 - 前两种设置不是允许在音频中搜索非缓冲位置,这是必需的。在两种测试情况下,文件内容都会完整发送,任何搜索非缓冲位置的尝试都会导致音频中断,直到文件下载“赶上”为止。
要求
GET /stream.php?id=9966 HTTP/1.1
Host: next.tjoonz.com
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Accept-Encoding: identity;q=1, *;q=0
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.134 Safari/537.36
Accept: */*
Referer: http://next.tjoonz.com/
Accept-Language: en-US,en;q=0.8,nl;q=0.6
Cookie: __cfduid=d2623ed31d1d855be05395a7fbcf76c311425543933; __uvt=; uvts=3EmUJ804REmm3E0w; wp-settings-1=hidetb%3D1%26editor%3Dhtml%26m10%3Dc%26m8%3Dc%26m5%3Dc%26m0%3Dc%26m9%3Dc%26m6%3Dc%26m3%3Dc%26imgsize%3Dmedium%26align%3Dnone%26m1%3Dc%26m2%3Dc%26m4%3Dc%26m11%3Dc%26m7%3Dc%26wplink%3D1%26urlbutton%3Dfile%26libraryContent%3Dbrowse%26ed_size%3D373%26dfw_width%3D822; wp-settings-time-1=1435585363; _ga=GA1.2.1985480703.1425543937; audio=yes
Range: bytes=0-
回复
HTTP/1.1 206 Partial Content
Date: Tue, 14 Jul 2015 18:39:52 GMT
Server: Apache
Content-Disposition: attachment; filename="sea-monkey-napcast-018.mp3"
Accept-Ranges: bytes
X-SENDFILE: /home/tjoonzvps/audio/sea-monkey-napcast-018.mp3
Set-Cookie: audio=yes; expires=Wed, 15-Jul-2015 18:39:52 GMT; path=/
Content-Length: 0
Content-Range: bytes 0-145036217/145036218
Keep-Alive: timeout=2, max=97
Connection: Keep-Alive
Content-Type: audio/mpeg
PHP 脚本(相关部分)
if( file_exists( $mix_file ) ) {
tjnz_increment_plays( $mix_id );
// get the 'Range' header if one was sent
if( isset( $_SERVER[ 'HTTP_RANGE' ] ) ) {
$range = $_SERVER[ 'HTTP_RANGE' ];
} else {
$range = false;
}
// get the data range requested (if any)
$filesize = filesize( $mix_file );
$start = 0;
$end = $filesize - 1;
if( $range ) {
$partial = true;
list( $param, $range ) = explode( '=', $range );
if( strtolower( trim( $param ) ) != 'bytes') {
header( 'HTTP/1.1 400 Invalid Request' );
die();
}
$range = explode( ',', $range );
$range = explode( '-', $range[ 0 ] );
if( count( $range ) != 2 ) {
header( 'HTTP/1.1 400 Invalid Request' );
die();
}
if ( $range[ 0 ] === '' ) {
$end = $filesize - 1;
$start = $end - intval( $range[ 0 ] );
} else if( $range[ 1 ] === '' ) {
$start = intval( $range[ 0 ] );
$end = $filesize - 1;
} else {
$start = intval( $range[ 0 ] );
$end = intval( $range[ 1 ] );
if( $end >= $filesize || ( !$start && ( !$end || $end == ( $filesize - 1 ) ) ) ) {
$partial = false;
}
}
} else {
$partial = false;
}
// send standard headers
header( 'Content-Type: audio/mpeg' );
header( 'Content-Length: ' . ( ( $end - $start ) + 1 ) );
header( 'Content-Disposition: attachment; filename="' . $mix_slug . '.mp3"' );
header( 'Accept-Ranges: bytes' );
// if requested, send extra headers and part of file...
if ( $partial ) {
header( 'HTTP/1.1 206 Partial Content' );
header( 'Content-Range: bytes ' . $start . '-' . $end . '/' . $filesize );
header( 'X-SENDFILE: ' . $mix_file );
} else {
header( 'X-SENDFILE: ' . $mix_file );
}
die();
}
答案1
快速浏览mod_sendfile 源代码这表明它根本不支持发送部分内容。如果它收到 200 以外的响应,则不会发送任何内容,因此您的 Content-Length 会更改为 0,并且不会返回任何响应主体。
您可以尝试将 X-Accel-Redirect 与 nginx 结合使用,其工作原理类似,做支持部分内容。只需将代码中的“X-Sendfile”更改为“X-Accel-Redirect”,并使用 nginx 而不是 Apache。请记住,包含静态文件的目录必须在 nginx 配置中location
定义internal
。这既启用了 X-Accel-Redirect,又向任何试图直接访问静态文件的人提供 404 错误。
location /audio/ {
internal;
}
答案2
mod_xsendfile 像这样工作得很好。我刚刚安装了它来测试。
<?php
header("X-Sendfile: /tmp/xsftest");
header("Content-Type: application/octet-stream");
header("Content-Disposition: attachment; filename=\"xsftest\"");
提供由生成的文件$ seq -f %03.0f 001 100 > /tmp/xsftest
$ curl -v -H "Range: bytes=100-151" -H -v http://myserver/xsf-test.php
> GET /xsf-test.php HTTP/1.1
> User-Agent: curl/7.38.0
> Host: myserver
> Accept: */*
> Range: bytes=100-151
>
< HTTP/1.1 206 Partial Content
< Date: Sun, 19 Jul 2015 21:52:43 GMT
* Server Apache is not blacklisted
< Server: Apache
< Content-Disposition: attachment; filename="xsftest"
< Last-Modified: Sun, 19 Jul 2015 21:47:01 GMT
< ETag: "60981c0-190-51b415c7afad3"
< Content-Length: 52
< Content-Range: bytes 100-151/400
< Content-Type: application/octet-stream
<
026
027
028
029
030
031
032
033
034
035
036
037
038
$
(我已删除敏感内容)
因此,您的应用程序没有在未缓冲的文件中搜索可能是因为您的应用程序不支持它,或者文件格式不支持它。
或者,你似乎使用了错误的浏览器。不幸的是。根据此错误报告Chrome 在搜索没有信息标签的 mp3 文件时会出现问题。我尝试使用上面提到的 php 脚本来提供从您的网站下载的 mp3 文件,并遵循 html(虽然很丑陋,但可以完成工作)。
<!html5>
<audio src="xsf-test.php" controls autoplay loop>
<p>Your browser does not support the <code>audio</code> element </p>
</audio>
Firefox 39 和 IE 11 播放和搜索都很流畅。Chrome 43 则不行。
答案3
仅供参考,我遇到了同样的问题,并在 Google 搜索中找到了此页面。
X-SendFile 依赖 Apache 来处理部分内容请求。无需在 php 中设置标头和响应代码。
问题(我分享过)是您在 php 代码中设置了 HTTP 响应代码以及 Content-Length 和 Content-Range 标头。解决方案是将所有这些都删除。由于某种原因,它会破坏 xsendfile 模块,并最终导致所描述的行为(发送 0 内容长度)。
删除所有这些后,Apache 会读取正确的文件内容,并根据客户端请求中提供的 Range 标头(或缺少标头)正确生成这些标头和响应代码(200 或 206)。