web-dev-qa-db-fra.com

Le navigateur n'annulera pas la demande s'il est routé via PHP

J'ai un site qui a une fonctionnalité qui amène Apache à router toutes les demandes de fichiers dans un répertoire particulier via un script PHP via .htaccess. Un problème est survenu que je ne sais pas trop comment résoudre ce problème.

Il y a un cours en ligne qui a une vidéo sur chaque page et les pages sont chargées via ajax. Avec la fonctionnalité ci-dessus désactivée, où Apache sert directement la vidéo, l'utilisateur peut appuyer sur le bouton Suivant pendant le téléchargement de la vidéo et la page suivante se chargera immédiatement. Lorsque la fonction est activée, s'ils appuient sur le bouton Suivant avant le téléchargement de la vidéo, le navigateur se bloque jusqu'à la fin du téléchargement, puis la page suivante se charge. J'ai du mal à comprendre pourquoi cela se produit, y a-t-il quelque chose dans l'invocation de PHP qui obligerait le navigateur à attendre la fin du fichier?

Voici les en-têtes de réponse pour une demande directe:

HTTP/1.1 206 Partial Content
Date: Fri, 31 Oct 2014 21:28:10 GMT
Server: Apache
Last-Modified: Mon, 20 Oct 2014 15:45:20 GMT
Etag: "1601ee2-c4fcc5-505dc9a1e4fe5"
Accept-Ranges: bytes
Content-Length: 12909765
Vary: User-Agent
Content-Range: bytes 0-12909764/12909765
Keep-Alive: timeout=15, max=142
Connection: Keep-Alive
Content-Type: video/mp4

Et pour la demande qui passe par PHP:

HTTP/1.1 206 Partial Content
Date: Fri, 31 Oct 2014 19:11:31 GMT
Server: Apache
P3P: CP="OTI DSP COR CURa ADMa HISa OUR IND STA"
X-Lms: auth
Last-Modified: Mon, 20 Oct 2014 15:45:20 GMT
Etag: "1601ee2-c4fcc5-505dc9a1e4fe5"
Accept-Ranges: bytes
Content-Length: 12909765
Vary: User-Agent
Content-Range: bytes 0-12909764/12909765
Keep-Alive: timeout=15, max=149
Connection: Keep-Alive
Content-Type: video/mp4

Ne figure pas dans la liste des en-têtes pour PHP, c'est que j'envoie des en-têtes vides pour le contrôle du cache, pragma et expire afin de remplacer le jeu d'en-têtes par défaut qui désactiverait la mise en cache des réponses PHP. , c'est à dire:

header('Cache-Control: ');
header('Pragma: ');
header('Expires: ');

Je constate le même comportement dans Firefox et Chrome, je n'ai pas encore testé avec d'autres navigateurs. Chrome est particulièrement mauvais, l'onglet entier cesse de répondre. Si je passe à un autre onglet, le contenu sera blanc, blanc. Je ne peux pas fermer l'onglet ni le navigateur à moins d'utiliser le gestionnaire de tâches de Chrome pour supprimer le processus associé à cet onglet. Il devient réactif une fois le transfert terminé.

Est-ce que quelqu'un sait ce qui pourrait causer ceci? Le serveur exécute Apache 2.2.26 et PHP 5.4.22. Dans WHM, "dso" est sélectionné comme gestionnaire PHP 5, et suEXEC est activé.

Edit:

J'ai remplacé mon instruction à lecture unique par une boucle pour vérifier l'abandon du client ou l'expiration du flux de données, ce qui n'a pas résolu le problème. Je colle ci-dessous la partie du code PHP qui traite de l'envoi du fichier (par opposition aux réponses pour 304, 403, 404, etc.):

(désolé pour le formatage, je ne sais pas pourquoi il a ajouté autant de lignes supplémentaires et mis en évidence en surbrillance)

// file not cached or cache outdated, we respond '200 OK' (or 206) and
// output the file.
if (!isset($headers['Range'])) {
  $response_code = 200;
}
else {
  $response_code = 206;
}

