Update QtWebApp to 1.8.3

nullptr redefinition for old __cplusplus versions has been removed, the rest is vanilla
This commit is contained in:
Luis Ángel San Martín 2021-10-03 21:17:49 +02:00
parent baccb1a21b
commit 986450eb20
20 changed files with 131 additions and 43 deletions

View File

@ -1,6 +1,27 @@
Dont forget to update the release number also in Dont forget to update the release number also in
QtWebApp.pro and httpserver/httpglobal.cpp. QtWebApp.pro and httpserver/httpglobal.cpp.
1.8.3
21.03.2021
The minLevel for logging can now be configured as string:
DEBUG/ALL=0, INFO=4, WARNING=1, ERROR/CRITICAL=2, FATAL=3
Info messages are now positioned between DEBUG and WARNING.
I also added an example for HTTP Basic authorization.
1.8.2
08.03.2021
Fix threadId not printed in log file.
1.8.1
07.02.2021
Add Cookie attribute "SameSite".
SessionStore does now emit a signal when a session expires.
1.8.0
06.02.2021
Fix compatibility issues to Qt 4.7 and 6.0.
Removed qtservice, use the Non-Sucking Service Manager (https://nssm.cc/) instead.
1.7.11 1.7.11
28.12.2019 28.12.2019
Fix Http Headers are not properly received if the two characters of Fix Http Headers are not properly received if the two characters of

View File

@ -1,25 +1,22 @@
QtWebAppLib is a library to develop server-side web applications in C++. QtWebAppLib is a library to develop server-side web applications in C++.
It requires the Qt SDK version 4.7.0 or newer. Works with Qt SDK version 4.7 until at least 6.0
License: LGPL v3. License: LGPL v3.
Project homepage: http://stefanfrings.de/qtwebapp/index.html Project homepage: http://stefanfrings.de/qtwebapp/index-en.html
Tutorial: http://stefanfrings.de/qtwebapp/tutorial/index.html Tutorial: http://stefanfrings.de/qtwebapp/tutorial/index.html
API doc: http://stefanfrings.de/qtwebapp/api/index.html API doc: http://stefanfrings.de/qtwebapp/api/index.html
There are three demo applications that demonstrate how to use the library. In Qt 6.0 or newer, you must install the optional "core5compat" component.
This package contains the QTextCodec class which is needed to decode template files.
It supports a lot more encodings, for example ISO-8859-15 with the EUR symbol.
Demo1 shows how to use the library by including the source code into your Demo1 shows how to use the library by including the source code into your
project. This does not depend on the shared library. project, the preferred method.
Demo2 shows how to link against the shared library. Demo2 shows how to link against the shared library.
Build the project QtWebApp to generate the shared library. Build the project QtWebApp to generate the shared library.
Demo3 shows how to use the qtservice component to start the application
as a Windows Service or Unix daemon. Start it with option -h to get help.
I recommend to include the library by source as shown in Demo1 and 3.
Stefan Frings Stefan Frings
http://stefanfrings.de http://stefanfrings.de

View File

@ -32,10 +32,10 @@ HttpConnectionHandler::HttpConnectionHandler(const QSettings *settings, HttpRequ
socket->moveToThread(thread); socket->moveToThread(thread);
// Connect signals // Connect signals
connect(socket, &QIODevice::readyRead, this, &HttpConnectionHandler::read); connect(socket, SIGNAL(readyRead()), SLOT(read()));
connect(socket, &QAbstractSocket::disconnected, this, &HttpConnectionHandler::disconnected); connect(socket, SIGNAL(disconnected()), SLOT(disconnected()));
connect(&readTimer, &QTimer::timeout, this, &HttpConnectionHandler::readTimeout); connect(&readTimer, SIGNAL(timeout()), SLOT(readTimeout()));
connect(thread, &QThread::finished, this, &HttpConnectionHandler::thread_done); connect(thread, SIGNAL(finished()), this, SLOT(thread_done()));
qDebug("HttpConnectionHandler (%p): constructed", static_cast<void*>(this)); qDebug("HttpConnectionHandler (%p): constructed", static_cast<void*>(this));
} }

