Vu sur HN : héberger un site web avec un serveur web en C
(github.com/cozis)Technologie de mon blog
Ce serveur web est un serveur minimal conçu pour héberger mon blog. Il a été construit dès le départ pour être suffisamment robuste pour tenir sur l’Internet public. Aucun reverse proxy n’est nécessaire. Vous pouvez le voir fonctionner réellement sur http://playin.coz.is/index.html. J’ai demandé sur Reddit qu’on essaie de le pirater, ce qui m’a permis de collecter des gigaoctets de logs de requêtes amusantes et malveillantes. J’en ai conservé une partie dans attempts.txt, et j’en examinerai davantage plus tard pour le plaisir.
Mais… pourquoi ?
J’aime créer mes propres outils, et j’en ai assez d’entendre que tout doit être « battle-tested ». Et s’il plante ? Les bugs, ça se corrige.
Spécifications
- Linux uniquement
- Implémente HTTP/1.1, le pipelining et les connexions keep-alive
- Prise en charge de HTTPS (avec BearSSL, jusqu’à TLS 1.2)
- Dépendances minimales (libc et BearSSL si HTTPS est utilisé)
- Timeouts configurables
- Logs d’accès, logs de crash, rotation des logs, limite d’utilisation disque
- Pas de
Transfer-Encoding: Chunked(répond avec411 Length Requiredpour inciter le client à renvoyer avecContent-Length) - Monocœur (cela changera quand j’aurai un meilleur VPS)
- Aucun cache de fichiers statiques (pour l’instant)
Benchmarks
Même si ce projet met l’accent sur la robustesse, il est loin d’être lent. Comparaison simple avec nginx (endpoint statique, tous deux en mono-thread, limite de 1K connexions) :
-
(blogtech)
$ wrk -c 500 -d 5s http://127.0.0.1:80/hello- Latence moyenne : 6.66ms
- Requêtes/s : 76974.24
- Transfert/s : 6.09MB
-
(nginx)
$ wrk -c 500 -d 5s http://127.0.0.1:8080/hello- Latence moyenne : 149.11ms
- Requêtes/s : 44227.78
- Transfert/s : 8.27MB
Configuration nginx :
worker_processes 1;
events {
worker_connections 1024;
}
http {
server {
listen 8080;
location /hello {
add_header Content-Type text/plain;
return 200 "Hello, world!";
}
}
}
Compilation et exécution
Par défaut, la compilation du serveur est en HTTP uniquement :
$ make
Cette commande génère les exécutables serve (build de release), serve_cov (build de couverture) et serve_debug (build de debug). La build de release écoute sur le port 80, et la build de debug sur le port 8080.
Pour activer HTTPS, il faut cloner et compiler BearSSL :
$ mkdir 3p
$ cd 3p
$ git clone https://www.bearssl.org/git/BearSSL
$ cd BearSSL
$ make -j
$ cd ../../
$ make -B HTTPS=1
Les mêmes exécutables sont générés, mais avec des connexions sécurisées possibles sur le port 443 (release) ou 8081 (debug). Les fichiers cert.pem et key.pem doivent être placés dans le même répertoire que l’exécutable. Pour changer le nom et l’emplacement, modifiez :
#define HTTPS_KEY_FILE "key.pem"
#define HTTPS_CERT_FILE "cert.pem"
Pour tester HTTPS en local, générez un certificat auto-signé (et sa clé privée) :
openssl genpkey -algorithm RSA -out key.pem -pkeyopt rsa_keygen_bits:2048
openssl req -new -x509 -key key.pem -out cert.pem -days 365
Utilisation
Le serveur sert du contenu statique depuis le dossier docroot/. Pour changer cela, modifiez la fonction respond :
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 :|"));
}
À partir de là, vous pouvez ajouter des endpoints en branchant sur le champ request.path. Le chemin n’est qu’une tranche du buffer de requête. L’URI n’est pas parsée.
Tests
J’exécute régulièrement le serveur avec valgrind et les sanitizers (address, undefined), et je le cible avec wrk. J’ajoute aussi des tests automatisés dans tests/test.py pour vérifier la conformité à la spécification HTTP/1.1. J’héberge mon propre site dessus et je le publie ici et là pour maintenir la pression. Tous les bots qui scannent sur Internet les sites web vulnérables font d’excellents fuzzers.
Problèmes connus
- Le serveur répond en HTTP/1.1 aux clients HTTP/1.0
Contribution
Je travaille principalement sur la branche DEV et je fusionne de temps à autre vers MAIN. Si vous ouvrez une pull request, ce sera plus simple de cibler DEV.
Résumé de GN⁺
- Ce projet est un serveur web visant des dépendances minimales et la robustesse.
- Il prend en charge HTTP/1.1 et HTTPS, et fournit diverses fonctions de log ainsi que des timeouts configurables.
- Les résultats de benchmark montrent des temps de réponse plus rapides que nginx.
- Il est conçu pour que les développeurs puissent prendre plaisir à créer leurs propres outils et à corriger les bugs.
- Des projets aux fonctionnalités similaires incluent Nginx et Apache HTTP Server.
1 commentaires
Avis Hacker News
Pas besoin de proxy inverse : avec Jetty, il a pu déployer des apps sur Internet sans proxy inverse et sans problème
Serveur web C développé maison : il a créé un serveur web en C qui a servi à exploiter des sites commerciaux
La satisfaction de construire un service : il trouve très satisfaisant de bâtir des services de base avec les API système
poll()Présentation d’un petit projet : il présente un projet amusant commencé pendant son temps libre
Recommandation du framework Kore : si écrire la partie exposée d’une app C est pénible, il recommande le framework Kore
Partage d’un lien intéressant : l’instance althttpd de sqlite.org traite plus de 500 000 requêtes HTTP par jour
Le plaisir de créer ses propres outils : il en a assez de l’idée selon laquelle tout doit être « battle-tested »
Conférence du Chaos Communication Congress : cela lui rappelle une présentation sur un blog/serveur web écrit en C avec des fonctions de sécurité intégrées
Site web stable : un site qui ne plante pas même lorsqu’il apparaît en page d’accueil
Retour aux fondamentaux : il aime cette approche qui revient à l’essentiel en n’utilisant que ce qui est nécessaire