Drogon 是一個基於 C++17/20 的 HTTP 應用程式框架,使用 Drogon 可以方便地用 C++ 建立各種類型的 Web App 伺服器端程式。
這個版本庫是 GitHub 上 Drogon 的鏡像庫。Drogon 是作者非常喜歡的美劇《冰與火之歌:權力遊戲》中的一條龍的名字(中文譯作卓耿),和龍有關但並不是 dragon 的誤寫,為了避免不必要的誤會在此說明。
Drogon 是一個跨平台框架,支援 Linux、macOS、FreeBSD/OpenBSD、HaikuOS 和 Windows。主要特點如下:
- 網路層使用基於 epoll(macOS/FreeBSD 下是 kqueue)的非阻塞 IO 框架,提供高並行、高效能的網路 IO。詳細請見 TFB Tests Results;
- 完全非同步的程式撰寫邏輯;
- 支援 HTTP 1.0/1.1(伺服器端和用戶端);
- 基於樣板(template)實作的簡單反射機制,使主程式框架、控制器(controller)和視圖(view)完全解耦;
- 支援 cookies 和內建的 session;
- 支援後端算繪,將控制器產生的資料交給視圖產生 HTML 頁面,視圖由 CSP 樣板檔案描述,透過 CSP 標籤將 C++ 程式碼嵌入 HTML 頁面,由 drogon 的命令列工具在編譯階段自動產生 C++ 程式碼並編譯;
- 支援執行期的視圖頁面動態載入(動態編譯和載入 so 檔案);
- 非常方便靈活的路徑(path)到控制器處理函式(handler)的對應方案;
- 支援過濾器(filter)鏈,方便在控制器之前執行統一的邏輯(如登入驗證、HTTP Method 限制驗證等);
- 支援 HTTPS(基於 OpenSSL);
- 支援 WebSocket(伺服器端和用戶端);
- 支援 JSON 格式的請求和回應,方便開發 RESTful API;
- 支援檔案下載和上傳,支援
sendfile
系統呼叫; - 支援 Gzip/Brotli 壓縮傳輸;
- 支援 pipelining;
- 提供輕量的命令列工具
drogon_ctl
,幫助簡化各種類別的建立和視圖程式碼的產生過程; - 非同步的讀寫資料庫,目前支援 PostgreSQL 和 MySQL(MariaDB)資料庫;
- 支援非同步讀寫 Redis;
- 基於執行緒池實作 sqlite3 資料庫的非同步讀寫,提供與上述資料庫相同的介面;
- 支援 ARM 架構;
- 方便的輕量級 ORM 實現,一般物件到資料庫的雙向對應;
- 支援外掛,可透過設定檔案在載入時動態載入;
- 支援內建插入點的 AOP;
- 支援 C++ coroutine。
不像大多數 C++ 框架,drogon 的主程式可以非常簡單。Drogon 使用了一些小技巧使主程式和控制器解耦。控制器的路由設定可以在控制器類別中定義或在設定檔案中完成。
下面是一個典型主程式的樣子:
#include <drogon/drogon.h>
using namespace drogon;
int main()
{
app().setLogPath("./")
.setLogLevel(trantor::Logger::kWarn)
.addListener("0.0.0.0", 80)
.setThreadNum(16)
.enableRunAsDaemon()
.run();
}
如果使用設定檔案,可以進一步簡化成:
#include <drogon/drogon.h>
using namespace drogon;
int main()
{
app().loadConfigFile("./config.json").run();
}
當然,Drogon 也提供了一些函式,讓使用者可以在 main()
函式中直接加入控制器邏輯,例如,使用者可以註冊一個 lambda 處理常式到 drogon 框架中,如下所示:
app().registerHandler("/test?username={name}",
[](const HttpRequestPtr& req,
std::function<void (const HttpResponsePtr &)> &&callback,
const std::string &name)
{
Json::Value json;
json["result"]="ok";
json["message"]=std::string("hello,")+name;
auto resp=HttpResponse::newHttpJsonResponse(json);
callback(resp);
},
{Get,"LoginFilter"});
這看起來很方便,但不適用於複雜的場景,試想如果有數十個或數百個處理函式要註冊進框架,main()
函式將變得難以閱讀。顯然,讓每個包含處理函式的類別在自己的定義中完成註冊是更好的選擇。所以,除非你的應用邏輯非常簡單,我們不建議使用上述介面,更好的做法是建立一個 HttpSimpleController 類別,如下:
/// The TestCtrl.h file
#pragma once
#include <drogon/HttpSimpleController.h>
using namespace drogon;
class TestCtrl:public drogon::HttpSimpleController<TestCtrl>
{
public:
void asyncHandleHttpRequest(const HttpRequestPtr& req, std::function<void (const HttpResponsePtr &)> &&callback) override;
PATH_LIST_BEGIN
PATH_ADD("/test",Get);
PATH_LIST_END
};
/// The TestCtrl.cc file
#include "TestCtrl.h"
void TestCtrl::asyncHandleHttpRequest(const HttpRequestPtr& req,
std::function<void (const HttpResponsePtr &)> &&callback)
{
//write your application logic here
auto resp = HttpResponse::newHttpResponse();
resp->setBody("<p>Hello, world!</p>");
resp->setExpiredTime(0);
callback(resp);
}
上述程式的大部分程式碼都可以由 drogon_ctl
指令產生(使用指令 drogon_ctl create controller TestCtr
)。使用者只需要加入自己的業務邏輯。在這個範例中,當用戶端存取 URL http://ip/test
時,控制器簡單地回傳一個 Hello, world!
頁面。
對於 JSON 格式的回應,我們可以這樣建立控制器:
/// The header file
#pragma once
#include <drogon/HttpSimpleController.h>
using namespace drogon;
class JsonCtrl : public drogon::HttpSimpleController<JsonCtrl>
{
public:
void asyncHandleHttpRequest(const HttpRequestPtr &req, std::function<void(const HttpResponsePtr &)> &&callback) override;
PATH_LIST_BEGIN
//list path definitions here;
PATH_ADD("/json", Get);
PATH_LIST_END
};
/// The source file
#include "JsonCtrl.h"
void JsonCtrl::asyncHandleHttpRequest(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback)
{
Json::Value ret;
ret["message"] = "Hello, World!";
auto resp = HttpResponse::newHttpJsonResponse(ret);
callback(resp);
}
讓我們更進一步,透過 HttpController 類別建立一個 RESTful API 的範例,如下所示(省略實作檔案):
/// The header file
#pragma once
#include <drogon/HttpController.h>
using namespace drogon;
namespace api
{
namespace v1
{
class User : public drogon::HttpController<User>
{
public:
METHOD_LIST_BEGIN
//use METHOD_ADD to add your custom processing function here;
METHOD_ADD(User::getInfo, "/{id}", Get); //path is /api/v1/User/{arg1}
METHOD_ADD(User::getDetailInfo, "/{id}/detailinfo", Get); //path is /api/v1/User/{arg1}/detailinfo
METHOD_ADD(User::newUser, "/{name}", Post); //path is /api/v1/User/{arg1}
METHOD_LIST_END
//your declaration of processing function maybe like this:
void getInfo(const HttpRequestPtr &req, std::function<void(const HttpResponsePtr &)> &&callback, int userId) const;
void getDetailInfo(const HttpRequestPtr &req, std::function<void(const HttpResponsePtr &)> &&callback, int userId) const;
void newUser(const HttpRequestPtr &req, std::function<void(const HttpResponsePtr &)> &&callback, std::string &&userName);
public:
User()
{
LOG_DEBUG << "User constructor!";
}
};
} // namespace v1
} // namespace api
如你所見,透過 HttpController
類別,使用者可以同時對應路徑和路徑參數,這對 RESTful API 應用來說非常方便。
另外,你可以發現前面所有的處理函式介面都是非同步的,處理器的回應是透過回呼物件回傳的。這種設計是考慮到效能,因為在非同步模式下,可以使用少量的執行緒(例如和處理器核心數相等的執行緒)處理大量的並行請求。
編譯上述所有原始檔案後,我們得到了一個非常簡單的網頁應用程式,這是一個不錯的開始。請瀏覽 GitHub 上的文件
歡迎您的貢獻。請閱讀貢獻指南以取得更多資訊。
歡迎交流討論。