Esta aplicación y tutorial se basan en el ejemplo del artículo Quelques cadriciels Web C++ publicado en LinuxFr.org.
Actualmente, existen muchos lenguajes y marcos interesantes para el desarrollo web del lado del servidor. En esta área, C++ no es el lenguaje más de moda, pero tiene algunas ventajas interesantes. En efecto:
El propósito de este tutorial es proporcionar:
Los códigos fuente de Cutelyst se incluyen aquí. Los códigos fuente para otros marcos web C++ están disponibles en este repositorio de Git. Los diferentes marcos que se utilizaron se resumen en el apéndice. Finalmente, hay una lista de bibliotecas de C++ disponible en Awesome C++.
Queremos implementar una aplicación que muestre imágenes de animales almacenadas en el servidor. Se utiliza un formulario para indicar el comienzo del nombre de los animales a mostrar. Puede mostrar la imagen en tamaño completo haciendo clic en la miniatura y puede mostrar una página de información a través de un enlace en la parte inferior de la página. Los datos de los animales (nombres y rutas de archivos) se almacenan en una base de datos SQLite en el servidor.
Aquí, la generación de páginas HTML se realiza en el servidor, aunque la tendencia actual es más bien proporcionar una API del lado del servidor y generar HTML del lado del cliente.
De forma muy convencional, se puede organizar el código de esta aplicación según una arquitectura de tipo MVC, es decir distinguiendo los datos (modelo), su visualización (vista) y su gestión (controlador).
Para nuestra aplicación, las imágenes están disponibles en el servidor y utilizamos una base de datos SQLite que contiene una tabla con los nombres y rutas de archivo de los animales. Archivo 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 ' );
...
Luego, la parte del modelo se reduce a un tipo Animal y una función getAnimals que consulta la base de datos y devuelve los registros de tipo Animal cuyo nombre comienza con el prefijo dado. Archivo Animal.hpp:
La parte de vista contiene dos funciones que devuelven páginas en formato HTML: renderAbout devuelve la página de información y renderHome devuelve la página principal con los animales solicitados por el usuario. Vista de archivo.hpp:
Finalmente, la parte del controlador recupera los eventos del cliente y luego actualiza el modelo y la vista. Para nuestra aplicación, no es necesario realizar ningún procesamiento complicado, solo recuperar solicitudes HTTP y llamar a las funciones anteriores.
C++ no parece tener herramientas de generación de documentos HTML tan exitosas como Lucid en Haskell. La biblioteca CTML se utiliza para definir la estructura de árbol de un documento y luego generar el código HTML correspondiente. Sin embargo, su sintaxis es bastante detallada y no hay verificación de etiquetas.
Estos sistemas consisten en escribir plantillas personalizables, es decir código HTML en el que se utilizan parámetros que serán sustituidos por los valores indicados al renderizar la plantilla.
Los marcos MVC suelen ofrecer sistemas de patrones avanzados, pero también hay herramientas independientes, por ejemplo, bigote. Moustache es un formalismo que tiene implementaciones en muchos lenguajes, incluidos varios en C++. Por ejemplo, animal-pistache/src/View.cpp usa la implementación del bigote kainjow y el siguiente código (animals-crow/src/View.cpp) la implementación del marco crow:
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 () {
...
}
También es relativamente sencillo generar código HTML manualmente, utilizando flujos de canales C++. Sin embargo, este método no facilita la reutilización del código ni la verificación del código HTML producido. Ejemplo de generación manual (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 () {
...
}
Permiten crear consultas SQL explícitamente, enviarlas al sistema de base de datos y recuperar el resultado. Los conectores SQL son generalmente fáciles de usar (basta con conocer el lenguaje SQL) pero no verifican que las consultas sean correctas.
Muchos marcos ofrecen conectores SQL. Por ejemplo, cppcms (ver animales-cppcms/src/Animal.cpp), tntnet (ver animales-tntnet/src/Animal.cc) y silicio (ver animales-silicon/src/main.cpp). También hay conectores independientes, por ejemplo sqlite_modern_cpp (ver animales-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;
}
El mapeo relacional de objetos (ORM) se utiliza para convertir datos de una tabla en una base de datos a una clase C++ y viceversa. Esto hace posible utilizar la base de datos de forma más segura porque los datos son verificados por el sistema de escritura y verificados en el momento de la compilación, ya que las solicitudes se realizan mediante funciones de C++. Sin embargo, un ORM define su propia capa de abstracción equivalente a SQL, pero necesariamente menos conocida.
Hay diferentes ORM de C++, por ejemplo wt (ver animales-wt/src/main.cpp), sqlpp11 (ver animales-crow/src/Animal.cpp) o sqlite_orm (ver animales-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;
}
Los micro frameworks web, como Sinatra en Ruby o Flask en Python, pretenden ser simples y ligeros. Ofrecen principalmente funciones para manejar solicitudes HTTP, así como un mecanismo de enrutamiento de URL. Si es necesario, se pueden completar con otras bibliotecas (generación de HTML, acceso a una base de datos en SQL...).
Hay varios micro-frameworks de C++, por ejemplo cuervo (ver animales-cuervo) o silicio (ver animales-silicio).
Aquí, las características del C++ moderno hacen que el código sea conciso y bastante agradable de leer.
En una fase de pretratamiento, Silicon genera el archivo símbolos.hh, que declara los símbolos definidos por el programador, incluidas las rutas ( _about, _home, _mystatic...). Esto permite verificar estáticamente que las rutas se utilizan correctamente en el código. Otros lenguajes utilizan la introspección para realizar este tipo de verificación, pero C++ no tiene esta característica.
Los marcos asincrónicos, como Node.js/Express en JavaScript, ofrecen las mismas funcionalidades que los micromarcos convencionales pero a través de funciones sin bloqueo. Por lo tanto, si una solicitud necesita un recurso, la aplicación puede cambiar a otra solicitud mientras espera que el recurso esté disponible. Esto mejora el rendimiento general de la aplicación, pero requiere un estilo de programación particular, basado en promesas conectadas a funciones de devolución de llamada para luego formar una cadena de procesamiento asincrónico.
Existen diferentes marcos asincrónicos en C++, por ejemplo cpprestsdk (ver animales-cpprestsdk) y pistacho (ver animales-pistacho).
Aquí encontramos una gestión clásica de ruta (con el nombre de la ruta y su función de procesamiento). Sin embargo, ahora tenemos operación asincrónica, mediante funciones sin bloqueo. Por ejemplo, para la ruta "estática", la función serverFile devuelve una promesa que está conectada a una función de devolución de llamada, que muestra un mensaje de registro una vez que se resuelve la promesa.
Los frameworks web MVC, como Ruby on Rails o Python Django, son herramientas clásicas cuyo objetivo es implementar cualquier tipo de aplicaciones web. Suelen proporcionar todas las características necesarias: enrutamiento de URL, sistema de plantillas, acceso a bases de datos, sistema de autenticación... Los frameworks MVC no parecen ser el dominio preferido de C++, pero todavía hay algunas herramientas interesantes, incluidas cppcms y cutelyst. .
Además de las características clásicas de un marco MVC, cppcms ofrece un sistema de plantillas bastante avanzado con herencia de vistas y gestión de contenido. Por ejemplo, se puede definir una vista principal MasterView y derivar vistas de ella. AboutView y HomeView heredan las características de MasterView y las complementan. Finalmente, podemos asociar un contenido a estas vistas (parámetros de las plantillas), también con un sistema de herencia. Usando el ejemplo anterior, podemos definir un contenido MasterContent para la vista MasterView, derivarlo HomeContent para la vista HomeView y usar directamente MasterContent para la vista AboutView (sin parámetros nuevos en la plantilla).
Los marcos MVC son herramientas eficaces para implementar aplicaciones complejas. Sin embargo, requieren mucha capacitación y pueden ser de gran tamaño para aplicaciones pequeñas y simples.
El marco tntnet ofrece un sistema basado en plantillas, similar a PHP. Incluso si este marco es bastante anecdótico en el ecosistema C++, parece bastante efectivo en su enfoque: escribir código HTML clásico y agregar secciones de código C++ donde sea necesario.
Tenga en cuenta que este tipo de marco quizás sea menos adecuado para el desarrollo de aplicaciones complejas (legibilidad de las plantillas, reutilización...).
Estas herramientas están inspiradas en frameworks gráficos de escritorio, como Qt o gtkmm, es decir, se basan en una jerarquía de widgets que conforman la interfaz e interactúan mediante un mecanismo de ranura de señal.
Los widgets basados en la web son sorprendentemente impopulares, incluso en todos los idiomas, aunque su potencial parece importante. De hecho, permiten desarrollar una aplicación fullstack cliente-servidor utilizando una biblioteca de interfaz gráfica clásica y sin tener que preocuparse demasiado por la arquitectura de red de la aplicación.
En C++, el marco más exitoso en esta categoría es sin duda Wt. Wt tiene muchos widgets clásicos o avanzados, un ORM SQL, un sistema de autenticación, la capacidad de manipular HTML y CSS, etc. En Wt, el programa principal es enrutar las URL a las aplicaciones correspondientes.
Estas aplicaciones Wt corresponden a interfaces gráficas convencionales, pero con una arquitectura cliente-servidor.
Para una aplicación más compleja, por ejemplo la página que muestra los animales, podemos definir un nuevo widget que implemente una miniatura y luego usar esta clase para mostrar todos los animales leídos en la base de datos.
A primera vista, esta implementación puede parecer más larga y complicada que implementaciones anteriores. Sin embargo, su código debería resultarle familiar a cualquier desarrollador de GUI de escritorio. Además, esta implementación gestiona toda la aplicación (fullstack), no sólo la parte del servidor. Por ejemplo, conectar la señal _myquery->textInput()
a la función HomeApp::filterAnimals
implica actualizaciones del lado del cliente en tiempo real, lo que sería mucho más difícil de implementar con marcos anteriores.
Para desarrollar aplicaciones web back-end, C++ es una opción muy factible. Con sus últimos desarrollos, el lenguaje es generalmente más simple y seguro de usar, sin comprometer el rendimiento. Muchas bibliotecas C++ están disponibles para el desarrollo web: plantillas, generación de HTML, conexión SQL, ORM... Los frameworks web también son numerosos y variados: frameworks MVC como RoR y Django, microframeworks como Sinatra y Flask, frameworks asíncronos como Node.js , marcos basados en plantillas PHP e incluso marcos completos basados en widgets. Por supuesto, todo esto interesará principalmente a los desarrolladores que ya conocen C++, porque muchos otros lenguajes también tienen herramientas muy interesantes para el desarrollo web.
Los siguientes marcos, bibliotecas y herramientas se utilizan para implementar el servidor HTTP, la generación de HTML y el acceso a la base de datos SQL.
Proyecto | Marco web | Generador HTML | Interfaz SQL |
---|---|---|---|
animales-cppcms | cppcms (marco web) | cppcms (sistema de plantillas) | cppcms (conector SQL) |
animales-cpprestsdk | cpprestsdk (marco de red asíncrono) | ctml (generador de documentos html) | sqlite_orm (ORM) |
animales-cuervo | http: crow (marco web ligero) | cuervo (sistema de plantillas) | sqlpp11 (ORM) |
animales-cutelyst | cutelyst (marco web) | beneficiario (sistema de plantillas) | cutelyst (conector SQL) |
animales-nodejs (Javascript/Node.js) | express (marco web ligero asíncrono) | pug (generador de documentos) | mejor-sqlite3 (conector SQL) |
animales-pistacho | pistache (marco web ligero asíncrono) | bigote kainjow (sistema de plantillas) | sqlite_modern_cpp (conector SQL) |
animales-scotty (Haskell) | scotty (marco web ligero) | lúcido y arcilla (generadores de documentos) | sqlite-simple (conector SQL) |
animales-silicio | silicio (marco web ligero) | ninguno | silicio (conector SQL) |
animales-tntnet | tntnet (marco web basado en plantillas) | tntnet (sistema de plantillas) | tntnet (conector SQL) |
animales-peso | wt (marco de GUI web) | wt (sistema de widgets + plantillas) | peso (ORM) |