Quelques cadriciels Web C++ (2/2)

Posté par (page perso) . Édité par Davy Defaud, Benoît Sibaud, ZeroHeure, palm123, Trollnad Dump et Xavier Claude. Modéré par patrick_g. Licence CC by-sa.
Tags :
38
12
déc.
2018
C et C++

Actuellement, il existe de nombreux langages et cadriciels intéressants pour le développement Web back‐end. Dans ce domaine, le C++ n’est pas le langage le plus à la mode, mais il possède cependant des atouts intéressants. En effet, le C++ possède de nombreuses bibliothèques (dont des cadriciels Web), il est réputé pour ses performances et, enfin, ses dernières normes le rendent plus agréable à utiliser.

L’objectif de cet article est de donner un aperçu des outils C++ disponibles pour le développement Web back‐end, à partir d’un exemple d’application. Les codes sources présentés ici sont disponibles sur ce dépôt Git. Les différents cadriciels utilisés sont résumés en annexe (partie 2). Enfin, une liste de bibliothèques C++ est disponible sur Awesome C++.

Partie 2 : les cadriciels Web.

Sommaire

Les cadriciels Web

Les micro‐cadriciels, à la Sinatra/Flask

Les micro‐cadriciels Web, comme Sinatra en Ruby ou Flask en Python, ont pour objectif d’être simples et légers. Ils proposent principalement des fonctionnalités pour traiter des requêtes HTTP ainsi qu’un mécanisme de routage d’URL. Si nécessaire, ils peuvent être complétés par d’autres bibliothèques (génération de HTML, accès à une base de données en SQL…).

Il existe plusieurs micro‐cadriciels C++, par exemple crow (voir animals-crow) ou silicon (voir animals-silicon) :

#include <silicon/api.hh>
#include <silicon/backends/mhd.hh>
#include <silicon/middlewares/sqlite_connection.hh>
#include <silicon/middleware_factories.hh>
#include "symbols.hh"

using namespace sl;
using namespace std;

...

int main() {

  // create app
  auto api = http_api(

    // serve the about page
    GET / _about = [] () { return renderAbout(); },

    // serve the home page (and filter the animals using the "myquery" parameter)
    GET / _animals * get_parameters(_myquery = optional(string())) =
      [] (const auto & p, sqlite_connection & c) {
        vector<Animal> animals = getAnimals(p.myquery, c);
        return renderHome(p.myquery, animals);
      },

    // serve static files (located in the "mystatic" directory)
    GET / _mystatic = file_server("./mystatic")

  );

  // create a connection factory to the database 
  auto factory = middleware_factories( sqlite_connection_factory("animals.db") );

  // run a server on port 3000
  mhd_json_serve(api, factory, 3000);
}

Ici, les fonctionnalités du C++ moderne rendent le code concis et plutôt agréable à lire (par exemple, la lambda pour la route _animals).

Dans une phase de prétraitement, Silicon génère le fichier symbols.hh, qui déclare les symboles définis par le programmeur, notamment les routes (_about, _home, _mystatic…). Ceci permet de vérifier statiquement que les routes sont utilisées correctement dans le code. D’autres langages utilisent l’introspection pour effectuer ce genre de vérification, mais C++ ne possède pas cette fonctionnalité.

Les cadriciels asynchrones, à la Node.js

Les cadriciels asynchrones, comme Node.js/Express en JavaScript, proposent les mêmes fonctionnalités que les micro‐cadriciels classiques mais via des fonctions non bloquantes. Ainsi, si une requête a besoin d’une ressource, l’application peut passer à une autre requête en attendant que la ressource soit disponible. Ceci permet d’améliorer les performances générales de l’application mais nécessite un style de programmation particulier, à base de promesses connectées à des fonctions de call‐back par des then pour former une chaîne de traitements asynchrones.

Il existe différents cadriciels asynchrones en C++, par exemple cpprestsdk (voir animals-cpprestsdk) et pistache (voir animals-pistache) :

