Table of Contents
Startseite

Writing Web Server Applications with QtWebApp

This tutorial teaches you to develop HTTP webserver applications in C++ with Qt and QtWebApp. You have to know C++ and HTML already.

You can download the current Qt SDK version here, and the latest QtWebApp library is always here.

Introduction into the Qt SDK

The introduction teaches how to use the development environment and explains some important features of Qt. If you have already developed Qt console applications, you may skip this section.

Software Installation and Test

Please download and install the current Qt SDK, which contains the integrated development environment (IDE) Qt Creator, Qt build tools, Qt runtime libraries and a C++ compiler.

Create a directory for your programming projects, then download and extract the QtWebApp ZIP file there. Start the QT Creator IDE and open the project file Demo1/Demo1.pro. This opens the "Configure Project" wizard:

Screenshot

You can simply click on the highlighted "Configure Project" button. Then click the green "Run" button in the left border to build and run the demo program. While your computer is busy, you might watch the "Compile Output" pane by clicking the related button in the bottom border. If everything works fine, a black console window opens, telling which configuration file the demo application is using:

Screenshot

Open the URL http://localhost:8080 in your favorite web browser to check whether the demo web server is working:

Screenshot

All required software is working properly if you see that website on your screen. You may close the demo application now.

Creating a New Project

Use the menu File/New to create a new project of type "Qt Console Application":

Screenshot

If you cannot find this template, check the selection in the upper right corner. It has to be "Desktop Templates".

Enter a name for your project (e.g. "MyFirstWebApp") and select the directory that you created for your programming projects. The next dialog box displays kits for the target operating system. Just click on the "Next" button here:

Screenshot

If you have version control software installed, then you can now select whether this project shall be put under version control. Select whatever you want here. I will not cover version control in this tutorial.

Qt Creator creates a raw main.cpp file in which you do now insert the "Hello World" line:

#include <QCoreApplication>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    qDebug("Hello World");
    return a.exec();
}
Click the green "Run" button in the left border to build and run the hello-world program.

Screenshot

Now lets take a look at the automatically generated project file MyFirstWebApp.pro. If you are using another Qt version, your file may look slightly different:

QT += core
QT -= gui

TARGET = MyFirstWebApp

CONFIG += console
CONFIG -= app_bundle

TEMPLATE = app

SOURCES += main.cpp
All possible settings are described here.

The QT variable specifies which Qt modules your application uses. Our project file adds the "core" module and removes the "gui" module which would be active by default otherwise.

The core module is always required. Since we will only output text, we don't need the gui module.

The core module is provided by the runtime library libQt5Core.so (Linux) or Qt5Core.dll (Windows). The gui module is provided by libQt5Gui.so or Qt5Gui.dll, which we don't need. Each module has a related runtime library.

If you are unsure which features you have to select, then take a look at the documentation of each class that you are using. The documentation of the QCoreApplication class tells that we have to add "core" to the QT variable.

The TARGET variable specifies the name of the executable file. So on Linux it will be "MyFirstWebApp" and on Windows it will be "MyFirstWebApp.exe".

The CONFIG variable sets configuration options for the build tools. In our case, we add "console" to be able to output text in the console window. The Mac OS specific option "app_bundle" is deactivated here, so Qt does not create an application bundle.

The TEMPLATE variable can be either "app" or "lib". Since we are developing an application, it's correctly set to "app". When creating a shared library, we would use the value "lib" instead.

The last variable SOURCES specifies the *.cpp files of your project. You will also add *.h files to the HEADERS variable later. But for the moment we don't have any header file in our project.

Multiple filenames are separated by space or by backslash plus line-break. For example:

SOURCES = file1.cpp file2.cpp file3.cpp

HEADERS = file1.h \
          file2.h \
          file3.h

I'm of the opinion that source files belong into a sub-directory with the name "src". So use the file manager of your operating system now to create a "src" folder, then move the file main.cpp into it. After that, you have to update the project file with the new location of the main.cpp file:

SOURCES += src/main.cpp
Use always a forward slash for directory names! Backward slashes have a different meaning to Qt as explained above. Press Ctrl-S to save the file, then wait one or two seconds until Qt Creator re-reads it and refreshes the screen.

Debugging

Now I like to introduce you in debugging. First enter some faulty code:
#include <QString>;

int main(int argc, char *argv[])
{
    qDebug("start");

    QString* str;
    str->append("test");
    qDebug(str->toLatin1());

    qDebug("end");
}
You might notice that the include file name is "QString", not "qstring.h". This is the special style of the Qt SDK. Actually, the file QString is just an alias for qstring.h, but that might change in future. Just use "QString" as the API Documentation tells, then you will encounter less problems with future Qt releases.

When you run this application, you will see the "start" message in the console window but the "end" message is missing. So something does not work obviously. And the problem cause is not the unusual include line - trust me.

Open the "Application Output" window by clicking on the related button in the bottom border. There you will see the following messages:

Screenshot

The message "MyFirstWebApp crashed" is a good hint to the problem cause. For some reason, the operating system terminated the program abnormally. But it does not tell us where the problem cause is.

Click the second "Run" button in the left border, the one with the bug on it. This runs the application with a debugger attached. The debugger will show where it crashed:

Screenshot

The debugger pops up the more detailed error message "Segmentation fault" which means that the program tried to access an area of memory that does not belong to the program. If available, then the debugger also opens the source code file where the error happened, which is qstring.h.

However, lets assume that errors are mostly in our own source code - not in the Qt libraries. A look into the bottom area shows that qstring.h was called by main.cpp line 8. Click on that to open the main file.

Screenshot

Here we see that variable "str" is not accessible. That means the pointer "str" points to an invalid location in memory. Some code must be missing that assigns a proper value to the pointer. The pointer has actually an undefined random value.

If you want to inspect the pointer anyway, then click onto "str" in the right panel with the right mouse button, then move the mouse cursor over the command "Open Memory Editor":

Screenshot

In this case, the random value is 0xb749f379.

Now lets fix the broken line:

    QString* str=new QString();
The program runs properly now. The console window shows the "start" message, the content of the variable str and finally the "end" message.

Now lets replace the whole program source code by another faulty one to try another feature of the debugger:

#include <QtGlobal>

int main(int argc, char *argv[])
{
    qDebug("start");

    int counter=0;
    while (counter<100) {
        counter+1;
    }    
    qDebug("The result is %i", counter);

    qDebug("end");
}
Since we don't use the QString class anymore, I removed the related include statement. But for the qDebug() function, we now have to include the file "QtGlobal". The related API Documentation is difficult to find by the search engine. Book-mark this website and check it whenever you search for a Qt function or constant that is not a class member. qDebug() is just one of them.

Run this program by clicking the first green "Run" button in the left border. The console window will open as usual and you can see the "start" message there. But then nothing else happens. The program does not reach it's end and you might notice that one CPU core remains 100% busy all the time.

Close the console window and restart it with the debugger attached. The program will hang again as before. But you can now click on the Interrupt button :

Screenshot

The debugger shows that you interrupted the execution at line 8 of main.cpp. On the right side you can see that the counter variable still has the value 0. It was not incremented as planned. And because the counter is always 0, the "while" loop repeats forever.

So the command "counter+1" in the loop does not work properly. It should be "counter=counter+1". But instead of fixing it, I like to show you something else first.

Stop the Program. Click with the right mouse button on the line number 5 to set a breakpoint on that line:

Screenshot

Then run the program with the debugger attached. It will pause at line 5. The console window is empty because the "start" message is not yet written out. Press F10 repeatedly to execute the program line by line. You will notice that line 9 gets never executed. This is a little strange, but can be explained.

The line 9 calculates the value of counter+1 but does not store the result anywhere. The C compiler detects such useless code and removes it automatically from the compilation. And the debugger simply ignores all lines that the compiler removed. You can stop the faulty program now.

Lets add a second line below the wrong one:

#include <QtGlobal>

int main(int argc, char *argv[])
{
    qDebug("start");

    int counter=0;
    while (counter<100) {
        counter+1;
        counter=counter+1;
    }    
    qDebug("The result is %i", counter);

    qDebug("end");
}
Start the program again with the debugger and press F10 repeatedly to see how it works now. You can see that the counter does now increment in each loop. And you can also see that the faulty line (Nr. 9) is still skipped.

You have surely noticed the yellow warning symbol left to the faulty line. Click on it to see the related warning message "statement has no effect ... counter+1" - what a surprise :-)

QCoreApplication

