本應用程式和教學課程是基於 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-cppcms/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 應用程式對應於傳統的圖形介面,但具有客戶端-伺服器架構。
對於更複雜的應用程序,例如顯示動物的頁面,我們可以定義一個新的小部件來實現縮圖,然後使用此類來顯示資料庫中讀取的所有動物。
乍一看,這個實作可能比以前的實作更長、更複雜。然而,它的程式碼對於任何桌面 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) |