#include "Animal.hpp"
#include "View.hpp"

#include <pistache/http.h>
#include <pistache/router.h>
#include <pistache/endpoint.h>

using namespace Pistache;
using namespace std;

// define server app
class App : public Http::Endpoint {

  private:
    Rest::Router router;

  public:
    App(Address addr) : Http::Endpoint(addr) {

      auto opts = Http::Endpoint::options()
        .flags(Tcp::Options::InstallSignalHandler)
        .flags(Tcp::Options::ReuseAddr);
      init(opts);

      // create a route for the about page
      Rest::Routes::Get(router, "/about", 
        [=](const Rest::Request &, Http::ResponseWriter response) {
            response.send(Http::Code::Ok, renderAbout());
            return Rest::Route::Result::Ok;
        });

      // create a route for the home page
      Rest::Routes::Get(router, "/", 
        [=](const Rest::Request & request, Http::ResponseWriter response) {
            auto myquery = request.query().get("myquery").getOrElse("");
            const vector<Animal> animals = getAnimals(myquery);
            response.send(Http::Code::Ok, renderHome(move(myquery), move(animals)));
            return Rest::Route::Result::Ok;
        });

      // create a route for serving static files
      Rest::Routes::Get(router, "/static/:filename", 
        [=](const Rest::Request & request, Http::ResponseWriter response) {
            auto filename = request.param(":filename").as<string>();
            // the Pistache API is non-blocking; for example, serveFile returns 
            // a Promise, for attaching a callback function
            Http::serveFile(response, "static/" + filename)
              .then(
                  [=](ssize_t s){ cout << filename << " (" << s << " bytes)" << endl; },
                  Async::NoExcept);
            return Rest::Route::Result::Ok;
        });

      setHandler(router.handler());
    }
};

// run server app on port 3000
int main() {
  App app({Ipv4::any(), 3000});
  app.serve();
}

On retrouve ici une gestion classique des routes (avec le nom de la route et sa fonction de traitement). Cependant, on a désormais un fonctionnement asynchrone, via des fonctions non bloquantes. Par exemple, pour la route « static », la fonction serveFile retourne une promesse que l’on connecte à une fonction call‐back, qui affiche un message de journal une fois la promesse résolue.

Les cadriciels MVC, à la RoR/Django

Les cadriciels Web MVC, comme Ruby on Rails ou Python Django, sont des outils classiques dont l’objectif est d’implémenter tout type d’application Web. Ils fournissent généralement toutes les fonctionnalités nécessaires : routage d’URL, système de patrons, accès à des bases de données, système d’authentification… Les cadriciels MVC ne semblent pas être le domaine de prédilection du C++, mais on trouve tout de même quelques outils intéressants, notamment cppcms.

En plus des fonctionnalités classiques d’un cadriciel MVC, cppcms propose un système de patrons assez évolué, avec héritage de vues et gestion de contenu. Par exemple, on peut définir une vue principale MasterView et en dériver des vues AboutView et HomeView qui héritent des caractéristiques de MasterView et les complètent. Enfin, on peut associer un contenu à ces vues (paramètres des patrons), également avec un système d’héritage. En reprenant l’exemple précédent, on peut définir un contenu MasterContent pour la vue MasterView, la dériver en HomeContent pour la vue HomeView et utiliser directement MasterContent pour la vue AboutView (pas de nouveau paramètre dans le patron).

Au niveau du code, le fichier animals-cppcms/src/content.h définit les contenus :

// define how to exchange data between the C++ code and the templates
namespace content  {

  // content for MasterView and AboutView
  struct MasterContent : cppcms::base_content {
    std::string title;  // the "title" parameter in master.tmpl
  };

  // datatype for the form in HomeView
  struct InfoForm : cppcms::form {
    cppcms::widgets::text myquery; 
    InfoForm() {
      add(myquery);
    }
  };

