Développement Web en C++

En attendant MeetingCpp j’ai commencé cet article, car j’étais étonné qu’il n’y ait aucune session prévue au sujet du développement web en C++. Je le finalise ce soir en rentrant de cette magnifique conférence, afin de me donner le temps de faire de mes nombreuses notes de véritables articles sur les sujets abordés et discutés.

En effet on ne trouve pas des masses de tutoriaux, ni de solutions pour le développement web en C++, du fait de la prédominances des langages tels que Php, Spring, Django, Ruby on Rails etc. dans ce domaine.

J’aimerai présenter dans cet article une des solutions existantes en tant que framework de développement web, permettant de parvenir très rapidement à de bons résultats, comme on peut y être habitué avec CodeIgniter, Zend, Django ou Ruby on Rails, mais avec un gain de performance.

Pourquoi du développement web en C++ ?

Avant de rentrer dans des détails d’implémentation, il est possible de se poser la question pourquoi du dévéloppement web en C++ ? En effet avec la pléthore de langages et de frameworks existants destinés à ce problème, on pourrait penser que cela n’a pas d’autre intérêt que la préférence de ce langage.

En réalité, il y a trois raisons qui peuvent mener à choisir de développer une application web ou un site internet en C++ :

  • Hautes performances & haute disponibilité : Benchmark CppCms vs Java, C#, PHP
  • Réduction du nombre de serveurs, ayant pour conséquence la réduction des coûts et de l’impact sur l’environnement : HipHop for Php: Move Fast
  • Ajout d’une interface web (html ou web service) pour une base C++ existante

CppCms parmi d’autres solutions

J’ai eu le loisir de tester et de comparer différentes solutions (i.e. POCO HttpServer, CppCms, FastCgiQt, WebToolkit, QtWebApp, inetd…) et bien qu’il y ait différents avantages et inconvénients pour chaque solution, la librairie la plus flexible et la plus riche qu’il m’ait été donnée de trouver est CppCms.

CppCms est bel et bien un framework et non un système de gestion de contenu comme son nom souhaiterait l’indiquer, il est très semblable au framework Django en Python ou encore à CodeIgniter en PHP, n’ayant pas grand chose à envier à ces derniers en matières de fonctionnalités disponibles (i.e. Moteur de template statique/dynamique, systèmes de cache, json, jsonrpc, url mapping, génération/validation de forms HTML, modules multilingues/multiencodage, validation des entrées, fonctions cryptographiques/hash, gestion des sessions, gestion des vues, logging, accès aux bases de données avec cppdb, connexion avec apache/lighttpd/nginx via SCGI/FCGI ou HTTP…)

En addition le code et la documentation de CppCms est très claire, à tel point que Boost.Locale qui provient de CppCms a été accepté très rapidement.

Boost ou booster ?

CppCms peut laisser un peu sceptique au premier abord, car ce dernier contient une bibliothèque : booster, qui correspond à une petite partie de boost sur lequel le framework se base en addition à des extensions. Il est donc possible d’utiliser boost ou directement booster dans le code client ou de mélanger les deux sans que cela ne pose problème. Il y a des parties de booster qui font double emploi avec boost tel que booster::shared_ptr qu’il m’est impossible de justifier, mais d’autres qui sont très justifiées tel que booster::aio et qui se différencie de Boost.Asio par différents points abordés plus après.

CppCms : Télécharger & Installer

Je pars du principe que vous êtes dans un environnement unix, même si les étapes sont semblables sous windows.

  • Récupérez le code source : http://sourceforge.net/projects/cppcms/files/
  • Récupérez quelques dépendences :
    apt-get install libpcre3-dev zlib1g-dev libgcrypt11-dev libicu-dev python
  • Compilez :
    tar xf cppcms-1.0.2.tar.bz2
    cd cppcms-1.0.2/
    mkdir build
    cd build/
    cmake ..
    make -jX (Où X correspond à votre nombre de coeurs processeur + 1)
    sudo make install

Hello World

Nous allons pour ce premier article visant à présenter CppCms, implémenter l’infatigable “Hello World”.

Un design pattern que l’on retrouve de nos jours dans la quasi-totalité des frameworks webs est le MVC, utile dans le cas de sites webs, il permet de séparer les données ou la logique (i.e. le Modèle) de l’interface graphique (i.e.la Vue) à l’aide d’un glue code traitant les interactions utilisateurs (i.e. le Contrôleur).

