📜 ⬆️ ⬇️

QtWebApp is a step-by-step chewed example with detailed comments.



In the process of developing an application on Qt, you may need to add a web interface to this application, which may be especially relevant when developing embedded systems using Qt. To solve this problem, you can either write your own solution, or use ready-made solutions. For example, the QtWebApp library, which provides the necessary functionality for creating a web interface.

The advantages of this library include:
  1. formation of pages with dynamic content for templates;
  2. the formation of fully dynamic pages;
  3. working with a cookie that will allow you to add authorization on the application;
  4. work with static files, for example, style.css or images;
  5. implementation of file uploads.

I propose to consider in detail one of the options for running a small application on Qt, which will have several web pages that use the QtWebApp library.
')
At the time of this writing, the QtWebApp 1.6.3 and Qt 5.6 library was originally used. The project was successfully launched with MSVC2013 and MinGW build kits. During the debugging process, a bug was noticed in the Template class of the QtWebApp library. After fixing the bug and communication with the developer, the library version was upgraded to 1.6.4. Based on this, it is also possible to note, plus the libraries, that the developer responded to the information about the bug within 24 hours, and on the same day the library version was upgraded. The final version of the sample application was prepared on version 1.6.4.

In this project, it is proposed to create an application that has three pages, a menu for selecting these pages, and three static files. One of the files is style.css, and the other two are images.

Project structure



The project will be formed as a Subdirs project, which will consist of the main project and the QtWebApp library project.
Project structure:

QtWebAppExample.pro - the main project profile
common - custom web server project


QtWebApp - library project


QtWebAppExample.pro


