Tuf?o——QT輕量級Web服務(wù)器庫使用筆記

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所在的debugrelease文件夾焦履,而是.exe所在目錄的上一級。插件系統(tǒng)所需的routes.json雏逾,如果也使用相對路徑(相對于應(yīng)用所在目錄的相對路徑)的話嘉裤,也必須注意這一點(diǎn)。(如果你對插件加載實(shí)在摸不著頭腦栖博,可以通過qInstallMessageHandlerqWarning消息輸出到外部文件里屑宠,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)的:

  1. 你從一個使用默認(rèn)構(gòu)造的HttpPluginServer開始
  2. 你調(diào)用了setConfig(<一個不存在的文件>)
    1. HttpPluginServer無法找到此文件
    2. HttpPluginServer::setConfig函數(shù)返回false
    3. HttpPluginServer對象保持之前的狀態(tài)
  3. 你調(diào)用了setConfig(<一個無效的文件>)
    1. HttpPluginServer開始監(jiān)控這個配置文件
    2. HttpPluginServer::setConfig函數(shù)返回true
    3. HttpPluginServer讀取了這個無效的文件丧叽,然后仍保持之前的狀態(tài)
  4. 你用一個有效的配置填充了這個配置文件
    1. HttpPluginServer對象加載這個新內(nèi)容
    2. HttpPluginServer嘗試加載每個插件并填充路由卫玖。如果有插件沒法被加載,它會忽略這個插件并通過qWarning輸出一個警告信息踊淳。如果你需要加載此插件假瞬,可對這個配置文件作出任何修改陕靠,隨后HttpPluginServer將會再次嘗試
  5. 你用一個無效的配置填充了這個配置文件
    1. HttpPluginServer對象看到并忽略了此次變更,繼續(xù)保持之前的狀態(tài)
  6. 你移除了這個配置文件
    1. 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簡潔易用的特性真是太棒了顾孽。不過祝钢,其性能到底如何,我并沒有具體測試若厚。以后拦英,還是要對此做具體測試。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末测秸,一起剝皮案震驚了整個濱河市疤估,隨后出現(xiàn)的幾起案子灾常,更是在濱河造成了極大的恐慌,老刑警劉巖铃拇,帶你破解...
    沈念sama閱讀 221,406評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件钞瀑,死亡現(xiàn)場離奇詭異,居然都是意外死亡慷荔,警方通過查閱死者的電腦和手機(jī)雕什,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,395評論 3 398
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來显晶,“玉大人贷岸,你說我怎么就攤上這事×坠停” “怎么了偿警?”我有些...
    開封第一講書人閱讀 167,815評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長唯笙。 經(jīng)常有香客問我螟蒸,道長,這世上最難降的妖魔是什么睁本? 我笑而不...
    開封第一講書人閱讀 59,537評論 1 296
  • 正文 為了忘掉前任尿庐,我火速辦了婚禮,結(jié)果婚禮上呢堰,老公的妹妹穿的比我還像新娘抄瑟。我一直安慰自己,他們只是感情好枉疼,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,536評論 6 397
  • 文/花漫 我一把揭開白布皮假。 她就那樣靜靜地躺著,像睡著了一般骂维。 火紅的嫁衣襯著肌膚如雪惹资。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,184評論 1 308
  • 那天航闺,我揣著相機(jī)與錄音褪测,去河邊找鬼。 笑死潦刃,一個胖子當(dāng)著我的面吹牛侮措,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播乖杠,決...
    沈念sama閱讀 40,776評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼分扎,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了胧洒?” 一聲冷哼從身側(cè)響起畏吓,我...
    開封第一講書人閱讀 39,668評論 0 276
  • 序言:老撾萬榮一對情侶失蹤墨状,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后菲饼,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體肾砂,經(jīng)...
    沈念sama閱讀 46,212評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,299評論 3 340
  • 正文 我和宋清朗相戀三年巴粪,在試婚紗的時候發(fā)現(xiàn)自己被綠了通今。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,438評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡肛根,死狀恐怖辫塌,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情派哲,我是刑警寧澤臼氨,帶...
    沈念sama閱讀 36,128評論 5 349
  • 正文 年R本政府宣布,位于F島的核電站芭届,受9級特大地震影響储矩,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜褂乍,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,807評論 3 333
  • 文/蒙蒙 一持隧、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧逃片,春花似錦屡拨、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,279評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至损离,卻和暖如春哥艇,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背僻澎。 一陣腳步聲響...
    開封第一講書人閱讀 33,395評論 1 272
  • 我被黑心中介騙來泰國打工貌踏, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人窟勃。 一個月前我還...
    沈念sama閱讀 48,827評論 3 376
  • 正文 我出身青樓祖乳,卻偏偏與公主長得像,于是被迫代替她去往敵國和親拳恋。 傳聞我的和親對象是個殘疾皇子凡资,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,446評論 2 359

推薦閱讀更多精彩內(nèi)容