View File

@ -20,7 +20,7 @@
namespace stefanfrings { namespace stefanfrings {
/** Alias type definition, for compatibility to different Qt versions */ /** Alias type definition, for compatibility to different Qt versions */
#if QT_VERSION >= 0x050000 #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
typedef qintptr tSocketDescriptor; typedef qintptr tSocketDescriptor;
#else #else
typedef int tSocketDescriptor; typedef int tSocketDescriptor;

View File

@ -18,7 +18,7 @@ HttpConnectionHandlerPool::HttpConnectionHandlerPool(const QSettings *settings,
this->sslConfiguration=NULL; this->sslConfiguration=NULL;
loadSslConfig(); loadSslConfig();
cleanupTimer.start(settings->value("cleanupInterval",1000).toInt()); cleanupTimer.start(settings->value("cleanupInterval",1000).toInt());
connect(&cleanupTimer, &QTimer::timeout, this, &HttpConnectionHandlerPool::cleanup); connect(&cleanupTimer, SIGNAL(timeout()), SLOT(cleanup()));
} }
@ -77,7 +77,8 @@ void HttpConnectionHandlerPool::cleanup()
{ {
delete handler; delete handler;
pool.removeOne(handler); pool.removeOne(handler);
qDebug("HttpConnectionHandlerPool: Removed connection handler (%p), pool size is now %i",handler,pool.size()); long int poolSize=(long int)pool.size();
qDebug("HttpConnectionHandlerPool: Removed connection handler (%p), pool size is now %li",handler,poolSize);
break; // remove only one handler in each interval break; // remove only one handler in each interval
} }
} }
@ -140,7 +141,7 @@ void HttpConnectionHandlerPool::loadSslConfig()
sslConfiguration->setLocalCertificate(certificate); sslConfiguration->setLocalCertificate(certificate);
sslConfiguration->setPrivateKey(sslKey); sslConfiguration->setPrivateKey(sslKey);
sslConfiguration->setPeerVerifyMode(QSslSocket::VerifyNone); sslConfiguration->setPeerVerifyMode(QSslSocket::VerifyNone);
sslConfiguration->setProtocol(QSsl::TlsV1SslV3); sslConfiguration->setProtocol(QSsl::AnyProtocol);
qDebug("HttpConnectionHandlerPool: SSL settings loaded"); qDebug("HttpConnectionHandlerPool: SSL settings loaded");
#endif #endif

View File

