QtWebApp的使用【在Qt中搭建HTTP服务器】(三)

833 阅读16分钟

在前面的学习中,我们编写了几个控制器,这些控制器通过将散布在C ++源文件中的硬编码片段进行级联,来生成HTML文档。源代码看起来没有很好的组织结构,生成的HTML文档也很丑陋。如果要生成更复杂的网站,那现有的代码结构会使这个过程变得很痛苦。

因此,整个世界的Web开发人员都在一些替代方案之间进行选择:

  • 反其道而行之,将程序代码插入HTML文档,例如JSP和PHP就选择了这种方法。
  • 在客户端使用Javascript(AJAX),将服务器提供的原始数据展示在屏幕上。
  • 将数据合并到准备好的HTML文件中,然后再将结果发送到Web浏览器。

第一种方法需要一种可以在运行时编译机器代码的编程语言,因此不能与 C ++ 一起很好地工作。

第二种方法可能在小型远程设备(例如智能手机)上运行缓慢,并依赖于浏览器有限的调试支持。但是,浏览器和工具每年也都在改进。

最后一种方法在技术上要简单得多,因为这只是对纯文本的搜索/替换操作。这在一般的网站上提供了最佳性能,同时消耗了适度的内存。现代CPU可以以惊人的速度修改字符串。这就是 Template 类所提供的。

模板

在使用 Template类 前,必须先了解: Template类将整个生成的文档保留在内存中,直到将其发送出去为止。所以,不应该尝试使用该类处理 100MB 或更大的文档。模板引擎仅用于正常大小的网站。

你可以将模板引擎用于任何基于文本的文件格式,而不仅仅是HTML。生成XML和JSON也很有用。

QtWebApp的模板引擎需要将一些配置设置添加到webapp1.ini中:

[templates]
path=../docroot
suffix=.html
encoding=UTF-8
cacheSize=1000000
cacheTime=60000
  • path 还是相对于配置文件的文件夹。这是我们用来存储模板文件(不完整的HTML文件,包含可变内容的占位符)的文件夹。
  • suffix 是后缀,将被添加到模板名称后面。如果加载模板"wolfi",那么模板引擎将会加载文件 “wolfi.html”。你可以使用任何后缀,它对程序没有任何意义。有些人喜欢使用“ .dhtml”来区分静态和动态HTML文件。因此,它是可配置的。
  • encoding 参数是必需的,因为模板文件会被读入QString对象里。我更喜欢UTF-8,因为它支持任何语言的所有字符。我还使用具有UTF-8功能的文本编辑器来编辑它们(Linux下的gedit或Windows下的Notepad ++)。
  • 为了提高性能,服务器缓存了QString对象。 CacheSize 指定缓存可能占用的最大内存量。
  • cacheTime 指定文件在内存中最多保留多长时间。我们已经为静态文件控制器设置了相同的设置。

当我们将高速缓存Cache设置得足够大以将所有模板文件保留在内存中时,将会获得非常好的性能。但是,只有在服务器有足够的可用RAM时才这样做。否则可能会冻结或崩溃。

模板引擎是QtWebApp中的一个单独模块,因此必须在项目文件MyFirstWebApp.pro中添加一行(每个人组织文件的方式不同,所以这里根据自己的实际情况选择路径):

include(../QtWebApp/QtWebApp/templateengine/templateengine.pri)
INCLUDEPATH += ../QtWebApp/QtWebApp/templateengine/include

我们需要一个 TemplateCache 实例的全局指针,使整个程序都可以访问它。

注意:要先把QtWebApp中templateengine文件夹下的文件先复制到你自己项目中的templateengine文件夹中!

在这里插入图片描述
首先添加到global.h:

#ifndef GLOBAL_H
#define GLOBAL_H

#include "httpsessionstore.h"
#include "staticfilecontroller.h"
#include "templatecache.h"

using namespace stefanfrings;

extern HttpSessionStore* sessionStore;
extern StaticFileController* staticFileController;
extern TemplateCache* templateCache;

#endif // GLOBAL_H

global.cpp:

#include "global.h"

