LUG Erding

HTTP und der Webproxy Squid


Einzelheiten zum Protokoll, sowie eine Übersicht über den Squid


Dirk Geschke, LUG-Erding


Letzte Änderung: 18.11.2012


HTTP

Im ersten Teil geht es um das HyperText Transfer Protocol, HTTP. Ohne Kenntnis dieses Protokolls dürfte es schwer sein, die Funktion eines Webproxys zu verstehen

Die Historie von HTTP

Die Idee der vernetzten Informationen, die das World-Wide-Web darstellt, ist eigentlich nicht neu. Die ersten Ideen hatte Vannevar Bush, er äußerte sie im Juli 1945 in seinem Essay As We May Think>. Es gibt auch einen Artikel bei Wikipedia dazu: As We May Think.

Er äußert darin erste Gedanken zu verknüpften Dokumenten und ein fiktives System Memex - Memory Extender. Dies stellt ein foto-elektrisches, mechanisches Gerät zum Erzeugen und Folgen von Links zwischen Dokumenten und Mikrofiche dar. 1945 gab es noch kein Internet...

Im Jahre 1960 veröffentlicht J.C.R. Licklider seinen Artikel mit dem Titel Man Computer Symbiosis. Dies war auch einer der Grundsteine auf dem Weg zum Arpanet.

In dem gleichen Jahr arbeitete auch Ted Nelson an seinem Projekt Xanadu. Er stellte das Projekt aber erst 1965 der Öffentlichkeit beim 20th National Conference, New York, Association for Computing Machinery vor. Wen Details dazu interessieren, der wird wieder bei Wikipedia fündig: Projekt Xanadu.

1962, also theoretisch früher war Douglas C. Engelbart mit seiner Publikation dran: AUGMENTING HUMAN INTELLECT: A Conceptual Framework

Der Theorie folgte die Praxis in 1968: 1968: NLS, or the oN-Line System

Die seltsame Abkürzung ist eine Folge dessen, dass die Finanzierung nicht von vornherein gesichert war. Daher fand die Entwicklung in Stufen statt, eine davon war OLS, ein off-line system.

Aufsehen erregte das ganze dann bei einer Demonstration diverser Techniken durch Douglas Engelbart: The Mother of All Demos.

Er zeigte nicht nur die Verlinkung von Texten sondern auch die von ihm erfundene Maus (auch wenn wieder jemand Anmerken mag, dass Telefunken eigentlich füher dran war...).

Die Idee ist also nicht wirklich neu, allerdings wurde das Arpanet erst 1969 ins Leben gerufen und bis eine nennenswerte Anzahl an Systemen dann wirklich verfügbar war, das hat noch ein bisschen gedauert.

Aber bereits im Jahr 1980 hat Tim Berners-Lee HyperText gedacht: Enquire Manual - In HyperText.

Er arbeitete für CERN, wem das europäische Kernforschungszentrum noch kein Begriff sein sollte, auch hier hilft Wikipedia weiter: CERN. Allerdings sollte nach den Problemem mit dem LHC und den vermuteten, die Erde verschlingenden, schwarzen Löchern, jeder diese Forschungsstätte kennen...

Das Dokument Information Management: A Proposal zählt wohl als die Geburtsstunde des World-Wide-Web. Allerdings wurde erst Ende 1990 ein Browser von ihm auf seiner NeXT-Station erstellt:

   WorldWideWeb: Editor, Server und line-mode Browser

Im Dezember 1992 wurde dann bei der Stanford University der erste Server außerhalb Europas in Betrieb genommen. (WWW ist eine europäische Erfindung!) Ab Januar entstanden dann eine Vielzahl von Browsern wie z.B. Midas, Erwise Viola, Samba, etc.

Viola war sogar in der Lage Bilder anzuzeigen, die anderen Browser waren rein textbasiert. Das dürfte in der heutigen Zeit irritierend sein aber es gibt durchaus auch heute noch brauchbare textbasierte Browser wie z.B. lynx oder w3m.

Die Darstellung der Bilder bei Viola erfolgte aber separat, die Bilder waren nicht so, wie man es heute gewohnt ist, im Browserfenster zusammen mit dem Text. Diese Leistung vollbrachte erst Mosaic, er wurde von der NCSA - National Center for Supercomputing Applications entwickelt: NCSA Mosaic.

Einer der Entwickler war Marc Andreesen, dieser machte sich ein Jahr später selbständig und gründete die Firma Mosaic Communications Corp. Dort entwickelte basierend auf Mosaic den Browser Netscape, später benannte er auch seine Firma in Netscape um. Da er den gegen Mosaic antreten musste, war der Codename intern Mozilla: Mosaic-Killer. Aber noch ein anderer populärer Browser basiert auf Mosaic: Der Internet Explorer von Microsoft.

Beim NCSA wurde nicht nur ein Webbrowser entwickelt, sondern auch ein Webserver, den NCSA httpd. Dieser wurde zwar entwickelt und eine zeitlang gepflegt. Wie es an Forschungszentren aber üblich ist, gibt es zum einen viele Zeitverträge aber auch sonst eine Fluktuation des Personals. Mit der Zeit verwaiste der Server und wurde nicht mehr gepflegt. Es gab aber von vielen Anwendern Patches um Fehler zu beseitigen und neue Funktionen in den Server zu implementieren.

Einige dieser Entwickler von Patches schlossen sich dann zu einer Gruppe zusammen, die den Server weiter pflegen wollte. Brian Behlendorf war einer von ihnen und er wollte schon immer ein Projekt mit dem Namen Apache haben. Daher wurde der Name gewählt, in Anlehnung an den Indianerstamm in den USA.

Es gibt aber noch eine weit verbreitete Erklärung, die zwar nicht ganz korrekt ist aber dennoch gut passt: Da der Apache aus Patches für den alten NCSA httpd entstanden ist, nannten manche den Grund für den Namen, dass dies schließlich ein a patchy server sei.

Geschichtlich sind aber noch andere Punkte interessant:

So stimmte CERN im April '93 zu, dass jeder das Webprotokoll frei und ohne Gebühren nutzen konnte. am 1.10.1994 wurde das W3C gegründet. Hier werden alle Standards beschlossen und veröffentlicht. Allerdings gab es diverse Firmen, die das ignorierten und ihre eigenen einführten und verwendeten. Das trifft aber in den berühmten Browser-Kriegen auf beide Parteien zu, sowohl Netscape als auch Microsoft waren dafür bekannt. Das war dann auch oft der Grund, warum damals viele Webseiten nur mit dem IE funktionierten und Netscape-Benutzer ausgesperrt wurden.

HTTP - Ein Überblick

Das Hyper Text Transport Protocol dient der Übertragung der WWW-Daten über das Internet. Im Wesentlichen sind das HTML-Seiten aber im Grunde genommen kann mit dem Protokoll alles übertragen werden, das wird es auch häufig. Die im WWW verlinkten Daten werden mit HTTP übermittelt.

HTTP baut dabei auf TCP auf, es wird der Standardport 80 verwendet. Wird in der URL kein Port angegeben, so ist es just dieser. Das Protokoll unterstützt mehrere Methoden wie die Daten übermittelt werden. Da es ein Internet-Standard ist, wird es in RFCs definiert:

Wer sich wundert: 1.0 war die erste offizille RFC-Version, davor gab es keine. So wurde der bisher bestehende Standard bei der Erstellung von 1.0 rückwirkend in 0.9 umbenannt, daher sind sie auch in einem RFC aufgeführt.

Falls schon jemand HTTP-Requests gesehen hat und sich fragt, warum man immer nur HTTP/1.0 oder HTTP/1.1 sieht, so ist das recht einfach. Zum einen wird die Version 0.9 nicht mehr wirklich verwendet zum anderen wurde erst mit Version 1.0 der HTTP-String zum Request hinzugefügt. Dazu gibt es aber später noch mehr, wenn es um den Aufbau von Requests geht.

Es gibt noch weitere RFCs, die jeweils Erweiterungen darstellen aber nicht zum eigentlichen Standard gehören. Ein wichtiger Vertreter ist hier WebDAV:

Wenig überraschend dürfte es wohl sein, dass das Protokoll viele Elemente bestehender Protokolle aufgreift und verwendet. Das kommt dann im nächsten Teil: Die Technik, beginnend mit URL und HTTP-Requests.

URLs

In RFC-3986 Uniform Resource Identifier (URI): : Generic Syntax werden URIs als Obermenge definiert, in die auch URLs fallen. Die Uniform Resource Locators dürften den meisten aus dem Browser bekannt vorkommen: Sie stehen in der Adresszeile im Browser, z.B.:

   http://www.lug-erding.de/index.html

Der erste Teil ist das Protokoll, dann folgt der Host, also der Server mit dem man sich verbinden möchte und letztlich die Ressource auf dem System, das ist index.html. Letzteres kann in der Regel weggelassen werden, der Webserver liefert diese automatisch aus, wenn der Request mit einem / endet (und die Datei existiert).

Eine vollständige URL für http sieht so aus, viele Teile sind aber optional

   http://{User:Passwort@}{Hostname}{:Port}/{Resource}{?Query-String}

Man kann sich bereits im Request mit Usernamen und Passwort gegen einen Server authentisieren. Dies kommt häufig mit dem ftp-Schema zum Einsatz: Per default wird sonst vom Webproxy, sofern einer in Verwendung ist, eine anonyme Methode zum einloggen verwendet.

Bei http wird Port 80, der Standardport, verwendet. Läuft der Server aber auf einem anderen Port, wie z.B. 8000, so kann dies hier explizit angegeben werden. Danach folgt die Ressource, die man anfordert. Das kann auch ein CGI-Skript sein, dass Anfragen verarbeitet. Die Informationen für das Skript können dann durch ein Fragezeichen abgesetzt werden. Mehrere Parameter werden dann durch ein kaufmännisches UND getrennt werden, z.B.:

   http://www.google.de/search?hl=de&source=hp&q=linux+erding&btnG=Google-Suche

Relevant bei Google ist der q-Wert: Er enthält die Suchwerte. Ferner können hier noch die bevorzugte Sprache (hl=de) angegeben werden usw. Wie der Teil nach dem Fragezeichen aufgebaut ist, bleibt hier Google, also dem Betreiber der Webseite freigestellt.

Der erste Teil ist, wie schon oben angedeutet, das Schema, in diesem Fall http. Hier können auch andere Schemata verwendet werden, wie z.B. ftp, dann wird automatisch mit Port 21 kommuniziert. Für das Laden lokaler Dateien wird file verwendet. Da man mit dem eigenen System kommuniziert kann der Hostname entfallen. Übrig bleiben dann aber drei Slashes, z.B.:

   file:///home/geschke/squid.pdf

Der Teil nach dem Slash hinter dem Hostnamen ist die relative URL, im RFC auch absoluter Pfad genannt. Dies ist bei Webanfragen, also HTTP-Requests, wichtig. Im Fall von

   http://www.lug-erding.de/vortrag/squid.pdf

lautet die relative URL, also der absolute Pfad: /vortrag/squid.pdf.

HTTP-Requests

Ein HTTP-Request besteht, je nach Methode, aus dem reinen Request mit (optionalen) Headerfeldern gefolgt von einer Leerzeile und dann dem optionalen Body. Beim reinen Holen der Seite wird ein GET-Request abgesetzt, hier gibt es keinen Body. Bei einem POST-Request existiert dieser aber.

Das ist auch der wesentliche Unterschied zwischen einem GET und einem POST: Beim GET werden Parameter per URL übertragen, beim POST können diese im Body des Requests untergebracht werden: Sie tauchen dann in der URL nicht mehr auf.

Der eigentliche, minimale HTTP-Request sieht dabei wie folgt aus:

   Methode <Leerzeichen> URL <Leerzeichen> Protokoll <CRLF>

Bei HTTP-Version 0.9 fehlt das Protokoll-Element. Die meisten Server werden dann aber mit einem Fehler reagieren, sie erwarten hier ein Protokoll oder anders ausgedrückt: Sie setzten mindestens 1.0 voraus.

Die vollständige URL wird allerdings erst mit neueren Servern akzeptiert, bei Proxys ist diese Form aber zwingend. Schließlich kann ein Proxy sonst nicht wissen, was das eigentliche Ziel ist.

Anstelle der vollständigen oder relativen URL können hier auch noch, basierend auf der verwendeten Methode ein '*' stehen (OPTIONS) oder eine authority (CONNECT). Zu den verschiedenen Methoden komme ich noch später.

Nach der Request-Zeile können noch optionale Headerfelder in der Form

   Headerfeld: Wert

folgen. Eine Ausnahme stellt HTTP 1.1 dar: Hier ist das Host-Headerfeld zwingend anzugeben. Die Headerfelder werden aber noch später beschrieben.

Nach den Headerfeldern folgt eine Leerzeile, unabhängig davon, ob eine Body folgt oder nicht.

Im einfachsten Fall besteht also ein HTTP Request aus z.B.:

   GET / HTTP/1.0

Der Server antwortet daraufhin mit einem HTTP-Header, der u.a. den Status enthält, dem Protokoll und weiteren optionalen Felder. Dieser Header gibt oft schon viel Aufschluss über den Inhalt. Bei Fehlersuchen ist es daher oft hilfreich, nur den Header zu betrachten. Folglich gibt es auch eine Methode, die nur den HTTP-Header zu einer URL anfordert. Diese heißt naheliegenderweise HEAD.

Das kann leicht mit telnet verwendet werden:

mail:~> telnet www.lug-erding.de 80
Trying 89.110.147.240...
Connected to www.lug-erding.de.
Escape character is '^]'.
HEAD / HTTP/1.0

HTTP/1.1 200 OK
Date: Tue, 25 May 2010 10:54:43 GMT
Server: Apache
Last-Modified: Tue, 11 May 2010 07:40:39 GMT
ETag: "1041200c-2b99-a44d2bc0"
Accept-Ranges: bytes
Content-Length: 11161
Connection: close
Content-Type: text/html

Connection closed by foreign host.

Die ersten 3 Zeilen werden von telnet ausgegeben, ebenso die letzte Zeile. Wichtig: Es muss nach dem Request eine Leerzeile folgen, d.h. nach HTTP/1.0 sollte man zweimal(!) die Enter-Taste drücken.

Die Antwort besteht zuerst aus dem Protokoll, dass der Server spricht, (in diesem Fall 1.1 im Gegensatz zum Request), gefolgt von einem Status- Code sowie (optionalen) Text zum Status. Letzterer dient wie bei all den anderen Codes zur einfacheren Fehlersuche.

Zur Antwort-Version sagt RFC-2145 Use and Interpretation of HTTP Version Numbers, dass die höchste unterstützte Version vom Server hier genannt werden soll. Daher antwortet der HTTP 1.1 Server auch mit dieser Version obwohl der Request offensichtlich ein 1.0-System erwähnt.

Im Wesentlichen betrifft der Unterschied aber nur die erlaubten Header- felder in der Antwort vom Server. Hier heißt es explizit, dass die Clients, die manche Felder nicht verstehen, diese schlichtweg ignorieren sollen.

Ein Request besteht aus reinem ASCII, nicht-ASCII-Zeichen müssen daher kodiert werden, ebenso wie einige verbotene Zeichen, die bereits für andere Dinge reserviert sind. Die Kodierung erfolgt mit einem Prozent- Zeichen und dem Wert, also z.B. %20 für ein Leerzeichen.

Zu den nicht-erlaubten Zeichen zählen, neben dem Leerzeichen:

  % / . .. # ? ; : $ , + @ & = 0x00-0x1f 0x7f-0xff

Einige Zeichen haben ja bereits eine Bedeutung, sei es als Bestandteil des Pfades (/ . ..) oder als Separator (? &), etc. Um diese Zeichen zu verwenden, müssen sie entsprechend kodiert werden. In der Regel übernimmt aber der Browser diese Aufgabe.

Weitere als unsicher eingestufte Zeichen, die deswegen ebenfalls kodiert werden sollen, sind:

  { } [ ] ~ | < > '

Die Tilde wird aber oft verwendet um persönliche Homeverzeichnisse zu kennzeichnen, z.B.:

  http://www.example.com/~geschke

Das würde dann in der Regel die Datei index.html im Homeverzeichnis des Benutzers geschke suchen und diese dann gegebenenfalls ausliefern.

Request-Methoden

RFC-2616 nennt folgende Methoden:

Es ist also durchaus überschaubar, dabei sind neben der eigentlichen Request-Zeile noch Headerfelder möglich, die analog zu E-Mail das Format

  Feld: Wert

haben.

Die wichtigste Methode ist wohl GET: Sie dient zum holen einer Ressource, in der Regel der HTML-Datei. Es können aber auch andere Inhalte geholt werden, wie Videos, PDFs, Softwarepakete, etc. Über die Headerfelder können weitere Angaben darüber gemacht werden, was man haben will oder welche Formen bevorzugt werden. So kann hier über die angegebene Sprache gleich die Antwort in der gewünschten Sprache ausgeliefert werden, auch wenn der Link immer gleich ist.

Bereits erwähnt wurde der HEAD-Request: Er ist eigentlich identisch zum GET, nur dass hier lediglich der Antwort-Header vom Server gesendet wird. Dies ist für die Fehlersuche oft hilfreich, die relevanten Infos sind oft hier schon zu finden.

Eine Variante zu GET ist POST: Während beim GET alle Daten, die zum Server gesendet werden sollen im Request enthalten sein müssen, ein GET hat keinen Body, besteht beim POST die Möglichkeit Daten im Body an den Server zu senden.

PUT und DELETE sind wohl selbsterklärend und werden heute eigentlich nicht mehr eingesetzt: Damit kann man Dateien zum Server übertragen bzw. dort löschen. Aus Sicherheitsgründen dürfte das wohl überall deaktiviert sein.

OPTIONS wiederum dient dazu, die Optionen des Servers zu erfragen, hier wird dann statt einen URL ein Asterisk verwendet, z.B.:

geschke@qfix:~$ telnet www.lug-erding.de 80
Trying 89.110.147.240...
Connected to www.lug-erding.de.
Escape character is '^]'.
OPTIONS * HTTP/1.0

HTTP/1.1 200 OK
Date: Thu, 27 May 2010 16:17:54 GMT
Server: Apache
Allow: GET,HEAD,POST,OPTIONS,TRACE
Content-Length: 0
Connection: close
Content-Type: text/plain

Der Server unterstützt also nicht alle Methoden. Die letzte in der Liste ist der TRACE-Request, dieser dient der Fehlersuche. Ein Request der mit TRACE abgesetzt wird, wird als Body in der Antwort wieder zurückgesendet. Dies dient dazu herauszufinden, welchen Weg ein Request genommen hat. Hier z.B. geht es über einen Webproxy:

geschke@qfix:~$ telnet 192.168.1.5 3128
Trying 192.168.1.5...
Connected to 192.168.1.5.
Escape character is '^]'.
TRACE http://www.lug-erding.de/ HTTP/1.0

HTTP/1.0 200 OK
Date: Thu, 27 May 2010 14:42:22 GMT
Server: Apache
Content-Type: message/http
X-Cache: MISS from majestix.physik.home
Via: 1.1 majestix.physik.home:3128 (squid/2.7.STABLE1)
Connection: close

TRACE / HTTP/1.0
Via: 1.0 majestix.physik.home:3128 (squid/2.7.STABLE1)
X-Forwarded-For: 192.168.1.54
Host: www.lug-erding.de
Cache-Control: max-age=2419200
Connection: keep-alive

Wie man in der Antwort sieht, taucht hier ein Via-Headerfeld auf, dies wurde vom Squid-Webproxy hinzugefügt, ähnlich wie die anderen Felder. Während der Request eine vollständige URL enthält, wandelt der Squid diese in einen relative URL um und setzt den geforderten Hostnamen in das Host-Feld.

Durch X-Forwarded-For kann der Server den tatsächlich anfordernden Client identifizieren. Viele entfernen diese Zeile bzw. untersagen dem Squid diese zu verwenden um ein wenig Datenschutz zu betreiben.

Die anderen beiden Header-Felder beziehen sich auf Caching-Optionen und ob eine persistente Verbindung angefordert wird.

Die letzte Option CONNECT dient nicht für Webserver sondern für Clients in Verwendung gegenüber einem Webproxy: Damit besteht die Möglichkeit Daten durch den Proxy zum Server zu tunneln, d.h. der Webproxy entfernt den CONNECT-Header und sendet die Daten im Body an die Adresse, die im CONNECT angegeben wurde.

Der Haupteinsatzzweck hierfür sind https-Verbindungen. Da diese zwischen Server und Client verschlüsselt sind, kann ein Webproxy den (Klartext-) Inhalt weder sehen, noch filtern oder gar im Cache ablegen. Daher werden hierdurch die Daten einfach durchgereicht.

Vorsicht ist hier aber angeraten: Hierüber kann alles getunnelt werden, auch unverschlüsselte und nicht-HTTP-Daten. Manche haben solche Proxys, sofern sie im Internet frei zugänglich sind, was wiederum bei Universitäten früher häufiger der Fall war, um darüber E-Mail-Spam zu versenden.

Aus diesem Grund sind gewöhnlich die Zielports für CONNECT-Requests eingeschränkt, meist ist hier nur Port 443 (https) erlaubt. Sollte z.B. Port 25 erlaubt sein, so geht dies hier:

geschke@qfix:~$ telnet 192.168.1.5 3128
Trying 192.168.1.5...
Connected to 192.168.1.5.
Escape character is '^]'.
CONNECT mail.lug-erding.de:25 HTTP/1.0

HTTP/1.0 200 Connection established

220 mail.lug-erding.de ESMTP Sendmail 8.14.3/8.14.3/Debian-5; Thu, 27 May 2010 18:35:54 +0200; 
HELO geschke.homlinux.org 
250 mail.lug-erding.de Hello 91-67-219-132-dynip.superkabel.de [91.67.219.132], pleased to meet you
quit
221 2.0.0 mail.lug-erding.de closing connection

D.h. es wird direkt eine permanente Verbindung mit dem Mailserver aufgebaut. Und obwohl ich von 192.168.1.54 aus auf 192.168.1.5 zugreife, sieht der Mailserver die IP-Adresse 91.67.219.132!

Man sollte also bei der Konfiguration durchaus Vorsicht walten lassen!

HTTP-Antworten

Die Antworten des Servers haben wir bereits mehrfach gesehen. Sie beginnt mit dem Protokoll, dabei wird hier das höchste vom Server gesprochene Protokoll angegeben. Es muss also nicht das gleiche sein, dass der Client in seinem Request angibt.

Danach folgt, durch ein Leerzeichen getrennt ein Statuscode, diese sind denen von E-Mail nicht unähnlich. Danach gibt es wieder ein Leerzeichen gefolgt von erläuternden Text zum Statuscode. Dieser dient wieder zur leichteren Fehlersuche, insbesondere wenn mit so Tools wie telnet gearbeitet wird.

Danach kann ein HTTP-Server-Header folgen, im üblichen Aufbau, d.h.:

   Feld: Wert

Je nach Methode kann dann der Body, auch Entity Body genannt, folgen. Dieser enthält dann die angeforderten Daten oder bei Fehlermeldungen einen entsprechenden Text. Dieser kann z.B. eine HTML-Datei sein, die einem Client, der nicht in der Lage ist mit Authentisierungsanforderungen automatisch umzugehen, warum der Zugriff nicht erlaubt ist.

Die gängigen Browser sehen aber den Code zur Authentisierung und erfragen dann Benutzernamen und Passwort beim Anwender. Danach setzen sie den Request erneut ab, jetzt jedoch mit den Authentisierungsdaten im Request-Header.

Ferner besteht auch die Möglichkeit persistente Verbindungen aufzubauen, d.h. nach Absetzen des Requests und Senden der Antwort kann danach die Verbindung bestehen bleiben um weitere Requests und Antworten darüber zu senden.

Bei HTTP-1.0 muss dies explizit über das Connection-Feld angefordert werden, bei HTTP-1.1 ist dies der Default. Daher verwende ich in den telnet-Tests immer Version 1.0, ansonsten müsste ich entweder explizit im Request-Header Connection: close angeben oder auf einen Timeout warten, damit die Verbindung nach der Antwort beendet wird:

geschke@qfix:~$ telnet www.lug-erding.de 80
Trying 89.110.147.240...
Connected to www.lug-erding.de.
Escape character is '^]'.
HEAD / HTTP/1.1
Host: www.lug-erding.de
Connection: close

HTTP/1.1 200 OK
Date: Thu, 27 May 2010 16:52:08 GMT
Server: Apache
Last-Modified: Thu, 27 May 2010 07:43:05 GMT
ETag: "10412003-2b9a-8a76f440"
Accept-Ranges: bytes
Content-Length: 11162
Connection: close
Content-Type: text/html

Hier sieht man in meinem Request gleich noch ein Headerfeld: Host. Über diese Pflichtfeld bei Version 1.1 muss der Host angegeben werden den man wünscht. Der Hintergrund ist, dass man darüber verschiedene Domains über einen Webserver mit einer IP-Adresse abbilden kann: Virtuelle Hosts.

Statuscodes

Die von HTTP verwendeten Statuscodes ähneln nicht nur zufällig denen von anderen Protokollen, er ist dreistellig, die erste Ziffer legt die Antwortklasse fest, die beiden anderen Ziffern sind aber nicht näher festgelegt.

Bei der ersten Ziffer gilt:

  1. Informativ: Die Anforderung wurde erhalten, der Prozess geht aber noch weiter. Das wird quasi für einen Zwischenstatus verwendet.
  2. Erfolg: Die Anfrage wurde erfolgreich empfangen, verstanden und auch akzeptiert.
  3. Redirection: Es müssen weitere Schritte unternommen werden damit der Request vollständig abgearbeitet werden kann. Z.B. kann hier ein Verweis auf eine andere Stelle im Internet verwiesen werden, wo die angeforderten Daten liegen.
  4. Client-Fehler: Die Anfrage ist nicht korrekt oder kann aus einem anderen Grund nicht erfüllt werden.
  5. Server-Fehler: Die Anfrage war gültig, der Server konnte sie aber dennoch nicht erfüllen/beantworten.

RFC-2616 definiert einige Statuscodes, im Infomationsbereich sind es nur zwei:

100 Continue

Dieser Wert dient dazu, dass der Server, basierend auf einer Anfrage des Clients mit entsprechendem Headerfeld, antworten kann ob er den Request annehmen würde. Wenn dem so ist, dann wird der Client den den Request-Body an den Server sendet.

Der Client sendet dazu im Header des Requests das Feld

   Expect: 100-continue

mit. Der Server antwortet mit 100 Continue, die Anfrage ist in Ordnung und kann gesendet werden oder er sendet ein 417 Expectation Failed.

Der RFC lässt nur die zwei Fälle zu, entweder 100 oder 417. Manche Webserver ignorieren aber das Expect und behandeln die Anfrage als eine gültige, die sie auch mit einem 2er Code beantworten. Viele ältere Clients schlucken das auch so. Squid hat das immer ignoriert oder genauer gesagt, er hat das Headerfeld einfach herausgefiltert. seit Version 2.7 liefert er dann aber ein 417 zurück. Das ist zwar das korrekte Verhalten, sorgt aber dafür, dass der eigentliche Request nicht gesendet wird, während alte Weblclients die 2er-Antwort geschluckt hatten. Daher gibt es beim Squid die Konfigurationsoption ignore_expect_100 on welche das alte Verhalten wieder herstellt, auch wenn es explizit gegen den RFC-Standard verstößt. Dabei wird das Expect-Headerfeld einfach herausgefiltert.

101 Switching Protocols

