206以Content-Length:0返回的部分内容

我正在使用mod_xsendfile (版本>= 0.10 )在Apache服务器上提供大型audio文件。 当我使用header( 'HTTP/1.1 200 OK' );时,文件服务正常header( 'HTTP/1.1 200 OK' ); 但是,这些文件是完整的服务。 因为我想允许访问者在audio文件中查找,所以我接受来自客户端的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

我尝试过的故障排除

  • 使用Range请求加载stream.php?id=9966 ,这是预期的设置。 脚本返回206 Partial Content 四次 (请参见下面的截图和预期的脚本)。 他们都将Content-Length设置为0
  • 直接在浏览器中加载stream.php?id=9966 。 导致GET发送没有Range请求。 脚本返回200 OK与整个文件内容。 Content-Length正确地返回。 文件开始在浏览器窗口下载。
  • Range请求的情况下,不要在脚本中设置206 Partial Content头。 导致脚本返回200 OK标头。 Content-Length 正确地返回,就像Content-Range
  • 强制stream.php始终返回200 OK ,即使返回Content-Range响应。 使用Range请求获取时, Content-LengthContent-Range 正确返回。
  • 前两个设置不允许在audio中寻找非缓冲位置,这是一个要求。 在这两种testing情况下,文件内容都是完整发送的,任何试图寻找非缓冲位置的尝试都会导致audio被切断,直到文件的下载“赶上”。

返回206个标题

请求

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

快速浏览一下mod_sendfile 源代码,发现它并不支持发送部分内容。 如果得到的响应不是200,它不会发送任何内容,所以你的Content-Length变为0,并且没有返回响应体。

你可以尝试使用Nginx的X-Accel-Redirect,它的工作原理类似,支持部分内容。 只需在代码中将“X-Sendfile”更改为“X-Accel-Redirect”,并使用nginx而不是Apache。 请记住,包含静态文件的目录在nginxconfiguration中必须有一个定义为internallocation 。 这既启用了X-Accel-Redirect,又为任何试图直接访问静态文件的人提供了404错误。

 location /audio/ { internal; } 

mod_xsendfile完全像这样工作。 我刚刚安装了它来testing。

 <?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浏览器在searchmp3文件时遇到了问题,该文件没有信息标记。 我曾尝试使用上面提到的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> 

火狐39和IE 11玩耍和寻求flawlesly。 Chrome 43不支持。

仅供参考,我遇到了同样的问题,并在Googlesearch中find了该页面。

X-SendFile依靠Apache来处理部分内容请求。 没有必要在PHP中设置标题和响应代码。

这个问题(我分享)是你在PHP代码中设置HTTP响应代码以及Content-Length和Content-Range标题。 解决办法是把所有这一切都拿出来。 它由于某种原因中断了xsendfile模块,并以描述的行为(发送0内容长度)结束。

除去所有这些,Apache将读取正确的文件内容,并根据客户端请求中提供的Range头(或缺less)正确生成这些头和响应代码(200或206)。