  // content for HomeView 
  // inherits from MasterContent, because HomeView inherits from MasterView
  struct HomeContent : MasterContent {
    std::vector<Animal> animals;  // the "animals" parameter in home.tmpl
    InfoForm info;  // the "info" parameter in home.tmpl
  };

}

Le fichier animals-cppcms/src/master.tmpl définit la vue MasterView :

<% c++ #include "content.h" %>

<% skin myskin %>
  <% view MasterView uses content::MasterContent %>

    <!-- "title" is set in the C++ code and used in the "render" template below -->
    <% template title() %> <%= title %> <% end %>

    <!-- "page_content" is set in the sub-template and used in the "render" template below -->
    <% template page_content() %> to be overriden in sub-templates <% end %>

    <!-- main template that uses the two previous ones and that is called from the C++ code -->
    <% template render() %>
      <html>

        <head>
          <style>
            body {
              background-color: azure;
            }
            ...
          </style>
        </head>

        <body>
          <!-- adds the "title" template defined above  -->
          <h1><% include title() %></h1>
          <!-- adds the "page_content" template defined above  -->
          <div> <% include page_content() %> </div>
        </body>

      </html>
    <% end template %> 

  <% end view %>
<% end skin %>

Le fichier animals-cppcms/src/about.tmpl définit la vue AboutView :

<% skin myskin %>

  <!-- inherits from MasterView -->
  <% view AboutView uses content::MasterContent extends MasterView %>

    <!-- defines "page_content", that is used in MasterView -->
    <% template page_content() %>
      <p>Generated by <a href='http://cppcms.com/wikipp/en/page/main'>Cppcms</a></p>
      <p><a href='<% url "/" %>'>Home</a></p>
    <% end template %> 

  <% end view %>

<% end skin %>

Le fichier animals-cppcms/src/about.tmpl définit la vue HomeView :

<% skin myskin %>

  <!-- inherits from MasterView -->
  <% view HomeView uses content::HomeContent extends MasterView %>

    <!-- defines "page_content", that is used in MasterView -->
    <% template page_content() %>

      <!-- creates a form using the "info" attribute defined in HomeContent -->
      <form method="get" action="" >
        <% form as_p info %>
      </form>

      <!-- add the HTML elements corresponding to the "animals" attribute defined in HomeContent -->
      <% foreach animal in animals %>
        <% item %>
          <a class="aCss" href="img/<%= animal.image %>" >
            <div class="divCss">
            <p><%= animal.name %></p>
              <img class="imgCss" src="img/<%= animal.image %>" />
            </div>
          </a>
        <% end %>
      <% end foreach %>

      <p style="clear:both"><a href='<% url "/about" %>'>About</a></p>

    <% end template %> 

  <% end view %>
<% end skin %>

Enfin, le programme principal définit le routage d’URL et initialise les contenus avant de lancer le rendu des vues. Fichier animals-cppcms/src/main.cpp :

// main application
class App : public cppcms::application {

  public:
    App(cppcms::service &srv) : cppcms::application(srv) {
      // about page
      dispatcher().assign("/about", &App::about, this);
      mapper().assign("about","/about");
      // home page
      dispatcher().assign("", &App::home, this);
      mapper().assign("");
      // images
      dispatcher().assign("/img/([a-z_0-9_\-]+\.jpg)", &App::serveJpg, this, 1);
      // root url
      mapper().root("/animals");
    }

  private:
    void about() {
      // AboutView inherits from MasterView and uses the same content type (MasterContent)
      content::MasterContent c;
      c.title = "About (Cppcms)";
      // render the AboutView template
      render("AboutView", c);
    }

    void home()  {
      // HomeView inherits from MasterView and uses its own content type 
      // (HomeContent, that inherits from MasterContent)
      content::HomeContent c;
      // data defined in MasterContent 
      c.title = "Animals (Cppcms)";
      // data defined in HomeContent
      c.info.load(context());
      c.animals = getAnimals(c.info.myquery.value());
      // render the HomeView template
      render("HomeView", c);
    }