Damit kann der Server explizit anweisen, dass ein anderes Protokoll verwendet wird. Dies soll aber nur erfolgen, wenn das andere Protokoll Vorteile bietet. Damit dies aber funktioniert, muss der Client vorher durch das Upgrade-Headerfeld bekannt geben, welche Protokoll er noch versteht. In der Praxis ist mir dieser Code aber noch nicht aufgefallen, er wird wohl vermutlich fast nie verwendet.

Die 2er Codes dürften deutlich häufiger zu finden sein, aber auch hier gilt, dass es meistens nur die wenigsten sind, die wirklich in Gebrauch sind. Der häufigste ist wohl 200.

200 OK

Das ist der einfachst Fall: Alles ist OK, die Anfrage war korrekt und der Server liefert die Antwort.

201 Created

Ein Objekt wurde auf dem Server erstellt, in der Antwort vom Server findet man auch die URL mit der dieses Objekt referenziert werden kann. Das dürfte in der Regel eine Antwort auf ein PUT sein, das dürfte aber nur selten bis gar nicht mehr vorkommen: Die Verwaltung der Webseiten erfolgt in der Regel nicht über HTTP.

202 Accepted

Dieser Code ist ähnlich zum vorherigen, allerdings wurde nur der Request akzeptiert und noch nicht umgesetzt

203 Non-Authoritative Information

Die Felder in dem Antwort-Header sind weder die vollständigen vom Server sondern wurden anderweitig zusammengestellt, z.B. von einem Cache. Dieser Code sollte nur verwendet werden, wenn anderenfalls ein 200 erfolgen würde. Mit anderen Worten: Dieser Code dürfte in der Praxis wohl nur extrem selten zu sehen sein.

204 No Content

Der ist wohl selbsterklärend, der Code kann verwendet werden wenn keine Daten zurückgeliefert werden sollen aber dabei können die Headerfelder erweitert/aktualisiert werden. Es dient also dazu, den Status zu aktualisieren aber nicht um den dargestellten Inhalt bei einem Browser zu ändern.

205 Reset Content

Die Daten wurden vom Server verarbeitet, der Browser soll daraufhin die Dokumentenansicht, die den Status bewirkt haben, zurücksetzen. Die Antwort darf keine Headerfelder enthalten. Mit anderen Worten: Dieser Code dürfte wohl auch eher selten verwendet werden.

206 Partial Content

Dies ist die Antwort auf einen partiellen GET-Request. Ein Webclient kann mit einem Range-Headerfeld nur einen Teil der Daten anfordern. Wenn dies erfolgt wird, so kann mit diesem Code bekannt gegeben werden, das auch nur dieser Teil in der Antwort zu finden ist. Der Acrobat- Reader macht dies z.B. beim Download von PDF-Dateien: Es werden die weiteren Seiten bei Bedarf nachgeladen. Auch Microsofts Windows- Update verwendet diese Funktion: Sie laden Teilbereiche der Patches von verschiedenen Servern herunter. Das soll wohl die Performance optimieren, allerdings dauert es recht lange die involvierten Server zu erhalten...

In der Antwort gibt es dann entweder einen Content-Range-Header, der angbit welcher Bereich im Body enthalten ist oder einen Content-Type vom Typ multipart/byteranges präsentieren, der ein Content-Range-Feld enthält. Ferner müssen ein paar Felder für Webcaches anwendend sein, so das Date-Feld, ein ETag und/oder Content-Location als ob dies eine Antwort auf einen vollständigen Request wäre, sowie Expires, Cache- Control und/oder Vary-Felder, wenn der Field-Value ein anderer sein könnte. Dies dient dazu, dass Caches in der Lage sind herauszufinden, ob der gecachte Teil für eine Antwort verwendet werden kann.

Die 3er Codes tauchen gelegentlich auf, man nimmt diese aber nicht wahr, da der Client in aller Regel automatisch auf diese reagiert und es nicht wirklich ein Problem gibt:

300 Multiple Choices

Ein wohl seltener Code der verwendet wird, wenn es mehrere Optionen für einen Request gibt. Daraufhin soll dem Client eine Auswahlliste präsentiert werden, mit der seine Option auswählen kann.

301 Moved Permanently

Der angeforderte Inhalt ist verfügbar aber permanent an einer anderen Stelle. Der Server liefert im Location-Headerfeld die neue Adresse zurück, Clients werden daraufhin die URL ändern. Dies kann z.B. bei dem Newsticker von Heise gesehen werden:

geschke@qfix:~$ telnet www.heise.de 80
Trying 193.99.144.85...
Connected to www.heise.de.
Escape character is '^]'.
HEAD /newsticker HTTP/1.0

HTTP/1.1 301 Moved Permanently
Date: Fri, 28 May 2010 13:47:35 GMT
Server: Apache
Location: http://www.heise.de/newsticker/
Vary: Accept-Encoding
Connection: close
Content-Type: text/html; charset=iso-8859-1

Hier war die URL auch fehlerhaft, der abschließende Slash (/) fehlte. Der Webserver erkennt aber, was gewünscht ist und liefert die korrekte URL zurück. Im Browser erkennt man dann, dass plötzlich der Slash am Ende der Adresse auftaucht.

302 Found

Dies ist ein temporärer Redirect: Die URL ist eigentlich korrekt, aber im Moment befindet sich der Inhalt an einer anderen Stelle. Die neue Adresse steht ebenfalls im Location-Feld, allerdings soll der Anwender gefragt werden, ob er dem zustimmt, sofern die Request-Methode etwas anderes als HEAD oder GET ist: Da dann Daten gesendet werden, soll der Anwender darüber informiert werden, dass es an eine andere als die ursprünglich angegebene Adresse gesendet werden.

303 See Other

Dieser Code dient dazu, einem Webclient mitzuteilen, wo die Daten per GET gefunden werden können. In erster Linie war dies als Antwort auf einen POST-Request gedacht: Basierend auf den gesendeten Daten wird der Client zu einer anderen URL geleitet. Dabei ändert sich dann die Request-Methode (POST -> GET)

304 Not Modified

Dies ist wohl das wichtigste Feld für einen Webproxy: Anstatt die als Antwort die Daten zu liefern, wird nur der Header gesendet und dem Webcache (oder auch Browser mit lokalem Cache) wird mitgeteilt, dass die Kopie im Cache noch gültig ist. Der Caching-Webclient verwendet dazu das ETag-Headerfeld, sofern vorhanden oder ein If-Modified-Since-Header um dem Server mitzuteilen, welche Daten im Cache vorhanden sind.

305 Use Proxy

Auch dieser Fall ist vorgesehen: Der Zugriff soll über einen Proxy erfolgen anstatt direkt. Im Location-Feld wird dabei die URL des Proxys angegeben, der verwendet werden soll.

306 Unused

Dieser Code wird nicht mehr verwendet, es gab ihn aber früher einmal. Nun ist er nicht mehr in Gebrauch und wird als reserviert deklariert.

307 Temporary Redirect

Die angeforderte Ressource ist temporär woanders zu finden. Was hier aber der Unterschied zu 302 Found ist, entzieht sich mir...

So wie die 4er Codes ein Problem mit dem Client bedeuten, so besagen die 5er Codes ein Problem mit dem Server. Bei Fehlersuche ist das dann durchaus relevant: Nicht, dass man die ganze Zeit an der falschen Stelle sucht...

Von daher dürfte es wohl eigentlich wenig überraschend sein, dass es mehr 4er Codes gibt: Das größte Problem bei einem Server ist, dass er nicht läuft. In diesem Fall wird er aber kaum einen Fehlercode senden...

Die Version 1.0 von HTTP kennt nur die Codes 400-404, jenseits von 417 werden sie eigentlich nur von WebDAV verwendet.

400 Bad Request

Dies wird in der Regel dann gesendet, wenn die Syntax des Requests falsch ist. Oft sieht man es auch, wenn z.B. das Protokoll nicht angegeben wurde.

401 Unauthorized

Hiermit teilt der Server dem Client mit, dass er sich zu authentisieren hat. Der Server sendet ein WWW-Authenticate mit um mitzuteilen, wie der Client sich authentisieren soll, mitunter ist auch schon ein Challenge dabei. Der eigentliche Standard sieht nur basic oder digest vor, im ersten Fall gibt es dann keinen Challenge.

402 Payment Required

Der Code gefällt mir persönlich am besten. Allerdings ist er nicht wirklich in Gebrauch und der RFC sagt dazu: This code is reserved for future use.

403 Forbidden

Der Request an sich war korrekt, jedoch versagt der Server einem dennoch die Antwort. Auch eine Authentisierung wird hier nicht helfen. Häufig wird dieser Code verwendet, wenn man versucht ein Verzeichnis, das z.B. keine index.html enthält aufzulisten. Wenn es der Server erlaubt, so sieht man alle Dateien im Verzeichnis, auch die, die nicht direkt verlinkt sind. Viele wolle in einem solchen Fall den Inhalt des so angegeben Verzeichnis nicht offenbaren, z.B:

geschke@qfix:~$ telnet www.lug-erding.de 80
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
HEAD /fun/ HTTP/1.0

HTTP/1.1 403 Forbidden
Date: Sat, 29 May 2010 18:10:54 GMT
Server: Apache

404 Not Found

Dieser Code taucht immer dann auf, wenn man einen Request sendet und das gewünschte Ziel nicht existiert. Dies passiert häufig, wenn der Link nicht korrekt ist oder aber das Ziel, was leider häufiger passiert, nicht mehr vorhanden ist. Bei Studenten-Accounts von Universitäten kommt das z.B. häufiger vor: Der Student ist mit dem Studium fertig und hat die Universität verlassen.

Bei falschen Links sieht es z.B. so aus:

geschke@qfix:~$ telnet localhost 80
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
HEAD /not-found HTTP/1.0

HTTP/1.1 404 Not Found
Date: Sat, 29 May 2010 18:14:08 GMT
Server: Apache

405 Method Not Allowed

Das ist wohl selbsterklärend: Die verwendete Methode ist einfach nicht erlaubt. Die Antwort des Servers enthält im Allow-Feld eine Liste der erlaubtem Methoden für diese Anforderung, z.B.:

geschke@qfix:~$ telnet localhost 80
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
PUT / HTTP/1.0

HTTP/1.1 405 Method Not Allowed
Date: Mon, 31 May 2010 07:46:28 GMT
Server: Apache/2.2.15 (Debian)
Allow: GET,HEAD,POST,OPTIONS
Vary: Accept-Encoding
Content-Length: 313
Connection: close
Content-Type: text/html; charset=iso-8859-1

Danach folgt noch eine HTML-Seite, die den Fehler via Browser darstellbar macht.

406 Not Acceptable

Die Anforderung des Clients hinsichtlich des Accept-Headerfeldes sind nicht erfüllbar. Das kann z.B. der Fall sein, wenn ein Bild angefordert wird, der Server nur gif-Bilder hat, der Client aber nur png akzeptieren mag.

Mit Version 1.1 darf der Server aber andere Daten ausliefern, auch wenn sie die Anforderungen des Accept-Headers nicht erfüllen.

407 Proxy Authentication Required

Dies ist das Analogon zu 401, jedoch ist diese Authentisierung gegenüber einem Proxy und nicht dem Server. Gerade wenn beide, also Server und Proxy, eine Authentisierung erfordern ist es sinnvoll, wenn diese unterschiedlich angefordert werden. Weiß z.B. ein Browser von einer notwendigen Proxy-Authentisierung, so wird er, nachdem er Benutzername und Passwort erhalten hat, diese jedesmal mit zum Proxy senden. Dadurch wird er Benutzer nicht mehr danach gefragt, wohl aber, wenn der Server ebenfalls eine Authentisierung verlangt.

408 Request Timeout

Die Verbindung zum Server war erfolgreich, der Client hat aber nicht in der erlaubten Zeit den vollständigen Request gesendet. Bei manchen Servern sind diese Timeouts recht kurz, arbeitet man dann z.B. manuell mit telnet, so kann es sein, dass der Server die Verbindung mit 408 beendet, da man zu langsam war...

409 Conflict

Es gibt ein Problem mit der angeforderten Ressource. Der Client sollte in der Lage sein das Problem zu beheben und einen erneuten Request senden. Dieser Code dürfte oft in Verbindung mit der PUT- Methode auftreten, er ist also eher selten anzutreffen.

410 Gone

Die angeforderte URL existiert nicht mehr und es gibt keinerlei Informationen, wo diese Ressource nun gefunden werden kann. Mit diesem Code wird einem Client also definitiv mitgeteilt, dass die URL ungültig geworden ist und man entsprechend reagieren sollte.

411 Length Required

Dieser Code wird gesendet, wenn der Server den Request auf Grund eines fehlenden Content-Length-Headers ablehnt. Der Client darf daraufhin den Request erneut senden, sofern dann das entsprechende Headerfeld angegeben ist und die Länge des Request-Bodys enthält.

412 Precondition Failed

Wenn die Headerfelder des Requests Felder enthalten, die der Server nicht erfüllen kann, diese aber Bedingungen für den Request enthalten, so wird dieser Code gesendet.

413 Request Entity Too Large

In diesem Fall war der Request größer als der Server bereit ist zu akzeptieren. Wenn dieser Zustand nur temporär ist, so sollte der Server ein Retry-After-Headerfeld enthalten, welches angibt, wann der Client es erneut versuchen soll.

414 Request URI Too Long

Wenn die URL länger ist als der Server verarbeiten kann, so kommt diese Fehlermeldung zum Einsatz. Das sollte selten vorkommen, kann aber passieren, wenn ein POST-Request in einen langen GET-Request umgewandelt wird.

415 Unsupported Media Type

Dieser Code ist wohl selbsterklärend: Der angeforderte Medientyp wird vom Server nicht verwendet.

416 Requested Range Not Satisfiable

Wenn der Client einen Range Headerfeld enthält, der Server diesen Bereich aber nicht abdecken kann, so kommt dieser Code zum Einsatz. In diesem Fall soll der Server ein Content-Range-Headerfeld senden, welches den aktuellen Bereich der Ressource angibt.

417 Expectation Failed

In diesem Fall kann der Expect-Eintrag im Request nicht erfüllt werden. Wenn der Server ein Proxy ist, so kann dieser Code gesendet werden, wenn der Proxy weiß, dass der Server den Expect-Wert nicht erfüllen kann.

Das waren die Fehlercodes für die Clients, die für den Server folgen. In diesem Fall sind es 5er-Codes:

500 Internal Server Error

Das ist wohl der allgemeinste, nichtssagende Servercode, quasi ein catch-all: Der Server hat ein Problem bei der Reuqestverarbeitung festgestellt.

501 Not Implemented

Der Server unterstützt die angeforderte Funktionalität nicht, z.B.:

geschke@qfix:~$ telnet localhost 80
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
MGET / HTTP/1.0

HTTP/1.1 501 Method Not Implemented
Date: Mon, 31 May 2010 07:44:41 GMT
Server: Apache/2.2.15 (Debian)
Allow: GET,HEAD,POST,OPTIONS
Vary: Accept-Encoding
Content-Length: 298
Connection: close
Content-Type: text/html; charset=iso-8859-1

Danach folgt im Body noch eine HTML-Seite, die den Fehler beschreibt. Man sieht auch die Allow-Zeile in der Antwort: Hier stehen die vom Server unterstützten Methoden. BTW: MGET existiert natürlich nicht als Methode, zumindest bis Version 1.1 nicht.

502 Bad Gateway

Der Server, der als Gateway oder Proxy arbeitet, hat einen ungültige Antwort vom nächsten, in der Hierarchie folgenden Server, erhalten.

503 Service Unavailable

Dieser Code wird oft bei einem IIS zu sehen sein: Der Server ist entweder wegen Wartungsarbeiten nicht in Funktion oder aber, das ist wohl der häufigere Fall, der Server ist überlastet. Dies wird als ein temporärer Fehler angesehen. Wenn die Downtime bekannt ist, so kann der Server ein Retry-After-Headerfeld senden mit der Zeit, wann ein Zugriff wieder möglich sein sollte.

Im RFC steht ferner, dass obwohl dieser Code existiert, muss kein Server in verwenden wenn er überlastet ist: Die Alternative besteht dann darin, einfach keine weiteren Verbindungen mehr anzunehmen. Dies ist auch die sinnvollere Lösung: Oft liegt die hohe Last im Sekundenbereich, da TCP die Pakete mehrmals sendet, wenn vom Server keine Antwort erfolgt. Oft kommt dann noch eine Verbindung zustande, nur ein paar Sekunden später. Das ist wohl sinnvoller als auf einen Reload im Browser zu warten. Außerdem erscheint dann die Verfügbar- keit des Servers viel besser zu sein.

504 Gateway Timeout

Dies wird bei Gateways oder Proxys verwendet, wenn keine zeitnahe Antwort von einem Upstream-Server (oder Proxy) erfolgt. Dies kann auch der Fall sein, wenn andere Dienste, die zur Erfüllung des Requests notwendig sind, in einen Timeout laufen.

505 HTTP Version Not Supported

Der Server unterstützt oder verweigert die HTTP-Version des Clients, die im Request angegeben ist. Der Server sollte dann in seiner Antwort eine Begründung für die Ablehnung enthalten. Da es aber eigentlich nur drei Versionen gibt: 0.9, 1.0 und 1.1 sollte das eher selten auftreten, meist wird auch mit einem 400 Bad Request geantwortet, wenn der Server die Protokollversion ablehnt. Im Fall von 0.9 fehlt z.B. das Protokoll am Ende des Requests.

Authentisierung

Relevant für Authentisierungsverfahren ist noch immer RFC-2617 HTTP Authentication: Basic and Digest Access Authentication

Hierin werden nur zwei Verfahren genannt: basic und digest.

Das erste Verfahren ist das wohl am weitesten verbreitete und zugleich sowohl das einfachste als auch das unsicherste Verfahren. Darin werden Benutzername und Passwort zwar Base64-kodiert aber dennoch unverschlüsselt übertragen: Wer es schafft, den Netzwerk- verkehr mitzulesen, der kann dadurch leicht an die gültigen Daten kommen.

Digest hingegen umgeht dieses Problem durch ein Challenge-Response- Verfahren. Dies ist ein wenig aufwändiger aber verhindert effektiv, dass die Credentials mitgesnifft werden können. Leider, das ist wohl das größte Problem, unterstützen nicht alle Webclients diese Art der Authentisierung. Das ist aber dann schon ein klassisches KO- Kriterium für diese Art der Authentisierung.

Was beide Verfahren aber gemein ist: Durch die Authentisierung kann zwar der Zugriff auf Webressourcen geregelt werden, es ist damit aber keine Verschlüsselung der übertragenen Daten gegeben. Hierauf muss dann auf TLS, also https, zurückgegriffen werden. Das erklärt dann aber wohl auch, warum sich die basic-Authentisierung so standhaft hält: Wenn die Verbindung ohnehin verschlüsselt ist, braucht man sich auch keine Gedanken mehr über die Verschlüsselung des Passwortes machen.

Einen wesentlichen Unterschied gibt es aber noch zu erwähnen: Bei basic wird das Passwort unverschlüsselt übertragen, es muss aber auf dem Server nicht im Klartext vorliegen. Auf der anderen Seite muss bei digest das Passwort auf dem Server im Klartext vorliegen. Warum, das wird spätestens bei den Erläuterungen zu dem Verfahren klar, es kommt also später noch.

Aus dem Hause Microsoft gibt es noch Authentisierungsverfahren, die aber keinen Standard darstellen, auch wenn sie in RFC-4559 SPNEGO-based Kerberos and NTLM HTTP Authentication in Microsoft Windows erwähnt werden. Dieser RFC ist aber nur INFORMATIONAL, es ist also, wie eingangs erwähnt, kein Standard.

Kerberos ist die neuere Variante aus dem Hause Microsoft und sollte NTLM ersetzen, welches eigentlich schon lange nicht mehr geben dürfte. Auf Kerberos werde ich nicht näher eingehen, zum einen hat Microsoft das Kerberos gegenüber der MIT-Version abgewandelt und zum anderen ist das Framework alleine schon von den vielen Begriffen her nicht gerade dazu gedacht, dass es einfach verständlich ist.

NTLM hingegen ist die LanManager-Erweiterung für WindowsNT und ist eigentlich schon lange für Tod erklärt worden. Allerdings hält es sich noch recht gut: Es funktioniert ohne den ganzen Kerberos-Aufwand, es ist deutlich einfacher und bietet zudem zwei Vorteile:

  1. Es wird kein Username/Passwort im Klartext übertragen.
  2. Der Browser kann die Authentisierung selbständig durchführen, es muss weder Username noch Passwort verwendet werden.

Dies kann dann als ein Single-Sign-On-Verfahren angesehen werden, die Authentisierung erfolgt hier gegen einen PDC, bei dem hat sich der Benutzer beim Anmelden am System authentisiert. Auf der anderen Seite muss jeder Dienst, der gegen NTLM authentisiert, im Hintergrund eine Verbindung zum PDC haben.

Da wird das Problem schon offensichtlich: Bei Webclients rund um den Globus, die auf einen NTLM-geschützten Server zugreifen wollen, wird dies nur sehr bedingt funktionieren.

Ein anderer Bereich wird da schon lukrativer: Authentisierung gegenüber einem Webproxy wie z.B. den Squid. Hier sind in der Regel sowohl der Proxy als auch die Clients in der gleichen Windows-Domäne. Technisch bedingt hat das Verfahren zudem einen erheblichen Overhead, der sich im LAN kaum bemerkbar machen dürfte: Die Authentisierung erfolgt über drei Requests und zwar für jeden(!) eigentlichen Request.

Im LAN sollte dies normalerweise kein Problem darstellen: Hier ist die verfügbare Transferrate deutlich größer als im Internet und etwas wie Latenz kann hier ignoriert werden. Kritisch ist dies dann, wenn z.B. Außenstandorte via Satellitenverbindung angeschlossen sind. Da kann das Verfahren zu erheblichen Verzögerungen beim Surfen führen.

Im folgenden werde ich mich aber auf die beiden Standard-Verfahren aus dem RFC-2617 beschränken: basic und digest.

Authentisierung: basic

Wie bereits erwähnt ist dies die älteste und einfachste Form um eine Authentisierung zu realisieren. Das Prozedere ist allerdings unabhängig vom Verfahren: Der Server wird mit einem 401 Unauthorized antworten, handelt es sich um eine Authentisierung gegen einen Proxy, so lautet die Antwort 407 Proxy Authentication Required.

Warum zwischen Webserver und Webproxy unterschieden wird ist wohl klar, wie sollte sonst ein Client wissen, welche Credentials gesendet werden sollen. D.h. es kann sich sowohl gegen einen Proxy als auch gegen einen Webserver authentisiert werden: Zuerst antwortet der Proxy mit 407, woraufhin die Credentials für den Proxy gesendet werden, danach erfolgt dann ein 401 vom Server.

Im Header der Antwort wird vermerkt wie sich authentisiert werden soll. Wenn es sich um einen Webserver handelt, so sendet dieser ein anderes Headerfeld als ein Webproxy. Schließlich müssen die zwei unterscheidbar sein und beides muss möglich sein.

Im Fall vom Server wird

   WWW-Authenticate: Basic realm="Web-Bereich"

und im Fall eines Proxys wird

   Proxy-Authenticate: Basic realm="Proxy-Bereich"

gesendet. Der realm dient dabei dazu den Bereich näher einzugrenzen, für den die Authentisierung gültig ist. Ein Server kann z.B. verschiedene Bereiche mit unterschiedlichen Benutzern/Passwörtern absichern. Bei der Basic-Authentisierung wird davon ausgegangen, dass alles hierarchisch unterhalb des durch den realm geschützten Bereiches der gleiche Username und Passwort gelten.

In diesem Fall wird also einmal nach den Credentials gefragt und sie werden dann bei allen Requests, bei denen der Browser vermutet, dass sie notwendig sind, automatisch mitsenden. D.h. das 401 taucht hier nur einmal am Anfang auf.

Beim Proxy verhält es sich analog: Der realm beschreibt, gegen welchen Proxy die Authentisierung erfolgen soll, der Benutzer wird diesen String auch bei der Abfrage präsentiert. Auch hier werden bei allen weiteren Requests, die an den gleichen Proxy gesendet werden, die Credentials gleich mitgesendet: Man muss sich nur einmal am Anfang authentisieren, der Browser wird die Daten dann in Zukunft automatisch mitsenden.

Die Antwort eines Clients auf einen Authentisierungsrequest ist einfach, er sendet einen Authorization- bzw. Proxy-Authorization-Header mit dem verwendeten Verfahren, hier basic, so wie mit seinen Credentials. Diese wiederum sind eigentlich recht simpel: Das ist im Fall von basic ein Benutzername, ein Doppelpunkt und das Passwort.

Für HTTP-Header gibt es allerdings eine Einschränkung: Dieser muss rein aus ASCII-Zeichen bestehen. Aus diesem Grund, sowohl der Benutzername als auch das Passwort müssen nicht rein aus ASCII-Zeichen bestehen, wird diese Zeichenkette noch Base64-kodiert, z.B.:

   Proxy-Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==

Hier ist ein einfaches Beispiel gegen einen Apache:

   http://localhost/basic/

Der erste GET-Request sieht so aus, ich habe hier alle Headerfelder im Header belassen:

GET /basic/ HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.1.6) Gecko/20091216 
  Iceweasel/3.5.8 (like Firefox/3.5.8)
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: de-de,de;q=0.8,en-us;q=0.5,en;q=0.3
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive

Was man sieht, ist was ein Browser alles mitsendet und vielen vermutlich so nicht unbedingt bewusst ist. In diesem Fall war es ein Iceweasel, der sich als ein Mozilla/5.0 präsentiert. Das macht im Übrigen ein IE auch, erst im Kommentar, also innerhalb der runden Klammern wird dort klar, dass es ein IE ist, z.B. zum Vergleich:

User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Avant Browser; Message Center; 
  .NET CLR 1.1.4322; SpamBlockerUtility 4.6.1; .NET CLR 1.0.3705; .NET CLR 2.0.50727)

