Aaron Fischer Ingenieur, Vater, Heimwerker, Problemlöser

03 Februar, 2008

ETag

Technologie

Ich habe mich heute wieder mal etwas mit Web-Caching beschäftigt und bin dabei auf eine sehr interessante Sache gestoßen: den ETag. Dieser ETag ist ein HTTP-Header der mit HTTP/1.1 eingeführt wurde. Mit ihm kann man feststellen ob sich der Content auf dem Server geändert hat, bevor man ihn herunterlädt.

Da es dazu noch keinen deutschen Wikipedia-Artikel gab, hab ich schnell einen angelegt, so muss ich das ganze hier nicht noch einmal erklären :)

Das ganze funktioniert folgendermaßen:

http = Net::HTTP.new("aaron-fischer.net", 80)
# <<Net::HTTP aaron-fischer.net:80 open=false>>

res = http.send_request("GET", "/rss")
# <<Net::HTTPOK 200 OK readbody=true>>
etag = res.header["etag"]
# ""947e30c936""

res = http.send_request("GET", "/rss", nil, {"If-None-Match" => etag})
# <<Net::HTTPNotModified 304 Not Modified readbody=true>>
res.code
# "304"
res.body
# nil

Eine HTTP-Verbindung wird aufgebaut und eine GET-Anfrage gesendet. Die Antwort (response) enthält den Header ETag, den wir uns zwischenspeichern. Bei der zweiten Anfrage der gleichen Datei senden wir den Header If-None-Match mit dem zuvor gespeicherten ETag mit. Als Antwort erhalten wir einen 304-Statuscode, allerdings diesmal ohne body.

Moderne Webserver unterstützen das automatische Generieren und Anhängen eines ETags an jede Response. In LigHTTPd haben die Option den Prefix etag.* (Siehe Doku). Wenn der Webserver dies nicht unterstützt oder man nur bestimmte Anfragen mit den ETag verstehen will (oder man aber dynamischen Content hat statt statische Seiten) kann man die Header-Manipulation auch eine Stufe darüber (in der Scriptsprache seiner Wahl) erledigen. Hier ein Beispiel in PHP:

// Generate the ETag from the last Entry in the Database
$res = Entry::findAll("Entry", array(
    "order" => "date DESC, id DESC",
    "limit" => 1
));
$etag = """ . substr(md5($res[0]->date . $res[0]->title), 0, 10) . """;
header("ETag: $etag");

// Check ETag
if (isset($_SERVER["HTTP_IF_NONE_MATCH"]) &&
    $_SERVER["HTTP_IF_NONE_MATCH"] == $etag) {
    header("HTTP/1.0 304 Not Modified");
    die();
}

Wirklich genutzt wird dieses tolle Feature momentan leider nur bei Feed-Readern. Es wird langsam Zeit (HTTP/1.1 ist schon über 10 Jahre alt!) sowas unter den Reload-Button der Webbroser zu legen. Bei einem Klick kann statt einem kompletten Refresh der Seite nur eine Meldung ala nothing changed angezeigt werden. Das würde einiges an Traffic sparen.