HttpSessionStore* sessionStore;
StaticFileController* staticFileController;
TemplateCache* templateCache;

在main.cpp中,我们配置了 TemplateCache:

int main(int argc, char *argv[])
{
    QCoreApplication app(argc, argv);
    QString configFileName=searchConfigFile();

    // Session store
    QSettings* sessionSettings=new QSettings(configFileName,QSettings::IniFormat,&app);
    sessionSettings->beginGroup("sessions");
    sessionStore=new HttpSessionStore(sessionSettings,&app);

    // Static file controller
    QSettings* fileSettings=new QSettings(configFileName,QSettings::IniFormat,&app);
    fileSettings->beginGroup("files");
    staticFileController=new StaticFileController(fileSettings,&app);

    // Configure template cache
    QSettings* templateSettings=new QSettings(configFileName,QSettings::IniFormat,&app);
    templateSettings->beginGroup("templates");
    templateCache=new TemplateCache(templateSettings,&app);

    // HTTP server
    QSettings* listenerSettings=new QSettings(configFileName,QSettings::IniFormat,&app);
    listenerSettings->beginGroup("listener");
    new HttpListener(listenerSettings,new RequestMapper(&app),&app);

    return app.exec();
}

现在我们有了模板缓存,就可以在需要时自动将模板文件加载到内存中。下一步,我们需要的是一个能加载并输出模板的控制器。为了快速预览,让我们简单地加载已经存在的文件hello.html。

创建一个新类 DataTemplateController,它继承自 HttpRequestHandler。我们将其绑定到路径“/list2”。

datatemplatecontroller.h:

#ifndef DATATEMPLATECONTROLLER_H
#define DATATEMPLATECONTROLLER_H

#include "httprequesthandler.h"

using namespace stefanfrings;

class DataTemplateController: public HttpRequestHandler {
    Q_OBJECT
public:
    DataTemplateController(QObject* parent=0);
    void service(HttpRequest& request, HttpResponse& response);
private:
    QList<QString> list;
};

#endif // DATATEMPLATECONTROLLER_H

datatemplatecontroller.cpp:

#include "datatemplatecontroller.h"
#include "template.h"
#include "global.h"

DataTemplateController::DataTemplateController(QObject* parent)
    : HttpRequestHandler(parent) {
    list.append("Robert");
    list.append("Lisa");
    list.append("Hannah");
    list.append("Ludwig");
    list.append("Miranda");
    list.append("Fracesco");
    list.append("Kim");
    list.append("Jacko");
}

void DataTemplateController::service(HttpRequest &request, HttpResponse &response) {
    QByteArray language=request.getHeader("Accept-Language");
    response.setHeader("Content-Type", "text/html; charset=UTF-8");

    Template t=templateCache->getTemplate("files/hello",language);
    response.write(t.toUtf8(),true);
}

添加到requestmapper.h:

#ifndef REQUESTMAPPER_H
#define REQUESTMAPPER_H

...
#include "datatemplatecontroller.h"

using namespace stefanfrings;

class RequestMapper : public HttpRequestHandler {
    Q_OBJECT
public:
    ...
private:
    ...
    DataTemplateController dataTemplateController;
};

#endif // REQUESTMAPPER_H

添加到requestmapper.cpp:

...
void RequestMapper::service(HttpRequest& request, HttpResponse& response) {
    QByteArray path=request.getPath();
    ...
    else if (path=="/list2") {
        dataTemplateController.service(request, response);
    }
    else if (path.startsWith("/files")) {
        staticFileController->service(request,response);
    }
    ...

    qDebug("RequestMapper: finished request");
}

现在,运行程序,并在浏览器中打开URL:http://localhost:8080/list2。你会看见"Hello World!" 因为那是模板文件/hello.html的内容。

在这里插入图片描述
这里只是用一个很简单的模板文件进行举例,在实际应用中,你可以编写一个用来展示登录界面的 .html 文件,在请求映射器中与"/login"绑定,这样一来,输入http://localhost:8080/login 时,就会显示该界面。

变量

现在让我们创建一个包含占位符的真实模板文件。

docroot/listdata.html:

<html>
    <body>
        Hello {name}
    </body>