    void serveJpg(string filename)  {
      // open and send the image file
      ifstream ifs("img/" + filename);
      if (ifs) {
        response().content_type("image/jpeg");
        response().out() << ifs.rdbuf();
      }
      else {
        response().status(404);
      }
    }
};

// create and run the application
int main(int argc, char ** argv) {
  try {
    cppcms::service srv(argc, argv);
    srv.applications_pool().mount(cppcms::applications_factory<App>());
    srv.run();
  }
  catch(exception const & e) {
    cerr << e.what() << endl;
  }
  return 0;
}

Les cadriciels MVC sont des outils efficaces pour implémenter des applications complexes. Cependant, ils nécessitent un apprentissage assez conséquent et peuvent être surdimensionnés pour des petites applications simples.

Les cadriciels basés patrons, à la PHP

Le cadriciel tntnet propose un système basé sur les patrons, à la manière de PHP. Si ce cadriciel est assez anecdotique dans l’écosystème C++, il semble cependant plutôt efficace dans son approche : écrire du code HTML classique et y ajouter des sections de code C++ là où c’est nécessaire.

Par exemple, le fichier animals-tntent/src/myimg.ecpp définit une application qui affiche une image dont le nom est passé en paramètre :

<%args>
  filename;
</%args>

<html>
  <body>
    <img src="static/img/<$filename$>" />
  </body>
</html>

De même, le fichier animals-tntent/src/home.ecpp définit une application plus complexe (appel de fonction C++, génération de code HTML via une boucle en C++, etc.) :

<%args>
  myquery;
</%args>

<%pre>
  #include "Animal.hpp"
</%pre>

<html>
  <head>
    <link rel="stylesheet" type="text/css" href="static/style.css">
  </head>

  <body>

    <h1>Animals (Tntnet)</h1>

    <form>
      <p> <input type="text" name="myquery" value="<$myquery$>"> </p>
    </form>

    <%cpp> for (const Animal & animal : getAnimals(myquery)) { </%cpp>

      <a href="myimg?filename=<$animal.image$>">
        <div class="divCss">
          <p> <$animal.name$> </p>
          <img class="imgCss" src="static/img/<$animal.image$>" />
        </div>
      </a>

    <%cpp> } </%cpp>

    <p style="clear: both"><a href="/about">About</a></p>

  </body>
</html>

Enfin, tntnet propose différents types de déploiement : programme CGI, serveur autonome, serveur d’applications tntnet compilées dans des bibliothèques dynamiques. Par exemple, pour implémenter un serveur autonome (animals-tntent/src/main.cpp) :

#include <tnt/tntnet.h>

// run server on port 3000
int main() {
  try {
    tnt::Tntnet app;
    app.listen(3000);
    app.mapUrl("^/$", "home");        // route the "/" url to the "home" application 
    app.mapUrl("^/about$", "about");  // route the "/about" url to the "about" application 
    app.mapUrl("^/myimg$", "myimg");  // ...
    app.mapUrl("^/(static/.*)", "$1", "static@tntnet");
    app.run();
    return 0;
  }
  catch (const std::exception & e) {
    std::cerr << e.what() << std::endl;
    return -1;
  }
}

À noter que ce type de cadriciel est peut‐être moins adapté au développement d’applications complexes (lisibilité des patrons, réutilisation…).

Les cadriciels basés widgets

Ces outils s’inspirent des cadriciels d’interfaces graphiques de bureau, comme Qt ou gtkmm, c’est‐à‐dire basés sur une hiérarchie de widgets composant l’interface et intéragissant via un mécanisme de signal‐slot.

Les cadriciels Web basés widgets sont étonnament peu répandus, même tous langages confondus, alors que leur potentiel semble important. En effet, ils permettent de développer une application fullstack client‐serveur en utilisant une bibliothèque d’interface graphique classique et sans avoir à trop se préoccuper de l’architecture réseau de l’application.

