In the previous section, we wrote several controllers that generate HTML documents by cascading hard-coded fragments scattered across C ++ source files. The source code doesn’t look well organized, and the generated HTML document is ugly. If you have to generate more complex sites, the existing code structure can make the process painful.

As a result, Web developers around the world are choosing between several alternatives:

  • Instead, insert program code into HTML documents, such as JSP and PHP.
  • Using Javascript (AJAX) on the client side, the raw data provided by the server is displayed on the screen.
  • Merge the data into the prepared HTML file, and then send the results to the Web browser.

The first approach requires a programming language that compiles machine code at run time and therefore does not work well with C ++.

The second approach may run slowly on small remote devices, such as smartphones, and rely on limited debugging support from browsers. But browsers and tools are getting better every year.

The last method is technically much simpler, because it’s just a search/replace operation on plain text. This provides the best performance on an average web site while consuming a modest amount of memory. Modern cpus can modify strings at astonishing speed. This is what the Template class provides.

The template

Before using the Template class, it is important to understand that the Template class keeps the entire generated document in memory until it is sent. Therefore, you should not attempt to use this class to process documents of 100MB or larger. The template engine is only used for normal-sized sites.

You can use the template engine for any text-based file format, not just HTML. Generating XML and JSON is also useful.

QtWebApp’s template engine needs to add some configuration Settings to webApp1.ini:

[templates] path=.. /docroot suffix=.html encoding=UTF-8 cacheSize=1000000 cacheTime=60000Copy the code
  • Path is also the folder relative to the configuration file. This is the folder where we store template files (incomplete HTML files with placeholders for variable content).
  • Suffix is the suffix that will be added to the template name. If the template “wolfi” is loaded, the template engine will load the file “wolfi.html”. You can use any suffix, it doesn’t mean anything to the program. Some people like to use “.dhtml “to distinguish between static and dynamic HTML files. Therefore, it is configurable.
  • The encoding argument is required because the template file is read into the QString. I prefer UTF-8 because it supports all characters in any language. I also used a UTF-8-capable text editor to edit them (Gedit for Linux or Notepad ++ for Windows).
  • To improve performance, the server caches qStrings. CacheSize Specifies the maximum amount of memory that the cache can use.
  • CacheTime specifies how long a file can remain in memory. We have set the same Settings for the static file controller.

When we make the Cache Cache large enough to keep all the template files in memory, we get great performance. However, this is only done if the server has enough RAM available. Otherwise it may freeze or crash.

The template engine is a separate module in QtWebApp, so you have to add a line to the project file myFirstWebapp.pro (everyone organizes the file differently, so choose your own path here) :

include(.. /QtWebApp/QtWebApp/templateengine/templateengine.pri) INCLUDEPATH += .. /QtWebApp/QtWebApp/templateengine/includeCopy the code

We need a global pointer to the TemplateCache instance so that the entire program can access it.

Note: copy files from the TemplateEngine folder in QtWebApp to your own templateEngine folder first!



First add to 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
Copy the code

Global. CPP:

#include "global.h"

HttpSessionStore* sessionStore;
StaticFileController* staticFileController;
TemplateCache* templateCache;
Copy the code

In main. CPP, we configure TemplateCache:

int main(int argc, char *argv[])
{
    QCoreApplication app(argc, argv);
    QString configFileName=searchConfigFile(a);// 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(a); }Copy the code

Now that we have a template cache, we can automatically load template files into memory when needed. Next, we need a controller that can load and output templates. For a quick preview, let’s simply load the existing file hello.html.

Create a new class DataTemplateController that inherits from HttpRequestHandler. We bind it to the path “/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
Copy the code

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);
}
Copy the code

Add to 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
Copy the code

Add to requestMapper.cpp:

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

Now, run the program, and open the URL in your browser: http://localhost:8080/list2. You’ll see “Hello World!” Because that’s the content of the template file /hello.html.



Using a simple template file as an example, you could write an.html file that shows the login interface, bind it to “/login” in the request mapper, and then typehttp://localhost:8080/login, the screen is displayed.

variable

Now let’s create a real template file that contains placeholders.

The docroot/listdata. HTML:

<html>
    <body>
        Hello {name}
    </body>
</html>
Copy the code

With the replacement datatemplatecontroller. CPP service () :

void DataTemplateController::service(HttpRequest &request, HttpResponse &response) {
    HttpSession session=sessionStore->getSession(request,response,true);
    QString username=session.get("username").toString(a); 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);
}
Copy the code

First, we get the current user’s HttpSession object. We then read the Accept-language header sent by most Web browsers to indicate which Language the user prefers. Then, set a response header to tell the browser that we are returning utF-8 encoded HTML documents.

After getting the template file from the cache, we set a variable name in it and write the result to the HTTP response.

Run the program, open http://localhost:8080/list2. You’ll just see “Hello “. The name is missing. Because the user has not created the session, the server does not yet know its name.

Now through http://localhost:8080/login to log in, and then by again call template at http://localhost:8080/list2.

This time, the page displays “Hello Test”. So the mutable placeholder {name} is replaced with the value “test” which is the user name in the user session.

Conditional statements

