Webseiten komprimiert ausliefern

Diverse Browser und sonstige HTTP-Clients sind in der Lage Datentransfers auch gzip-komprimiert entgegen zu nehmen, was besonders bei statischen Inhalten die Übertragung spürbar beschleunigen kann. Der Apache bringt dafür ein Modul mit, daß Inhalte on the fly komprimiert, wenn sie angefragt werden: mod_deflate (für die Apache 1.3er Serie übernimmt mod_gzip weitgehend dieselbe Aufgabe).

In der vorinstallierten httpd.conf unter CentOS steht dazu:


# AddEncoding allows you to have certain browsers uncompress
# information on the fly. Note: Not all browsers support this.
[…]
#
#AddEncoding x-compress .Z
#AddEncoding x-gzip .gz .tgz

# If the AddEncoding directives above are commented-out, then you
# probably should define those extensions to indicate media types:
#
AddType application/x-compress .Z
AddType application/vnd.adobe.air-application-installer-package+zip .air
AddType application/x-gzip .gz .tgz

Diese Erklärung ist ziemlich unintuitiv und entspricht dem Muster „von hinten durch die Brust ins Auge“. Was hier eigentlich gesagt wird ist, daß man Inhalte durchaus komprimiert ausliefern kann (wobei angedeutet wird, daß man sich eine Browserweiche oder etwas ähnliches bauen sollte, da es einige Browser gibt, die das immer noch nicht beherrschen), und daß man eigentlich nur zwei Zeilen auskommentieren bräuchte um dies zu tun. Darüber hinaus wird dann im nächsten Block erklärt, daß man die folgenden Zeilen nur stehen lassen sollte, wenn der Block darüber auskommentiert ist. (Ganz ehrlich: Wenn ich das lese, dann sehe ich schon den vorprogrammierten Konfigurations-Fehler der hier lauert. Wer schreibt solche Default-Configs?)

Was will uns der Autor also sagen: Wenn man die oberen beiden Direktiven aktiviert, dann sollte man die unteren drei auskommentieren und umgekehrt. Der Grund ist eigentlich naheliegend: Wenn der Apache einem Client mitteilen will, daß er ihm jetzt komprimierte Inhalte (die der Client aber darstellen soll/kann) schickt, dann ist der korrekte Weg dies zu tun, ihm ein entsprechendes Encoding zu melden. Wohingegen es falsch wäre, dem Client mitzuteilen diese Inhalte wären z.B. vom MIME-Type application/x-gzip, denn dann würde der Client annehmen er bekommt eine komprimierte Datei die er abspeichern und nicht darstellen soll.

Soweit so gut, aber wie schon erwähnt gibt es leider noch Browser die komprimierte Inhalte nicht vertragen. Außerdem wäre es durchaus nicht bei jedem Inhalt sinnvoll, diesen zu komprimieren (gzip kann z.B. bei schon komprimierten Bildern im PNG– oder JPEG-Format wenig ausrichten). Vielleicht ist dem einen oder anderen auch nicht ganz wohl bei dem Gedanken, den Apachen pauschal für alles was er so ausliefert Kompression anbieten zu lassen. Hier ist letztlich auch die Frage, ob die Nutzer das letztlich auch berücksichtigen, z.B. wenn man Webseiten hostet – in solch einem Fall müßte halt jeder Hosting-Kunde darauf achten, daß ihm die Kompression nirgendwo dazwischen funkt. Vor allem aber möchte man nicht alle möglichen Inhalte on the fly komprimieren lassen, weil dann die Last auf dem Webserver eventuell durch die Decke geht. In der Regel kennt man ja auch die Inhalte die sein Webserver ausliefert so halbwegs und kann selbst abschätzen, wo sich Kompression überhaupt lohnt, etwa bei Texten. Und aus Sicht eines Kompressionsalgorithmus sind HTML-, CSS– und JavaScript-Dateien letztlich nichts anders als unkomprimierter Text.

Es wäre doch ideal, wenn man solche Inhalte vorher (und nur einmalig) komprimiert und so bereitstellt, daß der Webserver einem Client der es unterstützt die komprimierte Version liefern kann. An sich ist auch das kein Problem, man muß aber ein paar kleine Klimmzüge machen:

Zunächst sollte man also die Finger von den oben zitierten Teilen der httpd.conf lassen, denn wie wir ja festgestellt haben, hätten diese alles andere als den gewünschten Effekt. Statt dessen muß man sich zunächst daran machen die entsprechenden Inhalte zu komprimieren. Hierbei sollte man darauf achten, daß die komprimierten Dateien nachher dem richtigen User und der richtigen Gruppe gehören und die richtigen Zugriffsrechte haben. Ich gehe darauf im weiteren nicht näher ein, sondern gehe einfach davon aus, daß diese Befehle mit einem Useraccount ausgeführt werden, bei dem die Rechte korrekt gesetzt werden.

