本应用程序和教程基于 LinuxFr.org 上发布的 Quelques cadriciels Web C++ 文章中的示例。
目前,有许多有趣的服务器端 Web 开发语言和框架。在这个领域,C++ 并不是最流行的语言,但它有一些有趣的优点。的确:
本教程的目的是提供:
这里包含 Cutelyst 的源代码。此 Git 存储库上提供了其他 C++ Web 框架的源代码。附录中总结了所使用的不同框架。最后,Awesome C++ 上提供了 C++ 库的列表。
我们想要实现一个应用程序来显示存储在服务器上的动物图像。表格用于指示要显示的动物名称的开头。您可以通过单击缩略图来显示完整尺寸的图像,并且可以通过页面底部的链接显示信息页面。动物数据(名称和文件路径)存储在服务器上的 SQLite 数据库中。
这里,HTML页面的生成是在服务器上执行的,尽管当前的趋势是提供服务器端API并生成客户端HTML。
以一种非常传统的方式,我们可以根据 MVC 类型的架构来组织该应用程序的代码,也就是说,通过区分数据(模型)、它们的显示(视图)和它们的管理(控制器)。
对于我们的应用程序,图像在服务器上可用,并且我们使用 SQLite 数据库,其中包含包含动物名称和文件路径的表。文件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 ' );
...
然后,模型部分归结为 Animal 类型和函数 getAnimals,该函数查询数据库并返回名称以给定前缀开头的 Animal 类型记录。文件动物.hpp:
视图部分包含两个以 HTML 格式返回页面的函数:renderAbout 返回信息页面,renderHome 返回包含用户请求的动物的主页。文件视图.hpp:
最后,控制器部分检索客户端的事件,然后更新模型和视图。对于我们的应用程序来说,不需要执行复杂的处理,只需检索 HTTP 请求并调用前面的函数即可。
C++ 似乎没有像 Haskell 中的 Lucid 那样成功的 HTML 文档生成工具。 CTML库用于定义文档的树结构,然后生成相应的HTML代码。然而,它的语法相当冗长,并且没有标签验证。
这些系统由编写可定制的模板组成,也就是说,其中使用的参数将被渲染模板时指示的值替换的 HTML 代码。
MVC 框架通常提供高级模式系统,但也有独立的工具,例如 Mustache。 Mustache 是一种形式主义,在许多语言中都有实现,其中包括 C++ 中的几种。例如,animal-pistache/src/View.cpp 使用 kainjow Mustache 实现,以下代码 (animals-crow/src/View.cpp) 使用 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 () {
...
}
使用 C++ 通道流手动生成 HTML 代码也相对简单。然而,这种方法不利于代码重用或生成的 HTML 代码的验证。手动生成示例(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 () {
...
}
它们使得显式构建 SQL 查询、将它们发送到数据库系统并检索结果成为可能。 SQL 连接器通常易于使用(只需了解 SQL 语言),但它们不会检查查询是否正确。
许多框架提供 SQL 连接器。例如,cpcms(请参阅animals-cpcms/src/Animal.cpp)、tntnet(请参阅animals-tntnet/src/Animal.cc)和silicon(请参阅animals-silicon/src/main.cpp)。还有独立的连接器,例如sqlite_modern_cpp(参见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;
}
对象关系映射 (ORM) 用于将数据库表中的数据转换为 C++ 类,反之亦然。这使得可以更安全地使用数据库,因为数据由打字系统检查,并在编译时检查,因为请求是由 C++ 函数发出的。然而,ORM 定义了自己的抽象层,相当于 SQL,但可能鲜为人知。
有不同的 C++ ORM,例如 wt(请参阅animals-wt/src/main.cpp)、sqlpp11(请参阅animals-crow/src/Animal.cpp)或 sqlite_orm(请参阅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;
}
微型 Web 框架,例如 Ruby 中的 Sinatra 或 Python 中的 Flask,旨在简单、轻量。它们主要提供处理 HTTP 请求的功能以及 URL 路由机制。如果需要,它们可以由其他库完成(HTML 生成、使用 SQL 访问数据库...)。
有几种 C++ 微框架,例如 crow(请参阅animals-crow)或 Silicon(请参阅animals-silicon)。
在这里,现代C++的特性使得代码简洁并且读起来相当愉快。
在预处理阶段,Silicon生成文件symbols.hh,它声明了程序员定义的符号,包括路由(_about、_home、_mystatic...)。这使得静态验证代码中是否正确使用路由成为可能。其他语言使用内省来执行这种检查,但C++没有这个功能。
异步框架(例如 JavaScript 中的 Node.js/Express)提供与传统微框架相同的功能,但通过非阻塞函数实现。因此,如果请求需要资源,应用程序可以在等待资源可用时切换到另一个请求。这提高了应用程序的整体性能,但需要特定的编程风格,基于连接到回调函数的承诺,然后形成异步处理链。
C++ 中有不同的异步框架,例如 cpprestsdk(请参阅animals-cpprestsdk)和 pistachio(请参阅animals-pistachio)。
这里我们找到一个经典的路由管理(有路由的名称及其处理函数)。但是,我们现在通过非阻塞函数进行异步操作。例如,对于“静态”路由,函数serveFile返回一个连接到回调函数的promise,一旦promise被解析,回调函数就会显示一条日志消息。
MVC Web 框架(例如 Ruby on Rails 或 Python Django)是经典工具,其目标是实现任何类型的 Web 应用程序。它们通常提供所有必要的功能:URL 路由、模板系统、数据库访问、身份验证系统……MVC 框架似乎不是 C++ 的首选领域,但仍然有一些有趣的工具,包括 cppcms amd Cutelyst 。
除了 MVC 框架的经典功能之外,cpcms 还提供了相当先进的模板系统,具有视图继承和内容管理功能。例如,可以定义一个主视图MasterView并从中派生视图AboutView和HomeView继承MasterView的特性并补充它们。最后,我们可以将内容与这些视图(模板的参数)相关联,也可以使用继承系统。使用前面的示例,我们可以为视图 MasterView 定义一个内容 MasterContent,为视图 HomeView 派生它的 HomeContent,并直接使用 MasterContent 为视图 AboutView(模板中没有新参数)。
MVC 框架是实现复杂应用程序的有效工具。然而,它们需要大量的培训,并且对于小型、简单的应用来说可能会过大。
tntnet 框架提供了一个基于模板的系统,类似于 PHP。即使这个框架在 C++ 生态系统中颇为轶事,但它的方法似乎相当有效:编写经典的 HTML 代码并在必要时添加 C++ 代码部分。
请注意,这种类型的框架可能不太适合复杂应用程序的开发(模板的可读性、重用性......)。
这些工具的灵感来自桌面图形框架,例如 Qt 或 gtkmm,即基于构成界面并通过信号槽机制交互的小部件层次结构。
基于网络的小部件令人惊讶地不受欢迎,即使在所有语言中也是如此,尽管它们的潜力似乎很重要。事实上,它们允许使用经典的图形界面库开发客户端-服务器全栈应用程序,而不必过多担心应用程序的网络架构。
在 C++ 中,此类中最成功的框架无疑是 Wt。 Wt有许多经典或高级的小部件,SQL ORM,身份验证系统,操作HTML和CSS的能力等。在Wt中,主要程序是将URL路由到相应的应用程序。
这些 Wt 应用程序对应于传统的图形界面,但具有客户端-服务器架构。
对于更复杂的应用程序,例如显示动物的页面,我们可以定义一个新的小部件来实现缩略图,然后使用此类来显示数据库中读取的所有动物。
乍一看,这个实现可能比以前的实现更长、更复杂。然而,它的代码对于任何桌面 GUI 开发人员来说都应该很熟悉。此外,此实现管理整个应用程序(全栈),而不仅仅是服务器部分。例如,将信号_myquery->textInput()
连接到HomeApp::filterAnimals
函数涉及实时客户端更新,这在以前的框架中很难实现。
要开发后端Web应用程序,C++是一个非常可行的选择。随着最新的发展,该语言通常使用起来更简单、更安全,而且不会影响性能。许多 C++ 库可用于 Web 开发:模板、HTML 生成、SQL 连接、ORM...Web 框架也多种多样:RoR 和 Django 等 MVC 框架、Sinatra 和 Flask 等微框架、Node.js 等异步框架、基于 PHP 模板的框架,甚至基于小部件的全栈框架。当然,这一切主要是那些已经了解 C++ 的开发人员感兴趣,因为许多其他语言也有非常有趣的 Web 开发工具。
以下框架、库和工具用于实现 HTTP 服务器、HTML 生成和 SQL 数据库访问。
项目 | 网络框架 | HTML 生成器 | SQL接口 |
---|---|---|---|
动物-cpcms | cppcms(网络框架) | cppcms(模板系统) | cppcms(SQL 连接器) |
动物-cprestsdk | cpprestsdk(异步网络框架) | ctml(html 文档生成器) | sqlite_orm(ORM) |
动物乌鸦 | http:crow(轻量级网络框架) | 乌鸦(模板系统) | sqlpp11 (ORM) |
动物可爱 | Cutelyst(网络框架) | grantlee(模板系统) | Cutelyst(SQL 连接器) |
动物-nodejs (Javascript/Node.js) | express(异步轻量级Web框架) | pug(文档生成器) | better-sqlite3(SQL 连接器) |
动物-开心果 | pistache(异步轻量级 Web 框架) | kainjow 胡子(模板系统) | sqlite_modern_cpp(SQL 连接器) |
动物-斯科蒂 (Haskell) | scotty(轻量级网络框架) | lucid 和clay(文档生成器) | sqlite-simple(SQL 连接器) |
动物硅 | Silicon(轻量级网络框架) | 没有任何 | 硅(SQL 连接器) |
动物-tntnet | tntnet(基于模板的 Web 框架) | tntnet(模板系统) | tntnet(SQL 连接器) |
动物-wt | wt(Web GUI 框架) | wt(小部件系统+模板) | 重量(ORM) |