Bien que pour ce premier “Hello World” nous n’allons pas utiliser cette organisation, CppCms ne fait pas exception et met à disposition le concept de contrôleur à l’aide de la classe mère cppcms::application, le modèle avec cppcms::base_content et les vues à l’aide du compilateur de fichier .tmpl : cppcms_tmpl_cc qui permet de mélanger C++ et le format de sortie : JSON, HTML…

Une cppcms::application synchrone simple

CppCms propose différents modèles de programmation, celui auquel un développeur web connaissant PHP, Django, Ruby on Rails ou Java est habitué est le modèle synchrone usant différents threads afin de traiter les requêtes reçues. C’est très simple à comprendre et scalable verticalement comme horizontalement via les systèmes de load balancing proposés avec FCGI ou SCGI (e.g. lighttpd mod_fcgi permet de définir plusieurs processus de gestion des requêtes, locaux ou distants).

Ici nous allons utiliser le serveur web intégré à CppCms en créant un petit projet organisé de la façon suivante:

hello_world/config.js :

hello_world/CMakeLists.txt :

Et dans le dossier src, le CMakeLists qui actuellement produit l’executable, hello_world/src/CMakeLists.txt

Et enfin le code de notre application, hello_world/src/SomeController.hpp :

  1. #ifndef SOMECONTROLLER_HPP
  2. #define SOMECONTROLLER_HPP
  3.  
  4. #include <cppcms/application.h>
  5. #include <cppcms/http_response.h>
  6. #include <cppcms/url_dispatcher.h>
  7.  
  8. class SomeController : public cppcms::application {
  9.     public:
  10.         SomeController(cppcms::service &srv) : cppcms::application(srv) {
  11.             dispatcher().assign("/hello",&SomeController::hello,this);
  12.             dispatcher().assign("/bye",&SomeController::bye,this);
  13.             dispatcher().assign(".*",&SomeController::redirect,this);
  14.         }
  15.  
  16.         void redirect() {
  17.             response().set_redirect_header("/hello");
  18.         }
  19.  
  20.         void hello() {
  21.             response().out() <<
  22.                 "<html>"
  23.                 "<body>"
  24.                 " <h1>Hello World</h1>"
  25.                 "</body>"
  26.                 "</html>\n";
  27.  
  28.         }
  29.  
  30.         void bye() {
  31.             response().out() <<
  32.                 "<html>"
  33.                 "<body>"
  34.                 " <h1>Bye</h1>"
  35.                 "</body>"
  36.                 "</html>\n";
  37.         }
  38. };
  39.  
  40. #endif

Afin de démarrer le service web et monter notre application à une url particulière il nous faut un point d’entrée, hello_world/src/main.cpp :

  1. #include <cppcms/applications_pool.h>
  2. #include <cppcms/service.h>
  3. #include <iostream>
  4.  
  5. #include <SomeController.hpp>
  6.  
  7. int main(int argc, char **argv) {
  8.  
  9.     try {
  10.         cppcms::service srv(argc,argv);
  11.         srv.applications_pool().mount(cppcms::applications_factory<SomeController>());
  12.         srv.run();
  13.     } catch(std::exception const &e) {
  14.         std::cerr << e.what() << std::endl;
  15.     }
  16.  
  17.     return 0;
  18. }

Configuration et mount()

Ici l’instance de cppcms::service que nous créons au sein de la fonction int main(int argc, char **argv) de l’application va justement définir sa configuration en fonction du fichier config.js que l’on passe à l’application. Ce fichier respecte un format JSON avec la possibilité en plus de commenter des lignes à l’aide de //.

Il est possible de configurer à l’aide de la clé “service” la façon dont CppCms doit servir des requêtes. Pour cet exemple nous resterons sur la configuration présente, mais pour une mise en production un petit changement vers fcgi ou scgi sera nécessaire (c.f. Configurer CppCms avec des serveurs webs).

Avec cette configuration, CppCms instancie un serveur http utilisant le nombre de threads donnés par worker_threads pour traiter les requêtes, il utilise ainsi la fabrique : cppcms::applications_factory() pour créer SomeController une fois par worker thread qui traite ce type de requêtes. La méthode mount() permet de définir sous quel chemin racine la cppcms::application sera servie, sans second paramètre, mount() utilise SomeController pour répondre à n’importe quelle requête entrante.