Revert the source code back to our first "Hello World" version.
#include <QCoreApplication>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    qDebug("Hello World");
    return a.exec();
}
Almost all Qt console applications use QCoreApplication, while graphical applications use QApplication.

Qt applictions are event driven. There is a queue in QCoreApplication which receives events and executes them one after the other. For example, if a web browser connects to the web server, the event "newConnection" will be put into the queue. Some code will be bound to that event which processes the incoming connection request.

The exec() method of QCoreApplication processes that queue in an endless loop until any event handler calls the QCoreApplication::quit() or QCoreApplication::exit().

So the start-up code of the application belongs to the place of our "Hello World" message and then everything else is driven by events. As long we have only that "Hello World" message, the application will hang, checking the event loop forever. The only action that works out of the box is closing the console window which terminates the application abnormally with the warning message "MyFirstWebApp crashed".

To get rid of that warning, the application must quit itself. Since it's no GUI application, we cannot handle any push button or menu command of the window frame. Instead we will use a timer to create an event that lets the application quit itself.

#include <QCoreApplication>
#include <QTimer>

int main(int argc, char *argv[])
{
    QCoreApplication app(argc, argv);
    qDebug("start");

    QTimer *timer = new QTimer();
    QObject::connect(timer, SIGNAL(timeout()), &app, SLOT(quit()));
    timer->start(2000);

    qDebug("entering the event loop");
    return app.exec();
}
The line with "QObject::connect" connects the signal "timeout()" of the timer with the method "quit()" of the core application. Then we start the timer with an interval of 2 seconds.

When you now run the program, you will see the "start" message as well as "entering the event loop", and then - after two seconds - the application quits. The Application Output Window looks better this time:

Screenshot

Exit code 0 means that the application exited without an error.

However, just closing the console window is also acceptable to almost all application. The operating system does automatically close all open files and connections and it releases the memory that was occupied by the application. So don't worry about the "MyFirstWebApp crashed" message.

Signals and Slots

I did not explain what exactly a signal is. Looking at the source code above, it seems that both signals and slots are object methods. Well, this is halfway true, and you should really learn the full truth.

From the objects viewpoint, a signal is a declared method within the "signals:" section. But you don't write an implementation for it. A slot is a method which you have to implement. You define what shall happen when a slot gets called. They are declared this way in the header files:

class QTimer : public QObject {
    Q_OBJECT
signals:
    void timeout();
}

class QCoreApplication : public QObject {
    Q_OBJECT
public slots:
    void quit();
}
Signals are always public, so the keyword "public" is optional. Slot methods may be private, protected or public. Signals and slot methods can also be called directly like regular methods.

The Q_OBJECT keyword activates some magic on top of the C++ programing language. This is the point, where the Meta Object Compiler (MOC) shows up. It generates code for reflection which is not a C++ feature, but required by the signal/slot mechanism.

Qt inserts the Meta Object Compiler into the C++ toolchain. It generates additional source code files which contain the code for reflection. For example, the file qtimer.cpp gets an additional generated file with the name moc_qtimer.cpp.

All objects that inherit directly or indirectly from QObject must contain the Q_OBJECT keyword.

You have to know that signals are connected at runtime. And because of that, the program can also modify and remove the connections at runtime. The connections are not hardwired like regular method calls. That's a fundamental difference to the C++ programming paradigm. In a GUI application, you could connect the keydown() event to a text box initially and later change the connection to some other input element.

By the way, signals and slots can have arguments to pass data from the sender to the recipient.

Even if you don't need that level of flexibility, you have to understand the signal/slot stuff because the whole Qt framework is using it. Please read the article Signals&Slots in the Qt documentation and click here to learn what's happening under hood.

So to summarize the timer event example above, we can say that the timer calls its own timeout() signal after two seconds delay, which is connected to the quit() method of QCoreApplication by some auto-magic of the MOC compiler. We use the connect() method, to set up that connection at runtime.

Are you baffled now? No matter. You can write web server applications without fiddling with signals and slots. The QtWebApp library handles the mandatory signals for you.

Memory Management

A regular C++ application creates object instances on the heap with new() and cleans up with delete(). The latter one is often forgotten, so C++ programs tend to have memory leaks. To prevent such errors, Qt introduces another alternative method.

Please download this example project and open it in your Qt Creator. It contains the following main file plus the sources of the Father and Child classes. Take a look at them.

#include <QCoreApplication>
#include "father.h"
#include "child.h"

int main(int argc, char *argv[])
{
    QCoreApplication app(argc, argv);
    qDebug("start");

    Father* father=new Father();
    Child*  child1=new Child(1);
    Child*  child2=new Child(2);
    Child*  child3=new Child(3);

    qDebug("now cleaning up");
    delete(father);
    delete(child1);
    delete(child2);
    delete(child3);

    return app.exec();
}
Here you see how a C++ program would normally clean up objects from heap. If you run it, you get the following output in the console window:

Screenshot

And now we apply the Qt style:

#include <QCoreApplication>
#include "father.h"
#include "child.h"

int main(int argc, char *argv[])
{
    QCoreApplication app(argc, argv);
    qDebug("start");

    Father* father=new Father();
    Child*  child1=new Child(1,father);
    Child*  child2=new Child(2,father);
    Child*  child3=new Child(3,father);

    qDebug("now cleaning up");
    delete(father);

    return app.exec();
}
This program produces exactly the same output as before, but contains only one single delete() command. The destructor of the Father object does automatically delete all children. The connection between them had been made by the "parent" argument of the constructor. This works also for whole trees of objects, with unlimited nesting depth. All objects that are (directly or indirectly) derived from QObject support this feature. So the whole Qt framework does.

Logging

You used already qDebug() several times to output text in the console window. You have surely noticed that qDebug() is similar to printf() from the C programming language.

The QtGlobal include file defines some more related functions:

The output of these methods can be redirected to something else by installing a message handler, as described here. QtWebApp contains the Logger class which does that. The Logger class writes all messages into a text file, which is quite useful for background services that don't have a console window.

Another useful related function is qPrintable() which converts strings to temporary char arrays:

QString name("Maria");
qDebug("The name is %s", qPrintable(name));
You cannot output strings directly with qDebug() because it supports only simple C types - no objects.

Interactive Console

Although it's not needed for web servers, I like to show you how to write interactive console applications:
#include <QTextStream>
#include <QString>

int main(int argc, char *argv[])
{
    QTextStream out(stdout);
    QTextStream in(stdin);

    out << "Enter your name: " << flush;
    QString value = in.readLine();
    out << "Hello " << value << endl;

    out << "Enter two numbers: " << flush;
    int num1;
    int num2;
    in >> num1;
    in >> num2;
    out << "You entered " << num1 << " and " << num2 << endl;
}

Screenshot

Use QTextStream to read from and write to the console. It's the Qt counterpart of the standard C++ objects stdio::cout and std::cin, which you could also use. However mixing the standard C++ library with Qt is considered as bad practise.

Notice that qDebug() is designed to output log messages, while QTextStream is designed to operate on the console.

We can omit the QCoreApplication because this program does not depend on events and the two used Qt classes also don't need it. However, many other Qt classes need it. In case of doubt, you should check the API documentation of the used classes.

Running outside Qt Creator

Linux

You can run Qt console applications from the command-line in a terminal window. If you get an error message that a shared library is not found, you have to set the LD_LIBRARY_PATH environment variable. For example by adding the following lines to the file .profile in your home directory:
LD_LIBRARY_PATH=/opt/qt/5.4/gcc/lib:$LD_LIBRARAY_PATH
export LD_LIBRARY_PATH
Then log out and log in again to activate the changes.

Windows

In case of Windows, you may distribute the required DLL files (e.g. Qt5Core.dll) together with your *.exe file in the same directory. If you don't do that, then Qt must be installed on the target computer by the regular installation tool (which is called Qt Maintenance Tool).

How to use QtWebApp

If you have ever developed web server applications with the Java Servlet API, you will feel like at home. My library provides almost the same functionality.

I will show you how to write a minimal web server application with QtWebApp. Then we will discover the main features step by step.

Hello World

Extract the QtWebApp-src.zip file in your programming folder and create a new Qt console project with the name "MyFirstWebApp", if not already done. You should then have the same folder structure than I have:

Screenshot

Add the following lines to the project file of the MyFirstWebApp project:

The first line activates the network module of Qt, and the second line includes the source code of the HTTP Server module of QtWebApp. So when you compile the program, the HTTP Server becomes part of the executable file.

Screenshot

