af83

Écrire un service web en C

Récemment Bruno expliquait comment utiliser Go pour écrire des services web. Go est un langage puissant, conçu autant pour les performances, que pour faciliter la vie des développeurs. Je vous conseille de relire ses deux premiers articles sur le sujet, le premier vous donnera les bases, et le second vous donnera envie d'en savoir plus.

Néanmoins, en y repensant, je me suis demandé à quoi pourrait ressembler un service web équivalent écrit en C. Faire du web en C est une idée saugrenue et dangereuse dans (presque) tous les cas. Enfin, si le langage lui même est moins amical que Go, il a derrière lui les années de développeurs barbus, et il lui reste encore de beaux jours… :)

Il y a mille manières de faire du web en C. Pour aller au plus vite, j'ai pris le parti d'utiliser Tofu d'Alessandro Ghedini. Le projet est récent, purement expérimental, mais démontre le type d'API que peut proposer une bibliothèque C pour des applications web.

Tofu est un projet qui me semble intéressant dans la mesure où il supporte plusieurs backends, pour n'en citer que deux :

  • zmq, qui lui permet de tourner derrière un certain mongrel2,
  • ou, plus simplement, la libevent, qu'on va sagement utiliser pour notre petit PoC.

Dans d'autres contextes que celui-ci, ce ne serait donc pas une brique monolithique déterminant toute une architecture, mais bien un élément qui pourrait s'intégrer avec d'autres. C'est un détail qui mérite d'être souligné.

Nous allons construire un service HTTP simplissime. Ce dernier va répondre sur une URL du type http://server/hello/af83, avec le JSON {"hello": "af83"}.

Commençons par créer un fichier hello.c. Pour utiliser les fonctions de Tofu, on doit inclure le header tofu.h, et pour parler JSON sans bégayer, jansson.h.

#include <tofu.h>
#include <jansson.h>

int main(int argc, char ** argv) {
    return 0;
}

Pour récupérer la dernière version de Tofu, un rapide git clone https://github.com/AlexBio/Tofu.git devra faire l'affaire. Après quoi il faudra suivre le README de la bibliothèque pour la compiler avec vos petites mimines, et finalement soit l'installer sur votre système, soit jouer avec les flags -I et -L de gcc pour linker votre programme…

C'est là qu'un outil comme go get nous faciliterait bien la vie !

Pour initialiser Tofu, dans notre fonction main, on fera :

// Initialise le serveur HTTP sur le port TCP 8080.
char *opts[] = { "0.0.0.0", "8080" };
tofu_ctx_t *ctx = tofu_ctx_init(TOFU_EVHTTP, opts);

// Lance le serveur HTTP, et bloque le programme.
tofu_loop(ctx);

Si vous compilez votre programme en l'état, vous obtiendrez un serveur HTTP des plus inutiles qui ne répondra que par des erreurs 404 quelle que soit l'URL accédée. C'est pas encore ça.

Tofu permet d'assigner des callbacks à des URL statiques, ou dynamiques. Dans cet exemple, on aimerait, lorsqu'on accède à /hello/af83, obtenir le JSON {"hello": "af83"}. On peut d'ores et déjà relier l'URL attendue à une tierce fonction en utilisant tofu_handle_with :

// à placer *avant* l'appel bloquant à tofu_loop, hein ;)
tofu_handle_with(ctx, GET, "/hello/:name", hello, 0x0);

Pour répondre sur cette URL, il nous faut définir le callback hello mentionné dans le code. Il doit renvoyer un pointeur du type tofu_rep_t, et accepter deux arguments :

// voilà un callback "hello" minimal...
tofu_rep_t *hello(tofu_req_t *req, void *argp) {
    tofu_rep_t *rep = tofu_rep_init();
    return rep;
}

Notre serveur HTTP ne renvoie plus d'erreur 404 sur /hello/af83, mais répond avec rien, ce qui est bien mais pas top. Pour bien faire, on va récupérer le paramètre name qu'on a créé avec tofu_handle_with : Tofu a le bon goût de fournir tofu_param pour ça.

char *param = tofu_param(req, "name");

On pourrait alors construire une réponse JSON « à la main » avec les informations qu'on a récupérées. Mais pourquoi se priver d'utiliser la lib Jansson qui sait échapper les caractères spéciaux, et construire du JSON propre ?

// `obj` est un objet JSON vide, soit : {}
json_t *obj = json_object();

// obj a désormais une clef "hello", qui reprend la valeur de param.
// Par exemple: {"hello": "af83"}
json_object_set(obj, "hello", json_string(param));

// `json_dumps` encode `obj` pour en faire une chaîne de caractères.
char *reply = json_dumps(obj, JSON_ENSURE_ASCII);

Il nous reste à mettre tout ça en forme pour répondre, à l'aide de tofu_head, et de tofu_write. L'exemple complet est disponible sur un Gist. Si vous êtes assez curieux pour y jeter un œil, vous vous rendrez compte qu'en à peine quelques lignes de C, on peut faire pas mal de choses…

Ce court billet pourrait illustrer une question que se posent souvent les développeurs (du moins on l'espère) : est-ce que j'utilise le bon outil pour ce boulot ?

Le C par exemple est un langage formidable pour écrire des programmes qu'on dit de « bas-niveau » (un serveur DNS, un kernel, une machine virtuelle, …), Go en est un autre. Pour du web en revanche, vous serez beaucoup mieux servis par des langages dynamiques comme Ruby, ou Javascript. S'il est donc facile (et surtout divertissant :p) d'écrire des petites applications web comme celle-ci en C, ça n'est que rarement souhaitable.

blog comments powered by Disqus