The general profile of the project is the subdirs template with the main project connected and the QtWebApp library. The sequence of connecting projects in the file is important. The QtWebApp library should be written first, otherwise, when building the project, errors will occur:
if at the time of building the main project, which depends on QtWebApp, the collected library files (.dll or .so) are not available, the project will not be built.
TEMPLATE = subdirs SUBDIRS += \ QtWebApp \ common CONFIG += ordered common.files = common/html-static/* CONFIG(debug, debug|release) { common.path = $$OUT_PWD/../HttpServiceDebug/html-static } else { common.path = $$OUT_PWD/../HttpService/html-static } INSTALLS += common 


common.pro


If the library profile is not fundamentally corrected in this example, then setting the profile of the main project of the web server can deliver some inconvenience to the novice user. As can be seen from the following script, the application responsible for graphic libraries is disabled in the application as unnecessary, but the network library is enabled for processing requests to the http-server.

In addition, it is necessary to correctly link the header files and source files, as well as the output folders for building the library, so that the assembled project can follow the correct path to the library files.

 QT += core network QT -= gui TARGET = common CONFIG += console CONFIG -= app_bundle CONFIG += c++11 TEMPLATE = app SOURCES += main.cpp \ webconfigurator.cpp \ webconfiguratorpage.cpp HEADERS += \ webconfigurator.h \ webconfiguratorpage.h \ httpsettings.hpp RESOURCES += \ resources.qrc CONFIG(debug, debug|release) { DESTDIR = $$OUT_PWD/../../HttpServiceDebug } else { DESTDIR = $$OUT_PWD/../../HttpService } win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../QtWebApp/release/ -lQtWebApp else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../QtWebApp/debug/ -lQtWebApp else:unix: LIBS += -L$$OUT_PWD/../QtWebApp/ -lQtWebApp INCLUDEPATH += $$PWD/../QtWebApp/httpserver DEPENDPATH += $$PWD/../QtWebApp/httpserver INCLUDEPATH += $$PWD/../QtWebApp/templateengine DEPENDPATH += $$PWD/../QtWebApp/templateengine INCLUDEPATH += $$PWD/../QtWebApp/qtservice DEPENDPATH += $$PWD/../QtWebApp/qtservice win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../QtWebApp/release/libQtWebApp.a else:win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../QtWebApp/debug/libQtWebApp.a else:win32:!win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../QtWebApp/release/QtWebApp.lib else:win32:!win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../QtWebApp/debug/QtWebApp.lib else:unix: PRE_TARGETDEPS += $$OUT_PWD/../QtWebApp/libQtWebApp.a DISTFILES += \ html-static/style.css \ html-static/favicon-32x32.png \ html-static/favicon.png 


QtWebApp.pro


The library project's default profile is shown below. The only change in the project was the presence of additional assembly settings as a static library.
 # Build this project to generate a shared library (*.dll or *.so). TARGET = QtWebApp TEMPLATE = lib QT -= gui CONFIG += staticlib VERSION = 1.6.4 mac { QMAKE_MAC_SDK = macosx10.10 QMAKE_CXXFLAGS += -std=c++11 CONFIG += c++11 QMAKE_LFLAGS_SONAME = -Wl,-install_name,/usr/local/lib/ } win32 { DEFINES += QTWEBAPPLIB_EXPORT } # Windows and Unix get the suffix "d" to indicate a debug version of the library. # Mac OS gets the suffix "_debug". CONFIG(debug, debug|release) { win32: TARGET = $$join(TARGET,,,d) mac: TARGET = $$join(TARGET,,,_debug) unix:!mac: TARGET = $$join(TARGET,,,d) } DISTFILES += doc/* mainpage.dox Doxyfile OTHER_FILES += ../readme.txt include(qtservice/qtservice.pri) include(logging/logging.pri) include(httpserver/httpserver.pri) include(templateengine/templateengine.pri) 


main.cpp


And now let's go through all the files in the common project in order to figure out how to run a Qt application with a web interface. Let's start with the start file of the application and with the function main, with which the application is launched.

There is getting the path to the settings file, which stores the settings of the web server, TCP / IP port, etc.
It also creates an object of the WebConfigurator class, which is responsible for processing requests and issuing, on request, the corresponding pages of the web server.
 #include <QCoreApplication> #include <QDir> #include <webconfigurator.h> int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); a.setApplicationName("QtWebAppExample"); QString configPath = QDir::currentPath() + "/" + QCoreApplication::applicationName() + ".ini"; new WebConfigurator(configPath); return a.exec(); } 

HttpSettings.hpp


It actually contains an auxiliary class that does not carry more than necessary information, but initialization of the settings file is included in it for convenience, and in the case of a more global project, this class will be conveniently extended. In this embodiment, of course, more like a matter of taste.

All parameters relate to the configuration of the connection port, the number of simultaneous sessions, the duration of the request.

The application settings will also contain the parameters of the static file controller, in particular, the path to the folder where the static files of the web server will be searched. In this application, this is the html-static folder, which will be located in the same folder as the executable file of the application.

 #ifndef HTTPSETTINGS_H #define HTTPSETTINGS_H #include <QSettings> class HttpSettings : public QSettings { public: explicit HttpSettings(const QString& fileName, QObject* parent = nullptr) : QSettings(fileName,QSettings::IniFormat,parent) { //  - setValue("port", value("port", 8080)); setValue("minThreads", value("minThreads", 1)); setValue("maxThreads", value("maxThreads", 100)); setValue("cleanupInterval", value("cleanupInterval", 1000)); setValue("readTimeout", value("readTimeout", 60000)); setValue("maxRequestSize", value("maxRequestSize", 16000)); setValue("maxMultiPartSize", value("maxMultiPartSize", 10000000)); //     setValue("html-static/path", value("html-static/path", "html-static")); setValue("html-static/encoding", value("html-static/encoding", "UTF-8")); setValue("html-static/maxAge", value("html-static/maxAge", 60000)); setValue("html-static/cacheTime", value("html-static/cacheTime", 60000)); setValue("html-static/cacheSize", value("html-static/cacheSize", 1000000)); setValue("html-static/maxCachedFileSize", value("html-static/maxCachedFileSize", 65536)); } }; #endif // HTTPSETTINGS_H 

WebConfigurator.h


And now we’ll take a close look at the contents of the WebConfigurator class, which is directly responsible for determining the pages to be sent from outside to a request.

The pages are defined using the QHash class object, which contains pointers to all web page objects and their corresponding key values ​​that correspond to URL addresses of requests. But QHash is used only for dynamic pages, and for static pages, an object of class StaticFileController is used.

 #ifndef WEBCONFIGURATOR_H #define WEBCONFIGURATOR_H #include <httprequesthandler.h> #include <httplistener.h> #include <webconfiguratorpage.h> #include <httpsettings.hpp> #include <staticfilecontroller.h> class WebConfigurator : public HttpRequestHandler { Q_OBJECT Q_DISABLE_COPY(WebConfigurator) public: WebConfigurator(QString &configPath); virtual ~WebConfigurator(); virtual void service(HttpRequest& request, HttpResponse& response) override; private: QString m_configPath; HttpSettings m_config; HttpListener m_httpListener; QHash<QString,WebConfiguratorPage*> m_pages; StaticFileController *m_staticFileController; }; #endif // WEBCONFIGURATOR_H 

Webconfigurator.cpp


The configurator is responsible for redirecting requests to relevant pages and images and is a repository of page data and images. If the page or image does not exist, then a 404 error is returned.
#include "webconfigurator.h"
 WebConfigurator::WebConfigurator(QString &configPath) : m_configPath(configPath), m_config(m_configPath), m_httpListener(&m_config, this) { /*   QHash    , *      - * */ m_pages.insert("/index.html", new IndexPage()); m_pages.insert("/second.html", new SecondPage()); m_pages.insert("/first.html", new FirstPage()); /*      *     ,    *        *    ,    *     * */ m_config.beginGroup("html-static"); m_staticFileController = new StaticFileController(&m_config); m_config.endGroup(); } WebConfigurator::~WebConfigurator() { foreach(WebConfiguratorPage* page, m_pages) { delete page; } delete m_staticFileController; } void WebConfigurator::service(HttpRequest &request, HttpResponse &response) { /*        *    . *   ,   ,   *          . *      404 * */ QByteArray path = request.getPath(); for(auto i = m_pages.begin(); i != m_pages.end(); ++i) { if(path.startsWith(i.key().toLatin1())) { return i.value()->handleRequest(request,response); } } if(path=="/") { response.redirect("/index.html"); return; } if(path.startsWith("/style.css") || path.startsWith("/favicon-32x32.png") || path.startsWith("/favicon.png")){ return m_staticFileController->service(request, response); } response.setStatus(404,"Not found"); } 