</html>

并用这一替换 datatemplatecontroller.cpp 的 service() :

void DataTemplateController::service(HttpRequest &request, HttpResponse &response) {
    HttpSession session=sessionStore->getSession(request,response,true);
    QString username=session.get("username").toString();
    QByteArray language=request.getHeader("Accept-Language");
    qDebug("language=%s",qPrintable(language));
    
    response.setHeader("Content-Type", "text/html; charset=UTF-8");

    Template t=templateCache->getTemplate("listdata",language);
    t.setVariable("name",username.toString());
    response.write(t.toUtf8(),true);
}

首先,我们获取当前用户的 HttpSession 对象。然后,我们读出大多数网络浏览器发送的Accept-Language标头,以指示用户喜欢哪种语言。
然后,设置一个响应头,以告知浏览器我们将返回UTF-8编码的HTML文档。

从缓存中获取模板文件后,我们在其中设置了一个变量 name ,然后将结果写入HTTP响应。

运行该程序,打开 http://localhost:8080/list2。你只会看到"Hello "。缺少了名称。因为用户还没有创建会话,因此服务器尚不知道其名称。

现在通过 http://localhost:8080/login 进行登录,然后通过再次调用模板 http://localhost:8080/list2

这次,页面中显示 “Hello test”。所以可变的占位符 {name} 被替换为值 “test” 这是用户会话中的用户名。

在这里插入图片描述

条件语句

现在让我们添加一个条件判断,用来区分用户是否已登录。

docroot / listdata.html:

<html>
    <body>
        {if logged-in}
            Hello {name}
        {else logged-in}
            You are not logged in. 
            <br>
            Go to the <a href="/login">login</a> page and then try again.
        {end logged-in}
    </body>
</html>

然后我们必须向 datatemplatecontroller.cpp 添加一行以设置条件 "logged-in"

void DataTemplateController::service(HttpRequest &request, HttpResponse &response) {
    HttpSession session=sessionStore->getSession(request,response,true);
    QString username=session.get("username").toString();
    QByteArray language=request.getHeader("Accept-Language");
    qDebug("language=%s",qPrintable(language));
    
    response.setHeader("Content-Type", "text/html; charset=UTF-8");

    Template t=templateCache->getTemplate("listdata",language);
    t.setVariable("name",username);
    t.setCondition("logged-in",!username.isEmpty());
    response.write(t.toUtf8(),true);
}

再次运行程序:
在这里插入图片描述
点击链接进行登录后,你将会看到:

在这里插入图片描述
再次打开 http://localhost:8080/list2

在这里插入图片描述

现在,你能看到不同的内容了,因为 "logged-in"现在为 true。除了 {if condition} ,你也可以使用相反的表述,如 {ifnot condition}

循环

模板引擎可以循环重复网站的某些部分。当你遍历数据列表时,就需要用到循环,这也是我们现在要做的。我们已经在构造函数中创建了一个列表,但到目前为止还没有使用到它。

docroot / listdata.html中的更改:

<html>
    <body>
        ...
        <p>
        List of names:
        <table>
            {loop row}
                <tr>
                    <td>{row.number}</td>
                    <td>{row.name}</td>
                </tr>
            {end row}
        </table>
    </body>
</html>

datatemplatecontroller.cpp中的更改:

void DataTemplateController::service(HttpRequest &request, HttpResponse &response) {
    HttpSession session=sessionStore->getSession(request,response,false);
    QString username=session.get("username").toString();
    QByteArray language=request.getHeader("Accept-Language");
    qDebug("language=%s",qPrintable(language));

    response.setHeader("Content-Type", "text/html; charset=UTF-8");

    Template t=templateCache->getTemplate("listdata",language);
    t.setVariable("name",username);
    t.setCondition("logged-in",!username.isEmpty());
    t.loop("row",list.size());
    for(int i=0; i<list.size(); i++) {
        QString number=QString::number(i);
        QString name=list.at(i);
        t.setVariable("row"+number+".number",number);
        t.setVariable("row"+number+".name",name);
    }
    response.write(t.toUtf8(),true);
}