En C++, le cadriciel le plus abouti dans cette catégorie est certainement Wt. Wt possède de nombreux widgets classiques ou évolués, un ORM SQL, un système d’authentification, la possibilité de manipuler du HTML et du CSS, etc. En Wt, le programme principal se résume à router des URL vers les applications correspondantes (animals-wt/src/main.cpp) :



int main(int argc, char ** argv) {
  try {
    WServer server(argc, argv, WTHTTP_CONFIGURATION);

    // route the url "/about" to an application "AboutApp"
    server.addEntryPoint(EntryPointType::Application, 
        [](const WEnvironment & env)
        { return make_unique<AboutApp>(env); },
        "/about");

    // route the url "/" to an application "HomeApp"
    server.addEntryPoint(EntryPointType::Application, 
        [=](const WEnvironment & env)
        { return make_unique<HomeApp>(env); },
        "/");

    server.run();
  }
  catch (Dbo::Exception & e) {
    cerr << "Dbo::Exception: " << e.what() << endl;
  }
  return 0;
}

Ces applications Wt correspondent à des interfaces graphiques classiques, mais avec une architecture client‐serveur. Par exemple, pour définir l’application « about » (page statique) via le système de patrons HTML/CSS, il suffit de définir la classe suivante (animals-wt/src/AboutApp.hpp) :

...

// Application class implementing the about page
class AboutApp : public Wt::WApplication {
  private:
    // main HTML template of the application
    const std::string _app_template = R"(
        <h1>About (Wt)</h1>
        <p>Generated by <a href="https://www.webtoolkit.eu/wt">Wt</a></p>
        <p><a href="/">Home</a></p>
    )";

  public:
    // create the application
    AboutApp(const Wt::WEnvironment & env) : Wt::WApplication(env) {
      // load css
      useStyleSheet({"style.css"});
      // create the main widget using the HTML template
      root()->addWidget(std::make_unique<Wt::WTemplate>(_app_template));
    }
};

Pour une application plus complexe, par exemple la page affichant les animaux, on peut définir un nouveau widget AnimalWidget qui implémente une vignette, puis utiliser cette classe pour afficher tous les animaux lus dans la base de données (voir animals-wt/src/HomeApp.hpp) :



bool isPrefixOf(const std::string & txt, const std::string & fullTxt) {
  return std::inner_product(std::begin(txt), std::end(txt), std::begin(fullTxt), 
      true, std::logical_and<char>(), std::equal_to<char>());
} 

// widget showing an animal (name + image + anchor) 
class AnimalWidget : public Wt::WAnchor {
  private:
    // pointer to the WText that contains the animal name
      Wt::WText * _animalName;

  public:
    AnimalWidget(const Animal & animal) {
      // set anchor href
      const std::string imagePath = "img/" + animal.image;
      setLink(Wt::WLink(imagePath));
      // create a container widget, inside the anchor widget
      auto cAnimal = addWidget(std::make_unique<Wt::WContainerWidget>());
      cAnimal->setStyleClass("divCss");
      // create a text widget, inside the container
      auto cText = cAnimal->addWidget(std::make_unique<Wt::WContainerWidget>());
      cText->setPadding(Wt::WLength("1em"));
      _animalName = cText->addWidget(std::make_unique<Wt::WText>(animal.name));
      // create an image widget, inside the container
      auto img = cAnimal->addWidget(std::make_unique<Wt::WImage>(imagePath));
      img->setStyleClass("imgCss");
    }

    void filter(const std::string & txt) {
      // show the widget if txt is null or if it is a prefix of the animal name
      setHidden(txt != "" and not isPrefixOf(txt, _animalName->text().toUTF8()));
    }
};

// Application class implementing the home page
class HomeApp : public Wt::WApplication {
  private:
    // the line edit widget (for querying animal to show/hide)
      Wt::WLineEdit * _myquery;

    // the animal widgets 
    std::vector<AnimalWidget*> _animalWidgets;

