URL: https://linuxfr.org/news/quelques-cadriciels-web-c-1-2 Title: Quelques cadriciels Web C++ (1/2) Authors: nokomprendo Davy Defaud, ZeroHeure, Julien Jorge, palm123, BenoĂźt Sibaud, bubarđŸŠ„ et claudex Date: 2018ćčŽ12月06æ—„T02:17:19+01:00 License: CC By-SA Tags: c++ Score: 34 Actuellement, il existe de nombreux langages et cadriciels (_frameworks_) intĂ©ressants pour le dĂ©veloppement Web cĂŽtĂ© serveur. 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++ dispose de nombreuses bibliothĂšques (dont des cadriciels Web), il est rĂ©putĂ© pour ses performances, 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](https://framagit.org/nokomprendo/tuto_fonctionnel/tree/master/posts/tuto_fonctionnel_31). 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++](http://fffaraz.github.io/awesome-cpp/). Partie 1 : exemple d’application, gĂ©nĂ©ration de HTML et accĂšs Ă  une base de donnĂ©es. ---- [Quelques cadriciels Web C++ (2/2)](https://linuxfr.org/news/quelques-frameworks-web-cpp-2-2) ---- # Exemple d’application ## Application finale On veut implĂ©menter une application qui permet d’afficher des images d’animaux stockĂ©es sur le serveur. Un formulaire permet d’indiquer le dĂ©but du nom des animaux Ă  afficher. On peut afficher l’image complĂšte en cliquant sur la vignette et l’on peut afficher une page d’information via un lien en bas de page. Les donnĂ©es des animaux (noms et fichiers) sont stockĂ©es dans une base SQLite sur le serveur. ![application finale](https://framagit.org/nokomprendo/tuto_fonctionnel/raw/master/posts/tuto_fonctionnel_31/images/animals2.gif) Ici, la gĂ©nĂ©ration des pages HTML est effectuĂ©e sur le serveur, mĂȘme si la tendance actuelle est plutĂŽt de proposer une API cĂŽtĂ© serveur et de gĂ©nĂ©rer le code HTML cĂŽtĂ© client. ## Architecture MVC De façon trĂšs classique, on peut organiser le code de cette application selon une architecture de type MVC, c’est‐à‐dire en distinguant les donnĂ©es (modĂšle), leur affichage (vue) et leur gestion (contrĂŽleur). Pour notre application, les images sont disponibles sur le serveur et on utilise une base de donnĂ©es SQLite contenant une table avec les noms et fichiers des animaux. Fichier `animals.sql` : ```sql CREATE TABLE animals ( id INTEGER PRIMARY KEY, name TEXT, image TEXT ); INSERT INTO animals (name, image) VALUES('dolphin', 'dolphin-marine-mammals-water-sea-64219.jpg'); INSERT INTO animals (name, image) VALUES('dog', 'night-garden-yellow-animal.jpg'); INSERT INTO animals (name, image) VALUES('owl', 'owl.jpg'); ... ``` La partie modĂšle se rĂ©sume alors Ă  un type `Animal` et Ă  une fonction `getAnimals` qui interroge la base de donnĂ©es et retourne les enregistrements de type `Animal` dont le nom commence par le prĂ©fixe donnĂ©. Fichier `Animal.hpp` : ```c++ #include #include // Animal datatype struct Animal { std::string name; std::string image; }; // query database (select animals whose name begins with myquery) std::vector getAnimals(const std::string & myquery); ``` La partie vue contient deux fonctions retournant des pages au format HTML : `renderAbout` retourne la page d’information et `renderHome` retourne la page principale avec les animaux demandĂ©s par l’utilisateur. Fichier `View.hpp` : ```c++ #include "Animal.hpp" // render the about page to HTML std::string renderAbout(); // render the home page to HTML std::string renderHome( const std::string & myquery, const std::vector & animals); ``` Enfin la partie contrĂŽleur rĂ©cupĂšre les Ă©vĂ©nements du client puis met Ă  jour le modĂšle et la vue. Pour notre application, il n’y a pas de traitement compliquĂ© Ă  rĂ©aliser, juste Ă  rĂ©cupĂ©rer les requĂȘtes HTTP et Ă  appeler les fonctions prĂ©cĂ©dentes. ## Exemple en JavaScript Avant de voir comment dĂ©velopper cette application en C++, voici [une implĂ©mentation possible en JavaScript](https://framagit.org/nokomprendo/tuto_fonctionnel/tree/master/posts/tuto_fonctionnel_31/animals-nodejs), basĂ©e sur le classique cadriciel Node.js. Pour accĂ©der Ă  la base de donnĂ©es, on peut utiliser le paquet [better-sqlite3](https://github.com/JoshuaWise/better-sqlite3). Il suffit d’ouvrir la base de donnĂ©es, d’exĂ©cuter une requĂȘte SQL et de rĂ©cupĂ©rer les donnĂ©es au format [[JSON]]. Fichier [`animals-nodejs/src/animals.js`](https://framagit.org/nokomprendo/tuto_fonctionnel/tree/master/posts/tuto_fonctionnel_31/animals-nodejs/src/animal.js) : ```js "use strict"; const db = require("better-sqlite3")("animals.db"); // query database (select animals whose name begins with myquery) exports.getAnimals = myquery => db.prepare("SELECT name,image FROM animals WHERE name LIKE ?||'%'").all(myquery); ``` Pour la vue, le paquet [_pug_](https://pugjs.org) permet de gĂ©nĂ©rer du code HTML Ă  partir d’une chaĂźne de caractĂšres, en utilisant un formatage particulier. Ceci apporte plusieurs avantages : le formatage utilisĂ© est plus concis Ă  Ă©crire que du code HTML, il n’y a pas de risque d’oublier de fermer une balise HTML, on peut traiter facilement des donnĂ©es d’entrĂ©e, par exemple la liste des animaux Ă  afficher... Fichier [`animals-nodejs/src/view.js`](https://framagit.org/nokomprendo/tuto_fonctionnel/tree/master/posts/tuto_fonctionnel_31/animals-nodejs/src/view.js) : ```js "use strict"; const pug = require('pug'); // render the about page to HTML const aboutFunc = pug.compile(` doctype html html head link(rel="stylesheet", type="text/css", href="static/style.css") body h1 About (Node.js) p Generated by a(href="https://nodejs.org/en/") Node.js | , a(href="https://expressjs.com/") Express | , a(href="https://github.com/JoshuaWise/better-sqlite3") Better-sqlite3 | and a(href="https://pugjs.org/api/getting-started.html") Pug a(href="/") Home `); exports.renderAbout = aboutFunc; // render the home page to HTML const homeFunc = pug.compile(` doctype html html head link(rel="stylesheet", type="text/css", href="static/style.css") body h1 Animals (Node.js) form(action="/", method="get") input(name="myquery", value=myquery) each animal in animals a(href="static/"+animal.image) div(class="divCss") p= animal.name img(src="static/"+animal.image, class="imgCss") p(style="clear:both") a(href="/about") About `); exports.renderHome = (myquery, animals) => homeFunc(myquery, animals); ``` Cependant Pug ne vĂ©rifie pas les types de balises. Par exemple, si l’on demande une balise ``, Pug gĂ©nĂšrera bien le code ` ... `, alors que cette balise n’existe pas dans la norme HTML. Enfin, on utilise le trĂšs classique [express](https://expressjs.com/), pour lancer un serveur avec routage des requĂȘtes HTTP. Fichier [`animals-nodejs/src/app.js`](https://framagit.org/nokomprendo/tuto_fonctionnel/tree/master/posts/tuto_fonctionnel_31/animals-nodejs/src/app.js) : ```js "use strict"; const port = 3000; const view = require("./view.js") const animal = require("./animal.js") const express = require("express"); const app = express(); // serve the about page app.get("/about", function (request, response) { const html = view.renderAbout(); response.send(html); }); // serve the home page (and filter the animals using the myquery parameter) app.get("/", function (request, response) { const myquery = request.query.myquery ? request.query.myquery : "" const animals = animal.getAnimals(myquery); const html = view.renderHome({myquery, animals}); response.send(html); }); // serve static files (located in the "static" directory) app.use("/static", express.static("./static")); // run a server listening on port 3000 app.listen(port, function () { console.log(`Listening on port ${port}...`); }); ``` À noter que Node.js est un cadriciel asynchrone, c’est‐à‐dire que des fonctions peuvent ĂȘtre appelĂ©es de façon non bloquante. Ceci permet d’optimiser les performances de l’application gĂ©nĂ©rale, au prix d’un peu de complexitĂ© de programmation (_promise/callback, async/await..._). Pour notre application, cela n’a pas vraiment d’influence, car la principale fonction potentiellement concernĂ©e (la fonction d’accĂšs Ă  la base de donnĂ©es, via better-sqlite3) est bloquante. ## Exemple en Haskell L’application est Ă©galement simple Ă  implĂ©menter en Haskell, avec le cadriciel [scotty](http://hackage.haskell.org/package/scotty). Pour le modĂšle, on dĂ©finit un type `Animal` et une fonction d’accĂšs Ă  la base de donnĂ©es par requĂȘte SQL via [sqlite-simple](https://hackage.haskell.org/package/sqlite-simple). Fichier [`animals-scotty/src/Animal.hs`](https://framagit.org/nokomprendo/tuto_fonctionnel/tree/master/posts/tuto_fonctionnel_31/animals-scotty/src/Animal.hs) : ```hs {-# LANGUAGE OverloadedStrings #-} module Animal where import qualified Data.Text.Lazy as L import qualified Database.SQLite.Simple as SQL import Database.SQLite.Simple.FromRow (FromRow, fromRow, field) -- Animal datatype data Animal = Animal { animalName :: L.Text , animalImage :: L.Text } deriving Show -- deserialize an Animal from the database instance FromRow Animal where fromRow = Animal <$> field <*> field -- query database (select animals whose name begins with myquery) getAnimals :: L.Text -> IO [Animal] getAnimals myquery = do let req = "SELECT name,image FROM animals WHERE name LIKE ?||'%'" SQL.withConnection "animals.db" (\conn -> SQL.query conn req (SQL.Only myquery)) ``` Pour la gĂ©nĂ©ration du code HTML et du code CSS, Haskell dispose de [DSL](https://fr.wikipedia.org/wiki/Langage_d%C3%A9di%C3%A9) (_Domain Specific Languages_), ici [lucid](https://hackage.haskell.org/package/lucid) et [clay](https://hackage.haskell.org/package/clay). Ceci permet non seulement d’assurer le formatage correct des balises mais Ă©galement que ces balises sont bien correctes. Ainsi, si l’on essaie de gĂ©nĂ©rer une balise `` (qui n’existe pas dans la norme HTML), le compilateur indiquera une erreur. Fichier [`animals-scotty/src/View.hs`](https://framagit.org/nokomprendo/tuto_fonctionnel/tree/master/posts/tuto_fonctionnel_31/animals-scotty/src/View.hs) : ```hs {-# LANGUAGE OverloadedStrings #-} module View where import Animal import qualified Clay as C import Control.Monad(forM_) import Lucid import qualified Data.Text.Lazy as L -- render the about page to HTML aboutPage :: L.Text aboutPage = renderText $ html_ $ do head_ $ style_ $ L.toStrict $ C.render $ myCss body_ $ do h1_ "About (Scotty)" p_ $ do "Generated by " a_ [href_ "http://hackage.haskell.org/package/scotty"] "Scotty" p_ $ a_ [href_ "/"] "Home" -- render the home page to HTML homePage :: L.Text -> [Animal] -> L.Text homePage myquery animals = renderText $ html_ $ do head_ $ style_ $ L.toStrict $ C.render $ myCss body_ $ do h1_ "Animals (Scotty)" -- add the HTML form form_ [action_ "/", method_ "get"] $ do input_ [name_ "myquery", value_ $ L.toStrict myquery] -- add every animal in a HTML div forM_ animals $ \ animal -> do let img = L.toStrict $ L.concat ["./img/", animalImage animal] a_ [href_ img] $ div_ [class_ "divCss"] $ do p_ $ toHtml $ animalName animal img_ [src_ img, class_ "imgCss"] p_ [style_ "clear: both"] $ a_ [href_ "/about"] "About" -- our CSS styles myCss :: C.Css myCss = do C.a C.# C.byClass "aCss" C.? do C.textDecoration C.none C.color C.inherit C.body C.? do C.backgroundColor C.azure C.div C.# C.byClass "divCss" C.? do C.backgroundColor C.beige C.border C.solid (C.px 1) C.black C.margin (C.em 1) (C.em 1) (C.em 1) (C.em 1) C.width (C.px 320) C.textAlign C.center C.float C.floatLeft C.img C.# C.byClass "imgCss" C.? do C.width (C.px 320) C.height (C.px 240) ``` Enfin, le serveur principal se rĂ©sume Ă  router les requĂȘtes HTTP en utilisant les fonctions du cadriciel scotty. Fichier [`animals-scotty/src/Main.hs`](https://framagit.org/nokomprendo/tuto_fonctionnel/tree/master/posts/tuto_fonctionnel_31/animals-scotty/src/Main.hs) : ```hs {-# LANGUAGE OverloadedStrings #-} import Animal import View import Control.Monad.Trans (liftIO) import Network.Wai.Middleware.RequestLogger (logStdoutDev) import Network.Wai.Middleware.Static (addBase, staticPolicy) import Web.Scotty (get, html, middleware, param, rescue, scotty) -- run a server listening on port 3000 main = scotty 3000 $ do -- show logs middleware logStdoutDev -- serve the about page get "/about" $ html aboutPage -- serve the home page (and filter the animals using the myquery parameter) get "/" $ do myquery <- param "myquery" `rescue` (\_ -> return "") animals <- liftIO $ getAnimals myquery html $ homePage myquery animals -- serve static files (located in the "static" directory) middleware $ staticPolicy $ addBase "static" ``` Par conception, Haskell permet Ă©galement de faire de l’asynchrone assez facilement (langage fonctionnel pur, environnement d’exĂ©cution supportant les [_green threads_](https://en.wikipedia.org/wiki/Green_threads)...). Au final, l’implĂ©mentation Haskell est assez similaire Ă  l’implĂ©mentation JavaScript. La principale diffĂ©rence est que Haskell fait du typage statique, ce qui allonge lĂ©gĂšrement le code mais permet de dĂ©tecter les erreurs de typage plus prĂ©cocement. # GĂ©nĂ©ration de HTML ## Les gĂ©nĂ©rateurs de documents HTML C++ ne semble pas avoir d’outils de gĂ©nĂ©ration de documents HTML aussi aboutis que Lucid en Haskell. La bibliothĂšque [CTML](https://github.com/tinfoilboy/CTML) permet de dĂ©finir la structure arborescente d’un document puis d’en gĂ©nĂ©rer le code HTML correspondant. Cependant, sa syntaxe est assez verbeuse et il n’y a pas de vĂ©rification des balises. Exemple d’utilisation ([`animals-cpprestsdk/src/View.cpp`](https://framagit.org/nokomprendo/tuto_fonctionnel/tree/master/posts/tuto_fonctionnel_31/animals-cpprestsdk/src/View.cpp)) : ```cpp const string css = R"( body { background-color: azure; } ... )"; string renderHome(const string & myquery, const vector & animals) { CTML::Document doc; doc.AddNodeToHead( CTML::Node("style", css)); doc.AddNodeToBody( CTML::Node("h1", "Animals (Cpprestsdk)")); doc.AddNodeToBody( CTML::Node("form") .AppendChild( CTML::Node("input") .UseClosingTag(false) .SetAttribute("type", "text") .SetAttribute("name", "myquery") .SetAttribute("value", myquery))); for (const Animal & animal : animals) doc.AddNodeToBody( CTML::Node("a.aCss") .SetAttribute("href", "static/"+animal.image) .AppendChild( CTML::Node("div.divCss") .AppendChild( CTML::Node("p", animal.name)) .AppendChild( CTML::Node("img.imgCss") .UseClosingTag(false) .SetAttribute("src", "static/"+animal.image)))); doc.AddNodeToBody( CTML::Node("p") .SetAttribute("style", "clear:both") .AppendChild( CTML::Node("a", "About") .SetAttribute("href", "/about"))); return doc.ToString(CTML::Readability::MULTILINE); } string renderAbout() { ... } ``` ## Les systĂšmes de patrons Ces systĂšmes consistent Ă  Ă©crire des patrons (_templates_) paramĂ©trables, c’est‐à‐dire du code HTML dans lequel on utilise des paramĂštres qui seront remplacĂ©s par les valeurs indiquĂ©es lors du rendu du patron. Les cadriciels MVC proposent gĂ©nĂ©ralement des systĂšmes de patrons Ă©voluĂ©s, mais il existe Ă©galement des outils indĂ©pendants, par exemple [mustache](http://mustache.github.io/). Mustache est un formalisme qui possĂšde des implĂ©mentations dans de nombreux langages, dont plusieurs en C++. Par exemple, [`animal-pistache/src/View.cpp`](https://framagit.org/nokomprendo/tuto_fonctionnel/blob/master/posts/tuto_fonctionnel_31/animals-pistache/src/View.cpp) utilise l’implĂ©mentation [kainjow mustache](https://github.com/kainjow/Mustache) et le code suivant ([`animals-crow/src/View.cpp`](https://framagit.org/nokomprendo/tuto_fonctionnel/blob/master/posts/tuto_fonctionnel_31/animals-crow/src/View.cpp)) l’implĂ©mentation du cadriciel [crow](https://github.com/ipkn/crow) : ```cpp const string css = ... string renderHome(const string & myquery, const vector & animals) { // create the template const string homeTmpl = R"(

