Diese Anwendung und das Tutorial basieren auf dem Beispiel im Artikel „Quelques cadriciels Web C++“, der auf LinuxFr.org veröffentlicht wurde.
Derzeit gibt es viele interessante Sprachen und Frameworks für die serverseitige Webentwicklung. In diesem Bereich ist C++ nicht die modernste Sprache, verfügt aber über einige interessante Vorzüge. In der Tat:
Der Zweck dieses Tutorials besteht darin, Folgendes bereitzustellen:
Quellcodes für Cutelyst sind hier enthalten. Quellcodes für andere C++-Webframeworks sind in diesem Git-Repository verfügbar. Die verschiedenen verwendeten Frameworks sind im Anhang zusammengefasst. Schließlich ist auf Awesome C++ eine Liste der C++-Bibliotheken verfügbar.
Wir möchten eine Anwendung implementieren, die auf dem Server gespeicherte Bilder von Tieren anzeigt. Mithilfe eines Formulars wird der Anfang des Namens der anzuzeigenden Tiere angegeben. Sie können das Bild in voller Größe anzeigen, indem Sie auf das Miniaturbild klicken. Über einen Link am Ende der Seite können Sie eine Informationsseite anzeigen. Tierdaten (Namen und Dateipfade) werden in einer SQLite-Datenbank auf dem Server gespeichert.
Hierbei erfolgt die Generierung von HTML-Seiten auf dem Server, wobei der aktuelle Trend eher dahin geht, eine serverseitige API bereitzustellen und clientseitiges HTML zu generieren.
Auf sehr konventionelle Weise kann man den Code dieser Anwendung nach einer MVC-Architektur organisieren, d. h. durch Unterscheidung der Daten (Modell), ihrer Anzeige (Ansicht) und ihrer Verwaltung (Controller).
Für unsere Anwendung stehen die Bilder auf dem Server zur Verfügung und wir nutzen eine SQLite-Datenbank, die eine Tabelle mit den Namen und Dateipfaden der Tiere enthält. Datei animals.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 ' );
...
Der Modellteil besteht dann aus einem Animal-Typ und einer Funktion getAnimals, die die Datenbank abfragt und die Typdatensätze Animal zurückgibt, deren Name mit dem angegebenen Präfix beginnt. Datei Animal.hpp:
Der Ansichtsteil enthält zwei Funktionen, die Seiten im HTML-Format zurückgeben: renderAbout gibt die Informationsseite zurück und renderHome gibt die Hauptseite mit den vom Benutzer angeforderten Tieren zurück. Dateiansicht.hpp:
Schließlich ruft der Controller-Teil die Ereignisse des Clients ab und aktualisiert dann das Modell und die Ansicht. Für unsere Anwendung ist keine komplizierte Verarbeitung erforderlich, sondern lediglich das Abrufen von HTTP-Anfragen und das Aufrufen der vorherigen Funktionen.
C++ scheint nicht über so erfolgreiche Tools zur Generierung von HTML-Dokumenten zu verfügen wie Lucid in Haskell. Mithilfe der CTML-Bibliothek wird die Baumstruktur eines Dokuments definiert und anschließend der entsprechende HTML-Code generiert. Allerdings ist die Syntax recht ausführlich und es gibt keine Überprüfung der Tags.
Diese Systeme bestehen aus dem Schreiben anpassbarer Vorlagen, also HTML-Code, in dem Parameter verwendet werden, die durch die beim Rendern der Vorlage angegebenen Werte ersetzt werden.
MVC-Frameworks bieten typischerweise erweiterte Mustersysteme, es gibt aber auch unabhängige Tools, zum Beispiel Moustache. Moustache ist ein Formalismus, der in vielen Sprachen implementiert ist, darunter mehrere in C++. Beispielsweise verwendet animal-pistache/src/View.cpp die Kainjow-Mustache-Implementierung und der folgende Code ( Animals-crow/src/View.cpp) die Implementierung des Crow-Frameworks:
const string css = ...
string renderHome ( const string & myquery, const vector<Animal> & animals) {
// create the template
const string homeTmpl = R"(
<html>
<head>
<style>
{{mycss}}
</style>
</head>
<body>
<h1>Animals (Crow)</h1>
<form>
<p> <input type="text" name="myquery" value="{{myquery}}"> </p>
</form>
{{#animals}}
<a href="static/{{image}}">
<div class="divCss">
<p> {{name}} </p>
<img class="imgCss" src="static/{{image}}" />
</div>
</a>
{{/animals}}
<p style="clear: both"><a href="/about">About</a></p>
</body>
</html>
)" ;
// 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. size (); i++) {
ctx[ " animals " ][i][ " name " ] = animals[i]. name ;
ctx[ " animals " ][i][ " image " ] = animals[i]. image ;
}
// render the template using the context
return crow::mustache::template_t (homeTmpl). render (ctx);
}
string renderAbout () {
...
}
Es ist auch relativ einfach, HTML-Code mithilfe von C++-Kanalströmen manuell zu generieren. Diese Methode erleichtert jedoch nicht die Wiederverwendung von Code oder die Überprüfung des erzeugten HTML-Codes. Beispiel einer manuellen Generierung ( Animals-silicon/src/main.cpp):
string renderHome ( const string & myquery, const vector<Animal> & animals) {
// create a string stream
ostringstream oss;
// generate some HTML code, in the stream
oss << R"(
<html>
<head>
<link rel="stylesheet" type="text/css" href="mystatic/style.css">
</head>
<body>
<h1>Animals (Silicon)</h1>
<form>
<p> <input type="text" name="myquery" value=" )" << myquery << R"( "> </p>
</form>
)" ;
for ( const Animal & a : animals) {
oss << R"(
<a href="mystatic/ )" << a. image << R"( ">
<div class="divCss">
<p> )" << a. name << R"( </p>
<img class="imgCss" src="mystatic/ )" << a. image << R"( " />
</div>
</a> )" ;
}
oss << R"(
<p style="clear: both"><a href="/about">About</a></p>
</body>
</html>
)" ;
// return the resulting string
return oss. str ();
}
string renderAbout () {
...
}
Sie ermöglichen es, SQL-Abfragen explizit aufzubauen, an das Datenbanksystem zu senden und das Ergebnis abzurufen. SQL-Konnektoren sind im Allgemeinen einfach zu verwenden (Sie kennen nur die SQL-Sprache), überprüfen jedoch nicht, ob die Abfragen korrekt sind.
Viele Frameworks bieten SQL-Konnektoren an. Zum Beispiel cppcms (siehe Animals-cppcms/src/Animal.cpp), tntnet (siehe Animals-tntnet/src/Animal.cc) und Silicon (siehe Animals-silicon/src/main.cpp). Es gibt auch unabhängige Konnektoren, zum Beispiel sqlite_modern_cpp (siehe Animals-pistache/src/Animal.cpp):
# include " Animal.hpp "
# include < sqlite_modern_cpp.h >
using namespace sqlite ;
using namespace std ;
vector<Animal> getAnimals ( const string & myquery) {
vector<Animal> 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;
}
Object-Relational Mapping (ORM) wird verwendet, um Daten aus einer Tabelle in einer Datenbank in eine C++-Klasse und umgekehrt zu konvertieren. Dies ermöglicht eine sicherere Nutzung der Datenbank, da die Daten vom Typisierungssystem und zur Kompilierungszeit überprüft werden, da die Anforderungen durch C++-Funktionen erfolgen. Allerdings definiert ein ORM seine eigene Abstraktionsschicht, die SQL entspricht, aber zwangsläufig weniger bekannt ist.
Es gibt verschiedene C++ ORMs, zum Beispiel wt (siehe Animals-wt/src/main.cpp), sqlpp11 (siehe Animals-crow/src/Animal.cpp) oder sqlite_orm (siehe Animals-cpprestsdk/src/Animal.cpp):
# include " Animal.hpp "
# include < sqlite_orm/sqlite_orm.h >
using namespace std ;
using namespace sqlite_orm ;
vector<Animal> getAnimals ( const string & myquery) {
vector<Animal> 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 <Animal>( where ( like (&Animal::name, myquery+ " % " )));
// process results
for ( auto & animal : results)
animals. push_back (animal);
return animals;
}
Micro-Web-Frameworks wie Sinatra in Ruby oder Flask in Python zielen darauf ab, einfach und leicht zu sein. Sie bieten hauptsächlich Funktionen zur Verarbeitung von HTTP-Anfragen sowie einen URL-Routing-Mechanismus. Bei Bedarf können sie durch weitere Bibliotheken ergänzt werden (HTML-Generierung, Zugriff auf eine Datenbank in SQL...).
Es gibt mehrere C++-Mikroframeworks, zum Beispiel crow (siehe Animals-Crow) oder Silicon (siehe Animals-Silicon).
Dabei machen die Features des modernen C++ den Code prägnant und angenehm lesbar.
In einer Vorbehandlungsphase generiert Silicon die Datei symbols.hh, die die vom Programmierer definierten Symbole einschließlich Routen (_about, _home, _mystatic...) deklariert. Dadurch ist es möglich, statisch zu überprüfen, ob die Routen im Code korrekt verwendet werden. Andere Sprachen verwenden Selbstbeobachtung, um diese Art der Überprüfung durchzuführen, C++ verfügt jedoch nicht über diese Funktion.
Asynchrone Frameworks wie Node.js/Express in JavaScript bieten die gleichen Funktionalitäten wie herkömmliche Mikro-Frameworks, jedoch über nicht blockierende Funktionen. Wenn also eine Anfrage eine Ressource benötigt, kann die Anwendung zu einer anderen Anfrage wechseln, während sie darauf wartet, dass die Ressource verfügbar ist. Dies verbessert die Gesamtleistung der Anwendung, erfordert jedoch einen bestimmten Programmierstil, der auf Versprechen basiert, die bis dahin mit Rückruffunktionen verbunden sind, um eine Kette asynchroner Verarbeitung zu bilden.
In C++ gibt es verschiedene asynchrone Frameworks, zum Beispiel cpprestsdk (siehe Animals-cpprestsdk) und pistachio (siehe Animals-pistachio).
Hier finden wir eine klassische Routenverwaltung (mit dem Namen der Route und ihrer Verarbeitungsfunktion). Allerdings haben wir jetzt einen asynchronen Betrieb über nicht blockierende Funktionen. Für die „statische“ Route gibt die Funktion „serveFile“ beispielsweise ein Versprechen zurück, das mit einer Rückruffunktion verbunden ist, die eine Protokollmeldung anzeigt, sobald das Versprechen aufgelöst ist.
MVC-Webframeworks wie Ruby on Rails oder Python Django sind klassische Tools, deren Ziel es ist, jede Art von Webanwendungen zu implementieren. Sie bieten normalerweise alle notwendigen Funktionen: URL-Routing, Vorlagensystem, Zugriff auf Datenbanken, Authentifizierungssystem ... Die MVC-Frameworks scheinen nicht die bevorzugte Domäne von C++ zu sein, aber es gibt dennoch einige interessante Tools, darunter cppcms und cutelyst .
Zusätzlich zu den klassischen Funktionen eines MVC-Frameworks bietet cppcms ein recht fortschrittliches Vorlagensystem mit Ansichtsvererbung und Inhaltsverwaltung. Beispielsweise kann man eine Hauptansicht MasterView definieren und daraus Ansichten ableiten. AboutView und HomeView erben die Eigenschaften von MasterView und ergänzen diese. Schließlich können wir diesen Ansichten (Parameter der Vorlagen) einen Inhalt zuordnen, ebenfalls mit einem Vererbungssystem. Anhand des vorherigen Beispiels können wir einen Inhalt „MasterContent“ für die Ansicht „MasterView“ definieren, daraus „HomeContent“ für die Ansicht „HomeView“ ableiten und direkt „MasterContent“ für die Ansicht „AboutView“ verwenden (kein neuer Parameter in der Vorlage).
MVC-Frameworks sind effektive Werkzeuge zur Implementierung komplexer Anwendungen. Sie erfordern jedoch viel Schulung und können für kleine, einfache Anwendungen überdimensioniert werden.
Das tntnet-Framework bietet ein vorlagenbasiertes System, ähnlich wie PHP. Auch wenn dieses Framework im C++-Ökosystem eher anekdotisch ist, scheint es in seinem Ansatz recht effektiv zu sein: klassischen HTML-Code zu schreiben und Abschnitte von C++-Code dort hinzuzufügen, wo es notwendig ist.
Beachten Sie, dass diese Art von Framework möglicherweise weniger für die Entwicklung komplexer Anwendungen geeignet ist (Lesbarkeit von Vorlagen, Wiederverwendung usw.).
Diese Tools sind von Desktop-Grafik-Frameworks wie Qt oder GTKMM inspiriert, das heißt, sie basieren auf einer Hierarchie von Widgets, die die Schnittstelle bilden und über einen Signal-Slot-Mechanismus interagieren.
Webbasierte Widgets sind überraschend unbeliebt, selbst in allen Sprachen, obwohl ihr Potenzial groß erscheint. Tatsächlich ermöglichen sie die Entwicklung einer Client-Server-Fullstack-Anwendung unter Verwendung einer klassischen grafischen Schnittstellenbibliothek, ohne sich zu viele Gedanken über die Netzwerkarchitektur der Anwendung machen zu müssen.
In C++ ist das erfolgreichste Framework in dieser Kategorie sicherlich Wt. Wt verfügt über viele klassische oder erweiterte Widgets, ein SQL ORM, ein Authentifizierungssystem, die Möglichkeit, HTML und CSS zu manipulieren usw. In Wt besteht das Hauptprogramm darin, URLs an die entsprechenden Anwendungen weiterzuleiten.
Diese Wt-Anwendungen entsprechen herkömmlichen grafischen Oberflächen, jedoch mit einer Client-Server-Architektur.
Für eine komplexere Anwendung, beispielsweise die Seite, auf der die Tiere angezeigt werden, können wir ein neues Widget definieren, das ein Miniaturbild implementiert, und dann diese Klasse verwenden, um alle in der Datenbank gelesenen Tiere anzuzeigen.
Auf den ersten Blick mag diese Implementierung länger und komplizierter erscheinen als frühere Implementierungen. Der Code dürfte jedoch jedem Desktop-GUI-Entwickler bekannt vorkommen. Darüber hinaus verwaltet diese Implementierung die gesamte Anwendung (Fullstack) und nicht nur den Serverteil. Beispielsweise beinhaltet die Verbindung des Signals _myquery->textInput()
mit der Funktion HomeApp::filterAnimals
clientseitige Aktualisierungen in Echtzeit, die mit früheren Frameworks viel schwieriger zu implementieren wären.
Für die Entwicklung von Back-End-Webanwendungen ist C++ eine sehr praktikable Option. Mit den neuesten Entwicklungen ist die Sprache im Allgemeinen einfacher und sicherer zu verwenden, ohne dass die Leistung beeinträchtigt wird. Für die Webentwicklung stehen viele C++-Bibliotheken zur Verfügung: Vorlagen, HTML-Generierung, SQL-Verbindung, ORM... Auch Web-Frameworks sind zahlreich und vielfältig: MVC-Frameworks wie RoR und Django, Mikro-Frameworks wie Sinatra und Flask, asynchrone Frameworks wie Node.js , PHP-Vorlagen-basierte Frameworks und sogar Fullstack-Frameworks, die auf Widgets basieren. Das alles wird natürlich vor allem Entwickler interessieren, die C++ bereits kennen, denn auch viele andere Sprachen verfügen über sehr interessante Tools für die Webentwicklung.
Die folgenden Frameworks, Bibliotheken und Tools dienen zur Implementierung des HTTP-Servers, der HTML-Generierung und des SQL-Datenbankzugriffs.
Projekt | Web-Framework | HTML-Generator | SQL-Schnittstelle |
---|---|---|---|
Tiere-cppcms | cppcms (Webframework) | cppcms (Vorlagensystem) | cppcms (SQL-Connector) |
Animals-cpprestsdk | cpprestsdk (asynchrones Netzwerk-Framework) | ctml (HTML-Dokumentgenerator) | sqlite_orm (ORM) |
Tiere-Krähe | http: crow (leichtes Web-Framework) | Krähe (Vorlagensystem) | sqlpp11 (ORM) |
Tiere-Cutelyst | Cutelyst (Web-Framework) | grantlee (Vorlagensystem) | cutelyst (SQL-Connector) |
Animals-nodejs (Javascript/Node.js) | Express (asynchrones, leichtes Web-Framework) | Mops (Dokumentengenerator) | better-sqlite3 (SQL-Connector) |
Tiere-Pistazien | pistache (asynchrones, leichtes Web-Framework) | Kainjow-Schnurrbart (Schablonensystem) | sqlite_modern_cpp (SQL-Connector) |
Tiere-Scotty (Haskell) | Scotty (leichtes Web-Framework) | lucid und clay (Dokumentgeneratoren) | sqlite-simple (SQL-Connector) |
Tiere-Silizium | Silizium (leichtes Web-Framework) | keiner | Silizium (SQL-Connector) |
Tiere-tntnet | tntnet (vorlagenbasiertes Webframework) | tntnet (Vorlagensystem) | tntnet (SQL-Connector) |
Tiere-Gew | wt (Web-GUI-Framework) | wt (Widget-System + Vorlagen) | Gewicht (ORM) |