Es folgt mit Accept eine Liste von MIME-Typen, die der Client akzeptiert, zusammen mit einem q-Wert. Letzterer gibt eine Gewichtung vor, welche Typen bevorzugt sind. Da ein ordentlicher Browser alles akzeptieren sollte, besteht der letzte Wert aus */*. Die folgenden Accept-Header bewirken ähnliches: Es werden die bevorzugten Daten des Clients angegeben, also bevorzugt deutsche Seiten mit einem ISO-8859-1 Zeichensatz. Das Feld Accept-Encoding gibt an, dass der Client auch gepackte Inhalte akzeptiert, so kann der Webserver die HTML-Daten vor der Auslieferung entweder mit gzip oder compress komprimieren. Die letzten beiden Felder geben an, dass der Client eine persistente Verbindung zum Server wünscht und gibt zudem einen Timeout dafür an.

Die Antwort des Servers hierauf lautet:

HTTP/1.1 401 Authorization Required
Date: Thu, 03 Jun 2010 09:56:05 GMT
Server: Apache/2.2.15 (Debian)
WWW-Authenticate: Basic realm="Basic-Login"
Vary: Accept-Encoding
Content-Encoding: gzip
Content-Length: 339
Keep-Alive: timeout=15, max=100
Connection: Keep-Alive
Content-Type: text/html; charset=iso-8859-1

Die meisten Felder dürften selbsterklärend sein, so wird das Datum der Bearbeitung ebenso wie der Typ des Servers gesendet. Dann kommt die Aufforderung sich mit basic-Authentisierung zum realm Basic-Login sich zu erkennen zu geben.

Interessant ist das Vary-Feld. Dieses ist für Webcaches relevant, mit diesem wird angegeben, dass die Antwort vom Server variieren kann und zwar beim Accept-Encoding. Die nächste Zeile gibt an, dass die Daten mit gzip gepackt versendet wurden.

Wenn also ein Webproxy die Seite cached und ein anderer Client, der kein Accept-Encoding: gzip im Request angibt, kann mit diesen Daten nichts anfangen. Anhand des Vary-Headerfeldes weiß der Proxy aber, dass es die Daten vermutlich auch ungepackt vom Server geben kann: Er wird daher die Daten nicht aus dem Cache nehmen und den Request auch nicht ablehnen, er wird stattdessen den Request an den Server weiterreichen.

Das Content-Length-Feld ist zwingend hier: Da die Verbindung persistent ist, muss der Client schließlich wissen, wann er alle Daten erhalten hat und erneut einen Request senden kann.

Das letzte Feld gibt dann die Art der Daten, die im HTTP-Body enthalten sind, als MIME-Type an.

Da es sich um eine Authentisierungsanforderung handelt, wird der Client nun den Request erneut senden, diesmal mit Authorization-Feld (hier habe ich die nicht-notwendigen Headerfelder der Übersicht halber entfernt):

GET /basic/ HTTP/1.1
Host: localhost
Authorization: Basic Z2VzY2hrZTpnZWhlaW0=

Das lässt sich z.B. leicht mit mimencode und der Option -u dekodieren:

$ echo Z2VzY2hrZTpnZWhlaW0= | mimencode -u
geschke:geheim

Der Benutzername ist also "geschke" und mein Passwort ist "geheim".

Diese Angaben sind, hinsichtlich dieses Servers, korrekt:

HTTP/1.1 200 OK
Date: Thu, 03 Jun 2010 09:56:14 GMT
Last-Modified: Tue, 27 Apr 2010 07:45:01 GMT
Etag: "26e77f-c4-485331224ed63"
Accept-Ranges: bytes

Hier habe ich noch drei Felder stehen lassen, die auch hinsichtlich eines Proxys interessant sind: Last-Modified gibt an, wann die Seite zum letzten Mal verändert wurde. Ein Cache kann dann einen If-Modified-Since-Headerfeld mit diesem Wert oder dem Date-Wert der gecachten Antwort senden. Sollte sich der Inhalt seitdem nicht geändert haben, so sendet der Server den Inhalt nicht noch einmal sondern nur einen Antwort mit dem Code 304 Not Modified, d.h. der Proxy kann die Antwort aus dem Cache entnehmen.

Manche Proxys bauen sich selber, in Ermangelung anderer Werte, ein eigenes Verfalldatum basierend auf diesen Header: Je nachdem wie alt die Seite ist, wird die Dauer, die die Seite im Cache verweilen kann größer. Was also seit langem nicht mehr verändert wurde, wird vermutlich auch in naher Zukunft nicht geändert werden.

Viel besser ist hier das ETag, auch Entity-Tag genannt: Dieser Wert soll eindeutig für den Inhalt sein, d.h. ändern sich die Daten, so ändert sich auch der ETag. Das ist wohl ein deutlich besseres Verfahren als über das Datum zu argumentieren, zumal es durchaus passieren kann, dass der Zeit- stempel nicht immer korrekt ist.

Der letzte Punkt gibt dann durch Accept-Ranges: bytes an, dass auch nur Bereiche von Daten angefordert werden können, d.h. der Server ist auch bereit nur einzelne Blöcke anstelle des gesamten Inhaltes zu übertragen. Der Acrobat-Reader (insbesondere als Plug-In) verwendet dies z.B. beim Download von PDF-Dateien: Es werden immer nur ein paar Seiten geladen, die auch angezeigt werden. Will man mehr sehen, so muss der Rest einfach nachgeladen werden.

Authentisierung: digest

Beim Lesen von RFC-2617 kann man leicht verwirrt werden, sofern es um die Digest-Methode geht. Der Grund dafür ist recht simpel, die Methode ist recht offen spezifiziert worden. Letztlich ist der eigentliche Mechanismus recht einfach: Der Server sendet eine Zeichenkette, nonce genannt. Der Client fügt diese seinem Passwort hinzu und erstellt aus dem Ergebnis einen Hash. Es können verschiedene Hashfunktionen dafür verwendet werden aber eigentlich gibt es derzeit nur eine: MD5.

Eine MD5-Summe ist natürlich nicht eindeutig, wie alle Hashfunktionen, die eine große Menge auf eine kleine abbilden: Es wird immer Kollisionen geben, d.h. zwei verschiedene Zeichenketten können den gleichen Hashwert ergeben. Allerdings sind solche Kollisionen rein zufällig extrem selten, sie sind, das ist der wichtigste Punkt, bislang nicht vorhersagbar.

Der wichtigste Aspekt ist: Diese Hashfunktionen sind nicht umkehrbar, man kann nicht zurückschließen, welche Daten zu diesem Hash geführt haben. Es ist auch nicht vorhersagbar, welche Ursprungsdaten vorliegen müssen um den gleichen Hashwert zu erhalten. Daher sind auch MD5-Summen als Prüfwerte so beliebt: Wird die Datei verändert, so ändert sich auch die Prüfsumme. Aber es gibt keine vorberechenbare Möglichkeit die Daten so z.B. durch Fülldaten zu ergänzen, dass der alte MD5-Wert ermittelt werden kann.

Daher ist dies Verfahren eigentlich sehr sicher. Durch den nonce wird zudem sichergestellt, dass der Hashwert, sofern sich die nonce auch wirklich ändert, nicht der gleiche ist: Er variiert mit jeder neuen nonce. Anderenfalls könnte man, sofern einmal die Verbindung mitgesnifft wurde, sich mit dem Hash authentisieren.

Das ganze Verfahren wird aber noch ein wenig komplizierter: Es besteht auch die Möglichkeit, dass ein Server sich gegenüber dem Client authentisiert. Dieser Fall ist auch vorgesehen. Allerdings muss dazu ein gemeinsames secret, gewöhnlich Passwort genannt, beiden Seiten bekannt sein. Das trifft natürlich auf das Client-Passwort zu. Der Client sendet also einfach einen client-nonce, cnonce, und der Server führt die gleichen Aktionen aus, wie es normalerweise der Client macht. Auf diese Weise können sowohl der Client als auch der Server auf die gleiche Weise den Hashwert ermitteln und das Ergebnis mit dem übertragenen Wert vergleichen: Nur wer das gemeinsame secret kennt, kann so, abgesehen von möglichen Kollisionen, den gleichen Hashwert ermitteln.

Details

Wie bereits erwähnt, kann es mehrere Algorithmen geben, diese werden über den Parameter algorithm vom Server mitgeteilt. Allerdings nennt der RFC nur zwei, die beide auf MD5 basieren: MD5 und MD5-sess. Letzteres war für die Authentisierung von Sessions gedacht, es vereinfacht die Berechnung ein wenig.

Ferner gibt es noch einen Parameter qop, dieser regelt die Quality Of Protection. Hier gibt es drei Möglichkeiten:

  1. Der Parameter fehlt, er wurde erst später eingeführt.
  2. auth: Es wird nur die Authentisierung berücksichtigt
  3. auth-int: Hier wird auch noch die Integrität der Daten berücksichtigt: Es gibt eine Prüfsumme über die Daten im Body der Verbindung. Hier wird übrigens zwischen Entity- und Daten- body unterschieden: Die Prüfsumme gilt für den Entity-Body, d.h. bevor irgendeine Transfer-Encoding stattgefunden hat.

Für den Algorithmus sind zwei Datenanteile relevant, zum einen sind das die sicherheitsbezogenen Daten, A1, zum anderen die nachrichtbezogenen Daten, A2.

Für die Berechnung von A1 gilt, basierend auf dem Algorithmus:

  MD5:       A1 = {Benutzer}:{Realm}:{Passwort}
  MD5-sess:  A1 = MD5({Benutzer}:{Realm}:{Passwort}):{nonce}:{cnonce}

Die Idee bei MD5-sess ist, dass der MD5-Wert aus Benutzer, Realm und Passwort nur einmal berechnet werden muss.

Für A2 gilt unter Berücksichtigung von qop:

qop A2
undefiniert Request-Methode:URL
auth Request-Methode:URL
auth-int Request-Methode:URL:MD5(entity-body

Die Request-Methode ist also etwas wie GET oder POST, die URL ist die aus der Request-Zeile. Dies kann entweder ein * sein, eine absolute oder eine relative URL, es muss aber mit der Request-Zeile überein stimmen, also keine Mischung.

Der Digest-Algorithmus lautet nun:

qop Digest-Algorithmus
undefiniert MD5(MD5(A1):nonce:MD5(A2))
auth/auth-int MD5(MD5(A1):nonce:nc:cnonce:qop:MD5(A2))

Der Parameter nc stellt dabei den nonce-count dar. Weitere Parameter sind nextnonce, damit kann der Server schon im Vorfeld, den nächsten nonce mitsenden und das weitere Verfahren abkürzen. Über den Parameter stale=true kann der Server auch mitteilen, dass der gleiche nonce noch weiter verwendet werden. Damit kann man für ein kleines Zeitfenster die Berechnung neuer Werte auslassen, bis der Server erneut ein 401 bzw. 407 mit neuer nonce sendet. Hierbei besteht natürlich die Gefahr, dass der Hash mitgesnifft und von anderen verwendet wird. Daher sollte das Zeitfenster recht klein sein.

Der Client sendet in seiner Antwort den uri-Parameter die URL mit, welche in den Hash eingegangen, ebenso wird die nonce, der nc, username, realm sowie qop mitgesendet. Mit der cnonce kann der Server zur Authentisierung aufgefordert werden. Die eigentliche Antwort steckt beim Client im response- Parameter, beim Server im rspauth-Parameter. Interessant ist auch noch der domain-Parameter: Hierdurch kann noch granularer festgelegt werden für welche URLs/Pfade die Methode gilt.

Ein Beispiel sollte das alles ein wenig verdeutlichen. Hier habe ich die Header, die nicht zur Authentisierung gehören der Übersicht halber entfernt. Der Zugriff erfolgt hier auf

   http://localhost/digest/

und der Client-Request sieht, wenig überraschend, einfach aus

GET /digest/ HTTP/1.1
Host: localhost

Die Antwort vom Server ist da schon spannender, hier kommt der 401-Code in Verbindung mit der Digest-Methode:

HTTP/1.1 401 Authorization Required
Date: Thu, 03 Jun 2010 09:56:41 GMT
WWW-Authenticate: Digest realm="LUG-Erding", 
  nonce="3E4qOR2IBAA=afd655f551e63c0f239f118842d2a0e002d92593", algorithm=MD5, 
  domain="/digest", qop="auth"

Hier wird nur authentisiert (qop="auth"), der Algorithmus ist, wenig überraschend, MD5 und der realm hat die gleiche Bedeutung wie bei basic- Authentisierung.

Hier ist nun die Antwort des Clients auf diese Aufforderung

GET /digest/ HTTP/1.1
Host: localhost
Authorization: Digest username="geschke", realm="LUG-Erding", 
  nonce="3E4qOR2IBAA=afd655f551e63c0f239f118842d2a0e002d92593", uri="/digest/",
  algorithm=MD5, response="006507c9201068d1d42546f2b65bb7ba", qop=auth, 
  nc=00000001, cnonce="a5a3399a2aa0895c"

Die ist also etwas länger, man sieht aber den nonce vom Server wieder. Es wäre jedoch unklug, wenn der Server diese einfach verwenden würde: Wenn es keine gerade von ihm generierte nonce ist, so ist das Verfahren wohl hin- fällig. Es wäre dann ein leichtes, alte mitgeschnittene Werte zu verwenden.

Hier sieht man aber auch, dass der Client eine cnonce mitsendet. Der Server reagiert darauf entsprechend:

HTTP/1.1 200 OK
Date: Thu, 03 Jun 2010 09:56:50 GMT
Authentication-Info: rspauth="a65658cb1cccea078b35c321a6ce3132", 
  cnonce="a5a3399a2aa0895c", nc=00000001, qop=auth

D.h. vom Server her ist der Zugriff korrekt, der Client kann noch an Hand von rspauth entscheiden, ob es auch der richtige Server ist, der hier antwortet.

Ein Problem dieser Methode besteht in der doch etwas aufwändigen Methode der Bestimmung des Hashwertes und der Überprüfung. Für einen Client sollte das kein Problem sein, für einen Server, der viele parallele Verbindungen verarbeitet, kann es schon problematisch sein. Auch der Server muss Buch darüber führen, welche nonces noch aktiv im Umlauf sind.

Die zeitlich limitierte Wiederverwendung eines nonces kann hier ein guter Kompromiss sein. Auch das senden des nächsten nonce über den Parameter nextnonce ist sinnvoll: Dadurch kann man viele 401er bzw. 407er Meldungen vermeiden und auf diese Weise den Durchsatz erhöhen.

Authentisierung: Fazit

Ein Problem hat man bei beiden Verfahren: Das Passwort. Bei der Basic- Authentisierung wird das Passwort im Klartext übertragen, es kann dann auf dem Server verschlüsselt vorliegen. Bei digest sieht es umgekehrt aus: Das Passwort wird nicht im Klartext übertragen, es muss aber im Klartext auf dem Server vorliegen, damit die Hashwerte für den Vergleich berechnet werden können. Dies ist analog zu PAP und CHAP.

Die Frage ist also: Wem vertraut man mehr, dem Netzwerk oder dem Betreiber des Servers? Hinzu kommt die schlechte Verbreitung von MD5, das dürfte wohl auch am doch recht komplexen RFC liegen: Die Methode ist nicht sehr einfach zu verstehen. Ferner gibt es noch Clients, die digest noch nicht beherrschen.

Ein Problem haben beide Verfahren: Gegen brute-force-Angriffe bietet nur ein gutes Passwort halbwegs passablen Schutz. Allerdings dürften die meisten Server in der Lage sein, Zugänge temporär zu sperren, wenn es zuviele falsche Authentisierungen in kurzer Zeit gibt.

Wie ich schon erwähnte, kann man via digest und einer Phishing-Seite nicht an das Passwort gelangen. Allerdings wird ein Benutzer in der Regel auch nicht mitbekommen, wenn die Phishing-Seite von digest auf basic umstellt.

Aber noch ein anderer, viel gewichtigeres Problem kann bei digest und Phishing-Seiten bzw. Angriffen auf dem Weg zum Server eintreten:

   Man-in-the-middle

Der Trick ist einfach: Der Client verbindet sich mit dem Phishing-Server, dieser macht zeitgleich eine Verbindung zum eigentlichen Server auf. Den vom Server erhaltenen nonce reicht er nun einfach an den Client weiter. Dieser ermittelt die digest-Antwort und sendet diese an den Phishing- Server. Da hier bei qop=auth nur die Authentisierung im Spiel ist, kann der Inhalt leicht ausgetauscht werden.

Ein Server kann auch mehrere Verfahren zur Authentisierung anbieten, der Client ist dann gefordert, dass sicherste, das er beherrscht, auszuwählen.

Ein sicherer Ausweg aus dem Dilemma besteht durch die Verwendung von SSL, hier ist die gesamte Verbindung verschlüsselt und der Server muss sich korrekt ausweisen können. Das wird durch das Zertifikat gewährleistet, sofern es von einer vertrauenswürdigen Stelle erstellt wurde, also kein selbstgeschnitztes, snake-oil Zertifikat.

Hierdurch wird nur der Server authentisiert, das gleiche kann aber auch für den Client erfolgen: Auch der Client kann Zertifikate verwenden um sich auszuweisen.

Der einfache Schutz durch Benutzername/Passwort ist aber oft im Netz ausreichend, wenn es z.B. um Forenbeiträge oder ähnliches geht. Hier wäre ein Schaden durch Missbrauch mitunter noch akzeptabel. Man sollte es sich aber vorher überlegen, ob dem auch so ist.

Im Zusammenhang mit einem Webproxy wird häufig entweder auf basic oder auf NTLM-Authentisierung gesetzt. Dies dient in erster Linie dazu, den Anwender zu identifizieren und passend dazu Zugangsbeschränkungen aufzu- erlegen oder zu entfernen.

Die Passwort-Abfrage hat den Vorteil: Dem Benutzer wird bewusst, dass er sich authentisieren muss und das dies vermutlich in Logdateien protokolliert wird. Das geht dann frei nach dem Motto:

   Ab jetzt verlässt Du das Firmennetz, die Benutzung des Internets  
   darf nur zu geschäftlichen Zwecken erfolgen.

Die Realität ist zwar meistens eine andere aber zumindest ist der Benutzer sich so bewusst, dass seine Aktivitäten in Logdateien auftauchen und mit ihm in Verbindung gebracht werden können.

Bei NTLM funktioniert dies in der Regel nicht: Die Authentisierung erfolgt im Hintergrund, der Anwender bekommt es nicht mit. Es sei denn, dass die Anmeldung fehlschlägt...

Wer sich nun fragt, was mit LDAP-Authentisierung ist, der kann sich wohl die Antwort selber geben. Das ist einfach nur eine andere Methode wie die Authentisierungsdaten auf dem Server gespeichert werden...

Allgemeine Headerfelder

Wie bereits mehrfach erwähnt gibt es Headerfelder für den Request und Headerfelder für die Antwort. Manche von diesem Feldern sind aber von allgemeiner Gültigkeit, sie dürfen sowohl im Request vorkommen als auch in einer Antwort.

Manchmal ist aber die Bedeutung eine andere...

Generell können Headerfelder, ähnlich wie bei SMTP, gefaltet (folded) werden, d.h. sie können umgebrochen werden. Dazu muss die nächste Zeile durch Leerzeichen oder Tab eingerückt werden um zum gleichen Headerfeld wie die Vorzeile zu gehören.

Connection

Dies ist eines der seltsamsten Headerfelder: Es kann drei unterschiedliche Feldwerte haben. Allgemein regelt es Informationen zu der Verbindung zwischen dem Client und Server, es kann

  1. Liste von HTTP Headerfeldnamen, die nur für diese Verbindung relevant sind.
  2. Beliebige Token-Werte, die nichtstandard-Optionen für diese Verbindung beschreiben.
  3. Der Wert close, damit wird bei persistenten Verbindungen angezeigt, dass die Verbindung nach der Abarbeitung des Requests geschlossen werden soll.

Zum ersten Punkt: Alle Headerfelder die hier aufgeführt werden dürfen nicht weitergeleitet werden. Sie beziehen sich allein auf dies Verbindung. D.h. alle Felder die hier aufgeführt werden, wird der Server erst bearbeiten und dann, vor einer Weiterleitung, entfernen. Das kann z.B. die Verbindungen mit einem Proxy sein. Die Headerfelder werden dann vom Proxy entfernt und der Rest wird zum eigentlichen Server weitergeleitet.

Normalerweise wurde eine Verbindung nach Beantwortung des Requests geschlossen. Um eine persistente Verbindung zu ermöglichen wurde der Wert

  Connection: keep-alive

für HTTP 1.0 eingeführt. In der Version 1.1 gibt es diese Einstellung nicht mehr, hier sind Verbindungen automatisch persistent. Daher gibt es den Wert close um mitzuteilen, dass man keine persistente Verbindung wünscht.

Obwohl 1.1 diese Einstellung nicht mehr auflistet, ist sie jedoch noch in Gebraucht. Wenn dieser Wert keep-alive gesetzt ist, so kann noch ein weiteres, optionales Feld mit gleichem Namen verwendet werden: Keep-Alive.

Hier können beliebige Namen mit Werten übertragen werden, definiert sind aber zwei für Antworten: max, für die maximalen Requests, die der Server bereit ist über diese Verbindung abzuhandeln und timeout. Letztere dient dazu, dem Client einen Hinweis darauf zu geben, wie lange der Server bereit ist, die Verbindung offenzuhalten.

Proxy-Connection

Ein Proxy ist angewiesen, alle Felder die er nicht kennt einfach an den Server weiterzuleiten. Da Connection: keep-alive erst später hinzugefügt wurde, kann es sein, dass der Proxy diesen nicht kennt. Als Folge glauben dann sowohl der Client als auch der Server, dass sie eine persistente Verbindung halten, was aber nicht der Fall sein muss und vermutlich in einem solchen Fall auch nicht sein wird.

Der Trick besteht, bei Verwendung eines Proxys, einfach das Headerfeld umzubenennen. Mit

   Proxy-Connection: keep-alive

weiß ein Proxy nun, dass der Client analog zu Connection: keep-alive eine persistente Verbindung zum Proxy wünscht. Wenn der Proxy dieses Feld aber nicht kennt, so leitet er es an den Server weiter: Dieser wird das Feld generös akzeptieren aber nicht beachten. Ein Webserver kennt dieses Feld nicht.

Diese einfache Lösung hat natürlich auch noch ein Haken: Was ist wenn Proxys in Reihe geschaltet sind und einer das Feld kennt und einer nicht?

Date

Dieses einfache Feld gibt einfach nur die Zeit an, wann der Request bearbeitet wurde oder genauer gesagt, wann die Antwort formuliert wurde. Dies ist ein Pflichtfeld in der Antwort vom Server, hierüber können einige Caches bestimmen wie aktuell eine Antwort ist.

Clients können dieses Feld auch verwenden, in der Regel tun sie es aber nicht.

Das Format ist das gleich wie bei SMTP, es gibt aber einige Abweichungen, da es am Anfang nicht so klar geregelt war. Die Zeit soll GMT, also die gute, alte Greenwich Mean Time sein, z.B.:

   Date: Thu, 10 Jun 2010 12:30:41 GMT

Via

Über dieses Feld kann erkannt werden, über welche Proxys ein Request lief, das ist im Zusammenhang mit der TRACE-Methode von Interesse und kann bei der Fehlersuche durchaus hilfreich sein, z.B.:

   Via: 1.0 gg.genua.de

Der erste Wert gibt die HTTP-Version an, es wurde hier mit Version 1.0 gesprochen.

MIME-Version

Das ist wohl selbsterklärend, damit wird die verwendete Version von MIME angegeben. Da es bislang nur eine gibt, wird der Wert wohl 1.0 sein. Das Feld wird aber gewöhnlich nicht verwendet.

Transfer-Encoding

Neben der Content-Encoding, bei der z.B. Umlaute in ASCII umgewandelt und dergleichen (dazu kommen wir noch später), gibt es auch die Transfer- Encoding. Dieser dient einer Kodierung der Daten für den eigentlichen Transport.

Klassisch gibt es dafür zwei Gründe:

  1. Sicherheit
  2. Unbekannte Größe

Der erste Punkt ist mittlerweile obsolet: Die Verschlüsselung ist nicht wirklich mehr aktuell seit es SSL gibt. Hier wird einfach die gesamte Verbindung, inklusive Headern, verschlüsselt. Da muss man nicht noch extra die Nutzdaten verschlüsseln.

Der zweite Punkt ist schon interessanter: Wenn a priori nicht klar ist, wie lang die Antwort sein wird, man aber dennoch Daten bereits kurz nach der Erstellung der ersten Bytes übertragen möchte, so bietet sich das an.

Derzeit ist nur chunked definiert, dabei werden die Daten in Blöcken definierter Länger übertragen. Zuerst wird eine HEX-Zahl gesendet, gefolgt von CRLF, danach die Anzahl an Bytes die der HEX-Zahl entsprechen, gefolgt von einem CRLF. Die Kombination CRLF ist nicht Bestandteil der HEX- Zahl.

Anschließend kommt wieder die nächste HEX-Zahl mit CRLF, dann die Anzahl an Bytes gefolgt von einem neuen CRLF, usw. Wenn die Übertragung dann abgeschlossen ist, kann noch ein Trailer folgen. Das sind Headerfelder die am Anfang via Trailer-Feld mitgeteilt werden, s.u.

Es gibt noch ein Analogon zu Transfer-Encoding, das eigentlich Accpet- Transfer-Encoding heißen müsste. Hiermit wird dann mitgeteilt, welche Form an Transfer-Encodings akzeptiert werden. Das Feld heißt aber "TE", Z.B.:

   TE: trailers,chunked

Trailers

Am Ende der durch Transfer-Encoding: chunked übertragenen Daten können zum Abschluss noch HTTP-Headerfelder auftauchen. Wenn dem so ist, muss zum einen dieses Feld am Anfang aufgeführt werden und es muss die Felder enthalten, die zum Schluss noch erscheinen werden, z.B.:

   Trailers: Content-MD5

Damit darf man dann am Ende einen Content-MD5-Headereintrag erwarten, der den MD5-Wert der übertragenen Daten beinhaltet.

Upgrade

Hiermit wird die Bitte zu einem Upgrade zu einer anderen Version kund getan. So könnte z.B. ein Client einen HTTP Request mit der Version 1.0 senden und mit dem Eintrag

   Upgrade: HTTP/1.1

testen, ob der Server auch Version 1.1 spricht. In der Regel wird das Feld wohl kaum zum Einsatz kommen: Webserver, die Version 1.1 sprechen haben auch ein HTTP/1.1 in der Antwort und daran kann wohl jeder Client erkennen, dass der Server das Protokoll beherrscht.

Warning

Hiermit können über den Statuscode hinausgehende Informationen zu einem Request übermittelt werden. Die Syntax ist recht einfach:

   Warning: Code Agent Text 

Optional kann noch ein Datum angefügt werden. Im RFC sind auch verschiedene Codes definiert, so sollte ein Proxy hiermit vermerken, falls er z.B. die Transfer-Encoding verändert hat.

Allgemeine Headerfelder für Caches

Wie bereits beim letzten Mal erwähnt gibt es noch zwei weitere allgemeine Headerfelder. Diese dienen zur Kontrolle von Caches, wobei Pragma etwas seltsam ist, es soll auch nicht mehr verwendet werden.

Pragma

Eigentlich ist Pragma nur für Requests und Antworten vorgesehen und kann Direktiven für spezielle Verbindungen beinhalten. So gesehen, können hier beliebig viele Daten in der Form von reinen Token oder mit Gleichheitszeichen erweiterte übertragen werden. Rein praktisch kommt aber gewöhnlich nur die aus dem RFC-vordefinierte Direktive no-cache vor.

Das Pragma: no-cache darf eigentlich nur in Requests vorkommen und muss von allen Zwischenstationen des Requests beachtet und weitergeleitet werden. Es taucht aber in der Praxis auch in Antworten vom Server auf um Caches mitzuteilen, dass der Inhalt nicht gecachet werden darf. Dieses Feld dient aber eigentlich nur für HTTP Version 1.0 und in 1.1 ist es nur noch aus Rückwärtskompatibilitätsgründen enthalten. Für 1.1 gibt es die Cache-Control Felder. Ein Client sollte aber beide Felder verwenden, wenn nicht sicher ist, dass der Server nicht die Version 1.1 unterstützt.

Webcaches sollen Pragma: no-cache wie Cache-Control: no-cache behandeln. Es wird auch keine weiteren Direktiven für Pragma geben.

Cache-Control

Die Direktiven dieses Feldes müssen von allen Webcaches beachtet werden, die bei einem Request involviert sind. Hiermit werden dann in der Regel die Default-Einstellungen der Webcaches überschrieben. Mitunter gibt es Requests, die gar nicht gecachet werden dürfen oder aber Server legt explizit fest, wie lange eine Antwort als gültig angesehen werden kann. Hierdurch kann z.B. ein Server die Last reduzieren.

Die Cache-Direktiven können sowohl im Request als auch in der Antwort vorhanden sein, es kann also sowohl ein Weblcient als auch ein Webserver das Cache-Verhalten beeinflussen. Dieser Headerfelder dürfen auf dem Weg auch nicht entfernt werden, d.h. jeder Proxy muss sie weiterleiten.

Ein interessanter Wert ist z.B.:

   Cache-Control: no-cache

Rein intuitiv sollte man annehmen, dass ein Caching Proxy diese Daten nicht aus dem Cache holen darf. Dem ist aber nicht so: Ein Cache darf den Inhalt aus dem Cache ausliefern. Es gibt aber eine Bedingung, der Cacheinhalt muss revalidiert werden, d.h. der Inhalt muss auf seine Aktualität hin überprüft werden. Der Proxy muss dazu den Server befragen, ob sein Cacheinhalt noch aktuell ist. Wie das geht, kommt noch später.

In einem Request können hinter no-cache noch Feldnamen stehen. Dann darf der Proxy die Antwort cachen und auch verwenden, mit Ausnahme der angegebenen Headerfelder. Diese müssen in der Antwort entfernt werden. Wer sich nun wundert, wozu das gut sein mag, dem kann ich dieses Beispiel nur nahelegen:

   Cache-Control: no-cache=Set-Cookie

Damit darf man also die Antwort speichern, nicht jedoch den Cookie...

Soll erreicht werden, dass der Inhalt niemals im Cache landet, so gibt es eine andere Option, diese darf sowohl im Request als auch in der Antwort stehen:

  Cache-Control: no-store

Kommt diese Zeile im Request vor, so darf kein Cache auch nur einen Teil des Requests oder der Antwort speichern. Bei Antworten, darf kein Teil der Antwort gespeichert werden und zwar sowohl für gemeinsame als auch private Caches (also auch nicht im Browsercache). Allerdings darf die Antwort natürlich im RAM gespeichert werden, nicht jedoch auf der Festplatte.

Der Sinn dieser Direktive sollte wohl klar sein: Private Daten sollten privat bleiben und nicht z.B. durch Cachedateien anderen zur Kenntnis gebracht werden.

In diesem Zusammenhang gibt es noch:

   Cache-Control: private

Diese Antworten dürfen in keinen von mehreren genutzten Cache abgelegt werden, wohl jedoch im lokalen Browsercache, auf den hat man nur selber Zugriff. D.h. man darf die Antwort im Cache ablegen aber nur für sich selber, andere dürfen diese Daten aus keinem Cache beziehen.

Den umgekehrten Fall gibt es auch:

   Cache-Control: public

Damit wird explizit mitgeteilt, dass ein Inhalt im öffentlichen Cache abgelegt werden darf. Sinnvoll ist dieser Eintrag immer dann, wenn der Inhalt normalerweise nicht gecachet werden darf, was wiederum der Fall ist, wenn z.B. ein Authorization-Header vorhanden ist. Diese sind sonst nicht im Cache ablegbar, schließlich würde sonst jemand eine Antwort ohne Authentisierung erhalten können. Diese Direktive darf auch nur in einer Antwort enthalten sein.

Mit max-age kann explizit eine Dauer in Sekunden angegeben werden, wie lange der Inhalt als gültig betrachtet werden kann. Der Eintrag darf daher nur in der Antwort vorkommen und impliziert ein public. Für gemeinsame "shared" Caches gibt es noch die Variante s-maxage. Damit dürfen aber auch explizit Daten gecachet werden, auch wenn es sonst nicht erlaubt wäre. Mit einer Angabe von 0 kann explizit vermieden werden, dass der Inhalt ohne Revalidierung aus dem Cache ausgeliefert werden darf, z.B.:

   Cache-Control: max-age=0  

Es gibt aber noch eine weitere Methode, mit der ein Cache dazu gebracht werden kann, den Inhalt auf Aktualität zu prüfen:

   Cache-Control: must-revalidate

Das ist sogar noch strikter als no-cache, es muss immer revalidiert werden. Bei no-cache kann der Inhalt auch dann ausgeliefert werden, wenn der Server nicht erreichbar ist. Das ist in diesem Fall auch nicht erlaubt. Es gibt auch noch eine analoge Direktive die nur für gemeinsam genutzte Caches gilt:

   Cache-Control: proxy-revalidate

Für Requests gibt es noch weitere Werte, wie z.B.:

   Cache-Control: only-if-cached

Der Client will dann nur eine Antwort, wenn sie bereits im Cache vorliegt. Ähnliches ist mit max-stale möglich: Der Cache darf auch eigentlich schon veraltete Einträge ausliefern. Hier kann auch optional noch eine Zeit in Sekunden angegeben werden. Der Cache darf dann keine abgelaufenen Daten ausliefern, wenn diese seit mehr als diesen Sekunden veraltet sind, z.B.:

   Cache-Control: max-stale=3600

Mit "min-fresh" kann ein Client angeben, wie alt die Antwort sein darf, die aus dem Cache ausgeliefert wird. Mit

   Cache-Control: max-fresh=10

Dürfen keine Antworten aus dem Cache erfolgen, wenn der Inhalt schon seit mehr als 10 Sekunden dort vorliegt.

Caches können auch Daten umwandeln um sie besser speichern zu können, z.B. um Platz zu sparen. So könnte ein Base64-kodiertes Bild wieder als jpg im Binärformat abgelegt werden. Mit

   Cache-Control: no-transform

kann dies explizit untersagt werden. Aber das dürfte wohl eigentlich so gut wie nie vorkommen.

Erweiterungen sind hier auch möglich und erlaubt. RFC-5861 definiert auf diese Weise zwei Erweiterungen:

   RFC-5861 HTTP Cache-Control Extensions for Stale Content

Der erste Wert ist stale-while-revalidate. Am einfachsten kann das an diesem Beispiel verdeutlicht werden:

   Cache-Control: max-age=600, stale-while-revalidate=30

Der Inhalt ist 600 Sekunden lang gültig, darf aber für weitere 30 Sekunden als gültig angesehen werden, wenn während dieser Zeit anderweitig eine Revalidierung stattfindet.

Die zweite Erweiterung zählt darauf ab die Gültigkeit zu verlängern, wenn der Server nicht mehr erreichbar ist. Wenn z.B. in einer Antwort folgendes steht

   Cache-Control: max-age=600, stale-if-error=1200

so ist der Inhalt normalerweise nur 600 Sekunden aus dem Cache ausliefbar. Wenn aber z.B. nach 900 Sekunden der Server nicht mehr antwortet oder mit einem 5er Code reagiert, so darf der Cache dennoch die gespeicherten Daten ausliefern und zwar in diesem Fall für weitere 1200 Sekunden.

Weitere wichtige Felder sind:

Expires

Mit diesem Feld kann direkt angegeben werden, wie lange eine Antwort als gültig angesehen werden kann. Für Caching Webproxies ist dieses Feld ideal, man weiß sofort, wie lange man aus dem Cache die Antworten liefern darf, z.B.:

   Expires: Sat, 19 Jun 2010 11:44:04 GMT

Wird hier die aktuelle Zeit angegeben, so ist der Inhalt nicht im Cache ablegbar, im anderen Fall bis zur angegebenen Zeit. Damit werden seiten aber auch direkt als cachebar definiert.

Wenn in der Antwort neben dem Expires-Feld auch ein Cache-Control mit max-age vorhanden ist, so zählt die max-age Zeit.

Manche setzten die Zeit explizit auf Null um deutlich zu machen, dass der Inhalt nicht für einen Cache als abgelaufen anzusehen hat.

Last-Modified

Mit diesem Feld kann ein Server angeben, wann der Inhalt zum letzten Mal verändert worden ist. Wie üblich ist das Datum dabei in GMT, wie z.B.:

   Last-Modified: Fri, 18 Jun 2010 09:51:44 GMT
>

Proxys verwenden dies, sofern sie keine anderen Anhaltspunkte haben, um zu berechnen, wie lange eine Seite als aktuell anzusehen ist. Je älter die Daten sind, desto größer ist die Wahrscheinlichkeit, dass sie sich in Kürze auch nicht ändern werden. Mittlerweile gibt es bessere Verfahren um die Gültigkeitsdauer zu ermitteln.

Leider wollen manche nicht, dass ihre Daten zu lange in Caches liegen bleiben und setzen hier das Datum auf das aktuelle. Damit sieht es aber so aus, als ob selbst uralte Informationen akutell sind. Manchmal fehlt explizit ein Datum auf der Webseite. Will man dann wissen ob es akutell oder uralt ist, so könnte man im Browser unter Page Info nachsehen, wie alt die Daten sind. Das funktioniert dann leider so nicht, wie man es gerne hätte. Da steht dann immer das aktuelle Datum...

ETag

Das Last-Modified-Feld hat noch eine Nebenwirkung. Mittels z.B. des Header- feldes If-Modiefied-Since kann ein Proxy nur neuere Antworten anfordern. Es kann aber sein, dass die Daten sich dennoch geändert haben aber älter sind, z.B. vom Backup, das Datum war falsch als die Seite erstellt wurde, etc.

Der Ausweg besteht im Entity Tag ETag. Dieser soll einen eindeutigen Wert bzgl. der aktuellen Daten haben, ähnlich z.B. zu einer MD5-Summe, z.B.:

    Etag: "7f7729-604-48960cc485200"

Das Format ist nicht fest vorgegeben, dass darf jeder Hersteller von Webservern selber machen: Hauptsache die Eindeutigkeit ist halbwegs gegeben.

Request-Headerfelder

Selbstverständlich gibt es viele Headerfelder, die nur für Clients, also in Requests vorkommen und auch dort nur Sinn ergeben. Das wichtigste Feld ist wohl

Host

Dieses Headerfeld ist mit Version 1.1 Pflicht und gibt den Hostnamen vom Server wieder, mit dem man sprechen will. Im "normalen" Request ist der Hostname schließlich meistens nicht mehr vorhanden, man sieht nur den absoluten Pfad.

Da die IPv4-Adressen aber recht knapp geworden sind, kam man schon früh auf die Idee, dass unter einer Adresse mehrere Webauftritte mit unterschiedlichen Domainnamen laufen zu lassen. Das könnte man über unterschiedliche Ports realisieren, der Server weiß auf welchem Port er angesprochen wird und weiß daher den gewünschten Domainnamen. Das ist allerdings unpraktisch, man muss in der URL den Port mit angeben und viele Firewalls dürften Zugriffe neben Port 80 häufig blocken.

Die bessere Lösung besteht in der Verwendung des Host-Headers. Hier erfährt der Webserver automatisch, welche Domain gewünscht ist. Bei HTTP-Version 1.0 ist das noch optional, bei 1.1 jedoch Pflicht. Fehlt der Eintrag im Header, so sollte der Server einen Fehler melden, z.B.:

   HEAD / HTTP/1.1
   
   HTTP/1.1 400 Bad Request
   Date: Sat, 19 Jun 2010 14:43:34 GMT
   Server: Apache
   Connection: close
   Content-Type: text/html; charset=iso-8859-1

Man sieht auch: Obwohl es eine persistente Verbindung ist, schließt der Server diese via Connection: close.

Manches mal bekommt man auch eine Fehlermeldung wenn man mit telnet und HTTP/1.0 arbeitet, wenn der Host-Eintrag fehlt

From

Hiermit kann die E-Mailadresse des Anwenders übertragen werden. Früher war das eigentlich beim Netscape der Normalfall. Das war aber zu einer Zeit, als Spam noch keine wesentliche Rolle spielte. Mittlerweile überträgt wohl kein Browser mehr die E-Mailadresse ungefragt. In RFC-2616 wird auch explizit darauf verwiesen, dass ein Mailclient die Adresse nicht ungefragt übermitteln soll.

Bei den meisten Browsern dürfte auch keine E-Mailadresse hinterlegt sein, etwas anderes war es früher beim Netscape Communicator und auch Mozilla. Hier waren sowohl ein Mailclient als auch ein Newsreader integriert. Das hat dann dazu geführt, dass in der Regel die eigene E-Mailadresse im System hinterlegt war.

User-Agent

Hiermit wird angegeben, mit welchem Browser man zugreift. In den Frühzeiten des Internets konnte hierüber ein Server z.B. erkennen, dass ein Netscape angefragt hat. Dieser beherrschte mehr als nur den Standard und entsprechend konnte der Server darauf reagieren. Der Standard wurde meistens später an das angepasst, was Netscape selber hinzugefügt hat. Es führte aber auch dazu, dass der IE sich standardmäßig als Mozilla meldet, z.B.:

   Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)

Im Kommentar wird es erst ersichtlich, dass dies ein IE unter Windows war. Microsoft hat im Browserkrieg ebenfalls Erweiterungen zum Standard hinzugefügt. Daher ist der IE6, obwohl hoffnungslos veraltet, oft noch im Gebrauch. An Hand obiger Einträge erkannten dann Server, dass ein IE zugegriffen hat und oft wurden andere Browser deswegen abgelehnt. Das hat zum Glück deutlich nachgelassen, aber damals hatte der IE ein Quasi-Monopol. Aus diesem Grund sehen manche Opera-Einträge aber wohl so aus:

   Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; en) Opera 8.50

Mittlerweile gibt es zum Glück bessere Methoden um herauszufinden, was ein Client kann und was nicht.

Diverse Auswertungen dürften aber auf just diesen String zurückgreifen um, wie z.B. bei NetCraft, die Browser-Marktanteile zu ermitteln. Wie man aber leicht sehen kann, ist es nicht unwahrscheinlich, dass hier ein paar Fehler unterlaufen und Systeme falsch klassifiziert werden.

Referer

Hiermit kann ein Browser übermitteln, von welcher Adresse aus er zu dieser gelangen ist. Das werten manche aus, um herauszufinden wo sie überall verlinkt sind. Hier erfolgte eine Verlinkung direkt von unserer Homepage:

   Referer: http://www.lug-erding.de/

Manche Server werten hier auch aus, ob der Zugriff über Suchmaschinen wie Google erfolgte. Dann sieht ein Referer-Eintrag z.B. so aus (ich habe den Eintrag etwas gestutzt):

   Rerferer: http://www.google.de/search?q=purpose+multipart+mail

Nun reagieren manche Server auf den Suchstring und färben passenderweise auf der Seite die Begriffe purpose, multipart und mail unterschiedlich bunt ein, nur für den Fall, dass sich schon einmal jemand darüber gewundert hat...

Es gibt noch ein paar Headerfelder mehr, die nicht in RFC-2616 aufgeführt sind aber von einigen Clients verwendet werden, darunter fallen auch einige UA-Header (UA steht für User Agent):

UA-CPU

Dies gibt den Typ und/oder Hersteller der CPU des Clients an

UA-Color

Hiermit können die Farbfähigkeiten des Clients übermittelt werden. Das dürfte heutzutage belanglos sein, früher waren die Zahl der Farben aber nicht so üppig.

UA-Disp

Fähigkeiten des Displays können hier stehen.

UA-OS

Das Betriebssystem des Clients kann heute gewöhnlich aus dem User-Agent-Feld ermittelt werden. Es könnte aber auch hier Erwähnung finden.

UA-Pixels

Hier werden die Pixelfähigkeiten des Client-Bildschirms angegeben.

Client-IP

Die IP-Adresse des Systems auf dem der Browser läuft, kann hier angegeben werden. Dies kann natürlich, bei Einsatz eines Proxys, eine andere sein, als der Server sieht.

Ratsam sind diese Headerfelder aber nicht, sie verraten doch mitunter einiges über den Anwender.

Als nächstes kommen dann die Accept-Request-Felder mit denen ein Client mitteilen kann, welche Formate er akzeptiert oder auch nur bevorzugt.

Accept-Headerfelder

Mit diesen Headerfeldern kann ein Client mitteilen, was für Daten er akzeptiert. Dies alleine ist aber eigentlich wenig hilfreich, ein guter Browser sollte schließlich alles akzeptieren. Aber es gibt noch eine viel bessere Eigenschaft: Man kann Typen priorisieren, also man kann z.B. explizit sagen, das man lieber deutsche Texte haben will und wenn das nicht geht, englische. Dieser Qualitätsfaktor wird mit dem q-Parameter angegeben, der zwischen 0 und 1 liegen kann. Dieser wird durch ein Semikolon getrennt an den Wert angehängt. Auch ein Asterisk als Wildcard für Medien-Typen und Subtypen ist erlaubt.

Accept

Hiermit können die gewünschten, d.h. akzeptierten Media-Types angegeben werden. Ein Beispiel dürfte es verdeutlichen:

   Accept: audio/*; q=0.2, audio/basic

Hiermit wird audio/basic als bevorzugt. Ansonsten wird alles andere an Audio-Daten akzeptiert.

Mein Firefox sendet z.B. bei diesem Link

   http://www.lug-erding.de/bilder/zocken7/thumbs/th_zocken035.jpg

ein

   Accept: image/png,image/*;q=0.8,*/*;q=0.5