@ -14,7 +14,9 @@ HttpCookie::HttpCookie()
secure=false; secure=false;
} }
HttpCookie::HttpCookie(const QByteArray name, const QByteArray value, const int maxAge, const QByteArray path, const QByteArray comment, const QByteArray domain, const bool secure, const bool httpOnly) HttpCookie::HttpCookie(const QByteArray name, const QByteArray value, const int maxAge, const QByteArray path,
const QByteArray comment, const QByteArray domain, const bool secure, const bool httpOnly,
const QByteArray sameSite)
{ {
this->name=name; this->name=name;
this->value=value; this->value=value;
@ -24,6 +26,7 @@ HttpCookie::HttpCookie(const QByteArray name, const QByteArray value, const int
this->domain=domain; this->domain=domain;
this->secure=secure; this->secure=secure;
this->httpOnly=httpOnly; this->httpOnly=httpOnly;
this->sameSite=sameSite;
this->version=1; this->version=1;
} }
@ -32,6 +35,7 @@ HttpCookie::HttpCookie(const QByteArray source)
version=1; version=1;
maxAge=0; maxAge=0;
secure=false; secure=false;
httpOnly=false;
QList<QByteArray> list=splitCSV(source); QList<QByteArray> list=splitCSV(source);
foreach(QByteArray part, list) foreach(QByteArray part, list)
{ {
@ -76,6 +80,10 @@ HttpCookie::HttpCookie(const QByteArray source)
{ {
httpOnly=true; httpOnly=true;
} }
else if (name=="SameSite")
{
sameSite=value;
}
else if (name=="Version") else if (name=="Version")
{ {
version=value.toInt(); version=value.toInt();
@ -125,6 +133,10 @@ QByteArray HttpCookie::toByteArray() const
if (httpOnly) { if (httpOnly) {
buffer.append("; HttpOnly"); buffer.append("; HttpOnly");
} }
if (!sameSite.isEmpty()) {
buffer.append("; SameSite=");
buffer.append(sameSite);
}
buffer.append("; Version="); buffer.append("; Version=");
buffer.append(QByteArray::number(version)); buffer.append(QByteArray::number(version));
return buffer; return buffer;
@ -170,6 +182,11 @@ void HttpCookie::setHttpOnly(const bool httpOnly)
this->httpOnly=httpOnly; this->httpOnly=httpOnly;
} }
void HttpCookie::setSameSite(const QByteArray sameSite)
{
this->sameSite=sameSite;
}
QByteArray HttpCookie::getName() const QByteArray HttpCookie::getName() const
{ {
return name; return name;
@ -210,6 +227,11 @@ bool HttpCookie::getHttpOnly() const
return httpOnly; return httpOnly;
} }
QByteArray HttpCookie::getSameSite() const
{
return sameSite;
}
int HttpCookie::getVersion() const int HttpCookie::getVersion() const
{ {
return version; return version;

View File

@ -13,9 +13,8 @@
namespace stefanfrings { namespace stefanfrings {
/** /**
HTTP cookie as defined in RFC 2109. This class can also parse HTTP cookie as defined in RFC 2109.
RFC 2965 cookies, but skips fields that are not defined in RFC Supports some additional attributes of RFC6265bis.
2109.
*/ */
class DECLSPEC HttpCookie class DECLSPEC HttpCookie
@ -35,11 +34,13 @@ public:
@param domain Optional domain for that the cookie will be sent. Defaults to the current domain @param domain Optional domain for that the cookie will be sent. Defaults to the current domain
@param secure If true, the cookie will be sent by the browser to the server only on secure connections @param secure If true, the cookie will be sent by the browser to the server only on secure connections
@param httpOnly If true, the browser does not allow client-side scripts to access the cookie @param httpOnly If true, the browser does not allow client-side scripts to access the cookie
@param sameSite Declare if the cookie can only be read by the same site, which is a stronger
restriction than the domain. Allowed values: "Lax" and "Strict".
*/ */
HttpCookie(const QByteArray name, const QByteArray value, const int maxAge, HttpCookie(const QByteArray name, const QByteArray value, const int maxAge,
const QByteArray path="/", const QByteArray comment=QByteArray(), const QByteArray path="/", const QByteArray comment=QByteArray(),
const QByteArray domain=QByteArray(), const bool secure=false, const QByteArray domain=QByteArray(), const bool secure=false,
const bool httpOnly=false); const bool httpOnly=false, const QByteArray sameSite=QByteArray());
/** /**
Create a cookie from a string. Create a cookie from a string.
@ -77,9 +78,15 @@ public:
/** Set secure mode, so that the cookie will be sent by the browser to the server only on secure connections */ /** Set secure mode, so that the cookie will be sent by the browser to the server only on secure connections */
void setSecure(const bool secure); void setSecure(const bool secure);
/** Set HTTP-only mode, so that he browser does not allow client-side scripts to access the cookie */ /** Set HTTP-only mode, so that the browser does not allow client-side scripts to access the cookie */
void setHttpOnly(const bool httpOnly); void setHttpOnly(const bool httpOnly);
/**
* Set same-site mode, so that the browser does not allow other web sites to access the cookie.
* Allowed values: "Lax" and "Strict".
*/
void setSameSite(const QByteArray sameSite);
/** Get the name of this cookie */ /** Get the name of this cookie */
QByteArray getName() const; QByteArray getName() const;
@ -104,6 +111,9 @@ public:
/** Get the HTTP-only flag of this cookie */ /** Get the HTTP-only flag of this cookie */
bool getHttpOnly() const; bool getHttpOnly() const;
/** Get the same-site flag of this cookie */
QByteArray getSameSite() const;
/** Returns always 1 */ /** Returns always 1 */
int getVersion() const; int getVersion() const;
@ -117,6 +127,7 @@ private:
QByteArray path; QByteArray path;
bool secure; bool secure;
bool httpOnly; bool httpOnly;
QByteArray sameSite;
int version; int version;
}; };

View File

@ -2,6 +2,6 @@
const char* getQtWebAppLibVersion() const char* getQtWebAppLibVersion()
{ {
return "1.7.11"; return "1.8.3";
} }

View File

@ -23,6 +23,5 @@
/** Get the library version number */ /** Get the library version number */
DECLSPEC const char* getQtWebAppLibVersion(); DECLSPEC const char* getQtWebAppLibVersion();
#endif // HTTPGLOBAL_H #endif // HTTPGLOBAL_H

View File

@ -83,7 +83,7 @@ void HttpListener::incomingConnection(tSocketDescriptor socketDescriptor) {
qDebug("HttpListener: Too many incoming connections"); qDebug("HttpListener: Too many incoming connections");
QTcpSocket* socket=new QTcpSocket(this); QTcpSocket* socket=new QTcpSocket(this);
socket->setSocketDescriptor(socketDescriptor); socket->setSocketDescriptor(socketDescriptor);
connect(socket, &QAbstractSocket::disconnected, socket, &QObject::deleteLater); connect(socket, SIGNAL(disconnected()), socket, SLOT(deleteLater()));
socket->write("HTTP/1.1 503 too many connections\r\nConnection: close\r\n\r\nToo many connections\r\n"); socket->write("HTTP/1.1 503 too many connections\r\nConnection: close\r\n\r\nToo many connections\r\n");
socket->disconnectFromHost(); socket->disconnectFromHost();
} }

View File

@ -482,7 +482,8 @@ void HttpRequest::parseMultiPartFile()
parameters.insert(fieldName,fileName); parameters.insert(fieldName,fileName);
qDebug("HttpRequest: set parameter %s=%s",fieldName.data(),fileName.data()); qDebug("HttpRequest: set parameter %s=%s",fieldName.data(),fileName.data());
uploadedFiles.insert(fieldName,uploadedFile); uploadedFiles.insert(fieldName,uploadedFile);
qDebug("HttpRequest: uploaded file size is %lli",uploadedFile->size()); long int fileSize=(long int) uploadedFile->size();
qDebug("HttpRequest: uploaded file size is %li",fileSize);
} }
else else
{ {

View File

@ -43,7 +43,6 @@ public:
*/ */
HttpSession& operator= (const HttpSession& other); HttpSession& operator= (const HttpSession& other);
/** /**
Destructor. Detaches from the shared data. Destructor. Detaches from the shared data.
*/ */
@ -54,7 +53,7 @@ public:
/** /**
Null sessions cannot store data. All calls to set() and remove() Null sessions cannot store data. All calls to set() and remove()
do not have any effect.This method is thread safe. do not have any effect. This method is thread safe.
*/ */
bool isNull() const; bool isNull() const;

View File

@ -13,7 +13,7 @@ HttpSessionStore::HttpSessionStore(const QSettings *settings, QObject* parent)
:QObject(parent) :QObject(parent)
{ {
this->settings=settings; this->settings=settings;
connect(&cleanupTimer,&QTimer::timeout,this,&HttpSessionStore::sessionTimerEvent); connect(&cleanupTimer,SIGNAL(timeout()),this,SLOT(sessionTimerEvent()));
cleanupTimer.start(60000); cleanupTimer.start(60000);
cookieName=settings->value("cookieName","sessionid").toByteArray(); cookieName=settings->value("cookieName","sessionid").toByteArray();
expirationTime=settings->value("expirationTime",3600000).toInt(); expirationTime=settings->value("expirationTime",3600000).toInt();
@ -64,7 +64,8 @@ HttpSession HttpSessionStore::getSession(HttpRequest& request, HttpResponse& res
QByteArray cookiePath=settings->value("cookiePath").toByteArray(); QByteArray cookiePath=settings->value("cookiePath").toByteArray();
QByteArray cookieComment=settings->value("cookieComment").toByteArray(); QByteArray cookieComment=settings->value("cookieComment").toByteArray();
QByteArray cookieDomain=settings->value("cookieDomain").toByteArray(); QByteArray cookieDomain=settings->value("cookieDomain").toByteArray();
response.setCookie(HttpCookie(cookieName,session.getId(),expirationTime/1000,cookiePath,cookieComment,cookieDomain)); response.setCookie(HttpCookie(cookieName,session.getId(),expirationTime/1000,
cookiePath,cookieComment,cookieDomain,false,false,"Lax"));
session.setLastAccess(); session.setLastAccess();
return session; return session;
} }
@ -79,7 +80,8 @@ HttpSession HttpSessionStore::getSession(HttpRequest& request, HttpResponse& res
HttpSession session(true); HttpSession session(true);
qDebug("HttpSessionStore: create new session with ID %s",session.getId().data()); qDebug("HttpSessionStore: create new session with ID %s",session.getId().data());
sessions.insert(session.getId(),session); sessions.insert(session.getId(),session);
response.setCookie(HttpCookie(cookieName,session.getId(),expirationTime/1000,cookiePath,cookieComment,cookieDomain)); response.setCookie(HttpCookie(cookieName,session.getId(),expirationTime/1000,
cookiePath,cookieComment,cookieDomain,false,false,"Lax"));
mutex.unlock(); mutex.unlock();
return session; return session;
} }
@ -111,6 +113,7 @@ void HttpSessionStore::sessionTimerEvent()
if (now-lastAccess>expirationTime) if (now-lastAccess>expirationTime)
{ {
qDebug("HttpSessionStore: session %s expired",session.getId().data()); qDebug("HttpSessionStore: session %s expired",session.getId().data());
emit sessionDeleted(session.getId());
sessions.erase(prev); sessions.erase(prev);
} }
} }
@ -122,6 +125,7 @@ void HttpSessionStore::sessionTimerEvent()
void HttpSessionStore::removeSession(HttpSession session) void HttpSessionStore::removeSession(HttpSession session)
{ {
mutex.lock(); mutex.lock();
emit sessionDeleted(session.getId());
sessions.remove(session.getId()); sessions.remove(session.getId());
mutex.unlock(); mutex.unlock();
} }

View File

@ -112,6 +112,14 @@ private slots:
/** Called every minute to cleanup expired sessions. */ /** Called every minute to cleanup expired sessions. */
void sessionTimerEvent(); void sessionTimerEvent();
signals:
/**
Emitted when the session is deleted.
@param sessionId The ID number of the session.
*/
void sessionDeleted(const QByteArray& sessionId);
}; };
} // end of namespace } // end of namespace