L’appel un peu plus loin à srv.run() lance le serveur HTTP ou FCGI/SCGI selon la configuration et démarre une event loop qui distribue les différentes requêtes au threads sur la base du reactor pattern (i.e. Il est possible de configurer quel backend utiliser via la clé “reactor”, avec l’une des valeurs suivantes : “default”, “select”, “poll”, “epoll”, “devpoll”, “kqueue”, cependant cela n’a rien de nécessaire sur les plateformes communes parce que default choisi toujours la meilleure option pour la plateforme en cours. Vous noterez que l’option iocp est manquante, cela résulte dans l’utilisation de select() sur windows).

Plutôt que des threads il est possible d’utiliser des processus pre-forkés en spécifiant la clé : worker_processes. Cela a deux principaux avantages : un crash d’une des instance de SomeController n’affecte que l’unique requête servie par ce dernier et sous la plupart des systèmes d’exploitations, au dessus un certain nombre de thread la gestion de ces derniers devient si complexe que leur coût en performance devient exponentiel (c.f. Scalable Network Programming, Or: The Quest For A Good Web Server (That Survives Slashdot)).

Cependant en restant sous un nombre réaliste de threads, la rapidité sera meilleure qu’avec fork, tout dépends de combien de clients simultanés doivent pouvoir être supportés et de la longueur du traitement de chaque requête, dans cet exemple nous n’utilisons que 10 threads.

Pour exécuter cet exemple sur votre pc, il vous suffit de taper les commandes suivantes :

Assignement d’url

Comme il est possible de le voir dans le fichier SomeController.hpp lors de l’instanciation de ce dernier il est possible de définir quelle méthode doit être appellée à quelle instance à l’aide du header : cppcms/url_dispatcher.h.

Ce dernier ne se limite pas à cette utilisation simpliste, en effet le premier paramètre est une expression régulière, il est ainsi possible de mapper des éléments capturés de l’url en tant que paramètre de la méthode à appeller, de telle sorte que l’on pourrait écrire pour une méthode avec la signature SomeController::getArticle(string categoryName, string articleId):
dispatcher().assign("/getArticle/(s+)/(d+)", &SomeController::getArticle,1,2);

Ainsi la méthode chargée de traiter la requête n’est appellé que si le format de l’url correspond. Cela n’empêche pas pour autant d’accèder directement à l’url complète ou aux paramètres GET, POST etc. à l’aide de la méthode request() retournant un objet de type cppcms::http::request représentant la requête.

Ecriture de la réponse

Comme il est possible de le voir dans les méthodes redirect(), hello() & bye on manipule la réponse http à l’aide de la méthode response() qui retourne un object de type cppcms::http::response et qui possède une API très riche pour manipuler les réponses, avec des méthodes confortables. Vous trouverez également une méthode io_mode() qui peut être définie sur normal, nogzip, raw, asynchronous ou asynchronous_raw.

De nombreuses autres fonctionallités à découvrir

Comme vous avez pu le constater il y a différents modes d’entrées/sorties possibles dans cppcms. Un mode très intéressant, mais peut-être le moins commun pour les personnes ne connaissant pas boost::asio est le mode asynchrone.

Chaque thread qui fait de l’entrée/sortie a la problèmatique de devoir attendre sur l’achèvement d’une entrée ou d’une sortie pour pouvoir continuer les traitements. On peut résoudre ce problème d’entrées/sorties bloquants les thread la majorité du temps, en délégant le transfert des données aux drivers et au matériel. Laissant ces derniers effectuer les entrées/sorties lorsque c’est actuellement le moment ou possible, et répondre ainsi à d’autres entrées/sorties avec le même thread. Cela décuple de façon très importante le nombre de requêtes pouvant être traitées avec le même nombre de threads, cela implique cependant plus de complexité et nécessite de gérer soit-même le nombre de connexions par thread, afin de rendre l’application scalable.

Pour ceux connaissant boost::asio il y a cependant certaines différences dans la façon avec laquelle CppCms implémente les mêmes principes avec sa librairie booster::aio. En effet io_service::run() ne peut être appellée par plusieurs threads à la fois et laisse tourner sa boucle d’évènement jusqu’à ce que stop() soit appellée et non pas lorsqu’il n’y a plus de tâches postées.

