mirror of
https://github.com/YACReader/yacreader
synced 2025-05-28 03:10:27 -04:00
primera versi?n con servidor http incorporado
This commit is contained in:
parent
95817d14ee
commit
2598d6494a
@ -6,10 +6,11 @@ TEMPLATE = app
|
||||
TARGET =
|
||||
DEPENDPATH += .
|
||||
INCLUDEPATH += .
|
||||
INCLUDEPATH += ../common
|
||||
INCLUDEPATH += ../common \
|
||||
./server
|
||||
CONFIG += release
|
||||
CONFIG -= flat
|
||||
QT += sql
|
||||
QT += sql network
|
||||
|
||||
# Input
|
||||
HEADERS += comic_flow.h \
|
||||
@ -45,6 +46,10 @@ SOURCES += comic_flow.cpp \
|
||||
../common/qnaturalsorting.cpp \
|
||||
data_base_management.cpp \
|
||||
bundle_creator.cpp
|
||||
|
||||
include(./server/server.pri)
|
||||
|
||||
|
||||
RESOURCES += images.qrc files.qrc
|
||||
RC_FILE = icon.rc
|
||||
|
||||
@ -52,3 +57,4 @@ TRANSLATIONS = yacreaderlibrary_es.ts
|
||||
|
||||
Release:DESTDIR = ../release
|
||||
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 <QApplication>
|
||||
|
||||
#include "startup.h"
|
||||
#define PICTUREFLOW_QT4 1
|
||||
|
||||
int main( int argc, char ** argv )
|
||||
@ -18,5 +18,8 @@ int main( int argc, char ** argv )
|
||||
mw->resize(800,480);
|
||||
mw->showMaximized();
|
||||
|
||||
Startup * s = new Startup();
|
||||
s->start();
|
||||
|
||||
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
|
Loading…
Reference in New Issue
Block a user