Auf HN vorgestellt: Website-Hosting mit einem C-Webserver
(github.com/cozis)Die Technologie meines Blogs
Dieser Webserver ist ein minimalistischer Webserver, der dafür entwickelt wurde, meinen Blog zu hosten. Er wurde von Grund auf robust gebaut, sodass er dem öffentlichen Internet standhalten kann. Ein Reverse Proxy ist nicht erforderlich. Eine funktionierende Demo gibt es unter http://playin.coz.is/index.html. Ich habe auf Reddit dazu aufgerufen, ihn zu hacken, und dabei Gigabytes an unterhaltsamen und bösartigen Request-Logs gesammelt. Einen Teil davon habe ich in attempts.txt gespeichert und werde später aus Spaß noch mehr durchsehen.
Aber ... warum?
Ich baue gern meine eigenen Werkzeuge und habe es satt, ständig zu hören, alles müsse erst „battle-tested“ sein. Was, wenn es crasht? Bugs kann man beheben.
Spezifikationen
- Nur für Linux
- Implementiert HTTP/1.1, Pipelining und Keep-Alive-Verbindungen
- HTTPS-Unterstützung (mit BearSSL bis TLS 1.2)
- Minimale Abhängigkeiten (libc und BearSSL bei Verwendung von HTTPS)
- Konfigurierbare Timeouts
- Access-Logs, Crash-Logs, Log-Rotation und Begrenzung der Festplattennutzung
- Kein
Transfer-Encoding: Chunked(antwortet mit411 Length Required, damit der Client erneut mitContent-Lengthsendet) - Single-Core (soll geändert werden, wenn ich einen besseren VPS bekomme)
- Kein statisches Datei-Caching (noch nicht)
Benchmarks
Der Schwerpunkt dieses Projekts liegt auf Robustheit, aber langsam ist es keineswegs. Ein einfacher Vergleich mit nginx (statischer Endpoint, beide Single-Thread, Limit von 1K Verbindungen):
-
(blogtech)
$ wrk -c 500 -d 5s http://127.0.0.1:80/hello- Durchschnittliche Latenz: 6.66ms
- Requests/s: 76974.24
- Transfer/s: 6.09MB
-
(nginx)
$ wrk -c 500 -d 5s http://127.0.0.1:8080/hello- Durchschnittliche Latenz: 149.11ms
- Requests/s: 44227.78
- Transfer/s: 8.27MB
nginx-Konfiguration:
worker_processes 1;
events {
worker_connections 1024;
}
http {
server {
listen 8080;
location /hello {
add_header Content-Type text/plain;
return 200 "Hello, world!";
}
}
}
Build und Ausführung
Standardmäßig wird der Server nur mit HTTP gebaut:
$ make
Dieser Befehl erzeugt die ausführbaren Dateien serve (Release-Build), serve_cov (Coverage-Build) und serve_debug (Debug-Build). Der Release-Build lauscht auf Port 80, der Debug-Build auf Port 8080.
Um HTTPS zu aktivieren, muss BearSSL geklont und gebaut werden:
$ mkdir 3p
$ cd 3p
$ git clone https://www.bearssl.org/git/BearSSL
$ cd BearSSL
$ make -j
$ cd ../../
$ make -B HTTPS=1
Es werden dieselben Binärdateien erzeugt, aber sichere Verbindungen sind dann über Port 443 (Release) oder 8081 (Debug) möglich. Die Dateien cert.pem und key.pem müssen im selben Verzeichnis wie die Binärdatei liegen. Um Namen und Speicherort zu ändern, passe Folgendes an:
#define HTTPS_KEY_FILE "key.pem"
#define HTTPS_CERT_FILE "cert.pem"
Um HTTPS lokal zu testen, erstelle ein selbstsigniertes Zertifikat (und den privaten Schlüssel):
openssl genpkey -algorithm RSA -out key.pem -pkeyopt rsa_keygen_bits:2048
openssl req -new -x509 -key key.pem -out cert.pem -days 365
Verwendung
Der Server liefert statische Inhalte aus dem Ordner docroot/ aus. Um das zu ändern, passe die Funktion respond an:
typedef struct {
Method method;
string path;
int major;
int minor;
int nheaders;
Header headers[MAX_HEADERS];
string content;
} Request;
void respond(Request request, ResponseBuilder *b) {
if (request.major != 1 || request.minor > 1) {
status_line(b, 505); // HTTP Version Not Supported
return;
}
if (request.method != M_GET) {
status_line(b, 405); // Method Not Allowed
return;
}
if (string_match_case_insensitive(request.path, LIT("/hello"))) {
status_line(b, 200);
append_content_s(b, LIT("Hello, world!"));
return;
}
if (serve_file_or_dir(b, LIT("/"), LIT("docroot/"), request.path, NULLSTR, false))
return;
status_line(b, 404);
append_content_s(b, LIT("Nothing here :|"));
}
Hier können Endpoints hinzugefügt werden, indem auf das Feld request.path verzweigt wird. Der Pfad ist lediglich ein Slice des Request-Buffers. Die URI wird nicht geparst.
Tests
Ich lasse den Server regelmäßig mit valgrind und Sanitizern (Address, Undefined) laufen und bearbeite ihn mit wrk. Außerdem ergänze ich in tests/test.py automatisierte Tests, um die Konformität mit der HTTP/1.1-Spezifikation zu prüfen. Ich hoste damit meine Website und poste sie hier und da, um weiter Last zu erzeugen. All die Bots im Internet, die verwundbare Websites scannen, sind hervorragende Fuzzer.
Bekannte Probleme
- Der Server antwortet HTTP/1.0-Clients mit HTTP/1.1
Beiträge
Ich arbeite hauptsächlich im DEV-Branch und merge gelegentlich nach MAIN. Wenn du einen Pull Request öffnest, ist es einfacher, DEV als Ziel zu wählen.
Zusammenfassung von GN⁺
- Dieses Projekt ist ein Webserver, der auf minimale Abhängigkeiten und Robustheit abzielt.
- Er unterstützt HTTP/1.1 und HTTPS und bietet verschiedene Logging-Funktionen sowie konfigurierbare Timeouts.
- Die Benchmark-Ergebnisse zeigen schnellere Antwortzeiten als nginx.
- Er ist so konzipiert, dass Entwickler Freude daran haben, ihre eigenen Werkzeuge zu bauen und Bugs zu beheben.
- Ähnliche Projekte mit vergleichbaren Funktionen sind Nginx und Apache HTTP Server.
1 Kommentare
Hacker-News-Kommentare
Kein Reverse Proxy nötig: Mit Jetty ließ sich die App ohne Reverse Proxy problemlos im Internet bereitstellen
Selbst entwickelter C-Webserver: Es wurde ein C-Webserver gebaut, der für kommerzielle Websites im Einsatz war
Zufriedenheit beim Aufbau von Services: Es ist sehr befriedigend, grundlegende Services mit System-APIs zu bauen
poll()istVorstellung eines kleinen Projekts: Ein interessantes Projekt, das in der Freizeit begonnen wurde
Empfehlung für das Kore-Framework: Wenn es unangenehm ist, den öffentlich exponierten Teil einer C-App selbst zu schreiben, wird das Kore-Framework empfohlen
Teilen eines interessanten Links: Die althttpd-Instanz von sqlite.org verarbeitet täglich mehr als 500.000 HTTP-Anfragen
Freude am Bau eigener Tools: Es besteht Ermüdung gegenüber der Meinung, dass alles „battle-tested“ sein müsse
Vortrag auf dem Chaos Communication Congress: Es wird an einen Vortrag über einen in C geschriebenen Blog-/Webserver mit Sicherheitsfunktionen erinnert
Stabile Website: Eine Website, die nicht abstürzt, selbst wenn sie auf der Startseite erscheint
Zurück zu den Grundlagen: Der Ansatz gefällt, zu den Grundlagen zurückzukehren und nur das Nötige zu verwenden