View File

@ -33,7 +33,8 @@ StaticFileController::StaticFileController(const QSettings *settings, QObject* p
maxCachedFileSize=settings->value("maxCachedFileSize","65536").toInt(); maxCachedFileSize=settings->value("maxCachedFileSize","65536").toInt();
cache.setMaxCost(settings->value("cacheSize","1000000").toInt()); cache.setMaxCost(settings->value("cacheSize","1000000").toInt());
cacheTimeout=settings->value("cacheTime","60000").toInt(); cacheTimeout=settings->value("cacheTime","60000").toInt();
qDebug("StaticFileController: cache timeout=%i, size=%i",cacheTimeout,cache.maxCost()); long int cacheMaxCost=(long int)cache.maxCost();
qDebug("StaticFileController: cache timeout=%i, size=%li",cacheTimeout,cacheMaxCost);
} }

View File

@ -7,7 +7,6 @@
#define TEMPLATE_H #define TEMPLATE_H
#include <QString> #include <QString>
#include <QRegExp>
#include <QIODevice> #include <QIODevice>
#include <QTextCodec> #include <QTextCodec>
#include <QFile> #include <QFile>

View File

@ -10,7 +10,8 @@ TemplateCache::TemplateCache(const QSettings* settings, QObject* parent)
{ {
cache.setMaxCost(settings->value("cacheSize","1000000").toInt()); cache.setMaxCost(settings->value("cacheSize","1000000").toInt());
cacheTimeout=settings->value("cacheTime","60000").toInt(); cacheTimeout=settings->value("cacheTime","60000").toInt();
qDebug("TemplateCache: timeout=%i, size=%i",cacheTimeout,cache.maxCost()); long int cacheMaxCost=(long int)cache.maxCost();
qDebug("TemplateCache: timeout=%i, size=%li",cacheTimeout,cacheMaxCost);
} }
QString TemplateCache::tryFile(const QString localizedName) QString TemplateCache::tryFile(const QString localizedName)