该模板将一个列表行包含在一个循环体内。这样该行将重复渲染多次。在循环体中,只有一行HTML代码。变量名称以循环体名称为前缀。

当模板引擎重复该循环3次时,内存中将包含以下临时文档:

<html>
    <body>
        ...
        <p>
        List of names:
        <table>
                <tr>
                    <td>{row0.number}</td>
                    <td>{row0.name}</td>
                </tr>
                <tr>
                    <td>{row1.number}</td>
                    <td>{row1.name}</td>
                </tr>       
                <tr>
                    <td>{row2.number}</td>
                    <td>{row2.name}</td>
                </tr>                           
        </table>
    </body>
</html>

你会发现 {loop row} 和 {end row} 标记消失了。表行重复了3次,变量名也用连续数字进行了修改。

此信息能帮助你了解在for () 循环中新增的C ++代码:

for(int i=0; i<list.size(); i++) {
    QString number=QString::number(i);
    QString name=list.at(i);
    t.setVariable("row"+number+".number",number);
    t.setVariable("row"+number+".name",name); }

Template类能正确地区分变量 {name} 和 {row.name},前者是用户名,而后者则在循环/表中使用。

运行程序并打开 http://localhost:8080/list2

在这里插入图片描述
注意:你可以使用t.enableWarnings(true) 为缺少的占位符启用警告消息。例如,当 C++ 代码尝试设置变量 {name} ,但在模板中没有这样的占位符时,这个错误通常会被静默忽略掉,但在开发或生产中,如果启用此类警告会很有帮助。

日志

到目前为止,我们只是将所有消息写到控制台窗口。这对生产环境不利,因为你可能需要查看旧的日志消息,例如两天前的消息。

你可以简单地将输出重定向到文件( MyFirstWebApp > logfile.txt ),但这有两个问题:

  • 在许多系统上,输出重定向有些慢。
  • 日志文件将变得无穷无尽,如果不停止Web服务器一小段时间,就无法避免这种情况。

因此,最好让Web服务器本身将所有消息写入文件。这就是记录器模块的作用。

要将日志记录模块的源文件包含到项目中,需要在项目文件中添加一行:

include(../QtWebApp/QtWebApp/logging/logging.pri)
INCLUDEPATH += ../QtWebApp/QtWebApp/logging/include

注意:要先将QtWebApp/QtWebApp/logging中的文件添加到自己的项目中,如图所示:

在这里插入图片描述
然后将下面的代码添加到程序的配置文件中:

[logging]
minLevel=2
bufferSize=100
fileName=../logs/webapp1.log
maxSize=1000000
maxBackups=2
timestampFormat=dd.MM.yyyy hh:mm:ss.zzz
msgFormat={timestamp} {typeNr} {type} {thread} {msg}
;type的取值有: 0=DEBUG, 1=WARNING, 2=CRITICAL, 3=FATAL, 4=INFO

上面的示例配置启用了本地线程环形缓冲区,该缓冲区收集内存中的次要消息,直到发生严重问题为止。然后它会将重要消息与收集的警告和调试信息一起写入日志文件。此方法可使日志文件在一切正常运行时保持较小,仅在出现问题时才编写调试消息。

从1.7.9版本开始,对INFO消息的处理就像是DEBUG消息,因此它们不再触发缓冲区的刷新。

使用这些缓冲区会浪费内存和性能,但好处更多。

  • 你可以通过设置 bufferSize = 0 来禁用缓冲。在这种情况下,只有在设置的 minLevel 及以上的版本才会被写入日志文件。
  • 文件名相对于配置文件所在的文件夹。
  • maxSize 参数限制日志文件的大小(以字节为单位)。超过此限制时,记录器将开始创建新文件。
  • maxBackups 指定在磁盘上应保留多少个旧日志文件。
  • timestampFormat 设置了时间戳的格式。
  • msgFormat 设置每个消息的格式。以下字段可用:
{timestamp} 创建日期和时间
{typeNr} 消息的类型或级别,数字格式(0-4)
{type} 消息格式的消息类型或级别(DEBUG, WARNING, CRITICAL, FATAL, INFO)
{thread} 线程的ID号
{msg} 消息文字
{xxx} 自定义的任何记录器变量