Now let’s add a conditional judgment to distinguish whether a user is logged in or not.

The 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>
Copy the code

Then we have to datatemplatecontroller. CPP add a line to set the terms “logged – in” :

void DataTemplateController::service(HttpRequest &request, HttpResponse &response) {
    HttpSession session=sessionStore->getSession(request,response,true);
    QString username=session.get("username").toString(a); 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);
}
Copy the code

Run the program again:



After clicking on the link to log in, you will see:



Open againhttp://localhost:8080/list2:

Now you can see something different because “logged-in” is now true. Instead of {if condition}, you can use the opposite statement, such as {ifnot condition}.

cycle

A template engine can loop over parts of a site. When you go through a list of data, you need to use a loop, and that’s what we’re going to do now. We’ve created a list in the constructor, but haven’t used it yet.

Changes in 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>
Copy the code

Datatemplatecontroller. CPP changes in:

void DataTemplateController::service(HttpRequest &request, HttpResponse &response) {
    HttpSession session=sessionStore->getSession(request,response,false);
    QString username=session.get("username").toString(a); 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(a); 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);
}
Copy the code

This template contains a list row in a loop body. This line will be rendered multiple times. In the body of the loop, there is only one line of HTML code. Variable names are prefixed with the body name of the loop.

When the template engine repeats this loop three times, memory contains the following temporary documents:

<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>
Copy the code

You’ll notice that {loop Row} and {end Row} tags have disappeared. Table rows are repeated three times, and variable names are changed with consecutive numbers.

This information can help you understand the new C ++ code in the for () loop:

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

The Template class correctly distinguishes between {name}, which is the user name, and {row.name}, which is used in loops/tables.

To run the program and open http://localhost:8080/list2:



Note: You can enable warning messages for missing placeholders using t.enableWarnings(true). For example, when C++ code tries to set the variable {name} but there is no such placeholder in the template, this error is usually silently ignored, but in development or production it is helpful to enable such warnings.

The log

So far, we’ve just written all the messages to the console window. This is not good for the production environment, because you may need to view old log messages, such as messages from two days ago.

You can simply redirect the output to a file (MyFirstWebApp > logfile.txt), but there are two problems with this:

  • On many systems, output redirection is somewhat slow.
  • Log files become endless, and you can’t avoid this without stopping the Web server for a short period of time.

Therefore, it is best to have the Web server itself write all the messages to a file. This is where the logger module comes in.

To include the source file for the logging module in your project, add a line to the project file:

include(.. /QtWebApp/QtWebApp/logging/logging.pri) INCLUDEPATH += .. /QtWebApp/QtWebApp/logging/includeCopy the code

Note: to add QtWebApp/QtWebApp/logging in the file to the own projects, as shown in figure:



Then add the following code to the program’s configuration file:

[logging]
minLevel=2
bufferSize=100fileName=.. /logs/webapp1.log maxSize=1000000
maxBackups=2timestampFormat=dd.MM.yyyy hh:mm:ss.zzz msgFormat={timestamp} {typeNr} {type} {thread} {msg} ; The value of type can be:0=DEBUG, 1=WARNING, 2=CRITICAL, 3=FATAL, 4=INFO
Copy the code

The example configuration above enables a local thread ring buffer that collects minor messages in memory until a serious problem occurs. It then writes the important messages to the log file along with the collected warning and debugging information. This approach keeps log files small when everything is running properly and debug messages are written only when problems occur.

Starting with version 1.7.9, INFO messages are treated like DEBUG messages, so they no longer trigger a buffer flush.

Using these buffers wastes memory and performance, but the benefits are even greater.

  • You can disable bufferSize by setting bufferSize = 0. In this case, only the version set at minLevel and above will be written to the log file.
  • The file name is relative to the folder where the configuration file resides.
  • The maxSize parameter limits the size of the log file in bytes. When this limit is exceeded, the logger starts creating a new file.
  • MaxBackups specifies how many old log files should be kept on the disk.
  • TimestampFormat Sets the format of the timestamp.
  • MsgFormat sets the format of each message. The following fields are available:
{timestamp} Date and time of creation {typeNr} Type or level of message, Number format (0-4) {type} Message type or level of message format (DEBUG, WARNING, CRITICAL, FATAL, INFO) {thread} Thread ID {MSG} message text {XXX} Custom any logger variables QT 5.0 and later have some other variables in debug mode: {file} Generate message source file name {function} generate message function {line} generate message line numberCopy the code

You can also use \n to insert newlines and \t to insert tabs into message formats. All of the above variables can also be used in log messages, for example:

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

We need a global pointer to an instance of FileLogger so that the entire program can access it. First add to 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
Copy the code

Global. CPP:

#include "global.h"

HttpSessionStore* sessionStore;
StaticFileController* staticFileController;
TemplateCache* templateCache;
FileLogger* logger;
Copy the code

In main. CPP, we configure FileLogger:

