2 points par GN⁺ 2024-09-26 | 1 commentaires | Partager sur WhatsApp

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 avec 411 Length Required pour inciter le client à renvoyer avec Content-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) :

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

 
GN⁺ 2024-09-26
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

    • Beaucoup recommandent d’utiliser un proxy inverse sans raison précise en matière de sécurité ou de performances
    • Il s’interroge sur la nécessité réelle d’un proxy inverse
  • Serveur web C développé maison : il a créé un serveur web en C qui a servi à exploiter des sites commerciaux

    • Il a géré beaucoup de trafic avec 128 Mo de RAM et 1 CPU
    • Il mentionne qu’Internet était moins hostile il y a 20 ans
    • Les bots jouent un excellent rôle de fuzzer, mais un vrai fuzzing reste nécessaire
  • La satisfaction de construire un service : il trouve très satisfaisant de bâtir des services de base avec les API système

    • Il est surpris des hautes performances de la fonction poll()
    • Les fonctions par connexion, les structures associées et les tableaux lui rappellent nginx, redis et memcached
    • Excellent travail
  • 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

    • Il intègre la gestion des certificats ACME, Pgsql, curl, les WebSocket et d’autres fonctionnalités
    • Il permet de construire et d’exécuter des modules en mélangeant Lua/Python et C
  • Partage d’un lien intéressant : l’instance althttpd de sqlite.org traite plus de 500 000 requêtes HTTP par jour

    • Elle sert 200 Go de contenu sur un Linode à 40 $/mois
    • 19 % des requêtes HTTP accèdent au dépôt de code source Fossil via CGI
  • Le plaisir de créer ses propres outils : il en a assez de l’idée selon laquelle tout doit être « battle-tested »

    • Les bugs, ça se corrige
  • 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

    • Avec stockage immuable, réduction des privilèges et impossibilité d’accéder aux certificats TLS, entre autres
  • 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

    • Il s’interroge sur l’impact des fonctionnalités inutiles des logiciels sur les performances
    • Il félicite le développeur