Tuf?o(以下稱之為tufao或Tufao)是GitHub上的一個開源C++11異步網(wǎng)絡(luò)庫煞躬,依賴于QT和Boost.Http開發(fā)惧蛹。它很酷的一點(diǎn)就是利用QT插件的特性實(shí)現(xiàn)了業(yè)務(wù)處理模塊的動態(tài)加載和動態(tài)更新胸遇。這條特性加上其簡潔的API讓我決定使用它勉抓。
一改衩,安裝
tufao的安裝相對而言還是比較簡單的权均,README的信息基本足以正確安裝它澎羞。
編譯時唯一要注意的一點(diǎn)是:tufao依賴于QT的Qt5Network庫和Qt5Core庫髓绽,以及boost庫的頭文件。前者需要根據(jù)編譯工具鏈在項(xiàng)目里正確地設(shè)置庫地址(如msvc2015 x64環(huán)境下lib庫在X:\Qt\Qt5.10.0\5.10.0\msvc2015_64\lib
)妆绞,后者需要引入boost庫的頭文件目錄(如X:\boost_1_70_0\bin\include\boost-1_70
)顺呕。我用minGW和MSVC都編譯了一遍,基本沒有太大問題括饶。
tufao在make install
后會將其自動生成的設(shè)置(pkg\tufao1.prf
)插入到QT的features
中株茶,這樣你就可以直接通過在.pro
文件里添加CONFIG += C++11 TUFAO1
的方式直接使用tufao庫。在我的電腦上图焰,這個地址是C:\Qt\Qt5.11.0\5.11.0\mingw53_32\mkspecs\features\tufao1.prf
启盛。
tufao1.prf
的內(nèi)容(作者注:以下代碼均額外添加了部分注釋)如下:
# 引入QT5Network庫;Qt5Core作為QT基本庫不需要特意設(shè)置引用
QT += network
# 定義tufao的版本
DEFINES += TUFAO_VERSION_MAJOR=1
# 引入tufao庫頭文件
INCLUDEPATH += "C:\Program Files (x86)\tufao\include\tufao-1"
# 引入tufao庫文件
win32 {
CONFIG(debug, debug|release): LIBS += -L"C:/Program Files (x86)/tufao/lib" -ltufao1d
CONFIG(release, debug|release): LIBS += -L"C:/Program Files (x86)/tufao/lib" -ltufao1
} else {
LIBS += -L"C:/Program Files (x86)/tufao/lib" -ltufao1
}
除此之外,在安裝了Doxygen后技羔,tufao的幫助文件也可以一并生成并插入到QT的本地幫助文檔中驰徊。不過我裝這個裝失敗了,tufao的源碼注釋寫得相當(dāng)棒堕阔,直接跳轉(zhuǎn)到相應(yīng)的聲明看注釋部分就可以解決使用上的問題了[TODO: 以后可能會再試試裝這個吧]棍厂。
二,例子
在安裝好tufao1.prf
后tufao的項(xiàng)目設(shè)置就非常輕松了:
(tufaoserver.pro
):
QT += core
TARGET = tufaoServer
TEMPLATE = app
# C++11 TUFAO1是使用Tufao的必須設(shè)置
CONFIG += C++11 TUFAO1
# 由于示例沒有使用界面超陆,所以gui庫被取消了
QT -= gui
SOURCES += main.cpp
簡單一步牺弹,就完成了tufao的項(xiàng)目設(shè)置浦马。
此外,tufao的Hello World
例子也相當(dāng)簡潔(main.cpp
):
#include <QCoreApplication>
#include <Tufao/HttpServer>
#include <Tufao/HttpServerRequest>
using namespace Tufao;
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
HttpServer server;
QObject::connect(&server, &HttpServer::requestReady,
[](HttpServerRequest &, HttpServerResponse &res){ // 業(yè)務(wù)代碼
res.writeHead(HttpResponseStatus::OK);
res.end("Hello World");
});
server.listen(QHostAddress::Any, 8080);
return a.exec();
}
完整的服務(wù)器模板也是如此(main.cpp
):
#include <QCoreApplication>
#include <Tufao/HttpPluginServer>
#include <Tufao/HttpFileServer>
#include <Tufao/NotFoundHandler>
#include <Tufao/HttpServerRequestRouter>
#include <Tufao/HttpServer>
using namespace Tufao;
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
HttpPluginServer plugins{"routes.json"/* 插件設(shè)置文件Path */}; // 插件系統(tǒng)
HttpServerRequestRouter router{ // 路由系統(tǒng)
{QRegularExpression{""}, plugins},
{QRegularExpression{""}, HttpFileServer::handler("public"/* 靜態(tài)資源文件夾Path */)}, // 靜態(tài)資源文件系統(tǒng)
{QRegularExpression{""}, NotFoundHandler::handler()} // 404錯誤消息
};
HttpServer server;
QObject::connect(&server, &HttpServer::requestReady,
&router, &HttpServerRequestRouter::handleRequest); // 將HttpServer的接收請求信號連接到路由系統(tǒng)的槽函數(shù)上
server.listen(QHostAddress::Any, 8080); // 開始監(jiān)聽8080端口张漂。tufao不會堵塞主線程晶默,這讓服務(wù)器程序的開發(fā)變得輕松了很多。
return a.exec();
}
相較而言航攒,tufao的Plugin庫模板就稍微有些難記了
(myplugin.pro
):
TARGET = myPlugin
# 生成類型是 庫 而不是app
TEMPLATE = lib
# plugin是QT的插件庫磺陡,這是tufao能夠?qū)崿F(xiàn)動態(tài)加載插件的主要原因。C++11 TUFAO1則用于加載TUFAO1庫漠畜,想用Tufao庫就必須設(shè)置
CONFIG += plugin C++11 TUFAO1
SOURCES += plugin.cpp
HEADERS += plugin.h
(plugin.h
):
#ifndef PLUGIN_H
#define PLUGIN_H
#include <Tufao/HttpServerPlugin>
class Plugin: public QObject, Tufao::HttpServerPlugin
{
Q_OBJECT
/*
* 作為QT插件币他,以下兩行宏使其能被主程序的QPluginLoader動態(tài)加載
*/
Q_PLUGIN_METADATA(IID TUFAO_HTTPSERVERPLUGIN_IID) // QT插件元信息
Q_INTERFACES(Tufao::HttpServerPlugin) // QT插件接口
public:
std::function<bool(Tufao::HttpServerRequest&, Tufao::HttpServerResponse&)>
createHandler(const QHash<QString, Tufao::HttpServerPlugin*> &dependencies,
const QVariant &customData = QVariant()) override;
};
#endif // PLUGIN_H
(plugin.cpp
):
#include "plugin.h"
#include <QtCore/QtPlugin>
#include <Tufao/HttpServerResponse>
using namespace Tufao;
std::function<bool(HttpServerRequest&, HttpServerResponse&)>
Plugin::createHandler(const QHash<QString, HttpServerPlugin*> &,
const QVariant &)
{
return [](HttpServerRequest &, HttpServerResponse &res){ // 業(yè)務(wù)代碼
res.writeHead(HttpResponseStatus::OK);
res.end("Hello World, I am a Tufao Plugin\n");
return true;
};
}
如myplugin.pro
所見,插件的類型是lib(MSVC編譯后會生成一個.lib
憔狞、一個.dll
和一個.pdb
)蝴悉,并且要引入qt的plugin
庫。qt插件庫需要一些獨(dú)特的用法瘾敢,使得tufao的Plugin庫模板略顯復(fù)雜拍冠。不過讀懂了之后就還算很容易理解的。
三簇抵,使用
tufao的一般使用沒有什么難點(diǎn)庆杜。插件系統(tǒng)和文件系統(tǒng)的路徑部分卻還是有個坑的。QT在使用默認(rèn)編譯路徑時碟摆,程序(通過QT Creator運(yùn)行時欣福,直接運(yùn)行.exe
文件反而沒這個坑)的應(yīng)用所在目錄
并不是源碼所在目錄,也不是.exe
所在的debug
或release
文件夾焦履,而是.exe
所在目錄的上一級。插件系統(tǒng)所需的routes.json
雏逾,如果也使用相對路徑(相對于應(yīng)用所在目錄
的相對路徑)的話嘉裤,也必須注意這一點(diǎn)。(如果你對插件加載實(shí)在摸不著頭腦栖博,可以通過qInstallMessageHandler
把qWarning
消息輸出到外部文件里屑宠,HttpPluginServer
對象會將和加載插件失敗相關(guān)的警告通過QtWarningMsg
傳遞出來。消息示例Warning: Tufao::HttpPluginServer: Couldn't load plugin "plugin/myPlugin.lib"
)仇让。
關(guān)于HttpPluginServer
類的行為模式典奉,在httppluginserver.h
第102行(文章更新日當(dāng)前版本)開始有個很好的解釋。原文:
The HttpPluginServer behaviour
==============================
An simplified use case to describing how HttpPluginServer reacts to
changes follows:
1. You start with a default-constructed HttpPluginServer
2. You use setConfig with an inexistent file
1. The HttpPluginServer do not find the file
2. HttpPluginServer::setConfig returns false
3. HttpPluginServer object remains in the previous state
3. You use setConfig with a invalid file
1. HttpPluginServer starts to monitor the config file
2. HttpPluginServer::setConfig returns true
3. HttpPluginServer reads the invalid file and remains in the previous
state.
4. You fill the config file with a valid config.
1. HttpPluginServer object load the new contents
2. HttpPluginServer try to load every plugin and fill the router. If a
plugin cannot be loaded, it will be skipped and a warning message is
sent through qWarning. If you need to load this plugin, make any
modification to the config file and HttpPluginServer will try again.
5. You fill the config file with an invalid config.
1. HttpPluginServer see and ignores the changes, remaining with the
previous settings.
6. You remove the config file.
1. HttpPluginServer object come back to the default-constructed state.
<h2>version: 0</h2>
If the last config had "version: 0", then it means no more monitoring
either (this is what default-constructed state means).
<h2>version: 1</h2>
If the last config had "version: 1", then HttpPluginServer will (after
the cleanup) start to monitor the containing folder, waiting until a
config file with the same name is available again to resume its
operation.
\note
A later call to HttpPluginServer::setConfig can be used to stop the
monitoring.
\note
If the containing dir is also erased, HttpPluginServer can do nothing
and the monitoring will stop.
我自己翻譯一下:
HttpPluginServer
行為一個簡單的用例來描述
HttpPluginServer
是怎樣對下述變化起反應(yīng)的:
- 你從一個使用默認(rèn)構(gòu)造的
HttpPluginServer
開始- 你調(diào)用了
setConfig(<一個不存在的文件>)
HttpPluginServer
無法找到此文件HttpPluginServer::setConfig
函數(shù)返回false
HttpPluginServer
對象保持之前的狀態(tài)- 你調(diào)用了
setConfig(<一個無效的文件>)
HttpPluginServer
開始監(jiān)控這個配置文件HttpPluginServer::setConfig
函數(shù)返回true
HttpPluginServer
讀取了這個無效的文件丧叽,然后仍保持之前的狀態(tài)- 你用一個有效的配置填充了這個配置文件
HttpPluginServer
對象加載這個新內(nèi)容HttpPluginServer
嘗試加載每個插件并填充路由卫玖。如果有插件沒法被加載,它會忽略這個插件并通過qWarning
輸出一個警告信息踊淳。如果你需要加載此插件假瞬,可對這個配置文件作出任何修改陕靠,隨后HttpPluginServer
將會再次嘗試- 你用一個無效的配置填充了這個配置文件
HttpPluginServer
對象看到并忽略了此次變更,繼續(xù)保持之前的狀態(tài)- 你移除了這個配置文件
HttpPluginServer
對象回到默認(rèn)構(gòu)造的狀態(tài)下脱茉。
version: 0
如果最后的配置使用的是"version: 0
"剪芥,這意味著之后不會再繼續(xù)監(jiān)控了(這也是默認(rèn)構(gòu)造狀態(tài)的情況)version: 1
如果最后的配置使用的是"version: 0
",HttpPluginServer
將會(在這次移除后)開始監(jiān)控這個曾包含配置文件的文件夾琴许,直到監(jiān)控到有一個同名的配置文件可以獲得以再次繼續(xù)它的操作税肪。
注意:
隨后調(diào)用一次HttpPluginServer::setConfig
可用于停止這種監(jiān)控。
注意:
如果這個包含文件夾也被刪除了榜田,HttpPluginServer
沒法做任何事益兄,隨后將會停止這種監(jiān)控
在httppluginserver.h
第154行(文章更新日當(dāng)前版本)開始則對routes.json
進(jìn)行了規(guī)則解釋。這里我也簡單翻譯一下串慰。
原文:
The file format
===============
The configuration file format is json-based. If you aren't used to JSON,
read the [json specification](http://json.org/).
\note
The old Tuf?o 0.x releases used a file with the syntax based on the
QSettings ini format and forced you to use the _tufao-routes-editor_
application to edit this file.
The file must have a root json object with 3 attributes:
- _version_: It must indicate the version of the configuration file. The
list of acceptable values are:
- _0_: Version recognizable by Tuf?o 1.x, starting from 1.0
- _1_: Version recognizable by Tuf?o 1.x, starting from 1.2. The only
difference is the autoreloading behaviour. If you delete the config
file, Tuf?o will start to monitor the containing folder and resume the
normal operation as soon as the file is added to the folder again.
- _plugins_: This attribute stores metadata about the plugins. All plugins
specified here will be loaded, even if they aren't used in the request
router. The value of this field must be an array and each element of
this array must be an object with the following attributes:
- _name_: This is the name of the plugin and defines how you will refer
to this plugin later. You can't have two plugins with the same name.
This attribute is **required**.
- _path_: This is the path of the plugin in the filesystem. Relative
paths are supported, and are relative to the configuration file. This
attribute is **required**.
- _dependencies_: This field specifies a list of plugins that must be
loaded before this plugin. This plugin will be capable of access
plugins listed here. This attribute is **optional**.
- _customData_: It's a field whose value is converted to a QVariant and
passed to the plugin. It can be used to pass arbitrary data, like
application name or whatever. This attribute is **optional**.
- _requests_: This attribute stores metadata about the requests handled by
this object. The value of this field is an array and each element of
this array describes a handler and is an object with the following
attributes:
- _path_: Defines the regex pattern used to filter requests based on the
url's path component. The regex is processed through
QRegularExpression. This attribute is **required** and **must** be an
valid regex.
- _plugin_: Defines what plugin is used to handle request matching the
rules defined in this containing block. This attribute is **required**.
- _method_: Define what HTTP method is accepted by this handler. This
field is **optional** and, if it's not defined, it won't be used to
filter the requests.
譯文:
文件格式
配置文件格式是基于JSON的偏塞。如果你沒用過JSON,閱讀json specification邦鲫。
注意:舊版的Tufao(0.x release)使用了一個基于QSetting ini格式的文件灸叼,并強(qiáng)制要求你使用
tufao-routes-editor
應(yīng)用來編輯此文件。這個文件必須包含一個有三個屬性的根JSON對象:
version
:它必須標(biāo)識這個配置文件的版本庆捺」沤瘢可接納的值列表如下:
0
:被Tuf?o 1.x
識別,自1.0
版本開始1
:被Tuf?o 1.x
識別滔以,自1.2
版本開始捉腥。唯一的不同點(diǎn)就是自動加載行為。如果你刪除了這個配置文件你画,Tufao將會開始監(jiān)控曾包含配置文件的文件夾抵碟,一旦配置文件再次加入到這個文件夾中,Tufao就會重新開始一般地操作坏匪。plugins
:這個屬性儲存了關(guān)于插件們的元信息拟逮。所有在這里指定的插件均將被加載(即使它們并沒有在請求路由中使用)。這個字段必須是一個數(shù)組适滓,且其每一個元素都必須是包含以下屬性的對象:
name
:這是此插件的名字敦迄,且定義了你一會兒將如何引用這個插件。你不能有兩個名字一樣的插件凭迹。這個屬性是必需的罚屋。path
:這是此插件在文件系統(tǒng)中的路徑。支持相對路徑(相對于這個配置文件而言的相對路徑)嗅绸。這個屬性是必需的脾猛。dependencies
:這個字段指定了一個必須在此插件前加載的插件列表。此插件將能夠訪問到列在此處的插件們鱼鸠。這個屬性是可選的尖滚。customData
:這個字段的值會被轉(zhuǎn)換成QVariant
并傳遞給此插件喉刘。它可以用于傳遞任意數(shù)據(jù),比如應(yīng)用名稱或其他什么數(shù)據(jù)漆弄。這個屬性是可選的睦裳。requests
:這個屬性儲存了關(guān)于被這個對象處理的請求們的元信息。這個字段的值是一個數(shù)組撼唾,而且這個數(shù)組的每個元素都描述了一個處理機(jī)且是一個包含下列屬性的對象:
path
:定義一個基于URLpath
部分的正則表達(dá)式廉邑,用于篩選請求。這里的正則會被QRegularExpression
處理倒谷。這個屬性是必需的蛛蒙,且必須是一個有效的正則表達(dá)式。plugin
:定義了哪個插件會被用于處理已適配被定義在這個包含塊內(nèi)的規(guī)則們的請求渤愁。這個屬性是必需的牵祟。method
:定義了哪個HTTP方法是被這個處理機(jī)認(rèn)可的。這個屬性是可選的抖格,且如果它沒有被定義诺苹,它就不會用來篩選請求。
routes.json
的樣例在源碼httppluginserver.h
中也很全面:
{
version: 1,
plugins: [
{
name: "home",
path: "/home/vinipsmaker/Projetos/tufao-project42/build/plugins/libhome.so",
customData: {appName: "Hello World", root: "/"}
},
{
name: "user",
path: "show_user.so",
dependencies: ["home"]
},
{
name: "404",
path: "/usr/lib/tufao/plugins/notfound.so",
customData: "<h1>Not Found</h1><p>I'm sorry, but it's your fault</p>"
}
],
requests: [
{
path: "^/$",
plugin: "home",
method: "GET"
},
{
path: "^/user/(\w*)$",
plugin: "user"
},
{
path: "",
plugin: "404"
}
]
}
可以看出例子給的是linux環(huán)境下的雹拄,插件的path
都是絕對路徑下的.so
文件收奔。在MSVC環(huán)境下,插件的path
需要填寫.lib
文件的地址(當(dāng)然滓玖,.dll
文件也需要和.lib
文件處于同一目錄下坪哄。至于.pdb
文件如果不是Debug環(huán)境,就不要放進(jìn)去了)势篡。這個地址可以是相對配置文件路徑
的相對路徑翩肌。插件的name
可以與插件庫文件的名字甚至是插件庫文件里的主class
的名字都不同,其主要是用于在requests
部分區(qū)分插件用的禁悠。
基本上念祭,懂得這些就可以做出基于tufao的web服務(wù)器了。
四绷蹲,總結(jié)
作為輕量級的web服務(wù)器,tufao簡潔易用的特性真是太棒了顾孽。不過祝钢,其性能到底如何,我并沒有具體測試若厚。以后拦英,還是要對此做具體測試。