QT 5.0和更高版本在调试模式下还有一些其他变量:
{file} 生成消息的源代码文件名
{function} 生成消息的功能
{line} 生成消息的行号

你也可以使用 \n 插入换行符和 \t将制表符插入消息格式。上面的所有变量也可以在日志消息中使用,例如:

qCritical("An error occured in {file}: out of disk space");

我们需要一个 FileLogger 实例的全局指针,使整个程序都可以访问它。首先添加到global.h:

#include "httpsessionstore.h"
#include "staticfilecontroller.h"
#include "templatecache.h"
#include "filelogger.h"

using namespace stefanfrings;

/** Storage for session cookies */
extern HttpSessionStore* sessionStore;

/** Controller for static files */
extern StaticFileController* staticFileController;

/** Cache for template files */
extern TemplateCache* templateCache;

/** Redirects log messages to a file */
extern FileLogger* logger;

#endif // GLOBAL_H

global.cpp:

#include "global.h"

HttpSessionStore* sessionStore;
StaticFileController* staticFileController;
TemplateCache* templateCache;
FileLogger* logger;

在main.cpp中,我们配置了 FileLogger:

int main(int argc, char *argv[])
{
    QCoreApplication app(argc, argv);
    QString configFileName=searchConfigFile();

    // Configure logging
    QSettings* logSettings=new QSettings(configFileName,QSettings::IniFormat,&app);
    logSettings->beginGroup("logging");
    logger=new FileLogger(logSettings,10000,&app);
    logger->installMsgHandler();

    // Log the library version
    qDebug("QtWebApp has version %s",getQtWebAppLibVersion());

    // Session store
    QSettings* sessionSettings=new QSettings(configFileName,QSettings::IniFormat,&app);
    sessionSettings->beginGroup("sessions");
    sessionStore=new HttpSessionStore(sessionSettings,&app);

    // Static file controller
    QSettings* fileSettings=new QSettings(configFileName,QSettings::IniFormat,&app);
    fileSettings->beginGroup("files");
    staticFileController=new StaticFileController(fileSettings,&app);

    // Configure template cache
    QSettings* templateSettings=new QSettings(configFileName,QSettings::IniFormat,&app);
    templateSettings->beginGroup("templates");
    templateCache=new TemplateCache(templateSettings,&app);

    // HTTP server
    QSettings* listenerSettings=new QSettings(configFileName,QSettings::IniFormat,&app);
    listenerSettings->beginGroup("listener");
    new HttpListener(listenerSettings,new RequestMapper(&app),&app);

    return app.exec();
}

数值10000是刷新间隔(以毫秒为单位),记录器将使用该刷新间隔来重新加载配置文件。因此,你可以在程序运行时编辑任何记录器设置,并且更改会在几秒钟后生效,无需重新启动服务器。如果不想自动重新加载,请使用值0。

顺便说一下,示例代码中的 getQtWebAppLibVersion() 方法能够查询并记录库的版本号。

注意:不要忘记创建一个空文件夹MyFirstWebApp/logs。记录器本身不会创建文件夹。

现在,启动应用程序,看看会发生什么。你不会看到太多内容,因为该程序没有错误,因此日志文件保持为空。但是可以看到控制台窗口中的输出减少到最小:
在这里插入图片描述
让我们将一个qCritical()消息插入logincontroller.cpp,然后我们可以看到日志缓冲区有效:

response.write("<form method='POST' action='/login'>");
if (!username.isEmpty()) {
    response.write("No, that was wrong!<br><br>");
    qCritical("Login failed!");
}
response.write("Please log in:<br>");

然后打开URL http://localhost:8080/login?username=test&password=wrong

再次查看日志文件:

在这里插入图片描述
现在通过将 minLevel 设为0来进行另一项测试。保存配置文件,等待10秒钟,然后打开URL: http://localhost:8080/hello。再次检查日志文件。可以看到尽管没有发生错误,但现在所有调试消息都已写出。因此,无需重新启动程序就可以更改日志级别。在这里插入图片描述

