mirror of
https://github.com/YACReader/yacreader
synced 2025-07-20 05:54:39 -04:00
primera versi?n con servidor http incorporado
This commit is contained in:
@ -6,10 +6,11 @@ TEMPLATE = app
|
|||||||
TARGET =
|
TARGET =
|
||||||
DEPENDPATH += .
|
DEPENDPATH += .
|
||||||
INCLUDEPATH += .
|
INCLUDEPATH += .
|
||||||
INCLUDEPATH += ../common
|
INCLUDEPATH += ../common \
|
||||||
|
./server
|
||||||
CONFIG += release
|
CONFIG += release
|
||||||
CONFIG -= flat
|
CONFIG -= flat
|
||||||
QT += sql
|
QT += sql network
|
||||||
|
|
||||||
# Input
|
# Input
|
||||||
HEADERS += comic_flow.h \
|
HEADERS += comic_flow.h \
|
||||||
@ -45,6 +46,10 @@ SOURCES += comic_flow.cpp \
|
|||||||
../common/qnaturalsorting.cpp \
|
../common/qnaturalsorting.cpp \
|
||||||
data_base_management.cpp \
|
data_base_management.cpp \
|
||||||
bundle_creator.cpp
|
bundle_creator.cpp
|
||||||
|
|
||||||
|
include(./server/server.pri)
|
||||||
|
|
||||||
|
|
||||||
RESOURCES += images.qrc files.qrc
|
RESOURCES += images.qrc files.qrc
|
||||||
RC_FILE = icon.rc
|
RC_FILE = icon.rc
|
||||||
|
|
||||||
@ -52,3 +57,4 @@ TRANSLATIONS = yacreaderlibrary_es.ts
|
|||||||
|
|
||||||
Release:DESTDIR = ../release
|
Release:DESTDIR = ../release
|
||||||
Debug:DESTDIR = ../debug
|
Debug:DESTDIR = ../debug
|
||||||
|
|
||||||
|
13
YACReaderLibrary/bundle_creator.cpp
Normal file
13
YACReaderLibrary/bundle_creator.cpp
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
#include "bundle_creator.h"
|
||||||
|
|
||||||
|
|
||||||
|
BundleCreator::BundleCreator(void)
|
||||||
|
:QObject()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
BundleCreator::~BundleCreator(void)
|
||||||
|
{
|
||||||
|
}
|
14
YACReaderLibrary/bundle_creator.h
Normal file
14
YACReaderLibrary/bundle_creator.h
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#ifndef __BUNDLE_CREATOR_H
|
||||||
|
#define __BUNDLE_CREATOR_H
|
||||||
|
|
||||||
|
#include <QtCore>
|
||||||
|
|
||||||
|
class BundleCreator : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
BundleCreator(void);
|
||||||
|
~BundleCreator(void);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
@ -1,7 +1,7 @@
|
|||||||
#include "library_window.h"
|
#include "library_window.h"
|
||||||
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
|
#include "startup.h"
|
||||||
#define PICTUREFLOW_QT4 1
|
#define PICTUREFLOW_QT4 1
|
||||||
|
|
||||||
int main( int argc, char ** argv )
|
int main( int argc, char ** argv )
|
||||||
@ -18,5 +18,8 @@ int main( int argc, char ** argv )
|
|||||||
mw->resize(800,480);
|
mw->resize(800,480);
|
||||||
mw->showMaximized();
|
mw->showMaximized();
|
||||||
|
|
||||||
|
Startup * s = new Startup();
|
||||||
|
s->start();
|
||||||
|
|
||||||
return app.exec();
|
return app.exec();
|
||||||
}
|
}
|
||||||
|
62
YACReaderLibrary/server/controllers/dumpcontroller.cpp
Normal file
62
YACReaderLibrary/server/controllers/dumpcontroller.cpp
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@author Stefan Frings
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "dumpcontroller.h"
|
||||||
|
#include <QVariant>
|
||||||
|
#include <QDateTime>
|
||||||
|
|
||||||
|
DumpController::DumpController(){}
|
||||||
|
|
||||||
|
void DumpController::service(HttpRequest& request, HttpResponse& response) {
|
||||||
|
|
||||||
|
response.setHeader("Content-Type", "text/html; charset=ISO-8859-1");
|
||||||
|
response.setCookie(HttpCookie("firstCookie","hello",600));
|
||||||
|
response.setCookie(HttpCookie("secondCookie","world",600));
|
||||||
|
|
||||||
|
QByteArray body("<html><body>");
|
||||||
|
body.append("<b>Request:</b>");
|
||||||
|
body.append("<br>Method: ");
|
||||||
|
body.append(request.getMethod());
|
||||||
|
body.append("<br>Path: ");
|
||||||
|
body.append(request.getPath());
|
||||||
|
body.append("<br>Version: ");
|
||||||
|
body.append(request.getVersion());
|
||||||
|
|
||||||
|
body.append("<p><b>Headers:</b>");
|
||||||
|
QMapIterator<QByteArray,QByteArray> i(request.getHeaderMap());
|
||||||
|
while (i.hasNext()) {
|
||||||
|
i.next();
|
||||||
|
body.append("<br>");
|
||||||
|
body.append(i.key());
|
||||||
|
body.append("=");
|
||||||
|
body.append(i.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
body.append("<p><b>Parameters:</b>");
|
||||||
|
i=QMapIterator<QByteArray,QByteArray>(request.getParameterMap());
|
||||||
|
while (i.hasNext()) {
|
||||||
|
i.next();
|
||||||
|
body.append("<br>");
|
||||||
|
body.append(i.key());
|
||||||
|
body.append("=");
|
||||||
|
body.append(i.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
body.append("<p><b>Cookies:</b>");
|
||||||
|
i=QMapIterator<QByteArray,QByteArray>(request.getCookieMap());
|
||||||
|
while (i.hasNext()) {
|
||||||
|
i.next();
|
||||||
|
body.append("<br>");
|
||||||
|
body.append(i.key());
|
||||||
|
body.append("=");
|
||||||
|
body.append(i.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
body.append("<p><b>Body:</b><br>");
|
||||||
|
body.append(request.getBody());
|
||||||
|
|
||||||
|
body.append("</body></html>");
|
||||||
|
response.write(body,true);
|
||||||
|
}
|
29
YACReaderLibrary/server/controllers/dumpcontroller.h
Normal file
29
YACReaderLibrary/server/controllers/dumpcontroller.h
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@author Stefan Frings
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef DUMPCONTROLLER_H
|
||||||
|
#define DUMPCONTROLLER_H
|
||||||
|
|
||||||
|
#include "httprequest.h"
|
||||||
|
#include "httpresponse.h"
|
||||||
|
#include "httprequesthandler.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
This controller dumps the received HTTP request in the response.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class DumpController : public HttpRequestHandler {
|
||||||
|
Q_OBJECT
|
||||||
|
Q_DISABLE_COPY(DumpController);
|
||||||
|
public:
|
||||||
|
|
||||||
|
/** Constructor */
|
||||||
|
DumpController();
|
||||||
|
|
||||||
|
/** Generates the response */
|
||||||
|
void service(HttpRequest& request, HttpResponse& response);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // DUMPCONTROLLER_H
|
38
YACReaderLibrary/server/controllers/fileuploadcontroller.cpp
Normal file
38
YACReaderLibrary/server/controllers/fileuploadcontroller.cpp
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@author Stefan Frings
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "fileuploadcontroller.h"
|
||||||
|
|
||||||
|
FileUploadController::FileUploadController() {}
|
||||||
|
|
||||||
|
void FileUploadController::service(HttpRequest& request, HttpResponse& response) {
|
||||||
|
|
||||||
|
if (request.getParameter("action")=="show") {
|
||||||
|
response.setHeader("Content-Type", "image/jpeg");
|
||||||
|
QTemporaryFile* file=request.getUploadedFile("file1");
|
||||||
|
if (file) {
|
||||||
|
while (!file->atEnd() && !file->error()) {
|
||||||
|
QByteArray buffer=file->read(65536);
|
||||||
|
response.write(buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
response.write("upload failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
response.setHeader("Content-Type", "text/html; charset=ISO-8859-1");
|
||||||
|
response.write("<html><body>");
|
||||||
|
response.write("Upload a JPEG image file<p>");
|
||||||
|
response.write("<form method=\"post\" enctype=\"multipart/form-data\">");
|
||||||
|
response.write(" <input type=\"hidden\" name=\"action\" value=\"show\">");
|
||||||
|
response.write(" File: <input type=\"file\" name=\"file1\"><br>");
|
||||||
|
response.write(" <input type=\"submit\">");
|
||||||
|
response.write("</form>");
|
||||||
|
response.write("</body></html>",true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
30
YACReaderLibrary/server/controllers/fileuploadcontroller.h
Normal file
30
YACReaderLibrary/server/controllers/fileuploadcontroller.h
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@author Stefan Frings
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef FILEUPLOADCONTROLLER_H
|
||||||
|
#define FILEUPLOADCONTROLLER_H
|
||||||
|
|
||||||
|
#include "httprequest.h"
|
||||||
|
#include "httpresponse.h"
|
||||||
|
#include "httprequesthandler.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
This controller displays a HTML form for file upload and recieved the file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
class FileUploadController : public HttpRequestHandler {
|
||||||
|
Q_OBJECT
|
||||||
|
Q_DISABLE_COPY(FileUploadController);
|
||||||
|
public:
|
||||||
|
|
||||||
|
/** Constructor */
|
||||||
|
FileUploadController();
|
||||||
|
|
||||||
|
/** Generates the response */
|
||||||
|
void service(HttpRequest& request, HttpResponse& response);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // FILEUPLOADCONTROLLER_H
|
33
YACReaderLibrary/server/controllers/formcontroller.cpp
Normal file
33
YACReaderLibrary/server/controllers/formcontroller.cpp
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@author Stefan Frings
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "formcontroller.h"
|
||||||
|
|
||||||
|
FormController::FormController() {}
|
||||||
|
|
||||||
|
void FormController::service(HttpRequest& request, HttpResponse& response) {
|
||||||
|
|
||||||
|
response.setHeader("Content-Type", "text/html; charset=ISO-8859-1");
|
||||||
|
|
||||||
|
if (request.getParameter("action")=="show") {
|
||||||
|
response.write("<html><body>");
|
||||||
|
response.write("Name = ");
|
||||||
|
response.write(request.getParameter("name"));
|
||||||
|
response.write("<br>City = ");
|
||||||
|
response.write(request.getParameter("city"));
|
||||||
|
response.write("</body></html>",true);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
response.write("<html><body>");
|
||||||
|
response.write("<form method=\"post\">");
|
||||||
|
response.write(" <input type=\"hidden\" name=\"action\" value=\"show\">");
|
||||||
|
response.write(" Name: <input type=\"text\" name=\"name\"><br>");
|
||||||
|
response.write(" City: <input type=\"text\" name=\"city\"><br>");
|
||||||
|
response.write(" <input type=\"submit\">");
|
||||||
|
response.write("</form>");
|
||||||
|
response.write("</body></html>",true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
30
YACReaderLibrary/server/controllers/formcontroller.h
Normal file
30
YACReaderLibrary/server/controllers/formcontroller.h
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@author Stefan Frings
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef FORMCONTROLLER_H
|
||||||
|
#define FORMCONTROLLER_H
|
||||||
|
|
||||||
|
#include "httprequest.h"
|
||||||
|
#include "httpresponse.h"
|
||||||
|
#include "httprequesthandler.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
This controller displays a HTML form and dumps the submitted input.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
class FormController : public HttpRequestHandler {
|
||||||
|
Q_OBJECT
|
||||||
|
Q_DISABLE_COPY(FormController);
|
||||||
|
public:
|
||||||
|
|
||||||
|
/** Constructor */
|
||||||
|
FormController();
|
||||||
|
|
||||||
|
/** Generates the response */
|
||||||
|
void service(HttpRequest& request, HttpResponse& response);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // FORMCONTROLLER_H
|
31
YACReaderLibrary/server/controllers/sessioncontroller.cpp
Normal file
31
YACReaderLibrary/server/controllers/sessioncontroller.cpp
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@author Stefan Frings
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "sessioncontroller.h"
|
||||||
|
#include "../static.h"
|
||||||
|
#include <QVariant>
|
||||||
|
#include <QDateTime>
|
||||||
|
|
||||||
|
SessionController::SessionController(){}
|
||||||
|
|
||||||
|
void SessionController::service(HttpRequest& request, HttpResponse& response) {
|
||||||
|
|
||||||
|
response.setHeader("Content-Type", "text/html; charset=ISO-8859-1");
|
||||||
|
|
||||||
|
// Get current session, or create a new one
|
||||||
|
HttpSession session=Static::sessionStore->getSession(request,response);
|
||||||
|
if (!session.contains("startTime")) {
|
||||||
|
response.write("<html><body>New session started. Reload this page now.</body></html>");
|
||||||
|
session.set("startTime",QDateTime::currentDateTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
QDateTime startTime=session.get("startTime").toDateTime();
|
||||||
|
response.write("<html><body>Your session started ");
|
||||||
|
response.write(startTime.toString().toLatin1());
|
||||||
|
response.write("</body></html>");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
29
YACReaderLibrary/server/controllers/sessioncontroller.h
Normal file
29
YACReaderLibrary/server/controllers/sessioncontroller.h
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@author Stefan Frings
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef SESSIONCONTROLLER_H
|
||||||
|
#define SESSIONCONTROLLER_H
|
||||||
|
|
||||||
|
#include "httprequest.h"
|
||||||
|
#include "httpresponse.h"
|
||||||
|
#include "httprequesthandler.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
This controller demonstrates how to use sessions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class SessionController : public HttpRequestHandler {
|
||||||
|
Q_OBJECT
|
||||||
|
Q_DISABLE_COPY(SessionController);
|
||||||
|
public:
|
||||||
|
|
||||||
|
/** Constructor */
|
||||||
|
SessionController();
|
||||||
|
|
||||||
|
/** Generates the response */
|
||||||
|
void service(HttpRequest& request, HttpResponse& response);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // SESSIONCONTROLLER_H
|
31
YACReaderLibrary/server/controllers/templatecontroller.cpp
Normal file
31
YACReaderLibrary/server/controllers/templatecontroller.cpp
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@author Stefan Frings
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "templatecontroller.h"
|
||||||
|
#include "template.h"
|
||||||
|
#include "../static.h"
|
||||||
|
|
||||||
|
TemplateController::TemplateController(){}
|
||||||
|
|
||||||
|
void TemplateController::service(HttpRequest& request, HttpResponse& response) {
|
||||||
|
|
||||||
|
response.setHeader("Content-Type", "text/html; charset=ISO-8859-1");
|
||||||
|
|
||||||
|
Template t=Static::templateLoader->getTemplate("demo",request.getHeader("Accept-Language"));
|
||||||
|
t.enableWarnings();
|
||||||
|
t.setVariable("path",request.getPath());
|
||||||
|
QMap<QByteArray,QByteArray> headers=request.getHeaderMap();
|
||||||
|
QMapIterator<QByteArray,QByteArray> iterator(headers);
|
||||||
|
t.loop("header",headers.size());
|
||||||
|
int i=0;
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
iterator.next();
|
||||||
|
t.setVariable(QString("header%1.name").arg(i),QString(iterator.key()));
|
||||||
|
t.setVariable(QString("header%1.value").arg(i),QString(iterator.value()));
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
|
||||||
|
response.write(t.toLatin1(),true);
|
||||||
|
}
|
30
YACReaderLibrary/server/controllers/templatecontroller.h
Normal file
30
YACReaderLibrary/server/controllers/templatecontroller.h
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@author Stefan Frings
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef TEMPLATECONTROLLER_H
|
||||||
|
#define TEMPLATECONTROLLER_H
|
||||||
|
|
||||||
|
#include "httprequest.h"
|
||||||
|
#include "httpresponse.h"
|
||||||
|
#include "httprequesthandler.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
This controller generates a website using the template engine.
|
||||||
|
It generates a Latin1 (ISO-8859-1) encoded website from a UTF-8 encoded template file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class TemplateController : public HttpRequestHandler {
|
||||||
|
Q_OBJECT
|
||||||
|
Q_DISABLE_COPY(TemplateController);
|
||||||
|
public:
|
||||||
|
|
||||||
|
/** Constructor */
|
||||||
|
TemplateController();
|
||||||
|
|
||||||
|
/** Generates the response */
|
||||||
|
void service(HttpRequest& request, HttpResponse& response);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // TEMPLATECONTROLLER_H
|
4
YACReaderLibrary/server/documentcache.h
Normal file
4
YACReaderLibrary/server/documentcache.h
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
#ifndef DOCUMENTCACHE_H
|
||||||
|
#define DOCUMENTCACHE_H
|
||||||
|
|
||||||
|
#endif // DOCUMENTCACHE_H
|
12
YACReaderLibrary/server/lib/bfHttpServer/bfHttpServer.pri
Normal file
12
YACReaderLibrary/server/lib/bfHttpServer/bfHttpServer.pri
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
INCLUDEPATH += $$PWD
|
||||||
|
DEPENDPATH += $$PWD
|
||||||
|
|
||||||
|
HEADERS += $$PWD/httplistener.h $$PWD/httpconnectionhandler.h $$PWD/httpconnectionhandlerpool.h $$PWD/httprequest.h $$PWD/httpresponse.h $$PWD/httpcookie.h $$PWD/httprequesthandler.h
|
||||||
|
HEADERS += $$PWD/httpsession.h $$PWD/httpsessionstore.h
|
||||||
|
HEADERS += $$PWD/staticfilecontroller.h
|
||||||
|
|
||||||
|
SOURCES += $$PWD/httplistener.cpp $$PWD/httpconnectionhandler.cpp $$PWD/httpconnectionhandlerpool.cpp $$PWD/httprequest.cpp $$PWD/httpresponse.cpp $$PWD/httpcookie.cpp $$PWD/httprequesthandler.cpp
|
||||||
|
SOURCES += $$PWD/httpsession.cpp $$PWD/httpsessionstore.cpp
|
||||||
|
SOURCES += $$PWD/staticfilecontroller.cpp
|
||||||
|
|
||||||
|
OTHER_FILES += $$PWD/../doc/readme.txt
|
@ -0,0 +1,154 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@author Stefan Frings
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "httpconnectionhandler.h"
|
||||||
|
#include "httpresponse.h"
|
||||||
|
#include <QTimer>
|
||||||
|
#include <QCoreApplication>
|
||||||
|
|
||||||
|
HttpConnectionHandler::HttpConnectionHandler(QSettings* settings, HttpRequestHandler* requestHandler)
|
||||||
|
: QThread()
|
||||||
|
{
|
||||||
|
Q_ASSERT(settings!=0);
|
||||||
|
Q_ASSERT(requestHandler!=0);
|
||||||
|
this->settings=settings;
|
||||||
|
this->requestHandler=requestHandler;
|
||||||
|
currentRequest=0;
|
||||||
|
busy = false; // pdiener: it is not busy if it is new
|
||||||
|
// execute signals in my own thread
|
||||||
|
moveToThread(this);
|
||||||
|
socket.moveToThread(this);
|
||||||
|
readTimer.moveToThread(this);
|
||||||
|
connect(&socket, SIGNAL(readyRead()), SLOT(read()));
|
||||||
|
connect(&socket, SIGNAL(disconnected()), SLOT(disconnected()));
|
||||||
|
connect(&readTimer, SIGNAL(timeout()), SLOT(readTimeout()));
|
||||||
|
readTimer.setSingleShot(true);
|
||||||
|
qDebug("HttpConnectionHandler (%p): constructed", this);
|
||||||
|
this->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
HttpConnectionHandler::~HttpConnectionHandler() {
|
||||||
|
qDebug("HttpConnectionHandler (%p): destroyed", this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void HttpConnectionHandler::run() {
|
||||||
|
qDebug("HttpConnectionHandler (%p): thread started", this);
|
||||||
|
try {
|
||||||
|
exec();
|
||||||
|
}
|
||||||
|
catch (...) {
|
||||||
|
qCritical("HttpConnectionHandler (%p): an uncatched exception occured in the thread",this);
|
||||||
|
}
|
||||||
|
qDebug("HttpConnectionHandler (%p): thread stopped", this);
|
||||||
|
// Change to the main thread, otherwise deleteLater() would not work
|
||||||
|
moveToThread(QCoreApplication::instance()->thread());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void HttpConnectionHandler::handleConnection(int socketDescriptor) {
|
||||||
|
qDebug("HttpConnectionHandler (%p): handle new connection", this);
|
||||||
|
busy = true;
|
||||||
|
Q_ASSERT(socket.isOpen()==false); // if not, then the handler is already busy
|
||||||
|
if (!socket.setSocketDescriptor(socketDescriptor)) {
|
||||||
|
qCritical("HttpConnectionHandler (%p): cannot initialize socket: %s", this,qPrintable(socket.errorString()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Start timer for read timeout
|
||||||
|
int readTimeout=settings->value("readTimeout",10000).toInt();
|
||||||
|
readTimer.start(readTimeout);
|
||||||
|
currentRequest=0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool HttpConnectionHandler::isBusy() {
|
||||||
|
//return socket.isOpen();
|
||||||
|
return busy; // pdiener: changed this from socket readout to bool variable
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpConnectionHandler::setBusy() {
|
||||||
|
this->busy = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void HttpConnectionHandler::readTimeout() {
|
||||||
|
qDebug("HttpConnectionHandler (%p): read timeout occured",this);
|
||||||
|
socket.write("HTTP/1.1 408 request timeout\r\nConnection: close\r\n\r\n408 request timeout\r\n");
|
||||||
|
socket.disconnectFromHost();
|
||||||
|
delete currentRequest;
|
||||||
|
currentRequest=0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void HttpConnectionHandler::disconnected() {
|
||||||
|
qDebug("HttpConnectionHandler (%p): disconnected", this);
|
||||||
|
socket.close();
|
||||||
|
delete currentRequest;
|
||||||
|
currentRequest=0;
|
||||||
|
readTimer.stop();
|
||||||
|
busy = false; // pdiener: now we have finished
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpConnectionHandler::read() {
|
||||||
|
#ifdef SUPERVERBOSE
|
||||||
|
qDebug("HttpConnectionHandler (%x): read input",(unsigned int) this);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Create new HttpRequest object if necessary
|
||||||
|
if (!currentRequest) {
|
||||||
|
currentRequest=new HttpRequest(settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect data for the request object
|
||||||
|
while (socket.bytesAvailable() && currentRequest->getStatus()!=HttpRequest::complete && currentRequest->getStatus()!=HttpRequest::abort) {
|
||||||
|
currentRequest->readFromSocket(socket);
|
||||||
|
if (currentRequest->getStatus()==HttpRequest::waitForBody) {
|
||||||
|
// Restart timer for read timeout, otherwise it would
|
||||||
|
// expire during large file uploads.
|
||||||
|
int readTimeout=settings->value("readTimeout",10000).toInt();
|
||||||
|
readTimer.start(readTimeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the request is aborted, return error message and close the connection
|
||||||
|
if (currentRequest->getStatus()==HttpRequest::abort) {
|
||||||
|
socket.write("HTTP/1.1 413 entity too large\r\nConnection: close\r\n\r\n413 Entity too large\r\n");
|
||||||
|
socket.disconnectFromHost();
|
||||||
|
delete currentRequest;
|
||||||
|
currentRequest=0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the request is complete, let the request mapper dispatch it
|
||||||
|
if (currentRequest->getStatus()==HttpRequest::complete) {
|
||||||
|
readTimer.stop();
|
||||||
|
qDebug("HttpConnectionHandler (%p): received request",this);
|
||||||
|
HttpResponse response(&socket);
|
||||||
|
try {
|
||||||
|
requestHandler->service(*currentRequest, response);
|
||||||
|
}
|
||||||
|
catch (...) {
|
||||||
|
qCritical("HttpConnectionHandler (%p): An uncatched exception occured in the request handler",this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finalize sending the response if not already done
|
||||||
|
if (!response.hasSentLastPart()) {
|
||||||
|
response.write(QByteArray(),true);
|
||||||
|
}
|
||||||
|
// Close the connection after delivering the response, if requested
|
||||||
|
if (QString::compare(currentRequest->getHeader("Connection"),"close",Qt::CaseInsensitive)==0) {
|
||||||
|
socket.disconnectFromHost();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Start timer for next request
|
||||||
|
int readTimeout=settings->value("readTimeout",10000).toInt();
|
||||||
|
readTimer.start(readTimeout);
|
||||||
|
}
|
||||||
|
// Prepare for next request
|
||||||
|
delete currentRequest;
|
||||||
|
currentRequest=0;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,97 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@author Stefan Frings
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef HTTPCONNECTIONHANDLER_H
|
||||||
|
#define HTTPCONNECTIONHANDLER_H
|
||||||
|
|
||||||
|
#include <QTcpSocket>
|
||||||
|
#include <QSettings>
|
||||||
|
#include <QTimer>
|
||||||
|
#include <QThread>
|
||||||
|
#include "httprequest.h"
|
||||||
|
#include "httprequesthandler.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
The connection handler accepts incoming connections and dispatches incoming requests to to a
|
||||||
|
request mapper. Since HTTP clients can send multiple requests before waiting for the response,
|
||||||
|
the incoming requests are queued and processed one after the other.
|
||||||
|
<p>
|
||||||
|
Example for the required configuration settings:
|
||||||
|
<code><pre>
|
||||||
|
readTimeout=60000
|
||||||
|
maxRequestSize=16000
|
||||||
|
maxMultiPartSize=1000000
|
||||||
|
</pre></code>
|
||||||
|
<p>
|
||||||
|
The readTimeout value defines the maximum time to wait for a complete HTTP request.
|
||||||
|
@see HttpRequest for description of config settings maxRequestSize and maxMultiPartSize
|
||||||
|
*/
|
||||||
|
|
||||||
|
class HttpConnectionHandler : public QThread {
|
||||||
|
Q_OBJECT
|
||||||
|
Q_DISABLE_COPY(HttpConnectionHandler)
|
||||||
|
public:
|
||||||
|
|
||||||
|
/**
|
||||||
|
Constructor.
|
||||||
|
@param settings Configuration settings of the HTTP webserver
|
||||||
|
@param requestHandler handler that will process each incomin HTTP request
|
||||||
|
*/
|
||||||
|
HttpConnectionHandler(QSettings* settings, HttpRequestHandler* requestHandler);
|
||||||
|
|
||||||
|
/** Destructor */
|
||||||
|
virtual ~HttpConnectionHandler();
|
||||||
|
|
||||||
|
/** Returns true, if this handler is busy */
|
||||||
|
bool isBusy();
|
||||||
|
|
||||||
|
/** Mark this handler as busy */
|
||||||
|
void setBusy();
|
||||||
|
|
||||||
|
/** This shows the busy-state from a very early time */
|
||||||
|
bool busy;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
/** Configuration settings */
|
||||||
|
QSettings* settings;
|
||||||
|
|
||||||
|
/** TCP socket of the current connection */
|
||||||
|
QTcpSocket socket;
|
||||||
|
|
||||||
|
/** Time for read timeout detection */
|
||||||
|
QTimer readTimer;
|
||||||
|
|
||||||
|
/** Storage for the current incoming HTTP request */
|
||||||
|
HttpRequest* currentRequest;
|
||||||
|
|
||||||
|
/** Dispatches received requests to services */
|
||||||
|
HttpRequestHandler* requestHandler;
|
||||||
|
|
||||||
|
/** Executes the htreads own event loop */
|
||||||
|
void run();
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
|
||||||
|
/**
|
||||||
|
Received from from the listener, when the handler shall start processing a new connection.
|
||||||
|
@param socketDescriptor references the accepted connection.
|
||||||
|
*/
|
||||||
|
void handleConnection(int socketDescriptor);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
|
||||||
|
/** Received from the socket when a read-timeout occured */
|
||||||
|
void readTimeout();
|
||||||
|
|
||||||
|
/** Received from the socket when incoming data can be read */
|
||||||
|
void read();
|
||||||
|
|
||||||
|
/** Received from the socket when a connection has been closed */
|
||||||
|
void disconnected();
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // HTTPCONNECTIONHANDLER_H
|
@ -0,0 +1,55 @@
|
|||||||
|
#include "httpconnectionhandlerpool.h"
|
||||||
|
|
||||||
|
HttpConnectionHandlerPool::HttpConnectionHandlerPool(QSettings* settings, HttpRequestHandler* requestHandler)
|
||||||
|
: QObject()
|
||||||
|
{
|
||||||
|
Q_ASSERT(settings!=0);
|
||||||
|
this->settings=settings;
|
||||||
|
this->requestHandler=requestHandler;
|
||||||
|
cleanupTimer.start(settings->value("cleanupInterval",1000).toInt());
|
||||||
|
connect(&cleanupTimer, SIGNAL(timeout()), SLOT(cleanup()));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
HttpConnectionHandlerPool::~HttpConnectionHandlerPool() {
|
||||||
|
foreach(HttpConnectionHandler* handler, pool) {
|
||||||
|
delete handler;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
HttpConnectionHandler* HttpConnectionHandlerPool::getConnectionHandler() {
|
||||||
|
HttpConnectionHandler* freeHandler=0;
|
||||||
|
foreach(HttpConnectionHandler* handler, pool) {
|
||||||
|
if (!handler->isBusy()) {
|
||||||
|
freeHandler=handler;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!freeHandler) {
|
||||||
|
int maxConnectionHandlers=settings->value("maxThreads",10).toInt();
|
||||||
|
if (pool.count()<maxConnectionHandlers) {
|
||||||
|
freeHandler=new HttpConnectionHandler(settings,requestHandler);
|
||||||
|
pool.append(freeHandler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (freeHandler) freeHandler->busy = true; // pdiener: set it to busy-state immediately
|
||||||
|
return freeHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void HttpConnectionHandlerPool::cleanup() {
|
||||||
|
int maxIdleHandlers=settings->value("minThreads",1).toInt();
|
||||||
|
int idleCounter=0;
|
||||||
|
foreach(HttpConnectionHandler* handler, pool) {
|
||||||
|
if (!handler->isBusy()) {
|
||||||
|
if (++idleCounter > maxIdleHandlers) {
|
||||||
|
pool.removeOne(handler);
|
||||||
|
qDebug("HttpConnectionHandlerPool: Removed connection handler (%p), pool size is now %i",handler,pool.size());
|
||||||
|
connect(handler,SIGNAL(finished()),handler,SLOT(deleteLater()));
|
||||||
|
handler->quit();
|
||||||
|
break; // remove only one handler in each interval
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,69 @@
|
|||||||
|
#ifndef HTTPCONNECTIONHANDLERPOOL_H
|
||||||
|
#define HTTPCONNECTIONHANDLERPOOL_H
|
||||||
|
|
||||||
|
#include <QList>
|
||||||
|
#include <QTimer>
|
||||||
|
#include <QObject>
|
||||||
|
#include "httpconnectionhandler.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
Pool of http connection handlers. Connection handlers are created on demand and idle handlers are
|
||||||
|
cleaned up in regular time intervals.
|
||||||
|
<p>
|
||||||
|
Example for the required configuration settings:
|
||||||
|
<code><pre>
|
||||||
|
minThreads=1
|
||||||
|
maxThreads=10
|
||||||
|
cleanupInterval=1000
|
||||||
|
maxRequestSize=16000
|
||||||
|
maxMultiPartSize=1000000
|
||||||
|
</pre></code>
|
||||||
|
The pool is empty initially and grows with the number of concurrent
|
||||||
|
connections. A timer removes one idle connection handler at each
|
||||||
|
interval, but a it leaves some spare handlers in memory to improve
|
||||||
|
performance.
|
||||||
|
@see HttpConnectionHandler for description of config settings readTimeout
|
||||||
|
@see HttpRequest for description of config settings maxRequestSize and maxMultiPartSize
|
||||||
|
*/
|
||||||
|
|
||||||
|
class HttpConnectionHandlerPool : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
Q_DISABLE_COPY(HttpConnectionHandlerPool)
|
||||||
|
public:
|
||||||
|
|
||||||
|
/**
|
||||||
|
Constructor.
|
||||||
|
@param settings Configuration settings for the HTTP server. Must not be 0.
|
||||||
|
@param requestHandler The handler that will process each received HTTP request.
|
||||||
|
@warning The requestMapper gets deleted by the destructor of this pool
|
||||||
|
*/
|
||||||
|
HttpConnectionHandlerPool(QSettings* settings, HttpRequestHandler* requestHandler);
|
||||||
|
|
||||||
|
/** Destructor */
|
||||||
|
virtual ~HttpConnectionHandlerPool();
|
||||||
|
|
||||||
|
/** Get a free connection handler, or 0 if not available. */
|
||||||
|
HttpConnectionHandler* getConnectionHandler();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
/** Settings for this pool */
|
||||||
|
QSettings* settings;
|
||||||
|
|
||||||
|
/** Will be assigned to each Connectionhandler during their creation */
|
||||||
|
HttpRequestHandler* requestHandler;
|
||||||
|
|
||||||
|
/** Pool of connection handlers */
|
||||||
|
QList<HttpConnectionHandler*> pool;
|
||||||
|
|
||||||
|
/** Timer to clean-up unused connection handler */
|
||||||
|
QTimer cleanupTimer;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
|
||||||
|
/** Received from the clean-up timer. */
|
||||||
|
void cleanup();
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // HTTPCONNECTIONHANDLERPOOL_H
|
199
YACReaderLibrary/server/lib/bfHttpServer/httpcookie.cpp
Normal file
199
YACReaderLibrary/server/lib/bfHttpServer/httpcookie.cpp
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@author Stefan Frings
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "httpcookie.h"
|
||||||
|
|
||||||
|
HttpCookie::HttpCookie() {
|
||||||
|
version=1;
|
||||||
|
maxAge=0;
|
||||||
|
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) {
|
||||||
|
this->name=name;
|
||||||
|
this->value=value;
|
||||||
|
this->maxAge=maxAge;
|
||||||
|
this->path=path;
|
||||||
|
this->comment=comment;
|
||||||
|
this->domain=domain;
|
||||||
|
this->secure=secure;
|
||||||
|
this->version=1;
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpCookie::HttpCookie(const QByteArray source) {
|
||||||
|
version=1;
|
||||||
|
maxAge=0;
|
||||||
|
secure=false;
|
||||||
|
QList<QByteArray> list=splitCSV(source);
|
||||||
|
foreach(QByteArray part, list) {
|
||||||
|
|
||||||
|
// Split the part into name and value
|
||||||
|
QByteArray name;
|
||||||
|
QByteArray value;
|
||||||
|
int posi=part.indexOf('=');
|
||||||
|
if (posi) {
|
||||||
|
name=part.left(posi).trimmed();
|
||||||
|
value=part.mid(posi+1).trimmed();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
name=part.trimmed();
|
||||||
|
value="";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set fields
|
||||||
|
if (name=="Comment") {
|
||||||
|
comment=value;
|
||||||
|
}
|
||||||
|
else if (name=="Domain") {
|
||||||
|
domain=value;
|
||||||
|
}
|
||||||
|
else if (name=="Max-Age") {
|
||||||
|
maxAge=value.toInt();
|
||||||
|
}
|
||||||
|
else if (name=="Path") {
|
||||||
|
path=value;
|
||||||
|
}
|
||||||
|
else if (name=="Secure") {
|
||||||
|
secure=true;
|
||||||
|
}
|
||||||
|
else if (name=="Version") {
|
||||||
|
version=value.toInt();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (this->name.isEmpty()) {
|
||||||
|
this->name=name;
|
||||||
|
this->value=value;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
qWarning("HttpCookie: Ignoring unknown %s=%s",name.data(),value.data());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray HttpCookie::toByteArray() const {
|
||||||
|
QByteArray buffer(name);
|
||||||
|
buffer.append('=');
|
||||||
|
buffer.append(value);
|
||||||
|
if (!comment.isEmpty()) {
|
||||||
|
buffer.append("; Comment=");
|
||||||
|
buffer.append(comment);
|
||||||
|
}
|
||||||
|
if (!domain.isEmpty()) {
|
||||||
|
buffer.append("; Domain=");
|
||||||
|
buffer.append(domain);
|
||||||
|
}
|
||||||
|
if (maxAge!=0) {
|
||||||
|
buffer.append("; Max-Age=");
|
||||||
|
buffer.append(QByteArray::number(maxAge));
|
||||||
|
}
|
||||||
|
if (!path.isEmpty()) {
|
||||||
|
buffer.append("; Path=");
|
||||||
|
buffer.append(path);
|
||||||
|
}
|
||||||
|
if (secure) {
|
||||||
|
buffer.append("; Secure");
|
||||||
|
}
|
||||||
|
buffer.append("; Version=");
|
||||||
|
buffer.append(QByteArray::number(version));
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpCookie::setName(const QByteArray name){
|
||||||
|
this->name=name;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpCookie::setValue(const QByteArray value){
|
||||||
|
this->value=value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpCookie::setComment(const QByteArray comment){
|
||||||
|
this->comment=comment;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpCookie::setDomain(const QByteArray domain){
|
||||||
|
this->domain=domain;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpCookie::setMaxAge(const int maxAge){
|
||||||
|
this->maxAge=maxAge;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpCookie::setPath(const QByteArray path){
|
||||||
|
this->path=path;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpCookie::setSecure(const bool secure){
|
||||||
|
this->secure=secure;
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray HttpCookie::getName() const {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray HttpCookie::getValue() const {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray HttpCookie::getComment() const {
|
||||||
|
return comment;
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray HttpCookie::getDomain() const {
|
||||||
|
return domain;
|
||||||
|
}
|
||||||
|
|
||||||
|
int HttpCookie::getMaxAge() const {
|
||||||
|
return maxAge;
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray HttpCookie::getPath() const {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HttpCookie::getSecure() const {
|
||||||
|
return secure;
|
||||||
|
}
|
||||||
|
|
||||||
|
int HttpCookie::getVersion() const {
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<QByteArray> HttpCookie::splitCSV(const QByteArray source) {
|
||||||
|
bool inString=false;
|
||||||
|
QList<QByteArray> list;
|
||||||
|
QByteArray buffer;
|
||||||
|
for (int i=0; i<source.size(); ++i) {
|
||||||
|
char c=source.at(i);
|
||||||
|
if (inString==false) {
|
||||||
|
if (c=='\"') {
|
||||||
|
inString=true;
|
||||||
|
}
|
||||||
|
else if (c==';') {
|
||||||
|
QByteArray trimmed=buffer.trimmed();
|
||||||
|
if (!trimmed.isEmpty()) {
|
||||||
|
list.append(trimmed);
|
||||||
|
}
|
||||||
|
buffer.clear();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
buffer.append(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (c=='\"') {
|
||||||
|
inString=false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
buffer.append(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QByteArray trimmed=buffer.trimmed();
|
||||||
|
if (!trimmed.isEmpty()) {
|
||||||
|
list.append(trimmed);
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
110
YACReaderLibrary/server/lib/bfHttpServer/httpcookie.h
Normal file
110
YACReaderLibrary/server/lib/bfHttpServer/httpcookie.h
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@author Stefan Frings
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef HTTPCOOKIE_H
|
||||||
|
#define HTTPCOOKIE_H
|
||||||
|
|
||||||
|
#include <QList>
|
||||||
|
#include <QByteArray>
|
||||||
|
|
||||||
|
/**
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class HttpCookie
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
/** Creates an empty cookie */
|
||||||
|
HttpCookie();
|
||||||
|
|
||||||
|
/**
|
||||||
|
Create a cookie and set name/value pair.
|
||||||
|
@param name name of the cookie
|
||||||
|
@param value value of the cookie
|
||||||
|
@param maxAge maximum age of the cookie in seconds. 0=discard immediately
|
||||||
|
@param path Path for that the cookie will be sent, default="/" which means the whole domain
|
||||||
|
@param comment Optional comment, may be displayed by the web browser somewhere
|
||||||
|
@param domain Optional domain for that the cookie will be sent. Defaults to the current domain
|
||||||
|
@param secure If true, the cookie will only be sent on secure connections
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Create a cookie from a string.
|
||||||
|
@param source String as received in a HTTP Cookie2 header.
|
||||||
|
*/
|
||||||
|
HttpCookie(const QByteArray source);
|
||||||
|
|
||||||
|
/** Convert this cookie to a string that may be used in a Set-Cookie2 header. */
|
||||||
|
QByteArray toByteArray() const ;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Split a string list into parts, where each part is delimited by semicolon.
|
||||||
|
Semicolons within double quotes are skipped. Double quotes are removed.
|
||||||
|
*/
|
||||||
|
static QList<QByteArray> splitCSV(const QByteArray source);
|
||||||
|
|
||||||
|
/** Set the name of this cookie */
|
||||||
|
void setName(const QByteArray name);
|
||||||
|
|
||||||
|
/** Set the value of this cookie */
|
||||||
|
void setValue(const QByteArray value);
|
||||||
|
|
||||||
|
/** Set the comment of this cookie */
|
||||||
|
void setComment(const QByteArray comment);
|
||||||
|
|
||||||
|
/** Set the domain of this cookie */
|
||||||
|
void setDomain(const QByteArray domain);
|
||||||
|
|
||||||
|
/** Set the maximum age of this cookie in seconds. 0=discard immediately */
|
||||||
|
void setMaxAge(const int maxAge);
|
||||||
|
|
||||||
|
/** Set the path for that the cookie will be sent, default="/" which means the whole domain */
|
||||||
|
void setPath(const QByteArray path);
|
||||||
|
|
||||||
|
/** Set secure mode, so that the cokkie will only be sent on secure connections */
|
||||||
|
void setSecure(const bool secure);
|
||||||
|
|
||||||
|
/** Get the name of this cookie */
|
||||||
|
QByteArray getName() const;
|
||||||
|
|
||||||
|
/** Get the value of this cookie */
|
||||||
|
QByteArray getValue() const;
|
||||||
|
|
||||||
|
/** Get the comment of this cookie */
|
||||||
|
QByteArray getComment() const;
|
||||||
|
|
||||||
|
/** Get the domain of this cookie */
|
||||||
|
QByteArray getDomain() const;
|
||||||
|
|
||||||
|
/** Set the maximum age of this cookie in seconds. */
|
||||||
|
int getMaxAge() const;
|
||||||
|
|
||||||
|
/** Set the path of this cookie */
|
||||||
|
QByteArray getPath() const;
|
||||||
|
|
||||||
|
/** Get the secure flag of this cookie */
|
||||||
|
bool getSecure() const;
|
||||||
|
|
||||||
|
/** Returns always 1 */
|
||||||
|
int getVersion() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
QByteArray name;
|
||||||
|
QByteArray value;
|
||||||
|
QByteArray comment;
|
||||||
|
QByteArray domain;
|
||||||
|
int maxAge;
|
||||||
|
QByteArray path;
|
||||||
|
bool secure;
|
||||||
|
int version;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // HTTPCOOKIE_H
|
56
YACReaderLibrary/server/lib/bfHttpServer/httplistener.cpp
Normal file
56
YACReaderLibrary/server/lib/bfHttpServer/httplistener.cpp
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@author Stefan Frings
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "httplistener.h"
|
||||||
|
#include "httpconnectionhandler.h"
|
||||||
|
#include "httpconnectionhandlerpool.h"
|
||||||
|
#include <QCoreApplication>
|
||||||
|
|
||||||
|
HttpListener::HttpListener(QSettings* settings, HttpRequestHandler* requestHandler, QObject *parent)
|
||||||
|
: QTcpServer(parent), pool(settings,requestHandler)
|
||||||
|
{
|
||||||
|
Q_ASSERT(settings!=0);
|
||||||
|
this->settings=settings;
|
||||||
|
// Start listening
|
||||||
|
int port=settings->value("port").toInt();
|
||||||
|
listen(QHostAddress::Any, port);
|
||||||
|
if (!isListening()) {
|
||||||
|
qCritical("HttpListener: Cannot bind on port %i: %s",port,qPrintable(errorString()));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
qDebug("HttpListener: Listening on port %i",port);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpListener::~HttpListener() {
|
||||||
|
close();
|
||||||
|
qDebug("HttpListener: closed");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void HttpListener::incomingConnection(int socketDescriptor) {
|
||||||
|
#ifdef SUPERVERBOSE
|
||||||
|
qDebug("HttpListener: New connection");
|
||||||
|
#endif
|
||||||
|
HttpConnectionHandler* freeHandler=pool.getConnectionHandler();
|
||||||
|
|
||||||
|
// Let the handler process the new connection.
|
||||||
|
if (freeHandler) {
|
||||||
|
// The descriptor is passed via signal/slot because the handler lives in another
|
||||||
|
// thread and cannot open the socket when called by another thread.
|
||||||
|
connect(this,SIGNAL(handleConnection(int)),freeHandler,SLOT(handleConnection(int)));
|
||||||
|
emit handleConnection(socketDescriptor);
|
||||||
|
disconnect(this,SIGNAL(handleConnection(int)),freeHandler,SLOT(handleConnection(int)));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Reject the connection
|
||||||
|
qDebug("HttpListener: Too many connections");
|
||||||
|
QTcpSocket* socket=new QTcpSocket(this);
|
||||||
|
socket->setSocketDescriptor(socketDescriptor);
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
76
YACReaderLibrary/server/lib/bfHttpServer/httplistener.h
Normal file
76
YACReaderLibrary/server/lib/bfHttpServer/httplistener.h
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@author Stefan Frings
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef LISTENER_H
|
||||||
|
#define LISTENER_H
|
||||||
|
|
||||||
|
#include <QTcpServer>
|
||||||
|
#include <QSettings>
|
||||||
|
#include <QBasicTimer>
|
||||||
|
#include "httpconnectionhandler.h"
|
||||||
|
#include "httpconnectionhandlerpool.h"
|
||||||
|
#include "httprequesthandler.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
Listens for incoming TCP connections and passes control to
|
||||||
|
one of the pooled connection handlers. This class is also
|
||||||
|
responsible for managing the pool.
|
||||||
|
<p>
|
||||||
|
Example for the required settings in the config file:
|
||||||
|
<code><pre>
|
||||||
|
port=8080
|
||||||
|
minThreads=1
|
||||||
|
maxThreads=10
|
||||||
|
cleanupInterval=1000
|
||||||
|
readTimeout=60000
|
||||||
|
maxRequestSize=16000
|
||||||
|
maxMultiPartSize=1000000
|
||||||
|
</pre></code>
|
||||||
|
The port number is the incoming TCP port that this listener listens to.
|
||||||
|
@see HttpConnectionHandlerPool for description of config settings minThreads, maxThreads and cleanupInterval
|
||||||
|
@see HttpConnectionHandler for description of config settings readTimeout
|
||||||
|
@see HttpRequest for description of config settings maxRequestSize and maxMultiPartSize
|
||||||
|
*/
|
||||||
|
|
||||||
|
class HttpListener : public QTcpServer {
|
||||||
|
Q_OBJECT
|
||||||
|
Q_DISABLE_COPY(HttpListener)
|
||||||
|
public:
|
||||||
|
|
||||||
|
/**
|
||||||
|
Constructor.
|
||||||
|
@param settings Configuration settings for the HTTP server. Must not be 0.
|
||||||
|
@param requestHandler Processes each received HTTP request, usually by dispatching to controller classes.
|
||||||
|
@param parent Parent object
|
||||||
|
*/
|
||||||
|
HttpListener(QSettings* settings, HttpRequestHandler* requestHandler, QObject* parent = 0);
|
||||||
|
|
||||||
|
/** Destructor */
|
||||||
|
virtual ~HttpListener();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
/** Serves new incoming connection requests */
|
||||||
|
void incomingConnection(int socketDescriptor);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
/** Configuration settings for the HTTP server */
|
||||||
|
QSettings* settings;
|
||||||
|
|
||||||
|
/** Pool of connection handlers */
|
||||||
|
HttpConnectionHandlerPool pool;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
|
||||||
|
/**
|
||||||
|
Emitted when the connection handler shall process a new incoming onnection.
|
||||||
|
@param socketDescriptor references the accepted connection.
|
||||||
|
*/
|
||||||
|
void handleConnection(int socketDescriptor);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // LISTENER_H
|
435
YACReaderLibrary/server/lib/bfHttpServer/httprequest.cpp
Normal file
435
YACReaderLibrary/server/lib/bfHttpServer/httprequest.cpp
Normal file
@ -0,0 +1,435 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@author Stefan Frings
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "httprequest.h"
|
||||||
|
#include <QList>
|
||||||
|
#include <QDir>
|
||||||
|
#include "httpcookie.h"
|
||||||
|
|
||||||
|
HttpRequest::HttpRequest(QSettings* settings) {
|
||||||
|
status=waitForRequest;
|
||||||
|
currentSize=0;
|
||||||
|
expectedBodySize=0;
|
||||||
|
maxSize=settings->value("maxRequestSize","16000").toInt();
|
||||||
|
maxMultiPartSize=settings->value("maxMultiPartSize","1000000").toInt();
|
||||||
|
|
||||||
|
// Convert relative path to absolute, based on the directory of the config file.
|
||||||
|
#ifdef Q_OS_WIN32
|
||||||
|
if (QDir::isRelativePath(tempDir) && settings->format()!=QSettings::NativeFormat)
|
||||||
|
#else
|
||||||
|
if (QDir::isRelativePath(tempDir))
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
QFileInfo configFile(settings->fileName());
|
||||||
|
tempDir=QFileInfo(configFile.absolutePath(),tempDir).absoluteFilePath();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpRequest::readRequest(QTcpSocket& socket) {
|
||||||
|
#ifdef SUPERVERBOSE
|
||||||
|
qDebug("HttpRequest: read request");
|
||||||
|
#endif
|
||||||
|
int toRead=maxSize-currentSize+1; // allow one byte more to be able to detect overflow
|
||||||
|
QByteArray newData=socket.readLine(toRead).trimmed();
|
||||||
|
currentSize+=newData.size();
|
||||||
|
if (!newData.isEmpty()) {
|
||||||
|
QList<QByteArray> list=newData.split(' ');
|
||||||
|
if (list.count()!=3 || !list.at(2).contains("HTTP")) {
|
||||||
|
qWarning("HttpRequest: received broken HTTP request, invalid first line");
|
||||||
|
status=abort;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
method=list.at(0);
|
||||||
|
path=list.at(1);
|
||||||
|
version=list.at(2);
|
||||||
|
status=waitForHeader;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpRequest::readHeader(QTcpSocket& socket) {
|
||||||
|
#ifdef SUPERVERBOSE
|
||||||
|
qDebug("HttpRequest: read header");
|
||||||
|
#endif
|
||||||
|
int toRead=maxSize-currentSize+1; // allow one byte more to be able to detect overflow
|
||||||
|
QByteArray newData=socket.readLine(toRead).trimmed();
|
||||||
|
currentSize+=newData.size();
|
||||||
|
int colon=newData.indexOf(':');
|
||||||
|
if (colon>0) {
|
||||||
|
// Received a line with a colon - a header
|
||||||
|
currentHeader=newData.left(colon);
|
||||||
|
QByteArray value=newData.mid(colon+1).trimmed();
|
||||||
|
headers.insert(currentHeader,value);
|
||||||
|
#ifdef SUPERVERBOSE
|
||||||
|
qDebug("HttpRequest: received header %s: %s",currentHeader.data(),value.data());
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
else if (!newData.isEmpty()) {
|
||||||
|
// received another line - belongs to the previous header
|
||||||
|
#ifdef SUPERVERBOSE
|
||||||
|
qDebug("HttpRequest: read additional line of header");
|
||||||
|
#endif
|
||||||
|
// Received additional line of previous header
|
||||||
|
if (headers.contains(currentHeader)) {
|
||||||
|
headers.insert(currentHeader,headers.value(currentHeader)+" "+newData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// received an empty line - end of headers reached
|
||||||
|
#ifdef SUPERVERBOSE
|
||||||
|
qDebug("HttpRequest: headers completed");
|
||||||
|
#endif
|
||||||
|
// Empty line received, that means all headers have been received
|
||||||
|
// Check for multipart/form-data
|
||||||
|
QByteArray contentType=headers.value("Content-Type");
|
||||||
|
if (contentType.startsWith("multipart/form-data")) {
|
||||||
|
int posi=contentType.indexOf("boundary=");
|
||||||
|
if (posi>=0) {
|
||||||
|
boundary=contentType.mid(posi+9);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QByteArray contentLength=getHeader("Content-Length");
|
||||||
|
if (!contentLength.isEmpty()) {
|
||||||
|
expectedBodySize=contentLength.toInt();
|
||||||
|
}
|
||||||
|
if (expectedBodySize==0) {
|
||||||
|
#ifdef SUPERVERBOSE
|
||||||
|
qDebug("HttpRequest: expect no body");
|
||||||
|
#endif
|
||||||
|
status=complete;
|
||||||
|
}
|
||||||
|
else if (boundary.isEmpty() && expectedBodySize+currentSize>maxSize) {
|
||||||
|
qWarning("HttpRequest: expected body is too large");
|
||||||
|
status=abort;
|
||||||
|
}
|
||||||
|
else if (!boundary.isEmpty() && expectedBodySize>maxMultiPartSize) {
|
||||||
|
qWarning("HttpRequest: expected multipart body is too large");
|
||||||
|
status=abort;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
#ifdef SUPERVERBOSE
|
||||||
|
qDebug("HttpRequest: expect %i bytes body",expectedBodySize);
|
||||||
|
#endif
|
||||||
|
status=waitForBody;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpRequest::readBody(QTcpSocket& socket) {
|
||||||
|
Q_ASSERT(expectedBodySize!=0);
|
||||||
|
if (boundary.isEmpty()) {
|
||||||
|
// normal body, no multipart
|
||||||
|
#ifdef SUPERVERBOSE
|
||||||
|
qDebug("HttpRequest: receive body");
|
||||||
|
#endif
|
||||||
|
int toRead=expectedBodySize-bodyData.size();
|
||||||
|
QByteArray newData=socket.read(toRead);
|
||||||
|
currentSize+=newData.size();
|
||||||
|
bodyData.append(newData);
|
||||||
|
if (bodyData.size()>=expectedBodySize) {
|
||||||
|
status=complete;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// multipart body, store into temp file
|
||||||
|
#ifdef SUPERVERBOSE
|
||||||
|
qDebug("HttpRequest: receiving multipart body");
|
||||||
|
#endif
|
||||||
|
if (!tempFile.isOpen()) {
|
||||||
|
tempFile.open();
|
||||||
|
}
|
||||||
|
// Transfer data in 64kb blocks
|
||||||
|
int fileSize=tempFile.size();
|
||||||
|
int toRead=expectedBodySize-fileSize;
|
||||||
|
if (toRead>65536) {
|
||||||
|
toRead=65536;
|
||||||
|
}
|
||||||
|
fileSize+=tempFile.write(socket.read(toRead));
|
||||||
|
if (fileSize>=maxMultiPartSize) {
|
||||||
|
qWarning("HttpRequest: received too many multipart bytes");
|
||||||
|
status=abort;
|
||||||
|
}
|
||||||
|
else if (fileSize>=expectedBodySize) {
|
||||||
|
#ifdef SUPERVERBOSE
|
||||||
|
qDebug("HttpRequest: received whole multipart body");
|
||||||
|
#endif
|
||||||
|
tempFile.flush();
|
||||||
|
if (tempFile.error()) {
|
||||||
|
qCritical("HttpRequest: Error writing temp file for multipart body");
|
||||||
|
}
|
||||||
|
parseMultiPartFile();
|
||||||
|
tempFile.close();
|
||||||
|
status=complete;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpRequest::decodeRequestParams() {
|
||||||
|
#ifdef SUPERVERBOSE
|
||||||
|
qDebug("HttpRequest: extract and decode request parameters");
|
||||||
|
#endif
|
||||||
|
QByteArray rawParameters;
|
||||||
|
if (headers.value("Content-Type")=="application/x-www-form-urlencoded") {
|
||||||
|
rawParameters=bodyData;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
int questionMark=path.indexOf('?');
|
||||||
|
if (questionMark>=0) {
|
||||||
|
rawParameters=path.mid(questionMark+1);
|
||||||
|
path=path.left(questionMark);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Split the parameters into pairs of value and name
|
||||||
|
QList<QByteArray> list=rawParameters.split('&');
|
||||||
|
foreach (QByteArray part, list) {
|
||||||
|
int equalsChar=part.indexOf('=');
|
||||||
|
if (equalsChar>=0) {
|
||||||
|
QByteArray name=part.left(equalsChar).trimmed();
|
||||||
|
QByteArray value=part.mid(equalsChar+1).trimmed();
|
||||||
|
parameters.insert(urlDecode(name),urlDecode(value));
|
||||||
|
}
|
||||||
|
else if (!part.isEmpty()){
|
||||||
|
// Name without value
|
||||||
|
parameters.insert(urlDecode(part),"");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpRequest::extractCookies() {
|
||||||
|
#ifdef SUPERVERBOSE
|
||||||
|
qDebug("HttpRequest: extract cookies");
|
||||||
|
#endif
|
||||||
|
foreach(QByteArray cookieStr, headers.values("Cookie")) {
|
||||||
|
QList<QByteArray> list=HttpCookie::splitCSV(cookieStr);
|
||||||
|
foreach(QByteArray part, list) {
|
||||||
|
#ifdef SUPERVERBOSE
|
||||||
|
qDebug("HttpRequest: found cookie %s",part.data());
|
||||||
|
#endif // Split the part into name and value
|
||||||
|
QByteArray name;
|
||||||
|
QByteArray value;
|
||||||
|
int posi=part.indexOf('=');
|
||||||
|
if (posi) {
|
||||||
|
name=part.left(posi).trimmed();
|
||||||
|
value=part.mid(posi+1).trimmed();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
name=part.trimmed();
|
||||||
|
value="";
|
||||||
|
}
|
||||||
|
cookies.insert(name,value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
headers.remove("Cookie");
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpRequest::readFromSocket(QTcpSocket& socket) {
|
||||||
|
Q_ASSERT(status!=complete);
|
||||||
|
if (status==waitForRequest) {
|
||||||
|
readRequest(socket);
|
||||||
|
}
|
||||||
|
else if (status==waitForHeader) {
|
||||||
|
readHeader(socket);
|
||||||
|
}
|
||||||
|
else if (status==waitForBody) {
|
||||||
|
readBody(socket);
|
||||||
|
}
|
||||||
|
if (currentSize>maxSize) {
|
||||||
|
qWarning("HttpRequest: received too many bytes");
|
||||||
|
status=abort;
|
||||||
|
}
|
||||||
|
if (status==complete) {
|
||||||
|
// Extract and decode request parameters from url and body
|
||||||
|
decodeRequestParams();
|
||||||
|
// Extract cookies from headers
|
||||||
|
extractCookies();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
HttpRequest::RequestStatus HttpRequest::getStatus() const {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
QByteArray HttpRequest::getMethod() const {
|
||||||
|
return method;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
QByteArray HttpRequest::getPath() const {
|
||||||
|
return urlDecode(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
QByteArray HttpRequest::getVersion() const {
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
QByteArray HttpRequest::getHeader(const QByteArray& name) const {
|
||||||
|
return headers.value(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<QByteArray> HttpRequest::getHeaders(const QByteArray& name) const {
|
||||||
|
return headers.values(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
QMultiMap<QByteArray,QByteArray> HttpRequest::getHeaderMap() const {
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray HttpRequest::getParameter(const QByteArray& name) const {
|
||||||
|
return parameters.value(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<QByteArray> HttpRequest::getParameters(const QByteArray& name) const {
|
||||||
|
return parameters.values(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
QMultiMap<QByteArray,QByteArray> HttpRequest::getParameterMap() const {
|
||||||
|
return parameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray HttpRequest::getBody() const {
|
||||||
|
return bodyData;
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray HttpRequest::urlDecode(const QByteArray source) {
|
||||||
|
QByteArray buffer(source);
|
||||||
|
buffer.replace('+',' ');
|
||||||
|
int percentChar=buffer.indexOf('%');
|
||||||
|
while (percentChar>=0) {
|
||||||
|
bool ok;
|
||||||
|
char byte=buffer.mid(percentChar+1,2).toInt(&ok,16);
|
||||||
|
if (ok) {
|
||||||
|
buffer.replace(percentChar,3,(char*)&byte,1);
|
||||||
|
}
|
||||||
|
percentChar=buffer.indexOf('%',percentChar+1);
|
||||||
|
}
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void HttpRequest::parseMultiPartFile() {
|
||||||
|
qDebug("HttpRequest: parsing multipart temp file");
|
||||||
|
tempFile.seek(0);
|
||||||
|
bool finished=false;
|
||||||
|
while (!tempFile.atEnd() && !finished && !tempFile.error()) {
|
||||||
|
|
||||||
|
#ifdef SUPERVERBOSE
|
||||||
|
qDebug("HttpRequest: reading multpart headers");
|
||||||
|
#endif
|
||||||
|
QByteArray fieldName;
|
||||||
|
QByteArray fileName;
|
||||||
|
while (!tempFile.atEnd() && !finished && !tempFile.error()) {
|
||||||
|
QByteArray line=tempFile.readLine(65536).trimmed();
|
||||||
|
if (line.startsWith("Content-Disposition:")) {
|
||||||
|
if (line.contains("form-data")) {
|
||||||
|
int start=line.indexOf(" name=\"");
|
||||||
|
int end=line.indexOf("\"",start+7);
|
||||||
|
if (start>=0 && end>=start) {
|
||||||
|
fieldName=line.mid(start+7,end-start-7);
|
||||||
|
}
|
||||||
|
start=line.indexOf(" filename=\"");
|
||||||
|
end=line.indexOf("\"",start+11);
|
||||||
|
if (start>=0 && end>=start) {
|
||||||
|
fileName=line.mid(start+11,end-start-11);
|
||||||
|
}
|
||||||
|
#ifdef SUPERVERBOSE
|
||||||
|
qDebug("HttpRequest: multipart field=%s, filename=%s",fieldName.data(),fileName.data());
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
qDebug("HttpRequest: ignoring unsupported content part %s",line.data());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (line.isEmpty()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef SUPERVERBOSE
|
||||||
|
qDebug("HttpRequest: reading multpart data");
|
||||||
|
#endif
|
||||||
|
QTemporaryFile* uploadedFile=0;
|
||||||
|
QByteArray fieldValue;
|
||||||
|
while (!tempFile.atEnd() && !finished && !tempFile.error()) {
|
||||||
|
QByteArray line=tempFile.readLine(65536);
|
||||||
|
if (line.startsWith("--"+boundary)) {
|
||||||
|
// Boundary found. Until now we have collected 2 bytes too much,
|
||||||
|
// so remove them from the last result
|
||||||
|
if (fileName.isEmpty() && !fieldName.isEmpty()) {
|
||||||
|
// last field was a form field
|
||||||
|
fieldValue.remove(fieldValue.size()-2,2);
|
||||||
|
parameters.insert(fieldName,fieldValue);
|
||||||
|
qDebug("HttpRequest: set parameter %s=%s",fieldName.data(),fieldValue.data());
|
||||||
|
}
|
||||||
|
else if (!fileName.isEmpty() && !fieldName.isEmpty()) {
|
||||||
|
// last field was a file
|
||||||
|
#ifdef SUPERVERBOSE
|
||||||
|
qDebug("HttpRequest: finishing writing to uploaded file");
|
||||||
|
#endif
|
||||||
|
uploadedFile->resize(uploadedFile->size()-2);
|
||||||
|
uploadedFile->flush();
|
||||||
|
uploadedFile->seek(0);
|
||||||
|
parameters.insert(fieldName,fileName);
|
||||||
|
qDebug("HttpRequest: set parameter %s=%s",fieldName.data(),fileName.data());
|
||||||
|
uploadedFiles.insert(fieldName,uploadedFile);
|
||||||
|
qDebug("HttpRequest: uploaded file size is %i",(int) uploadedFile->size());
|
||||||
|
}
|
||||||
|
if (line.contains(boundary+"--")) {
|
||||||
|
finished=true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (fileName.isEmpty() && !fieldName.isEmpty()) {
|
||||||
|
// this is a form field.
|
||||||
|
currentSize+=line.size();
|
||||||
|
fieldValue.append(line);
|
||||||
|
}
|
||||||
|
else if (!fileName.isEmpty() && !fieldName.isEmpty()) {
|
||||||
|
// this is a file
|
||||||
|
if (!uploadedFile) {
|
||||||
|
uploadedFile=new QTemporaryFile();
|
||||||
|
uploadedFile->open();
|
||||||
|
}
|
||||||
|
uploadedFile->write(line);
|
||||||
|
if (uploadedFile->error()) {
|
||||||
|
qCritical("HttpRequest: error writing temp file, %s",qPrintable(uploadedFile->errorString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (tempFile.error()) {
|
||||||
|
qCritical("HttpRequest: cannot read temp file, %s",qPrintable(tempFile.errorString()));
|
||||||
|
}
|
||||||
|
#ifdef SUPERVERBOSE
|
||||||
|
qDebug("HttpRequest: finished parsing multipart temp file");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpRequest::~HttpRequest() {
|
||||||
|
foreach(QByteArray key, uploadedFiles.keys()) {
|
||||||
|
QTemporaryFile* file=uploadedFiles.value(key);
|
||||||
|
file->close();
|
||||||
|
delete file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QTemporaryFile* HttpRequest::getUploadedFile(const QByteArray fieldName) {
|
||||||
|
return uploadedFiles.value(fieldName);
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray HttpRequest::getCookie(const QByteArray& name) const {
|
||||||
|
return cookies.value(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get the map of cookies */
|
||||||
|
QMap<QByteArray,QByteArray>& HttpRequest::getCookieMap() {
|
||||||
|
return cookies;
|
||||||
|
}
|
||||||
|
|
214
YACReaderLibrary/server/lib/bfHttpServer/httprequest.h
Normal file
214
YACReaderLibrary/server/lib/bfHttpServer/httprequest.h
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@author Stefan Frings
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef HTTPREQUEST_H
|
||||||
|
#define HTTPREQUEST_H
|
||||||
|
|
||||||
|
#include <QByteArray>
|
||||||
|
#include <QTcpSocket>
|
||||||
|
#include <QMap>
|
||||||
|
#include <QMultiMap>
|
||||||
|
#include <QSettings>
|
||||||
|
#include <QTemporaryFile>
|
||||||
|
#include <QUuid>
|
||||||
|
|
||||||
|
/**
|
||||||
|
This object represents a single HTTP request. It reads the request
|
||||||
|
from a TCP socket and provides getters for the individual parts
|
||||||
|
of the request.
|
||||||
|
<p>
|
||||||
|
The follwing config settings are required:
|
||||||
|
<code><pre>
|
||||||
|
maxRequestSize=16000
|
||||||
|
maxMultiPartSize=1000000
|
||||||
|
</pre></code>
|
||||||
|
<p>
|
||||||
|
MaxRequestSize is the maximum size of a HTTP request. In case of
|
||||||
|
multipart/form-data requests (also known as file-upload), the maximum
|
||||||
|
size of the body must not exceed maxMultiPartSize.
|
||||||
|
The body is always a little larger than the file itself.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class HttpRequest {
|
||||||
|
Q_DISABLE_COPY(HttpRequest)
|
||||||
|
friend class HttpSessionStore;
|
||||||
|
public:
|
||||||
|
|
||||||
|
/** Values for getStatus() */
|
||||||
|
enum RequestStatus {waitForRequest, waitForHeader, waitForBody, complete, abort};
|
||||||
|
|
||||||
|
/**
|
||||||
|
Constructor.
|
||||||
|
@param settings Configuration settings
|
||||||
|
*/
|
||||||
|
HttpRequest(QSettings* settings);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Destructor.
|
||||||
|
*/
|
||||||
|
virtual ~HttpRequest();
|
||||||
|
|
||||||
|
/**
|
||||||
|
Read the request from a socket. This method must be called repeatedly
|
||||||
|
until the status is RequestStatus::complete or RequestStatus::abort.
|
||||||
|
@param socket Source of the data
|
||||||
|
*/
|
||||||
|
void readFromSocket(QTcpSocket& socket);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Get the status of this reqeust.
|
||||||
|
@see RequestStatus
|
||||||
|
*/
|
||||||
|
RequestStatus getStatus() const;
|
||||||
|
|
||||||
|
/** Get the method of the HTTP request (e.g. "GET") */
|
||||||
|
QByteArray getMethod() const;
|
||||||
|
|
||||||
|
/** Get the decoded path of the HTPP request (e.g. "/index.html") */
|
||||||
|
QByteArray getPath() const;
|
||||||
|
|
||||||
|
/** Get the version of the HTPP request (e.g. "HTTP/1.1") */
|
||||||
|
QByteArray getVersion() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Get the value of a HTTP request header.
|
||||||
|
@param name Name of the header
|
||||||
|
@return If the header occurs multiple times, only the last
|
||||||
|
one is returned.
|
||||||
|
*/
|
||||||
|
QByteArray getHeader(const QByteArray& name) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Get the values of a HTTP request header.
|
||||||
|
@param name Name of the header
|
||||||
|
*/
|
||||||
|
QList<QByteArray> getHeaders(const QByteArray& name) const;
|
||||||
|
|
||||||
|
/** Get all HTTP request headers */
|
||||||
|
QMultiMap<QByteArray,QByteArray> getHeaderMap() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Get the value of a HTTP request parameter.
|
||||||
|
@param name Name of the parameter
|
||||||
|
@return If the parameter occurs multiple times, only the last
|
||||||
|
one is returned.
|
||||||
|
*/
|
||||||
|
QByteArray getParameter(const QByteArray& name) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Get the values of a HTTP request parameter.
|
||||||
|
@param name Name of the parameter
|
||||||
|
*/
|
||||||
|
QList<QByteArray> getParameters(const QByteArray& name) const;
|
||||||
|
|
||||||
|
/** Get all HTTP request parameters */
|
||||||
|
QMultiMap<QByteArray,QByteArray> getParameterMap() const;
|
||||||
|
|
||||||
|
/** Get the HTTP request body */
|
||||||
|
QByteArray getBody() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Decode an URL parameter.
|
||||||
|
E.g. replace "%23" by '#' and replace '+' by ' '
|
||||||
|
@param source The url encoded string
|
||||||
|
*/
|
||||||
|
static QByteArray urlDecode(const QByteArray source);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Get an uploaded file. The file is already open. It will
|
||||||
|
be closed and deleted by the destructor of this HttpRequest
|
||||||
|
object (after processing the request).
|
||||||
|
<p>
|
||||||
|
For uploaded files, the method getParameters() returns
|
||||||
|
the original fileName as provided by the calling web browser.
|
||||||
|
*/
|
||||||
|
QTemporaryFile* getUploadedFile(const QByteArray fieldName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Get the value of a cookie
|
||||||
|
@param name Name of the cookie
|
||||||
|
*/
|
||||||
|
QByteArray getCookie(const QByteArray& name) const;
|
||||||
|
|
||||||
|
/** Get the map of cookies */
|
||||||
|
QMap<QByteArray,QByteArray>& getCookieMap();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
/** Request headers */
|
||||||
|
QMultiMap<QByteArray,QByteArray> headers;
|
||||||
|
|
||||||
|
/** Parameters of the request */
|
||||||
|
QMultiMap<QByteArray,QByteArray> parameters;
|
||||||
|
|
||||||
|
/** Uploaded files of the request, key is the field name. */
|
||||||
|
QMap<QByteArray,QTemporaryFile*> uploadedFiles;
|
||||||
|
|
||||||
|
/** Received cookies */
|
||||||
|
QMap<QByteArray,QByteArray> cookies;
|
||||||
|
|
||||||
|
/** Storage for raw body data */
|
||||||
|
QByteArray bodyData;
|
||||||
|
|
||||||
|
/** Request method */
|
||||||
|
QByteArray method;
|
||||||
|
|
||||||
|
/** Request path (in raw encoded format) */
|
||||||
|
QByteArray path;
|
||||||
|
|
||||||
|
/** Request protocol version */
|
||||||
|
QByteArray version;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Status of this request.
|
||||||
|
@see RequestStatus
|
||||||
|
*/
|
||||||
|
RequestStatus status;
|
||||||
|
|
||||||
|
/** Maximum size of requests in bytes. */
|
||||||
|
int maxSize;
|
||||||
|
|
||||||
|
/** Maximum allowed size of multipart forms in bytes. */
|
||||||
|
int maxMultiPartSize;
|
||||||
|
|
||||||
|
/** Directory for temp files */
|
||||||
|
QString tempDir;
|
||||||
|
|
||||||
|
/** Current size */
|
||||||
|
int currentSize;
|
||||||
|
|
||||||
|
/** Expected size of body */
|
||||||
|
int expectedBodySize;
|
||||||
|
|
||||||
|
/** Name of the current header, or empty if no header is being processed */
|
||||||
|
QByteArray currentHeader;
|
||||||
|
|
||||||
|
/** Boundary of multipart/form-data body. Empty if there is no such header */
|
||||||
|
QByteArray boundary;
|
||||||
|
|
||||||
|
/** Temp file, that is used to store the multipart/form-data body */
|
||||||
|
QTemporaryFile tempFile;
|
||||||
|
|
||||||
|
/** Parset he multipart body, that has been stored in the temp file. */
|
||||||
|
void parseMultiPartFile();
|
||||||
|
|
||||||
|
/** Sub-procedure of readFromSocket(), read the first line of a request. */
|
||||||
|
void readRequest(QTcpSocket& socket);
|
||||||
|
|
||||||
|
/** Sub-procedure of readFromSocket(), read header lines. */
|
||||||
|
void readHeader(QTcpSocket& socket);
|
||||||
|
|
||||||
|
/** Sub-procedure of readFromSocket(), read the request body. */
|
||||||
|
void readBody(QTcpSocket& socket);
|
||||||
|
|
||||||
|
/** Sub-procedure of readFromSocket(), extract and decode request parameters. */
|
||||||
|
void decodeRequestParams();
|
||||||
|
|
||||||
|
/** Sub-procedure of readFromSocket(), extract cookies from headers */
|
||||||
|
void extractCookies();
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // HTTPREQUEST_H
|
@ -0,0 +1,19 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@author Stefan Frings
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "httprequesthandler.h"
|
||||||
|
|
||||||
|
HttpRequestHandler::HttpRequestHandler(QObject* parent)
|
||||||
|
: QObject(parent)
|
||||||
|
{}
|
||||||
|
|
||||||
|
HttpRequestHandler::~HttpRequestHandler() {}
|
||||||
|
|
||||||
|
void HttpRequestHandler::service(HttpRequest& request, HttpResponse& response) {
|
||||||
|
qCritical("HttpRequestHandler: you need to override the dispatch() function");
|
||||||
|
qDebug("HttpRequestHandler: request=%s %s %s",request.getMethod().data(),request.getPath().data(),request.getVersion().data());
|
||||||
|
response.setStatus(501,"not implemented");
|
||||||
|
response.write("501 not implemented",true);
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@author Stefan Frings
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef HTTPREQUESTHANDLER_H
|
||||||
|
#define HTTPREQUESTHANDLER_H
|
||||||
|
|
||||||
|
#include "httprequest.h"
|
||||||
|
#include "httpresponse.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
The request handler generates a response for each HTTP request. Web Applications
|
||||||
|
usually have one central request handler that maps incoming requests to several
|
||||||
|
controllers (servlets) based on the requested path.
|
||||||
|
<p>
|
||||||
|
You need to override the service() method or you will always get an HTTP error 501.
|
||||||
|
<p>
|
||||||
|
@warning Be aware that the main request handler instance must be created on the heap and
|
||||||
|
that it is used by multiple threads simultaneously.
|
||||||
|
@see StaticFileController which delivers static local files.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class HttpRequestHandler : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
Q_DISABLE_COPY(HttpRequestHandler)
|
||||||
|
public:
|
||||||
|
|
||||||
|
/** Constructor */
|
||||||
|
HttpRequestHandler(QObject* parent=0);
|
||||||
|
|
||||||
|
/** Destructor */
|
||||||
|
virtual ~HttpRequestHandler();
|
||||||
|
|
||||||
|
/**
|
||||||
|
Generate a response for an incoming HTTP request.
|
||||||
|
@param request The received HTTP request
|
||||||
|
@param response Must be used to return the response
|
||||||
|
@warning This method must be thread safe
|
||||||
|
*/
|
||||||
|
virtual void service(HttpRequest& request, HttpResponse& response);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // HTTPREQUESTHANDLER_H
|
122
YACReaderLibrary/server/lib/bfHttpServer/httpresponse.cpp
Normal file
122
YACReaderLibrary/server/lib/bfHttpServer/httpresponse.cpp
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@author Stefan Frings
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "httpresponse.h"
|
||||||
|
|
||||||
|
HttpResponse::HttpResponse(QTcpSocket* socket) {
|
||||||
|
this->socket=socket;
|
||||||
|
statusCode=200;
|
||||||
|
statusText="OK";
|
||||||
|
sentHeaders=false;
|
||||||
|
sentLastPart=false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpResponse::setHeader(QByteArray name, QByteArray value) {
|
||||||
|
Q_ASSERT(sentHeaders==false);
|
||||||
|
headers.insert(name,value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpResponse::setHeader(QByteArray name, int value) {
|
||||||
|
Q_ASSERT(sentHeaders==false);
|
||||||
|
headers.insert(name,QByteArray::number(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
QMap<QByteArray,QByteArray>& HttpResponse::getHeaders() {
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpResponse::setStatus(int statusCode, QByteArray description) {
|
||||||
|
this->statusCode=statusCode;
|
||||||
|
statusText=description;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpResponse::writeHeaders() {
|
||||||
|
Q_ASSERT(sentHeaders==false);
|
||||||
|
QByteArray buffer;
|
||||||
|
buffer.append("HTTP/1.1 ");
|
||||||
|
buffer.append(QByteArray::number(statusCode));
|
||||||
|
buffer.append(' ');
|
||||||
|
buffer.append(statusText);
|
||||||
|
buffer.append("\r\n");
|
||||||
|
foreach(QByteArray name, headers.keys()) {
|
||||||
|
buffer.append(name);
|
||||||
|
buffer.append(": ");
|
||||||
|
buffer.append(headers.value(name));
|
||||||
|
buffer.append("\r\n");
|
||||||
|
}
|
||||||
|
foreach(HttpCookie cookie,cookies.values()) {
|
||||||
|
buffer.append("Set-Cookie: ");
|
||||||
|
buffer.append(cookie.toByteArray());
|
||||||
|
buffer.append("\r\n");
|
||||||
|
}
|
||||||
|
buffer.append("\r\n");
|
||||||
|
writeToSocket(buffer);
|
||||||
|
sentHeaders=true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpResponse::writeToSocket(QByteArray data) {
|
||||||
|
int remaining=data.size();
|
||||||
|
char* ptr=data.data();
|
||||||
|
while (socket->isOpen() && remaining>0) {
|
||||||
|
int written=socket->write(data);
|
||||||
|
ptr+=written;
|
||||||
|
remaining-=written;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpResponse::write(QByteArray data, bool lastPart) {
|
||||||
|
Q_ASSERT(sentLastPart==false);
|
||||||
|
if (sentHeaders==false) {
|
||||||
|
QByteArray connectionMode=headers.value("Connection");
|
||||||
|
if (!headers.contains("Content-Length") && !headers.contains("Transfer-Encoding") && connectionMode!="close" && connectionMode!="Close") {
|
||||||
|
if (!lastPart) {
|
||||||
|
headers.insert("Transfer-Encoding","chunked");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
headers.insert("Content-Length",QByteArray::number(data.size()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writeHeaders();
|
||||||
|
}
|
||||||
|
bool chunked=headers.value("Transfer-Encoding")=="chunked" || headers.value("Transfer-Encoding")=="Chunked";
|
||||||
|
if (chunked) {
|
||||||
|
if (data.size()>0) {
|
||||||
|
QByteArray buffer=QByteArray::number(data.size(),16);
|
||||||
|
buffer.append("\r\n");
|
||||||
|
writeToSocket(buffer);
|
||||||
|
writeToSocket(data);
|
||||||
|
writeToSocket("\r\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
writeToSocket(data);
|
||||||
|
}
|
||||||
|
if (lastPart) {
|
||||||
|
if (chunked) {
|
||||||
|
writeToSocket("0\r\n\r\n");
|
||||||
|
}
|
||||||
|
else if (!headers.contains("Content-Length")) {
|
||||||
|
socket->disconnectFromHost();
|
||||||
|
}
|
||||||
|
sentLastPart=true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool HttpResponse::hasSentLastPart() const {
|
||||||
|
return sentLastPart;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void HttpResponse::setCookie(const HttpCookie& cookie) {
|
||||||
|
Q_ASSERT(sentHeaders==false);
|
||||||
|
if (!cookie.getName().isEmpty()) {
|
||||||
|
cookies.insert(cookie.getName(),cookie);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QMap<QByteArray,HttpCookie>& HttpResponse::getCookies() {
|
||||||
|
return cookies;
|
||||||
|
}
|
134
YACReaderLibrary/server/lib/bfHttpServer/httpresponse.h
Normal file
134
YACReaderLibrary/server/lib/bfHttpServer/httpresponse.h
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@author Stefan Frings
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef HTTPRESPONSE_H
|
||||||
|
#define HTTPRESPONSE_H
|
||||||
|
|
||||||
|
#include <QMap>
|
||||||
|
#include <QString>
|
||||||
|
#include <QTcpSocket>
|
||||||
|
#include "httpcookie.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
This object represents a HTTP response, in particular the response headers.
|
||||||
|
<p>
|
||||||
|
Example code for proper response generation:
|
||||||
|
<code><pre>
|
||||||
|
response.setStatus(200,"OK"); // optional, because this is the default
|
||||||
|
response.writeBody("Hello");
|
||||||
|
response.writeBody("World!",true);
|
||||||
|
</pre></code>
|
||||||
|
<p>
|
||||||
|
Example how to return an error:
|
||||||
|
<code><pre>
|
||||||
|
response.setStatus(500,"server error");
|
||||||
|
response.write("The request cannot be processed because the servers is broken",true);
|
||||||
|
</pre></code>
|
||||||
|
<p>
|
||||||
|
For performance reason, writing a single or few large packets is better than writing
|
||||||
|
many small packets. In case of large responses (e.g. file downloads), a Content-Length
|
||||||
|
header should be set before calling write(). Web Browsers use that information to display
|
||||||
|
a progress bar.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class HttpResponse {
|
||||||
|
Q_DISABLE_COPY(HttpResponse)
|
||||||
|
public:
|
||||||
|
|
||||||
|
/**
|
||||||
|
Constructor.
|
||||||
|
@param socket used to write the response
|
||||||
|
*/
|
||||||
|
HttpResponse(QTcpSocket* socket);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Set a HTTP response header
|
||||||
|
@param name name of the header
|
||||||
|
@param value value of the header
|
||||||
|
*/
|
||||||
|
void setHeader(QByteArray name, QByteArray value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Set a HTTP response header
|
||||||
|
@param name name of the header
|
||||||
|
@param value value of the header
|
||||||
|
*/
|
||||||
|
void setHeader(QByteArray name, int value);
|
||||||
|
|
||||||
|
/** Get the map of HTTP response headers */
|
||||||
|
QMap<QByteArray,QByteArray>& getHeaders();
|
||||||
|
|
||||||
|
/** Get the map of cookies */
|
||||||
|
QMap<QByteArray,HttpCookie>& getCookies();
|
||||||
|
|
||||||
|
/**
|
||||||
|
Set status code and description. The default is 200,OK.
|
||||||
|
*/
|
||||||
|
void setStatus(int statusCode, QByteArray description=QByteArray());
|
||||||
|
|
||||||
|
/**
|
||||||
|
Write body data to the socket.
|
||||||
|
<p>
|
||||||
|
The HTTP status line and headers are sent automatically before the first
|
||||||
|
byte of the body gets sent.
|
||||||
|
<p>
|
||||||
|
If the response contains only a single chunk (indicated by lastPart=true),
|
||||||
|
the response is transferred in traditional mode with a Content-Length
|
||||||
|
header, which is automatically added if not already set before.
|
||||||
|
<p>
|
||||||
|
Otherwise, each part is transferred in chunked mode.
|
||||||
|
@param data Data bytes of the body
|
||||||
|
@param lastPart Indicator, if this is the last part of the response.
|
||||||
|
*/
|
||||||
|
void write(QByteArray data, bool lastPart=false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Indicates wheter the body has been sent completely. Used by the connection
|
||||||
|
handler to terminate the body automatically when necessary.
|
||||||
|
*/
|
||||||
|
bool hasSentLastPart() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Set a cookie. Cookies are sent together with the headers when the first
|
||||||
|
call to write() occurs.
|
||||||
|
*/
|
||||||
|
void setCookie(const HttpCookie& cookie);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
/** Request headers */
|
||||||
|
QMap<QByteArray,QByteArray> headers;
|
||||||
|
|
||||||
|
/** Socket for writing output */
|
||||||
|
QTcpSocket* socket;
|
||||||
|
|
||||||
|
/** HTTP status code*/
|
||||||
|
int statusCode;
|
||||||
|
|
||||||
|
/** HTTP status code description */
|
||||||
|
QByteArray statusText;
|
||||||
|
|
||||||
|
/** Indicator whether headers have been sent */
|
||||||
|
bool sentHeaders;
|
||||||
|
|
||||||
|
/** Indicator whether the body has been sent completely */
|
||||||
|
bool sentLastPart;
|
||||||
|
|
||||||
|
/** Cookies */
|
||||||
|
QMap<QByteArray,HttpCookie> cookies;
|
||||||
|
|
||||||
|
/** Write raw data to the socket. This method blocks until all bytes have been passed to the TCP buffer */
|
||||||
|
void writeToSocket(QByteArray data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Write the response HTTP status and headers to the socket.
|
||||||
|
Calling this method is optional, because writeBody() calls
|
||||||
|
it automatically when required.
|
||||||
|
*/
|
||||||
|
void writeHeaders();
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // HTTPRESPONSE_H
|
158
YACReaderLibrary/server/lib/bfHttpServer/httpsession.cpp
Normal file
158
YACReaderLibrary/server/lib/bfHttpServer/httpsession.cpp
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@author Stefan Frings
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "httpsession.h"
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QUuid>
|
||||||
|
|
||||||
|
|
||||||
|
HttpSession::HttpSession(bool canStore) {
|
||||||
|
if (canStore) {
|
||||||
|
dataPtr=new HttpSessionData();
|
||||||
|
dataPtr->refCount=1;
|
||||||
|
dataPtr->lastAccess=QDateTime::currentMSecsSinceEpoch();
|
||||||
|
dataPtr->id=QUuid::createUuid().toString().toAscii();
|
||||||
|
#ifdef SUPERVERBOSE
|
||||||
|
qDebug("HttpSession: created new session data with id %s",dataPtr->id.data());
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
dataPtr=0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpSession::HttpSession(const HttpSession& other) {
|
||||||
|
dataPtr=other.dataPtr;
|
||||||
|
if (dataPtr) {
|
||||||
|
dataPtr->lock.lockForWrite();
|
||||||
|
dataPtr->refCount++;
|
||||||
|
#ifdef SUPERVERBOSE
|
||||||
|
qDebug("HttpSession: refCount of %s is %i",dataPtr->id.data(),dataPtr->refCount);
|
||||||
|
#endif
|
||||||
|
dataPtr->lock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpSession& HttpSession::operator= (const HttpSession& other) {
|
||||||
|
HttpSessionData* oldPtr=dataPtr;
|
||||||
|
dataPtr=other.dataPtr;
|
||||||
|
if (dataPtr) {
|
||||||
|
dataPtr->lock.lockForWrite();
|
||||||
|
dataPtr->refCount++;
|
||||||
|
#ifdef SUPERVERBOSE
|
||||||
|
qDebug("HttpSession: refCount of %s is %i",dataPtr->id.data(),dataPtr->refCount);
|
||||||
|
#endif
|
||||||
|
dataPtr->lastAccess=QDateTime::currentMSecsSinceEpoch();
|
||||||
|
dataPtr->lock.unlock();
|
||||||
|
}
|
||||||
|
if (oldPtr) {
|
||||||
|
int refCount;
|
||||||
|
oldPtr->lock.lockForRead();
|
||||||
|
refCount=oldPtr->refCount--;
|
||||||
|
#ifdef SUPERVERBOSE
|
||||||
|
qDebug("HttpSession: refCount of %s is %i",oldPtr->id.data(),oldPtr->refCount);
|
||||||
|
#endif
|
||||||
|
oldPtr->lock.unlock();
|
||||||
|
if (refCount==0) {
|
||||||
|
delete oldPtr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpSession::~HttpSession() {
|
||||||
|
if (dataPtr) {
|
||||||
|
int refCount;
|
||||||
|
dataPtr->lock.lockForRead();
|
||||||
|
refCount=--dataPtr->refCount;
|
||||||
|
#ifdef SUPERVERBOSE
|
||||||
|
qDebug("HttpSession: refCount of %s is %i",dataPtr->id.data(),dataPtr->refCount);
|
||||||
|
#endif
|
||||||
|
dataPtr->lock.unlock();
|
||||||
|
if (refCount==0) {
|
||||||
|
qDebug("HttpSession: deleting data");
|
||||||
|
delete dataPtr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
QByteArray HttpSession::getId() const {
|
||||||
|
if (dataPtr) {
|
||||||
|
return dataPtr->id;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return QByteArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HttpSession::isNull() const {
|
||||||
|
return dataPtr==0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpSession::set(const QByteArray& key, const QVariant& value) {
|
||||||
|
if (dataPtr) {
|
||||||
|
dataPtr->lock.lockForWrite();
|
||||||
|
dataPtr->values.insert(key,value);
|
||||||
|
dataPtr->lock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpSession::remove(const QByteArray& key) {
|
||||||
|
if (dataPtr) {
|
||||||
|
dataPtr->lock.lockForWrite();
|
||||||
|
dataPtr->values.remove(key);
|
||||||
|
dataPtr->lock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant HttpSession::get(const QByteArray& key) const {
|
||||||
|
QVariant value;
|
||||||
|
if (dataPtr) {
|
||||||
|
dataPtr->lock.lockForRead();
|
||||||
|
value=dataPtr->values.value(key);
|
||||||
|
dataPtr->lock.unlock();
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HttpSession::contains(const QByteArray& key) const {
|
||||||
|
bool found=false;
|
||||||
|
if (dataPtr) {
|
||||||
|
dataPtr->lock.lockForRead();
|
||||||
|
found=dataPtr->values.contains(key);
|
||||||
|
dataPtr->lock.unlock();
|
||||||
|
}
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
QMap<QByteArray,QVariant> HttpSession::getAll() const {
|
||||||
|
QMap<QByteArray,QVariant> values;
|
||||||
|
if (dataPtr) {
|
||||||
|
dataPtr->lock.lockForRead();
|
||||||
|
values=dataPtr->values;
|
||||||
|
dataPtr->lock.unlock();
|
||||||
|
}
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
qint64 HttpSession::getLastAccess() const {
|
||||||
|
qint64 value=0;
|
||||||
|
if (dataPtr) {
|
||||||
|
dataPtr->lock.lockForRead();
|
||||||
|
value=dataPtr->lastAccess;
|
||||||
|
dataPtr->lock.unlock();
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void HttpSession::setLastAccess() {
|
||||||
|
if (dataPtr) {
|
||||||
|
dataPtr->lock.lockForRead();
|
||||||
|
dataPtr->lastAccess=QDateTime::currentMSecsSinceEpoch();
|
||||||
|
dataPtr->lock.unlock();
|
||||||
|
}
|
||||||
|
}
|
118
YACReaderLibrary/server/lib/bfHttpServer/httpsession.h
Normal file
118
YACReaderLibrary/server/lib/bfHttpServer/httpsession.h
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@author Stefan Frings
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef HTTPSESSION_H
|
||||||
|
#define HTTPSESSION_H
|
||||||
|
|
||||||
|
#include <QByteArray>
|
||||||
|
#include <QVariant>
|
||||||
|
#include <QReadWriteLock>
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
This class stores data for a single HTTP session.
|
||||||
|
A session can store any number of key/value pairs. This class uses implicit
|
||||||
|
sharing for read and write access. This class is thread safe.
|
||||||
|
@see HttpSessionStore should be used to create and get instances of this class.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class HttpSession {
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
/**
|
||||||
|
Constructor.
|
||||||
|
@param canStore The session can store data, if this parameter is true.
|
||||||
|
Otherwise all calls to set() and remove() do not have any effect.
|
||||||
|
*/
|
||||||
|
HttpSession(bool canStore=false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Copy constructor. Creates another HttpSession object that shares the
|
||||||
|
data of the other object.
|
||||||
|
*/
|
||||||
|
HttpSession(const HttpSession& other);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Copy operator. Detaches from the current shared data and attaches to
|
||||||
|
the data of the other object.
|
||||||
|
*/
|
||||||
|
HttpSession& operator= (const HttpSession& other);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
Destructor. Detaches from the shared data.
|
||||||
|
*/
|
||||||
|
virtual ~HttpSession();
|
||||||
|
|
||||||
|
/** Get the unique ID of this session. This method is thread safe. */
|
||||||
|
QByteArray getId() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Null sessions cannot store data. All calls to set() and remove()
|
||||||
|
do not have any effect.This method is thread safe.
|
||||||
|
*/
|
||||||
|
bool isNull() const;
|
||||||
|
|
||||||
|
/** Set a value. This method is thread safe. */
|
||||||
|
void set(const QByteArray& key, const QVariant& value);
|
||||||
|
|
||||||
|
/** Remove a value. This method is thread safe. */
|
||||||
|
void remove(const QByteArray& key);
|
||||||
|
|
||||||
|
/** Get a value. This method is thread safe. */
|
||||||
|
QVariant get(const QByteArray& key) const;
|
||||||
|
|
||||||
|
/** Check if a key exists. This method is thread safe. */
|
||||||
|
bool contains(const QByteArray& key) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Get a copy of all data stored in this session.
|
||||||
|
Changes to the session do not affect the copy and vice versa.
|
||||||
|
This method is thread safe.
|
||||||
|
*/
|
||||||
|
QMap<QByteArray,QVariant> getAll() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Get the timestamp of last access. That is the time when the last
|
||||||
|
HttpSessionStore::getSession() has been called.
|
||||||
|
This method is thread safe.
|
||||||
|
*/
|
||||||
|
qint64 getLastAccess() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Set the timestamp of last access, to renew the timeout period.
|
||||||
|
Called by HttpSessionStore::getSession().
|
||||||
|
This method is thread safe.
|
||||||
|
*/
|
||||||
|
void setLastAccess();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
struct HttpSessionData {
|
||||||
|
|
||||||
|
/** Unique ID */
|
||||||
|
QByteArray id;
|
||||||
|
|
||||||
|
/** Timestamp of last access, set by the HttpSessionStore */
|
||||||
|
qint64 lastAccess;
|
||||||
|
|
||||||
|
/** Reference counter */
|
||||||
|
int refCount;
|
||||||
|
|
||||||
|
/** Used to synchronize threads */
|
||||||
|
QReadWriteLock lock;
|
||||||
|
|
||||||
|
/** Storage for the key/value pairs; */
|
||||||
|
QMap<QByteArray,QVariant> values;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Pointer to the shared data. */
|
||||||
|
HttpSessionData* dataPtr;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // HTTPSESSION_H
|
107
YACReaderLibrary/server/lib/bfHttpServer/httpsessionstore.cpp
Normal file
107
YACReaderLibrary/server/lib/bfHttpServer/httpsessionstore.cpp
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@author Stefan Frings
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "httpsessionstore.h"
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QUuid>
|
||||||
|
|
||||||
|
HttpSessionStore::HttpSessionStore(QSettings* settings, QObject* parent)
|
||||||
|
:QObject(parent)
|
||||||
|
{
|
||||||
|
this->settings=settings;
|
||||||
|
connect(&cleanupTimer,SIGNAL(timeout()),this,SLOT(timerEvent()));
|
||||||
|
cleanupTimer.start(60000);
|
||||||
|
cookieName=settings->value("cookieName","sessionid").toByteArray();
|
||||||
|
expirationTime=settings->value("expirationTime",3600000).toInt();
|
||||||
|
qDebug("HttpSessionStore: Sessions expire after %i milliseconds",expirationTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpSessionStore::~HttpSessionStore()
|
||||||
|
{
|
||||||
|
cleanupTimer.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray HttpSessionStore::getSessionId(HttpRequest& request, HttpResponse& response) {
|
||||||
|
// The session ID in the response has priority because this one will be used in the next request.
|
||||||
|
mutex.lock();
|
||||||
|
// Get the session ID from the response cookie
|
||||||
|
QByteArray sessionId=response.getCookies().value(cookieName).getValue();
|
||||||
|
if (sessionId.isEmpty()) {
|
||||||
|
// Get the session ID from the request cookie
|
||||||
|
sessionId=request.getCookie(cookieName);
|
||||||
|
}
|
||||||
|
// Clear the session ID if there is no such session in the storage.
|
||||||
|
if (!sessionId.isEmpty()) {
|
||||||
|
if (!sessions.contains(sessionId)) {
|
||||||
|
qDebug("HttpSessionStore: received invalid session cookie with ID %s",sessionId.data());
|
||||||
|
sessionId.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mutex.unlock();
|
||||||
|
return sessionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpSession HttpSessionStore::getSession(HttpRequest& request, HttpResponse& response, bool allowCreate) {
|
||||||
|
QByteArray sessionId=getSessionId(request,response);
|
||||||
|
mutex.lock();
|
||||||
|
if (!sessionId.isEmpty()) {
|
||||||
|
HttpSession session=sessions.value(sessionId);
|
||||||
|
if (!session.isNull()) {
|
||||||
|
mutex.unlock();
|
||||||
|
session.setLastAccess();
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Need to create a new session
|
||||||
|
if (allowCreate) {
|
||||||
|
QByteArray cookieName=settings->value("cookieName","sessionid").toByteArray();
|
||||||
|
QByteArray cookiePath=settings->value("cookiePath").toByteArray();
|
||||||
|
QByteArray cookieComment=settings->value("cookieComment").toByteArray();
|
||||||
|
QByteArray cookieDomain=settings->value("cookieDomain").toByteArray();
|
||||||
|
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));
|
||||||
|
mutex.unlock();
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
// Return a null session
|
||||||
|
mutex.unlock();
|
||||||
|
return HttpSession();
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpSession HttpSessionStore::getSession(const QByteArray id) {
|
||||||
|
mutex.lock();
|
||||||
|
HttpSession session=sessions.value(id);
|
||||||
|
mutex.unlock();
|
||||||
|
session.setLastAccess();
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpSessionStore::timerEvent() {
|
||||||
|
// Todo: find a way to delete sessions only if no controller is accessing them
|
||||||
|
mutex.lock();
|
||||||
|
qint64 now=QDateTime::currentMSecsSinceEpoch();
|
||||||
|
QMap<QByteArray,HttpSession>::iterator i = sessions.begin();
|
||||||
|
while (i != sessions.end()) {
|
||||||
|
QMap<QByteArray,HttpSession>::iterator prev = i;
|
||||||
|
++i;
|
||||||
|
HttpSession session=prev.value();
|
||||||
|
qint64 lastAccess=session.getLastAccess();
|
||||||
|
if (now-lastAccess>expirationTime) {
|
||||||
|
qDebug("HttpSessionStore: session %s expired",session.getId().data());
|
||||||
|
sessions.erase(prev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mutex.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** Delete a session */
|
||||||
|
void HttpSessionStore::removeSession(HttpSession session) {
|
||||||
|
mutex.lock();
|
||||||
|
sessions.remove(session.getId());
|
||||||
|
mutex.unlock();
|
||||||
|
}
|
104
YACReaderLibrary/server/lib/bfHttpServer/httpsessionstore.h
Normal file
104
YACReaderLibrary/server/lib/bfHttpServer/httpsessionstore.h
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@author Stefan Frings
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef HTTPSESSIONSTORE_H
|
||||||
|
#define HTTPSESSIONSTORE_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QMap>
|
||||||
|
#include <QTimer>
|
||||||
|
#include <QMutex>
|
||||||
|
#include "httpsession.h"
|
||||||
|
#include "httpresponse.h"
|
||||||
|
#include "httprequest.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
Stores HTTP sessions and deletes them when they have expired.
|
||||||
|
The following configuration settings are required in the config file:
|
||||||
|
<code><pre>
|
||||||
|
expirationTime=3600000
|
||||||
|
cookieName=sessionid
|
||||||
|
</pre></code>
|
||||||
|
The following additional configurations settings are optionally:
|
||||||
|
<code><pre>
|
||||||
|
cookiePath=/
|
||||||
|
cookieComment=Session ID
|
||||||
|
cookieDomain=stefanfrings.de
|
||||||
|
</pre></code>
|
||||||
|
*/
|
||||||
|
|
||||||
|
class HttpSessionStore : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
Q_DISABLE_COPY(HttpSessionStore);
|
||||||
|
public:
|
||||||
|
|
||||||
|
/** Constructor. */
|
||||||
|
HttpSessionStore(QSettings* settings, QObject* parent);
|
||||||
|
|
||||||
|
/** Destructor */
|
||||||
|
virtual ~HttpSessionStore();
|
||||||
|
|
||||||
|
/**
|
||||||
|
Get the ID of the current HTTP session, if it is valid.
|
||||||
|
This method is thread safe.
|
||||||
|
@warning Sessions may expire at any time, so subsequent calls of
|
||||||
|
getSession() might return a new session with a different ID.
|
||||||
|
@param request Used to get the session cookie
|
||||||
|
@param response Used to get and set the new session cookie
|
||||||
|
@return Empty string, if there is no valid session.
|
||||||
|
*/
|
||||||
|
QByteArray getSessionId(HttpRequest& request, HttpResponse& response);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Get the session of a HTTP request, eventually create a new one.
|
||||||
|
This method is thread safe. New sessions can only be created before
|
||||||
|
the first byte has been written to the HTTP response.
|
||||||
|
@param request Used to get the session cookie
|
||||||
|
@param response Used to get and set the new session cookie
|
||||||
|
@param allowCreate can be set to false, to disable the automatic creation of a new session.
|
||||||
|
@return If autoCreate is disabled, the function returns a null session if there is no session.
|
||||||
|
@see HttpSession::isNull()
|
||||||
|
*/
|
||||||
|
HttpSession getSession(HttpRequest& request, HttpResponse& response, bool allowCreate=true);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Get a HTTP session by it's ID number.
|
||||||
|
This method is thread safe.
|
||||||
|
@return If there is no such session, the function returns a null session.
|
||||||
|
@param id ID number of the session
|
||||||
|
@see HttpSession::isNull()
|
||||||
|
*/
|
||||||
|
HttpSession getSession(const QByteArray id);
|
||||||
|
|
||||||
|
/** Delete a session */
|
||||||
|
void removeSession(HttpSession session);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
/** Configuration settings */
|
||||||
|
QSettings* settings;
|
||||||
|
|
||||||
|
/** Storage for the sessions */
|
||||||
|
QMap<QByteArray,HttpSession> sessions;
|
||||||
|
|
||||||
|
/** Timer to remove expired sessions */
|
||||||
|
QTimer cleanupTimer;
|
||||||
|
|
||||||
|
/** Name of the session cookie */
|
||||||
|
QByteArray cookieName;
|
||||||
|
|
||||||
|
/** Time when sessions expire (in ms)*/
|
||||||
|
int expirationTime;
|
||||||
|
|
||||||
|
/** Used to synchronize threads */
|
||||||
|
QMutex mutex;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
|
||||||
|
/** Called every minute to cleanup expired sessions. */
|
||||||
|
void timerEvent();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // HTTPSESSIONSTORE_H
|
@ -0,0 +1,114 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@author Stefan Frings
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "staticfilecontroller.h"
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QDateTime>
|
||||||
|
|
||||||
|
StaticFileController::StaticFileController(QSettings* settings, QObject* parent)
|
||||||
|
:HttpRequestHandler(parent)
|
||||||
|
{
|
||||||
|
maxAge=settings->value("maxAge","60000").toInt();
|
||||||
|
encoding=settings->value("encoding","UTF-8").toString();
|
||||||
|
docroot=settings->value("path",".").toString();
|
||||||
|
// Convert relative path to absolute, based on the directory of the config file.
|
||||||
|
#ifdef Q_OS_WIN32
|
||||||
|
if (QDir::isRelativePath(docroot) && settings->format()!=QSettings::NativeFormat)
|
||||||
|
#else
|
||||||
|
if (QDir::isRelativePath(docroot))
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
QFileInfo configFile(settings->fileName());
|
||||||
|
docroot=QFileInfo(configFile.absolutePath(),docroot).absoluteFilePath();
|
||||||
|
}
|
||||||
|
qDebug("StaticFileController: docroot=%s, encoding=%s, maxAge=%i",qPrintable(docroot),qPrintable(encoding),maxAge);
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void StaticFileController::service(HttpRequest& request, HttpResponse& response) {
|
||||||
|
QByteArray path=request.getPath();
|
||||||
|
// Forbid access to files outside the docroot directory
|
||||||
|
if (path.startsWith("/..")) {
|
||||||
|
qWarning("StaticFileController: somebody attempted to access a file outside the docroot directory");
|
||||||
|
response.setStatus(403,"forbidden");
|
||||||
|
response.write("403 forbidden",true);
|
||||||
|
}
|
||||||
|
// Check if we have the file in cache
|
||||||
|
qint64 now=QDateTime::currentMSecsSinceEpoch();
|
||||||
|
CacheEntry* entry=cache.object(path);
|
||||||
|
if (entry && (cacheTimeout==0 || entry->created>now-cacheTimeout)) {
|
||||||
|
qDebug("StaticFileController: Cache hit for %s",path.data());
|
||||||
|
setContentType(path,response);
|
||||||
|
response.setHeader("Cache-Control","max-age="+QByteArray::number(maxAge/1000));
|
||||||
|
response.write(entry->document);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
qDebug("StaticFileController: Cache miss for %s",path.data());
|
||||||
|
// The file is not in cache.
|
||||||
|
// If the filename is a directory, append index.html.
|
||||||
|
if (QFileInfo(docroot+path).isDir()) {
|
||||||
|
path+="/index.html";
|
||||||
|
}
|
||||||
|
QFile file(docroot+path);
|
||||||
|
if (file.exists()) {
|
||||||
|
qDebug("StaticFileController: Open file %s",qPrintable(file.fileName()));
|
||||||
|
if (file.open(QIODevice::ReadOnly)) {
|
||||||
|
setContentType(path,response);
|
||||||
|
response.setHeader("Cache-Control","max-age="+QByteArray::number(maxAge/1000));
|
||||||
|
if (file.size()<=maxCachedFileSize) {
|
||||||
|
// Return the file content and store it also in the cache
|
||||||
|
entry=new CacheEntry();
|
||||||
|
while (!file.atEnd() && !file.error()) {
|
||||||
|
QByteArray buffer=file.read(65536);
|
||||||
|
response.write(buffer);
|
||||||
|
entry->document.append(buffer);
|
||||||
|
}
|
||||||
|
entry->created=now;
|
||||||
|
cache.insert(request.getPath(),entry,entry->document.size());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Return the file content, do not store in cache
|
||||||
|
while (!file.atEnd() && !file.error()) {
|
||||||
|
response.write(file.read(65536));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file.close();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
qWarning("StaticFileController: Cannot open existing file %s for reading",qPrintable(file.fileName()));
|
||||||
|
response.setStatus(403,"forbidden");
|
||||||
|
response.write("403 forbidden",true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
response.setStatus(404,"not found");
|
||||||
|
response.write("404 not found",true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void StaticFileController::setContentType(QString fileName, HttpResponse& response) const {
|
||||||
|
if (fileName.endsWith(".png")) {
|
||||||
|
response.setHeader("Content-Type", "image/png");
|
||||||
|
}
|
||||||
|
else if (fileName.endsWith(".jpg")) {
|
||||||
|
response.setHeader("Content-Type", "image/jpeg");
|
||||||
|
}
|
||||||
|
else if (fileName.endsWith(".gif")) {
|
||||||
|
response.setHeader("Content-Type", "image/gif");
|
||||||
|
}
|
||||||
|
else if (fileName.endsWith(".txt")) {
|
||||||
|
response.setHeader("Content-Type", qPrintable("text/plain; charset="+encoding));
|
||||||
|
}
|
||||||
|
else if (fileName.endsWith(".html") || fileName.endsWith(".htm")) {
|
||||||
|
response.setHeader("Content-Type", qPrintable("text/html; charset=charset="+encoding));
|
||||||
|
}
|
||||||
|
// Todo: add all of your content types
|
||||||
|
}
|
@ -0,0 +1,81 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@author Stefan Frings
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef STATICFILECONTROLLER_H
|
||||||
|
#define STATICFILECONTROLLER_H
|
||||||
|
|
||||||
|
#include "httprequest.h"
|
||||||
|
#include "httpresponse.h"
|
||||||
|
#include "httprequesthandler.h"
|
||||||
|
#include <QCache>
|
||||||
|
|
||||||
|
/**
|
||||||
|
Delivers static files. It is usually called by the applications main request handler when
|
||||||
|
the caller request a path that is mapped to static files.
|
||||||
|
<p>
|
||||||
|
The following settings are required in the config file:
|
||||||
|
<code><pre>
|
||||||
|
path=docroot
|
||||||
|
encoding=UTF-8
|
||||||
|
maxAge=60000
|
||||||
|
cacheTime=60000
|
||||||
|
cacheSize=1000000
|
||||||
|
maxCachedFileSize=65536
|
||||||
|
</pre></code>
|
||||||
|
The path is relative to the directory of the config file. In case of windows, if the
|
||||||
|
settings are in the registry, the path is relative to the current working directory.
|
||||||
|
<p>
|
||||||
|
The encoding is sent to the web browser in case of text and html files.
|
||||||
|
<p>
|
||||||
|
The cache improves performance of small files when loaded from a network
|
||||||
|
drive. Large files are not cached. Files are cached as long as possible,
|
||||||
|
when cacheTime=0. The maxAge value (in msec!) controls the remote browsers cache.
|
||||||
|
<p>
|
||||||
|
Do not instantiate this class in each request, because this would make the file cache
|
||||||
|
useless. Better create one instance during start-up and call it when the application
|
||||||
|
received a related HTTP request.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class StaticFileController : public HttpRequestHandler {
|
||||||
|
Q_OBJECT
|
||||||
|
Q_DISABLE_COPY(StaticFileController);
|
||||||
|
public:
|
||||||
|
|
||||||
|
/** Constructor */
|
||||||
|
StaticFileController(QSettings* settings, QObject* parent = 0);
|
||||||
|
|
||||||
|
/** Generates the response */
|
||||||
|
void service(HttpRequest& request, HttpResponse& response);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
/** Encoding of text files */
|
||||||
|
QString encoding;
|
||||||
|
|
||||||
|
/** Root directory of documents */
|
||||||
|
QString docroot;
|
||||||
|
|
||||||
|
/** Maximum age of files in the browser cache */
|
||||||
|
int maxAge;
|
||||||
|
|
||||||
|
struct CacheEntry {
|
||||||
|
QByteArray document;
|
||||||
|
qint64 created;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Timeout for each cached file */
|
||||||
|
int cacheTimeout;
|
||||||
|
|
||||||
|
/** Maximum size of files in cache, larger files are not cached */
|
||||||
|
int maxCachedFileSize;
|
||||||
|
|
||||||
|
/** Cache storage */
|
||||||
|
QCache<QString,CacheEntry> cache;
|
||||||
|
|
||||||
|
/** Set a content-type header in the response depending on the ending of the filename */
|
||||||
|
void setContentType(QString file, HttpResponse& response) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // STATICFILECONTROLLER_H
|
7
YACReaderLibrary/server/lib/bfLogging/bfLogging.pri
Normal file
7
YACReaderLibrary/server/lib/bfLogging/bfLogging.pri
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
INCLUDEPATH += $$PWD
|
||||||
|
DEPENDPATH += $$PWD
|
||||||
|
|
||||||
|
HEADERS += $$PWD/logmessage.h $$PWD/logger.h $$PWD/filelogger.h $$PWD/dualfilelogger.h
|
||||||
|
SOURCES += $$PWD/logmessage.cpp $$PWD/logger.cpp $$PWD/filelogger.cpp $$PWD/dualfilelogger.cpp
|
||||||
|
|
||||||
|
OTHER_FILES += $$PWD/../doc/readme.txt
|
20
YACReaderLibrary/server/lib/bfLogging/dualfilelogger.cpp
Normal file
20
YACReaderLibrary/server/lib/bfLogging/dualfilelogger.cpp
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@author Stefan Frings
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "dualfilelogger.h"
|
||||||
|
|
||||||
|
|
||||||
|
DualFileLogger::DualFileLogger(QSettings* firstSettings, QSettings* secondSettings, const int refreshInterval, QObject* parent)
|
||||||
|
:Logger(parent)
|
||||||
|
{
|
||||||
|
firstLogger=new FileLogger(firstSettings, refreshInterval, this);
|
||||||
|
secondLogger=new FileLogger(secondSettings, refreshInterval, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void DualFileLogger::log(const QtMsgType type, const QString& message) {
|
||||||
|
firstLogger->log(type, message);
|
||||||
|
secondLogger->log(type, message);
|
||||||
|
}
|
60
YACReaderLibrary/server/lib/bfLogging/dualfilelogger.h
Normal file
60
YACReaderLibrary/server/lib/bfLogging/dualfilelogger.h
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@author Stefan Frings
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef DUALFILELOGGER_H
|
||||||
|
#define DUALFILELOGGER_H
|
||||||
|
|
||||||
|
#include "logger.h"
|
||||||
|
#include "filelogger.h"
|
||||||
|
#include <QString>
|
||||||
|
#include <QSettings>
|
||||||
|
#include <QtGlobal>
|
||||||
|
|
||||||
|
/**
|
||||||
|
Logs messages into two log files. This is specially useful to get one less detailed
|
||||||
|
logfile for normal operation plus one more detailed file for debugging.
|
||||||
|
@see FileLogger for a description of the required config settings.
|
||||||
|
@see set() describes how to set logger variables
|
||||||
|
@see LogMessage for a description of the message decoration.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class DualFileLogger : public Logger {
|
||||||
|
Q_OBJECT
|
||||||
|
Q_DISABLE_COPY(DualFileLogger)
|
||||||
|
public:
|
||||||
|
|
||||||
|
/**
|
||||||
|
Constructor.
|
||||||
|
@param firstSettings Configuration settings for the first log file, usually stored in an INI file.
|
||||||
|
Must not be 0.
|
||||||
|
Settings are read from the current group, so the caller must have called settings->beginGroup().
|
||||||
|
Because the group must not change during runtime, it is recommended to provide a
|
||||||
|
separate QSettings instance to the logger that is not used by other parts of the program.
|
||||||
|
@param secondSettings Same as firstSettings, but for the second log file.
|
||||||
|
@param refreshInterval Interval of checking the config settings in msec, or 0=disabled
|
||||||
|
@param parent Parent object
|
||||||
|
*/
|
||||||
|
DualFileLogger(QSettings* firstSettings, QSettings* secondSettings, const int refreshInterval=10000, QObject *parent = 0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Decorate and log a message.
|
||||||
|
This method is thread safe.
|
||||||
|
@param type Message type (level)
|
||||||
|
@param message Message text
|
||||||
|
@see LogMessage for a description of the message decoration.
|
||||||
|
*/
|
||||||
|
virtual void log(const QtMsgType type, const QString& message);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
/** First logger */
|
||||||
|
FileLogger* firstLogger;
|
||||||
|
|
||||||
|
/** Second logger */
|
||||||
|
FileLogger* secondLogger;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // DUALFILELOGGER_H
|
176
YACReaderLibrary/server/lib/bfLogging/filelogger.cpp
Normal file
176
YACReaderLibrary/server/lib/bfLogging/filelogger.cpp
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@author Stefan Frings
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "filelogger.h"
|
||||||
|
#include <QTime>
|
||||||
|
#include <QStringList>
|
||||||
|
#include <QThread>
|
||||||
|
#include <QtGlobal>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QTimerEvent>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
void FileLogger::refreshSettings() {
|
||||||
|
mutex.lock();
|
||||||
|
// Save old file name for later comparision with new settings
|
||||||
|
QString oldFileName=fileName;
|
||||||
|
|
||||||
|
// Load new config settings
|
||||||
|
settings->sync();
|
||||||
|
fileName=settings->value("fileName").toString();
|
||||||
|
// Convert relative fileName to absolute, based on the directory of the config file.
|
||||||
|
#ifdef Q_OS_WIN32
|
||||||
|
if (QDir::isRelativePath(fileName) && settings->format()!=QSettings::NativeFormat)
|
||||||
|
#else
|
||||||
|
if (QDir::isRelativePath(fileName))
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
QFileInfo configFile(settings->fileName());
|
||||||
|
fileName=QFileInfo(configFile.absolutePath(),fileName).absoluteFilePath();
|
||||||
|
}
|
||||||
|
maxSize=settings->value("maxSize",0).toLongLong();
|
||||||
|
maxBackups=settings->value("maxBackups",0).toInt();
|
||||||
|
msgFormat=settings->value("msgFormat","{timestamp} {type} {msg}").toString();
|
||||||
|
timestampFormat=settings->value("timestampFormat","{yyyy-MM-dd hh:mm:ss.zzz}").toString();
|
||||||
|
minLevel=static_cast<QtMsgType>(settings->value("minLevel",0).toInt());
|
||||||
|
bufferSize=settings->value("bufferSize",0).toInt();
|
||||||
|
disabled=settings->value("disabled",false).toBool();
|
||||||
|
|
||||||
|
// Create new file if the filename has been changed
|
||||||
|
if (oldFileName!=fileName) {
|
||||||
|
fprintf(stderr,"Logging to %s\n",qPrintable(fileName));
|
||||||
|
close();
|
||||||
|
open();
|
||||||
|
}
|
||||||
|
mutex.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
FileLogger::FileLogger(QSettings* settings, const int refreshInterval, QObject* parent)
|
||||||
|
: Logger(parent)
|
||||||
|
{
|
||||||
|
Q_ASSERT(settings!=0);
|
||||||
|
Q_ASSERT(refreshInterval>=0);
|
||||||
|
this->settings=settings;
|
||||||
|
file=0;
|
||||||
|
if (refreshInterval>0)
|
||||||
|
refreshTimer.start(refreshInterval,this);
|
||||||
|
flushTimer.start(1000,this);
|
||||||
|
refreshSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
FileLogger::~FileLogger() {
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void FileLogger::write(const LogMessage* logMessage) {
|
||||||
|
// Write to the file
|
||||||
|
if (!disabled) {
|
||||||
|
// Try to write to the file
|
||||||
|
if (file) {
|
||||||
|
|
||||||
|
// Write the message
|
||||||
|
file->write(qPrintable(logMessage->toString(msgFormat,timestampFormat)));
|
||||||
|
|
||||||
|
// Flush error messages immediately, to ensure that no important message
|
||||||
|
// gets lost when the program terinates abnormally.
|
||||||
|
if (logMessage->getType()>=QtCriticalMsg) {
|
||||||
|
file->flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for success
|
||||||
|
if (file->error()) {
|
||||||
|
close();
|
||||||
|
qWarning("Cannot write to log file %s: %s",qPrintable(fileName),qPrintable(file->errorString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall-back to the super class method, if writing failed
|
||||||
|
if (!file) {
|
||||||
|
Logger::write(logMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileLogger::open() {
|
||||||
|
if (fileName.isEmpty()) {
|
||||||
|
qWarning("Name of logFile is empty");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
file=new QFile(fileName);
|
||||||
|
if (!file->open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) {
|
||||||
|
qWarning("Cannot open log file %s: %s",qPrintable(fileName),qPrintable(file->errorString()));
|
||||||
|
file=0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void FileLogger::close() {
|
||||||
|
if (file) {
|
||||||
|
file->close();
|
||||||
|
delete file;
|
||||||
|
file=0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileLogger::rotate() {
|
||||||
|
// count current number of existing backup files
|
||||||
|
int count=0;
|
||||||
|
forever {
|
||||||
|
QFile bakFile(QString("%1.%2").arg(fileName).arg(count+1));
|
||||||
|
if (bakFile.exists()) {
|
||||||
|
++count;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove all old backup files that exceed the maximum number
|
||||||
|
while (maxBackups>0 && count>=maxBackups) {
|
||||||
|
QFile::remove(QString("%1.%2").arg(fileName).arg(count));
|
||||||
|
--count;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rotate backup files
|
||||||
|
for (int i=count; i>0; --i) {
|
||||||
|
QFile::rename(QString("%1.%2").arg(fileName).arg(i),QString("%1.%2").arg(fileName).arg(i+1));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Backup the current logfile
|
||||||
|
QFile::rename(fileName,fileName+".1");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void FileLogger::timerEvent(QTimerEvent* event) {
|
||||||
|
if (!event) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (event->timerId()==refreshTimer.timerId()) {
|
||||||
|
refreshSettings();
|
||||||
|
}
|
||||||
|
else if (event->timerId()==flushTimer.timerId() && file) {
|
||||||
|
mutex.lock();
|
||||||
|
|
||||||
|
// Flush the I/O buffer
|
||||||
|
file->flush();
|
||||||
|
|
||||||
|
// Rotate the file if it is too large
|
||||||
|
if (maxSize>0 && file->size()>=maxSize) {
|
||||||
|
close();
|
||||||
|
rotate();
|
||||||
|
open();
|
||||||
|
}
|
||||||
|
|
||||||
|
mutex.unlock();
|
||||||
|
}
|
||||||
|
}
|
131
YACReaderLibrary/server/lib/bfLogging/filelogger.h
Normal file
131
YACReaderLibrary/server/lib/bfLogging/filelogger.h
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@author Stefan Frings
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef FILELOGGER_H
|
||||||
|
#define FILELOGGER_H
|
||||||
|
|
||||||
|
#include <QtGlobal>
|
||||||
|
#include <QSettings>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QMutex>
|
||||||
|
#include <QBasicTimer>
|
||||||
|
#include "logger.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
Logger that uses a text file for output. Settings are read from a
|
||||||
|
config file using a QSettings object. Config settings can be changed at runtime.
|
||||||
|
They are copied to private fields in regular intervals for permance reason.
|
||||||
|
<p>
|
||||||
|
Example for the required configuration settings:
|
||||||
|
<code><pre>
|
||||||
|
disabled=false
|
||||||
|
fileName=logs/QtWebApp.log
|
||||||
|
maxSize=1000000
|
||||||
|
maxBackups=2
|
||||||
|
minLevel=0
|
||||||
|
msgformat={timestamp} {typeNr} {type} thread={thread}: {msg}
|
||||||
|
timestampFormat=dd.MM.yyyy hh:mm:ss.zzz
|
||||||
|
bufferSize=0
|
||||||
|
</pre></code>
|
||||||
|
|
||||||
|
- fileName is the name of the log file, relative to the directory of the settings file.
|
||||||
|
In case of windows, if the settings are in the registry, the path is relative to the current
|
||||||
|
working directory.
|
||||||
|
- maxSize is the maximum size of that file in bytes. The file will be backed up and
|
||||||
|
replaced by a new file if it becomes larger than this limit. Please note that
|
||||||
|
the actual file size may become a little bit larger than this limit. 0=unlimited.
|
||||||
|
- maxBackups defines the number of backup files to keep. 0=unlimited.
|
||||||
|
- minLevel defines the minimum level of message types to be written into the file.
|
||||||
|
- msgFormat defines the decoration of log messages.
|
||||||
|
- timestampFormat defines the format of timestamps.
|
||||||
|
- buffersize defines the size of the backtrace buffer. 0=disabled.
|
||||||
|
The buffer stores log messages of any level from the time before an error occurs.
|
||||||
|
It can be used to provide detailed debug information when an error occurs, while keeping
|
||||||
|
the logfile clean as long no error occurs. Using this buffer may reduce performance
|
||||||
|
significantly.
|
||||||
|
|
||||||
|
@see set() describes how to set logger variables
|
||||||
|
@see LogMessage for a description of the message decoration.
|
||||||
|
@see Logger for a descrition of the backtrace buffer
|
||||||
|
*/
|
||||||
|
|
||||||
|
class FileLogger : public Logger {
|
||||||
|
Q_OBJECT
|
||||||
|
Q_DISABLE_COPY(FileLogger)
|
||||||
|
public:
|
||||||
|
|
||||||
|
/**
|
||||||
|
Constructor.
|
||||||
|
@param settings Configuration settings, usually stored in an INI file. Must not be 0.
|
||||||
|
Settings are read from the current group, so the caller must have called settings->beginGroup().
|
||||||
|
Because the group must not change during runtime, it is recommended to provide a
|
||||||
|
separate QSettings instance to the logger that is not used by other parts of the program.
|
||||||
|
@param refreshInterval Interval of checking the config settings in msec, or 0=disabled
|
||||||
|
@param parent Parent object
|
||||||
|
*/
|
||||||
|
FileLogger(QSettings* settings, const int refreshInterval=10000, QObject* parent = 0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Destructor. Closes the file.
|
||||||
|
*/
|
||||||
|
virtual ~FileLogger();
|
||||||
|
|
||||||
|
/** Write a message to the log file */
|
||||||
|
virtual void write(const LogMessage* logMessage);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
/**
|
||||||
|
Handler for timer events.
|
||||||
|
Refreshes config settings or synchronizes I/O buffer, depending on the event.
|
||||||
|
This method is thread-safe.
|
||||||
|
@param event used to distinguish between the two timers.
|
||||||
|
*/
|
||||||
|
void timerEvent(QTimerEvent* event);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
/** Configured name of the log file */
|
||||||
|
QString fileName;
|
||||||
|
|
||||||
|
/** Configured maximum size of the file in bytes, or 0=unlimited */
|
||||||
|
long maxSize;
|
||||||
|
|
||||||
|
/** Configured maximum number of backup files, or 0=unlimited */
|
||||||
|
int maxBackups;
|
||||||
|
|
||||||
|
/** Whether this logger is disabled */
|
||||||
|
bool disabled;
|
||||||
|
|
||||||
|
/** Pointer to the configuration settings */
|
||||||
|
QSettings* settings;
|
||||||
|
|
||||||
|
/** Output file, or 0=disabled */
|
||||||
|
QFile* file;
|
||||||
|
|
||||||
|
/** Timer for refreshing configuration settings */
|
||||||
|
QBasicTimer refreshTimer;
|
||||||
|
|
||||||
|
/** Timer for flushing the file I/O buffer */
|
||||||
|
QBasicTimer flushTimer;
|
||||||
|
|
||||||
|
/** Open the output file */
|
||||||
|
void open();
|
||||||
|
|
||||||
|
/** Close the output file */
|
||||||
|
void close();
|
||||||
|
|
||||||
|
/** Rotate files and delete some backups if there are too many */
|
||||||
|
void rotate();
|
||||||
|
|
||||||
|
/**
|
||||||
|
Refreshes the configuration settings.
|
||||||
|
This method is thread-safe.
|
||||||
|
*/
|
||||||
|
void refreshSettings();
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // FILELOGGER_H
|
154
YACReaderLibrary/server/lib/bfLogging/logger.cpp
Normal file
154
YACReaderLibrary/server/lib/bfLogging/logger.cpp
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@author Stefan Frings
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "logger.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <QMutex>
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QThread>
|
||||||
|
|
||||||
|
Logger* Logger::defaultLogger=0;
|
||||||
|
|
||||||
|
|
||||||
|
QThreadStorage<QHash<QString,QString>*> Logger::logVars;
|
||||||
|
|
||||||
|
|
||||||
|
QThreadStorage<QList<LogMessage*>*> Logger::buffers;
|
||||||
|
|
||||||
|
|
||||||
|
QMutex Logger::mutex;
|
||||||
|
|
||||||
|
|
||||||
|
Logger::Logger(QObject* parent)
|
||||||
|
: QObject(parent),
|
||||||
|
msgFormat("{timestamp} {type} {msg}"),
|
||||||
|
timestampFormat("dd.MM.yyyy hh:mm:ss.zzz"),
|
||||||
|
minLevel(QtDebugMsg),
|
||||||
|
bufferSize(0)
|
||||||
|
{}
|
||||||
|
|
||||||
|
|
||||||
|
Logger::Logger(const QString msgFormat, const QString timestampFormat, const QtMsgType minLevel, const int bufferSize, QObject* parent)
|
||||||
|
:QObject(parent) {
|
||||||
|
this->msgFormat=msgFormat;
|
||||||
|
this->timestampFormat=timestampFormat;
|
||||||
|
this->minLevel=minLevel;
|
||||||
|
this->bufferSize=bufferSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Logger::msgHandler(const QtMsgType type, const char* message) {
|
||||||
|
static QMutex recursiveMutex(QMutex::Recursive);
|
||||||
|
static QMutex nonRecursiveMutex(QMutex::NonRecursive);
|
||||||
|
|
||||||
|
// Prevent multiple threads from calling this method simultaneoulsy.
|
||||||
|
// But allow recursive calls, which is required to prevent a deadlock
|
||||||
|
// if the logger itself produces an error message.
|
||||||
|
recursiveMutex.lock();
|
||||||
|
|
||||||
|
// Fall back to stderr when this method has been called recursively.
|
||||||
|
if (defaultLogger && nonRecursiveMutex.tryLock()) {
|
||||||
|
defaultLogger->log(type,message);
|
||||||
|
nonRecursiveMutex.unlock();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
fputs(message,stderr);
|
||||||
|
fflush(stderr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Abort the program after logging a fatal message
|
||||||
|
if (type>=QtFatalMsg) {
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
recursiveMutex.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Logger::~Logger() {
|
||||||
|
if (defaultLogger==this) {
|
||||||
|
qInstallMsgHandler(0);
|
||||||
|
defaultLogger=0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Logger::write(const LogMessage* logMessage) {
|
||||||
|
fputs(qPrintable(logMessage->toString(msgFormat,timestampFormat)),stderr);
|
||||||
|
fflush(stderr);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Logger::installMsgHandler() {
|
||||||
|
defaultLogger=this;
|
||||||
|
qInstallMsgHandler(msgHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Logger::set(const QString& name, const QString& value) {
|
||||||
|
mutex.lock();
|
||||||
|
if (!logVars.hasLocalData()) {
|
||||||
|
logVars.setLocalData(new QHash<QString,QString>);
|
||||||
|
}
|
||||||
|
logVars.localData()->insert(name,value);
|
||||||
|
mutex.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Logger::clear(const bool buffer, const bool variables) {
|
||||||
|
mutex.lock();
|
||||||
|
if (buffer && buffers.hasLocalData()) {
|
||||||
|
QList<LogMessage*>* buffer=buffers.localData();
|
||||||
|
while (buffer && !buffer->isEmpty()) {
|
||||||
|
LogMessage* logMessage=buffer->takeLast();
|
||||||
|
delete logMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (variables && logVars.hasLocalData()) {
|
||||||
|
logVars.localData()->clear();
|
||||||
|
}
|
||||||
|
mutex.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Logger::log(const QtMsgType type, const QString& message) {
|
||||||
|
mutex.lock();
|
||||||
|
|
||||||
|
// If the buffer is enabled, write the message into it
|
||||||
|
if (bufferSize>0) {
|
||||||
|
// Create new thread local buffer, if necessary
|
||||||
|
if (!buffers.hasLocalData()) {
|
||||||
|
buffers.setLocalData(new QList<LogMessage*>());
|
||||||
|
}
|
||||||
|
QList<LogMessage*>* buffer=buffers.localData();
|
||||||
|
// Append the decorated log message
|
||||||
|
LogMessage* logMessage=new LogMessage(type,message,logVars.localData());
|
||||||
|
buffer->append(logMessage);
|
||||||
|
// Delete oldest message if the buffer became too large
|
||||||
|
if (buffer->size()>bufferSize) {
|
||||||
|
delete buffer->takeFirst();
|
||||||
|
}
|
||||||
|
// If the type of the message is high enough, print the whole buffer
|
||||||
|
if (type>=minLevel) {
|
||||||
|
while (!buffer->isEmpty()) {
|
||||||
|
LogMessage* logMessage=buffer->takeFirst();
|
||||||
|
write(logMessage);
|
||||||
|
delete logMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buffer is disabled, print the message if the type is high enough
|
||||||
|
else {
|
||||||
|
if (type>=minLevel) {
|
||||||
|
LogMessage logMessage(type,message,logVars.localData());
|
||||||
|
write(&logMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mutex.unlock();
|
||||||
|
}
|
144
YACReaderLibrary/server/lib/bfLogging/logger.h
Normal file
144
YACReaderLibrary/server/lib/bfLogging/logger.h
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@author Stefan Frings
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef LOGGER_H
|
||||||
|
#define LOGGER_H
|
||||||
|
|
||||||
|
#include <QtGlobal>
|
||||||
|
#include <QThreadStorage>
|
||||||
|
#include <QHash>
|
||||||
|
#include <QStringList>
|
||||||
|
#include <QMutex>
|
||||||
|
#include "logmessage.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
Decorates and writes log messages to the console, stderr.
|
||||||
|
<p>
|
||||||
|
The decorator uses a predefined msgFormat string to enrich log messages
|
||||||
|
with additional information (e.g. timestamp).
|
||||||
|
<p>
|
||||||
|
The msgFormat string and also the message text may contain additional
|
||||||
|
variable names in the form <i>{name}</i> that are filled by values
|
||||||
|
taken from a static thread local dictionary.
|
||||||
|
<p>
|
||||||
|
The buffer stores log messages of any level from the time before an error occurs.
|
||||||
|
It can be used to provide detailed debug information when an error occurs, while
|
||||||
|
keeping the logfile clean as long no error occurs. Using this buffer may
|
||||||
|
reduce performance significantly.
|
||||||
|
<p>
|
||||||
|
The logger can be registered to handle messages from
|
||||||
|
the static global functions qDebug(), qWarning(), qCritical() and qFatal().
|
||||||
|
@see set() describes how to set logger variables
|
||||||
|
@see LogMessage for a description of the message decoration.
|
||||||
|
@warning You should prefer a derived class, for example FileLogger,
|
||||||
|
because logging to the console is less useful.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Logger : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
Q_DISABLE_COPY(Logger)
|
||||||
|
public:
|
||||||
|
|
||||||
|
/**
|
||||||
|
Constructor.
|
||||||
|
Uses the same defaults as the other constructor.
|
||||||
|
@param parent Parent object
|
||||||
|
*/
|
||||||
|
Logger(QObject* parent);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
Constructor.
|
||||||
|
@param msgFormat Format of the decoration, e.g. "{timestamp} {type} thread={thread}: {msg}"
|
||||||
|
@param timestampFormat Format of timestamp, e.g. "dd.MM.yyyy hh:mm:ss.zzz"
|
||||||
|
@param minLevel Minimum type of messages that are written out.
|
||||||
|
@param bufferSize Size of the backtrace buffer, number of messages per thread. 0=disabled.
|
||||||
|
@param parent Parent object
|
||||||
|
@see LogMessage for a description of the message decoration.
|
||||||
|
*/
|
||||||
|
Logger(const QString msgFormat="{timestamp} {type} {msg}", const QString timestampFormat="dd.MM.yyyy hh:mm:ss.zzz", const QtMsgType minLevel=QtDebugMsg, const int bufferSize=0, QObject* parent = 0);
|
||||||
|
|
||||||
|
/** Destructor */
|
||||||
|
virtual ~Logger();
|
||||||
|
|
||||||
|
/**
|
||||||
|
Decorate and log the message, if type>=minLevel.
|
||||||
|
This method is thread safe.
|
||||||
|
@param type Message type (level)
|
||||||
|
@param message Message text
|
||||||
|
@see LogMessage for a description of the message decoration.
|
||||||
|
*/
|
||||||
|
virtual void log(const QtMsgType type, const QString& message);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Installs this logger as the default message handler, so it
|
||||||
|
can be used through the global static logging functions (e.g. qDebug()).
|
||||||
|
*/
|
||||||
|
void installMsgHandler();
|
||||||
|
|
||||||
|
/**
|
||||||
|
Sets a thread-local variable that may be used to decorate log messages.
|
||||||
|
This method is thread safe.
|
||||||
|
@param name Name of the variable
|
||||||
|
@param value Value of the variable
|
||||||
|
*/
|
||||||
|
static void set(const QString& name, const QString& value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Clear the thread-local data of the current thread.
|
||||||
|
@param buffer Whether to clear the backtrace buffer
|
||||||
|
@param variables Whether to clear the log variables
|
||||||
|
*/
|
||||||
|
static void clear(const bool buffer=true, const bool variables=true);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
/** Format string for message decoration */
|
||||||
|
QString msgFormat;
|
||||||
|
|
||||||
|
/** Format string of timestamps */
|
||||||
|
QString timestampFormat;
|
||||||
|
|
||||||
|
/** Minimum level of message types that are written out */
|
||||||
|
QtMsgType minLevel;
|
||||||
|
|
||||||
|
/** Size of backtrace buffer, number of messages per thread. 0=disabled */
|
||||||
|
int bufferSize;
|
||||||
|
|
||||||
|
/** Used to synchronize access to the static members */
|
||||||
|
static QMutex mutex;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Decorate and write a log message to stderr. Override this method
|
||||||
|
to provide a different output medium.
|
||||||
|
*/
|
||||||
|
virtual void write(const LogMessage* logMessage);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
/** Pointer to the default logger, used by msgHandler() */
|
||||||
|
static Logger* defaultLogger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Message Handler for the global static logging functions (e.g. qDebug()).
|
||||||
|
Forward calls to the default logger.
|
||||||
|
<p>
|
||||||
|
In case of a fatal message, the program will abort.
|
||||||
|
Variables in the in the message are replaced by their values.
|
||||||
|
This method is thread safe.
|
||||||
|
@param type Message type (level)
|
||||||
|
@param message Message text
|
||||||
|
*/
|
||||||
|
static void msgHandler(const QtMsgType type, const char* message);
|
||||||
|
|
||||||
|
/** Thread local variables to be used in log messages */
|
||||||
|
static QThreadStorage<QHash<QString,QString>*> logVars;
|
||||||
|
|
||||||
|
/** Thread local backtrace buffers */
|
||||||
|
static QThreadStorage<QList<LogMessage*>*> buffers;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // LOGGER_H
|
68
YACReaderLibrary/server/lib/bfLogging/logmessage.cpp
Normal file
68
YACReaderLibrary/server/lib/bfLogging/logmessage.cpp
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@author Stefan Frings
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "logmessage.h"
|
||||||
|
#include <QThread>
|
||||||
|
|
||||||
|
LogMessage::LogMessage(const QtMsgType type, const QString& message, QHash<QString,QString>* logVars) {
|
||||||
|
this->type=type;
|
||||||
|
this->message=message;
|
||||||
|
timestamp=QDateTime::currentDateTime();
|
||||||
|
threadId=QThread::currentThreadId();
|
||||||
|
|
||||||
|
// Copy the logVars if not null,
|
||||||
|
// so that later changes in the original do not affect the copy
|
||||||
|
if (logVars) {
|
||||||
|
this->logVars=*logVars;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString LogMessage::toString(const QString& msgFormat, const QString& timestampFormat) const {
|
||||||
|
QString decorated=msgFormat+"\n";
|
||||||
|
decorated.replace("{msg}",message);
|
||||||
|
|
||||||
|
if (decorated.contains("{timestamp}")) {
|
||||||
|
decorated.replace("{timestamp}",QDateTime::currentDateTime().toString(timestampFormat));
|
||||||
|
}
|
||||||
|
|
||||||
|
QString typeNr;
|
||||||
|
typeNr.setNum(type);
|
||||||
|
decorated.replace("{typeNr}",typeNr);
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case QtDebugMsg:
|
||||||
|
decorated.replace("{type}","DEBUG");
|
||||||
|
break;
|
||||||
|
case QtWarningMsg:
|
||||||
|
decorated.replace("{type}","WARNING");
|
||||||
|
break;
|
||||||
|
case QtCriticalMsg:
|
||||||
|
decorated.replace("{type}","CRITICAL");
|
||||||
|
break;
|
||||||
|
case QtFatalMsg:
|
||||||
|
decorated.replace("{type}","FATAL");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
decorated.replace("{type}",typeNr);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString threadId;
|
||||||
|
threadId.setNum((unsigned int)QThread::currentThreadId());
|
||||||
|
decorated.replace("{thread}",threadId);
|
||||||
|
|
||||||
|
// Fill in variables
|
||||||
|
if (decorated.contains("{") && !logVars.isEmpty()) {
|
||||||
|
QList<QString> keys=logVars.keys();
|
||||||
|
foreach (QString key, keys) {
|
||||||
|
decorated.replace("{"+key+"}",logVars.value(key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return decorated;
|
||||||
|
}
|
||||||
|
|
||||||
|
QtMsgType LogMessage::getType() const {
|
||||||
|
return type;
|
||||||
|
}
|
74
YACReaderLibrary/server/lib/bfLogging/logmessage.h
Normal file
74
YACReaderLibrary/server/lib/bfLogging/logmessage.h
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@author Stefan Frings
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef LOGMESSAGE_H
|
||||||
|
#define LOGMESSAGE_H
|
||||||
|
|
||||||
|
#include <QtGlobal>
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QHash>
|
||||||
|
|
||||||
|
/**
|
||||||
|
Represents a single log message together with some data
|
||||||
|
that are used to decorate the log message.
|
||||||
|
|
||||||
|
The following variables may be used in the message and in msgFormat:
|
||||||
|
|
||||||
|
- {timestamp} Date and time of creation
|
||||||
|
- {typeNr} Type of the message in numeric format (0-3)
|
||||||
|
- {type} Type of the message in string format (DEBUG, WARNING, CRITICAL, FATAL)
|
||||||
|
- {thread} ID number of the thread
|
||||||
|
- {msg} Message text (only useable in msgFormat)
|
||||||
|
- {xxx} For any user-defined logger variable
|
||||||
|
*/
|
||||||
|
|
||||||
|
class LogMessage
|
||||||
|
{
|
||||||
|
Q_DISABLE_COPY(LogMessage)
|
||||||
|
public:
|
||||||
|
|
||||||
|
/**
|
||||||
|
Constructor. All parameters are copied, so that later changes to them do not
|
||||||
|
affect this object.
|
||||||
|
@param type Type of the message
|
||||||
|
@param message Message text
|
||||||
|
@param logVars Logger variables, 0 is allowed
|
||||||
|
*/
|
||||||
|
LogMessage(const QtMsgType type, const QString& message, QHash<QString,QString>* logVars);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Returns the log message as decorated string.
|
||||||
|
@param msgFormat Format of the decoration. May contain variables and static text,
|
||||||
|
e.g. "{timestamp} {type} thread={thread}: {msg}"
|
||||||
|
@param timestampFormat Format of timestamp, e.g. "dd.MM.yyyy hh:mm:ss.zzz"
|
||||||
|
@see QDatetime for a description of the timestamp format pattern
|
||||||
|
*/
|
||||||
|
QString toString(const QString& msgFormat, const QString& timestampFormat) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Get the message type.
|
||||||
|
*/
|
||||||
|
QtMsgType getType() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
/** Logger variables */
|
||||||
|
QHash<QString,QString> logVars;
|
||||||
|
|
||||||
|
/** Date and time of creation */
|
||||||
|
QDateTime timestamp;
|
||||||
|
|
||||||
|
/** Type of the message */
|
||||||
|
QtMsgType type;
|
||||||
|
|
||||||
|
/** ID number of the thread */
|
||||||
|
Qt::HANDLE threadId;
|
||||||
|
|
||||||
|
/** Message text */
|
||||||
|
QString message;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // LOGMESSAGE_H
|
@ -0,0 +1,7 @@
|
|||||||
|
INCLUDEPATH += $$PWD
|
||||||
|
DEPENDPATH += $$PWD
|
||||||
|
|
||||||
|
HEADERS += $$PWD/template.h $$PWD/templateloader.h $$PWD/templatecache.h
|
||||||
|
SOURCES += $$PWD/template.cpp $$PWD/templateloader.cpp $$PWD/templatecache.cpp
|
||||||
|
|
||||||
|
OTHER_FILES += $$PWD/../doc/readme.txt
|
187
YACReaderLibrary/server/lib/bfTemplateEngine/template.cpp
Normal file
187
YACReaderLibrary/server/lib/bfTemplateEngine/template.cpp
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@author Stefan Frings
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "template.h"
|
||||||
|
#include <QFileInfo>
|
||||||
|
|
||||||
|
Template::Template(QString source, QString sourceName)
|
||||||
|
: QString(source) {
|
||||||
|
this->sourceName=sourceName;
|
||||||
|
this->warnings=false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Template::Template(QFile& file, QTextCodec* textCodec) {
|
||||||
|
sourceName=QFileInfo(file.fileName()).baseName();
|
||||||
|
if (!file.isOpen()) {
|
||||||
|
file.open(QFile::ReadOnly | QFile::Text);
|
||||||
|
}
|
||||||
|
QByteArray data=file.readAll();
|
||||||
|
file.close();
|
||||||
|
if (data.size()==0 || file.error()) {
|
||||||
|
qCritical("Template: cannot read from %s, %s",qPrintable(sourceName),qPrintable(file.errorString()));
|
||||||
|
append(textCodec->toUnicode(data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int Template::setVariable(QString name, QString value) {
|
||||||
|
int count=0;
|
||||||
|
QString variable="{"+name+"}";
|
||||||
|
int start=indexOf(variable);
|
||||||
|
while (start>=0) {
|
||||||
|
replace(start, variable.length(), value);
|
||||||
|
count++;
|
||||||
|
start=indexOf(variable,start+variable.length());
|
||||||
|
}
|
||||||
|
if (count==0 && warnings) {
|
||||||
|
qWarning("Template: missing variable %s in %s",qPrintable(variable),qPrintable(sourceName));
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Template::setCondition(QString name, bool value) {
|
||||||
|
int count=0;
|
||||||
|
QString startTag=QString("{if %1}").arg(name);
|
||||||
|
QString elseTag=QString("{else %1}").arg(name);
|
||||||
|
QString endTag=QString("{end %1}").arg(name);
|
||||||
|
// search for if-else-end
|
||||||
|
int start=indexOf(startTag);
|
||||||
|
while (start>=0) {
|
||||||
|
int end=indexOf(endTag,start+startTag.length());
|
||||||
|
if (end>=0) {
|
||||||
|
count++;
|
||||||
|
int ellse=indexOf(elseTag,start+startTag.length());
|
||||||
|
if (ellse>start && ellse<end) { // there is an else part
|
||||||
|
if (value==true) {
|
||||||
|
QString truePart=mid(start+startTag.length(), ellse-start-startTag.length());
|
||||||
|
replace(start, end-start+endTag.length(), truePart);
|
||||||
|
}
|
||||||
|
else { // value==false
|
||||||
|
QString falsePart=mid(ellse+elseTag.length(), end-ellse-elseTag.length());
|
||||||
|
replace(start, end-start+endTag.length(), falsePart);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (value==true) { // and no else part
|
||||||
|
QString truePart=mid(start+startTag.length(), end-start-startTag.length());
|
||||||
|
replace(start, end-start+endTag.length(), truePart);
|
||||||
|
}
|
||||||
|
else { // value==false and no else part
|
||||||
|
replace(start, end-start+endTag.length(), "");
|
||||||
|
}
|
||||||
|
start=indexOf(startTag,start);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
qWarning("Template: missing condition end %s in %s",qPrintable(endTag),qPrintable(sourceName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// search for ifnot-else-end
|
||||||
|
QString startTag2="{ifnot "+name+"}";
|
||||||
|
start=indexOf(startTag2);
|
||||||
|
while (start>=0) {
|
||||||
|
int end=indexOf(endTag,start+startTag2.length());
|
||||||
|
if (end>=0) {
|
||||||
|
count++;
|
||||||
|
int ellse=indexOf(elseTag,start+startTag2.length());
|
||||||
|
if (ellse>start && ellse<end) { // there is an else part
|
||||||
|
if (value==false) {
|
||||||
|
QString falsePart=mid(start+startTag2.length(), ellse-start-startTag2.length());
|
||||||
|
replace(start, end-start+endTag.length(), falsePart);
|
||||||
|
}
|
||||||
|
else { // value==true
|
||||||
|
QString truePart=mid(ellse+elseTag.length(), end-ellse-elseTag.length());
|
||||||
|
replace(start, end-start+endTag.length(), truePart);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (value==false) { // and no else part
|
||||||
|
QString falsePart=mid(start+startTag2.length(), end-start-startTag2.length());
|
||||||
|
replace(start, end-start+endTag.length(), falsePart);
|
||||||
|
}
|
||||||
|
else { // value==true and no else part
|
||||||
|
replace(start, end-start+endTag.length(), "");
|
||||||
|
}
|
||||||
|
start=indexOf(startTag2,start);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
qWarning("Template: missing condition end %s in %s",qPrintable(endTag),qPrintable(sourceName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (count==0 && warnings) {
|
||||||
|
qWarning("Template: missing condition %s or %s in %s",qPrintable(startTag),qPrintable(startTag2),qPrintable(sourceName));
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Template::loop(QString name, int repetitions) {
|
||||||
|
Q_ASSERT(repetitions>=0);
|
||||||
|
int count=0;
|
||||||
|
QString startTag="{loop "+name+"}";
|
||||||
|
QString elseTag="{else "+name+"}";
|
||||||
|
QString endTag="{end "+name+"}";
|
||||||
|
// search for loop-else-end
|
||||||
|
int start=indexOf(startTag);
|
||||||
|
while (start>=0) {
|
||||||
|
int end=indexOf(endTag,start+startTag.length());
|
||||||
|
if (end>=0) {
|
||||||
|
count++;
|
||||||
|
int ellse=indexOf(elseTag,start+startTag.length());
|
||||||
|
if (ellse>start && ellse<end) { // there is an else part
|
||||||
|
if (repetitions>0) {
|
||||||
|
QString loopPart=mid(start+startTag.length(), ellse-start-startTag.length());
|
||||||
|
QString insertMe;
|
||||||
|
for (int i=0; i<repetitions; ++i) {
|
||||||
|
// number variables, conditions and sub-loop within the loop
|
||||||
|
QString nameNum=name+QString::number(i);
|
||||||
|
QString s=loopPart;
|
||||||
|
s.replace(QString("{%1.").arg(name), QString("{%1.").arg(nameNum));
|
||||||
|
s.replace(QString("{if %1.").arg(name), QString("{if %1.").arg(nameNum));
|
||||||
|
s.replace(QString("{ifnot %1.").arg(name), QString("{ifnot %1.").arg(nameNum));
|
||||||
|
s.replace(QString("{else %1.").arg(name), QString("{else %1.").arg(nameNum));
|
||||||
|
s.replace(QString("{end %1.").arg(name), QString("{end %1.").arg(nameNum));
|
||||||
|
s.replace(QString("{loop %1.").arg(name), QString("{loop %1.").arg(nameNum));
|
||||||
|
insertMe.append(s);
|
||||||
|
}
|
||||||
|
replace(start, end-start+endTag.length(), insertMe);
|
||||||
|
}
|
||||||
|
else { // repetitions==0
|
||||||
|
QString elsePart=mid(ellse+elseTag.length(), end-ellse-elseTag.length());
|
||||||
|
replace(start, end-start+endTag.length(), elsePart);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (repetitions>0) { // and no else part
|
||||||
|
QString loopPart=mid(start+startTag.length(), end-start-startTag.length());
|
||||||
|
QString insertMe;
|
||||||
|
for (int i=0; i<repetitions; ++i) {
|
||||||
|
// number variables, conditions and sub-loop within the loop
|
||||||
|
QString nameNum=name+QString::number(i);
|
||||||
|
QString s=loopPart;
|
||||||
|
s.replace(QString("{%1.").arg(name), QString("{%1.").arg(nameNum));
|
||||||
|
s.replace(QString("{if %1.").arg(name), QString("{if %1.").arg(nameNum));
|
||||||
|
s.replace(QString("{ifnot %1.").arg(name), QString("{ifnot %1.").arg(nameNum));
|
||||||
|
s.replace(QString("{else %1.").arg(name), QString("{else %1.").arg(nameNum));
|
||||||
|
s.replace(QString("{end %1.").arg(name), QString("{end %1.").arg(nameNum));
|
||||||
|
s.replace(QString("{loop %1.").arg(name), QString("{loop %1.").arg(nameNum));
|
||||||
|
insertMe.append(s);
|
||||||
|
}
|
||||||
|
replace(start, end-start+endTag.length(), insertMe);
|
||||||
|
}
|
||||||
|
else { // repetitions==0 and no else part
|
||||||
|
replace(start, end-start+endTag.length(), "");
|
||||||
|
}
|
||||||
|
start=indexOf(startTag,start);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
qWarning("Template: missing loop end %s in %s",qPrintable(endTag),qPrintable(sourceName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (count==0 && warnings) {
|
||||||
|
qWarning("Template: missing loop %s in %s",qPrintable(startTag),qPrintable(sourceName));
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Template::enableWarnings(bool enable) {
|
||||||
|
warnings=enable;
|
||||||
|
}
|
||||||
|
|
167
YACReaderLibrary/server/lib/bfTemplateEngine/template.h
Normal file
167
YACReaderLibrary/server/lib/bfTemplateEngine/template.h
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@author Stefan Frings
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef TEMPLATE_H
|
||||||
|
#define TEMPLATE_H
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
#include <QRegExp>
|
||||||
|
#include <QIODevice>
|
||||||
|
#include <QTextCodec>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
/**
|
||||||
|
Enhanced version of QString for template processing. Templates
|
||||||
|
are usually loaded from files, but may also be loaded from
|
||||||
|
prepared Strings.
|
||||||
|
Example template file:
|
||||||
|
<p><code><pre>
|
||||||
|
Hello {username}, how are you?
|
||||||
|
|
||||||
|
{if locked}
|
||||||
|
Your account is locked.
|
||||||
|
{else locked}
|
||||||
|
Welcome on our system.
|
||||||
|
{end locked}
|
||||||
|
|
||||||
|
The following users are on-line:
|
||||||
|
Username Time
|
||||||
|
{loop user}
|
||||||
|
{user.name} {user.time}
|
||||||
|
{end user}
|
||||||
|
</pre></code></p>
|
||||||
|
<p>
|
||||||
|
Example code to fill this template:
|
||||||
|
<p><code><pre>
|
||||||
|
Template t(QFile("test.tpl"),QTextCode::codecForName("UTF-8"));
|
||||||
|
t.setVariable("user", "Stefan");
|
||||||
|
t.setCondition("locked",false);
|
||||||
|
t.loop("user",2);
|
||||||
|
t.setVariable("user0.name,"Markus");
|
||||||
|
t.setVariable("user0.time,"8:30");
|
||||||
|
t.setVariable("user1.name,"Roland");
|
||||||
|
t.setVariable("user1.time,"8:45");
|
||||||
|
</pre></code></p>
|
||||||
|
<p>
|
||||||
|
The code example above shows how variable within loops are numbered.
|
||||||
|
Counting starts with 0. Loops can be nested, for example:
|
||||||
|
<p><code><pre>
|
||||||
|
<table>
|
||||||
|
{loop row}
|
||||||
|
<tr>
|
||||||
|
{loop row.column}
|
||||||
|
<td>{row.column.value}</td>
|
||||||
|
{end row.column}
|
||||||
|
</tr>
|
||||||
|
{end row}
|
||||||
|
</table>
|
||||||
|
</pre></code></p>
|
||||||
|
<p>
|
||||||
|
Example code to fill this nested loop with 3 rows and 4 columns:
|
||||||
|
<p><code><pre>
|
||||||
|
t.loop("row",3);
|
||||||
|
|
||||||
|
t.loop("row0.column",4);
|
||||||
|
t.setVariable("row0.column0.value","a");
|
||||||
|
t.setVariable("row0.column1.value","b");
|
||||||
|
t.setVariable("row0.column2.value","c");
|
||||||
|
t.setVariable("row0.column3.value","d");
|
||||||
|
|
||||||
|
t.loop("row1.column",4);
|
||||||
|
t.setVariable("row1.column0.value","e");
|
||||||
|
t.setVariable("row1.column1.value","f");
|
||||||
|
t.setVariable("row1.column2.value","g");
|
||||||
|
t.setVariable("row1.column3.value","h");
|
||||||
|
|
||||||
|
t.loop("row2.column",4);
|
||||||
|
t.setVariable("row2.column0.value","i");
|
||||||
|
t.setVariable("row2.column1.value","j");
|
||||||
|
t.setVariable("row2.column2.value","k");
|
||||||
|
t.setVariable("row2.column3.value","l");
|
||||||
|
</pre></code></p>
|
||||||
|
@see TemplateLoader
|
||||||
|
@see TemplateCache
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Template : public QString {
|
||||||
|
public:
|
||||||
|
|
||||||
|
/**
|
||||||
|
Constructor that reads the template from a string.
|
||||||
|
@param source The template source text
|
||||||
|
@param sourceName Name of the source file, used for logging
|
||||||
|
*/
|
||||||
|
Template(QString source, QString sourceName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Constructor that reads the template from a file. Note that this class does not
|
||||||
|
cache template files by itself, so using this constructor is only recommended
|
||||||
|
to be used on local filesystem.
|
||||||
|
@param file File that provides the source text
|
||||||
|
@param textCodec Encoding of the source
|
||||||
|
@see TemplateLoader
|
||||||
|
@see TemplateCache
|
||||||
|
*/
|
||||||
|
Template(QFile& file, QTextCodec* textCodec);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Replace a variable by the given value.
|
||||||
|
Affects tags with the syntax
|
||||||
|
|
||||||
|
- {name}
|
||||||
|
|
||||||
|
After settings the
|
||||||
|
value of a variable, the variable does not exist anymore,
|
||||||
|
it it cannot be changed multiple times.
|
||||||
|
@param name name of the variable
|
||||||
|
@param value new value
|
||||||
|
@return The count of variables that have been processed
|
||||||
|
*/
|
||||||
|
int setVariable(QString name, QString value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Set a condition. This affects tags with the syntax
|
||||||
|
|
||||||
|
- {if name}...{end name}
|
||||||
|
- {if name}...{else name}...{end name}
|
||||||
|
- {ifnot name}...{end name}
|
||||||
|
- {ifnot name}...{else name}...{end name}
|
||||||
|
|
||||||
|
@param name Name of the condition
|
||||||
|
@param value Value of the condition
|
||||||
|
@return The count of conditions that have been processed
|
||||||
|
*/
|
||||||
|
int setCondition(QString name, bool value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Set number of repetitions of a loop.
|
||||||
|
This affects tags with the syntax
|
||||||
|
|
||||||
|
- {loop name}...{end name}
|
||||||
|
- {loop name}...{else name}...{end name}
|
||||||
|
|
||||||
|
@param name Name of the loop
|
||||||
|
@param repetitions The number of repetitions
|
||||||
|
@return The number of loops that have been processed
|
||||||
|
*/
|
||||||
|
int loop(QString name, int repetitions);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Enable warnings for missing tags
|
||||||
|
@param enable Warnings are enabled, if true
|
||||||
|
*/
|
||||||
|
void enableWarnings(bool enable=true);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
/** Name of the source file */
|
||||||
|
QString sourceName;
|
||||||
|
|
||||||
|
/** Enables warnings, if true */
|
||||||
|
bool warnings;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // TEMPLATE_H
|
@ -0,0 +1,30 @@
|
|||||||
|
#include "templatecache.h"
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QStringList>
|
||||||
|
#include <QSet>
|
||||||
|
|
||||||
|
TemplateCache::TemplateCache(QSettings* settings, QObject* parent)
|
||||||
|
:TemplateLoader(settings,parent)
|
||||||
|
{
|
||||||
|
cache.setMaxCost(settings->value("cacheSize","1000000").toInt());
|
||||||
|
cacheTimeout=settings->value("cacheTime","60000").toInt();
|
||||||
|
qDebug("TemplateCache: timeout=%i, size=%i",cacheTimeout,cache.maxCost());
|
||||||
|
}
|
||||||
|
|
||||||
|
QString TemplateCache::tryFile(QString localizedName) {
|
||||||
|
qint64 now=QDateTime::currentMSecsSinceEpoch();
|
||||||
|
// search in cache
|
||||||
|
qDebug("TemplateCache: trying cached %s",qPrintable(localizedName));
|
||||||
|
CacheEntry* entry=cache.object(localizedName);
|
||||||
|
if (entry && (cacheTimeout==0 || entry->created>now-cacheTimeout)) {
|
||||||
|
return entry->document;
|
||||||
|
}
|
||||||
|
// search on filesystem
|
||||||
|
entry=new CacheEntry();
|
||||||
|
entry->created=now;
|
||||||
|
entry->document=TemplateLoader::tryFile(localizedName);
|
||||||
|
// Store in cache even when the file did not exist, to remember that there is no such file
|
||||||
|
cache.insert(localizedName,entry,entry->document.size());
|
||||||
|
return entry->document;
|
||||||
|
}
|
||||||
|
|
77
YACReaderLibrary/server/lib/bfTemplateEngine/templatecache.h
Normal file
77
YACReaderLibrary/server/lib/bfTemplateEngine/templatecache.h
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
#ifndef TEMPLATECACHE_H
|
||||||
|
#define TEMPLATECACHE_H
|
||||||
|
|
||||||
|
#include "templateloader.h"
|
||||||
|
#include <QCache>
|
||||||
|
|
||||||
|
/**
|
||||||
|
Caching template loader, reduces the amount of I/O and improves performance
|
||||||
|
on remote file systems. The cache has a limited size, it prefers to keep
|
||||||
|
the last recently used files. Optionally, the maximum time of cached entries
|
||||||
|
can be defined to enforce a reload of the template file after a while.
|
||||||
|
<p>
|
||||||
|
In case of local file system, the use of this cache is optionally, since
|
||||||
|
the operating system caches files already.
|
||||||
|
<p>
|
||||||
|
Loads localized versions of template files. If the caller requests a file with the
|
||||||
|
name "index" and the suffix is ".tpl" and the requested locale is "de_DE, de, en-US",
|
||||||
|
then files are searched in the following order:
|
||||||
|
|
||||||
|
- index-de_DE.tpl
|
||||||
|
- index-de.tpl
|
||||||
|
- index-en_US.tpl
|
||||||
|
- index-en.tpl
|
||||||
|
- index.tpl
|
||||||
|
<p>
|
||||||
|
The following settings are required:
|
||||||
|
<code><pre>
|
||||||
|
path=.
|
||||||
|
suffix=.tpl
|
||||||
|
encoding=UTF-8
|
||||||
|
cacheSize=1000000
|
||||||
|
cacheTime=60000
|
||||||
|
</pre></code>
|
||||||
|
The path is relative to the directory of the config file. In case of windows, if the
|
||||||
|
settings are in the registry, the path is relative to the current working directory.
|
||||||
|
<p>
|
||||||
|
Files are cached as long as possible, when cacheTime=0.
|
||||||
|
@see TemplateLoader
|
||||||
|
*/
|
||||||
|
|
||||||
|
class TemplateCache : public TemplateLoader {
|
||||||
|
Q_OBJECT
|
||||||
|
Q_DISABLE_COPY(TemplateCache);
|
||||||
|
public:
|
||||||
|
|
||||||
|
/**
|
||||||
|
Constructor.
|
||||||
|
@param settings configurations settings
|
||||||
|
@param parent Parent object
|
||||||
|
*/
|
||||||
|
TemplateCache(QSettings* settings, QObject* parent=0);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
/**
|
||||||
|
Try to get a file from cache or filesystem.
|
||||||
|
@param localizedName Name of the template with locale to find
|
||||||
|
@return The template document, or empty string if not found
|
||||||
|
*/
|
||||||
|
virtual QString tryFile(QString localizedName);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
struct CacheEntry {
|
||||||
|
QString document;
|
||||||
|
qint64 created;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Timeout for each cached file */
|
||||||
|
int cacheTimeout;
|
||||||
|
|
||||||
|
/** Cache storage */
|
||||||
|
QCache<QString,CacheEntry> cache;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // TEMPLATECACHE_H
|
103
YACReaderLibrary/server/lib/bfTemplateEngine/templateloader.cpp
Normal file
103
YACReaderLibrary/server/lib/bfTemplateEngine/templateloader.cpp
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@author Stefan Frings
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "templateloader.h"
|
||||||
|
#include <QFile>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QStringList>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QSet>
|
||||||
|
|
||||||
|
TemplateLoader::TemplateLoader(QSettings* settings, QObject* parent)
|
||||||
|
: QObject(parent)
|
||||||
|
{
|
||||||
|
templatePath=settings->value("path",".").toString();
|
||||||
|
// Convert relative path to absolute, based on the directory of the config file.
|
||||||
|
#ifdef Q_OS_WIN32
|
||||||
|
if (QDir::isRelativePath(templatePath) && settings->format()!=QSettings::NativeFormat)
|
||||||
|
#else
|
||||||
|
if (QDir::isRelativePath(templatePath))
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
QFileInfo configFile(settings->fileName());
|
||||||
|
templatePath=QFileInfo(configFile.absolutePath(),templatePath).absoluteFilePath();
|
||||||
|
}
|
||||||
|
fileNameSuffix=settings->value("suffix",".tpl").toString();
|
||||||
|
QString encoding=settings->value("encoding").toString();
|
||||||
|
if (encoding.isEmpty()) {
|
||||||
|
textCodec=QTextCodec::codecForLocale();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
textCodec=QTextCodec::codecForName(encoding.toAscii());
|
||||||
|
}
|
||||||
|
qDebug("TemplateLoader: path=%s, codec=%s",qPrintable(templatePath),textCodec->name().data());
|
||||||
|
}
|
||||||
|
|
||||||
|
TemplateLoader::~TemplateLoader() {}
|
||||||
|
|
||||||
|
QString TemplateLoader::tryFile(QString localizedName) {
|
||||||
|
QString fileName=templatePath+"/"+localizedName+fileNameSuffix;
|
||||||
|
qDebug("TemplateCache: trying file %s",qPrintable(fileName));
|
||||||
|
QFile file(fileName);
|
||||||
|
if (file.exists()) {
|
||||||
|
file.open(QIODevice::ReadOnly);
|
||||||
|
QString document=textCodec->toUnicode(file.readAll());
|
||||||
|
file.close();
|
||||||
|
if (file.error()) {
|
||||||
|
qCritical("TemplateLoader: cannot load file %s, %s",qPrintable(fileName),qPrintable(file.errorString()));
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return document;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
Template TemplateLoader::getTemplate(QString templateName, QString locales) {
|
||||||
|
mutex.lock();
|
||||||
|
QSet<QString> tried; // used to suppress duplicate attempts
|
||||||
|
QStringList locs=locales.split(',',QString::SkipEmptyParts);
|
||||||
|
|
||||||
|
// Search for exact match
|
||||||
|
foreach (QString loc,locs) {
|
||||||
|
loc.replace(QRegExp(";.*"),"");
|
||||||
|
loc.replace('-','_');
|
||||||
|
QString localizedName=templateName+"-"+loc.trimmed();
|
||||||
|
if (!tried.contains(localizedName)) {
|
||||||
|
QString document=tryFile(localizedName);
|
||||||
|
if (!document.isEmpty()) {
|
||||||
|
mutex.unlock();
|
||||||
|
return Template(document,localizedName);
|
||||||
|
}
|
||||||
|
tried.insert(localizedName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search for correct language but any country
|
||||||
|
foreach (QString loc,locs) {
|
||||||
|
loc.replace(QRegExp("[;_-].*"),"");
|
||||||
|
QString localizedName=templateName+"-"+loc.trimmed();
|
||||||
|
if (!tried.contains(localizedName)) {
|
||||||
|
QString document=tryFile(localizedName);
|
||||||
|
if (!document.isEmpty()) {
|
||||||
|
mutex.unlock();
|
||||||
|
return Template(document,localizedName);
|
||||||
|
}
|
||||||
|
tried.insert(localizedName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search for default file
|
||||||
|
QString document=tryFile(templateName);
|
||||||
|
if (!document.isEmpty()) {
|
||||||
|
mutex.unlock();
|
||||||
|
return Template(document,templateName);
|
||||||
|
}
|
||||||
|
|
||||||
|
qCritical("TemplateCache: cannot find template %s",qPrintable(templateName));
|
||||||
|
mutex.unlock();
|
||||||
|
return Template("",templateName);
|
||||||
|
}
|
@ -0,0 +1,85 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@author Stefan Frings
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef TEMPLATELOADER_H
|
||||||
|
#define TEMPLATELOADER_H
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
#include <QSettings>
|
||||||
|
#include <QTextCodec>
|
||||||
|
#include "template.h"
|
||||||
|
#include <QMutex>
|
||||||
|
|
||||||
|
/**
|
||||||
|
Loads localized versions of template files. If the caller requests a file with the
|
||||||
|
name "index" and the suffix is ".tpl" and the requested locale is "de_DE, de, en-US",
|
||||||
|
then files are searched in the following order:
|
||||||
|
|
||||||
|
- index-de_DE.tpl
|
||||||
|
- index-de.tpl
|
||||||
|
- index-en_US.tpl
|
||||||
|
- index-en.tpl
|
||||||
|
- index.tpl
|
||||||
|
|
||||||
|
The following settings are required:
|
||||||
|
<code><pre>
|
||||||
|
path=.
|
||||||
|
suffix=.tpl
|
||||||
|
encoding=UTF-8
|
||||||
|
</pre></code>
|
||||||
|
The path is relative to the directory of the config file. In case of windows, if the
|
||||||
|
settings are in the registry, the path is relative to the current working directory.
|
||||||
|
@see TemplateCache
|
||||||
|
*/
|
||||||
|
|
||||||
|
class TemplateLoader : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
Q_DISABLE_COPY(TemplateLoader);
|
||||||
|
public:
|
||||||
|
|
||||||
|
/**
|
||||||
|
Constructor.
|
||||||
|
@param settings configurations settings
|
||||||
|
@param parent parent object
|
||||||
|
*/
|
||||||
|
TemplateLoader(QSettings* settings, QObject* parent=0);
|
||||||
|
|
||||||
|
/** Destructor */
|
||||||
|
virtual ~TemplateLoader();
|
||||||
|
|
||||||
|
/**
|
||||||
|
Get a template for a given locale.
|
||||||
|
This method is thread safe.
|
||||||
|
@param templateName base name of the template file, without suffix and without locale
|
||||||
|
@param locales Requested locale(s), e.g. "de_DE, en_EN". Strings in the format of
|
||||||
|
the HTTP header Accept-Locale may be used. Badly formatted parts in the string are silently
|
||||||
|
ignored.
|
||||||
|
@return If the template cannot be loaded, an error message is logged and an empty template is returned.
|
||||||
|
*/
|
||||||
|
Template getTemplate(QString templateName, QString locales=QString());
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
/**
|
||||||
|
Try to get a file from cache or filesystem.
|
||||||
|
@param localizedName Name of the template with locale to find
|
||||||
|
@return The template document, or empty string if not found
|
||||||
|
*/
|
||||||
|
virtual QString tryFile(QString localizedName);
|
||||||
|
|
||||||
|
/** Directory where the templates are searched */
|
||||||
|
QString templatePath;
|
||||||
|
|
||||||
|
/** Suffix to the filenames */
|
||||||
|
QString fileNameSuffix;
|
||||||
|
|
||||||
|
/** Codec for decoding the files */
|
||||||
|
QTextCodec* textCodec;
|
||||||
|
|
||||||
|
/** Used to synchronize threads */
|
||||||
|
QMutex mutex;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // TEMPLATELOADER_H
|
58
YACReaderLibrary/server/main.cpp
Normal file
58
YACReaderLibrary/server/main.cpp
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@author Stefan Frings
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
@mainpage
|
||||||
|
This project provies libraries for writing server-side web application in C++
|
||||||
|
based on the Qt toolkit. It is a light-weight implementation that works like
|
||||||
|
Java Servlets from the programmers point of view.
|
||||||
|
<p>
|
||||||
|
Features:
|
||||||
|
|
||||||
|
- HTTP 1.1 web server
|
||||||
|
- persistent connections
|
||||||
|
- chunked and non-chunked transfer
|
||||||
|
- file uploads (multipart encoded forms)
|
||||||
|
- cookies
|
||||||
|
- sessions
|
||||||
|
- uses dynamic thread pool
|
||||||
|
- optional file cache
|
||||||
|
- Template engine
|
||||||
|
- multi-language
|
||||||
|
- optional file cache
|
||||||
|
- Logger
|
||||||
|
- configurable without program restart
|
||||||
|
- automatic backup and file rotation
|
||||||
|
- configurable message format
|
||||||
|
- messages may contain thread-local variables
|
||||||
|
- optional buffer for writing history details in case of an error
|
||||||
|
- Example application
|
||||||
|
- Install and run as Windows service, unix daemon or at the command-line
|
||||||
|
- Search config file in multiple common directories
|
||||||
|
- Demonstrates how to write servlets for common use-cases
|
||||||
|
|
||||||
|
If you write a real application based on this source, take a look into startup.cpp,
|
||||||
|
which contains startup and shutdown procedures. The example source sets
|
||||||
|
up a single listener on port 8080, however multiple listeners with individual
|
||||||
|
configuration could be set up.
|
||||||
|
<p>
|
||||||
|
Incoming requests are mapped to controller classes in requestmapper.cpp, based on the
|
||||||
|
requested path. If you want to learn form the example, then focus on these classes.
|
||||||
|
<p>
|
||||||
|
High-availability and HTTPS encryption can be easily added by putting an Apache HTTP server
|
||||||
|
in front of the self-written web application using the mod-proxy module with sticky sessions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "startup.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
Entry point of the program.
|
||||||
|
Passes control to the service helper.
|
||||||
|
*/
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
// Use a qtservice wrapper to start the application as a Windows service or Unix daemon
|
||||||
|
Startup startup(argc, argv);
|
||||||
|
return startup.exec();
|
||||||
|
}
|
46
YACReaderLibrary/server/requestmapper.cpp
Normal file
46
YACReaderLibrary/server/requestmapper.cpp
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@author Stefan Frings
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "requestmapper.h"
|
||||||
|
#include "static.h"
|
||||||
|
#include "staticfilecontroller.h"
|
||||||
|
#include "controllers/dumpcontroller.h"
|
||||||
|
#include "controllers/templatecontroller.h"
|
||||||
|
#include "controllers/formcontroller.h"
|
||||||
|
#include "controllers/fileuploadcontroller.h"
|
||||||
|
#include "controllers/sessioncontroller.h"
|
||||||
|
|
||||||
|
RequestMapper::RequestMapper(QObject* parent)
|
||||||
|
:HttpRequestHandler(parent) {}
|
||||||
|
|
||||||
|
void RequestMapper::service(HttpRequest& request, HttpResponse& response) {
|
||||||
|
QByteArray path=request.getPath();
|
||||||
|
qDebug("RequestMapper: path=%s",path.data());
|
||||||
|
|
||||||
|
if (path.startsWith("/dump")) {
|
||||||
|
DumpController().service(request, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (path.startsWith("/template")) {
|
||||||
|
TemplateController().service(request, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (path.startsWith("/form")) {
|
||||||
|
FormController().service(request, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (path.startsWith("/file")) {
|
||||||
|
FileUploadController().service(request, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (path.startsWith("/session")) {
|
||||||
|
SessionController().service(request, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
// All other pathes are mapped to the static file controller.
|
||||||
|
else {
|
||||||
|
Static::staticFileController->service(request, response);
|
||||||
|
}
|
||||||
|
}
|
36
YACReaderLibrary/server/requestmapper.h
Normal file
36
YACReaderLibrary/server/requestmapper.h
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@author Stefan Frings
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef REQUESTMAPPER_H
|
||||||
|
#define REQUESTMAPPER_H
|
||||||
|
|
||||||
|
#include "httprequesthandler.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
The request mapper dispatches incoming HTTP requests to controller classes
|
||||||
|
depending on the requested path.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class RequestMapper : public HttpRequestHandler {
|
||||||
|
Q_OBJECT
|
||||||
|
Q_DISABLE_COPY(RequestMapper)
|
||||||
|
public:
|
||||||
|
|
||||||
|
/**
|
||||||
|
Constructor.
|
||||||
|
@param parent Parent object
|
||||||
|
*/
|
||||||
|
RequestMapper(QObject* parent=0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Dispatch a request to a controller.
|
||||||
|
@param request The received HTTP request
|
||||||
|
@param response Must be used to return the response
|
||||||
|
*/
|
||||||
|
void service(HttpRequest& request, HttpResponse& response);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // REQUESTMAPPER_H
|
26
YACReaderLibrary/server/server.pri
Normal file
26
YACReaderLibrary/server/server.pri
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
INCLUDEPATH += $$PWD
|
||||||
|
DEPENDPATH += $$PWD
|
||||||
|
|
||||||
|
HEADERS += \
|
||||||
|
$$PWD/static.h \
|
||||||
|
$$PWD/startup.h \
|
||||||
|
$$PWD/requestmapper.h \
|
||||||
|
$$PWD/controllers/dumpcontroller.h \
|
||||||
|
$$PWD/controllers/templatecontroller.h \
|
||||||
|
$$PWD/controllers/formcontroller.h \
|
||||||
|
$$PWD/controllers/fileuploadcontroller.h \
|
||||||
|
$$PWD/controllers/sessioncontroller.h
|
||||||
|
|
||||||
|
SOURCES += \
|
||||||
|
$$PWD/static.cpp \
|
||||||
|
$$PWD/startup.cpp \
|
||||||
|
$$PWD/requestmapper.cpp \
|
||||||
|
$$PWD/controllers/dumpcontroller.cpp \
|
||||||
|
$$PWD/controllers/templatecontroller.cpp \
|
||||||
|
$$PWD/controllers/formcontroller.cpp \
|
||||||
|
$$PWD/controllers/fileuploadcontroller.cpp \
|
||||||
|
$$PWD/controllers/sessioncontroller.cpp
|
||||||
|
|
||||||
|
include(lib/bfLogging/bfLogging.pri)
|
||||||
|
include(lib/bfHttpServer/bfHttpServer.pri)
|
||||||
|
include(lib/bfTemplateEngine/bfTemplateEngine.pri)
|
77
YACReaderLibrary/server/startup.cpp
Normal file
77
YACReaderLibrary/server/startup.cpp
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@author Stefan Frings
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "static.h"
|
||||||
|
#include "startup.h"
|
||||||
|
#include "dualfilelogger.h"
|
||||||
|
#include "httplistener.h"
|
||||||
|
#include "requestmapper.h"
|
||||||
|
#include "staticfilecontroller.h"
|
||||||
|
#include <QDir>
|
||||||
|
#include <QApplication>
|
||||||
|
|
||||||
|
/** Name of this application */
|
||||||
|
#define APPNAME "YACReaderLibrary"
|
||||||
|
|
||||||
|
/** Publisher of this application */
|
||||||
|
#define ORGANISATION "Butterfly"
|
||||||
|
|
||||||
|
/** Short description of this application */
|
||||||
|
#define DESCRIPTION "Web service based on Qt"
|
||||||
|
|
||||||
|
void Startup::start() {
|
||||||
|
// Initialize the core application
|
||||||
|
QCoreApplication* app = QApplication::instance();
|
||||||
|
app->setApplicationName(APPNAME);
|
||||||
|
//app->setOrganizationName(ORGANISATION);
|
||||||
|
QString configFileName=Static::getConfigDir()+"/"+QCoreApplication::applicationName()+".ini";
|
||||||
|
|
||||||
|
// Configure logging into files
|
||||||
|
QSettings* mainLogSettings=new QSettings(configFileName,QSettings::IniFormat,app);
|
||||||
|
mainLogSettings->beginGroup("mainLogFile");
|
||||||
|
QSettings* debugLogSettings=new QSettings(configFileName,QSettings::IniFormat,app);
|
||||||
|
debugLogSettings->beginGroup("debugLogFile");
|
||||||
|
Logger* logger=new DualFileLogger(mainLogSettings,debugLogSettings,10000,app);
|
||||||
|
logger->installMsgHandler();
|
||||||
|
|
||||||
|
// Configure template loader and cache
|
||||||
|
QSettings* templateSettings=new QSettings(configFileName,QSettings::IniFormat,app);
|
||||||
|
templateSettings->beginGroup("templates");
|
||||||
|
Static::templateLoader=new TemplateCache(templateSettings,app);
|
||||||
|
|
||||||
|
// Configure session store
|
||||||
|
QSettings* sessionSettings=new QSettings(configFileName,QSettings::IniFormat,app);
|
||||||
|
sessionSettings->beginGroup("sessions");
|
||||||
|
Static::sessionStore=new HttpSessionStore(sessionSettings,app);
|
||||||
|
|
||||||
|
// Configure static file controller
|
||||||
|
QSettings* fileSettings=new QSettings(configFileName,QSettings::IniFormat,app);
|
||||||
|
fileSettings->beginGroup("docroot");
|
||||||
|
Static::staticFileController=new StaticFileController(fileSettings,app);
|
||||||
|
|
||||||
|
// Configure and start the TCP listener
|
||||||
|
qDebug("ServiceHelper: Starting service");
|
||||||
|
QSettings* listenerSettings=new QSettings(configFileName,QSettings::IniFormat,app);
|
||||||
|
listenerSettings->beginGroup("listener");
|
||||||
|
listener = new HttpListener(listenerSettings,new RequestMapper(app),app);
|
||||||
|
|
||||||
|
qDebug("ServiceHelper: Service has started");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Startup::stop() {
|
||||||
|
qDebug("ServiceHelper: Service has been stopped");
|
||||||
|
// QCoreApplication destroys all objects that have been created in start().
|
||||||
|
listener->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Startup::Startup()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
30
YACReaderLibrary/server/startup.h
Normal file
30
YACReaderLibrary/server/startup.h
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@author Stefan Frings
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef STARTUP_H
|
||||||
|
#define STARTUP_H
|
||||||
|
|
||||||
|
class HttpListener;
|
||||||
|
/**
|
||||||
|
Helper class to install and run the application as a windows
|
||||||
|
service.
|
||||||
|
*/
|
||||||
|
class Startup
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
//QTcpServer
|
||||||
|
HttpListener * listener;
|
||||||
|
public:
|
||||||
|
|
||||||
|
/** Constructor */
|
||||||
|
Startup();
|
||||||
|
/** Start the server */
|
||||||
|
void start();
|
||||||
|
/** Stop the server */
|
||||||
|
void stop();
|
||||||
|
protected:
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // STARTUP_H
|
59
YACReaderLibrary/server/static.cpp
Normal file
59
YACReaderLibrary/server/static.cpp
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@author Stefan Frings
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "static.h"
|
||||||
|
#include <QCoreApplication>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
QString Static::configDir=0;
|
||||||
|
|
||||||
|
TemplateLoader* Static::templateLoader=0;
|
||||||
|
|
||||||
|
HttpSessionStore* Static::sessionStore=0;
|
||||||
|
|
||||||
|
StaticFileController* Static::staticFileController=0;
|
||||||
|
|
||||||
|
QString Static::getConfigFileName() {
|
||||||
|
return QString("%1/%2.ini").arg(getConfigDir()).arg(QCoreApplication::applicationName());
|
||||||
|
}
|
||||||
|
|
||||||
|
QString Static::getConfigDir() {
|
||||||
|
if (!configDir.isNull()) {
|
||||||
|
return configDir;
|
||||||
|
}
|
||||||
|
// Search config file
|
||||||
|
|
||||||
|
QString binDir=QCoreApplication::applicationDirPath();
|
||||||
|
QString organization=QCoreApplication::organizationName();
|
||||||
|
QString configFileName=QCoreApplication::applicationName()+".ini";
|
||||||
|
|
||||||
|
QStringList searchList;
|
||||||
|
searchList.append(QDir::cleanPath(binDir));
|
||||||
|
searchList.append(QDir::cleanPath(binDir+"/../etc"));
|
||||||
|
searchList.append(QDir::cleanPath(binDir+"/../../etc")); // for development under windows
|
||||||
|
searchList.append(QDir::rootPath()+"etc/xdg/"+organization);
|
||||||
|
searchList.append(QDir::rootPath()+"etc/opt");
|
||||||
|
searchList.append(QDir::rootPath()+"etc");
|
||||||
|
|
||||||
|
foreach (QString dir, searchList) {
|
||||||
|
QFile file(dir+"/"+configFileName);
|
||||||
|
if (file.exists()) {
|
||||||
|
// found
|
||||||
|
configDir=dir;
|
||||||
|
qDebug("Using config file %s",qPrintable(file.fileName()));
|
||||||
|
return configDir;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// not found
|
||||||
|
foreach (QString dir, searchList) {
|
||||||
|
qWarning("%s/%s not found",qPrintable(dir),qPrintable(configFileName));
|
||||||
|
}
|
||||||
|
qFatal("Cannot find config file %s",qPrintable(configFileName));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
64
YACReaderLibrary/server/static.h
Normal file
64
YACReaderLibrary/server/static.h
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
/**
|
||||||
|
@file
|
||||||
|
@author Stefan Frings
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef STATIC_H
|
||||||
|
#define STATIC_H
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
#include "templatecache.h"
|
||||||
|
#include "httpsessionstore.h"
|
||||||
|
#include "staticfilecontroller.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
This class contains some static resources that are used by the application.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Static
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
/**
|
||||||
|
Search the main config file and return its full path.
|
||||||
|
On the first call, the INI file gets searched. If not found,
|
||||||
|
the application aborts with an error message.
|
||||||
|
<p>
|
||||||
|
The filename is the applications name plus the ending ".ini". It is searched
|
||||||
|
in the following directories:
|
||||||
|
|
||||||
|
- Same directory as the applications executable file
|
||||||
|
- In ../etc relative to the applications executable file
|
||||||
|
- In ../../etc relative to the applications executable file
|
||||||
|
- In /etc/xdg/{organisation name} on the root drive
|
||||||
|
- In /etc/opt on the root drive
|
||||||
|
- In /etc on the root drive
|
||||||
|
|
||||||
|
*/
|
||||||
|
static QString getConfigFileName();
|
||||||
|
|
||||||
|
/**
|
||||||
|
Gets the directory where the main config file is located.
|
||||||
|
On the first call, the INI file gets searched. If not found,
|
||||||
|
the application aborts with an error message.
|
||||||
|
@see getConfigFileName()
|
||||||
|
*/
|
||||||
|
static QString getConfigDir();
|
||||||
|
|
||||||
|
/** Cache for template files */
|
||||||
|
static TemplateLoader* templateLoader;
|
||||||
|
|
||||||
|
/** Storage for session cookies */
|
||||||
|
static HttpSessionStore* sessionStore;
|
||||||
|
|
||||||
|
/** Controller for static files */
|
||||||
|
static StaticFileController* staticFileController;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
/** Directory of the main config file */
|
||||||
|
static QString configDir;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // STATIC_H
|
Reference in New Issue
Block a user