Retour au blog
mai 2018·3 min de lectureTraduit par IA
Langue:DeutschEnglishFrançaisÖsterreichischKlingonian Version originale: Deutsch

Traiter une requête HTTP Range avec PHP

#downloadmanagement#php#range-request

Parfois, nous ne voulons pas charger tout le fichier depuis le serveur, mais seulement un certain extrait (Range) du fichier. Nous voulons donc définir un octet de début et un octet de fin et renvoyer toutes les données de cette plage. C'est utile pour les téléchargements partiels (= quand on veut reprendre un téléchargement déjà commencé ou développer une gestion de téléchargements via PHP) ou aussi dans le domaine du streaming vidéo, quand on ne veut pas charger toute la vidéo depuis le début mais seulement la partie pertinente, qui sera lue ensuite (ou la partie vers laquelle on vient de sauter). Nous y parvenons en créant une requête Range.

1. Requête

Pour cela, nous ajoutons à la requête l'en-tête suivant :

Range: bytes=STARTBYTE-ENDBYTE

afin que le serveur sache quelle portion livrer. Cela ne fonctionne toutefois ainsi que si nous accédons directement au fichier, c'est-à-dire si le serveur gère lui-même le téléchargement et traite le fichier. Si le tout doit auparavant passer par PHP, il faut écrire son propre script.

2. Serveur - Bases / lire la plage

Dans un premier temps, nous devons déterminer quelle plage doit être renvoyée. Il y a plusieurs possibilités, mais la plus simple est d'extraire la plage par expression régulière.

if (isset($_SERVER['HTTP_RANGE'])) {
  $range = preg_match('/^bytes=((\d*-\d*,? ?)+)$/', $_SERVER['HTTP_RANGE'], $matches) ? $matches[1] : [0, filesize("video.mp4")];
}

$range[0] contient désormais le premier octet que nous devons renvoyer. $range[1] contient le dernier octet que nous devons renvoyer. Si l'en-tête Range n'a pas été défini dans la requête, nous renvoyons le fichier entier, donc nous commençons à l'octet 0 et finissons à la taille du fichier en octets, que nous obtenons avec filesize(). Il faut maintenant lire le fichier, entre l'octet de début et l'octet de fin.

$fp = fopen('video.mp4', 'r');
fseek($fp, $range[0]);
echo fgets($fp, $range[1] - $range[0]);

Ici, nous ouvrons le fichier dans la variable « fp ». fseek place le curseur à la première position d'octet que nous devons renvoyer, et fgets renvoie le nombre d'octets à lire. Il est important de ne pas passer comme second paramètre de la fonction fgets l'octet de fin lui-même, mais le nombre d'octets à lire (= octet de fin moins octet de début). Nous pouvons maintenant envoyer une requête avec un en-tête au format

Range: bytes=Startbyte-Endbyte

3. Serveur - Avancé

La spécification sur tools.ietf.org décrit toutefois d'autres façons de demander une plage précise. La partie correspondante ressemble à ceci :

The first 500 bytes:
Range: bytes=0-499

The second 500 bytes:
Range: bytes=500-999

All bytes except for the first 500 until the end of document:
Range: bytes=500-

The last 500 bytes of the document:
Range: bytes=-500

Two separate ranges:
Range: bytes=50-99,200-249

The first 100 bytes, 1000 bytes starting from the byte number 500,
and the remainder of the document starting from byte number 4000:
Range: bytes=0-99,500-1499,4000-

The first 100 bytes, 1000 bytes starting from the byte number 500,
and the last 200 bytes of the document:
Range: bytes=0-99,500-1499,-200

Nous devons donc aussi prendre en charge les formats suivants pour rester conformes à la RFC :

  • Range: bytes=500- Tous les octets à partir de l'octet 500 jusqu'à la fin du fichier
  • Range: bytes=-500 Les 500 derniers octets du fichier
  • Range: bytes=50-99,200-249 Les octets 50-99 et 200-249

Et le tout aussi combiné, par exemple

  • Range: bytes=0-99,500-1499,4000- Octets 0-99, 500-1499, 4000 jusqu'à la fin du fichier
  • Range: bytes=0-99,500-1499,-200 Octets 0-99, 500-1499 et les 200 derniers octets du fichier

Le plus simple est de passer par une boucle foreach.

$fp = fopen('video.mp4', 'r');
$filesize = filesize("video.mp4");
foreach (split(substr($_SERVER['HTTP_RANGE'], 0, 6), ',') as $range) {
  preg_match_all("/(\d*)-(\d*)/", $range, $parts);
  if ($parts[2][0] == "") $parts[2][0] = $filesize;
  if ($parts[1][0] == "") {
    $parts[1][0] = $filesize - $parts[2][0];
    $parts[2][0] = $filesize;
  }
  fseek($fp, $parts[1][0]);
  echo fgets($fp, $parts[2][0] - $parts[1][0]);
}

Nous commençons alors à lire le fichier à partir de l'octet $parts[1][0] sur une longueur de $parts[2][0] - $parts[1][0] octets.

4. Remarques

Il faut en outre noter que nous devons éventuellement définir un en-tête de fichier, alias type MIME, dans le fichier PHP pour que le navigateur sache de quel format il s'agit. Pour un fichier .mp4, cela donne par exemple :

header("Content-Type: video/mp4");
header("Content-Length: " . filesize("video.mp4"));

Tous les types MIME se trouvent sur la page de selfHTML.

Plus récentMéthodes de protection contre le SPAM n° 3 : encore une fois, tout est une question de timingPlus ancienMéthodes de protection contre le SPAM n° 1 : le honeypot