记录器变量

上面提到了记录器支持自定义的变量。这些变量是线程局部的,并保留在内存中,直到被清除为止。对于Web应用程序,在每条消息中记录当前用户的名称可能会很有用。

让我们将下面的代码添加到 requestmapper.cpp 以设置记录器变量:

void RequestMapper::service(HttpRequest& request, HttpResponse& response) {
    QByteArray path=request.getPath();
    qDebug("RequestMapper: path=%s",path.data());
    HttpSession session=sessionStore->getSession(request,response,false);
    QString username=session.get("username").toString();
    logger->set("currentUser",username);
    ...
}

这样,请求映射器会在将请求传递到控制器类之前,为所有传入的HTTP请求查询调用它们的用户的名称。

现在,我们可以修改配置文件以使用该变量:

msgFormat={timestamp} {typeNr} {type} {thread} User:{currentUser} {msg}

运行程序并打开URL:http://localhost:8080/login?username=test&password=hello 两次。然后再次检查日志文件:
在这里插入图片描述
可以看到该变量 {currentUser} 在用户登录之前为空。随后所有后续请求都使用该用户的名称登录。

注意:我们在RequestMapper类里投入了很多静态资源 (logger, sessionStore, staticFileController, templateCache) 。在实际的应用程序中,建议创建一个单独的类,例如名为"Globals"的类,这样,每个人都知道在哪里可以找到这样的资源。或按照Demo1项目中的示例,将它们放在任何类之外的cpp源文件中。

日志缓冲区和线程池

因为线程被重新用于后续的HTTP请求,所以记录器可能会输出你所关心的以外的信息。例如,假设第一个成功的HTTP请求产生一些隐藏的调试消息,而第二个请求由相同的处理线程却产生了错误。那么,日志文件中将包含错误消息以及所有缓冲的调试消息。但是其中也有一些来自先前的你已经不需要的HTTP请求。

要清除两个HTTP请求之间的缓冲区,请将以下代码添加到 requestmapper.cpp:

void RequestMapper::service(HttpRequest& request, HttpResponse& response) {
    ...
    else {
        response.setStatus(404,"Not found");
        response.write("The URL is wrong, no such document.");
    }

    qDebug("RequestMapper: finished request");
    logger->clear(true,true);
}

这样,无论何时完成HTTP请求的处理,都将清理记录器的内存。当同一线程处理下一个请求时,它将以空缓冲区开始。

Windows服务

要将Web服务器作为Unix守护程序运行,只需像往常一样为它编写一个初始化脚本。但这在Windows下并不是那么简单。Trolltech网站(该公司不再存在)上有一个很棒的免费库 "qtservice helper" ,该库简化了实际Windows服务的创建。我将其嵌入到QtWebApp中。qtservice也与Linux兼容。

要使用 qtservice 模块,必须在项目文件中添加:

include(../QtWebApp/QtWebApp/qtservice/qtservice.pri)
INCLUDEPATH += ../QtWebApp/QtWebApp/qtservice/include

注意:要先将QtWebApp/QtWebApp/qtservice中的文件放到自己的项目中(每个人组织文件的方式不同,这里需要根据实际情况配置路径),qtservice的.pri文件有点区别,这里要特别注意,否则无法通过编译

在这里插入图片描述
创建一个名称为Startup的新类,该类继承自 QtService<QCoreApplication>

startup.h:

#ifndef STARTUP_H
#define STARTUP_H

#include <QCoreApplication>
#include "qtservice.h"
#include "httplistener.h"

using namespace stefanfrings;

class Startup : public QtService<QCoreApplication> {

public:
    Startup(int argc, char *argv[]);

protected:
    void start();
    void stop();

private:
    HttpListener* listener;
};

#endif // STARTUP_H

startup.cpp:

#include "startup.h"

void Startup::start() {
    // todo
}

void Startup::stop() {
    qWarning("Webserver has been stopped");
}

Startup::Startup(int argc, char *argv[])
    : QtService<QCoreApplication>(argc, argv, "MyFirstWebApp") {
    setServiceDescription("My first web server");
    setStartupType(QtServiceController::AutoStartup);
}

