⬅ Zurück zur Übersicht

HTTP Range Request mit PHP verarbeiten

david am Donnerstag, 17.05.2018 - 18:29:36
⬅ Zurück zur Übersicht

Manchmal wollen wir nicht die gesamte Datei vom Server laden, sondern nur einen gewissen Ausschnitt (Range) der Datei. Wir möchten also ein Startbyte und ein Endbyte definieren und alle Daten aus diesem Bereich zurückgeben.
Das ist sinnvoll bei partial Downloads (= wenn wir als einen bereits begonnenen Downloadvorgang fortsetzen wollen oder ein Downloadmanagement via PHP entwicklen wollen) oder auch im Bereich des Videostreamings, wenn wir nicht von Anfang an das gesamte Video laden wollen sondern nur den relevanten Teil, der als nächstes abgespielt wird (oder der Teil, an den gerade gesprungen wurde). Das erreichen wir, indem wir einen Range Request erstellen.

1. Request

Dazu hängen wir an den Request folgenden Header:

Range: bytes=STARTBYTE-ENDBYTE

sodass der Server weiß, welchen Abschnitt er ausliefern soll. Das geht so allerdings nur, wenn wir direkt auf die Datei zugreifen. Also der Server den Download selbst managt und die Datei verarbeitet. Wenn das ganze vorher noch PHP durchlaufen soll, müssen wir uns hierzu ein eigenes Skript schreiben.

2. Server – Grundlagen / Range abfragen

Dazu müssen wir im ersten Schritt herausfinden, welcher Bereich zurückgegeben werden soll. Dazu gibt es mehrere Möglichkeiten, die einfachste ist jedoch, den Range via Regex herauszufiltern.

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

$range[0] enthält jetzt den ersten Byte, den wir zurückgeben müssen
$range[1] enthält den letzten Byte, den wir zurückgeben müssen
Wenn der Range Header im Request nicht gesetzt wurde, geben wir die ganze Datei zurück – beginnen also bei Byte 0 und enden bei der Dateigröße in Bytes, welche wir mit filesize() herausfinden können.
Jetzt müssen wir die Datei noch auslesen, und zwar zwischen dem Startbyte und dem Endbyte.

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

Hier öffnen wir die Datei in die Variable „fp“.
fseek setzt dabei den Cursor an die erste Byteposition, welche wir zurückgeben sollen und fgets gibt die Anzahl an Bytes zurück, welche wir auslesen müssen. Wichtig ist also, dass wir als zweiten Parameter für die Funktion fgets nicht das Endbyte selbst übergeben, sondern die Anzahl an Bytes, welche gelesen werden soll (= Endbyte minus Startbyte)
Jetzt können wir also einen Request mit einem Header im Format

Range: bytes=Startbyte-Endbyte

senden.

3. Server – Erweitert

Die Spezifikation unter unter https://tools.ietf.org beschreibt jedoch noch weitere Möglichkeiten, einen bestimmten Range anzufordern. Der entsprechende Teil sieht so aus:

Examples of the Range: header with the bytes parameter
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
(byte numbering starts from zero):
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

Wir müssen also außerdem noch folgende Formate unterstüten, um RFC konform zu bleiben:

Range: bytes=500-
Alle Bytes ab Byte 500 bis zum Ende der Datei

Range: bytes=-500
Die letzten 500 Bytes der Datei

Range: bytes=50-99,200-249
Die Bytes 50 – 99 und 200 – 249

Und das ganze auch noch kombiniert, wie z.B.

Range: bytes=0-99,500-1499,4000-
Bytes 0 – 99, 500-1499,4000 bis zum Ende der Datei

Range: bytes=0-99,500-1499,-200
Bytes 0 – 99, 500 – 1499 und die letzten 200 Bytes der Datei

Am einfachsten geht das ganze über eine foreach – Schleife.

1
2
3
4
5
6
7
8
9
10
11
12
$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]);
}

Wir beginnen dann die Datei ab Byte $parts[1][0] auszulesen für eine Länge von $parts[2][0] – $parts[1][0] Bytes.

4. Hinweise

Zu beachten ist weiterhin, dass wir ggfs. noch einen Fileheader aka MIME-Type in der PHP Datei setzen müssen, damit der Browser weiß um welches Format es sich handelt.
Bei einer .mp4 Datei sieht das ganze dann z.B. so aus:

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

Alle MIME-Types finden wir auf der Seite von selfHTML.

Kommentar schreiben

Kommentare