    // main HTML template of the application
    const std::string _app_template = R"(
        <h1>Animals (Wt)</h1>
        <p>${myquery}</p>
        ${animals}
        <p style="clear: both"><a href="/about">About</a></p>
    )";

    // show all animals that match the _myquery prefix
    void filterAnimals() {
      for(auto aw : _animalWidgets)
        aw->filter(_myquery->text().toUTF8());
    }

  public:
    // create the application
    HomeApp(const Wt::WEnvironment & env) : WApplication(env) {
      // load css
      useStyleSheet({"style.css"});
      // create the main widget using the HTML template
      auto r = root()->addWidget(std::make_unique<Wt::WTemplate>(_app_template));
      // create the remaining widgets and bind them to the template placeholders
      _myquery = r->bindWidget("myquery", std::make_unique<Wt::WLineEdit>());
      // connect the widget _myquery to the function filterAnimals 
      _myquery->textInput().connect(this, &HomeApp::filterAnimals);

      // create a container widget for the animals
      auto w = r->bindWidget("animals", std::make_unique<Wt::WContainerWidget>());
      // open the database
      Wt::Dbo::Session session;
      session.setConnection(std::make_unique<Wt::Dbo::backend::Sqlite3>("animals.db"));
      session.mapClass<AnimalDb>("animals");
      // query the database
      Wt::Dbo::Transaction transaction(session);
      Wt::Dbo::collection<Wt::Dbo::ptr<AnimalDb>> dboAnimals = session.find<AnimalDb>();
      for (const Wt::Dbo::ptr<AnimalDb> & dboAnimal : dboAnimals) {
        // add a widget
        auto aw = w->addWidget(std::make_unique<AnimalWidget>(*dboAnimal));
        // store a pointer, for future updates
        _animalWidgets.push_back(aw);
      }
    }
};

À première vue, cette implémentation peut sembler plus longue et plus compliquée que les implémentations précédentes. Cependant, son code devrait sembler familier à n’importe quel développeur d’interface graphique de bureau. De plus, cette implémentation gère l’ensemble de l’application (fullstack), et non la partie serveur uniquement. Par exemple, la connexion du signal _myquery->textInput() à la fonction HomeApp::filterAnimals implique des mises à jour en temps réel côté client, ce qui serait nettement plus difficile à implémenter avec les cadriciels précédents.

Intégration avec Nix

Avant de conclure, voici une petite remarque concernant l’intégration, dans un projet de code, des cadriciels présentés. En effet, ces cadriciels sont rarement présents dans les logithèques des systèmes d’exploitation et doivent donc généralement être installés manuellement. Pour cela, on choisit classiquement l’une des deux solutions suivantes :

  • télécharger le code source de la bibliothèque, l’installer dans un dossier local ou système et régler les variables d’environnement de façon à trouver la bibliothèque ;
  • intégrer la bibliothèque directement au projet, par exemple via les sous‐modules Git, et configurer le projet de façon à trouver la bibliothèque.

Aucune de ces solutions n’est vraiment satisfaisante : la première solution introduit une dépendance externe peu portable qu’il faut mettre à jour manuellement ; la seconde solution introduit des duplications de code potentielles et nécessite une configuration de projet particulière.

Une troisième solution, particulièrement avantageuse, consiste à utiliser le système de paquets Nix. Par exemple, pour récupérer et empaqueter cpprestsdk depuis son projet GitHub, il suffit d’écrire le fichier Nix suivant (voir animals-cpprestsdk/nix/cpprestsdk.nix) :

{ stdenv, fetchFromGitHub, cmake, boost, openssl, websocketpp, zlib }:

stdenv.mkDerivation {

  name = "cpprestsdk";

  src = fetchFromGitHub {
    owner = "Microsoft";
    repo = "cpprestsdk";
    rev = "204a52610234ac5180e80a6883b62c0ad085f51e";
    sha256 = "0mj2m6n889zdhwxdx24ljxfqryivvn3w9vzs94ppzcx5apa5jb3w";
  };

  enableParallelBuilding = true;

  buildInputs = [ boost cmake openssl websocketpp zlib ];
}

