HTTP Server
« | 19 Feb 2022 | »Mit der Entwicklung eines eigenen kleinen HTTP Servers bin ich damals vor 9 Jahren in die Embedded-Welt eingestiegen.
Auch im GATE Projekt befindet sich eine adäquate neue rein auf C aufbauende Implementierung, die mir die Verwaltung von MicroServices erleichtern soll.
Challenge: HTTP
HTTP hat deshalb einen solchen Reiz, weil man nur einen Webbrowser braucht, um
auf ein System zugreifen zu können.
Nicht grundlos haben heute auch fast alle Mikrocontroller bereits eine
HTTP-Server Bibliothek in ihrem Basisumfang.
Nachdem es bereits zahlreiche Implementierung gibt, stellt sich die Frage
Warum tue ich mir eine Bit-für-Bit Eigenimplementierung überhaupt an?
Und die Antwort ist wie so oft:
Keine der mir bekannten Bibliotheken ist aus meiner Sicht “unabhängig” genug,
als dass ich sie mit den Zielen des GATE-Projektes problemlos vereinen könnte.
Und das fängt leider bei deren GPL-Variant an.
Und während Windows und Linux halbwegs gut abgedeckt sind, machen der HTTPS Zwang eine Portierung auf Windows CE, BSD oder ganz aktuell EFI unmöglich. Und Mikrocontroller Lösungen sind wieder auf deren Hardware zugeschnitten, womit reguläre Desktops und Server nicht bedient werden können.
Umsetzung
Bevor ich überhaupt an Dinge wie HTTP/2 denke, bleibe ich mal bei HTTP/1.0 und HTTP/1.1.
Da ist die Sache theoretisch noch einfach:
- Man öffnet einen Server-Socket
- Nimmt Verbindungen an und scannt nach
\r\n\r\n
um das Ende eines HTTP Headers festzustellen. - Dann parst man die Headerzeilen und entscheidet, ob weiterer Content gelesen werden muss.
- Ist der HTTP Request vollständig entgegengenommen, darf ein beliebiger Callback die Daten verarbeiten.
- Das Ergebnis wird in einen HTTP-Response Header gepresst und danach darf ein entsprechender Response Stream an den Client zurückgeschickt werden.
Der Teufel steckt aber wie so oft im Detail:
Content-Type: Keep-Alive
- In einer Socket Session können mehrere HTTP Requests aneinander gereiht
sein. Man braucht also eine gute State-Engine, die bei
Keep-Alive
nach der Abwicklung des ersten Requests einen weiteren durchführt, ohne dass Daten versehentlich zwischen beiden geteilt werden. - HTTP Pipelining macht zusätzlich erforderlich, dass gelesene Daten nicht sofort bearbeitet werden, sondern bis zur Bearbeitung des nachfolgenden Requests zwischengespeichert bleiben.
- In einer Socket Session können mehrere HTTP Requests aneinander gereiht
sein. Man braucht also eine gute State-Engine, die bei
Content-Encoding
- An allen Stellen darf Kompression eingesetzt und der Client darf darüber entscheiden, ob der Server ihm auch so antworten dürfen soll.
Transfer-Encoding
chunked
: So können sowohl Requests als auch Responses in Stückchen zerteilt werden. Das ist super für’s “Streaming”, aber mühsam zu implementieren.deflate
,gzip
: Tritt in Konkurrenz zuContent-Encoding
Accept-Ranges
- Abgebrochene Downloads fortsetzen zu können, ist ein Feature aus den 90ern, dass man auch nicht einfach weglassen sollte.
HTTP CONNECT
undUpgrade: websocket
- Beide sollte man entweder “korrekt” abwehren oder “standardkonform” umsetzen.
- … und vieles mehr.
Fazit
Man kommt also sehr schnell in jede Menge von Fallkombinationen, deren Validität schwieriger festzustellen ist und die auch komplex bei der Umsetzung sind.
However … wenn ich bedenke, wie mühsam es bei anderen Projekten war, genau diese unterschiedlichen Fälle mit Fremdbibliotheken richtig abzudecken, dann keimt um so mehr Motivation auf, es trotzdem selbst zu implementieren.
Denn gerade diverse Encoding-Kombinationen lassen sich auch von großen “professionellen” Bibliotheken nicht immer korrekt abbilden und da wird es dann nochmal schwieriger, in den Fremdcode einzugreifen, oder aber man muss auf einige Features verzichten.
HTTP ist jedenfalls ein Muss, und darf daher nicht fehlen.