Man wechselt also in den Ordner mit seinen Webinhalten und läßt z.B. alle CSS– und JavaScript-Dateien komprimieren (statische HTML-Seiten lasse ich hier mal außen vor). Dabei hat man das kleine Problem, daß gzip im Gegensatz bzip die Option –keep nicht versteht und also nicht in der Lage ist eine Datei zu komprimieren und das Original liegen zu lassen (anders ausgedrückt: wenn man mit gzip eine Datei komprimiert, ist die Originaldatei danach weg und es liegt nur noch die komprimierte Variante vor). Man muß sich daher eines Tricks bedienen, nämlich die Originaldatei an STDIN von gzip schicken und die komprimierte Variante über STDOUT entgegennehmen und in eine Datei umleiten. Die folgenden Befehle suchen alle JavaScript- und CSS Dateien in allen Unterordnern und legen jeweils eine komprimierte Variante daneben.


for i in `find . -name *.js`; do gzip --best --rsyncable --stdout "${i}" > "${i}".gz; done
for i in `find . -name *.css`; do gzip --best --rsyncable --stdout "${i}" > "${i}".gz; done

Hierbei habe ich für gzip zusätzlich die Optionen –best und –rsyncable gewählt. Die erste sorgt für maximale Kompression (da wir nur einmal komprimieren müssen, darf es ruhig etwas länger dauern), die zweite sorgt dafür, daß die komprimierten Daten rsync-freundlich komprimiert werden. (Da wir unsere Backups mit rsync machen, kann das ja nur von Vorteil sein.)

Nachdem das nun erledigt ist, bieten sich nun verschiedene Möglichkeiten an. Man könnte z.B. den MultiView-Mechanismus vom Apachen ausnutzen, und die komprimierte Variante quasi als View bereitstellen. Dieser Weg ist aber etwas kompliziert und birgt viele Gelegenheiten sich mehr Probleme zu machen als nötig. Viel einfacher (und daher in meinen Augen eleganter) ist es daher, wenn man mit .htaccess-Dateien arbeitet. So behält der einzelne Nutzer jeweils die Kontrolle über das Geschehen, man kann die Komprimierung auf die Ordner beschränken in denen es einem sinnvoll erscheint und Änderungen erfordern keinen Neustart des Apachen.

So eine .htaccess kann z.B. so aussehen:


RewriteEngine On
RewriteCond %{HTTP:Accept-Encoding} .*gzip.*
RewriteCond %{REQUEST_FILENAME}.gz -f
RewriteRule (.*)\.(js|css)$ $1.$2.gz [L]

<FilesMatch .*\.js\.gz>
AddEncoding x-gzip .gz
AddType application/x-javascript .gz
</FilesMatch>

<FilesMatch .*\.css\.gz>
AddEncoding x-gzip .gz
AddType text/css .gz
</FilesMatch>

Hiermit wird die RewriteEngine aktiviert. Als Bedingungen für einen Rewrite definiere ich, daß der Client die Unterstützung von gzip signalisiert haben muß und auch eine mit gzip komprimierte Variante der Datei vorliegen muß. Sollte also mal keine komprimierte Variante vorliegen (etwa weil später eine CSS-Datei dazu kommt und jemand vergißt die komprimierte Variante zu erstellen), dann nimmt Apache einfach die unkomprimierte Variante. Treffen beide Bedingungen zu, dann werden Requests für Dateien die auf .js und .css Enden so umgeschrieben, daß sie bei der komprimierten Variante heraus kommen. Die nächsten zwei Blöcke legen dann darüber hinaus fest, daß diese Dateien nicht mit dem MIME-Type x-gzip übertragen werden dürfen, sondern mit dem originalen MIME-Type (also application/x-javascript oder text/css, aber dem Hinweis auf die Komprimierung im Encoding.

Damit das alles auch funktioniert, muß es aber erlaubt sein, dies alles in .htaccess-Dateien zu definieren. Für das entsprechende Verzeichnis muß man also mindestens das folgende erlauben:


<Directory /var/www/html>
AllowOverride FileInfo
</Directory>

Ab diesem Punkt gilt es, sich an diese Sache zu erinnern. Wenn man jetzt an einer JavaScript- oder CSS-Datei mal was ändert, dann sollte man nicht vergessen auch die komprimierte Variante neu zu erzeugen, sonst wundert man sich am Ende, warum die Änderung nicht überall ankommt.