Pour inclure, dans un projet, des cadriciels ainsi empaquetés, il suffit alors de les appeler dans le fichier de configuration default.nix du projet (voir animals-cpprestsdk/default.nix) :

with import <nixpkgs> {};

let
  _cpprestsdk = callPackage ./nix/cpprestsdk.nix {};
  _ctml = callPackage ./nix/ctml.nix {};
  _sqlite_orm = callPackage ./nix/sqlite_orm.nix {};
in

stdenv.mkDerivation {
  name = "animals-cpprestsdk";
  src = ./.;

  buildInputs = [
    boost
    _cpprestsdk
    _ctml
    openssl
    sqlite
    _sqlite_orm
  ];

  buildPhase = '' 
    g++ -O2 -o animals-cpprestsdk src/*.cpp -lcpprest -lboost_system -lssl -lsqlite3
    sqlite3 animals.db < animals.sql
  '';

  installPhase = ''
    mkdir -p $out/static
    cp animals-cpprestsdk $out/
    cp animals.db $out/
    cp $src/static/* $out/static/
  '';
}

On peut alors facilement compiler et lancer le programme, avec les commandes suivantes :

nix-build
cd result
./animals-cpprestsdk

Cette méthode à l’avantage de rendre les dépendances explicites et isolées, mais sans duplication. Elle facilite également la réutilisation et les mises à jour.

Conclusion

Pour développer des applications Web back‐end, le C++ est une option tout à fait envisageable. Avec ses dernières évolutions, le langage est généralement plus simple et plus sûr à utiliser, sans compromis sur les performances. De nombreuses bibliothèques C++ sont disponibles pour le développement Web : patrons, génération de HTML, connexion SQL, ORM… Les cadriciels Web sont également nombreux et variés : cadriciel MVC à la RoR/Django, micro‐cadriciel à la Sinatra/Flask, cadriciel asynchrone à la Node.js, cadriciel basé patrons à la PHP, et même cadriciel fullstack basé widgets. Enfin, on notera qu’avec Nix, il est très facile de configurer un projet intégrant ce genre de bibliothèques. Bien évidemment, tout ceci intéressera essentiellement des développeurs qui connaissent déjà C++, car beaucoup d’autres langages ont également des outils très intéressants pour le développement Web.

Annexe : résumé des projets et des cadriciels présentés

À noter qu’il s’agit uniquement d’exemples d’associations. Il est généralement possible d’utiliser n’importe quel cadriciel Web avec n’importe quel générateur HTML et n’importe quelle interface SQL.

projet cadriciel Web générateur HTML interface SQL
animals-cppcms cppcms (cadriciel MVC) cppcms (système de patrons) cppcms (connecteur SQL)
animals-cpprestsdk cpprestsdk (cadriciel réseau asynchrone) ctml (générateur de documents) sqlite_orm (ORM)
animals-crow http: crow (micro‐cadriciel) crow (système de patrons) sqlpp11 (ORM)
animals-nodejs (Javascript/Node.js) express (micro‐cadriciel asynchrone) pug (générateur de documents) better-sqlite3 (connecteur SQL)
animals-pistache pistache (micro‐cadriciel asynchrone) kainjow mustache (système de patrons) sqlite_modern_cpp (connecteur SQL)
animals-scotty (Haskell) scotty (micro‐cadriciel) lucid and clay (générateurs de documents) sqlite-simple (connecteur SQL)
animals-silicon silicon (micro‐cadriciel) aucun silicon (connecteur SQL)
animals-tntnet tntnet (cadriciel basé patrons) tntnet (système de patrons) tntnet (connecteur SQL)
animals-wt wt (cadriciel basé widgets) wt (système de widgets + patrons) wt (ORM)

Aller plus loin

  • # Beast

    Posté par (page perso) . Évalué à 2 (+1/-0).

    J'ajouterai Boost.Beast qui est vraiment ce qu'on peut appeler un micro framework.

    Je l'utilise et c'est assez sympa mais rudimentaire.

    l'azerty est ce que subversion est aux SCMs

    • [^] # Re: Beast

      Posté par . Évalué à 1 (+0/-0).

      Je trouve qu'il manque de simplicité. Il faudrait un wrapper, l'écriture est bien verbeuse.

      • [^] # Re: Beast

        Posté par . Évalué à 1 (+0/-0).

        Et j'ajouterais : il semble que beast soit en bonne voie pour devenir un standard (C++21?).

  • # Le titre

    Posté par . Évalué à 5 (+4/-0). Dernière modification le 12/12/18 à 19:20.

    Quelques cadriciels Web C++ (1/2)
    Quelques frameworks web C++ (2/2)

    Cadriciels c'est bien aussi. Pour les moteurs de recherche 1/2, 2/2 c’est plus logique.

    Tout le monde a un cerveau. Mais peu de gens le savent.

    • [^] # Re: Le titre

      Posté par . Évalué à 2 (+0/-0).

      Je suis ravi que ça te plaise. Je suis à l’initiative du remplacement de framework par cadriciel, l’auteur n’est donc pas fautif de cette incohérence entre les deux parties. Je viens de faire ma passe de correction sur la seconde, c’est à présent harmonisé.

  • # Sécurité

    Posté par . Évalué à 1 (+1/-0).

    Je me pose une question sur la sécurité de tous ces frameworks.
    Sur un système qui doit offrir des websocket, une API Rest et des fichiers statiques, comment protège-t-on tout ce petit monde ?
    Il faut que chacun est un support de TLS (je pense que cela est fait), mais l'authentification ? Comment fait-on ? Faut-il implémenter un système d'authentification sur chacun d'eux ?

    Cela oblige aussi à utiliser des ports différents pour chaque sous-système. Il faut donc vérifier l'ouverture des ports sur les différents proxy.

    • [^] # Re: Sécurité

      Posté par . Évalué à 3 (+1/-0).

      Généralement tu utilise un reverse proxy qui va au moins permettre de n'avoir que le port 443 d'ouvert.

      Ensuite tu peut faire du gateway offloading et laisser à ce reverse proxy la gestion de la TLS. Ça permet de ne pas avoir à se poser la question sur comment configurer chaque service (mise en place du certificat, mais aussi choix des ciphers par exemple).

      Enfin pour l'authentification ça dépend de ton authentification et selon le choix fait tu va l'implémenter sur chaque service ou sur le reverse proxy. Quand tu le fais sur le reverse proxy il va ajouter des entêtes à la requête pour permettre au service suivant de gérer les droit (le reverse proxy s'assure que la requête est bien celle de tel utilisateur, chaque service vérifient que l'utilisateur en question a le droit de faire cette requête).

  • # Cutelyst

    Posté par . Évalué à 2 (+0/-0).

    Cutelyst (que je n'ai toujours pas testé) qui à le bon gout de permettre de partager ton code Qt (donc qui doit en théorie permettre simultanément d'écrire une appli web et une appli desktop)

  • # isPrefix

    Posté par . Évalué à 1 (+1/-0).

    Implantation astucieuse de isPrefix, mais le logical_and est plutôt sur bool que sur char, et cette implémentation ne s'arrête pas dès que possible. Je serai plutôt parti sur std::mismatch.

    • [^] # Re: isPrefix

      Posté par (page perso) . Évalué à 1 (+0/-0). Dernière modification le 02/01/19 à 15:54.

      Bien vu, merci.
      Apparemment il y a starts_with en C++20 qui devrait encore mieux convenir.

Envoyer un commentaire

Suivre le flux des commentaires

Note : les commentaires appartiennent à ceux qui les ont postés. Nous n'en sommes pas responsables.