Animals (Crow)

{{#animals}}

{{name}}

{{/animals}}

About

AltStyle ă«ă‚ˆăŁăŠć€‰æ›ă•ă‚ŒăŸăƒšăƒŒă‚ž (->ă‚ȘăƒȘă‚žăƒŠăƒ«) /

)"; // create a context containing the data to use in the template crow::mustache::context ctx; ctx["mycss"] = css; ctx["myquery"] = myquery; for (unsigned i=0; i & animals) { // create a string stream ostringstream oss; // generate some HTML code, in the stream oss << R"(

Animals (Silicon)

)"; for (const Animal & a : animals) { oss << R"(

)" << a.name << R"(

)"; } oss << R"(

About

)"; // return the resulting string return oss.str(); } string renderAbout() { ... } ``` # AccĂšs Ă  une base de donnĂ©es SQL ## Les connecteurs SQL Ils permettent de construire explicitement des requĂȘtes SQL, de les envoyer au systĂšme de base de donnĂ©es et d’en rĂ©cupĂ©rer le rĂ©sultat. Les connecteurs SQL sont gĂ©nĂ©ralement faciles Ă  utiliser (il suffit de connaĂźtre le langage SQL) mais ils ne vĂ©rifient pas que les requĂȘtes sont correctes. De nombreux cadriciels proposent des connecteurs SQL. Par exemple, [cppcms](http://cppcms.com/wikipp/en/page/main/) (voir [`animals-cppcms/src/Animal.cpp`](https://framagit.org/nokomprendo/tuto_fonctionnel/tree/master/posts/tuto_fonctionnel_31/animals-cppcms/src/Animal.cpp)), [tntnet](http://www.tntnet.org/tntnet.html) (voir [`animals-tntnet/src/Animal.cc`](https://framagit.org/nokomprendo/tuto_fonctionnel/tree/master/posts/tuto_fonctionnel_31/animals-tntnet/src/Animal.cc)) et [silicon](http://siliconframework.org/) (voir [`animals-silicon/src/main.cpp`](https://framagit.org/nokomprendo/tuto_fonctionnel/tree/master/posts/tuto_fonctionnel_31/animals-silicon/src/main.cpp)). Il existe Ă©galement des connecteurs indĂ©pendants, par exemple [sqlite_modern_cpp](https://github.com/SqliteModernCpp/sqlite_modern_cpp) (voir [`animals-pistache/src/Animal.cpp`](https://framagit.org/nokomprendo/tuto_fonctionnel/tree/master/posts/tuto_fonctionnel_31/animals-pistache/src/Animal.cpp)) : ```cpp #include "Animal.hpp" #include using namespace sqlite; using namespace std; vector getAnimals(const string & myquery) { vector animals; try { // open database database db("animals.db"); // query database and process results db << "SELECT name,image FROM animals WHERE name LIKE ?||'%'" << myquery>> [&](string name, string image) { animals.push_back({name, image}); }; } catch (exception & e) { cerr << e.what() << endl; } return animals; } ``` ## Les ORM Les ORM (_Object‐Relational Mapping_) permettent de convertir des donnĂ©es d’une table d’une base vers une classe C++, et rĂ©ciproquement. Ceci permet d’utiliser la base de façon plus sĂ»re, car les donnĂ©es sont vĂ©rifiĂ©es par le systĂšme de typage et vĂ©rifiĂ©es Ă  la compilation puisque les requĂȘtes sont rĂ©alisĂ©es par des fonctions C++. Cependant, un ORM dĂ©finit sa propre couche d’abstraction Ă©quivalente au SQL, mais forcĂ©ment moins connue. Il existe diffĂ©rents ORM C++, par exemple [wt](https://www.webtoolkit.eu/wt) (voir [`animals-wt/src/main.cpp`](https://framagit.org/nokomprendo/tuto_fonctionnel/tree/master/posts/tuto_fonctionnel_31/animals-wt/src/main.cpp)), [sqlpp11](https://github.com/rbock/sqlpp11) (voir [`animals-crow/src/Animal.cpp`](https://framagit.org/nokomprendo/tuto_fonctionnel/tree/master/posts/tuto_fonctionnel_31/animals-crow/src/Animal.cpp)) ou [sqlite_orm](https://github.com/fnc12/sqlite_orm) (voir [`animals-cpprestsdk/src/Animal.cpp`](https://framagit.org/nokomprendo/tuto_fonctionnel/tree/master/posts/tuto_fonctionnel_31/animals-cpprestsdk/src/Animal.cpp)) : ```cpp #include "Animal.hpp" #include using namespace std; using namespace sqlite_orm; vector getAnimals(const string & myquery) { vector animals; // open database and map the "animals" table to the "Animal" datatype auto storage = make_storage( "animals.db", make_table("animals", make_column("name", &Animal::name), make_column("image", &Animal::image))); // query database auto results = storage.get_all(where(like(&Animal::name, myquery+"%"))); // process results for(auto & animal : results) animals.push_back(animal); return animals; } ``` Suite en partie 2 (cadriciels Web), dans laquelle il sera question de micro‐cadriciels, tels que Sinatra et Flask pour le Ruby et le Python, et de Crow et Silicon pour le C++.