WebConfiguratorPage.h


This header file contains the declaration of the main class responsible for the formation of pages and three classes of pages inherited from it for the project: index.html, first.html, second.html.
 #ifndef WEBCONFIGURATORPAGE_H #define WEBCONFIGURATORPAGE_H #include <QObject> #include <httprequesthandler.h> #include <httplistener.h> #include <template.h> class WebConfiguratorPage : public QObject { Q_OBJECT public: WebConfiguratorPage(const QString& title); virtual void handleRequest(HttpRequest&, HttpResponse&) {} virtual ~WebConfiguratorPage() {} protected: Template commonTemplate() const; private: QString m_title; }; class IndexPage : public WebConfiguratorPage { Q_OBJECT public: IndexPage() : WebConfiguratorPage("EDISON") {} virtual ~IndexPage() {} public: virtual void handleRequest(HttpRequest &request, HttpResponse &response) override; }; class FirstPage : public WebConfiguratorPage { Q_OBJECT public: FirstPage() : WebConfiguratorPage("First Page") {} virtual ~FirstPage() {} public: virtual void handleRequest(HttpRequest &request, HttpResponse &response) override; }; class SecondPage : public WebConfiguratorPage { Q_OBJECT public: SecondPage() : WebConfiguratorPage("Second Page") {} virtual ~SecondPage() {} public: virtual void handleRequest(HttpRequest &request, HttpResponse &response) override; }; #endif // WEBCONFIGURATORPAGE_H 