Es wird also png gewünscht, liegt das nicht vor, wird irgendein image-Type vor beliebigen Daten bevorzugt.

Wie es der Link aber vermuten lässt, liefert der Server eine JPEG-Datei:

   Content-Type: image/jpeg

Rein intuitiv dürfte es klar sein: Je spezifischer die Angabe, desto mehr ist der Eintrag bevorzugt. Bei gleichem q-Wert ist also text/html mit mehr Gewicht zu betrachten als text/*.

Accept-Charset

Hier kann ein Client den bevorzugten Zeichensatz angeben. Bei meinem Feuerfuchs sieht das im Moment dann so aus:

   Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7

Ich bevorzuge also ISO-8859-1, wenn das nicht möglich ist, wäre UTF-8 die nächstbeste Option. Wenn auch das nicht lieferbar ist, nehme ich alles was kommt. Damit kann man dann auch z.B. chinesische Seiten bekommen.

Accept-Encoding

Über dieses Feld kann geregelt werden, in welcher Weise die Daten beim Übertragen gepackt werden dürfen. Es gibt derzeit vier mögliche Werte: gzip, deflate, compress, identity. Der letzte Wert bedeutet, dass die Daten bei der Übertragung nicht komprimiert werden dürfen.

deflate beschreibt dabei den zlib-Algorithmus.

Bei mir sieht dass wieder so aus:

   Accept-Encoding: gzip,deflate

Accept-Language

Auch die bevorzugte Sprache kann mit übermittelt werden. So liefert z.B. der Apache bei der Standardinstallation die Startseite immer in der vom Browser gewünschten Sprache aus. In meinem Fall sieht das im Moment so aus:

   Accept-Language: de-de,en-us;q=0.7,en;q=0.3

Ich bevorzuge hier also deutsche Seiten, vor amerikanischem Englisch und wenn auch das nicht lieferbar ist, dann Englisch allgemein.

TE

Dieses Feld hatte ich schon erwähnt, es müsste eigentlich den Namen Accept-Transfer-Encoding haben, denn genau das ist die Funktion. Wird hier z.B. der Wert trailers angegeben, so akzeptiert der Client bei einem chunked-Transfer Trailerfelder, also Headerfelder am Ende der Übertragung. Fehlt dieses Feld, so ist nur chunked als Transfer-Encoding erlaubt. Ein Beispiel könnte so aussehen:

   TE: trailers,chunked

Beim nächsten Mal kommen dann die Conditional-Felder, also die für das Caching relevanten Felder mit denen einem Webserver mitgeteilt wird, unter welchen Bedingungen man den Inhalt haben möchte.

Conditional-Headerfelder

Es besteht auch die Möglichkeit, bedingte Anfragen an den Server zu senden. Dies ist gerade beim Einsatz von Caches sehr hilfsam, es ist damit leicht möglich Daten vom Server zu validieren oder die Daten neu zu holen, wenn die im Cache veraltet sind.

Expect

Mit diesem Feld kann die Erwartung eines Statuscodes an den Server gesendet werden. Eigentlich ist hier nur ein Wert definiert:

   Expect: 100-continue

Wenn ein Server dies nicht versteht oder ein Proxy bekommt einen anderen Status vom Server zurück, so muss ein 417 Expectation Failed gesendet werden. Interessanterweise macht dies der Squid erst ab Version 2.7 und prompt gab es zahlreiche Fehler weswegen es die Squid-Direktive ignore_expect_100 gibt. In einem konkreten Fall hatte der Server nicht mit dem Zwischenstatus 100 geantwortet, sondern mit 200 OK und hat die Daten ausgeliefert. Das ist ein Verstoß gegen RFC-2617 und prompt hat der Squid daraufhin ein 417 an den Client gesendet.

Dem Client war aber offensichtlich der Return-Code egal, er hat auch das 200 akzeptiert. Was einem dann prompt zu der Frage anregt: Warum sendet er dann überhaupt ein Expect-Headerfeld?

If-Match

Mit diesem Headerfeld kann explizit ein ETag angegeben werden, welches man vom Server beziehen möchte. Wenn einem ein spezielles egal ist, so kann ein Asterisk angegeben werden. Das dürfte aber recht sinnbefreit sein, da kann dann das Feld gleich weggelassen werden.

Existiert kein passender Eintrag, so muss der Server mit 412 Precondition Failed antworten.

If-Modified-Since

Das ist der Klassiker unter den Validierungsmethoden für Caches: Man fragt nach Daten die neuer sind als die, die im Cache vorliegen. Hat der Server damals ein Last-Modified mitgesendet, so wird dessen Datum hier eingesetzt. Der Grund liegt darin, dass manche Server das Datum, also die Zeichenkette, direkt vergleichen und nicht ob der Inhalt neuer ist.

Wenn es zu der angegebenen URL noch die gleiche Datei gibt, so wird nur der Header ohne Body mit einem 304 Not Modified zurückgesendet. Dadurch weiß dann der Cache, das seine lokalen Daten aktuell sind.

Diese Methode kann aber dann fehlschlagen, wenn die Daten z.B. durch ältere ersetzt werden oder das Datum zwischenzeitlich einmal falsch war. Es kann auch sein, dass eine Datei korrumpiert war und über ein Backup durch eine ältere Version ersetzt wurde. Auch dann ist das Datum älter aber die Daten im Cache sind dennoch falsch.

Eine bessere Lösung besteht in der Verwendung von ETag um zu validieren, ob sich die Daten geändert haben.

If-None-Match

Das klingt auf den ersten Blick seltsam aber es ist die wichtigste Methode zur Validierung, wenn ein ETag vorhanden ist. Mit diesem Feld wird das alte ETag gesendet und der Server wird angewiesen, den Inhalt nur dann zu senden, wenn sich das ETag geändert hat. Das ist dann auch gänzlich unabhängig vom Datum und macht deutlich, wie wichtig es ist, einen guten ETag zu haben. Wenn sich die Daten ändern aber das ETag nicht, so sind die alten Daten dennoch weiterhin als gültig anzusehen. Das Feld kann z.B. so aussehen:

   If-None-Match: "36-9c0-40fd6f87"

Wenn das nicht erfüllt ist, also der ETag noch immer der gleich ist, so sendet der Server nur ein 304 Not Modified im Header und keinen Body. Im anderen Fall wird er den neuen Inhalt mit einem neuen, anderen ETag senden.

Wenn in einem Request sowohl If-Modified-Since und If-None-Match auftauchen, so darf der Server nur dann einen 304 senden, wenn beide Bedingungen zu dem gleichen Ergebnis führen.

If-Range

Dieses Feld dient zur Validierung von lediglich Bereichen aus einem Cache. Wenn nur ein bestimmter Bereich vorliegt, so kann auch dieser mitunter aus dem Cache bedient werden. Hier kann entweder ein ETag oder ein HTTP-Datum (Last-Modified) angegeben werden. Dieses Feld ergibt aber nur einen Sinn in Verbindung mit dem Range-Headerfeld, es muss schließlich klar sein, um welchen Bereich es sich handelt und für welchen z.B. das ETag bestimmt wurde.

If-Unmodified-Since

Das ist das Gegenteil von If-Modified-Since und dient dazu einen alten Inhalt erneut zu beziehen. Wenn sich der Inhalt geändert hat, so muss der Server mit einem 412 Precondition Failed antworten. Es hat hier wohl wenig Sinn, die Daten aus einem Cache zu holen. Es wird schließlich explizit eine alte Version vom Server angefordert. Sollte die Antwort ohne dieses Feld in etwas anderem als einem 2er Code oder 412 lauten, so soll dieses Feld vom Server ignoriert werden.

Range

Mit diesem Headerfeld kann explizit nur ein Bereich in Bytes anstelle des gesamten Inhalts. Hier kann ein kompletter Bereich angegeben werden oder auch nur ab welche Byte die Antwort erwartet wird, z.B.:

   Ranges: bytes=5000-

Theoretisch könnte auch etwas anderes als bytes dort stehen, im RFC-2616 ist aber nur dieser Fall definiert.

Cookies

Diese kleinen Kekse werden geliebt und gehasst zugleich. Sie dienen dazu, dass der Server sie beim Client ablegen und von dort wieder abrufen kann. In gewisser Weise dienen sie somit zur Wiedererkennung von Clients. Es gibt zwei Arten von Cookies: Die einen dienen nur während der Verbindung, die anderen sind persistent, ersterer wird am Ende der Verbindung gelöscht, die anderen Cookies bleiben dagegen auch nach Neustart des Browsers erhalten. Natürlich nur in sofern, dass der Browser sie nicht explizit, durch den Anwender definiert, beim Starten löscht.

Interessant ist auch, dass im RFC-2616 Cookies überhaupt nicht vorkommen. Sie waren ursprünglich eine Erfindung von Netscape, diese Variante wird als Version 0 bezeichnet. Die neue Version 1 ist in RFC-2965 definiert, der Titel zeigt auch schon, was der eigentliche Einsatzzweck von Cookies ist:

      HTTP State Management Mechanism

Um zu verstehen, warum Cookies eingesetzt werden, die oft Dinge wie Web- Einstellungen oder auch einfach IDs, die dem Anwender zugeteilt wurden, enthalten, sollte man sich klar machen, dass HTTP-Verbindungen oft nicht persistent sind. Ursprünglich wurde jede Verbindung mit der Beantwortung des Requests beendet, ein erneuter Zugriff auf Links oder andere Ressourcen erfolgt über eine neue Verbindung. Damit ist aber das Wissen der bereits getätigten Aktionen verloren. Als Bindeglied, das über mehrere Verbindungen funktioniert, können die Cookies angesehen werden. Aber selbst persistente Verbindungen werden gelegentlich beendet und neu aufgebaut. Auch wenn ein Zugriff zu einem späteren Zeitpunkt erfolgt, kann über Cookies noch die Beziehung zur alten Verbindung hergestellt werden.

Set-Cookie

Mit diesem Feld kann ein Server ein Cookie im Netscape-Format, auch als Version 0 bezeichnet, setzen. In der Regel wird hier ein name=value Paar angegeben mit ein paar zusätzlichen optionalen Zusätzen, die durch ein Semikolon getrennt werden. Gewöhnlich wird hierüber eine Session-ID zugewiesen, der Name ist frei wählbar, wie der Wert auch.

Die Attribute können sein:

Expires
Hiermit kann die Lebensdauer eines Cookies angegeben werden. Wenn das hier angegebene Datum überschritten wird, so wird der Cookie nicht mehr gespeichert und auch nicht mehr gesendet. Als Zeitzone kommt wie üblich nur GMT in Frage, z.B.:
       expires=Mon, 25-Jun-2012 09:21:01 GMT
Domain
Hierüber kann geregelt werden, an welche Domains der Cookie gesendet werden darf. Nur Domains und Subdomains dürfen den Cookie erhalten. Es dürfen auch nur Server aus der Domain derartige Cookies setzen, um TLDs zu vermeiden müssen mindestens zwei Punkte enthalten sein. Ist keine Domain angegeben, so wird der Servername verwendet, von dem der Cookie stammt, z.B.:
       domain=.google.com
Path
Während mit Domain der oder die Server festgelegt werden kann, an die der Cookie gesendet werden soll, so kann über den Path das noch etwas granularer geregelt werden. Nur wenn der Path in der URL auftaucht, also auch alles unterhalb des Pfades, darf der Cookie gesendet werden. Ein einfacher Slash "/" passt auf alles, z.B.:
       path=/ 
Secure
Wenn dieses Attribut im Cookie enthalten ist, so darf der Keks nur über eine SSL-verschlüsselte Verbindung gesendet werden.

Alles in allem, kann dann ein Set-Cookie-Headerfeld so aussehen:

   Set-Cookie: PREF=ID=664e1d089291a944:TM=1277544061:LM=1277544061; 
               expires=Mon, 25-Jun-2012 09:21:01 GMT; path=/;
               domain=.google.com

Cookie

Nachdem mit Set-Cookie ein oder mehrere Cookies gesetzt wurden, können sie mit dem Cookie-Headerfeld an den Server zurückgesendet werden. Hier steht dann nur noch die name=value-Paare, getrennt durch Semikolon. Also etwas wie:

   Cookie: Name1=Wert1; Name2=Wert2; ...

Dies enthält also wenig magisches: Die vom Server gesetzten Werte werden in der Regel einfach an ihn zurückgesendet werden. Der Hauptgrund ist, wie eingangs erwähnt, dass die Verbindungen selten persistent sind. Daher kann ein Server nicht wissen, welche Requests zusammengehören. Über Cookies kann dieser Zusammenhang hergestellt werden.

Set-Cookie2

Diese neue, offizielle RFC-Cookie-Version ist nur selten in Gebrauch. Hier wurde einiges verbessert und man wollte wohl weg von den durch Netscape definierten Cookies und lieber durch RFCs standardisierte Cookies ersetzen. Die Version 1 Cookie-Header werden über Set-Cookie2 gesetzt. Das Verfahren verwendet einen Versionsstring, der Kompatibilität zu den alten Cookies gewährleisten soll, so dass Clients weiterhin das Cookie-Headerfeld einsetzen können.

Das Format ist wieder ähnlich, es werden name=value-Werte gesetzt, es gibt ebenfalls Attribute, eines davon, die Version, ist Pflicht.

Version
Hiermit kann die Version angegeben werden, derzeit gibt es aber nur die Version=1
Comment
Auch optionale Kommentare zu Cookies sind nun möglich, hiermit kann ein Anwender leichter erkennen, ob er einen Cookie annehmen mag.
CommentURL
Anstelle des direkten Kommentars kann auch ein Link zu ausführlicher Information zum Cookie angegeben werden.
Discard
Damit kann einem Client mitgeteilt werden, dass der Cookie mit Beendung des Browsers gelöscht werden soll.
Domain
Zu welchen Domains soll der Cookie gesendet werden. Startet die Domain nicht mit einem Punkt, so soll der Browser hier einen annehmen.
Path
Dies Attribut ist gleich zu dem alten, von Netscape definierten.
Port
Zusätzlich zur Domain kann auch noch explizit ein Port oder auch mehrere Ports angegeben werden, zu denen der Cookie gesendet werden soll. Wird das Feld ohne Wert verwendet, so darf eine Verbindung zu dem aktuellen Port den Cookie verwenden, im anderen Fall nur zu den angegebenen Ports, z.B.:
        Port="80,8080"
oder nur
        Port
Secure
Der Cookie darf nur über eine SSL-verschlüsselte Verbindung gesendet werden.

Die Cookies werden, wie mit Version 0, mit dem Cookie-Feld übermittelt. Der Unterschied besteht aber darin, dass die Version angegeben werden muss und es werden auch die Attribute aus dem Set-Cookie2 mitgesendet. Die Namen starten dann mit einem Dollar-Zeichen, z.B. kann ein Server diese beiden Cookies setzen

   Set-Cookie2: Part_Number="Rocket_Launcher_0001"; Version="1"; 
          Path="/acme"
   Set-Cookie2: Part_Number="Riding_Rocket_0023"; Version="1";
           Path="/acme/ammo"

Dann würde ein Client an alles was in der URL /acme/ammo/ enthält, folgenden Cookie senden:

   Cookie: $Version="1";
           Part_Number="Riding_Rocket_0023"; $Path="/acme/ammo";
           Part_Number="Rocket_Launcher_0001"; $Path="/acme"

Cookie2

Ein Server weiß normalerweise nicht, welche Version ein Client unterstützt. Wenn der Server nun eine Version verwendet, die der Client nicht versteht, so kann der Client über dieses Feld mitteilen, was die höchste Version ist, die er noch unterstützt. Das ist derzeit aber nur Version 1, eine andere gibt es bislang noch nicht. Die Version 1 scheint aber nicht wirklich weit verbreitet zu sein, bislang ist mir noch kein solcher Keks begegnet.

Sicherheit von Cookies

Hier gibt es die wildesten Gerüchte, wie schlimm Cookies sein können oder das es gar Exploits via Cookies geben soll. Wie man nun leicht sehen kann, ist das wohl schwerlich möglich: Es wird eine Zeichenkette zwischen Server und Client ausgetauscht. Das gilt als Bindeglied für eine Verbindung.

Natürlich kann der Betreiber eines Servers darüber Unfug treiben: Wenn die Cookies, z.B. explizit Zahlungsanweisungen enthalten und vom Server auch so ausgewertet werden. Das sollte aber kein Server machen, das wäre auch ein Missbrauch der Funktion. Sehr wohl kann aber ein Webserver einen Warenkorb mit einem Cookie assoziieren. Die Bezahlung erfolgt dann aber hoffentlich auf einem anderen Weg: Dafür gibt es SSL.

Es gibt aber einen Punkt, der die Privatsphäre betrifft und man durchaus bedenken sollte: Auf vielen Webseiten wird auch Werbung von einer Werbefirma platziert, z.B. via DoubleClick. Hier wird ein Link zu DoubleClick gesetzt, diese setzen in der Regel auch Cookies. Besucht man nun eine andere URL, bei der auch DoubleClick involviert ist, so wird der gesetzte Cookie wieder an DoubleClick gesendet. Die wissen nun, wenn auch nur über die Cookie-ID, dass der Benutzer schon einmal auf einer anderen Seite gesehen wurde.

Nun wird man sagen: Was soll es, nun weiß DoubleClick, dass ich mehrere Seiten besucht habe, wo sie Werbung platziert haben. Da taucht aber das Problem auf: Da die URL in der Webseite verlinkt ist, die man sich ansieht, so wird das Referer-Headerfeld gesetzt und DoubleClick weiß darüber, von welcher Webseite aus zugegriffen wurde.

Auf diese Weise kann man das Surfverhalten des Anwenders gut analysieren. Auch wenn man den Namen des Anwenders nicht direkt kennt, so können aber Profile erstellt werden. Wenn man dann irgendwann einmal bei einem Online- Gewinnspiel von DoubleClick mitmacht, so haben sie dann auch schon schnell einmal eine Adresse zu dem Profil...

Content-Felder

Einfach gesagt, tun diese Felder genau das: Über diese Felder werden weitere Informationen über den Inhalt bereitgestellt. Darüber kann dann ein Browser entscheiden, wie der die Daten darstellt. Ob also ein Plugin oder eine externe Anwendung gestartet wird, kann darüber genauso angestoßen werden, wie ein simples Abspeichern der Daten. Manche Browser versuchen dies auch über die Endung der Datei zu bestimmen aber der richtige Weg ist über die Content-Headerfelder. Wenn diese allerdings nicht stimmen, gibt es oft seltsame Phänomene: Der Browser könnte das falsche Plugin starten oder er bietet die Daten zum Download an anstatt sie z.B. im Browser anzuzeigen.

Content-Base

RFC-2616 kennt dieses Feld nicht mehr, es ist aber noch Bestandteil des Vorgängers: RFC-2068. Die Idee bestand darin, über dieses Feld die URL anzugeben, zu der alle weiteren Pfade relativ sein sollen. Fehlt dieses Feld, so wird entweder die URL aus der ursprünglichen Request verwendet oder die unter Content-Location angegebene. Das Feld dürfte aber auch ohne fehlenden Eintrag in RFC-2616 keine Rolle spielen: In der Regel möchte man relative Pfade zur Basis-URL haben. Wenn man also z.B. bei http://www.lug-erding.de/ auf Vorträge klickt, so lautet der Link hier /vortrag/index.html. Dies führt dann zur absoluten URL, in diesem Fall, von http://www.lug-erding.de/vortrag/index.html. Das hat den Vorteil, dass alle Links noch stimmen, wenn die Domain geändert wird. Für Tests könnte man z.B. einen Webserver auf dem lokalen System starten, dann würde hier der Link auf http://localhost/vortrag/index.html zeigen, das wäre hier auch erwünscht.

Content-Encoding

Hiermit kann angegeben werden, ob und wie der Inhalt vor der Übertragung kodiert wurde. Gewöhnlich steht hier dann gzip oder compress: Die Daten wurden komprimiert und der Browser soll sie vor der Darstellung wieder entpacken. Im Gegensatz zu Transfer-Encoding darf ein Proxy solche Felder nicht modifizieren.

Hier gelten auch die gleichen Encodings wie beim Transfer-Encoding: gzip, compress, deflate und identity. Der letzte Wert, identity, besagt, dass kein Encoding stattgefunden hat, in diesem Fall kann das Feld auch gleich entfallen.

Content-Language

Dieses Feld ist etwas seltsam, es teilt dem Client mit, welche Sprache man verstehen sollte um den Inhalt zu verstehen. Damit wird also nicht angegeben, in welcher Sprache der Inhalt verfasst ist. Daher können hier auch mehrere Sprachen stehen, wie z.B.:

   Content-Language: en,fr

Das kann z.B. für ein englisches Buch sein, mit dem man die französische Sprache erlernen kann.

Content-Length

Hiermit wird die Größe es Bodys in Bytes angegeben. Im Fall eines HEAD- Requests wird hier die Größe angegeben, die bei einem GET gesendet werden würde. Anwendungen sind gehalten, sich an diese Angabe zu halten. Eine Empfehlung des RFCs ist, dass dieses Feld immer verwendet werden soll, wenn es vorher möglich ist die übertragene Größe zu bestimmen.

Content-Location

Über dieses Feld kann angegeben werden, wo die Daten anderweitig bezogen werden können. Das Feld taucht auch bei Redirects auf, wenn die alte URL nicht mehr gültig ist und die neue URL bekannt ist. Hierüber wird auch die Basis-URL definiert, alle relativen Links sind zu dieser URL zu sehen.

Content-MD5

Auch dieses Feld ist uns schon begegnet: Hier kann die MD5 des Bodys stehen. Darüber kann ein Client erkennen, ob der Inhalt bei der Übertragung verändert wurde, dies könnte z.B. durch Proxys erfolgt sein. Es darf aber nicht als ein Sicherheitsmerkmal interpretiert werden. Wer in der Lage ist, den Inhalt während der Übertragung verändern kann, kann dies vermutlich auch mit diesem Headerfeld: Es ist kein Hilfsmittel gegen Missbrauch.

Content-Range

Da auch Byteranges, also nur Teile einer Datei, angefordert werden können, so kann über dieses Feld der Bereich mitgeteilt werden, der tatsächlich übertragen wird. Zusätzlich wird durch einen Slash abgetrennt die Länge des ganzen Objektes mitgeteilt. Auch ein Asterisk "*" ist erlaubt, wenn die Länge zum Zeitpunkt des Requests nicht bekannt ist. Wichtig: Das erste Byte trägt die Position 0, z.B. besagt

   Content-Range: bytes 0-499/1234

das die ersten 500 von 1234 Bytes in der Antwort enthalten sind. Folglich könnte das letzte Segement so aussehen:

   Content-Range: bytes 500-1233/1234

Content-Type

Das dürfte das interessanteste Feld sein: Hiermit wird dem Browser mitgeteilt, um welche Art von Daten es sich handelt. Der Aufbau ist der alte, bereits durch SMTP bekannte. Es wird der Typ und Subtyp, durch einen Slash getrennt, angegeben. Der Subtyp verfeinert den Typ, z.B. text ist ein Typ und html ein Subtyp. Anschließend können, durch Semikolon getrennt, weitere Parameter angegeben werden, wie z.B.:

   Content-Type: text/html; charset=ISO-8859-15

Das besagt, dass es eine Textdatei im HTML-Format ist, welche im Latin1- Format mit Euro-Zeichen kodiert ist. Wann z.B. welches Plugin beim Firefox verwendet wird, kann man leicht durch Eingabe der URL about:plugins herausfinden.

Wenn der Typ nicht bekannt ist, es aber eine Dateiendung gibt, so kann man diese z.B. über /etc/mime.types zuordnen. Manche Applikationen, wie z.B. lynx, greifen auf /etc/mailcap zurück, um das Programm zu ermitteln, die bestimmte Typen verarbeiten. Diese werden dann, nachdem die Daten geladen worden sind und der Browser nicht selber darstellen kann, mit just diesen Daten gestartet.

So wird darüber z.B. gesteuert, dass bei PDF-Dateien der xpdf gestartet werden soll, sofern ein X11-Display zur Verfügung steht (ich habe es etwas gekürzt):

   application/pdf; /usr/bin/xpdf '%s'; test=test "$DISPLAY" != ""

Restliche Felder

Jetzt haben wir hoffentlich alle relevanten Felder beisammen:

Authentisierungsfelder

Diese Felder hatten wir eigentlich schon in Abschnitt 8. Hier folgt daher nur eine kurze Auflistung. Definierte Verfahren sind laut RFC-2617 nur Basic und Digest.

WWW-Authenticate - Proxy-Authenticate
Diese beiden Felder dienen dem Server um einem Client mitzuteilen, dass er sich gegen einen Webserver bzw. Proxy authentisieren soll. Als erstes Argument wird dabei das Verfahren angegeben. In der Regel wird noch ein Realm mitgegeben, dies soll dem Anwender präsentiert werden, damit dieser leichter erkennen kann, welcher Benutzername und welches Passwort hier gemeint sind.
Authorization - Proxy-Authorization
Mit diesen Feldern teilt der Client im Request dem Server, einem Webserver bzw. Proxy, seine Authentisierungsdaten mit. Der erste Parameter gibt wieder das verwendete Verfahren an, also in der Regel Basic oder Digest.

Sonstige Header

Diese Header hatte ich bislang noch nicht irgendwo unterbringen können, sie spielen jedoch gelegentlich eine Rolle.

Max-Forwards
In Verbindung mit der TRACE- oder OPTIONS-Methode kann hierüber die Zahl der Proxys für eine Verbindung begrenzt werden. Das kann, ähnlich wie bei einem Traceroute, dazu dienen die einzelnen Proxys gezielt zu identifizieren und so vielleicht Schleifen zu entdecken. Dazu wird einfach sukzessive der Wert hochgesetzt. Das Argument ist einfach die maximale Zahl der erlaubten Proxys. Jeder Proxy analysiert und aktualisiert dieses Feld indem er den Wert um eins heruntersetzt. Ist dieser Wert beim Empfangen bereits Null, so darf der Proxy den Request nicht weiterleiten.
Bei einem OPTIONS-Request antwortet der Proxy dann mit seinen eigenen OPTIONS, ein TRACE-Request endet hier.
Vary
Dies ist ein wichtiges Feld für Caches. Gelegentlich gibt es, je nach Clientanforderung, unterschiedliche Antworten. So kann es eine Seite in Deutsch aber auch in Englisch geben, ohne, dass es der URL anzusehen ist. Über das Vary-Headerfeld kann der Server mitteilen, dass es auch andere Optionen gibt. Hier stehen dann die Feldnamen, die variieren können, z.B.:
   Vary: Accept-Encoding,Cookie
Mit einem anderen Cookie oder Accept-Encoding könnte es also eine andere Antwort vom Server geben. Ein Cache muss dies wissen und beachten, damit er nicht die falsche Antwort aus dem Cache liefert.
Accept-Ranges
Aufgetaucht ist dieses Feld schon früher, hierüber kann ein Server einem Client mitteilen, dass er bereit ist, auch Requests für Bereiche der Daten zu empfangen oder auch nicht. Es wird zudem mitgeteilt, welche Art von Bereichen akzeptiert werden. Eigentlich gibt es aber nur zwei Varianten:
   Accept-Ranges: bytes
und das verneinen von Range-Requests:
   Accept-Ranges: none
Retry-After
Wenn der Server einen 503 Service Unavailable Status liefert, so kann er über dieses Feld mitteilen, wann es ein Client erneut versuchen soll. Dies kann entweder ein Datum oder die Zeit in Sekunden sein. Bei Wartungsarbeiten ist dies durchaus sinnvoll, da weiß man, hoffentlich, wann der Server wieder bereit sein wird.
Das Feld kann aber auch bei Weiterleitungen verwendet werden um dem Client anzudeuten, dass er ein wenig warten soll. Das kann hilfreich sein, wenn der Inhalt erst noch dynamisch generiert werden muss und daher nicht sofort bereit steht.
Server
Beim Client gibt es das User-Agent-Feld um mitzuteilen, welcher Browser den Request gesendet hat. Das Pendant dazu ist das Server-Feld: Ein Server kann hier angeben, welche Software hier im Einsatz ist. Manche verschleiern, aus welchen Gründen auch immer, welchen Server sie verwenden. Beim Apache gibt es auch die Variante, bei der nur sagt, dass der Server ein Apache ist, d.h. ohne weitere Informationen über die Version oder Erweiterungen. Der Apache von Debian sagt hier:
   Server: Apache/2.2.15 (Debian)
Allow
In Abschnitt 7 tauchte dieses Feld schon öfters auf. Mit diesem Feld können die vom Server erlaubten Methoden aufgelistet werden, z.B.:
   Allow: GET,HEAD,POST,OPTIONS
Dieses Feld kommt bei OPTIONS-Request zum Tragen oder aber auch wenn eine nicht-akzeptierte Methode zum Einsatz kommt um die erlaubten aufzulisten.

Eigene Felder

Man kann natürlich auch andere, eigene Felder verwenden. Oft werden diese, analog zu E-Mail, mit X- beginnend verwendet, z.B. X-Cache oder X-Moz. Ein Client oder Server, der die Felder nicht kennt, ignoriert sie einfach, dadurch gibt es keine Probleme, wenn der Standard tatsächlich einmal erweitert werden soll. Ob dann aber auch alles so funktioniert, wie es geünscht ist, bleibt eine andere Frage. Aber oft dienen die Felder nur dazu, zusätzliche Informationen für Debugging-Zwecke zu übertragen.

So ist das hier wohl ein selbsterklärendes Headerfeld vom Squid:

   X-Cache: MISS from majestix.physik.home

oder

   X-Cache: HIT from majestix.physik.home

Bei mehreren involvierten Caches können hier auch mehrere Einträge vorhanden sein, z.B.:

   X-Cache: MISS from sq75.wikimedia.org, 
            HIT from amssq33.esams.wikimedia.org, 
            MISS from amssq34.esams.wikimedia.org, 
            MISS from majestix.physik.home

Scheinbar setzt Wikimedia Squids als reverse Proxys vor den eigentlichen Servern ein. Dadurch, dass die Squids die gecachten Seiten bereits senden, wird der Server entlastet.

Damit wird also mitgeteilt, ob die Anfrage aus einen Cache beantwortet wurde oder ob sie im Cache fehlte. Ferner gibt es noch für Proxys den Header

   X-Forwarded-For: 192.168.1.54

Damit teilt ein Proxy dem Server oder nächstem Proxy in der Kette mit, von wem eigentlich der Request stammt. Das ist der DNS-Name oder die IP-Adresse des Clients. Bei mehreren Proxys stehen hier die Adressen der vorherigen Proxys ebenfalls in der Liste. Vielen ist das Feld nicht bewusst, es kann auch deaktiviert werden. Es ist allerdings per Voreinstellung enthalten.

Hier sind noch zwei schöne Felder:

   X-Content-Type-Options: nosniff

Damit kann man dem IE abgewöhnen, den Content-Type selber ermitteln zu wollen. Der IE hat die Angewohnheit, selber zu versuchen den Content-Type zu ermitteln, unabhängig vom angegebenen. So kann er in einer als Text- Datei deklarierten Antwort durchaus ein ausführbares Programm entdecken und es auch ausführen. Wer also seinem Virenscanner sagt, er bräuchte keinen Text scannen....

Ab IE8 gibt es wohl einen XSS-Filter gegen Cross-Site-Script-Attacken, der kann auch vom Server abgestellt werden:

   X-XSS-Protection: 0

Zum Trost: Er kann auch explizit akitiviert werden:

   X-XSS-Protection: 1; mode=block

Der ist auch nett:

   X-Moz: prefetch

Damit fordert der Firefox bereits Seiten an, die der Anwender vielleicht lesen mag. Dies macht er, wenn er sich gerade langweilt, siehe Link prefetching FAQ

Ob ein Server, wenn er so einen Heder sieht, dann langsamer antworten wird um Ressourcen zu sparen?

Performance-Optimierungen

Hier geht es nicht darum, einen Webserver schneller zu machen, sondern vielmehr darum wie man das Beziehen der Daten vom Server oder den Servern verbessern kann.

Zum einen kann man natürlich schon auf TCP-Ebene einiges optimieren, es hängt auch durchaus von der Art der Leitung ab. Das ist aber auch oft eine Domäne der Webproxys: Hier kann man die Optimierungen am Proxy vornehmen und muss nicht alle Clients einzeln anpassen. Da ein Proxy gewöhnlich im LAN steht ist die Optimierung der Client - Proxy Anbindung eher nebensächlich.

Klassische Anbindung

Im einfachsten Fall baut der Client eine TCP-Verbindung zum Server auf, setzt den Request ab, wartet auf die Daten und baut die Verbindung dann ab. Anschließend werden die Daten analysiert, meistens ist es eine HTML- Seite, welche weitere Elemente beinhaltet die per HTTP eingebunden werden. D.h. der Client macht für die nächste Ressource eine Verbindung auf, setzt den Request ab, usw.

Um es einmal anschaulich zu verdeutlichen, zuerst wird die URL analysiert und der Hostname extrahiert. Anschließend erfolgt die DNS-Anfrage um die IP-Adresse des Servers zu ermitteln. Dann erst erfolgt der Aufbaue der Verbindung, das beziehen der Daten und der Abbau der Verbindung:

     Client                   Server
         -------- SYN --------->
         <----- SYN + ACK ------
         -------- ACK --------->
         --- GET / HTTP/1.0 --->
         <---- index.html ------
         <-------- FIN ---------
         --------- ACK -------->
         --------- FIN -------->
         <-------- ACK ---------

Der Empfang der Datei kann dabei natürlich über mehrere TCP/IP-Pakete erfolgen. Man sieht aber deutlich, dass es einen deutlichen Overhead durch den Auf- und Abbau der TCP-Verbindung gibt. In schnellen Netzen, wie z.B. dem LAN, spielt das meist keine große Rolle. Kommen aber längere Laufzeiten und/oder volle Leitungen hinzu, so kann das schon zu Problemen führen. Auch Latenzen, wie sie z.B. durch Satellitenverbindungen entstehen, schlagen hier negativ zu Buche.

Parallele Verbindungen

Ein Ausweg besteht im parallelen Laden von Seiten. Hier wird zwar der TCP-Overhead nicht umgangen, es werden aber parallele Verbindungen zum Server hergestellt. Die Optimierung besteht alleine darin, dass die Verbindungen parallel aufgebaut werden, man muss nicht mehr warten bis alle Inhalte der Reihe nach geladen wurden.

Dies funktioniert aber nur sinnvoll, wenn die Leitung nicht überlastet ist. Anderenfalls würden die parallelen Verbindungen miteinander konkurrieren und der Gesamtdurchsatz kann, z.B. durch Paketverluste oder automatischen TCP-Anpassungen, sinken. Auch kann der Speicherverbrauch dadurch steigen, wenn viele Verbindungen parallel offen sind.

Auf der Serverseite kann es dadurch auch zu Engpässen kommen, wenn zu viele Clients zu viele Verbindungen zum gleichen Server parallel aufbauen.

Parallele Verbindungen können also das Laden von Daten beschleunigen, sie können aber auch negativ wirken. Hier gibt es also durchaus Raum für ein paar Optimierungen die z.B. von der Internetanbindung abhängen. Jede neue Verbindung dürfte auch am Slow-Start-Mechanismus leiden, der langsam erst die Paketgröße hochsetzt.

Persistente Verbindungen

Mit der Option keep-alive kann auch eine persistente Verbindung zum Server aufgebaut werden. Dies ist bei HTTP/1.1 auch der default. Dabei findet der TCP-Hanshake und das Beenden der Verbindung nur einmal statt, das reduziert den Overhead. Es wird also ein Request abgesetzt, die Daten geladen und ein neue Request wird gesendet, der wiederum zu neuen Daten vom Server führt, etc. Auf diese Weise kann die TCP-Verbindung optimal ausgenutzt werden, langsame Starmechanismen wirken sich nur am Anfang aus und die Zahl der offenen Verbindungen ist gering.

Auf der anderen Seite bedeutet dies aber auch, dass die Daten erst nach und nach eintreffen. Zudem muss aufgepasst werden, dass diese persistenten Verbindungen auch rechtzeitig abgebaut werden um die gebundenen Ressourcen, sowohl beim Client als auch Server, wieder freizugeben.

Persistente Verbindungen können auch zu Problemen mit Proxys führen, die dieses Funktion nicht beherrschen. Daher wurde auch das Feld Proxy-Connection eingeführt, es hilft aber auch nur bedingt, wenn mehrere Proxys in Reihe geschaltet sind, die vielleicht keine persistenten Verbindungen beherrschen.

Die Antwort der meisten Browserhersteller ist eine Mischung aus beiden. Dann öffnet der Browser mehrere persistente Verbindungen parallel um die optimale Geschwindigkeit zu erreichen.

Pipelined Connections

Der Durchsatz von persistenten Verbindungen kann noch ein wenig optimiert werden. Normalerweise wird ein Request abgesetzt, dann wird auf die Daten gewartet. Ein Server braucht in der Regel etwas Zeit um die Daten zu finden, gegebenenfalls aufzubereiten und zu senden. Diese Verzögerung könnte man doch ausnutzen: Pipelining. Hierbei sendet der Client, ähnlich wie beim SMTP-Pipelining, alle Requests der Reihe nach ohne erst auf die Antworten zu warten. Diese sammelt er dann anschließend ein.

Das Verfahren kann bei hohen Latenzen auf der Leitung sehr effektiv sein, es hat aber auch einige Nachteile. So müssen die Antworten in der gleichen Reihenfolge gesendet werden, wie sie angefordert wurden. Wenn aber einige davon vom Server erst spät bereitgestellt werden müssen die anderen, um die Reihenfolge einzuhalten, zwischengespeichert werden.

Auch darf der Server jederzeit die Verbindung kappen, d.h. es müssen nicht erst alle Requests abgearbeitet sein. Ein Client muss darauf vorbereitet sein und entsprechend reagieren können.

Ein Problem kann auch bei Fehlern vom Server entstehen: Dieser muss zum einen nicht leicht zuzuordnen sein, er kann jederzeit auftreten, aber auch kann dies zu Problemen mit den andereren Requests bedingen: Will man diese wirklich bearbeitet haben, wenn vorher ein Fehler aufgetreten ist.

Das Verfahren ist also nicht ganz unproblematisch, es birgt doch ein paar unschöne Risiken. Vermutlich ist es deswegen auch per default im Firefox deaktiviert. Hier ist network.http.pipelining auf false gesetzt.

Sonstige Methoden

Auf der Serverseite gibt es durchaus auch Bestrebungen das Verhalten zu optimieren. So gibt es nicht selten nur eine IP-Adresse für eine ganze Serverfarm. D.h. persistente Verbindungen könnten hier vielleicht langsamer sein, da man dann immer nur mit einem Server aus der Farm spricht. Heise setzt z.B. auf diese Weise eine Serverfarm ein, www.heise.de hat nur eine IP-Adresse:

   www.heise.de.           86400   IN      A       193.99.144.85

Andere wiederum verteilen die Daten explizit auf verschiedene Server, so macht es z.B. E-Bay:

   www.ebay.de
   thumbs1.ebaystatic.com
   thumbs2.ebaystatic.com
   thumbs3.ebaystatic.com
   p.ebaystatic.com
   q.ebaystatic.com
   include.ebaystatic.com
   shop.ebay.de

Hier kann es also ohnehin nicht schaden parallele Verbindungen offen zu halten: Es sind auch verschiedene Server.

Es ist also alles andere als trivial, die optimalen Einstellung zu wählen. Dabei ist hier nur berücksichtigt, wie ein Client an die Daten kommt. Ein weiteres Problem ist dann, was der Client damit macht. Oft muss erst ein Großteil der eingebundenden Daten vorliegen um das Layout der Seite für die Darstellung korrekt bestimmen zu können. Dieses Rendern kann durchaus länger dauern und man hat oft das Gefühl, dass die Leitung langsamer ist als man eingekauft hat.

Manche Browser gehen hier einen Mittelweg: Sie zeigen schon einmal was sie haben und zeichnen dann die gesamte Seite neu, wenn das endgültige Layout ermittelt wurde.

Anmerkungen zu TLS/SSL und SNI

Um Server Name Indication (SNI) zu vestehen, muss man wohl etwas weiter ausholen. Standardmäßig erfolgt ein https-Zugriff als HTTP über SSL/TLS. Alle diesen Methoden ist aber eines gemeinsam: Erst wird eine vershlüsselte Verbindung, auf Basis von Zertfikaten, aufgebaut und darüber wird dann ganz normal HTTP gesprochen.

Für den Aufbau der SSL/TLS-Verbindung ist nur die IP-Adresse des Servers notwendig. Gleiches gilt für reines HTTP. Virtuelle Hosts, also die Abbildung von verschiedenen Domains über einen Server und eine IP-Adresse funktionieren auf Basis des Host-Headers. Der Server wertet diesen Eintrag aus und weiß so, welche Ressource auf welchem virtuellen Server angefordert wurde.

Und hier ist das Henne-Ei-Problem: Der Host-Header wird erst gesendet, wenn die SSL/TLS-Verbindung aufgebaut ist. Aber für den Aufbau dieser Verbindung wird das Zertifikat benutzt. Das ist aber an den Domainnamen gekoppelt, das Zertifikat gilt in der Regeln nicht für verschiedene Domains. Folglich kann man das Zertifikat nur auf Basis der angesprochenen IP-Adresse auswählen oder anders ausgedrückt: Pro IP-Adresse kann es nur ein Zertifikat und somit nur eine HTTPS-Verbindung geben.

SNI, Server Name Identifikation, ist eine Erweiterung von TLS. Dabei wird beim TLS-Handshake im server_name Feld der Name der gewünschten Domain mitgesendet. Der Server kann dann daran erkennen, welches Zertifikat für die gewünschte Verschlüsselung verwendet werden muss.

Im Zeitalter von knappen IPv4-Adressen scheint das ein goldener Wurf zu sein. Die Betonung liegt aber scheint.

Definiert wird die Erweiterung in RFC-3546 Transport Layer Security (TLS) Extensions aus dem Jahr 2003, es ist also schon durchaus älter. Dennoch erfodert diese Erweiterung, dass sie von TLS auf dem Server und auf dem Client implementiert ist.

Im Fall von Apache existiert SNI ab 2.2.12, die Version ist erst im Sommer 2009 ershienen. Halbwegs stabil lief es erst ab 2.2.14, die Version ist im Oktober 2009 erschienen, die Verbreitung ist noch entsprechend gering. Es erfodert auch eine halbwegs aktuelle OpenSSL-Version ab 0.9.8f. Der IIS von Microsoft unterstützt es bislang wohl noch nicht.

Der andere Punkt ist, dass der Client es auch unterstützen muss, das tun freilich noch nicht alle. Die größeren Browser, wie Firefox, Chrome, Opera und Safari unterstützen es, scheinbar aber der IE6 nicht. Allerdings gibt es da einen Haken in der Microsoft-Welt: Dort wird TLS über das Betriebssystem und nicht den Browser realisiert. Und, man ahnt es schon, XP kann das nicht, es geht erst ab Vista. XP ist aber durchaus noch in Gebrauch.

Damit haben wir aber den relevanten Punkt: Abgesehen von ein paar Paranoiden dürften Zertifikate in erster Linie im E-Commerce eingesetzt. Aber just da ist es wichtig möglichst alle erreichen zu können. Hier wird wohl kaum einer auf SNI ausweichen solange die Gefahr besteht einige potentielle Kunden auszusperren. Zudem ist SNI noch relativ neu, d.h. es ist hier durchaus auch noch mit einigen Bugs zu rechnen.

Ich kenne niemanden, der SNI einsetzt, außer vielleicht

    https://sni.velox.ch/

Aber das ist auch eine Testseite für SNI...

Fazit zu HTTP

Neben SMTP dürfte HTTP wohl zu den wichtigsten höheren Protokollen gehören. Man könnte auch sagen, dass diese beiden Protokolle die Basis für die Verbreitung des Internets bildeten. Wer hätte schon Interesse am Internet wenn E-Mail und das WWW fehlen würden?

SMTP mit allen Feinheiten hatten wir schon ausführlich besprochen und nun haben wir auch noch das meiste und wohl wichtigste rund um HTTP behandelt. Dazu zählen das Verständnis zum Aufbau von URLs, einer Spezialform der URIs, den Aufbau von Requests sowie der hier möglichen Methoden (GET, POST, HEAD, etc.).

Ob ein Request erfolgreich war oder ob es Probleme damit gab, sei es beim Client oder dem Server, wird, ähnlich wie bei SMTP, über Statuscodes nebst erläuterndem Text mitgeteilt. Die Codes können leicht automatisch, in der Regel durch den Browser, ausgewertet werden, der Text dient der manuellen Fehlersuche: Er ist nicht für die maschinelle Verarbeitung gedacht, das geht mit Codes viel leichter.

Ferner haben wir uns die Authentisierungsverfahren und -methoden gegenüber einem Webserver oder Webproxy angesehen, wobei eigentlich nur basic und digest offiziell unterstützt werden.

Viele für die Verarbeitung von Requests notwendigen Informationen können über Headerfelder mitgeteilt werden. Diese gelten teilweise für Anforderungen und Antworten gleichermaßen, es gibt auch Felder die zwar identisch lauten aber unterschiedliche Bedeutungen haben und Felder, die nur für Anforderungen oder nur für Antworten verwendet werden dürfen.

Hier gilt, um Probleme mit neuen Feldern zu vermeiden, dass unbekannte Felder einfach zu ignorieren sind. Dadurch muss ein Server auch nicht alle Felder, die mit dem Request übermittelt werden, auswerten, um ihn bearbeiten zu können. Für den Client gilt umgekehrt das gleiche.

Bei den Feldern gibt es ein von Mythen umgebenes spezielles Feld: Die Cookies. Auch hier dürfte nun klar sein, was es mit ihnen auf sich hat und wie schlimm sie wirklich sind: In aller Regel sind sie harmlos, sie können aber zum Tracking verwendet werden.

Den Abschluss bildeten dann Überlegungen zur Performance-Optimierung. Hier gilt aber auch das, was bei allem anderen auch gilt: Die Überlegungen beschränken sich auf HTTP. Sehr wohl kann man auf Anwendungsebene, also dem Aufbau der Webseiten auch noch viel an Geschwindigkeit gewinnen. Das ist aber außerhalb des hier besprochenen Rahmens.

Hinsichtlich Performance können aber Webcaches hilfreich sein, zu denen werden wir dann bald kommen. Es gibt auch noch andere Verfahren, um die Zugriffe zu optimieren. Einer der bekanntesten Dienste dürfte wohl Akamai sein: Sie betreiben über 15.000 Server in 69 Staaten in mehr als 1000 Netzwerken.

Die Idee dabei ist, dass sie über DNS regeln, welcher der Server den Inhalt liefern soll. Dabei werden räumliche Nähe als auch Auslastung des Netzwerks und der Server berücksichtigt. RTL2 ist z.B. einer der Kunden von Akamai, dies ist leicht über das DNS herauszufinden:

www.rtl2.de.            3600    IN      CNAME   www.rtl2.de.edgesuite.net.
www.rtl2.de.edgesuite.net. 21600 IN     CNAME   a1195.g.akamai.net.
a1195.g.akamai.net.     20      IN      A       92.123.72.114
a1195.g.akamai.net.     20      IN      A       92.123.72.80
a1195.g.akamai.net.     20      IN      A       92.123.72.91
a1195.g.akamai.net.     20      IN      A       92.123.72.112

Hier wird es nun interessant: www.rtl2.de hat einen CNAME in der Domain edgesuite.net. Der Nameserver wird von Akamai betrieben. Von dort wird aber nicht die IP-Adresse zurückgeliefert, sondern ein erneuter CNAME. Wer sich nun fragt: Was soll das bringen?

Die Antwort ist eigentlich recht einfach: Durch den ersten CNAME wird der auflösende Nameserver, also in der Regel der Nameserver des Clients oder dessen ISPs zu Akamai dirigiert: Dieser Nameserver sieht nun die Adresse des anfragenden (DNS-)Clients. In den meisten Fällen kann man an Hand der IP-Adresse den Ort einigermaßen lokalisieren. Nun sucht der DNS-Server von Akamai einen DNS-Server in der Nähe aus, das wird über den zweiten CNAME bewerkstelligt.

Der lokale Nameserver hat nun die Aufgabe die IP-Adressen zurückzuliefern, die die schnellsten Resultate liefern, also physikalisch in der Nähe sind, im richtigen Netzwerk stehen und möglichst nicht überlastet sind. Hier kommen dann auch die TTLs vom DNS ins Spiel. Die erste Zeit, 3600 Sekunden, also eine Stunde, wird von RTL2 vergeben. Die zweite Zeit des ersten CNAMEs ist mit 21600 Sekunden oder 6 Stunden recht lang: Daran ändert sich so schnell nichts, das dürfen die Clients im Cache behalten um weitere Anfragen zu beschleunigen.

Danach wird es dann interessant: Der letzte Nameserver liefert für die aktuelle Anfrage die IP-Adresse des derzeit besten Webservers zurück. Da dieser schnell auf z.B. Netzwerkengpässe reagieren können muss, ist die TTL hier extrem kurz: 20 Sekunden. Das reicht um eine Webseite aufzubauen und daher nicht ständig das DNS erneut bemühen zu müssen. Für weitere Links sind dann schon wieder neue Anfragen notwendig. Hier können dann, basierend z.B. auf der aktuellen Netzwerklage, völlig andere IP-Adressen zurückgeliefert werden.

Das ist der Trick den Akamai einsetzt. Er verlangt aber auch, dass alle ihre Server den aktuellen Inhalt liefern können. Wer sich noch fragt, was a1195 bedeutet: Das ist die Kundennummer von RTL2 bei Akamai...

Squid

Damit endet dann schon einmal der erste Teil und wir widmen uns dem zweiten Teil:

    Webproxys, Webcaches und Squid im besonderen

Wobei ich vermutlich den Squid nur rudimentär besprechen werde, er ist einfach extrem mächtig und viele der Fähigkeiten dürfte die meisten nur langweilen. Da heißt es dann wohl eher darauf hinzuweisen, was er kann und für die Details auf einschlägige Webseiten, Bücher, Google & Co zu verweisen.

Webproxy - allgemein

Ein Proxy ist in der IT ein Stellvertreterprogramm, d.h. er übernimmt die Aufgaben von anderen und führt sie aus. Ein Webproxy macht dies in der Regel mit Webrequests. Ein Client sendet z.B. seinen HTTP-Request an den Proxy und dieser erledigt dann die Aufgabe: Er löst den Namen auf, baut eine Verbindung zum Server auf, holt die angeforderten Daten und sendet diese dann zum Client. Die geholten Daten können auch auf dem Webproxy gespeichert werden und bei einem erneuten Zugriff direkt ausgeliefert werden oder nach einer Validierung der Daten gegenüber dem Server. In diesem Fall wird von einem Webcache gesprochen, oft ist es aber ein Synonym für Webproxy, die meisten haben einen Cache um zum einen die Auslieferung der Daten zu beschleunigen und die Datenleitung zum Internet zu entlasten.

Das hat viele Vor- aber auch ein paar Nachteile. Daher gibt es viele Argumente für oder gegen einen Proxy. Meistens kommt ein Proxy dann zum Einsatz, wenn mehrere Personen auf das WWW zugreifen wollen/sollen. Er ist dann der zentrale Drehpunkt und kann im Zeitalter knapper IPv4- Adressen helfen: Die Clients können mit privaten, RFC-1918-Adressen zugreifen, der Proxy geht mit seiner offiziellen IP-Adresse dann ins Internet. So gesehen, findet hier kein NAT statt, es gibt keine direkte Verbindung zwischen Client und Server.

Dies bietet zudem die Möglichkeit, die Webzugriffe an einer zentralen Stelle zu regulieren: Wer darf wann auf was zugreifen. Hier kann der Benutzer authentisiert werden, man kann die Zugriffe nach belieben umleiten, z.B. auf einen anderen Proxy. In Firmen kann das z.B. der Fall sein, um interne Zugriffe auf Inhalte anderer Konzerne über VPN zu realisieren statt über das Internet.

Es besteht auch die Möglichkeit Requests zu modifizieren um z.B. den eigentlichen Browser zu verstecken und damit die Privatsphäre wieder herzustellen. Unter

   http://panopticlick.eff.org

kann man sich z.B. ansehen, wie einfach ein Fingerprinting eines Browsers sein kann. Alleine durch die eingesetzte Version und dessen Fähigkeiten, Erweiterungen, bevorzugte Sprache, etc. kann ein Benutzer fast eindeutig identifiziert werden. Mit erlaubtem JavaScript geht das sogar noch genauer:

Your browser fingerprint appears to be unique among the 1,098,223 tested so far.

Nun wird hier zwar ein Benutzer nicht direkt identifiziert, sondern nur der verwendete Browser. Aber wenn man sich irgendwo einmal identifiziert, so kann man jederzeit bei Webzugriffen wiedergefunden werden. Das möchte der eine oder andere Anwender vielleicht nicht.

Da ist es dann gut, wenn man z.B. via ACLs die Browser-Version und einige andere Headerfelder auf neutrale Werte setzen kann.

Auch können umfangreiche Logdateien auf dem Webproxy angelegt werden, was die Fehlersuche deutlich erleichtert: Auf dem Client gib es gewöhnliche keine Logdateien und falls doch, so hat man darauf in der Regel keinen einfachen Zugriff.

Ein weiterer Vorteil besteht darin, dass nur ein Port für die Kommunikation zwischen Client und Webproxy benötigt wird: Der Client spricht nur HTTP mit dem Proxy, auch wenn z.B. eine FTP-URL angefordert wird. Wer sich immer mit den Feinheiten von FTP beschäftigt hat, weiß um diese Vorteile. Die zweite Verbindung für den Datenkanal ist immer ein Ärgernis für Betreiber von Firewalls/Paketfiltern. Da ist es dann gut, wenn man diese Prozedur auf ein System, den Proxy, beschränken kann.

Beim nächsten Mal geht es dann um die zusätzlichen Fähigkeiten vom Squid weiter. Die meisten werden zwar auch von anderen Proxys beherrscht, es hat nur wenig Sinn, die alle aufzuzählen und zu unterscheiden. Der Squid zählt wohl zu den ältesten und mit den meisten Features versehenen Webproxy und Webcache.

Webproxy Squid

Hier kommen nun ein paar Besonderheiten des Squids. In der Regel leisten das auch andere Proxys, aber diese hier werden alle vom Squid angeboten.

Der Squid dürfte wohl der bekannteste Open Source Proxy sein. Er ist auch sehr weit verbreitet und wegen seiner sehr guten Dokumentation auch recht beliebt. Die Homepage ist hier zu finden:

   http://www.squid-cache.org/

Historisches

Der erste Webproxy war auch der erste Webserver: Das war der CERN HTTP Server, der zugleich auch ein Proxy war. Diese Funktionalität wurde im Jahr 1994 hinzugefügt. Im selben Jahr startete die Internet Research Task Force Group, der Forschungszweig der IETF, das Harvest-Projekt. Das sollte eine Kombination von Tools sein, mit dem Internet-Informationen gesammelt, extrahiert, organisiert, durchsucht und auch gecachet werden sollen.

Das war so zu sagen eine Vorform von Google. Der Cache war ein Teil davon, er hatte drei große Vorteile gegenüber dem CERN-Proxy: Das Dateisystems wurde effektiver genutzt, er hatte ein Ein-Prozess-Design und es konnten Cache-Hierarchien über das Internet via Internet Cache Protocol verwendet werden.

Gegen Ende 1995 verließen viele das Harvest-Team und machten sich im Internetbereich selbstständig. Die ursprünglichen Autoren des Harvest Cache-Codes wandelten das Produkt in eine kommerzielle Version um. Ihre Firma wurde dann später von Network Appliance (NetApp) gekauft. Anfang 1996 fing dann Duane Wessels beim National Laboratory for Applied Network Research an und arbeitete am Information Resource Caching Projekt, welches vom NSF gefördert wurde.

Man kann es sich denken: Er nahm den alten Cache-Code vom Harvest-Projekt und benannte ihn in Squid, den Tintenfisch, um. Das Projekt gedeihte gut und es kamen viele neue Funktionen hinzu. Die Förderung lief dann im Jahr Juli 2000 aus und wird seitdem durch eine Vielzahl Freiwilliger weiter- entwickelt. Diese Entwicklung dauert auch noch an. So ist die neueste Version auf C++ umgestellt, der Support für IPv6 ist, wenn auch noch nicht so ganz vollständig, eingebaut, etc. Der Squid läuft auf allen bekannteren Unix-Systemen, darunter fällt natürlich auch Mac OS X, und es gibt auch eine Portierung auf Windows.

Besonderheiten von Squid

Hier gibt es viele Features, die über das hinausgehen, was andere Proxys bieten. Ich spreche hier aber nur die besonderen Eigenschaften an.

ACLs

Access Control Lists dürften wohl die wichtigste Funktion für einen Proxy sein. Darüber kann nicht nur gesteuert werden, wer auf den Proxy zugreifen darf, sondern auch, wie mit einzelnen Requests umgegangen wird. Diese können z.B. direkt an den Webserver geleitet werden oder über andere Proxys dirigiert werden. Auch können die URLs umgeschrieben und zu anderen Seiten gelenkt werden. Das wird z.B. bei den berühmten Schmuddelfiltern verwendet.

URL-Filter

Dies kann z.B. über Dateien mit Listen von verbotenen URLs oder Domains erfolgen oder aber über zusätzliche Programme wie den SquidGuard:

   http://www.squidguard.org/

Hier gibt es auch kategorisierte Listen, die für den privaten Gebrauch auch kostenlos sind:

   http://www.shallalist.de/

Hier sind die verfügbaren Kategorien aufgelistet:

   http://www.shallalist.de/categories.html

Viren-Scanning

Ferner besteht die Möglichkeit über eine ICAP-Schnittstelle externe Virenscanner anzusteuern. Dadurch können die heruntergeladenen Daten erst einmal auf Viren überprüft werden. Hier hat man aber oft mit Timingproblemen zu kämpfen: Der Proxy muss erst den gesamten Inhalt vom Server beziehen bevor der Virenscanner seine Arbeit beginnen kann. Dies kann, je nach Größe und Inhalt der Daten, durchaus länger dauern als der Timeout des Clients: Dieser bekommt keine Daten und schließt dann einfach die offene Verbindung. Wenn das der Scanprozess nicht mitbekommt, so läuft er einfach weiter. Mitunter kann der Inhalt auch erneut angefordert und man ist in einer Schleife gefangen.

Auswege aus dieser Misere bestehen in der Verwendung eines Transferstatus. Dabei wird ein Fortschrittsbalken oder ähnliches ausgeliefert in Verbindung mit einem Refresh. Dadurch wird die Seite so lange aktualisiert, bis der Virenscanner seine Arbeit erledigt hat und die Daten ausliefert. Das ist ein elegantes Verfahren für Webbrowser wie den Firefox. Das funktioniert aber nicht bei automatischen Tools, wie z.b. wget. Hier muss man sich andere Mechanismen überlegen um den Request nicht in einen Timeout laufen zu lassen.

Eine andere, von vielen verwendete Methode besteht im trickling. Dabeiwerden tropfenweise einzelne Bytes durchgelassen. Dadurch bleibt dieVerbindung aktiv und es gibt keinen Timeout. Der Anwender merkt am Anfang nur einen sehr langsamen Download der Daten. Auf den ersten Blick klingt dies wie eine sehr gute und effektive Methode. Das Problem ist jedoch, dass dabei mitunter schon virenverseuchte Daten übertragen wurden, die der Virenscanner noch gar nicht gescannt hatte. Im Falle eines Virus wird zwar die Übertragung der Daten gestoppt, das Dateifragment mit eventuell dem Schadcode, liegt im Dateisystem und könnte vielleicht seine Wirkung dennoch entfalten. Daher lehnen viele dieses Verfahren wiederum ab. Es dürfte aber dennoch sicherer sein, als gar nicht zu scannen.

Authentisierung

Squid beherrscht die meisten Authentisierungsverfahren, die verfügbar sind, so ist eine Authentisierung via Basic, Digest, NTLM als auch Kerberos im Bereich des möglichen. Diese greifen dann meist auf externe Programme zurück, bei NTLM ist dies z.B. der winbindd aus dem Samba-Projekt. Aber auch hier wird eine Vielzahl verwendet. So können die Benutzernamen und Passwörter einfach aus einer Datei stammen, via PAM oder auch via LDAP ermittelt werden.

Reverse Proxy

Der Squid kann auch als ein revers Proxy fungieren. Dies wird auch als surrogate mode oder einfach als HTTP-Accelerator bezeichnet. Dabei steht dann der Proxy nicht im LAN der Clients sondern steht vor dem eigentlichen Webserver. Dabei werden dann die (statischen) Seiten aus dem Cache ausgeliefert und der Server wird dadurch entlastet.

Intercepting Proxy

In diesem Modus agiert der Proxy volltransparent, die Clients merken nicht, dass ein Proxy verwendet wird. Dazu wird der Traffic auf Port 80 einfach über Paketfilterregeln zum Squid umdirigiert und er arbeitet dann als Proxy. Dieses Verfahren klingt besser als es ist. Der Vorteil, dass kein Proxys beim Client eingetragen werden muss, zieht eine Vielzahl von Nachteilen mit sich. So kann dies nur auf bekannten Ports funktionieren, wird ein anderer Port verwendet, so müsste man jeweils die Filterregeln anpassen. Eine Authentisierung ist auch nur bedingt möglich: Der Client erwartet mit einem Server zu sprechen, da sind dann Proxy-Authentication- Meldungen etwas unangebracht. Wenn sich der Proxy dann aber als Server ausgbit (401 statt 407): Was macht man dann, wenn der Server ebenfalls eine Authentisierung verlangt?

Auch ein Umlenken auf eine Webseite zur Authentisierung kann Probleme mit sich ziehen: Zum einen kann das nicht jedes Programm (z.B. wget) und was ist mit dem Browser-Cache? Das kann trickreich werden, hier die korrekten Parameter im HTTP-Header zu verwenden.

SSL Bump

Dies ist eines der neueren Features: Das Aufbrechen von SSL-Verbindungen damit man Zugriff auf den Inhalt hat. Im Fall von Virenscanning wird so etwas benötigt: Wenn die Verschlüsselung vom Server zum Client durchgängig ist, wie soll dann der Proxy nach Viren scannen? Aber auch wenn Filterungen wie z.B. das Herausschneiden von aktiven Inhalten (JavaScript, ActiveX, ...) erfolgen sollen, so muss man die Verschlüsselung aufbrechen.

Heikel ist es dahingehend, dass die Daten auf dem Squid terminiert werden müssen. Diese werden dann zum Client hin einfach neu verschlüsselt. Hier werden dann aber die Zertifikate nicht mehr stimmen. Ein Trick besteht darin, einfach on-the-fly neue zu erstellen und mit den Daten des Original- Zertifikats zu füllen. Das bedingt aber eine eigene CA auf dem Squid und der public-root-key muss in den Browsern integriert sein.

Parents und Siblings

Squid kann Anfragen kaskadieren, d.h. an den nächsten Proxy (parent) weiterreichen oder aber auch gleichrangige, parallel laufende Proxys (siblings) befragen. Bei Siblings, also Geschwistern, dient es dazu zu erfragen, ob diese die Daten bereits im Cache haben. Ist dies nicht der Fall, so wird der Request regulär weiterverarbeitet. Unterstützte Protokolle sind dabei ICP - Internet Cache Protocol, HTCP - Hyptertext Caching Protocol, CARP - Cache Array Routing Protocol.

QoS-Support

Die Unterstützung von Quality-of-Service ist auch möglich, das muss aber vom Betriebssystem unterstützt werden. Bislang geht dies wohl nur zusammen mit Linux. Dabei können dann Pakete mit QoS-Werten versehen werden wenn sie aus dem Cache stammen oder an einen Parent oder Sibling gehen. Das Feature dürfte aber selten zum Einsatz kommen.

IPv6

Die neuesten Squid-Versionen unterstützen mittlerweile IPv6. Allerdings nicht so, wie es alle gerne hätten. Squid greift dazu auf den split-stack zurück, was unter Linux gut funktioniert: Es gibt nur einen offenen Socket, der sowohl IPv6 als auch IPv4 verarbeitet. Das machen aber nicht alle Betriebssysteme so:

   Sadly, OpenBSD, Mac OSX and Windows XP require what is called the 
   split-stack  form of IPv6. Which means sockets opened for IPv6 cannot
   be used for IPv4. There are currently some issues inside Squid with 
   the handling of socket descriptors which are stalling progress on IPv6 
   for those OS. 

Allerdings scheint sich da mit der Squid-Version 3.1.6 etwas getan zu haben, das Problem ist teilweise behoben:

   Limited support for IPv6 split-stack has been worked out.

   This means that users of MacOS X, OpenBSD and any others which forcibly
   disabled IPv6 due to lack of Squid support may enable as desired. IPv6
   DNS and contact with IPv6 clients is fully operational. Contact with
   IPv6-enabled websites and several management protocols is partially
   supported although some special squid.conf alterations are needed.

HTTP/1.0 versus HTTP/1.1 in der Antwort

Auch wenn Squid zum Großteil bereits das Protokoll 1.1 versteht und auch mit dem Server spricht, so wird noch in der Antwort an den Client ein HTTP/1.0 verwendet. Das sorgt gelegentlich für Probleme, insbesondere wenn persistente Verbindungen mit dem Server benötigt werden und dieser eine Authentisierung wie zum Beispiel NTLM verwendet. Dieses Protokoll verlangt persistente Verbindungen, bei eier 4er Meldung vom Server wegen der benötigten Authentisierung, schließt der Client dann die Verbindung und es funktioniert nicht.

Ab der Squid-Version 3.2 antwortet der Squid nun automatisch mit einem HTTP/1.1. Das dürfte dann so manche Probleme mit Cliens beenden und vielleicht ein paar neue Probleme aufreißen?

Besonderheiten bei Proxys/Caches

Das Verhalten eines Clients gegenüber einem Proxy ist ein wenig anders als gegenüber einer direkten Verbindung zum Server. Abgesehen von der Verwendung spezieller Headerfelder für die Beeinflussung von Caches oder zur Authentisierung gegenüber einem Proxy statt einem Webserver gibt es hier durchaus auch noch andere Aspekte, die man bedenken und auch wissen sollte.

Request

Die meisten Caches sind HTTP/1^.0-konform, verstehen aber den Großteil der HTTP/1.1-Syntax. Auch wenn es auf den ersten Blick recht harmlos aussieht, so ist der Aufwand für eine vollständige HTTP/1.1-Unterstützung nicht zu verachten. Wer wissen will, welche HTTP/1.1-Funktionen aus der Squid noch nicht oder nicht vollständig unterstützt, der findet hier ein paar Übersichtsseiten:

   http://wiki.squid-cache.org/Features/HTTP11

Hier ist die Checklist interessant, sie vergleicht die verschiedenen Squid-Versionen und deren HTTP/1.1-Unterstützung.

Da ein Proxy aber auf alle Fälle HTTP/1.0 unterstützen muss, gibt es ein Problem. Ein normaler Request für die Startseite sieht so aus:

   GET / HTTP/1.0

Mit so einer Zeile kann aber ein Proxy nichts anfangen: Die Adresse des Webservers fehlt. Diese muss der Webserver eigentlich nicht wissen, der Client hat sich ja bereits mit ihm verbunden. Das zeigt aber auch eines der Probleme von HTTP/1.0: Es ist damit nur eine Domain abbildbar bzw. mehrere Domains würden immer den gleichen Inhalt liefern. Der Webserver ist nicht in der Lage herauszufinden, welche (virtuelle) Domain denn gewünscht ist.

Ein Proxy hat hier noch das zusätzliche Problem, dass er ja nicht das letzte Ziel ist, d.h. er kann mit der angesprochenen IP-Adresse nichts anfangen, es ist seine eigene. Mit HTTP/1.1 ist dieses Dilemma durch die Einführung des Host-Pflichtfeldes gelöst worden, da sieht es dann z.B. so aus:

   GET / HTTP/1.1
   Host: www.lug-erding.de

Aber Proxys gibt es zum einen schon länger und sie müssen auch in der Lage sein mit HTTP/1.0 Requests umgehen zu können. Daher ist hier der Aufruf anders geregelt: Hier muss die vollständige URL übergeben werden, also im obigen Fall wäre das:

   GET http://www.lug-erding.de/ HTTP/1.0

Mit HTTP/1.1 müssen Webserver so einen Aufruf auch verarbeiten können, dies machen aber noch nicht alle Webserver. Will man aber z.B. einen Proxy mittels telnet testen, so sollte man daran denken, hier die ganze URL zu übergeben, ansonsten antwortet der Proxy mit etwas wie:

   HTTP/1.0 400 Bad Request

FTP

Ein weiterer Aspekt bei Proxys betrifft FTP. Ein Browser kann auch Daten von einem FTP-Server via FTP beziehen. D.h. der Browser spricht dann auch FTP mit dem Server. Bei der Eingabe von z.B.:

   ftp://ftp.isc.org/isc/bind9/

spricht der Browser mit dem Server ftp.isc.org, sofern kein Proxy involviert ist. Die Umwandlung der Verzeichnisliste in eine HTML-Datei zur Darstellung im Browser nimmt in der Regel auch der Browser vor.

Im Fall von einem Proxy muss dies aber anders laufen: Hier spricht der Browser dann nicht FTP mit dem Proxy sondern auch HTTP, das sieht dann so aus:

   GET ftp://ftp.isc.org/isc/bind9/ HTTP/1.0

Hier wandelt dann aber der Proxy die Dateiliste in eine HTML-Datei um. Wenn man an die Widrigkeiten von FTP und die jeweils zwei notwendigen Verbindungen denkt, von denen eine noch dynamisch aufgebaut wird, so ist das eine feine Sache: Man muss sich nur noch darum kümmern, dass der Proxy sauber FTP mit dem Server sprechen kann. Gerade bei der Verwendung von Firewalls ist das sehr hilfreich, die Datenverbindung mit ihren dynamischem Charakter ist nicht einfach zu behandeln.

Es gibt auch noch andere Protokolle wie Gopher, die spielen heutzutage aber keine Rolle mehr. Eine Ausnahme bildet noch SSL.

SSL

Verschlüsselte Verbindungen sind jedoch über diese Methode nicht abbildbar, hier kann der Proxy die Kommunikation zwischen Client und Server nicht mehr mitlesen, er kann also auch nicht eingreifen. Die Verschlüsselung ist hier durchgehend vom Client zum Server. Es gibt zwar auch ein paar Tricks um diese aufzubrechen, beim Squid heißt das dann SSL Bump. Aber das ist nicht wirklich ein knacken der Verschlüsselung, vielmehr wird diese jeweils beim Proxy beendet und neu aufgebaut: Das ist ein klassischer Man-in-the-middle Vorgang.

Um solche Verbindungen aber dennoch über einen Proxy abbilden zu können, gibt es die CONNECT-Methode. Diese hatten wir aber bereits bei den Request- Methoden besprochen. Hier wird einfach nur ein HTTP-Header mit der CONNECT- Methode vor den eigentlichen Daten gesetzt, der Rest wird einfach direkt weitergeleitet. Hier findet natürlich auch kein Caching statt, weder der genaue Request sind bekannt, noch könnte man die verschlüsselten Daten sinnvoll Cachen. Sollte jemand anderes den gleichen Inhalt anfordern, so wäre der auch verschlüsselt, aber mit einem anderen Schlüssel!.

PURGE-Methode

Dies ist eine Squid-Erweiterung der Request-Methoden und lässt sich auch nur mit dem mitgelieferten squidlcient-Programm nutzen. Diese Methode wurde eingeführt, damit man explizit Daten aus dem Cache löschen (purge) kann. Der Aufruf ist eigentlich ansonsten identisch, es wird die URL der aus dem Cache zu löschenden Daten übergeben, z.B.:

   squidclient -m PURGE http://www.lug-erding.de/

Im erfolgreichen Fall kommt eine Meldung wie

   HTTP/1.0 200 OK

Diese Methode ist aber normalerweise deaktiviert, d.h. sie muss explizit erlaubt werden. Gewöhnlich wird dann nur localhost als Quelle zugelassen, d.h. jeder der auf dem Proxy eingeloggt ist, darf den Cache manipulieren.

Es gibt aber noch andere Methoden, frische Daten, also nicht aus dem Cache, anzufordern.

Prefetching

Das klingt im ersten Moment seltsam: Wie soll ein Proxy schon vorher wissen, welche Webseiten einen interessieren? Auch die Frage, ob es sinnvoll ist, muss beachtet werden. Aber im Grunde genommen ist die Idee nicht so verkehrt, es kommt auf den Einsatzzweck an.

Heutzutage haben die meisten einen schnellen DSL-Anschluss oder etwas vergleichbares. Da hat das Prefetching, also das frühzeitige Auffüllen des Caches bevor die Daten angefragt werden, wenig Vorteile.

Anders jedoch sieht es aus, wenn man nur eine langsame Internetverbindung hat, z.B. via Modem oder ISDN. Auch Satellitenverbindungen sollte man hier ins Auge fassen: Die sind zwar sehr schnell, sie haben aber eine sehr hohe Latenzzeit. Eine halbe Sekunde ist hier sogar schon normal: D.h. wenn man den Request absendet, dauert es erst einmal eine halbe Sekunde, bis die Antwort gesendet wird. Die kommt dann zwar sehr schnell, aber die halbe Sekunde kann stören. Insbesondere, wenn weitere Elemente für den Aufbau der Webseite nachgeladen werden müssen: Für jedes weitere Element muss man auch weiter halbe Sekunden hinzuaddieren. D.h. trotz einer hohen Downloadrate kann es doch recht lange dauern, bis die Seite aufgebaut ist. Da macht das Surfen dann nur noch bedingt Spaß...

Die einfache Idee ist, dass man dann z.B. in der Nacht schon einmal alle möglichen Seiten holt und schon einmal im Cache ablegt. Dafür ist es aber ratsam, dass man eine Flatrate hat, sonst kann das teuer werden. Die dafür relevanten Links kann man einfach aus der Logdatei des Proxys extrahieren. Die Wahrscheinlichkeit, dass am nächsten Tag noch einmal die gleichen Seiten besucht werden, ist recht hoch. Dazu kann man, bis zu einer noch zu definierenden Linktiefe, den Cache vorfüllen und kann dann am nächsten Tag deutlich schneller auf die Inhalte zugreifen.

Das setzt aber auch voraus, dass die Betreiber von Webseiten auch cachebaren Inhalt produzieren. Manche Webserver generieren den Inhalt immer dynamisch, da hat man dann schlechte Karten...

Cachemgr

Das ist ein Feature vom Squid aber andere Proxys haben etwas vergleichbares. Damit kann man die Effektivität des Caches bestimmen, dieverse Statistiken auslesen und dergleichen. Dieser kann über den squidclient oder auch direkt via telnet ausgelesen werden, z.B.:

    GET cache_object://majestix.physik.home/ HTTP/1.0

Dies liefert eine Liste mit weiteren Informationen die angefordert werden können, z.B.:

 mem                    Memory Utilization      public
 cbdata                 Callback Data Registry Contents public
 events                 Event Queue     public
 config                 Current Squid Configuration     hidden
 ipcache                IP Cache Stats and Contents     public
 fqdncache              FQDN Cache Stats and Contents   public
 idns                   Internal DNS Statistics public
 external_acl           External ACL stats      public
 http_headers           HTTP Header Statistics  public
 menu                   This Cachemanager Menu  public
 shutdown               Shut Down the Squid Process     hidden
 reconfigure            Reconfigure the Squid Process   hidden
 offline_toggle         Toggle offline_mode setting     hidden
 info                   General Runtime Information     public
 filedescriptors        Process Filedescriptor Allocation       public
 objects                All Cache Objects       public
 vm_objects             In-Memory and In-Transit Objects        public
 openfd_objects         Objects with Swapout files open public
 pending_objects        Objects being retreived from the network        public
 client_objects         Objects being sent to clients   public
 io                     Server-side network read() size histograms      public
 counters               Traffic and Resource Counters   public
 peer_select            Peer Selection Algorithms       public
 digest_stats           Cache Digest and ICP blob       public
 5min                   5 Minute Average of Counters    public
 60min                  60 Minute Average of Counters   public
 utilization            Cache Utilization       public
 histograms             Full Histogram Counts   public
 active_requests        Client-side Active Requests     public
 storedir               Store Directory Stats   public
 store_check_cachable_stats     storeCheckCachable() Stats      public
 store_io               Store IO Interface Stats        public
 pconn                  Persistent Connection Utilization Histograms    public
 refresh                Refresh Algorithm Statistics    public
 forward                Request Forwarding Statistics   public
 client_list            Cache Client List       public
 asndb                  AS Number Database      public
 server_list            Peer Cache Statistics   public
 non_peers              List of Unknown sites sending ICP messages      public

Diese kann man einfach an die URL anhängen, z.B.:

  GET cache_object://majestix.physik.home/client_list HTTP/1.0

könnte so etwas liefern:

   HTTP/1.0 200 OK
   Server: squid/2.7.STABLE1
   Date: Fri, 06 Aug 2010 07:56:49 GMT
   Content-Type: text/plain
   Expires: Fri, 06 Aug 2010 07:56:49 GMT
   X-Cache: MISS from majestix.physik.home
   Via: 1.0 majestix.physik.home:3128 (squid/2.7.STABLE1)
   Connection: close
   
   Cache Clients:
   Address: 192.168.1.5
   Name: 192.168.1.5
   Currently established connections: 0
       ICP Requests 0
       HTTP Requests 26163
           TCP_HIT                   86   0%
           TCP_MISS               15206  58%
           TCP_REFRESH_HIT          107   0%
           TCP_REFRESH_MISS          47   0%
           TCP_CLIENT_REFRESH_M      12   0%
           TCP_IMS_HIT              683   3%
           TCP_NEGATIVE_HIT          49   0%
           TCP_MEM_HIT              146   1%
           TCP_DENIED              9827  38%
   
   Address: 192.168.0.10
   Name: 192.168.0.10
   Currently established connections: 2
   ...

Es gibt aber auch ein cgi-Skript um dies Ausgaben auch via Browser zu ermöglichen und in HTML auszugeben.

Cluster

Wie schon erwähnt, kann man auch Proxys zu Clustern verbinden, Proxys in gleicher Hierarchie-Ebene werden dabei als Siblings bezeichnet, höhere, übergeordnete Proxys sind Parents. Es gibt diverse Protokolle mit denen sich die Proxys unterhalten können um herauszufinden, ob ein anderer Proxy den Inhalt bereits im Cache hat. Das kann sinnvoll sein, wenn man die Proxys hochverfügbar anlegt und die Last über beide verteilt. Welche es da gibt, hatte ich schon beim letzten Mal erwähnt: ICP, HTCP und CARP.

Proxy-Cluster sind aber eher ungewöhnlich, sie kommen doch recht selten zum Einsatz. Kaskadierungen hingegen schon. Das hat oft etwas damit zu tun, dass Konzerne größer sind und die Zugriffe auf Intranetseiten von anderen Konzernteilen über deren Proxys laufen sollen und oft auch müssen.

Filtermöglichkeiten

Diesmal geht es nur um die Möglichkeiten mit dem Squid. Diese ACLs stehen natürlich nicht allen Versionen zur Verfügung. Durch den Rewrite mit Version 3 gibt es hier durchaus ein paar Unterschiede. Diese sind aber eher spezieller Natur und man sollte in dem Fall explizit schauen welche Version man verwenden soll.

Dies ist eine der größten Stärken vom Squid, er kann auf nahezu alles reagieren, filtern, umleiten, modifizieren, etc. Das kann sowohl an Hand der Request-Methode (GET, POST, CONNECT, etc.) erfolgen, als auch auf Basis des verwendeten Protokolls wie HTTP oder FTP. Auch kann es gesonderte Behandlungen basierend auf den Browser geben, der IP-Adressen des Clients oder dem Ziel aber auch der URLs. Bei letzteren könne auch reguläre Ausdrücke verwendet werden, so kann sogar auf einzelnen Code- Wörtern der URL reagiert werden.

Die Berücksichtigung der Tageszeit kann auch ein Kriterium sein, eben so wie der MIME-Typ (z.B. Audio). Es kann zudem auf externe Programme, wie z.B. SquidGuard, zugegriffen werden. Auch können Headerfelder zum Request hinzugefügt oder auch entfernt werden.

In Verbindung mit den vielen Authentisierungsmaßnahmen kann dann auch auf Basis von Anwendern oder Gruppen reagiert werden. Dadurch kann z.B. einigen der Zugriff explizit auf alles erlaubt werden oder auch eine Liste von Zielen gezielt verboten werden, etc.

ACLs

ACLs werden beim Squid im wesentlichen durch das Schlüsselwort acl eingeleitet, danach folgt der Name für die so definierte ACL. So einfach kann das sein. Dabei kann es mehrere Zeilen mit dem gleichen ACL-Namen geben, die Einträge werden dann einfach verknüpft, so ist etwas wie:

   acl SSL_ports port 443 4643 8443

das gleiche wie

   acl SSL_ports port 443
   acl SSL_ports port 4643
   acl SSL_ports port 8443

Dabei haben wir schon einen Typen: port. Dieser definiert Zielports. Hier liegt eine der Stärken von Squid, es gibt eine Vielzahl von Typen.

Es kann via src die Source-Adresse angesprochen werden, z.B. als IP-Adresse/Netzmaske oder auch als Bereich. Gewöhnlich wird hier der Netzbereich angegeben, wie z.B.:

  acl clients_allowed src 10.0.0.0/8 

oder auch

  acl clients_allowed src 10.0.0.0/255.0.0.0

Das gleiche ist für das Ziel möglich mit dem Typ dst. Auch auf Basis von DNS sind Angaben möglich wie srcdomain oder dstdomain. Hier ist in der Regel die srcdomain irrelevant, da wird meistens mit dem IP-Bereich argumentiert. Bei den Zielen hingegen sieht es anders aus.

So kann eine ACL so lauten:

   acl ohne_passwort .lug-erding.de

Damit könnte man eine ACL definieren, die den Zugriff auf lug-erding.de sowie allen Subdomains erlaubt. Aber bislang ist es nur die Definition einer ACL! Was damit angestellt wird, ist eine andere Frage. Diese wird nicht über den Namen der ACL geregelt sondern später über die Zugriffs- regeln.

Neben der reinen Domain (nebst Subdomains, die über den Punkt am Anfang geregelt werden) können auch reguläre Ausdrücke verwendet werden. Hier heißen die Typen dann srcdom_regex oder dstdom_regex. Diese kennen auch noch die Option -i, dann wird Groß- und Kleinschreibung nicht unterschieden.

Diese Typen beziehen sich nur auf die Domain, mitunter möchte man aber ein regulärer Ausdruck für die gesamte URL anwenden. Das ist auch möglich und zwar mit url_regex für die gesamte URL, urlpath_regex für den reinen Pfad der URL und urllogin, welche auf das Login-Feld passt. All diese Typen kennen auch die Option -i.

Via port können Ports definiert werden, via proto Protokolle. Auch die Methode ist auswählbar über method, sowie der verwendete Browser über browser. Letzterer kennt wieder einmal die Option -i, wie auch der Typ referer_regex oder ident_regex. Ohne regulären Ausdruck gibt es auch noch den Typen ident.

Für die Authentisierung gibt es den Typen proxy_auth oder auch die Variante proxy_auth_regex, beide kennen die Option -i und verlangen einen gültigen Benutzer bzw. den regulären Ausdruck für einen. Hier gibt es noch die spezielle Option REQUIRED, damit sind alle gültigen Benutzer erlaubt.

Via peer_name kann ein Upstream-Proxy definiert werden, via time können Uhrzeiten und Tage festgelegt werden. Auch der Status-Code der Antwort kann über eine ACL behandelt werden: http_status.

Weitere Typen, die eher selten verwendet werden, sind:

myip
die ACL für die Adresse auf der der Squid angesprochen wurde
myport
dito für den Port
arp
die ARP-Adresse des Clients, muss aber beim Kompilieren explizit erlaubt werden.
src_as, dest_as
hier können AS-Nummer verwendet werden um Adressbereiche anzusprechen.
snmp_community
basierend auf den snmp-Agent
maxconn
die Anzahl der Verbindungen die ein Client zum Squid offen hat
max_user_ip
die maximale Zahl an Source-IP-Adressen, die ein Benutzer in Verwendung hat
req_mime_type
der angeforderte MIME-Typ
req_header
ein angeforderter HTTP-Header
rep_mime_type
der MIME-Typ der in der Antwort enthalten ist
rep_header
ein Header-Feld aus der Antwort
external
Verweis auf eine externe ACL, hierfür ist ein Hilfsprogramm, der sogenannte helper notwendig
url_group
hier können Gruppen behandelt werden welche über den externen helper definiert werden.
user_cert
ein Zertifikat des Clients
ca_cert
wer das Zertifikat ausgestellt hat
ext_user
ein extern definierter Benutzer, also via helper
ext_user_regex
ein regulärer Ausdruck für die vom externen helper gelieferten Benutzernamen

Wie man sieht: Es gibt eine Vielzahl an Möglichkeiten um erst einmal ACLs zu definieren. Diese können durch Angabe eines Dateinamens, in doppelten Hochkommata, auch in externe Dateien ausgelagert werden. Bei längeren und komplizierteren ACLs ist das etwas übersichtlicher. Viel effektiver ist dies aber bei einer zentralen Verwaltung der ACLs: Man muss dann nur die externen Dateien aktualisieren und nicht immer die gesamte squid.conf. Anschließend muss dem Squid nur mitgeteilt werden, dass er die Konfiguration neu einlesen muss. Dies kann entweder über das Signal SIGHUP erfolgen oder durch ein:

squid -k reconfigure

Interessant wird es aber erst mit der http_access-Direktive. Diese erlaubt oder verbietet den Zugriff. Hier können auch mehrere ACLs in einer Zeile angegeben werden, dann werden die Argumente aber UND verknüpft, d.h. beide ACLs müssen positiv sein, damit das Ergebnis passt. Die Direktive kennt zwei Werte: allow und deny, für die ACL gibt es noch die Negationsmöglichkeit via !, z.B. erlaubt die Direktive

   http_access deny CONNECT !SSL_ports

nicht den Zugriff über die CONNECT-Methode für nicht-SSL-Ports. CONNECT ist hier auch eine ACL:

   acl CONNECT method CONNECT

Wie man leicht erkennt, sind die ACLs immens mächtig. Wichtig ist aber auch zu wissen, dass die http_access-Zeilen der Reihe nach abgearbeitet werden. Was am Anfang erlaubt ist, kann später nicht mehr verboten werden. Meistens ist die letzte Direktive ein

   http_access deny all

D.h. alle Zugriffe, die bislang noch nicht erlaubt sind, werden einfach verboten.

Zugriffsregelungen

es geht wieder weiter, hier geht es um ein paar Einstellungsoptionen vom Squid. Ich habe sie einmal, nicht ganz so passend, Zugriffsregelungen genannt

ACLs als solche haben noch keine wirkliche Bedeutung, es sind erst einmal nur Definitionen. Eine Regel wurde letztes mal schon erwähnt:

   http_access

Hierüber wird der Zugriff auf den Proxy durch die Clients geregelt. Es sind zwei Werte möglich allow oder deny. Das dürfte dann wohl selbst erklärend sein.

Danach folgt dann die ACL oder mehrere. Wird mehr als eine ACL angegeben, so werden diese UND-verknüpft, d.h. beide ACLs müssen erfüllt sein, z.B.:

   acl LUG-URL dstdom .lug-erding.de
   acl LUGSRC src 192.168.1.0/24
   http_access allow LUG-URL LUGSRC

Dies erlaubt allen Clients aus dem Netz 192.168.1.0/24 auf die Domain lug-erding.de zuzugreifen. Der Punkt am Anfang erlaubt auch alle Sub- domains, wie z.B. www.lug-erding.de.

Es kann aber auch der Zugriff auf Parents oder Siblings geregelt werden. Diese anderen Proxys werden beim Squid als Cache-Peers bezeichnet. Sie werden passender Weise mit cache_peer definiert. Die Definition lautet hier:

  cache_peer  Hostname  Typ  Proxy-Port  ICP-Port  Optionen

Der Typ kann dabei entweder parent oder sibling sein. Meistens wird aber nur der erste Typ verwendet, er gibt den nächsten Proxy in der Kette der Zugriffe an. Der Proxy-Port ist wohl selbsterklärend, der ICP-Port wohl auch. Letzterer kann auf Null gesetzt werden, dann wird ICP (oder auch HTCP) deaktiviert.

Die Option no-query deaktiviert ICP-Anfragen ebenfalls.

Es gibt auch Optionen für die Auswahl des Cache-Peers wenn es mehrere gibt. default ist wohl selbst erklärend, round-robin wohl auch: Dabei wird er abwechselnd mit anderen verwendet. weighted-round-robin bietet noch an, die Verteilung zu gewichten.

Es gibt noch eine Vielzahl weiterer Optionen, so kann auch eine Authenti- sierung mit angegeben werden damit sich der Proxy beim nächsten anmelden kann.

Mittels

   cache_peer_domain 

kann man dann einem Cache eine spezielle Domain zuordnen, für die er verwendet werden soll. Durch vorgestelltes Ausrufezeichen ("!") kann dies explizit verneint, also verboten werden.

Flexibler geht es aber mit

   cache_peer_access

Hier könne dann ACLs verwendet werden, die ebenfalls durch ein vorgestelltes Ausrufezeichen verneint werden können.

Bei diesen Direktiven ist aber ein wenig Vorsicht angeraten, so sollte man darauf achten, welche erlaubt und welche verboten sind um das klar zu Regeln, z.B.:

   acl Firma-A dstdom .firma-A.de
   acl Firma-B dstdom .firma-B.de

   cache_peer_access Peer.Firma-A.de allow Firma-A
   cache_peer_access Peer.Firma-A.de deny all
   
   cache_peer_access Peer.Firma-B.de allow Firma-B
   cache_peer_access Peer.Firma-B.de deny all

   cache_peer_access Parent.Firewall.de deny Firma-A
   cache_peer_access Parent.Firewall.de deny Firma-B
   cache_peer_access Parent.Firewall.de allow all

Anderenfalls könnte es doch passieren, dass die Requests zum falschen Peer gesendet werden.

In diesem Zusammenhang gibt es aber noch zwei Direktiven, die man beachten sollte:

   always_direct

   never_direct

Hierüber kann geregelt werden, welche Requests direkt und welche nicht direkt, also über einen Cache-Peer, behandelt werden sollen.

Einen ähnlichen Effekt bietet

   hierarchy_stoplist

Hierüber kann Squid mitgeteilt werden, wann er nicht versuchen soll den Inhalt von einem anderen Cache zu beziehen. Das sind Seiten, von denen man weiß, dass sie nicht im Cache liegen können. Per Voreinstellung sieht dieser Eintrag so aus:

   hierarchy_stoplist cgi-bin ?

D.h. wenn ein Fragezeichen oder cgi-bin in der URL auftauchen, was für die Übermittlung individueller Parameter verwendet wird, so wird gar nicht erst im Cache nach einer Antwort gesucht. Es wird kein Nachbar-Cache danach gefragt, der Request wird direkt zum Server oder dem definierten Parent gesendet.

Eine weitere, erst mit Squid-3 verfügbare Option ist

   url_rewrite_acess

Squid bietet die Möglichkeit, jede URL, mit ein paar mehr Parametern wie der Zugriffsmethode und den authentisierten Benutzernamen, an ein externes Programm zu senden. Dieses wird mit

   url_rewrite_program

angegeben. Über die Access-Direktive kann nun geregelt werden, was alles zum Rewrite-Programm gesendet werden soll und was nicht. Mit Hilfe des Rewrite-Programms können URLs umgeschrieben werden. Das Programm liefert entweder eine leere Zeile zurück, dann ist die URL nicht umgeschrieben, oder eine neue URL.

Der squidGuard ist ein prominenter Vertreter dieser Programme. Er prüft, ob es sich bei dem Zugriff um eine erlaubte URL handelt. Ist dies nicht der Fall, so kann ein URL zurückgeliefert werden, die auf eine Seite verweist, welche den Grund für die Blockierung angibt.

Die Aktionen der Option

  request_header_access

sind ein klarer Verstoß gegen HTTP: Darüber können explizit Header-Felder aus dem Request gefiltert werden. Hierüber kann aber auch eine gewisse Anonymität wieder hergestellt werden. Der vollständige Eintrag sieht dann so aus:

  request_header_access header_name allow|deny [!]aclname

Z.B. könnte man durch

   acl WinzigWeich dstdom .microsoft.com
   request_header_access User-Agent deny WinzigWeich

verhindern, dass der User-Agent (z.B. firefox) an Microsoft gesendet wird.

Anstatt die Headerfelder zu entfernen, gibt es auch die Möglichkeit diese durch etwas anderes zu ersetzen. Dies erfolgt mit

   header_replace header_name message

also z.B.:

   header_replace User-Agent Nutscrape/1.0 (CP/M; 8-bit)

Neben den Request-Headern können natürlich auch die Reply-Header manipuliert werden.

Es gibt auch noch Einstellungen für die maximale Größe einer Antwort vom Server:

   reply_body_max_size

oder der Daten die man zum Server senden darf (POST):

   request_body_max_size

Auch die minimale und maximale Größe der Daten, die im Cache landen sollen, kann festgelegt werden, hier mit den Default-Werten von Squid-3.1:

   minimum_object_size 0 KB

   maximum_object_size 4096 kB

Mittels dem Eintrag

   cache_mem

kann dann noch definiert werden, wie groß der Cache insgesamt sein darf. Erst wenn diese Wert erreicht wird, fängt der Squid an aufzuräumen und alte Objekte zu löschen.

Es gibt noch eine Vielzahl weiterer Optionen, es sind rund 300 insgesamt. Diese sind in der squid.conf sehr gut dokumentiert, ab der Version 3 sind sie in eine separate Datei ausgegliedert: squid.conf.default.

Ein interessanter Eintrag ist vielleicht noch

   refresh_pattern [-i] regex min percent max [options]

Die Option -i ist wieder für Groß-/Kleinschreibung, ist sie angegeben, so wird diese nicht beachtet.

min gibt an, wieviele Minuten ein Objekt ohne angegebene Expire-Zeit im Cache verweilen soll. Dieser sollte eigentlich immer 0 sein, ansonsten kann es zu Problemen mit dynamischen Seiten kommen. Über percent kann angegeben werden, wieviel Zeit seit der letzten Modifikation des Objekts (sollte im Header stehen) als Zeit für die Cache-Verweildauer verwendet werden sollen. max gibt dann den maximal zulässigen Wert an.

Hier sind ein paar Beispiele:

   refresh_pattern ^ftp:           1440    20%     10080
   refresh_pattern -i (/cgi-bin/|\?) 0     0%      0
   refresh_pattern .               0       20%     4320

Man könnte auch so etwas machen:

   refresh_pattern -i \.(gif|png|jpg|jpeg|ico)$ 10080 90% 43200 override-expire 
      ignore-no-cache ignore-private

Damit werden Bilder über Gebühr lange im Cache gehalten. Die Idee ist oft nicht verkehrt, die Bilder ändern sich selten. Insbesondere fallen in diesen Bereich auch diese vielen kleinen Icons zur Navigation.

Man könnte auch youtube-Videos länger im Cache belassen, für den Fall, dass die GVU wieder zuschlägt:

   refresh_pattern -i youtube.com/.* 10080 90% 43200

Das ist vor allem aber dann hilfreich, wenn man keine schnelle Internet- anbindung oder einen hohe Verzögerung (Satellitenverbindung) hat.

Caching

Das ist ein schwieriges Thema. Wenn der Server mitteilt, wie lange eine Antwort gültig ist, so ist es einfach. Aber meistens ist es nicht der Fall, dann muss auf andere Weise ermittelt werden, was im Cache landen darf und was aus dem Cache verwendet werden darf.

Da gibt es dann durchaus die Option, den Server zu befragen, ob die im Cache befindlichen Daten noch aktuell sind. Das ergibt aber nur dann einen Sinn, wenn die Anfrage deutlich kürzer ist, als die Daten die geholt werden sollen.

Einfach wird es bei POST-Requests: Hier werden Daten zum Server gesendet, da ist dann damit zu rechnen, dass die Antwort dynamisch, basierend auf den Daten vom POST-Request, ist.

Wenn eine Authentisierung am Server notwendig ist, so sollte man die Antworten auch nicht im Cache behalten, zumindest in keinem geteilten Cache, den auch andere benutzen. Das kann aber vom Server überschrieben werden in dem er es mit Cache-Control: public explizit als im Cache ablegbar deklariert.

Dynamische Seiten, die jedesmal neu zusammengebaut werden, sind in der Regel auch nicht gut geeignet um im Cache zu landen. Wie man das aber herausfindet ist eine andere Sache.

Um zu verhindern, dass eine Seite im Cache landet, gibt es die Möglichkeit

   Expires: 0

zu setzen oder hier das aktuelle Datum einzutragen. Auch ein (altes)

   Pragma: no-cache

ist eine Option ähnlich zu

  Cache-Conrol: no-store.

Es existiert auch ein

   Cache-Control: no-cache

Das wiederum erlaubt dennoch das Cachen, die Daten müssen aber vor der Auslieferung an den Client gegenüber dem Server validiert werden.

Über Cache-Control kann ein Server auch explizit Felder benennen, die nicht im Cache landen sollen/dürfen. Klassischerweise werden Cookies natürlich nicht im Cache abgelegt: Ein anderer Benutzer soll auch einen anderen Cookie verwenden...

Die vielen möglichen Werte von Cache-Control hatten wir aber schon vor einiger Zeit besprochen.

Wichtig ist auch das Vary-Feld, hierüber wird mitgeteilt, dass es die Ressource auch in anderen Formaten gibt und zwar unter der gleichen URL!

Gut zu cachende Inhalte sind in der Regel die mit einem Return-Code von 200, 206, 300, 301 und 410.

Auch basierend auf der Methode kann gesagt werden:

Methode cachefähig?
GET ja
HEAD kann zum Update der Daten im Cache verwendet werden
POST nein, nur wenn es der Antwort-Header explizit erlaubt
PUT nein
DELETE nein
OPTIONS nein
TRACE nein
CONNECT nein

Bei CONNECT dürfte es klar sein, dass der Inhalt an sich schon im Cache landen dürfte, da aber alles vom Server bis zum Client verschlüsselt ist, kann der Webproxy nur die verschlüsselten Daten sehen. Die sehen aber bei jedem Request (hoffentlich) anders aus: Der Schlüssel ist ein anderer.

Eine Variante hatte ich schon erwähnt: Wenn man keinerlei Informationen darüber erhält, wie lange der Inhalt gültig sein mag, so kann man mit Heuristik arbeiten. Vergleicht man den Zeitpunkt, wann die Seite das letzte Mal geändert wurde mit dem Zeitpunkt des Holens, so kann mit einer gewissen Berechtigung eine Annahme darüber getroffen werden, wie lange die Seite sich nicht ändern wird.

Wenn z.B. beim ersten Aufruf der Seite ein Datum von vor ein paar Jahren als Zeitpunkt der letzten Änderung präsentiert wird, so kann man wohl davon ausgehen, dass sich die Seite in den nächsten Tagen vermutlich auch nicht ändern wird.

Auf der anderen Seite gibt es aber Leute, die alle Requests auch in der Applikation sehen wollen. Die setzen dann das Last-Modified-Feld immer auf das aktuelle Datum auch wenn die Seite nachweislich seit Jahren den gleichen Inhalt präsentiert. Der Grund liegt oft darin, dass die Ersteller der Webseiten oft andere Personen sind als die Betreiber des Webservers. Erstere wollen aber mitunter auch die Zahl der Zugriffe mitbekommen. Wenn sich der Inhalt aber nicht geändert hat, so antwortet bereits der Webserver und die Applikation (z.B. PHP-Seite) bekommt das nicht mit.

Das ist dann der Bereich, wo man auch auf der Cache-Seite anfangen kann zu tricksen und die Daten dennoch im Cache verwendet, so wie im letzten Beispiel mit den Bildern. Das ist aber auch ein Verstoß gegen den Standard, dies sollte einem bewusst sein.

Aber selbst wenn es im Cache landet, so kann man über den Browser einen Refresh erzwingen. Der normale Reload macht dies nicht, der Grund liegt darin, dass viele schon auf Reload klicken bevor die Seite vollständig angekommen ist. Ein erneuter Reload wäre da Kontraproduktiv.

Der erzwungene Reload ist bei jedem Browser anders, beim Firefox erfolgt dies über SHIFT+Reload (also SHIFT-Taste festhalten und auf Reload klicken), beim IE ist das CTRL+Reload.

Der Unterschied zum normalen Reload ist recht einfach: Es werden die Header-Felder If-Modified-Since und, sofern ETag verwendet wird, das If-None-Match weggelassen. Daraufhin muss der Server den Inhalt neu senden. Gegenüber dem Cache wird noch ein Cache-Control: no-cache sowie ein Pragma: no-cache.

Das hat noch einen interessanten Nebeneffekt, der allerdings nur selten zum Tragen kommt: DNS-Aktualisierung.

Ein Webproxy tut gut daran, nicht jeden Host aus dem Request via DNS aufzulösen. Daher werden die DNS-Antworten ebenfalls im Speicher gehalten, der Proxy merkt sich einfach die IP-Adresse zum DNS-Namen. Die Auflösung erfolgt aber über den normalen Resolver, dieser liefert nur die gefundenen oder nicht gefundenen Adressen zurück, die TTLs die damit verbunden sind jedoch nicht. D.h. selbst wenn sich eine IP-Adresse geändert hat und die TTL vom DNS-Server abgelaufen ist, kann es sein, dass der Webproxy noch immer die alte Adresse verwendet. Beim Squid ist das in der Regel ein Tag.

Durch den erzwungenen Reload wird auch die DNS-Anfrage erneuert. Das ist mit ein Grund, warum dieser Reload dann länger dauern kann.

Debugging-Tips

Eine ausführliche Anleitung wie man alle Fälle analysieren kann, würde wohl hier den Rahmen sprengen. Dennoch kann man leicht mit eine paar Tipps aushelfen. Es ist oft viel einfacher als man denkt.

Bis auf kleine Unterschiede, z.B. bei der URL, sind Proxys ähnlich leicht zu Debuggen wie direkte Zugriffe auf Webserver. Von daher gelten die nun folgenden Tipps meist für Webproxys wie Webserver gleichermaßen.

Fehlermeldung im Browser

Die Fehlermeldungen im Browser geben oft schon einen sehr guten Hinweis darauf, was schief läuft. Allerdings sind manche Fehlermeldungen nicht wirklich sehr aufschlussreich, insbesondere wenn sie vom IE stammen.

Manchmal betreffen die Fehler aber nur Teile der Seite, d.h. manchmal kann z.B. JavaScript nicht nachgeladen werden oder ähnliches. Diese Fehler werden dann in der Regel nicht angezeigt.

Logdateien

Logdateien, sofern man Zugriff darauf hat, geben meistens schon eine gute Auskunft. Hier sieht man den Statuscode direkt, auch über welchen Upstream Cache es gegangen ist oder mit welchem Server gesprochen wurde. Das ist auch eine gute Quelle für rückwirkende Fehlersuche.

Beim Squid findet man die relevanten Daten in der Datei access.log. Diese kann in mehreren verschiedenen Formaten geschrieben werden. Das native Format sieht z.B. so aus:

1272486968.401   4 ::1 TCP_MISS/200 4493 GET http://localhost/index.html - DIRECT/::1 text/html

Die erste Zahl gibt die Uhrzeit in Sekunden seit dem 1.1.1970 an, das ist die Unix-Zeitrechnung. Danach folgt die Dauer in Sekunden. Diese kann man unter BSD leicht mit date -r umgerechnet werden. Unter Linux geht dies auch, allerdings ein wenig anders:

   # date -d @1272486968.401 
   Mi 28. Apr 22:36:08 CEST 2010

Anschließend erfolgt noch die IP-Adresse des Clients. In diesem Fall ist es localhost über die Loopback-IPv6-Adresse ::1, bei IPv4 wäre das 127.0.0.1. Die vom Client angeforderte Ressource ist nicht im Cache, der Status-Code vom Server ist 200: TCP_MISS/200. Die über die GET-Methode geholte Datei index.html entspricht Dabei 4493 Bytes, diese wurde direkt, also ohne weiteren Proxy, geladen. Der Server ist wieder ::1 und der Content-Type ist text/html. Das sind schon viele Informationen, die einem bei einer Fehlersuche sehr gut helfen können.

Eine weitere Logdatei vom Squid ist cache.log, diese enthält Fehler, die der Proxy bereits entdeckt hat oder auf die er gestoßen ist, z.B. eine falsch codierte URL oder ähnliches.

Beim Webserver Apache gibt es ähnliche Dateien, hier heißen sie ebenfalls access.log Das sind schon viele Informationen, die einem bei einer Fehlersuche sehr gut helfen können.

Eine weitere Logdatei vom Squid ist cache.log, diese enthält Fehler, die der Proxy bereits entdeckt hat oder auf die er gestoßen ist, z.B. eine falsch codierte URL oder ähnliches.

Beim Webserver Apache gibt es ähnliche Dateien, hier heißt die eigentliche Logdatei ebenfalls access.log. Das Format ist aber ein wenig anders, es ist aber auch leicht anpassbar:

::1 - - [28/Apr/2010:22:36:08 +0200] "GET /index.html HTTP/1.1" 
  200 4463 "http://localhost/bilder/index.html" "Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.1.6) 
  Gecko/20091216 Iceweasel/3.5.8 (like Firefox/3.5.8)"

Als erstes steht hier der Client, die nächsten zwei Einträge fehlen, der zweite wäre der Username, sofern eine Authentisierung stattgefunden hätte. Danach folgt das Datum des Zugriffs, die aufgerufene URL nebst Protokoll- version. Als nächstes kommt der ausgelieferte Statuscode und die übertragene Bytezahl. Der Unterschied zum Squid-Log liegt an den unterschiedlichen Headern, der Proxy fügt noch eigene hinzu, wenn er die Daten zum Client überträgt. Die Größe ist also durchaus mit Vorsicht zu betrachten.

Danach folgt, von wo man auf diesen Link verwiesen wurde, das ist der Referer- Eintrag und ist mitunter recht interessant, wenn man einen Webserver betreibt. Mitunter findet man hier den Verweis, dass die Seite über z.B. Google gefunden wurde, dann sind oft auch die Suchbegriffe vorhanden. Manche Webserver werten diesen Eintrag aus und färben die Suchbegriffe dann auf der ausgelieferten Seite ein. It's not really magic!

Der letzte Eintrag gibt den User-Agent an, also über welches Programm zugegriffen wurde. Netscape hatte früher den Standard selber erweitert. Webserver haben daraufhin nachgesehen, ob ein Netscape-Browser zugreift um die erweiterten Modi dann zu verwenden. Da Netscape intern als der Mosaic Killer, dem freien Vorgänger von Netscape, bezeichnet wurde, nannte man den Netscape intern Mozilla. Alle modernen Webbrowser, die den neuen Netscape-Standard beherrschen, melden sich daher als Mozilla, so z.B. auch der IE.

Welcher Browser es wirklich ist, steht im Kommentar in runden Klammern. Wie man leicht erkennen kann, stehen hier auch Versionsinformationen, die es fast schon leicht machen, einen Client eindeutig zu identifizieren. Die Einträge in Zusammenhang mit den Header-Einträgen des Requests können fast zu einem eindeutigen Fingerprint zusammengefasst werden. In Verbindung mit JavaScript geht dies sogar noch viel präziser. Da helfen dann mitunter noch nicht einmal Anonymisierungstools wie Tor...

Die Fehlermeldungen des Webservers landen gewöhnlich in der Datei error.log, auch sie kann bei Problemen helfen, insbesondere, wenn es zu Fehlern mit CGI-Skripten kommt.

telnet

Das ist die einfachste Art einen Server oder Proxy direkt zu testen, das Programm dürfte auf fast allen Systemen bereits installiert sein. Dabei kennt das Programm zwei Modi: zeichen- und zeilenbasiertes Arbeiten. Wird mit einem anderen Port als Port 23/telnet verwendet, so schaltet das Programm automatisch in den Zeilenmodus: Es kann erst die ganze Zeile editiert werden, gesendet wird sie erst mit Betätigung der Return- Taste.

Man muss jedoch ein paar Dinge beachten: Bei Verbindung mit einem Proxy muss die vollständige URL übertragen werden, zum Server wird aber nur der relative Pfad gesendet. Den virtuellen Host, den man ansprechen möchte, wird über einen Host-Eintrag realisiert. Dieser ist mit HTTP/1.1 Pflicht, d.h. fehlt dieser, so gibt es eine Fehlermeldung vom Server. Bei HTTP/1.0 ist er optional, es gibt keinen Fehler. Man spricht dann aber mitunter mit dem falschen virtuellen Server!

Es gibt noch etwas: Bei HTTP/1.1 ist die Verbindung per default persistent, d.h. die Verbindung wird mit der Antwort auf den Request in der Regel nicht sofort geschlossen. Dazu muss dann ein Connection: close im Header mitgesendet werden. Bei HTTP/1.0 wird hingegen die Verbindung gleich geschlossen.

Hier ist es aber auch oft hilfreich anstelle eines GET einen HEAD zu senden. Der Inhalt ist oft per telnet schwer zu lesen, es ist dann oft reines HTML. Wenn man Pech hat, so erwischt man sogar gepackten Inhalt oder andere Daten, die binär sind. Das kann einen durchaus den Terminal zerschießen, er verhält sich dann recht seltsam und muss über einen Reset reaktiviert werden.

Für die Fehlersuche ist das aber oft nicht notwendig, da reichen meistens schon die Status-Codes, Redirect-Einträge, etc.

tcpdump

Wenn die Logdateien nicht ausreichen um den Fehler zu finden und ein telnet zu einfach ist, weil diverse Headerfelder und/oder Fähigkeiten nicht ohne viel Aufwand simuliert werden können, kann man noch zum tcpdump oder die modernere Variante wireshark greifen. Ich rate in der Regel immer zu einem tcpdump mit schreiben in eine Datei. Diese kann auch von wireshark gelesen werden, es sind dazu dann aber auch keine privilegierten Rechte mehr nötig.

Schwierig bei einem tcpdump ist es, die richtigen Filter zu setzen, so dass man nur den interessanten Verkehr mitschneidet. Auf Webservern und Proxys ist in der Regel viel Verkehr...

Bei Proxys kann es hilfreich sein, entweder nur auf die Client-IP-Adresse zu filtern oder bei Problemen mit einem bestimmten Webserver just diesen. Bei Webservern sollte man ebenfalls nur nach dem Client mit Problemen filtern. Das erleichtert die Fehlersuche durchaus, z.B. kann mit

   tcpdump -i eth0 -s 0 -w /var/tmp/dump host 192.168.1.17

explizit aller Netzwerkverkehr vom Client 192.168.1.17 auf dem Interface eth0 mitgeschnitten werden. Die Option -s 0 gibt die maximale Größe der zu protokollierenden Pakete an, die Null bewirkt dabei ein setzen des Limits auf das Maximum: Mann möchte alles mitschneiden. Wird der Wert Null nicht akzeptiert, manche tcpdump-Versionen machen das, so sollte man bei Ethernet den Wert 1514 annehmen, bei Gbit-Ethernet mit Jumbo-Frames auch noch deutlich darüber hinaus. 1500 ist die MTU der Netzwerkschnittstelle, die 14 Extra-Bytes beziehen sich auf den Ethernetheader, der wird auch noch mitgeschnitten. Am Ende eines Ethernetframes folgen zwar noch 4 Bytes, die sind aber ohne Relevanz hier.

Über das Schlüsselwort port kann auch der Port angegeben werden, wenn man den Client und den Port angeben will, so erfolgt die Verknüpfung über ein and, z.B.:

   tcpdump -s 0 -w /var/tmp/dump host 192.168.1.17 and port 3128

Über die Option -w kann eine Datei angegeben werden in der die Daten dann geschrieben werden. Anstelle der Option -i für das Interface, ohne diese Angabe wird die erste Schnittstelle verwendet, kann mit -r auch aus einer Datei stattdessen gelesen werden. Zum einfachen Lesen bietet sich etwas wie

   tcpdump -n -r /var/tmp/dump -X -v | less

an. Die Option -n verhindert dabei, dass die IP-Adressen via DNS aufgelöst werden, die -v ist für verbose, -X gibt den Inhalt in Hex-Codes aus und am Rande die ASCII-Zeichen als ASCII, die anderen als Punkt.

Da man meistens nur den Request, die Header-Felder und Antworten haben möchte, hilft oft das Programm strings. Ein strings /var/tmp/dump liefert alle ausgebbaren Zeilen aus und ignoriert den Rest. Dabei müssen per default 4 ASCII-Zeichen in Folge vorhanden sein um dann ausgegeben zu werden. Diesen Wert kann man über die Option -n verändern.

LiveHTTPHeaders

Dieses Firefox-Plugin ermöglicht es im Client die Headerfelder aufzuzeichnen, das ist ebenfalls eine sehr einfache und elegante Hilfe bei der Fehlersuche. Allerdings funktioniert dies zum einen nur auf dem Client und in diesem Fall nur mit dem Firefox, sofern die Erweiterung auch installiert ist.

Vermutlich gibt es für andere, neuere Browser ähnliche Erweiterungen. Für Tools wie wget oder lynx wird es diese wohl kaum geben. Die Erweiterung hat aber den immensen Vorteil, dass man die Header ohne Root-Rechte mitlesen kann. Das ist bei tcpdump z.B. in der Regel nicht der Fall.

RFCs

Last but least sollte man die RFCs nicht vergessen. Hier steht, wie etwas richtig funktionieren soll. Bei Streitfragen ist es immer gut, wenn man weiß, was im RFC steht. Für HTTP ist hier noch immer RFC-2616 relevant. Es gibt aber durchaus Erweiterungen oder auch spezielle RFCs zu Themen wie z.B. MIME.

Die RFCs sind in der Regel über

   http://www.rfc-editor.org/

zu finden.

Fazit

Bislang haben wir recht umfangreich über HTTP gesprochen, beginnend mit einem kurzen Ausflug in die Historie gefolgt von einem allgemeinen Blick auf das HyperText Transfer Protocol. Auch die Frage, wie eine URL aussieht, wie ein vollständiger Request aufgebaut ist sowie welche Request-Methoden existieren, haben wir besprochen.

Ebenso haben wir uns den Antworten gewidmet, hier insbesondere den vielen Statuscodes. Anschließend widmeten wir uns einem Ausflug in die Welt der Authentisierung und der möglichen Verfahren. Diese sind auch für Proxys interessant, werden sie dort doch auch verwendet, es hat auch wenig Sinn, wenn man das Rad neu erfindet.

Langweilig und doch interessant waren die zahlreichen Headerfelder. Es gibt hier doch einige Felder und Werte, die den meisten vermutlich nicht bekannt waren, wie das Protokoll als solches auch. Die Felder teilen sich im Prinzip in drei Gebiete auf: allgemeine Felder, die sowohl im Request als auch in der Antwort auftauchen dürfen, als auch Felder, die nur im Request und auch Felder, die nur in der Antwort auftauchen dürfen.

Manche Felder haben identische Namen aber unterschiedliche Bedeutungen, je nach Art der Verwendung. Manche Felder werden auch doppelt verwendet, andere sind wiederum nicht offiziell, aber etabliert.

Den Cookies haben wir auch einen genaueren Blick spendiert, sind sie doch sagenumwoben, manche behaupten gar, sie seien gefährlich. Im Grunde sind sie jedoch völlig harmlos. Sie können aber zum Verfolgen von Verbindungen verwendet werden, wenn z.B. auf den meisten Seiten ein Link zum gleichen Server eingebettet ist. Das ist bei DoubleClick oft der Fall, von daher schadet es nicht, wenn man die Bedeutung kennt. Damit kann dann jeder selber abschätzen, in wie weit er den Cookies trauen mag oder nicht.

Ferner haben wir auch ein paar Überlegungen zur Performance-Optimierung von Webzugriffen gemacht. Da gibt es durchaus einige Bereiche über die ein Gewinn erbracht werden kann. Aber nicht alle Verfahren sind immer die richtigen.

Danach ging es dann weiter zu den Webproxys und Webcaches, diese sind oft in einem Programm vereint. Dabei ging es sowohl um die Funktion als auch um den Sinn und Einsatzbereich. Ein besonderes Augenmerk galt dabei dem Programm Squid, es ist eines der ältesten, weitverbreitesten und bekannten Programmen in diesem Bereich. Squid ist sowohl frei als Open Source zu erhalten, als auch im Internet gut dokumentiert sowie diskutiert.

Eine der Stärken liegt dabei in den Filtermöglichkeiten, aber auch den Zugriffsregelungen sowie dem Cashing der Daten. Auch hier gibt es einige Dinge die man beachten oder bedenken sollte.

Den Abschluss bildeten dabei die Konfiguration der Proxys im oder für den Client, sei es direkt, via PAC-Datei oder über WPAD. Das Ende waren dann allgemeine Tipps zur Fehlersuche bei Problemen mit Webproxys oder Webservern.

Damit haben wir natürlich nicht alles rund um HTTP und Proxys besprochen, wohl aber das wichtigste. Ich denke es ist auch genug dabei gewesen um sich selber ein Bild der Lage zu verschaffen, zu verstehen, wie HTTP funktioniert, wann, wo und wie Proxys zum Einsatz kommen oder kommen sollten und wie man selber Fehler erkennen sowie verstehen kann. Ansonsten sollte es aber jetzt aber auch leicht sein, sich selber den "Rest" für ein aktuelles Problem, was hier nicht angesprochen wurde, im Internet zu verschaffen.

Von den höheren Protokollen dürfte HTTP derzeit eines der wichtigsten im Internet sein, man kann es mit (E)SMTP als die zwei essentiellen Protokolle bezeichnen, die das Internet so beliebt gemacht haben.

Bedroht ist HTTP, im Gegensatz zu SMTP durch Spam, zum Glück noch nicht. Der Standard ist mittlerweile auch schon älter und kaum verändert worden. Das trifft allerdings nicht auf die übertragenen Daten zu, HTML hat da durchaus seine Probleme. Aber auch diese sind mittlerweile einigermaßen im Griff. Sie waren massiv durch den Browser-Krieg, IE gegen Netscape, bedroht. Da hatte jeder Hersteller seine eigenen, natürlich nicht standardisierten und auch nicht kompatiblen Erweiterungen ins Spiel gebracht. Das dürfte auch ein Grund sein, warum der IE6 noch immer einen nennenswerten Marktanteil hat. Aber mittlerweile haben sich die Standards angenähert, viele der Erweiterungen sind im Standard eingeflossen und neuere Browser halten sich nun auch verstärkt daran.

Diesen Punkt haben wir hier aber explizit ausgeklammert: HTML.

Die Beschreibungssprache für Webinhalte ist auch ein Standard, dieser liegt aber nicht in Form eines RFCs vor. Vielmehr gibt es hier ein W3C- Konsortium, welches über den Standard wacht und neue verabschiedet. Das ist aber eine ganz andere Geschichte.

Ende.

Halt, noch nicht ganz.

Wir können noch ein Gesamt-LUG-Erding-Fazit ziehen. Wenn wir nun den Blick einmal zurück wenden, von Zeit zu Zeit sollte man so etwas tun, so kann man feststellen, dass wir eigentlich alle wichtigen Protokolle, die das Internet betreffen, abgehandelt haben.

Da wären die unteren Protokolle wie Ethernet, IP, ICMP, TCP, UDP, etc. als auch die höheren Anteile: FTP, DNS, SMTP und nun HTTP. Damit haben wir eigentlich alles was wichtig ist, abgehandelt. Bereiche, die wir nur sporadisch an- aber nie besprochen haben, sind z.B. TLS oder IPsec. Die fehlen in der Tat. Aber diese sind alles andere als einfach, um nicht zu sagen, komplex und schwer verständlich, sie spielen im Alltag auch eine eher untergeordnete Rolle. Sie sind extrem wichtig, aber die meisten werden damit nie direkt in Verbindung kommen.

Somit gesehen, sind wir netzwerktechnisch eigentlich vollständig, oder fehlt einem hier noch etwas?

Jeder sollte eigentlich nun in der Lage sein, das Internet zu verstehen, wie alles zusammenhängt und was alles zusammengehört. Man sollte nun jedem Pseudowissenschaftler (gelegentlich auch als Politiker inkarniert) widersprechen können, sofern man den Drang verspürt. Auch jede noch so gut ins Rennen gechickte Lüge dürfte nun leicht entlarvbar sein. Nicht jeder noch so redebegabter Consultant kann uns nun hinters Licht führen!

Nun können wir uns dann dem nächsten Projekt widmen, was immer das auch sein mag...

Dirk Geschke, dirk@lug-erding.de