Я обслуживаю большие аудиофайлы на сервере Apache, используя mod_xsendfile
(версия >= 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();
}
Быстрое прочтение mod_sendfile исходный код показывает, что он просто не поддерживает отправку частичного содержимого. Если он получает ответ, отличный от 200, он ничего не отправляет, поэтому ваш Content-Length изменяется на 0, и тело ответа не возвращается.
Вы можете попробовать использовать X-Accel-Redirect с nginx, который работает аналогично и делает поддерживать частичное содержание. Просто измените «X-Sendfile» на «X-Accel-Redirect» в своем коде и используйте nginx вместо Apache. Имейте в виду, что каталог, содержащий статические файлы, должен иметь location
определяется как internal
в конфигурации nginx. Это одновременно включает X-Accel-Redirect и обслуживает ошибки 404 всем, кто пытается напрямую получить доступ к статическому файлу.
location /audio/ {
internal;
}
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 этого не делает.
К вашему сведению, я столкнулся с той же проблемой и нашел эту страницу в поиске Google.
X-SendFile полагается на Apache для обработки частичных запросов содержимого. Нет необходимости устанавливать заголовки и код ответа в php.
Проблема (которой я поделился) заключается в том, что вы устанавливаете код ответа HTTP, а также заголовки Content-Length и Content-Range в коде php. Решение состоит в том, чтобы убрать все это. По какой-то причине он нарушает работу модуля xsendfile, и в итоге происходит описанное поведение (отправка нулевой длины содержимого).
После удаления всего этого Apache считывает правильное содержимое файла и правильно генерирует эти заголовки и код ответа (200 или 206) на основе заголовка Range (или его отсутствия), предоставленного в запросе клиента.