WebConfiguratorPage.cpp


 #include "webconfiguratorpage.h" #include <QFile> #include <QDebug> WebConfiguratorPage::WebConfiguratorPage(const QString &title) : m_title(title) { } Template WebConfiguratorPage::commonTemplate() const { /*       common.htm. *      ... * */ QFile file(":/html/common.htm"); Template common(file, QTextCodec::codecForName("UTF-8")); common.setVariable("Title", m_title); /*    . *        , *        . *        ,  *     . *       common.htm,  *      "Navigation" * */ bool navigation = true; common.setCondition("Navigation", navigation); if(navigation) { /*          * ,        common.htm * */ common.loop("Items", 3); common.setVariable("Items0.href", "/index.html"); common.setVariable("Items0.name", "Main page"); common.setVariable("Items1.href", "/first.html"); common.setVariable("Items1.name", "First page"); common.setVariable("Items2.href", "/second.html"); common.setVariable("Items2.name", "Second page"); } return common; } /*         . *      ,     *        * */ void IndexPage::handleRequest(HttpRequest &request, HttpResponse &response) { if (request.getMethod() == "GET") { //     Template common = commonTemplate(); QFile file(":/html/index.htm"); Template contents(file, QTextCodec::codecForName("UTF-8")); /*           *       ,      *      {Content} * */ common.setVariable("Content", contents); response.setHeader("Content-Type", "text/html; charset=ISO-8859-1"); response.write(common.toUtf8()); return; } else { return; } return; } void FirstPage::handleRequest(HttpRequest &request, HttpResponse &response) { if (request.getMethod() == "GET") { Template common = commonTemplate(); QFile file(":/html/first.htm"); Template contents(file, QTextCodec::codecForName("UTF-8")); common.setVariable("Content", contents); response.setHeader("Content-Type", "text/html; charset=ISO-8859-1"); response.write(common.toUtf8()); return; } else { return; } return; } void SecondPage::handleRequest(HttpRequest &request, HttpResponse &response) { if (request.getMethod() == "GET") { Template common = commonTemplate(); QFile file(":/html/second.htm"); Template contents(file, QTextCodec::codecForName("UTF-8")); common.setVariable("Content", contents); response.setHeader("Content-Type", "text/html; charset=ISO-8859-1"); response.write(common.toUtf8()); return; } else { return; } return; } 

Common.htm



Toward the curtain, consider the contents of the templates.
 <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <title>{Title}</title> <meta name="viewport" content="width=device-width, initial-scale=1" /> <link rel="stylesheet" type="text/css" href="style.css"> <link rel="icon" type="image/png" href="favicon-32x32.png" sizes="32x32"/> </head> <body> <div class="content"> <a href="http://edsd.ru"><div class="logo"></div><h1>{Title}</h1></a> {if Navigation} <ul class="menu"> {loop Items} <li class = "menuitem"> <a href={Items.href}>{Items.name}</a> </li> {end Items} </ul> {end Navigation} {Content} </div> </body> </html> 

index.htm


 <h2>EDISON</h2> <p>   </p> 

Result


As a result, we get a working application with a web server, which is perfect for embedded systems.

And this application will generate the next web page.

Note


The draft application can be downloaded from the link: download .
When building a project, be sure to install the install stage so that the necessary static files are installed in the appropriate folder for the executable file.

Little bit about the bug


Add a few words about the bug, which in itself was more like the result of unsuccessful code refactoring, or rather, the developer was simply tired at some point. The fact is that in earlier versions of QtWebApp, namely in version 1.5.10, the code was correct and looked like this.
 if (data.size()==0 || file.error())   {       qCritical("Template: cannot read from %s, %s",qPrintable(sourceName),qPrintable(file.errorString()));   } else {        append(textCodec->toUnicode(data));   } 


Whereas in version 1.6.3 one single line was omitted.
 if (data.size()==0 || file.error())   {       qCritical("Template: cannot read from %s, %s",qPrintable(sourceName),qPrintable(file.errorString()));       append(textCodec->toUnicode(data));   } 


As a result, no data was added to the page template, and the user received a blank page. According to Stefan Frings, the developer of QtWebApp, he usually uses a different approach to the formation of a web interface than we do, so he simply did not notice this problem.

Source: https://habr.com/ru/post/280932/


All Articles