La bibliothèque libevent est une bibliothèque très utile et puissante pour gérer une boucle d’événements, mais peu de personnes utilisent tout son potentiel.
La fonction principale de libevent est de fournir une gestion d'événements arrivant sur des descripteurs de fichiers ou de manière temporelle mais cette bibliothèque est plus riche que cela et elle sait gérer d'autres événements liés à des protocoles ou des besoins particuliers.
Nous allons ici présenter la mise en oeuvre de la bibliothèque puis nous intéresser à la gestion du protocole HTTP intégré à la libevent.
Introduction à Libevent
Libevent est une bibliothèque de notification d’événement. Son API fournit un mécanisme d'appel de callback lorsqu'un événement spécifique a lieu sur un descripteur de fichier ou lorsqu'un timeout est atteint. Libevent est destiné à remplacer la boucle d’événements trouvée dans les serveurs de réseau.
Une application a juste besoin d'appeler event_dispatch(), puis ajouter ou supprimer des événements dynamiquement sans avoir à modifier la boucle d'événements. On peut également utiliser libevent dans un contexte multi-threadé, en isolant chaque "event_base", ce qui permet à un seul thread à la fois d'y avoir accès. Cette bibliothèque a été portée sur la plupart des OS, ce qui la rend d'autant plus intéressante.
Voici un exemple d'enregistrement d'une callback dans boucle d'évènements :
/* On commence par créer notre boucle d'évènements */ struct event_base *ev_accept = event_base_new(); /* * Ensuite on associe à notre boucle le fd qu'il faudra surveiller, * les options de surveillance du fd, * une callback de la forme : short, void(*)(evutil_socket_t, short, void *), * et tout paramètre nécessaire au callback. */ event_new(&ev_accept, listen_fd, EV_READ|EV_PERSIST, my_callback, NULL); /* Il faut ensuite ajouter l'event à la boucle des évènements surveillés */ event_add(&ev_accept, NULL); /* On démarre ensuite la boucle. A noté qu'il existe plusieurs fonctions permettant de lancer la boucle d'évènements, celle-ci étant active jusqu'à la fin du programme. */ event_dispatch(ev_accept); /* Une fois que nous quittons le programme, nous supprimons les évènements ajoutés et nous libérons la mémoire. */ event_del(ev_accept); event_free(ev_accept);
Le principe est donc assez simple. On commence par associer une callback à un descripteur de fichier en définissant le type d'événement, ensuite on l'ajoute à la boucle d'événements puis on lance le tout.
Mais l'intérêt de cet article porte sur un autre aspect de libevent. Nous nous intéresserons ici au fait qu'elle offre une API complète permettant le support de nombreux protocoles, notamment l'HTTP dont nous parlerons juste après.
Un serveur web
Pour se servir du protocole HTTP nous passons par libev_http qui est intégré à libevent. L'intérêt d'une telle bibliothèque est de pouvoir communiquer avec une interface utilisant ce protocole à partir d'un système embarqué. Le fonctionnement est vraiment simple et l'API est complète, permettant d'écrire un message complet au format HTTP en seulement quelques lignes.
Le fonctionnement de base est exactement le même que celui de libevent, à savoir qu'on a un mécanisme d'appel à une callback qui se déclenche lorsqu'un événement spécifique a lieu. Cette fois c'est un événement sur la socket du serveur web qui déclenchera l'appel.
Voici un exemple de code assez simple afin d'écrire un message en http :
/* On doit toujours créer notre event base qui sera notre boucle d'évènements */ struct event_base *base; /* On doit ensuite créer une structure evhttp qui sera sera liée à notre event_base */ struct evhttp *http; /* On associe notre boucle d'évènements à notre structure evhttp */ http = evhttp_new (base); /* * On enregistre notre callback au format evhttp, à savoir : * static void send_document_cb (struct evhttp_request *req, void* arg) * La requête peut ensuite être analysée via une chaîne de caractères grâce à : * const char *uri = evhttp_request_get_uri (req); */ evhttp_set_gencb (http, send_document_cb, NULL);
Comme vous pouvez le voir, il est assez simple et aisé de gérer une boucle d'évènements complète.
Exemple complet
Nous allons maintenant voir un exemple qui reste simple mais qui met en place un exemple plus concret d'utilisation de cette bibliothèque.
Tout d'abord, voici les liens vers les fichiers présentant le code :
- Le programme : http_server.c
- La page web : http_server.html
Notre exemple se compose donc d'un fichier en html et d'un en langage C, illustrant de façon simple un envoi de paramètres (user/password) via une page html.
Ces données sont ensuite envoyées à une adresse que vous aurez définie (127.0.0.1:8080 dans le code html présenté) par une requête http de type POST. Le programme reçoit la requête HTTP, détecte le format "application/x-www-form-urlencoded" pour récupérer les champs du formulaire. Enfin le seul traitement sur ces données consiste ici à les afficher.
On envoie ensuite une réponse http afin de prévenir que nous avons bien tout reçu.
Cette bibliothèque fournit tous les éléments afin de récupérer la réponse au format http, en extraire les données intéressantes dans le cas de données de formulaires, construire une réponse http et l'envoyer.
La contrainte est la connaissance préalable de la forme du message. Par exemple, lorsque nous retirons l'entête du message, voici le contenu que nous pouvons obtenir (tiré de l'exemple fourni) :
id=Open&pass=Wide
Il faut connaître les noms des champs du formulaire (ici "id" et "pass") afin d'utiliser l'api fournie par libevent. Si ce n'est pas le cas, il restera la possibilité de parcourir tous les champs de la liste ou bien d'analyser le message manuellement. Cette dernière méthode restant valable pour l'utilisation d'autres formats (xml ou json par exemple).
Le rôle de cette bibliothèque se limite au protocole http et au décodage des URL (ainsi que des formulaires qui utilisent le même format). Du coté génération, seul le format http est implémenté avec les headers et le contenu, la génération de html ne fait pas partie de libev_http (ni même d'autre format comme xml ou json).
Conclusion
Libevent est une bibliothèque assez complète, très utile dès l'instant où un programme utilise de nombreux descripteurs de fichiers pour un select(). Elle est portable et "thread-safe", ce qui fait d'elle un choix plus qu'honorable.
On a pu voir ici un exemple de ses capacités grâce au protocole http avec la libev_http, mais elle permet de faire bien plus que cela !
Vous pouvez avoir plus d'infos sur leur site : http://libevent.org/.
Une documentation de l'API existe également ici : http://monkey.org/~provos/libevent/doxygen-2.0.1/index.html.