Wenn Sie sich mit HTTP/2 beschäftigt haben, haben Sie wahrscheinlich schon von Server Push gehört. Wenn nicht, hier ist die Kurzfassung: Server Push ermöglicht es Ihnen, ein Asset proaktiv zu senden, wenn der Client ein anderes anfordert. Um es zu verwenden, benötigen Sie einen HTTP/2-fähigen Webserver und setzen dann einen Link-Header für das zu pushende Asset wie folgt:
Link: </css/styles.css>; rel=preload
Wenn diese Regel als Antwortheader für eine HTML-Ressource, z. B. index.html, gesetzt ist, sendet der Server nicht nur index.html, sondern auch styles.css als Antwort. Dies eliminiert die Latenz des Rückwegs vom Server, was bedeutet, dass das Dokument schneller gerendert werden kann. Zumindest in diesem Szenario, wenn CSS gepusht wird. Sie können alles pushen, was Ihr Herz begehrt.
Ein Problem beim Server Push, über das einige Entwickler spekuliert haben, ist, dass er unter Umständen nicht in allen Situationen cache-bewusst ist, abhängig von einer Reihe von Faktoren.Browser haben die Möglichkeit, Pushes abzulehnen, und einige Server haben ihre eigenen Abwehrmechanismen. Zum Beispiel hat das mod_http2-Modul von Apache die H2PushDiarySize-Direktive, die versucht, dieses Problem zu lösen. H2O Server hat eine Funktion namens „Cache-aware Server Push“, die einen Fingerabdruck der gepushten Assets in einem Cookie speichert. Das sind großartige Neuigkeiten, aber nur, wenn Sie H2O Server tatsächlich verwenden können, was je nach Ihren Anwendungsanforderungen möglicherweise keine Option für Sie ist.
Wenn Sie einen HTTP/2-Server verwenden, der dieses Problem noch nicht gelöst hat, machen Sie sich keine Sorgen. Sie können dieses Problem mit etwas Backend-Code selbst leicht lösen.
Eine super einfache Cache-bewusste Server Push-Lösung
Nehmen wir an, Sie haben eine Website, die über HTTP/2 läuft, und Sie pushen ein paar Assets, wie eine CSS-Datei und eine JavaScript-Datei. Nehmen wir auch an, dass sich dieser Inhalt selten ändert und diese Assets eine lange max-age-Zeit in ihrem Cache-Control-Header haben. Wenn dies Ihre Situation beschreibt, gibt es diese schnelle und schmutzige Backend-Lösung, die Sie verwenden können:
if (!isset($_COOKIE["h2pushes"])) {
$pushString = "Link: </css/styles.css>; rel=preload,";
$pushString .= "</js/scripts.js>; rel=preload";
header($pushString);
setcookie("h2pushes", "h2pushes", 0, 2592000, "", ".myradwebsite.com", true);
}
Dieses PHP-zentrierte Beispiel prüft auf die Existenz eines Cookies namens h2pushes. Wenn der Besucher kein bekannter Benutzer ist, schlägt die Cookie-Prüfung erwartungsgemäß fehl. Wenn das passiert, werden die entsprechenden Link-Header erstellt und mit der Antwort über die Funktion header gesendet. Nachdem die Header gesetzt wurden, wird setcookie verwendet, um ein Cookie zu erstellen, das potenzielle redundante Pushes verhindert, falls der Benutzer zurückkehrt. In diesem Beispiel beträgt die Ablaufzeit für das Cookie 30 Tage (2.592.000 Sekunden). Wenn das Cookie abläuft (oder gelöscht wird), wiederholt sich der Vorgang.
Dies ist nicht streng „cache-bewusst“ in dem Sinne, dass der Server sicher weiß, ob das Asset auf der Client-Seite gecacht ist, aber die Logik stimmt. Das Cookie wird nur gesetzt, wenn der Benutzer die Seite besucht hat. Bis es gesetzt ist, wurden Assets gepusht und Caching-Richtlinien, die durch den Cache-Control-Header festgelegt wurden, sind aktiv. Das funktioniert gut. Gut, das heißt, bis Sie ein Asset ändern müssen.
Eine flexiblere Cache-bewusste Server Push-Lösung
Was ist, wenn Sie eine Website betreiben, die Server Push verwendet, aber Assets sich häufig ändern? Sie möchten sicherstellen, dass keine redundanten Pushes auftreten, aber Sie möchten auch Assets pushen, wenn sie sich geändert haben, oder vielleicht möchten Sie später zusätzliche Assets pushen. Dies erfordert etwas mehr Code als unsere vorherige Lösung.
function pushAssets() {
$pushes = array(
"/css/styles.css" => substr(md5_file("/var/www/css/styles.css"), 0, 8),
"/js/scripts.js" => substr(md5_file("/var/www/js/scripts.js"), 0, 8)
);
if (!isset($_COOKIE["h2pushes"])) {
$pushString = buildPushString($pushes);
header($pushString);
setcookie("h2pushes", json_encode($pushes), 0, 2592000, "", ".myradwebsite.com", true);
} else {
$serializedPushes = json_encode($pushes);
if ($serializedPushes !== $_COOKIE["h2pushes"]) {
$oldPushes = json_decode($_COOKIE["h2pushes"], true);
$diff = array_diff_assoc($pushes, $oldPushes);
$pushString = buildPushString($diff);
header($pushString);
setcookie("h2pushes", json_encode($pushes), 0, 2592000, "", ".myradwebsite.com", true);
}
}
}
function buildPushString($pushes) {
$pushString = "Link: ";
foreach($pushes as $asset => $version) {
$pushString .= "<" . $asset . ">; rel=preload";
if ($asset !== end($pushes)) {
$pushString .= ",";
}
}
return $pushString;
}
// Push those assets!
pushAssets();
Okay, vielleicht ist es mehr als nur ein bisschen Code, aber er ist immer noch verständlich. Wir definieren zunächst eine Funktion namens pushAssets, die das cache-bewusste Push-Verhalten steuert. Diese Funktion beginnt mit der Definition eines Arrays von Assets, die wir pushen möchten. Da wir Assets erneut pushen möchten, wenn sie sich ändern, müssen wir sie für den späteren Vergleich mit einem Fingerabdruck versehen. Wenn Sie beispielsweise eine Datei namens styles.css bereitstellen, sie aber ändern, versionieren Sie das Asset mit einer Abfragezeichenfolge (z. B. /css/styles.css?v=1), um sicherzustellen, dass der Browser keine veraltete Version davon liefert. In diesem Fall verwenden wir die Funktion md5_file, um eine Prüfsumme des Assets basierend auf seinem Inhalt zu erstellen. Da MD5-Prüfsummen 32 Bytes lang sind, verwenden wir substr, um sie auf 8 zu kürzen. Immer wenn sich diese Assets ändern, ändert sich die Prüfsumme, was bedeutet, dass die Assets automatisch versioniert werden.
Nun zum Hauptteil: Wie zuvor prüfen wir auf das Vorhandensein des h2pushes-Cookies. Wenn es nicht vorhanden ist, verwenden wir die Hilfsfunktion buildPushString, um den Link-Header-String aus den in der $pushes-Array definierten Assets zu erstellen und die Header mit der header-Funktion zu setzen. Dann erstellen wir das Cookie, aber diesmal erstellen wir eine speicherbare Darstellung des $pushes-Arrays mit der Funktion json_encode und speichern diesen Wert im Cookie. Wir könnten diesen Wert serialize, aber das stellt ein potenziell ernstes Sicherheitsrisiko dar, wenn wir ihn später unserialize, also sollten wir uns an etwas Sichereres wie json_encode halten.
Jetzt kommt der interessante Teil: Was tun wir mit zurückkehrenden Besuchern? Wenn sich herausstellt, dass der Besucher zurückkehrt und ein h2pushes-Cookie hat, codieren wir das $pushes-Array mit json_encode und vergleichen den Wert dieses JSON-codierten Arrays mit dem im h2pushes-Cookie gespeicherten Wert. Wenn kein Unterschied besteht, tun wir nichts weiter und fahren fröhlich fort. Wenn es jedoch eine Diskrepanz gibt, müssen wir herausfinden, was sich geändert hat. Dazu verwenden wir die Funktion json_decode, um den Wert des h2pushes-Cookies wieder in ein Array umzuwandeln, und verwenden array_diff_assoc, um die Unterschiede zwischen dem $pushes-Array und dem JSON-dekodierten $oldPushes-Array zu finden.
Mit den von array_diff_assoc zurückgegebenen Unterschieden verwenden wir die Hilfsfunktion buildPushString, um erneut einen String von Ressourcen zu erstellen, die erneut gepusht werden sollen. Die Header werden gesendet und der Cookie-Wert wird mit dem JSON-codierten Inhalt des $pushes-Arrays aktualisiert. Herzlichen Glückwunsch. Sie haben gerade gelernt, wie Sie Ihren eigenen cache-bewussten Server Push-Mechanismus erstellen!
Fazit
Mit etwas Einfallsreichtum ist es nicht allzu schwierig, Assets so zu pushen, dass redundante Pushes für wiederkehrende Besucher minimiert werden. Wenn Sie nicht das Glück haben, einen Webserver wie H2O nutzen zu können, ist diese Lösung für Ihre Zwecke möglicherweise ausreichend. Sie wird derzeit auf meiner eigenen Website verwendet und scheint recht gut zu funktionieren. Sie ist auch sehr wartungsarm. Ich kann Assets auf meiner Website ändern, und mit dem verwendeten Fingerabdruckmechanismus aktualisieren sich Asset-Referenzen selbst, und Pushes passen sich an Asset-Änderungen an, ohne dass ich zusätzliche Arbeit leisten muss.
Eine Sache, die man bedenken sollte, ist, dass Browser im Zuge ihrer Weiterentwicklung wahrscheinlich besser darin werden, zu erkennen, wann sie Pushes ablehnen und aus dem Cache bedienen sollten. Wenn Browser dieses Verhalten nicht perfektionieren, werden HTTP/2-Server wahrscheinlich einen cache-bewussten Push-Mechanismus für den Benutzer implementieren, ähnlich wie H2O es tut. Bis dieser Tag kommt, ist dies jedoch vielleicht etwas, das Sie in Betracht ziehen sollten. Obwohl der Code in PHP geschrieben ist, sollte die Portierung auf eine andere Backend-Sprache trivial sein.
Viel Spaß beim Pushen!
Cool
Sehr gut. Ich bin ein Krypto-Noob, also ist das vielleicht kein Problem, aber gibt es Risiken von MD5-Kollisionen? http://stackoverflow.com/questions/1999824/whats-the-shortest-pair-of-strings-that-causes-an-md5-collision
Das schlimmste Szenario, das mir einfällt, wäre, dass Ihr Snippet denkt, der Client hätte das aktualisierte Asset – d. h. ein Fehlalarm. Auch hier verstehe ich diese Dinge nicht wirklich, also könnte es ein Nicht-Problem sein. Ich mochte diesen Kommentar aus dem SO-Thread. „Entwickler, die Hash-Eindeutigkeit annehmen, sind eine großartige Quelle für WTF-Bugs.“
Das ist eine sehr gute Frage. Ich bin sicher, dass es in einem Bereich von acht Bytes möglich ist, dass es Kollisionen gibt, aber ich glaube, der Bereich ist breit genug, dass es ein Ausnahmeszenario wäre. Außerdem ist das Einzige, was wirklich zählt: „Hat sich Asset X seit dem letzten Mal geändert, als es gepusht wurde?“ Sobald das Asset gepusht und das Cookie gesetzt ist, sollten die Dinge stabil sein, bis das Asset erneut aktualisiert wird.
Lassen Sie mich wissen, ob das Sinn ergibt oder ob Sie denken, dass ich etwas übersehen habe.
Sie werden niemals* eine Kollision erleben. Es ist einfach so unwahrscheinlich. MD5 sollte nicht für kryptografische Funktionen verwendet werden.
Anstelle von MD5 könnten Sie auch die Änderungszeit der Datei verwenden, um Änderungen zu erkennen (soweit ich weiß, wird dies jedoch nicht in allen Setups zuverlässig aktualisiert).
Bezüglich des zweiten Teils des Beitrags würde ich vorsichtig sein, dies für eine Website mit hohem Traffic zu verwenden, insbesondere wenn viele viele Dateien mit Prüfsummen versehen werden. Diese erhöhte I/O-Last könnte sich negativ auf die Serverlast / Antwortzeiten auswirken.
*in Ihrem Leben
Entschuldigung, der Stern vor der letzten Zeile ist irgendwie weggefallen..