206 Conteúdo parcial retornado com Content-Length: 0

2

Estou atendendo grandes arquivos de áudio em um servidor Apache usando mod_xsendfile (versão >= 0.10 ). Os arquivos são exibidos corretamente quando eu uso header( 'HTTP/1.1 200 OK' ); . No entanto, esses arquivos são exibidos integralmente. Como desejo permitir que os visitantes procurem nos arquivos de áudio, estou aceitando Range solicitações dos clientes.

Aqui é onde eu me deparo com problemas. Quando eu uso header( 'HTTP/1.1 206 Partial Content' ); , o script PHP responde com Content-Length: 0 . Isso é estranho para mim, porque na resposta Content-Range , o intervalo da solicitação e o tamanho total do arquivo são mencionados corretamente. Por exemplo:

Content-Length:0
Content-Range:bytes 0-139143856/139143857

Os cabeçalhos usados pelo meu script PHP para enviar esses dois cabeçalhos são:

$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 );

O cabeçalho Content-Range só é enviado se houver uma solicitação Range , caso contrário meu script omite esse cabeçalho.

Por que Content-Length retorna 0 quando eu defini explicitamente seu valor?

Coisas que tentei para resolver problemas

  • Carregue stream.php?id=9966 com uma solicitação Range , que é a configuração pretendida. O script retorna 206 Partial Content quatro vezes (veja captura de tela e roteiro pretendido abaixo). Todos eles têm Content-Length definido incorretamente como 0 .
  • Carregue stream.php?id=9966 diretamente no navegador. Faz com que GET seja enviado sem uma solicitação Range . O script retorna 200 OK com todo o conteúdo do arquivo. Content-Length é retornado corretamente . O arquivo começa a ser baixado na janela do navegador.
  • Não defina o cabeçalho 206 Partial Content no script no caso de uma solicitação Range . Faz com que o script retorne 200 OK header. Content-Length é corretamente retornado, assim como Content-Range .
  • Força stream.php a sempre retornar 200 OK , mesmo quando retornar uma resposta Content-Range . Quando GET com uma solicitação Range , Content-Length e Content-Range são retornados corretamente .
  • As duas configurações anteriores não permitem a busca de uma posição sem buffer no áudio, o que é um requisito. Em ambos os casos de teste, o conteúdo do arquivo é enviado em sua totalidade e qualquer tentativa de procurar uma posição sem buffer faz com que o áudio seja interrompido até que o download do arquivo seja "capturado".

Solicitação

GET/stream.php?id=9966HTTP/1.1Host:next.tjoonz.comConnection:keep-alivePragma:no-cacheCache-Control:no-cacheAccept-Encoding:identity;q=1,*;q=0User-Agent:Mozilla/5.0(WindowsNT6.1;WOW64)AppleWebKit/537.36(KHTML,likeGecko)Chrome/43.0.2357.134Safari/537.36Accept:*/*Referer:http://next.tjoonz.com/Accept-Language:en-US,en;q=0.8,nl;q=0.6Cookie:__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=yesRange:bytes=0-

Resposta

HTTP/1.1206PartialContentDate:Tue,14Jul201518:39:52GMTServer:ApacheContent-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

Script PHP (a parte relevante)

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();
}
    
por Marc Dingena 14.07.2015 / 21:14

3 respostas

2

Uma rápida leitura do mod_sendfile código-fonte revela que simplesmente não suporta o envio de conteúdo parcial. Se obtiver uma resposta diferente de 200, ela não enviará nada, portanto, seu tamanho de conteúdo será alterado para 0 e nenhum corpo de resposta será retornado.

Você pode tentar usar o X-Accel-Redirect com o nginx, que funciona de forma semelhante e suporta o conteúdo parcial. Apenas mude "X-Sendfile" para "X-Accel-Redirect" no seu código e use nginx ao invés do Apache. Lembre-se de que o diretório que contém os arquivos estáticos deve ter um location definido como internal na configuração nginx. Isso permite o X-Accel-Redirect e serve erros 404 para qualquer um que tentar acessar o arquivo estático diretamente.

location /audio/ {
    internal;
}
    
por 16.07.2015 / 22:47
0

mod_xsendfile funciona perfeitamente assim. Acabei de instalar para testar.

<?php
header("X-Sendfile: /tmp/xsftest");
header("Content-Type: application/octet-stream");
header("Content-Disposition: attachment; filename=\"xsftest\"");

exibe o arquivo gerado por $ 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
$

(eu tirei o conteúdo sensível)

Portanto, seu aplicativo que não está procurando nos arquivos sem buffer pode ser porque seu aplicativo não o suporta ou o formato de arquivo não o suporta.

Ou, ao que parece, você está usando um navegador errado. Infelizmente. De acordo com este relatório de erros , o Chrome tem problemas com arquivos mp3, que não têm o tag de informação. Eu tentei usar o script php mencionado acima para servir um arquivo mp3 baixado do seu site e seguindo html (feio, mas faz o trabalho).

<!html5>

<audio src="xsf-test.php" controls autoplay loop>
<p>Your browser does not support the <code>audio</code> element </p>
</audio>

O Firefox 39 e o IE 11 tocam e procuram flawlesly. O Chrome 43 não.

    
por 19.07.2015 / 23:57
0

FYI, encontrei este mesmo problema e encontrei esta página em uma pesquisa no Google.

O X-SendFile depende do Apache para lidar com as solicitações de conteúdo parcial. Não há necessidade de definir os cabeçalhos e o código de resposta em php.

O problema (que eu compartilhei) é que você está definindo o código de resposta HTTP, bem como os cabeçalhos Content-Length e Content-Range no código php. A solução é tirar tudo isso. Ele quebra o módulo xsendfile por algum motivo e termina com o comportamento descrito (enviando um comprimento de conteúdo 0).

Com tudo isso removido, o Apache lê o conteúdo do arquivo correto e gera esses cabeçalhos e o código de resposta (200 ou 206) corretamente com base no cabeçalho do intervalo (ou falta dele) fornecido na solicitação do cliente.

    
por 08.06.2016 / 22:24