As an alternative, you may work with a shared library. To generate it, you open the project QtWebApp/QtWebApp/QtWebApp.pro and build it. Then take a look at QtWebapp/Demo2/Demo2.pro to see how to link against the shared library. However, I recommend to include the source code as shown above, because that's less error-prone.

The next step is to create the configuration file MyFirstWebApp/etc/webapp1.ini. You need to do that with the file manager of your operating system, because Qt Creator cannot create new folders. The file content is:

[listener]
;host=192.168.0.100
port=8080
minThreads=4
maxThreads=100
cleanupInterval=60000
readTimeout=60000
maxRequestSize=16000
maxMultiPartSize=10000000

The host and port parameters specify on which IP address and port the web server listens. If you comment out the host (as above) then the server listens on all network interfaces. Public webservers use port 80 while internal web servers usually listen on port 8080. You may use any free port that you like.

Unix users should keep in mind that the port numbers below 1024 are reserved to the "root" user. Windows users might need to configure the Windows firewall to enable access from other computers.

QtWebApp can process multiple HTTP requests concurrently, so it's multi-threaded. Because starting a new thread takes a significant amount of time, QtWebApp re-uses threads for subsequent HTTP requests.

The maxThreads value specifies the maximum number of concurrent worker threads. Before going into the production environment, you should use a load generator tool to find out how much load your server can handle without running out of memory or becoming sluggish.

The web server always starts with an empty thread pool. The threads are created on demand when HTTP requests come in. Idle threads are closed slowly by a timer. Every cleanupInterval in milliseconds, the server closes one idle thread. But the minThreads amount is always kept running.

A low minThreads value reduces the memory load at times of low activity. A high minThreads value plus enough memory improves the performance.

With the given values, your server can handle up to 100 concurrent HTTP connections. It keeps 4 idle worker thread running to ensure good response times after a while of inactivity.

The readTimeout setting protects the server against simple denial-of-service attacks by opening lots of connections and not using them. Silent connections are closed after that number of milliseconds. Under normal circumstances, the web browsers close connections.

The maxRequestSize protects the server from memory overload by very large HTTP requests. This value applies to the regular requests. The other maxMultiPartSize value applies to multi-part requests which occur when the web browser uploads a file to the server. If you want to accept files of 10 Megabytes, you have to set this value a little larger because of the HTTP protocol overhead.

File uploads are stored in temporary files. The location of temporary files is defined by the operating system.

Well, lets continue to create our first web application.

To make this configuration file visible in Qt Creator, add one line to the project file:

Now we add some code to load that file:
#include <QCoreApplication>
#include <QSettings>

int main(int argc, char *argv[])
{
    QCoreApplication app(argc, argv);

    QSettings* listenerSettings=
         new QSettings("../MyFirstWebApp/etc/webapp1.ini",QSettings::IniFormat,&app);
    qDebug("config file loaded");

    return app.exec();
}
You may run the program to check whether the config file can be loaded. You might need to modify the path above so it matches your personal computer setup.

If you run the program on the target computer, the configuration files may be located somewhere else. So it's a good idea to search it automatically in several folders:

#include <QCoreApplication>
#include <QSettings>
#include <QFile>
#include <QDir>
#include <QString>