// send Last-Modified header, and set response code
header('Last-Modified: '.gmdate('D, d M Y H:i:s', $mtime).' GMT',
  true, $response_code);
header('Content-Type: ' . get_mime_type($file));
if ($method == 'GET' || $method == 'HEAD') {
  header('Accept-Ranges: bytes');
}

$filesize = filesize($file);
$start_read_pos = 0;
$max_read_chunk = 65536;
$total_read_size = $filesize;
$content_length = $filesize;

// adjust content length and read position based on the range
if (isset($headers['Range'])) {
  // Range: bytes=1000-1999
  // Range: bytes=2000-
  $range_chunks = explode('=', $headers['Range']);
  // the first element should be "bytes"
  $range = $range_chunks[1]; // e.g. 1024-2048, or 1024-
  $range_chunks = explode('-', $range);
  $start_read_pos = intval($range_chunks[0]);
  $end_read_pos = isset($range_chunks[1]) ? intval($range_chunks[1]) : 0;
  if ($end_read_pos == 0) {
    $end_read_pos = $filesize - 1;
  }
  $total_read_size = $end_read_pos - $start_read_pos + 1;
  $content_length = $total_read_size;
}

header('Content-Length: ' . $content_length);
if (isset($headers['Range'])) {
  header('Content-Range: bytes ' . $start_read_pos . '-' . 
    $end_read_pos . '/' . $filesize);
}

// only output the content if this is not a head request
if ($method != 'HEAD') {

  // read the file data

  if ($ext != 'php') {
    $fh = fopen($file, 'rb');
    if ($fh) {

      if ($start_read_pos != 0) {
        // discard $start_read_pos many bytes
        fread($fh, $start_read_pos);
      }

      $total_sent = 0;
      $read_chunk = $max_read_chunk;
      $timeout = false;
      while(!connection_aborted() &&
            !feof($fh) &&
            !$timeout &&
            $total_sent < $total_read_size) {
        // if the next (i.e. last) read requires fewer 
        // than the max read chunk
        if ($total_sent + $max_read_chunk > $total_read_size) {
          $read_chunk = $total_read_size - $total_sent;
        }
        echo fread($fh, $read_chunk);
        $total_sent += $read_chunk;
        $stream_info = stream_get_meta_data($fh);
        if ($stream_info['timed_out']) {
          $timeout = true;
        }
      }

      fclose($fh);
    }
  }
  else {
    // don't show the PHP code
    // pad to match the content length 
    echo str_pad('PHP code', $content_length);header
  }

  exit();
}
2
Steve

Le problème réside probablement dans le code de gestion des en-têtes de plage, bien qu'il soit difficile de dire avec certitude. Servir des vidéos avec PHP a tendance à ne pas utiliser beaucoup la mémoire, en particulier si elles sont volumineuses. Je suggère que vous utilisiez quelque chose comme X-Sendfile à la place. De cette façon, vous effectuez toujours vos vérifications d’authentification en PHP, mais vous obligez votre serveur Web à faire le gros travail pour le traitement des fichiers.

2
Tim Fountain

Je pense que le problème ici n’est probablement pas lié à votre configuration Apache mais à votre script PHP. Par exemple, si votre script charge l'intégralité du fichier vidéo en une fois, puis l'envoie au navigateur en une fois, le problème peut être résolu:

<?php
header( 'Content-Type: video/mp4' );
$sVideo = @file_get_contents( '/var/www/www.example.com/videos/video.mp4' );
echo( $sVideo );
exit;

Cependant, si au lieu de cela vous chargiez le fichier vidéo et le renvoyiez au navigateur sous forme d'une série de morceaux de données beaucoup plus petits, cela pourrait résoudre votre problème:

<?php
header( 'Content-Type: video/mp4' );
$hVideo = fopen( '/var/www/www.example.com/videos/video.mp4', 'rb' );
while( !feof( $hVideo )) {
  echo( fread( $hVideo, 4096 ));
  $aStreamInfo = stream_get_meta_data( $hVideo );
  if( $aStreamInfo['timed_out'] ) break;
}
fclose( $hVideo );
exit;
1
richhallstoke