int main(int argc, char *argv[])
{
    QCoreApplication app(argc, argv);
    QString configFileName=searchConfigFile(a);// Configure logging
    QSettings* logSettings=new QSettings(configFileName,QSettings::IniFormat,&app);
    logSettings->beginGroup("logging");
    logger=new FileLogger(logSettings,10000,&app);
    logger->installMsgHandler(a);// 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(a); }Copy the code

The value 10000 is the refresh interval (in milliseconds) that the logger will use to reload the configuration file. As a result, you can edit any logger Settings while the program is running, and the changes take effect a few seconds later without having to restart the server. If you do not want to automatically reload, use the value 0.

By the way, the getQtWebAppLibVersion() method in the sample code queries and records the library version number.

Note: Don’t forget to create an empty folder, MyFirstWebApp/logs. The logger itself does not create folders.

Now, launch the application and see what happens. You won’t see much because the program has no errors, so the log file remains empty. But you can see that the output in the console window is reduced to a minimum:



Let’s insert a qCritical () message into loginController.cpp, and we can see the log buffer in effect:

response.write("<form method='POST' action='/login'>");
if(! username.isEmpty()) {
    response.write("No, that was wrong! 



"
); qCritical("Login failed!"); } response.write("Please log in:<br>"); Copy the code

Then open the URL http://localhost:8080/login? The username = test&password = wrong.

Look at the log file again:



Now do another test by setting minLevel to 0. Save the configuration file, wait 10 seconds, then open the URL:http://localhost:8080/hello. Check the log file again. You can see that all the debug messages are now written out, even though no errors have occurred. Therefore, the logging level can be changed without restarting the program.

Logger variable

As mentioned above, loggers support custom variables. These variables are thread-local and remain in memory until they are cleared. For Web applications, it might be useful to record the name of the current user in each message.

Let’s add the following code to requestMapper.cpp to set the logger variable:

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

In this way, the request mapper looks up the name of the user who called all incoming HTTP requests before passing them on to the controller class.

Now we can modify the configuration file to use this variable:

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

Run the program and open the URL:http://localhost:8080/login?username=test&password=helloTwice a day. Then check the log file again:



You can see that the variable {currentUser} is empty until the user logs in. All subsequent requests are then logged in using that user’s name.

Note: We put a lot of static resources in the RequestMapper class (Logger, sessionStore, staticFileController, templateCache). In a real-world application, it is recommended to create a separate class, such as one called “Globals”, so that everyone knows where to find such resources. Or, as shown in the Demo1 project, place them in CPP source files outside of any class.

Log buffers and thread pools

Because the thread is reused for subsequent HTTP requests, the logger may output information that is not of interest to you. For example, suppose the first successful HTTP request produces some hidden debug messages, while the second request, by the same processing thread, produces an error. The log file will then contain error messages and all cached debug messages. But some of them come from previous HTTP requests that you no longer need.

To clear the buffer between two HTTP requests, add the following code to 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);
}

Copy the code

This clears the memory of the logger whenever the processing of the HTTP request is completed. When the same thread processes the next request, it will start with an empty buffer.

Windows service

To run the Web server as a Unix daemon, simply write an initialization script for it as usual. But that’s not so easy on Windows. The Trolltech website (the company no longer exists) has a great free library called “QtService Helper” that simplifies the creation of actual Windows services. I embedded it in QtWebApp. Qtservice is also compatible with Linux.

To use the qtService module, you must add to the project file:

include(.. /QtWebApp/QtWebApp/qtservice/qtservice.pri) INCLUDEPATH += .. /QtWebApp/QtWebApp/qtservice/includeCopy the code

Note: to QtWebApp QtWebApp/first qtservice the files in your project (each people organize files in a different way, according to the actual situation is needed here configuration path), qtservice. Pri file is a little difference, it is important to pay special attention to here, otherwise you won’t compile



Create a new class named Startup that inherits fromQtService<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
Copy the code

Startup. CPP:

#include "startup.h"

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

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

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

Then move the entire main.ccp code into startup. CPP. The contents of the main() function now belong to Startup::start().

Replace the first line “QCoreApplication app(argc, argv)” with “QCoreApplication* app = Application ()”. And change all occurrences of “&app” to “app”. Finally, delete app.exec().

That’s the whole startup. CPP at the end:

#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(a) {
    QString binDir=QCoreApplication::applicationDirPath(a); QString appName=QCoreApplication::applicationName(a); 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(a);qDebug("using config file %s".qPrintable(configFileName));
        return configFileName;
    }
    else {
        qFatal("config file not found"); }}void Startup::start(a) {
    QCoreApplication* app = application(a); QString configFileName=searchConfigFile(a);// Configure logging
    QSettings* logSettings=new QSettings(configFileName,QSettings::IniFormat,app);
    logSettings->beginGroup("logging");
    logger=new FileLogger(logSettings,10000,app);
    logger->installMsgHandler(a);// 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(a) {
    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);
}

Copy the code

Now the new main.cpp is very small:

#include "startup.h"

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

To run the program in a console window (or Qt Creator) as before, you must enter the additional command line argument “-e” here:



Then run the program. It should look the same as before without the QtService library.

When you now want to go to the target Windows server, type the command “-i” to install Windows services. Start the service without entering a command. The -t command terminates the service.

You can also use the Service Manager screen in the System control panel to check the status and start and stop services.