PHPGangsta - Der praktische PHP Blog

PHP Blog von PHPGangsta


HTTP Range-Request Header in PHP parsen

with one comment

Hört sich eigentlich nach einer einfachen Aufgabe an: HTTP-Clients können beim Download nur Teile einer Datei anfragen, beispielsweise die ersten 500 Bytes eines Videos. Der Server announced die Unterstützung dieses Partial-Downloads mit dem Response-Header:

Accept-Ranges: bytes

Ein HTTP-Client, der partielle Downloads unterstützt kann nun mit dem folgenden Header die ersten 500 Bytes anfordern:

Range: bytes=0-500

Ein Webserver, der direkt die Datei ausliefert, tut wie ihm befohlen. Wird die angefragte Datei jedoch von einem PHP-Script ausgeliefert, muss im PHP-Script dieser Header geparst werden, damit man in PHP weiß wie viele Bytes man ausliefern soll.

Durchsucht man das Internet nach einer Antwort, findet man sehr häufig folgende Lösungen. Achtung, nicht dem RFC https://tools.ietf.org/html/draft-ietf-http-range-retrieval-00 entsprechend, nicht unbedingt nutzen:

http://stackoverflow.com/questions/2209204/parsing-http-range-header-in-php

function getRanges()
{
  return preg_match('/^bytes=((\d*-\d*,? ?)+)$/', @$_SERVER['HTTP_RANGE'], $matches) ? $matches[1] : array();
}

https://gist.github.com/codler/3906826

preg_match('/bytes=(d+)-(d+)/', $_SERVER['HTTP_RANGE'], $matches)
https://licson.net/post/stream-videos-php/
// Parse the range header to get the byte offset
$ranges = array_map(
  'intval', // Parse the parts into integer
  explode(
    '-', // The range separator
    substr($_SERVER['HTTP_RANGE'], 6) // Skip the `bytes=` part of the header
  )
);

// If the last range param is empty, it means the EOF (End of File)
if(!$ranges[1]){
  $ranges[1] = $size - 1;
}

http://stackoverflow.com/questions/2209204/parsing-http-range-header-in-php

if (isset($_SERVER['HTTP_RANGE'])) {
    if (!preg_match('^bytes=\d*-\d*(,\d*-\d*)*$', $_SERVER['HTTP_RANGE'])) {
        header('HTTP/1.1 416 Requested Range Not Satisfiable');
        header('Content-Range: bytes */' . filelength); // Required in 416.
        exit;
    }

    $ranges = explode(',', substr($_SERVER['HTTP_RANGE'], 6));
    foreach ($ranges as $range) {
        $parts = explode('-', $range);
        $start = $parts[0]; // If this is empty, this should be 0.
        $end = $parts[1]; // If this is empty or greater than than filelength - 1, this should be filelength - 1.

        if ($start > $end) {
            header('HTTP/1.1 416 Requested Range Not Satisfiable');
            header('Content-Range: bytes */' . filelength); // Required in 416.
            exit;
        }

        // ...
    }
}

All diese Beispiele unterstützen jedoch nicht die volle Funktionalität, die der Range-Header bietet. Folgende Header sind beispielsweise möglich:

Range: bytes=0-500                 // Die ersten 500 Bytes
Range: bytes=-500                  // Die letzten 500 Bytes (nicht 0-500!)
Range: bytes=500-                  // Ab Byte 500 bis zum Ende
Range: bytes=0-500,1000-1499,-200  // Die ersten 500 Bytes, von Byte 1000 bis 1499, und die letzten 200 Bytes

Die meisten Code-Snippets, die man im Internet findet, unterstützen nur Beispiel 1, bei dem ein StartByte und ein EndByte explizit hingeschrieben wird.
Manche unterstützen eine fehlende Angabe, wie in Beispiel 2, und setzen es dann auf 0, was aber falsch ist!
Fast kein Code-Snippet unterstützt die kommaseparierte Angabe von mehreren Ranges. Man fragt sich natürlich auch welche Clients so etwas brauchen und machen, aber theoretisch geht es, und die Applikation sollte im Idealfall damit umgehen können.

Ich will hier nur auf die Problematik hinweisen, eine vollständige Funktion zu erstellen, die alle 4 möglichen Varianten unterstützt, bleibt dem geneigten Leser überlassen 🙂

Wer sich mit dem Thema beschäftigt, sollte auch die Beobachtungen von Steve Souders lesen, iOS macht ganz komische Requests bei Videos

Written by Michael Kliewe

Mai 16th, 2017 at 9:44 pm

Posted in PHP

Tagged with

One Response to 'HTTP Range-Request Header in PHP parsen'

Subscribe to comments with RSS or TrackBack to 'HTTP Range-Request Header in PHP parsen'.

  1. Ich hatte mich mit dem Thema auch mal beschäftigen dürfen aber in einem Java Servlet. Da hatte alles auch erst prima mit dem Desktop Browser funktioniert und Probleme gab es damals auf nem ipad.

    Zuerst wurde immer das erste Byte angefordert, was vermutlich ein Check sein sollte, ob ranges überhaupt vom Server unterstützt sind. Und dann wurden Bytes zufällig über das Video verteilt geladen. Ich denke das sollte wohl helfen schneller zu einem anderen Punkt zu springen ohne lange Ladezeit.

    War auf jeden Fall mal interessant das zu untersuchen und noch spannender zu sehen, dass es sich seit einigen Jahren nicht geändert hat 😉

    Norbert

    19 Okt 17 at 14:00

Leave a Reply

You can add images to your comment by clicking here.