Esta aplicação e tutorial são baseados no exemplo do artigo Quelques cadriciels Web C++ publicado em LinuxFr.org.
Atualmente, existem muitas linguagens e frameworks interessantes para desenvolvimento web do lado do servidor. Nesta área, C++ não é a linguagem mais moderna, mas possui alguns trunfos interessantes. De fato:
O objetivo deste tutorial é fornecer:
Os códigos-fonte do Cutelyst estão incluídos aqui. Os códigos-fonte para outras estruturas da web C++ estão disponíveis neste repositório Git. As diferentes estruturas utilizadas estão resumidas no apêndice. Finalmente, uma lista de bibliotecas C++ está disponível no Awesome C++.
Queremos implementar uma aplicação que exiba imagens de animais armazenadas no servidor. Um formulário é utilizado para indicar o início do nome dos animais a serem exibidos. Você pode exibir a imagem em tamanho real clicando na miniatura e exibir uma página de informações por meio de um link na parte inferior da página. Os dados dos animais (nomes e caminhos de arquivos) são armazenados em um banco de dados SQLite no servidor.
Aqui, a geração de páginas HTML é realizada no servidor, embora a tendência atual seja fornecer uma API do lado do servidor e gerar HTML do lado do cliente.
De uma forma muito convencional, pode-se organizar o código desta aplicação segundo uma arquitetura do tipo MVC, ou seja, distinguindo os dados (modelo), a sua visualização (visualização) e a sua gestão (controlador).
Para nossa aplicação, as imagens ficam disponíveis no servidor e utilizamos um banco de dados SQLite contendo uma tabela com os nomes e caminhos dos arquivos dos animais. Arquivo 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 ' );
...
A parte do modelo então se resume a um tipo Animal e uma função getAnimals que consulta o banco de dados e retorna o tipo registra Animal cujo nome começa com o prefixo fornecido. Arquivo Animal.hpp:
A parte view contém duas funções que retornam páginas em formato HTML: renderAbout retorna a página de informações e renderHome retorna a página principal com os animais solicitados pelo usuário. Visualização de arquivo.hpp:
Finalmente, a parte do controlador recupera os eventos do cliente e então atualiza o modelo e a visão. Para nossa aplicação, não há nenhum processamento complicado a ser realizado, apenas recuperar solicitações HTTP e chamar as funções anteriores.
C++ não parece ter ferramentas de geração de documentos HTML tão bem-sucedidas quanto Lucid em Haskell. A biblioteca CTML é usada para definir a estrutura em árvore de um documento e então gerar o código HTML correspondente. Porém, sua sintaxe é bastante detalhada e não há verificação de tags.
Estes sistemas consistem em escrever templates customizáveis, ou seja, código HTML no qual são utilizados parâmetros que serão substituídos pelos valores indicados na renderização do template.
Estruturas MVC normalmente oferecem sistemas de padrões avançados, mas também existem ferramentas independentes, por exemplo, bigode. Bigode é um formalismo que possui implementações em muitas linguagens, incluindo várias em C++. Por exemplo, animal-pistache/src/View.cpp usa a implementação do bigode kainjow e o seguinte código (animais-crow/src/View.cpp) a implementação da estrutura 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 () {
...
}
Também é relativamente simples gerar código HTML manualmente, usando fluxos de canal C++. No entanto, este método não facilita a reutilização de código ou a verificação do código HTML produzido. Exemplo de geração manual (animais-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 () {
...
}
Eles possibilitam construir consultas SQL explicitamente, enviá-las ao sistema de banco de dados e recuperar o resultado. Os conectores SQL geralmente são fáceis de usar (basta conhecer a linguagem SQL), mas não verificam se as consultas estão corretas.
Muitas estruturas oferecem conectores SQL. Por exemplo, cppcms (ver animais-cppcms/src/Animal.cpp), tntnet (ver animais-tntnet/src/Animal.cc) e silício (ver animais-silicon/src/main.cpp). Existem também conectores independentes, por exemplo sqlite_modern_cpp (veja pets-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;
}
O Mapeamento Objeto-Relacional (ORM) é usado para converter dados de uma tabela em um banco de dados para uma classe C++ e vice-versa. Isso possibilita a utilização do banco de dados com mais segurança, pois os dados são verificados pelo sistema de digitação e verificados em tempo de compilação, já que as solicitações são feitas por funções C++. Entretanto, um ORM define sua própria camada de abstração equivalente ao SQL, mas necessariamente menos conhecida.
Existem diferentes ORMs C++, por exemplo wt (consulte Animals-wt/src/main.cpp), sqlpp11 (consulte Animals-crow/src/Animal.cpp) ou sqlite_orm (consulte 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 frameworks web, como Sinatra em Ruby ou Flask em Python, pretendem ser simples e leves. Eles oferecem principalmente recursos para lidar com solicitações HTTP, bem como um mecanismo de roteamento de URL. Se necessário, podem ser completados por outras bibliotecas (geração de HTML, acesso a uma base de dados em SQL...).
Existem várias microestruturas C++, por exemplo, crow (ver Animals-Crow) ou Silicon (ver Animals-Silicon).
Aqui, os recursos do C++ moderno tornam o código conciso e bastante agradável de ler.
Numa fase de pré-tratamento, o Silicon gera o arquivo symbol.hh, que declara os símbolos definidos pelo programador, incluindo rotas ( _about, _home, _mystatic...). Isso torna possível verificar estaticamente se as rotas são utilizadas corretamente no código. Outras linguagens utilizam a introspecção para realizar esse tipo de verificação, mas C++ não possui esse recurso.
Frameworks assíncronos, como Node.js/Express em JavaScript, oferecem as mesmas funcionalidades que microframeworks convencionais, mas por meio de funções sem bloqueio. Assim, se uma solicitação necessita de um recurso, a aplicação pode alternar para outra solicitação enquanto espera que o recurso esteja disponível. Isso melhora o desempenho geral do aplicativo, mas requer um estilo de programação específico, baseado em promessas conectadas a funções de retorno de chamada para formar uma cadeia de processamento assíncrono.
Existem diferentes estruturas assíncronas em C++, por exemplo cpprestsdk (consulte Animals-cpprestsdk) e pistache (consulte Animals-pistachio).
Aqui encontramos um gerenciamento clássico de rota (com o nome da rota e sua função de processamento). Porém, agora temos operação assíncrona, por meio de funções não bloqueadoras. Por exemplo, para a rota "estática", a função serveFile retorna uma promessa que está conectada a uma função de retorno de chamada, que exibe uma mensagem de log assim que a promessa for resolvida.
Frameworks web MVC, como Ruby on Rails ou Python Django são ferramentas clássicas cujo objetivo é implementar qualquer tipo de aplicação web. Eles geralmente fornecem todos os recursos necessários: roteamento de URL, sistema de templates, acesso a bancos de dados, sistema de autenticação... Os frameworks MVC não parecem ser o domínio preferido do C++, mas ainda existem algumas ferramentas interessantes, incluindo cppcms e cutelyst .
Além dos recursos clássicos de uma estrutura MVC, o cppcms oferece um sistema de templates bastante avançado com herança de visualização e gerenciamento de conteúdo. Por exemplo, pode-se definir uma visualização principal MasterView e derivar visualizações dela. AboutView e HomeView herdam as características do MasterView e as complementam. Por fim, podemos associar um conteúdo a estas visualizações (parâmetros dos templates), também com um sistema de herança. Usando o exemplo anterior, podemos definir um conteúdo MasterContent para a visualização MasterView, derivá-lo HomeContent para a visualização HomeView e usar diretamente MasterContent para a visualização AboutView (sem novo parâmetro no modelo).
Frameworks MVC são ferramentas eficazes para implementar aplicações complexas. No entanto, eles exigem muito treinamento e podem ser superdimensionados para aplicações pequenas e simples.
A estrutura tntnet oferece um sistema baseado em modelo, semelhante ao PHP. Mesmo que esta estrutura seja bastante anedótica no ecossistema C++, parece bastante eficaz na sua abordagem: escrever código HTML clássico e adicionar secções de código C++ onde for necessário.
Note que este tipo de framework talvez seja menos adequado para o desenvolvimento de aplicações complexas (legibilidade de templates, reutilização...).
Essas ferramentas são inspiradas em frameworks gráficos de desktop, como Qt ou gtkmm, ou seja, baseadas em uma hierarquia de widgets que compõem a interface e interagem por meio de um mecanismo de slot de sinal.
Os widgets baseados na Web são surpreendentemente impopulares, mesmo em todas as línguas, embora o seu potencial pareça importante. Na verdade, eles permitem desenvolver uma aplicação fullstack cliente-servidor utilizando uma biblioteca de interface gráfica clássica e sem ter que se preocupar muito com a arquitetura de rede da aplicação.
Em C++, o framework de maior sucesso nesta categoria é certamente o Wt. O Wt possui muitos widgets clássicos ou avançados, um SQL ORM, um sistema de autenticação, a capacidade de manipular HTML e CSS, etc. No Wt, o programa principal é rotear URLs para os aplicativos correspondentes.
Estas aplicações Wt correspondem a interfaces gráficas convencionais, mas com arquitetura cliente-servidor.
Para uma aplicação mais complexa, por exemplo a página que exibe os animais, podemos definir um novo widget que implemente uma miniatura, e então utilizar esta classe para exibir todos os animais lidos no banco de dados.
À primeira vista, esta implementação pode parecer mais longa e complicada do que as implementações anteriores. No entanto, seu código deve parecer familiar para qualquer desenvolvedor de GUI de desktop. Além disso, esta implementação gerencia toda a aplicação (fullstack), não apenas a parte do servidor. Por exemplo, conectar o sinal _myquery->textInput()
à função HomeApp::filterAnimals
envolve atualizações em tempo real do lado do cliente, o que seria muito mais difícil de implementar com estruturas anteriores.
Para desenvolver aplicações web back-end, C++ é uma opção muito viável. Com seus desenvolvimentos mais recentes, a linguagem é geralmente mais simples e segura de usar, sem comprometer o desempenho. Muitas bibliotecas C++ estão disponíveis para desenvolvimento web: templates, geração de HTML, conexão SQL, ORM... Os frameworks Web também são numerosos e variados: frameworks MVC como RoR e Django, micro-frameworks como Sinatra e Flask, frameworks assíncronos como Node.js , estruturas baseadas em modelos PHP e até mesmo estruturas fullstack baseadas em widgets. Claro que tudo isso interessará principalmente aos desenvolvedores que já conhecem C++, pois muitas outras linguagens também possuem ferramentas muito interessantes para desenvolvimento web.
As seguintes estruturas, bibliotecas e ferramentas usadas para implementar o servidor HTTP, a geração de HTML e o acesso ao banco de dados SQL.
Projeto | Estrutura da Web | Gerador HTML | InterfaceSQL |
---|---|---|---|
animais-cppcms | cppcms (estrutura web) | cppcms (sistema de modelos) | cppcms (conector SQL) |
animais-cpprestsdk | cpprestsdk (estrutura de rede assíncrona) | ctml (gerador de documentos HTML) | sqlite_orm (ORM) |
animais-corvo | http: crow (framework web leve) | corvo (sistema de modelo) | sqlpp11 (ORM) |
animal-fofo | cutelyst (framework web) | grantlee (sistema de templates) | cutelyst (conector SQL) |
animais-nodejs (Javascript/Node.js) | express (framework web leve assíncrono) | pug (gerador de documentos) | melhor-sqlite3 (conector SQL) |
animais-pistache | pistache (framework web leve e assíncrono) | bigode kainjow (sistema de modelagem) | sqlite_modern_cpp (conector SQL) |
animais-scotty (Haskell) | scotty (framework web leve) | lúcido e argila (geradores de documentos) | sqlite-simple (conector SQL) |
animais-silício | silício (estrutura web leve) | nenhum | silício (conector SQL) |
animais-tntnet | tntnet (framework web baseado em modelo) | tntnet (sistema de templates) | tntnet (conector SQL) |
animais-peso | wt (estrutura GUI da web) | wt (sistema de widgets + modelos) | peso (ORM) |