View File

@ -1,6 +1,10 @@
INCLUDEPATH += $$PWD INCLUDEPATH += $$PWD
DEPENDPATH += $$PWD DEPENDPATH += $$PWD
greaterThan(QT_VERSION,6) {
QT += core5compat
}
HEADERS += $$PWD/templateglobal.h HEADERS += $$PWD/templateglobal.h
HEADERS += $$PWD/template.h HEADERS += $$PWD/template.h
HEADERS += $$PWD/templateloader.h HEADERS += $$PWD/templateloader.h

View File

@ -9,6 +9,12 @@
#include <QStringList> #include <QStringList>
#include <QDir> #include <QDir>
#include <QSet> #include <QSet>
#include <QTextStream>
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
#include <QRegularExpression>
#else
#include <QRegExp>
#endif
using namespace stefanfrings; using namespace stefanfrings;
@ -36,7 +42,7 @@ TemplateLoader::TemplateLoader(const QSettings *settings, QObject *parent)
{ {
textCodec=QTextCodec::codecForName(encoding.toLocal8Bit()); textCodec=QTextCodec::codecForName(encoding.toLocal8Bit());
} }
qDebug("TemplateLoader: path=%s, codec=%s",qPrintable(templatePath),textCodec->name().data()); qDebug("TemplateLoader: path=%s, codec=%s",qPrintable(templatePath),qPrintable(encoding));
} }
TemplateLoader::~TemplateLoader() TemplateLoader::~TemplateLoader()
@ -67,13 +73,23 @@ QString TemplateLoader::tryFile(QString localizedName)
Template TemplateLoader::getTemplate(QString templateName, QString locales) Template TemplateLoader::getTemplate(QString templateName, QString locales)
{ {
QSet<QString> tried; // used to suppress duplicate attempts QSet<QString> tried; // used to suppress duplicate attempts
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
QStringList locs=locales.split(',',Qt::SkipEmptyParts);
#else
QStringList locs=locales.split(',',QString::SkipEmptyParts); QStringList locs=locales.split(',',QString::SkipEmptyParts);
#endif
// Search for exact match // Search for exact match
foreach (QString loc,locs) foreach (QString loc,locs)
{ {
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
loc.replace(QRegularExpression(";.*"),"");
#else
loc.replace(QRegExp(";.*"),""); loc.replace(QRegExp(";.*"),"");
#endif
loc.replace('-','_'); loc.replace('-','_');
QString localizedName=templateName+"-"+loc.trimmed(); QString localizedName=templateName+"-"+loc.trimmed();
if (!tried.contains(localizedName)) if (!tried.contains(localizedName))
{ {
@ -88,7 +104,11 @@ Template TemplateLoader::getTemplate(QString templateName, QString locales)
// Search for correct language but any country // Search for correct language but any country
foreach (QString loc,locs) foreach (QString loc,locs)
{ {
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
loc.replace(QRegularExpression("[;_-].*"),"");
#else
loc.replace(QRegExp("[;_-].*"),""); loc.replace(QRegExp("[;_-].*"),"");
#endif
QString localizedName=templateName+"-"+loc.trimmed(); QString localizedName=templateName+"-"+loc.trimmed();
if (!tried.contains(localizedName)) if (!tried.contains(localizedName))
{ {

View File

@ -8,8 +8,8 @@
#include <QString> #include <QString>
#include <QSettings> #include <QSettings>
#include <QTextCodec>
#include <QMutex> #include <QMutex>
#include <QTextCodec>
#include "templateglobal.h" #include "templateglobal.h"
#include "template.h" #include "template.h"