/**
 * 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");
    }
}



int main(int argc, char *argv[])
{
    QCoreApplication app(argc, argv);

    // Load the configuration file
    QString configFileName=searchConfigFile();
    QSettings* listenerSettings=new QSettings(configFileName, QSettings::IniFormat, &app);
    qDebug("config file loaded");

    return app.exec();
}
The procedure searchConfigFile() searches the file in several folders. Three of them are specially for running the program inside Qt Creator, where the executable is in the shadow build folder.

The method QDir::canonicalPath() converts relative path names to the absolute form, which looks nicer in the following debug message.

If the file was not found, then the application outputs an error message with qFatal() which also aborts the program.

Once the configuration file is loaded, you can create a HTTP listener object, which is the core of the web server:

#include <QCoreApplication>
#include <QSettings>
#include <QFile>
#include <QDir>
#include <QString>
#include "httplistener.h"
#include "httprequesthandler.h"

using namespace stefanfrings;

int main(int argc, char *argv[])
{
    QCoreApplication app(argc, argv);
    
    // Load the configuration file
    QString configFileName=searchConfigFile();    
    QSettings* listenerSettings=new QSettings(configFileName, QSettings::IniFormat, &app);
    listenerSettings->beginGroup("listener");
    
    // Start the HTTP server
    new HttpListener(listenerSettings, new HttpRequestHandler(&app), &app);

    return app.exec();
}

The method QSettings::beginGroup() selects the group "[listener]" from the configuration file. We will add more groups later.

The HttpRequestHandler receives all incoming HTTP requests and it generates the responses. By default, the request handler simply returns an error page. We will change that soon.

It is important to create the HttpListener on the heap with "new", otherwise it would terminate immediately after the program start.

Run the program and open the URL http://localhost:8080 in your favorite web browser. You will receive the error page "501 not implemented" and debug messages in the console window.

Screenshot

That are a lot of messages which slow down the program, but they are helpful for debugging. In the left border of Qt Creator, you can change the build mode from "Debug" to "Release" by clicking the purple button. The release version is less verbose:

Screenshot

So for production, you should prefer the release build.

In order to output a "Hello World" message, we have to write our own request handler. Click on the src folder with the right mouse button, select "Add New..." and then select "C++ class".

Screenshot

helloworldcontroller.h:

#ifndef HELLOWORLDCONTROLLER_H
#define HELLOWORLDCONTROLLER_H

#include "httprequesthandler.h"

using namespace stefanfrings;

class HelloWorldController : public HttpRequestHandler {
    Q_OBJECT
public:
    HelloWorldController(QObject* parent=0);
    void service(HttpRequest& request, HttpResponse& response);
};

#endif // HELLOWORLDCONTROLLER_H
helloworldcontroller.cpp:
#include "helloworldcontroller.h"

HelloWorldController::HelloWorldController(QObject* parent)
    : HttpRequestHandler(parent) {
    // empty
}

void HelloWorldController::service(HttpRequest &request, HttpResponse &response) {
    response.write("Hello World",true);
}
The optional argument "true" indicates that this was the last call to write() for the current HTTP request.

Two changes in main.cpp:

#include "helloworldcontroller.h"

    new HttpListener(listenerSettings,new HelloWorldController(&app),&app);
Run the program and open the URL http://localhost:8080 in your web browser.

Screenshot

You've done it! Your webserver greets the world.

The final "Hello World" project: MyFirstWebApp_2.zip

Dynamic HTML

The "Hello World" application from above did output simple plain text. But the language of the web is HTML. So lets see how to generate HTML. We will output the current time and display some data from a list object.

Similar to the first HelloWorldController, create another new class with name ListDataController.

listdatacontroller.h:

#ifndef LISTDATACONTROLLER_H
#define LISTDATACONTROLLER_H

#include <QList>
#include <QString>
#include "httprequesthandler.h"

using namespace stefanfrings;

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

#endif // LISTDATACONTROLLER_H
listdatacontroller.cpp:
#include <QTime>
#include "listdatacontroller.h"

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

void ListDataController::service(HttpRequest &request, HttpResponse &response) {
    response.setHeader("Content-Type", "text/html; charset=ISO-8859-1");
    response.write("<html><body>");

    response.write("The time is ");
    QString now=QTime::currentTime().toString("HH:mm:ss");
    response.write(now.toLatin1());

    response.write("<p>List of names:");
    response.write("<table border='1' cellspacing='0'>");
    for(int i=0; i<list.size(); i++) {
        QString number=QString::number(i);
        QString name=list.at(i);
        response.write("<tr><td>");
        response.write(number.toLatin1());
        response.write("</td><td>");
        response.write(name.toLatin1());
        response.write("</td></tr>");
    }
    response.write("</table>");

    response.write("</body></header>",true);
}
The constructor fills the list with some names. The service method outputs a HTML document with the current time and a table that shows the content of the list object.

Note that we set a HTTP response header before writing the document, which tells the browser what file format (see Internet Media Types) and character encoding we are using. ISO-8859-1 is the same as Latin 1, which is the default 8bit encoding for Europe.

Replace the controller in main.cpp by the new one:

#include "listdatacontroller.h"

    new HttpListener(listenerSettings,new ListDataController(&app),&app);
Run and test the application. The output should look so:

Screenshot

Request Mapper

We have now two different controller classes in our application but can only use one at a time. Now we create a "RequestMapper" class that will switch between both controllers. As before, the new class inherits again from HttpRequestHandler.

requestmapper.h:

#ifndef REQUESTMAPPER_H
#define REQUESTMAPPER_H

#include "httprequesthandler.h"

using namespace stefanfrings;

class RequestMapper : public HttpRequestHandler {
    Q_OBJECT
public:
    RequestMapper(QObject* parent=0);
    void service(HttpRequest& request, HttpResponse& response);
};

#endif // REQUESTMAPPER_H
requestmapper.cpp:
#include "requestmapper.h"
#include "helloworldcontroller.h"
#include "listdatacontroller.h"

RequestMapper::RequestMapper(QObject* parent)
    : HttpRequestHandler(parent) {
    // empty
}

void RequestMapper::service(HttpRequest& request, HttpResponse& response) {
    QByteArray path=request.getPath();
    qDebug("RequestMapper: path=%s",path.data());

    if (path=="/" || path=="/hello") {
        HelloWorldController().service(request, response);
    }
    else if (path=="/list") {
        ListDataController().service(request, response);
    }
    else {
        response.setStatus(404,"Not found");
        response.write("The URL is wrong, no such document.",true);
    }

    qDebug("RequestMapper: finished request");
}
Replace the old controller in main.cpp by the new request mapper:
#include "requestmapper.h"

    new HttpListener(listenerSettings,new RequestMapper(&app),&app);
The request mapper calls one of our two controllers depending on the requested path.

So when you now open http://localhost:8080/ or http://localhost:8080/hello, you get the "Hello World" page. And when you open http://localhost:8080/list, you get the list of names.

If you try to open any wrong URL, for example http://localhost:8080/lalala, then you receive the error message "The URL is wrong..." plus the status code 404 which is the technical value for "Not found". Some programs use that to handle errors. If you do not set a status code, then the default 200 is used, which means "success". See List of HTTP status codes in Wikipedia.

If multipe concurrent HTTP requests come in at the same time, then the service() method is executed multiple times in parallel. So this method is multi-threaded. That has to be considered when you access variables that are declared outside the service() method.

People say that the request mapper is a "singleton" or in "application scope" because there is exactly one instance of it.

The two controller classes (HelloWorldController and ListDataController) are in "request scope", which means that each request is handled by its own fresh instance of this class. That costs some performance but simplifies programming a little.

A small modification changes the scope of the two controller classes to "application scope":

new requestmapper.h:

#ifndef REQUESTMAPPER_H
#define REQUESTMAPPER_H

#include "httprequesthandler.h"
#include "helloworldcontroller.h"
#include "listdatacontroller.h"

using namespace stefanfrings;

class RequestMapper : public HttpRequestHandler {
    Q_OBJECT
public:
    RequestMapper(QObject* parent=0);
    void service(HttpRequest& request, HttpResponse& response);
private:
    HelloWorldController helloWorldController;
    ListDataController listDataController;
};

#endif // REQUESTMAPPER_H
new requestmapper.cpp:
#include "requestmapper.h"

RequestMapper::RequestMapper(QObject* parent)
    : HttpRequestHandler(parent) {
    // empty
}

void RequestMapper::service(HttpRequest& request, HttpResponse& response) {
    QByteArray path=request.getPath();
    qDebug("RequestMapper: path=%s",path.data());

    if (path=="/" || path=="/hello") {
        helloWorldController.service(request, response);
    }
    else if (path=="/list") {
        listDataController.service(request, response);
    }
    else {
        response.setStatus(404,"Not found");
        response.write("The URL is wrong, no such document.");
    }

    qDebug("RequestMapper: finished request");
}
Now every request re-uses the only one instance of HelloWorldController or ListDataController. They are only created once during startup because the HttpRequestMapper does also exist only once.

When you have learned how to use sessions, you can also create instances of controllers per session and store them in the session store. Then you have a "session scope". Sessions are explained farther down.

The request mapper project: MyFirstWebApp_3.zip

Processing User Input

Web applications usually process user input. We will develop a login form to see how that goes.

Create a new class with name LoginController. Again, it is derived from HttpRequestHandler.

logincontroller.h:

#ifndef LOGINCONTROLLER_H
#define LOGINCONTROLLER_H

#include "httprequesthandler.h"

using namespace stefanfrings;

class LoginController : public HttpRequestHandler {
    Q_OBJECT
public:
    LoginController(QObject* parent=0);
    void service(HttpRequest& request, HttpResponse& response);
};

#endif // LOGINCONTROLLER_H
logincontroller.cpp:
#include "logincontroller.h"

LoginController::LoginController(QObject* parent)
    :HttpRequestHandler(parent) {
    // empty
}

void LoginController::service(HttpRequest &request, HttpResponse &response) {
    QByteArray username=request.getParameter("username");
    QByteArray password=request.getParameter("password");

    qDebug("username=%s",username.constData());
    qDebug("password=%s",password.constData());

    response.setHeader("Content-Type", "text/html; charset=ISO-8859-1");
    response.write("<html><body>");

    if (username=="test" and password=="hello") {
        response.write("Yes, correct");
    }
    else {
        response.write("<form method='POST' action='/login'>");
        if (!username.isEmpty()) {
            response.write("No, that was wrong!<br><br>");
        }
        response.write("Please log in:<br>");
        response.write("Name:  <input type='text' name='username'><br>");
        response.write("Password: <input type='password' name='password'><br>");
        response.write("<input type='submit'>");
        response.write("</form");
    }

    response.write("</body></html>",true);
}
Add this new controller to the request mapper as we did above.

New lines in requestmapper.h:

#ifndef REQUESTMAPPER_H
#define REQUESTMAPPER_H

#include "httprequesthandler.h"
#include "helloworldcontroller.h"
#include "listdatacontroller.h"
#include "logincontroller.h"

class RequestMapper : public HttpRequestHandler {
    Q_OBJECT
public:
    RequestMapper(QObject* parent=0);
    void service(HttpRequest& request, HttpResponse& response);
private:
    HelloWorldController helloWorldController;
    ListDataController listDataController;
    LoginController loginController;
};

#endif // REQUESTMAPPER_H
And three new lines in requestmapper.cpp:
#include "requestmapper.h"

RequestMapper::RequestMapper(QObject* parent)
    : HttpRequestHandler(parent) {
    // empty
}

void RequestMapper::service(HttpRequest& request, HttpResponse& response) {
    QByteArray path=request.getPath();
    qDebug("RequestMapper: path=%s",path.data());

    if (path=="/" || path=="/hello") {
        helloWorldController.service(request, response);
    }
    else if (path=="/list") {
        listDataController.service(request, response);
    }
    else if (path=="/login") {
        loginController.service(request, response);
    }
    else {
        response.setStatus(404,"Not found");
        response.write("The URL is wrong, no such document.");
    }

    qDebug("RequestMapper: finished request");
}
Next time I will not display the whole source code anymore.

Run the program and open the URL http://localhost:8080/login. You will see this form:

Screenshot

Try to login with a wrong name and password. Then the browser shows the error message "That was wrong" and prompts to try again. If you enter the correct credentials (username "test" and password "hello"), then you get the success message.

The source code does not contain much stuff that needs an explanation. The HTML form defines two input fields with the names "username" and "password". And the controller uses request.getParameter() to get the values.

Request.getParameter() returns an empty QByteArray when either the parameter is empty or when there is no such parameter in the incoming HTTP request. This latter case happens when you open the URL http://localhost:8080/login initially. The form fields are only sent from the web browser to the web server when the user clicks on the submit button.

If you feel the need to distinguish between empty and missing fields, then you may use request.getParameterMap() and then check if the required parameter is in the returned map.

As an alternativ to forms, parameters can also be transported as part of the URL. For example, you can also log in by opening the URL http://localhost:8080/login?username=test&password=hello.

Some special characters must be encoded into escape sequences when used in an URL. For example, if the username would be "Stefan Frings", then you have to write http://localhost:8080/login?username=Stefan%20Frings&password=hello. The HttpRequest class decodes that automatically back to the original form "Stefan Frings".

If you ever need to encode strings into URL format, you may use the QUrl class.

Sessions

The next logical step is to work with session data. That means, we keep data about the current user somewhere and use that data in subsequent requests. The first data that we will store are the name of the user and the time when he logged in.

QtWebApp uses cookies under the hood to identify users.

We have to add a new section to the configuration file webapp1.ini which controls the session store class:

[sessions]
expirationTime=3600000
cookieName=sessionid
;cookieDomain=mydomain.com
cookiePath=/
cookieComment=Identifies the user
The expiration time defines the number of milliseconds after that an unused session is deleted from memory. When the user comes back after that time, his session is lost, so he must login again.

The cookie name can be any - however the name "sessionid" is commonly used. Some load balancer depend on that name, so you should not change it unless having a good reason.

Each cookie is always bound to a domain. A cookie that was generated by google.com is only sent to servers on that domain. The cookieDomain parameter is automatically set by the web browser if you leave that setting empty or comment it out. You can specify another domain name, but I have no idea why men should do that. So leave it empty unless you know what you are doing.

The cookie path can be used to restrict the cookie to a part of your domain. If you change the cookiePath to /my/very/cool/online/shop, then the browser would send the cookie to your server only for pages that start with this path. The default value is "/" which means the cookie is valid for all websites on your domain.

The cookieComment is a text that some web browsers display in the cookie management screen.

Let's load the new configuration settings and initialize a storage container for the session data.

changes in main.cpp:

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");
    RequestMapper::sessionStore=new HttpSessionStore(sessionSettings,&app);

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

    return app.exec();
}
Notice that main.cpp now loads the configuration file twice. The sessionSettings object picks out the "session" section, while the listenerSettings picks out the "listener" section. Each section needs its own instance of QSettings. That is really important because the classes need permanent access to their settings during the whole runtime.

To make the session store reachable to the whole program, we have a public static pointer in the RequestMapper class with name "sessionStore". As an alternative, we could place the static pointer in any other class or even in the global space.

So we need to add two lines to requestmapper.h:

#include "httpsessionstore.h"
...

class RequestMapper : public HttpRequestHandler {
    Q_OBJECT
public:
    ...
    static HttpSessionStore* sessionStore;
private:
    ...
};
and one line to requestmapper.cpp:
#include "requestmapper.h"

HttpSessionStore* RequestMapper::sessionStore=0;

RequestMapper::RequestMapper(QObject* parent)
    : HttpRequestHandler(parent) {
    // empty
}
...
Now that we have created a storage for session data, we can start using it.

Add to logincontroller.cpp:

#include <QTime>
#include "logincontroller.h"
#include "requestmapper.h"

LoginController::LoginController(QObject* parent)
    :HttpRequestHandler(parent) {
    // empty
}

void LoginController::service(HttpRequest &request, HttpResponse &response) {
    HttpSession session=RequestMapper::sessionStore->getSession(request,response,true);
    QByteArray username=request.getParameter("username");
    QByteArray password=request.getParameter("password");

    qDebug("username=%s",username.constData());
    qDebug("password=%s",password.constData());

    response.setHeader("Content-Type", "text/html; charset=ISO-8859-1");
    response.write("<html><body>");

    if (session.contains("username")) {
        QByteArray username=session.get("username").toByteArray();
        QTime logintime=session.get("logintime").toTime();
        response.write("You are already logged in.<br>");
        response.write("Your name is: "+username+"<br>");
        response.write("You logged in at: "+logintime.toString("HH:mm:ss").toLatin1()+"<br>");
    }
    else {
        if (username=="test" and password=="hello") {
            response.write("Yes, correct");
            session.set("username",username);
            session.set("logintime",QTime::currentTime());
        }
        else {
            response.write("<form method='POST' action='/login'>");
            if (!username.isEmpty()) {
                response.write("No, that was wrong!<br><br>");
            }
            response.write("Please log in:<br>");
            response.write("Name:  <input type='text' name='username'><br>");
            response.write("Password: <input type='password' name='password'><br>");
            response.write("<input type='submit'>");
            response.write("</form");
        }
    }

    response.write("</body></html>",true);
}
Here it is important to call sessionStore->getSession() before the first call to response.write(), because it creates or refreshes the session cookie which is technically a HTTP header. And all HTTP headers must be sent before the HTML document.

Run and test the program by opening http://localhost:8080/login.

Screenshot

When you now take a look into the console window of the web server, you will see that a cookie and a session had been created with a unique id, which is a long random hexadecimal number.

I took the following screenshot from my Chromium browser, where you can see my cookie:

Screenshot

The user sessionis initially empty. It just exists and has a unique id number. Nothing else. Enter username "test" and password "hello". Then you get the expected confirmation.

Screenshot

While confirming the successful login, the server puts the username and login time into the users session. You can put any object into the session that is supported by QVariant. When you put an object into the session, you give it a symbolic name for later access.

Now open again the URL http://localhost:8080/login. Then you see the result of all that work:

Screenshot

So after successful validation of username and password, the server uses the session to remember data of that user.

The session store is kept in memory. When you restart the web server, then all data in the session store are lost. So use it only for some few temporary data. Persistent data belong into a database but that's not covered by this tutorial.

The session cookie project: MyFirstWebApp_4.zip

Cookies

As an alternative to the session store, you can also store small amount of data in cookies. Cookies can only store 8bit text, and only 4 Kilobytes are guaranteed to work. Also the number of cookies per domain is limited, so use them sparing.

To try that out, lets add a new controller class with name CookieTestController and bind it to the path "/cookie". I hope you know already how to do that. In case of problems take a look into the ZIP file at the end of this chapter.

cookietestcontroller.cpp:

#include "cookietestcontroller.h"

CookieTestController::CookieTestController(QObject* parent)
    : HttpRequestHandler(parent) {
    // empty
}

void CookieTestController::service(HttpRequest &request, HttpResponse &response) {

    QByteArray cookie=request.getCookie("testcookie");
    if (!cookie.isEmpty()) {
        response.write("Found cookie with value: "+cookie,true);
    }
    else {
        HttpCookie cookie("testcookie","123456789",60);
        response.setCookie(cookie);
        response.write("A new cookie has been created.",true);
    }

}
The name of the cookie is "testcookie" and it has the value "123456789". The 60 is the validity time in seconds. This cookie expires one minute after it has been created. We use that short time to see what happens when the cookie expires. You would normally use much larger times, possibly several days.

Request.getCookie() returns only the value of the cookie, not a whole HttpCookie object. This is for performance reason.

Run the program and open http://localhost:8080/cookie.

Screenshot

Then load the same page again, and you will see that the server received the cookie properly:

Screenshot

Cookies are stored in the web browser and sent to the web server with each HTTP request. Other than session data, cookies are are not lost if you restart the server, because cookies are stored on the client side.

And this is again a screenshot from my Chromium browser:

Screenshot

Here you can also see that the session cookie is still there. We have now two cookies for the domain localhost.

If you wait two minutes and then load again http://localhost:8080/cookie you will see that the test cookie has expired. The server creates a new one.

If you want to prevent that the cookie expires while the user is active, then you have to re-create the cookie on each request. Then the browser calculates a new expiration date on each request.

The cookie test project: MyFirstWebApp_5.zip

HTTP Redirects

Sometimes you want to redirect the browser to another page. This is often used for websites that require the user to log in. If the user is not logged in, he gets redirected to the login page. Of course, the login page itself must be accessible to anonymous users.

This is very simple. Changes in requestmapper.cpp:

void RequestMapper::service(HttpRequest& request, HttpResponse& response) {
    QByteArray path=request.getPath();
    qDebug("RequestMapper: path=%s",path.data());

    QByteArray sessionId=sessionStore->getSessionId(request,response);
    if (sessionId.isEmpty() && path!="/login") {
        qDebug("RequestMapper: redirect to login page");
        response.redirect("/login");
        return;
    }

    else if (path=="/login") {
        ...
    }

    else if (path=="/whatever") {
        ...
    }

    qDebug("RequestMapper: finished request");
}

Internationalisation

Did you wonder why the HTTP server always uses QByteArray instead of QString? This has a simple cause: performance. The whole HTTP protocol is based on an 8bit encoding, so I decided not to waste CPU time by converting that forth and back unnecessarily.

But you can use Unicode - of course. I show you one example:

void UnicodeController::service(HttpRequest& request, HttpResponse& response) {
    QString chinese=QString::fromUtf8("美丽的花朵需要重症监护");
    response.setHeader("Content-Type", "text/html; charset=UTF-8");
    response.write(chinese.toUtf8(),true);
}
That is the chinese translation for "beautiful flowers need intensive care", presented by Google Translator (I don't speak chinese).

The conversion from QString to UTF-8 is not less performant than the conversion to Latin1. So feel free to use Unicode if you need. But do never forget to use QString::fromUtf8. If you simply write chinese="美丽的花朵需要重症监护", you would only get garbage. See QString documentation.

You may also use the tr() function of Qt, to provide translations by separate resource files. This is described here.

Static Files

QtWebapp would be incomplete if it could not deliver static files that are stored in a folder on the server. The StaticFileController provides that feature. But before you can use it, you need some additional configuration settings in the ini file:
[files]
path=../docroot
encoding=UTF-8
maxAge=90000
cacheTime=60000
cacheSize=1000000
maxCachedFileSize=65536
The path setting specifies the base folder where static files are stored. It is relative to the configuration file. You could also write absolute path names like "/opt/server/docroot" or "C:/server/docroot".

The encoding parameter is only used for *.txt and *.html files, to tell the browser which encoding these files have. If you ever need different encodings simultaneously, you have to create multiple instances of the StaticFileController - one for each encoding.

The other parameters control cacheing. First you should know that the operating system caches files already. However I found out that both Linux and Windows do not perform well with small files. Therefore I recommend to use an application-internal cache - but only for small files.

So cacheTime controls how many milliseconds files are hold in memory at maximum. The value 0 means, that files are kept in memory as long there is enough space. CacheSize specifies how much memory the cache is allowed to occupy. One megabyte is a good value to start with. If people request files that are not in the cache, then the oldest files are removed to free up space for the new files.

The maxCachedFileSize controls the maximum size of individual files in the cache. Larger files are not cached by the web server application. But as I wrote - the operating system caches large files well. It turned out that 64 kilobytes is a good value to start with.

The maxAge parameter means basically the same as cacheTime but controls the cache of the web browser - not the server.

Now we need to initialize one instance of the StaticFileController: requestmapper.h:

#ifndef REQUESTMAPPER_H
#define REQUESTMAPPER_H

...
#include "staticfilecontroller.h"

using namespace stefanfrings;

class RequestMapper : public HttpRequestHandler {
    Q_OBJECT
public:
    ...
    static StaticFileController* staticFileController;
private:
    ...
};

#endif // REQUESTMAPPER_H
requestmapper.cpp:
#include "requestmapper.h"
#include "httpsession.h"

HttpSessionStore* RequestMapper::sessionStore=0;
StaticFileController* RequestMapper::staticFileController=0;

...

void RequestMapper::service(HttpRequest& request, HttpResponse& response) {
    QByteArray path=request.getPath();
    ...
    else if (path=="/cookie") {
        cookieTestController.service(request, response);
    }
    else if (path.startsWith("/files")) {
        staticFileController->service(request,response);
    }
    else {
        response.setStatus(404,"Not found");
        response.write("The URL is wrong, no such document.");
    }

    qDebug("RequestMapper: finished request");
}
main.cpp:
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");
    RequestMapper::sessionStore=new HttpSessionStore(sessionSettings,&app);

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

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

    return app.exec();
}
Now create the folder MyFirstWebApp/docroot/files and then create an HTML file with the name hello.html:
<html>
    <body>
        Hello World!
    </body>
</html>
Start the program and open http://localhost:8080/files/hello.html. Your browser will receive the content of that file.

You can add other files (images, css, javascript, ...) to this folder and create more sub-folders if you like.

In case of "file not found" errors, the debug messages will help you find out which file the server really attempted to load.

The file loader project: MyFirstWebApp_6.zip

So far, you learned to use all objects of the HTTP Server module of QtWebApp. But there is more...

Templates

You wrote several controllers that generate HTML documents by concatenating hardcoded text fragments which are spread in the C++ source code files. The source code does not look well formatted, and the generated HTML documents are also ugly. If you have to generate larger complex websites, that would become painful.

So the whole world of web developers choose between some alternatives:

  1. Go the reverse way by inserting program code into HTML documents, like JSP and PHP can do - for example.
  2. Use Javascript (AJAX) on the client side to generate screens from raw data provided by the server.
  3. Merge data into prepared HTML files before sending the result to the web browser.
The first approach requires a programming language that can compile machine code at runtime - so that wont work well with C++.

The second approach lacks performance and suffers from not having a real good Javascript development environment available. However that became better over the past years. I would not recommend it for now (2015) but I see good chances that I can recommend it in the near future.

The last approach is technically much simpler because it's just matter of search/replace operations on plain text. And that provides the best performance on average web sites while it consumes a moderate amount of memory. Modern CPU's can modify strings with an impressive speed. So this is what my Template class provides.

But first I like to warn you: The Template class keeps the whole generated document in memory until it has been sent out. You should not attempt to process documents of 100 Megabytes or more with that class. Use the Template engine only for websites of normal size.

You may use the template engine for any text based file formats, not only HTML. It is also useful to generate XML and JSON.

The template engine of QtWebApp needs some configuration settings which you add to webapp1.ini:

[templates]
path=../docroot
suffix=.html
encoding=UTF-8
cacheSize=1000000
cacheTime=60000
The path is again relative to the folder of the configuration file. This is the folder where we will store the template files - the incomplete HTML files that contain placeholders for variable content.

The suffix will be appended to template names. If you load the template "wolfi", then the template engine loads the file "wolfi.html". You can use any suffix, it has no meaning to the program. Some people prefer ".dhtml" to distinguish between static and dynamic HTML files. Therefore it's configurable.

The encoding paramter is needed because template files are read into QString objects. I prefer UTF-8 because it supports all characters of any language. I also use an UTF-8 capable text editor to edit them (gedit under Linux or Notepad++ under Windows).

To improve performance, the server caches the QString objects. CacheSize specifies the maximum amount of memory that the cache may occupy and cacheTime specifies how long the files are hold in memory at maximum. We had the same settings already for the static file controller.

You get very good performance when you make the cache large enough to keep all template files in memory. But do that only if the server has enough RAM free. Otherwise it may freeze or crash.

The template engine is a separate module in QtWebApp, so you have to add one line to the project file MyFirstWebApp.pro:

include(../QtWebApp/QtWebApp/templateengine/templateengine.pri)
Next we need a storage class (the cache) for the template files. So add a static pointer variable to requestmapper.h:
...
#include "templatecache.h"

using namespace stefanfrings;

class RequestMapper : public HttpRequestHandler {
    Q_OBJECT
public:
    ...
    static TemplateCache* templateCache;
private:
    ...
};
And one new line in requestmapper.cpp:
#include "requestmapper.h"
#include "httpsession.h"

HttpSessionStore* RequestMapper::sessionStore=0;
StaticFileController* RequestMapper::staticFileController=0;
TemplateCache* RequestMapper::templateCache=0;
...
Now we can write the code to load the configuration settings and initialize the template cache.

So add this to the main.cpp file:

    // Configure template cache
    QSettings* templateSettings=new QSettings(configFileName,QSettings::IniFormat,&app);
    templateSettings->beginGroup("templates");
    RequestMapper::templateCache=new TemplateCache(templateSettings,&app);
Now we have a template cache, which automatically loads template files into memory when needed.

The next thing that we need is a controller which loads and outputs a template. To get a quick preview, let's simply load the already existing file hello.html.

Create a new class with name DataTemplateController, which is derived from HttpRequestHandler. We will bind it to the path "/list2". You know the procedure already.

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 "requestmapper.h"
#include "datatemplatecontroller.h"
#include "template.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=RequestMapper::templateCache->getTemplate("files/hello",language);
    response.write(t.toUtf8(),true);
}
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
Add to 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");
}

You can now run and test the program by opening http://localhost:8080/list2. You will see "Hello World!" because that's the content of the template files/hello.html.

Variables

Now lets create a real template file that contains placeholders.

docroot/listdata.html:

<html>
    <body>
        Hello {name}
    </body>
</html>
And replace the service method of datatemplatecontroller.cpp by this one:
void DataTemplateController::service(HttpRequest &request, HttpResponse &response) {
    HttpSession session=RequestMapper::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=RequestMapper::templateCache->getTemplate("listdata",language);
    t.setVariable("name",username.toString());
    response.write(t.toUtf8(),true);
}
First, we fetch the session object of the current user. Then we read out the Accept-Language header which is sent by most web browsers to indicate which language the user prefers.

Then we set a response header to tell the browser that we are going to return a HTML document in UTF-8 encoding.

After we fetched the template file from the cache, we set one variable in it and then write the result to the HTTP response.

Try that program, open http://localhost:8080/list2. You will simply see "Hello". The name is missing because there is no name. The calling user has no session, so the server does not know his name yet.

Now login in via http://localhost:8080/login and then call the the template again via http://localhost:8080/list2.

This time, the response says "Hello test". So the variable placeholder {name} was replaced by the value "test" which is the username in the users session.

Conditions

Now lets add a conditional part. We want to distinguish whether the user is logged in or not.

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>
We have to add one line to datatemplatecontroller.cpp to set the condition "logged-in":
void DataTemplateController::service(HttpRequest &request, HttpResponse &response) {
    HttpSession session=RequestMapper::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=RequestMapper::templateCache->getTemplate("listdata",language);
    t.setVariable("name",username);
    t.setCondition("logged-in",!username.isEmpty());
    response.write(t.toUtf8(),true);
}
Try that program again, open http://localhost:8080/list2. This time you see:

Screenshot

So click on that link, then log in:

Screenshot

Screenshot

And now open again http://localhost:8080/list2:

Screenshot

You see a different content now, because the condition "logged-in" is now true. Instead of the {if condition} you may also use the reverse counterpart, which is {ifnot condition}.

Loops

The template engine can repeat parts of the website in loops. You need loops when you iterate over a list of data, which we will do now. We have already created a list in the constructor but not used until now.

New lines 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>
New lines in datatemplatecontroller.cpp:
void DataTemplateController::service(HttpRequest &request, HttpResponse &response) {
    HttpSession session=RequestMapper::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=RequestMapper::templateCache->getTemplate("listdata",language);
    t.setVariable("name",username);
    t.setCondition("logged-in",!username.isEmpty());
    t.loop("row",list.size());
    for(int i=0; i&t;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);
}

The template contains a loop around the table row. So that row will be repeated several times. Inside the loop we have the HTML code for one single row. The variable names are prefixed by the name of the loop.

When the template engine repeats that loop 3 times, then we have the following temporary document in memory:

<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>
You see that the {loop row} and {end row} markers have vanished. The table row had been repeated 3 times, and the variable names had been modified with consecutive numbers.

This information should help you to understand the new C++ code lines inside the for() loop:

    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);
    }
The documentation of the Template class shows a more complex example with two nested loops. And you can do that with any number of levels. However the variable names become somewhat complicated then. I will not show it here because I do not want to bloat the tutorial.

Notice that the Template class distinguishes between the variables {name} and {row.name} properly. The first ist the name of the user while the latter is used in the loop/table.

Run the program and open http://localhost:8080/list2.

Warnings

You can use the method t.enableWarnings(true) to enable warning messages for missing placeholders. For example when the C++ code attempts to set the variable {name} but there is no such placeholder in the template, then this error would normally be silently ignored. But during development, it might be helpful to enable such warnings - or maybe also in production.

Languages

You saw already that we pass the preferred language of the user to the template engine, but we did not really use that feature until now. The related lines of code had been:
    QByteArray language=request.getHeader("Accept-Language");
    qDebug("language=%s",qPrintable(language));
    Template t=RequestMapper::templateCache->getTemplate("listdata",language);
The template engine can process several language versions of the same file. Lets add some other languages:

For german, the file listdata-de.html:

<html>
    <body>
        {if logged-in}
            Hallo {name}
        {else logged-in}
            Du bist nicht angemeldet.
            <br>
            Gehe zur <a href="/login">Anmeldeseite</a> und versuche es danach erneut.
        {end logged-in}

        <p>
        Liste der Namen:
        <table>
            {loop row}
                <tr>
                    <td>{row.number}</td>
                    <td>{row.name}</td>
                </tr>
            {end row}
        </table>
    </body>
</html>
For french, the file listdata-fr.html:
<html>
    <body>
        {if logged-in}
            Bonjour {name}
        {else logged-in}
            Vous n'êtes pas connecté.
            <br>
            Accédez à la <a href="/login">page de connexion</a> et réessayez plus tard .
        {end logged-in}

        <p>
        Liste des noms :
        <table>
            {loop row}
                <tr>
                    <td>{row.number}</td>
                    <td>{row.name}</td>
                </tr>
            {end row}
        </table>
    </body>
</html>
Now restart the program and configure your web browser to prefer german websites, then open http://localhost:8080/list2.

You will see the German version of that generated website:

Screenshot

If there is no such file for the preferred language(s), then the template loader falls back to the default file wich has no language suffix (listdata.html in this case). The log messages help you to fix problems if an unexpected language appears.

If you need a more complex template engine with additional features, then take a look at Clearsilver. That's the best one for C/C++ as far as I know.

You should also learn how to use the tr() macro provided by the Qt framework to translate strings.

The file template project: MyFirstWebApp_7.zip

Logging

Now we come to the auxiliary modules of QtWebApp. My personal favorite is the logging module. Until now, we simply wrote all messages to the console window. That's not good for a production environment because you need to look into old log messages, for example from two days ago.

You could simply redirect the output to a file (MyFirstWebApp > logfile.txt) but that has two issues:

So better let the web server itself write all messages to files. That's what the logger modules does.

To include the sources of the logging module to your project, add one line to the project file:

include(../QtWebApp/QtWebApp/logging/logging.pri)

Then add another section to the *.ini file of your program:

[logging]
fileName=../logs/webapp1.log
minLevel=2
bufferSize=100
maxSize=1000000
maxBackups=2
timestampFormat=dd.MM.yyyy hh:mm:ss.zzz
msgFormat={timestamp} {typeNr} {type} {thread} {msg}
The filename is relative to the folder of the configuration file. I think you guessed that already.

The minLevel specifies which amount of log messages you want to see.

All log messages are kept in a thread local ring buffer in memory. If a message of minLevel or higher occurs, then the content of the buffer is written out to the file.

That were a lot information in few words. Let me explain what that means in practice when using the settings above.

As long the program generates only debug messages, the logfile will remain empty. Let's assume an incoming HTTP request produces a lot of debug messages and then shortly before finish, an error occurs. Then the whole buffered messages are written to the logfile - but only the messages of that particular thread. So you will have the error message in the file, plus lots of useful debug messages of the time before.

But if everything works fine, you would have a beautiful totally empty logfile. System operators like that - really.

The bufferSize limits the number of log messages that are kept in memory. If the application runs 20 threads with a buffer size of 100, then you have at maximum 20×100 messages in memory. Of course, that costs memory and some performance. But the benefit out-weights the costs for sure.

You can set the bufferSize to 0 if you do not want to buffer messages. I would not recommend that.

The maxSize parameter limits the size of the logfile. When this limit is exceeded, then the logger switches over to a new file. The setting maxBackups specifies, how many old logfiles shall be kept on the disk.

I assume you can guess what the timestampFormat setting does. Each message is usually prefixed by a timestamp. And here you specify the format of that. See the documentation of QDateTime::toString() to get an explanation of all the characters. The characters above are straight forward, but there are some more available.

Finally the msgFormat setting specifies the format of each message. The following fields are available:

QT 5.0 and newer have some additional variables in debug mode: I am very happy that the Qt developers added these three fields to their framework. I missed them a lot for many years.

You can also use \n to insert a line break and \t to insert a tab character into the message format. All of the variables above can also be used inside the log message, e.g.

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

Now add some few lines of code to main.cpp which loads the settings and initializes the logger class:

#include "filelogger.h"
...

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");
    FileLogger* logger=new FileLogger(logSettings,10000,&app);
    logger->installMsgHandler();

    // Log the library version
    qDebug("QtWebApp has version %s",getQtWebAppLibVersion()); 
    ...
}   
The number 10000 is a refresh interval in milliseconds, which the logger uses to reload the configuration file. So you can edit any logger setting while the program is running and the changes become effective after some few seconds without needing to restart the server. Use the value 0 if you don't want automatic reloading.

By the way, I just gave you some example code which queries and logs the version number of the library. Some few people asked me to add that feature.

Don't forget to create an empty folder MyFirstWebApp/logs. The logger does not create folders itself.

You can now start the application and see what happens. You wont see much because the program has no errors, so the logfile remains empty. But you can see that the output in the console window is reduced to a minimum:

Screenshot

Lets insert a qCritical() message into logincontroller.cpp, then we can see that the log buffer works:

            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>");
Then open the URL http://localhost:8080/login?username=test&password=wrong.

Look again into the logfile - and voilà, there it is:

Screenshot

Now do another test by reducing the minLevel to 0. Save the ini file, wait 10 seconds and after that open the URL http://localhost:8080/hello. Check the logfile again. You can see that now all the debug messages are written out although no error occurred. So changing the log level without program restart works fine.

Logger Variables

Above, I wrote that the logger supports user-defined variables. These variables are thread-local and remain in memory until you clear them. For web applications, it might be useful to log the name of the current user with each message. Lets try that. For this purpose we have to change the visibility of our logger instance so the Requestmapper can call methods of the logger.

New lines in requestmapper.h:

#include "logger.h"
...
using namespace stefanfrings;

class RequestMapper : public HttpRequestHandler {
    Q_OBJECT
public:
    ...
    static Logger* logger;
private:
    ...
}
New line in requestmapper.cpp:
Logger* RequestMapper::logger=0;
Change in main.cpp:
    // Configure logging
    QSettings* logSettings=new QSettings(configFileName,QSettings::IniFormat,&app);
    logSettings->beginGroup("logging");
    RequestMapper::logger=new FileLogger(logSettings,10000,&app);
    RequestMapper::logger->installMsgHandler();
Now we can access the instaces of our logger class via the static variable from the whole program. Add code to requestmapper.cpp to set a logger variable:
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);
    ...
}
This way the request mapper queries the name of the calling user for all incoming HTTP requests before passing the request over to the controller classes.

We can now modify the ini file to use that variable:

msgFormat={timestamp} {typeNr} {type} {thread} User:{currentUser} {msg}
Run the program and open the URL http://localhost:8080/login?username=test&password=hello two times. Then check the logfile again:

Screenshot

You can see that the variable {currentUser} is empty until the user logged in. All following requests are then logged with the name of that user.

Log Buffers and Thread Pooling

Because threads are re-used for subsequent HTTP requests, the logger may output more details than you are interested in. For example, let's assume that the first successful HTTP request produces some hidden debug messages and then the second request which is handled by the same thread produces an error. Then your log file will contain the error message plus all buffered debug messages. But some of them came from the previous HTTP request, so you don't need them.

To clear the buffer between two HTTP requests, add 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);
}
So whenever the processing of a HTTP request has been finished, you clean up the memory of the logger. When the same thread processes the next request, it will start with empty buffers.

The logging project: MyFirstWebApp_8.zip

Windows Services

To run the web server as a Unix Daemon, you would simply write an init script for it as usual. But it's not that simple under Windows. I found a great free library on the Website of Trolltech (that company does not exist anymore) that simplifies the creation of real Windows services. It's the "qtservice helper". I embedded this into QtWebApp.

Anyway, qtservice is also compatible to Linux.

To use the qtservice module you have to add one line to the project file:

include(../QtWebApp/QtWebApp/qtservice/qtservice.pri)
Create a new class with name Startup which inherits from 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);
}
Then move the whole code of main.ccp into startup.cpp. The content of the main() method belongs now in Startup::start(). Replace the first line "QCoreApplication app(argc, argv)" by "QCoreApplication* app = application()". Change all occurrences of "&app" to "app". and remove the final app.exec().

So that's the final whole startup.cpp:

#include <QCoreApplication>
#include <QSettings>
#include <QFile>
#include <QDir>
#include <QString>
#include "httprequesthandler.h"
#include "httpsessionstore.h"
#include "httplistener.h"
#include "staticfilecontroller.h"
#include "templatecache.h"
#include "filelogger.h"
#include "startup.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");
    RequestMapper::logger=new FileLogger(logSettings,10000,app);
    RequestMapper::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");
    RequestMapper::sessionStore=new HttpSessionStore(sessionSettings,app);

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

    // Configure template cache
    QSettings* templateSettings=new QSettings(configFileName,QSettings::IniFormat,app);
    templateSettings->beginGroup("templates");
    RequestMapper::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);
}
And the new main.cpp is now very small:
#include "startup.h"

int main(int argc, char *argv[])
{
    Startup startup(argc, argv);
    return startup.exec();
}
To run the program as before in a console window (or with Qt Creator), you have to append the command-line argument "-e". I show you where to enter that:

Screenshot

So enter here "-e" without the quotes. Then run the program. It should look exactly as before without the qtservice library.

Windows

When you now go to the target Windows server, you enter the command "MyFirstWebApp -i" to install the windows service. Then you run "MyFirstWebapp" without arguments to start the service. The command "MyFirstWebApp -t" would terminate the service.

You can also use the service manager screen in the system control panel to check the status, start and stop the service.

And you can also use the "net" command, e.g. "net start MyFirstWebApp".

Linux

As I wrote above, you have to start the daemon by an init script as usual. In addition, run the command "./MyFirstWebApp -i" once to create a configuration setting that qtservice needs. Then your init script can start the daemon by calling "MyFirstWebApp" without arguments. And it can terminate the daemon by calling "MyFirstWebApp -t".

Without qtservice, you can also run the web-server as a Linux daemon. In this case, you would need to start it by "MyFirstWebApp &" (with an ampersand after the executable name). And you can terminate it with the kill or pkill command.

On both operating systems, the argument -h shows a help about the available options.

The qtservice project: MyFirstWebApp_9.zip

FAQ

How can I set up HTTPS?

You need to install the openssl library version 0.9.8 or 1.0.x and buy an SSL certificate. Create an unsigned test certificate by the command

openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout my.key -out my.cert

Add two settings to the configuration file which point to the certificate and key files.

[listener]
...
port=443
sslKeyFile=ssl/my.key
sslCertFile=ssl/my.cert
If you want to support both HTTP and HTTPS simultaneously you need two listeners - one with ssl settings (usually on port 443) and another one without ssl settings (usually on port 80).

Web browsers display a warning when they receive an unsigned certificate. So for the public internet you have to buy a signed certificate.

I get a warning: cannot resolve SSLv2_client_method

The Qt libraries output this warning when your openssl library does not support SSL version 2. This protocol version has been disabled because of vulnerabilities. QtWebApp does not depend on it, so you can ignore the warning.

How can I set up a HA cluster?

Put an Apache HTTP server before your web application. The Apache module mod_proxy can share load between multiple nodes and detect broken nodes by configurable rules. The sticky session feature ensures that a logged-in user is always routed to the same server as long that server is running.

Can I run QtWebApp on embedded ARM systems?

Yes, on all platforms that are supported by Qt. I tried it successfully on an Android 4.1 phone and I got positive feedback for the Raspberry Pi and Odroid. However, I have no idea how to install a daemon on Android without rooting it.

Is it safe to kill the program?

That depends on what your program does. The whole QtWebApp library and all demo applications can be killed safely at any time. They do not need any code that catches the kill event. But if you write a more complex application, your business logic might require that.

Does QtWebApp support something like JSP?

No it doesn't and I will never ever develop that.

Why is there no mapping file like web.xml?

Because all that stuff around mapping and filtering can be easily developed with some few lines of code in the RequestMapper.

Does QtWebApp support AJAX?

Yes, of course. You don't need any special class or method for that on the server side. And for the client side, there are a lot of Javascript frameworks available. The example Javascript code in the Wikipedia article may be used as well.

How to support HTML5 SSE (Server-Side-Events)?

You need to write a special request handler that runs in an endless loop to keep the connection open. Within that loop you can send events to the browser by calling HttpResponse::write().

See the Book High Performance Browser Networking, Chapter 16 from Ilya Grigorik.

I do not recommend to use SSE because it keeps an IP socket open all the time. The maximum number of connections is limited by the operating system and network infrastructure. Open connections do also draw energy from the battery of mobile devices.

How to prevent crash during shutdown?

When you encounter a segmentation fault sporadically at shutdown of your application under high load, you should check the deletion order of objects. The automatic clean-up procedure of Qt (via parent ownership) tends to delete objects in the wrong order.

You have to close or delete the listener before any other other object that is used by the listener (e.g. the request handler).

Note that console applications are usually closed instantly by the operating system when you press Ctrl-C or close the console window. The destructors are not called in that case.

Final Words

I hope this tutorial helped you to get a quick start.

I did not explain everything in detail, so take the time to read the API documentation when you use a class and take a look at the demo programs. They do some things slightly different than I showed in this tutorial, which is Ok. There are often several correct solutions possible for one job.

Some few people were highly interested in the file-upload feature which is shown by the demo programs. You will see that this is much easier with QtWebApp than with the Java Servlet API.

Commercial users might be unsure whether it's a good idea to develop an important web server using the library of a one-man-show. Nobody knows how long I will maintain it. But the project is open-source, small, and well commented. The license allows you to modify the code and sell or publish the modified version.

So you have everything that you need for a safe business. Even when I stop working on it, your copy of QtWebApp will continue to work. There is nothing to fear.

Feel free to contact me via email in case of further questions.