booster::aio à l’instar de boost::asio n’utilise pas iocp sur Windows, parce qu’il utilise toujours le Reactor pattern. Un autre point est qu’il y a moins de fonctionnalités que dans boost::asio et est orienté objet plutôt que concepts.

Je posterai sous peu un article expliquant cela plus en détails et présenterai dans quels cas avec CppCms les applications webs asynchrones sont intéressantes, cela ne ferait aucun sens pour notre hello_world, mais plutôt dans le cas d’implémentation de server-push ou de long-polling à l’aide de la classe : cppcms::http::context permettant de conserver une requête en cours sur le serveur et de la servir partiellement sans bloquer un thread par utilisateur, à l’aide de fonctions telles qu’async_flush_output(handler).

Conclusion

Développer des applications webs en C++ n’est plus un problème et a différents avantages, je n’ai présenté ici que la base la plus simple de ce framework, mais il y a de nombreuses fonctionnalités qui méritent d’être connues comme le support de Json::Rpc, le compilateur de vues/templates, les mécanismes de cache intégrés, les classes de sérialisation/désérialisation, la gestion des sessions, des formulaires et de l’internationalisation.

Si vous souhaitez en savoir plus n’hésitez pas à visiter les exemples et la documentation officielle : CppCMS 1.x.x — Stable. Je serai également ravi de vous répondre ou de vous aider.

Cette entrée a été publiée dans C++, Développement, et marquée avec , , , le par .

A propos Damien Buhl

Je me nomme Damien Buhl (alias daminetreg). Développeur embarqué chez Fr. Sauter AG à Basel, anciennement étudiant à l'eXia.Cesi Strasbourg. Le manifeste agile, le développement en C++ avec Boost & Javascript pour le web et l'embarqué, les amis, le sport, la conception UML, la motivation en addition à Linux, Symbian, Android, Qt & Mootools sont pour moi les éléments nécessaires à ma survie.

4 réflexions au sujet de « Développement Web en C++ »

  1. metagoto

    Bon article!
    C++ coté server c’est effectivement très intéressant. Et ce n’est que le début! Enfin je pense, j’espère..

    En guise de projet personnel j’ai mis au point il y a 3 ou 4 ans un mini framework web C++.
    Le projet avait pour but de réunir diverses technologies telles que le moteur javascript v8 (pour le templating), la librairie asynchrone ioxx (de Peter Simons), mongodb, boost et les quelques premières features C++0x (à l’epoque) de gcc.
    Un blog fut développé en tant qu’application de test/demo. Ca marchait très bien. L’application se trouvait derrière un server fastcgi (nginx). Les perfs étaient excellentes! Du même ordre que CppCms.
    Le blog est maintenant fermé. Les sources sont toujours sur github cependant:
    http://github.com/metagoto/fcgixx
    http://github.com/metagoto/blog
    Je garde un très bon souvenir de cette expérience. Et il y aurait moyen de faire 15 fois mieux maintenant avec C++11…

    Répondre
  2. Damien Buhl Auteur de l’article

    Merci metagoto, en effet fgixx à l’air pas mal du tout. C’est fou que je ne l’ai pas connu auparavant, car j’ai écrit une thèse sur le sujet il y a deux ans et dans mon état de l’art j’ai listé beaucoup de projets, mais je ne connaissais malheureusement pas fcgixx.

    En effet avec C++11 et la cpp-netlib de Dean Berris (délégation google du comité de standardisation) le C++ risque de posséder soit pour C++14 ou C++17 ces fonctionalites dans le standard.

    En tous cas bravo pour fcgixx c’est vraiment bien fait, le code est élégant et l’idée d’utiliser v8 pour le templating est sympathique. :)

    Répondre
  3. metagoto

    Merci.
    Qui ne connait pas Dean Berris? ;)

    Pas étonnant que fcgixx soit passé inaperçu, le projet n’était pas censé être d’utilité publique! Ce n’était pas le but.
    Le templating “django-like” à base de v8 a été extrait et légèrement amélioré dans un de mes autres repository “r8t” sur github.

    Il est vrai qu’à l’époque je n’avais pas trouvé de system de templating orienté html mis à part google ctemplate et un ou deux autres projets moins intéressants. Au final j’avais préféré monter un nouveau system.
    Je ne sais pas du tout si cette situation s’est améliorée depuis, à savoir si de nouvelles solutions C++ ont vu le jour. Ca serait sympa.

    Répondre

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Vous pouvez utiliser ces balises et attributs HTML : <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">