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
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
28.12.2019
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++.
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.
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
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
project. This does not depend on the shared library.
project, the preferred method.
Demo2 shows how to link against 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
http://stefanfrings.de

View File

@ -28,16 +28,16 @@ HttpConnectionHandler::HttpConnectionHandler(const QSettings *settings, HttpRequ
readTimer.setSingleShot(true);
// Create TCP or SSL socket
createSocket();
createSocket();
socket->moveToThread(thread);
// Connect signals
connect(socket, &QIODevice::readyRead, this, &HttpConnectionHandler::read);
connect(socket, &QAbstractSocket::disconnected, this, &HttpConnectionHandler::disconnected);
connect(&readTimer, &QTimer::timeout, this, &HttpConnectionHandler::readTimeout);
connect(thread, &QThread::finished, this, &HttpConnectionHandler::thread_done);
connect(socket, SIGNAL(readyRead()), SLOT(read()));
connect(socket, SIGNAL(disconnected()), SLOT(disconnected()));
connect(&readTimer, SIGNAL(timeout()), SLOT(readTimeout()));
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 {
/** 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;
#else
typedef int tSocketDescriptor;

View File

@ -18,7 +18,7 @@ HttpConnectionHandlerPool::HttpConnectionHandlerPool(const QSettings *settings,
this->sslConfiguration=NULL;
loadSslConfig();
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;
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
}
}
@ -140,7 +141,7 @@ void HttpConnectionHandlerPool::loadSslConfig()
sslConfiguration->setLocalCertificate(certificate);
sslConfiguration->setPrivateKey(sslKey);
sslConfiguration->setPeerVerifyMode(QSslSocket::VerifyNone);
sslConfiguration->setProtocol(QSsl::TlsV1SslV3);
sslConfiguration->setProtocol(QSsl::AnyProtocol);
qDebug("HttpConnectionHandlerPool: SSL settings loaded");
#endif

View File

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

View File

@ -13,9 +13,8 @@
namespace stefanfrings {
/**
HTTP cookie as defined in RFC 2109. This class can also parse
RFC 2965 cookies, but skips fields that are not defined in RFC
2109.
HTTP cookie as defined in RFC 2109.
Supports some additional attributes of RFC6265bis.
*/
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 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 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,
const QByteArray path="/", const QByteArray comment=QByteArray(),
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.
@ -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 */
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);
/**
* 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 */
QByteArray getName() const;
@ -104,6 +111,9 @@ public:
/** Get the HTTP-only flag of this cookie */
bool getHttpOnly() const;
/** Get the same-site flag of this cookie */
QByteArray getSameSite() const;
/** Returns always 1 */
int getVersion() const;
@ -117,6 +127,7 @@ private:
QByteArray path;
bool secure;
bool httpOnly;
QByteArray sameSite;
int version;
};

View File

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

View File

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

View File

@ -83,7 +83,7 @@ void HttpListener::incomingConnection(tSocketDescriptor socketDescriptor) {
qDebug("HttpListener: Too many incoming connections");
QTcpSocket* socket=new QTcpSocket(this);
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->disconnectFromHost();
}

View File

@ -482,7 +482,8 @@ void HttpRequest::parseMultiPartFile()
parameters.insert(fieldName,fileName);
qDebug("HttpRequest: set parameter %s=%s",fieldName.data(),fileName.data());
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
{

View File

@ -43,7 +43,6 @@ public:
*/
HttpSession& operator= (const HttpSession& other);
/**
Destructor. Detaches from the shared data.
*/
@ -54,7 +53,7 @@ public:
/**
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;

View File

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

View File

@ -112,6 +112,14 @@ private slots:
/** Called every minute to cleanup expired sessions. */
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

View File

@ -33,7 +33,8 @@ StaticFileController::StaticFileController(const QSettings *settings, QObject* p
maxCachedFileSize=settings->value("maxCachedFileSize","65536").toInt();
cache.setMaxCost(settings->value("cacheSize","1000000").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
#include <QString>
#include <QRegExp>
#include <QIODevice>
#include <QTextCodec>
#include <QFile>

View File

@ -10,7 +10,8 @@ TemplateCache::TemplateCache(const QSettings* settings, QObject* parent)
{
cache.setMaxCost(settings->value("cacheSize","1000000").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)

View File

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

View File

@ -9,6 +9,12 @@
#include <QStringList>
#include <QDir>
#include <QSet>
#include <QTextStream>
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
#include <QRegularExpression>
#else
#include <QRegExp>
#endif
using namespace stefanfrings;
@ -35,8 +41,8 @@ TemplateLoader::TemplateLoader(const QSettings *settings, QObject *parent)
else
{
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()
@ -67,13 +73,23 @@ QString TemplateLoader::tryFile(QString localizedName)
Template TemplateLoader::getTemplate(QString templateName, QString locales)
{
QSet<QString> tried; // used to suppress duplicate attempts
QStringList locs=locales.split(',',QString::SkipEmptyParts);
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
QStringList locs=locales.split(',',Qt::SkipEmptyParts);
#else
QStringList locs=locales.split(',',QString::SkipEmptyParts);
#endif
// Search for exact match
foreach (QString loc,locs)
{
loc.replace(QRegExp(";.*"),"");
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
loc.replace(QRegularExpression(";.*"),"");
#else
loc.replace(QRegExp(";.*"),"");
#endif
loc.replace('-','_');
QString localizedName=templateName+"-"+loc.trimmed();
if (!tried.contains(localizedName))
{
@ -88,7 +104,11 @@ Template TemplateLoader::getTemplate(QString templateName, QString locales)
// Search for correct language but any country
foreach (QString loc,locs)
{
loc.replace(QRegExp("[;_-].*"),"");
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
loc.replace(QRegularExpression("[;_-].*"),"");
#else
loc.replace(QRegExp("[;_-].*"),"");
#endif
QString localizedName=templateName+"-"+loc.trimmed();
if (!tried.contains(localizedName))
{

View File

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