然后将main.ccp的整个代码移到startup.cpp中。main() 函数的内容现在属于 Startup::start()。

用 “QCoreApplication* app = application()”替换第一行“QCoreApplication app(argc, argv)” 。并将所有出现的"&app" 改为 “app”。最后删除 app.exec()

这就是最后的整个startup.cpp:

#include <QCoreApplication>
#include <QSettings>
#include <QFile>
#include <QDir>
#include <QString>
#include "requestmapper.h"
#include "startup.h"
#include "global.h"

/**
 * Search the configuration file.
 * Aborts the application if not found.
 * @return The valid filename
 */
QString searchConfigFile() {
    QString binDir=QCoreApplication::applicationDirPath();
    QString appName=QCoreApplication::applicationName();
    QFile file;
    file.setFileName(binDir+"/webapp1.ini");
    if (!file.exists()) {
        file.setFileName(binDir+"/etc/webapp1.ini");
        if (!file.exists()) {
            file.setFileName(binDir+"/../etc/webapp1.ini");
            if (!file.exists()) {
                file.setFileName(binDir+"/../"+appName+"/etc/webapp1.ini");
                if (!file.exists()) {
                    file.setFileName(binDir+"/../../"+appName+"/etc/webapp1.ini");
                    if (!file.exists()) {
                        file.setFileName(binDir+"/../../../../../"+appName+"/etc/webapp1.ini");
                        if (!file.exists()) {
                            file.setFileName(QDir::rootPath()+"etc/webapp1.ini");
                        }
                    }
                }
            }
        }
    }
    if (file.exists()) {
        QString configFileName=QDir(file.fileName()).canonicalPath();
        qDebug("using config file %s", qPrintable(configFileName));
        return configFileName;
    }
    else {
        qFatal("config file not found");
    }
}

void Startup::start() {
    QCoreApplication* app = application();
    QString configFileName=searchConfigFile();

    // Configure logging
    QSettings* logSettings=new QSettings(configFileName,QSettings::IniFormat,app);
    logSettings->beginGroup("logging");
    logger=new FileLogger(logSettings,10000,app);
    logger->installMsgHandler();

    // Log the library version
    qDebug("QtWebApp has version %s",getQtWebAppLibVersion());

    // Session store
    QSettings* sessionSettings=new QSettings(configFileName,QSettings::IniFormat,app);
    sessionSettings->beginGroup("sessions");
    sessionStore=new HttpSessionStore(sessionSettings,app);

    // Static file controller
    QSettings* fileSettings=new QSettings(configFileName,QSettings::IniFormat,app);
    fileSettings->beginGroup("files");
    staticFileController=new StaticFileController(fileSettings,app);

    // Configure template cache
    QSettings* templateSettings=new QSettings(configFileName,QSettings::IniFormat,app);
    templateSettings->beginGroup("templates");
    templateCache=new TemplateCache(templateSettings,app);

    // HTTP server
    QSettings* listenerSettings=new QSettings(configFileName,QSettings::IniFormat,app);
    listenerSettings->beginGroup("listener");
    listener=new HttpListener(listenerSettings,new RequestMapper(app),app);
}

void Startup::stop() {
    delete listener;
    qWarning("Webserver has been stopped");
}

Startup::Startup(int argc, char *argv[])
    : QtService<QCoreApplication>(argc, argv, "MyFirstWebApp") {
    setServiceDescription("My first web server");
    setStartupType(QtServiceController::AutoStartup);
}

现在新的main.cpp非常小:

#include "startup.h"

int main(int argc, char *argv[])
{
    Startup startup(argc, argv);
    return startup.exec();
}

要像以前一样在控制台窗口(或Qt Creator)中运行该程序,必须在这里输入附加命令行参数“ -e”:

在这里插入图片描述
然后运行程序。它看起来应该和之前没有qtservice库一样。

当你现在想转到目标Windows服务器时,输入命令“ -i”以安装Windows服务。不输入命令则直接启动服务。而命令“ -t”将终止服务。

你还可以使用系统控制面板中的服务管理器屏幕来检查状态,启动和停止服务。