mirror of
https://github.com/YACReader/yacreader
synced 2025-06-04 01:28:55 -04:00
Update httpserver to 1.6.5
This commit is contained in:
parent
44d7c892c1
commit
706e0921f3
@ -5,62 +5,107 @@
|
|||||||
|
|
||||||
#include "httpconnectionhandler.h"
|
#include "httpconnectionhandler.h"
|
||||||
#include "httpresponse.h"
|
#include "httpresponse.h"
|
||||||
#include <QTimer>
|
|
||||||
#include <QCoreApplication>
|
|
||||||
|
|
||||||
HttpConnectionHandler::HttpConnectionHandler(QSettings* settings, HttpRequestHandler* requestHandler)
|
HttpConnectionHandler::HttpConnectionHandler(QSettings* settings, HttpRequestHandler* requestHandler, QSslConfiguration* sslConfiguration)
|
||||||
: QThread()
|
: QThread()
|
||||||
{
|
{
|
||||||
Q_ASSERT(settings!=0);
|
Q_ASSERT(settings!=0);
|
||||||
Q_ASSERT(requestHandler!=0);
|
Q_ASSERT(requestHandler!=0);
|
||||||
this->settings=settings;
|
this->settings=settings;
|
||||||
this->requestHandler=requestHandler;
|
this->requestHandler=requestHandler;
|
||||||
|
this->sslConfiguration=sslConfiguration;
|
||||||
currentRequest=0;
|
currentRequest=0;
|
||||||
busy = false;
|
busy=false;
|
||||||
|
|
||||||
|
// Create TCP or SSL socket
|
||||||
|
createSocket();
|
||||||
|
|
||||||
// execute signals in my own thread
|
// execute signals in my own thread
|
||||||
moveToThread(this);
|
moveToThread(this);
|
||||||
socket.moveToThread(this);
|
socket->moveToThread(this);
|
||||||
readTimer.moveToThread(this);
|
readTimer.moveToThread(this);
|
||||||
connect(&socket, SIGNAL(readyRead()), SLOT(read()));
|
|
||||||
connect(&socket, SIGNAL(disconnected()), SLOT(disconnected()));
|
// Connect signals
|
||||||
|
connect(socket, SIGNAL(readyRead()), SLOT(read()));
|
||||||
|
connect(socket, SIGNAL(disconnected()), SLOT(disconnected()));
|
||||||
connect(&readTimer, SIGNAL(timeout()), SLOT(readTimeout()));
|
connect(&readTimer, SIGNAL(timeout()), SLOT(readTimeout()));
|
||||||
readTimer.setSingleShot(true);
|
readTimer.setSingleShot(true);
|
||||||
|
|
||||||
qDebug("HttpConnectionHandler (%p): constructed", this);
|
qDebug("HttpConnectionHandler (%p): constructed", this);
|
||||||
this->start();
|
this->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
HttpConnectionHandler::~HttpConnectionHandler() {
|
HttpConnectionHandler::~HttpConnectionHandler()
|
||||||
socket.close();
|
{
|
||||||
quit();
|
quit();
|
||||||
wait();
|
wait();
|
||||||
qDebug("HttpConnectionHandler (%p): destroyed", this);
|
qDebug("HttpConnectionHandler (%p): destroyed", this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void HttpConnectionHandler::run() {
|
void HttpConnectionHandler::createSocket()
|
||||||
qDebug("HttpConnectionHandler (%p): thread started", this);
|
{
|
||||||
try {
|
// If SSL is supported and configured, then create an instance of QSslSocket
|
||||||
exec();
|
#ifndef QT_NO_OPENSSL
|
||||||
}
|
if (sslConfiguration)
|
||||||
catch (...) {
|
{
|
||||||
qCritical("HttpConnectionHandler (%p): an uncatched exception occured in the thread",this);
|
QSslSocket* sslSocket=new QSslSocket();
|
||||||
}
|
sslSocket->setSslConfiguration(*sslConfiguration);
|
||||||
qDebug("HttpConnectionHandler (%p): thread stopped", this);
|
socket=sslSocket;
|
||||||
// Change to the main thread, otherwise deleteLater() would not work
|
qDebug("HttpConnectionHandler (%p): SSL is enabled", this);
|
||||||
moveToThread(QCoreApplication::instance()->thread());
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
// else create an instance of QTcpSocket
|
||||||
|
socket=new QTcpSocket();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void HttpConnectionHandler::handleConnection(tSocketDescriptor socketDescriptor) {
|
void HttpConnectionHandler::run()
|
||||||
|
{
|
||||||
|
qDebug("HttpConnectionHandler (%p): thread started", this);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
exec();
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
qCritical("HttpConnectionHandler (%p): an uncatched exception occured in the thread",this);
|
||||||
|
}
|
||||||
|
socket->close();
|
||||||
|
delete socket;
|
||||||
|
readTimer.stop();
|
||||||
|
qDebug("HttpConnectionHandler (%p): thread stopped", this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void HttpConnectionHandler::handleConnection(tSocketDescriptor socketDescriptor)
|
||||||
|
{
|
||||||
qDebug("HttpConnectionHandler (%p): handle new connection", this);
|
qDebug("HttpConnectionHandler (%p): handle new connection", this);
|
||||||
busy = true;
|
busy = true;
|
||||||
Q_ASSERT(socket.isOpen()==false); // if not, then the handler is already busy
|
Q_ASSERT(socket->isOpen()==false); // if not, then the handler is already busy
|
||||||
|
|
||||||
if (!socket.setSocketDescriptor(socketDescriptor)) {
|
//UGLY workaround - we need to clear writebuffer before reusing this socket
|
||||||
qCritical("HttpConnectionHandler (%p): cannot initialize socket: %s", this,qPrintable(socket.errorString()));
|
//https://bugreports.qt-project.org/browse/QTBUG-28914
|
||||||
|
socket->connectToHost("",0);
|
||||||
|
socket->abort();
|
||||||
|
|
||||||
|
if (!socket->setSocketDescriptor(socketDescriptor))
|
||||||
|
{
|
||||||
|
qCritical("HttpConnectionHandler (%p): cannot initialize socket: %s", this,qPrintable(socket->errorString()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef QT_NO_OPENSSL
|
||||||
|
// Switch on encryption, if SSL is configured
|
||||||
|
if (sslConfiguration)
|
||||||
|
{
|
||||||
|
qDebug("HttpConnectionHandler (%p): Starting encryption", this);
|
||||||
|
((QSslSocket*)socket)->startServerEncryption();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// Start timer for read timeout
|
// Start timer for read timeout
|
||||||
int readTimeout=settings->value("readTimeout",10000).toInt();
|
int readTimeout=settings->value("readTimeout",10000).toInt();
|
||||||
readTimer.start(readTimeout);
|
readTimer.start(readTimeout);
|
||||||
@ -70,49 +115,60 @@ void HttpConnectionHandler::handleConnection(tSocketDescriptor socketDescriptor)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool HttpConnectionHandler::isBusy() {
|
bool HttpConnectionHandler::isBusy()
|
||||||
|
{
|
||||||
return busy;
|
return busy;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpConnectionHandler::setBusy() {
|
void HttpConnectionHandler::setBusy()
|
||||||
|
{
|
||||||
this->busy = true;
|
this->busy = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void HttpConnectionHandler::readTimeout() {
|
void HttpConnectionHandler::readTimeout()
|
||||||
|
{
|
||||||
qDebug("HttpConnectionHandler (%p): read timeout occured",this);
|
qDebug("HttpConnectionHandler (%p): read timeout occured",this);
|
||||||
|
|
||||||
//Commented out because QWebView cannot handle this.
|
//Commented out because QWebView cannot handle this.
|
||||||
//socket.write("HTTP/1.1 408 request timeout\r\nConnection: close\r\n\r\n408 request timeout\r\n");
|
//socket->write("HTTP/1.1 408 request timeout\r\nConnection: close\r\n\r\n408 request timeout\r\n");
|
||||||
|
|
||||||
socket.disconnectFromHost();
|
socket->flush();
|
||||||
|
socket->disconnectFromHost();
|
||||||
delete currentRequest;
|
delete currentRequest;
|
||||||
currentRequest=0;
|
currentRequest=0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void HttpConnectionHandler::disconnected() {
|
void HttpConnectionHandler::disconnected()
|
||||||
|
{
|
||||||
qDebug("HttpConnectionHandler (%p): disconnected", this);
|
qDebug("HttpConnectionHandler (%p): disconnected", this);
|
||||||
socket.close();
|
socket->close();
|
||||||
readTimer.stop();
|
readTimer.stop();
|
||||||
busy = false;
|
busy = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpConnectionHandler::read() {
|
void HttpConnectionHandler::read()
|
||||||
while (socket.bytesAvailable()) {
|
{
|
||||||
#ifdef SUPERVERBOSE
|
// The loop adds support for HTTP pipelinig
|
||||||
qDebug("HttpConnectionHandler (%p): read input",this);
|
while (socket->bytesAvailable())
|
||||||
#endif
|
{
|
||||||
|
#ifdef SUPERVERBOSE
|
||||||
|
qDebug("HttpConnectionHandler (%p): read input",this);
|
||||||
|
#endif
|
||||||
|
|
||||||
// Create new HttpRequest object if necessary
|
// Create new HttpRequest object if necessary
|
||||||
if (!currentRequest) {
|
if (!currentRequest)
|
||||||
|
{
|
||||||
currentRequest=new HttpRequest(settings);
|
currentRequest=new HttpRequest(settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect data for the request object
|
// Collect data for the request object
|
||||||
while (socket.bytesAvailable() && currentRequest->getStatus()!=HttpRequest::complete && currentRequest->getStatus()!=HttpRequest::abort) {
|
while (socket->bytesAvailable() && currentRequest->getStatus()!=HttpRequest::complete && currentRequest->getStatus()!=HttpRequest::abort)
|
||||||
|
{
|
||||||
currentRequest->readFromSocket(socket);
|
currentRequest->readFromSocket(socket);
|
||||||
if (currentRequest->getStatus()==HttpRequest::waitForBody) {
|
if (currentRequest->getStatus()==HttpRequest::waitForBody)
|
||||||
|
{
|
||||||
// Restart timer for read timeout, otherwise it would
|
// Restart timer for read timeout, otherwise it would
|
||||||
// expire during large file uploads.
|
// expire during large file uploads.
|
||||||
int readTimeout=settings->value("readTimeout",10000).toInt();
|
int readTimeout=settings->value("readTimeout",10000).toInt();
|
||||||
@ -121,50 +177,99 @@ void HttpConnectionHandler::read() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If the request is aborted, return error message and close the connection
|
// If the request is aborted, return error message and close the connection
|
||||||
if (currentRequest->getStatus()==HttpRequest::abort) {
|
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();
|
socket->write("HTTP/1.1 413 entity too large\r\nConnection: close\r\n\r\n413 Entity too large\r\n");
|
||||||
|
socket->flush();
|
||||||
|
socket->disconnectFromHost();
|
||||||
delete currentRequest;
|
delete currentRequest;
|
||||||
currentRequest=0;
|
currentRequest=0;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the request is complete, let the request mapper dispatch it
|
// If the request is complete, let the request mapper dispatch it
|
||||||
if (currentRequest->getStatus()==HttpRequest::complete) {
|
if (currentRequest->getStatus()==HttpRequest::complete)
|
||||||
|
{
|
||||||
readTimer.stop();
|
readTimer.stop();
|
||||||
qDebug("HttpConnectionHandler (%p): received request",this);
|
qDebug("HttpConnectionHandler (%p): received request",this);
|
||||||
HttpResponse response(&socket);
|
|
||||||
//response.setHeader("Connection","close"); No funciona bien con NSURLConnection
|
// Copy the Connection:close header to the response
|
||||||
try {
|
HttpResponse response(socket);
|
||||||
|
bool closeConnection=QString::compare(currentRequest->getHeader("Connection"),"close",Qt::CaseInsensitive)==0;
|
||||||
|
if (closeConnection)
|
||||||
|
{
|
||||||
|
response.setHeader("Connection","close");
|
||||||
|
}
|
||||||
|
|
||||||
|
// In case of HTTP 1.0 protocol add the Connection:close header.
|
||||||
|
// This ensures that the HttpResponse does not activate chunked mode, which is not spported by HTTP 1.0.
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bool http1_0=QString::compare(currentRequest->getVersion(),"HTTP/1.0",Qt::CaseInsensitive)==0;
|
||||||
|
if (http1_0)
|
||||||
|
{
|
||||||
|
closeConnection=true;
|
||||||
|
response.setHeader("Connection","close");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the request mapper
|
||||||
|
try
|
||||||
|
{
|
||||||
requestHandler->service(*currentRequest, response);
|
requestHandler->service(*currentRequest, response);
|
||||||
}
|
}
|
||||||
catch (...) {
|
catch (...)
|
||||||
|
{
|
||||||
qCritical("HttpConnectionHandler (%p): An uncatched exception occured in the request handler",this);
|
qCritical("HttpConnectionHandler (%p): An uncatched exception occured in the request handler",this);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finalize sending the response if not already done
|
// Finalize sending the response if not already done
|
||||||
if (!response.hasSentLastPart()) {
|
if (!response.hasSentLastPart())
|
||||||
|
{
|
||||||
response.write(QByteArray(),true);
|
response.write(QByteArray(),true);
|
||||||
}
|
}
|
||||||
|
|
||||||
//socket.disconnectFromHost(); //CAMBIADO sólo se van a soportar conexiones NO persistentes
|
qDebug("HttpConnectionHandler (%p): finished request",this);
|
||||||
|
|
||||||
// Close the connection after delivering the response, if requested
|
// Find out whether the connection must be closed
|
||||||
if (QString::compare(currentRequest->getHeader("Connection"),"close",Qt::CaseInsensitive)==0) {
|
if (!closeConnection)
|
||||||
socket.disconnectFromHost();
|
{
|
||||||
|
// Maybe the request handler or mapper added a Connection:close header in the meantime
|
||||||
|
bool closeResponse=QString::compare(response.getHeaders().value("Connection"),"close",Qt::CaseInsensitive)==0;
|
||||||
|
if (closeResponse==true)
|
||||||
|
{
|
||||||
|
closeConnection=true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// If we have no Content-Length header and did not use chunked mode, then we have to close the
|
||||||
|
// connection to tell the HTTP client that the end of the response has been reached.
|
||||||
|
bool hasContentLength=response.getHeaders().contains("Content-Length");
|
||||||
|
if (!hasContentLength)
|
||||||
|
{
|
||||||
|
bool hasChunkedMode=QString::compare(response.getHeaders().value("Transfer-Encoding"),"chunked",Qt::CaseInsensitive)==0;
|
||||||
|
if (!hasChunkedMode)
|
||||||
|
{
|
||||||
|
closeConnection=true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
|
// Close the connection or prepare for the next request on the same connection.
|
||||||
|
if (closeConnection)
|
||||||
|
{
|
||||||
|
socket->flush();
|
||||||
|
socket->disconnectFromHost();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
// Start timer for next request
|
// Start timer for next request
|
||||||
int readTimeout=settings->value("readTimeout",10000).toInt();
|
int readTimeout=settings->value("readTimeout",10000).toInt();
|
||||||
readTimer.start(readTimeout);
|
readTimer.start(readTimeout);
|
||||||
}
|
}
|
||||||
// Prepare for next request
|
|
||||||
delete currentRequest;
|
delete currentRequest;
|
||||||
currentRequest=0;
|
currentRequest=0;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
qDebug("HttpConnectionHandler (%p): received request",this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,13 +6,29 @@
|
|||||||
#ifndef HTTPCONNECTIONHANDLER_H
|
#ifndef HTTPCONNECTIONHANDLER_H
|
||||||
#define HTTPCONNECTIONHANDLER_H
|
#define HTTPCONNECTIONHANDLER_H
|
||||||
|
|
||||||
|
#ifndef QT_NO_OPENSSL
|
||||||
|
#include <QSslConfiguration>
|
||||||
|
#endif
|
||||||
#include <QTcpSocket>
|
#include <QTcpSocket>
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
|
#include "httpglobal.h"
|
||||||
#include "httprequest.h"
|
#include "httprequest.h"
|
||||||
#include "httprequesthandler.h"
|
#include "httprequesthandler.h"
|
||||||
|
|
||||||
|
/** Alias type definition, for compatibility to different Qt versions */
|
||||||
|
#if QT_VERSION >= 0x050000
|
||||||
|
typedef qintptr tSocketDescriptor;
|
||||||
|
#else
|
||||||
|
typedef int tSocketDescriptor;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/** Alias for QSslConfiguration if OpenSSL is not supported */
|
||||||
|
#ifdef QT_NO_OPENSSL
|
||||||
|
#define QSslConfiguration QObject
|
||||||
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
The connection handler accepts incoming connections and dispatches incoming requests to to a
|
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,
|
request mapper. Since HTTP clients can send multiple requests before waiting for the response,
|
||||||
@ -26,26 +42,21 @@
|
|||||||
</pre></code>
|
</pre></code>
|
||||||
<p>
|
<p>
|
||||||
The readTimeout value defines the maximum time to wait for a complete HTTP request.
|
The readTimeout value defines the maximum time to wait for a complete HTTP request.
|
||||||
@see HttpRequest for description of config settings maxRequestSize and maxMultiPartSize
|
@see HttpRequest for description of config settings maxRequestSize and maxMultiPartSize.
|
||||||
*/
|
*/
|
||||||
|
class DECLSPEC HttpConnectionHandler : public QThread {
|
||||||
#if QT_VERSION >= 0x050000
|
|
||||||
typedef qintptr tSocketDescriptor;
|
|
||||||
#else
|
|
||||||
typedef int tSocketDescriptor;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
class HttpConnectionHandler : public QThread {
|
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_DISABLE_COPY(HttpConnectionHandler)
|
Q_DISABLE_COPY(HttpConnectionHandler)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Constructor.
|
Constructor.
|
||||||
@param settings Configuration settings of the HTTP webserver
|
@param settings Configuration settings of the HTTP webserver
|
||||||
@param requestHandler handler that will process each incomin HTTP request
|
@param requestHandler Handler that will process each incoming HTTP request
|
||||||
|
@param sslConfiguration SSL (HTTPS) will be used if not NULL
|
||||||
*/
|
*/
|
||||||
HttpConnectionHandler(QSettings* settings, HttpRequestHandler* requestHandler);
|
HttpConnectionHandler(QSettings* settings, HttpRequestHandler* requestHandler, QSslConfiguration* sslConfiguration=NULL);
|
||||||
|
|
||||||
/** Destructor */
|
/** Destructor */
|
||||||
virtual ~HttpConnectionHandler();
|
virtual ~HttpConnectionHandler();
|
||||||
@ -61,8 +72,8 @@ private:
|
|||||||
/** Configuration settings */
|
/** Configuration settings */
|
||||||
QSettings* settings;
|
QSettings* settings;
|
||||||
|
|
||||||
/** TCP socket of the current connection */
|
/** TCP socket of the current connection */
|
||||||
QTcpSocket socket;
|
QTcpSocket* socket;
|
||||||
|
|
||||||
/** Time for read timeout detection */
|
/** Time for read timeout detection */
|
||||||
QTimer readTimer;
|
QTimer readTimer;
|
||||||
@ -76,9 +87,15 @@ private:
|
|||||||
/** This shows the busy-state from a very early time */
|
/** This shows the busy-state from a very early time */
|
||||||
bool busy;
|
bool busy;
|
||||||
|
|
||||||
/** Executes the htreads own event loop */
|
/** Configuration for SSL */
|
||||||
|
QSslConfiguration* sslConfiguration;
|
||||||
|
|
||||||
|
/** Executes the threads own event loop */
|
||||||
void run();
|
void run();
|
||||||
|
|
||||||
|
/** Create SSL or TCP socket */
|
||||||
|
void createSocket();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,3 +1,10 @@
|
|||||||
|
#ifndef QT_NO_OPENSSL
|
||||||
|
#include <QSslSocket>
|
||||||
|
#include <QSslKey>
|
||||||
|
#include <QSslCertificate>
|
||||||
|
#include <QSslConfiguration>
|
||||||
|
#endif
|
||||||
|
#include <QDir>
|
||||||
#include "httpconnectionhandlerpool.h"
|
#include "httpconnectionhandlerpool.h"
|
||||||
|
|
||||||
HttpConnectionHandlerPool::HttpConnectionHandlerPool(QSettings* settings, HttpRequestHandler* requestHandler)
|
HttpConnectionHandlerPool::HttpConnectionHandlerPool(QSettings* settings, HttpRequestHandler* requestHandler)
|
||||||
@ -6,35 +13,46 @@ HttpConnectionHandlerPool::HttpConnectionHandlerPool(QSettings* settings, HttpRe
|
|||||||
Q_ASSERT(settings!=0);
|
Q_ASSERT(settings!=0);
|
||||||
this->settings=settings;
|
this->settings=settings;
|
||||||
this->requestHandler=requestHandler;
|
this->requestHandler=requestHandler;
|
||||||
cleanupTimer.start(settings->value("cleanupInterval",10000).toInt());
|
this->sslConfiguration=NULL;
|
||||||
|
loadSslConfig();
|
||||||
|
cleanupTimer.start(settings->value("cleanupInterval",1000).toInt());
|
||||||
connect(&cleanupTimer, SIGNAL(timeout()), SLOT(cleanup()));
|
connect(&cleanupTimer, SIGNAL(timeout()), SLOT(cleanup()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
HttpConnectionHandlerPool::~HttpConnectionHandlerPool() {
|
HttpConnectionHandlerPool::~HttpConnectionHandlerPool()
|
||||||
foreach(HttpConnectionHandler* handler, pool) {
|
{
|
||||||
connect(handler,SIGNAL(finished()),handler,SLOT(deleteLater()));
|
// delete all connection handlers and wait until their threads are closed
|
||||||
handler->quit();
|
foreach(HttpConnectionHandler* handler, pool)
|
||||||
|
{
|
||||||
|
delete handler;
|
||||||
}
|
}
|
||||||
|
delete sslConfiguration;
|
||||||
|
qDebug("HttpConnectionHandlerPool (%p): destroyed", this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
HttpConnectionHandler* HttpConnectionHandlerPool::getConnectionHandler() {
|
HttpConnectionHandler* HttpConnectionHandlerPool::getConnectionHandler()
|
||||||
|
{
|
||||||
HttpConnectionHandler* freeHandler=0;
|
HttpConnectionHandler* freeHandler=0;
|
||||||
mutex.lock();
|
mutex.lock();
|
||||||
// find a free handler in pool
|
// find a free handler in pool
|
||||||
foreach(HttpConnectionHandler* handler, pool) {
|
foreach(HttpConnectionHandler* handler, pool)
|
||||||
if (!handler->isBusy()) {
|
{
|
||||||
|
if (!handler->isBusy())
|
||||||
|
{
|
||||||
freeHandler=handler;
|
freeHandler=handler;
|
||||||
freeHandler->setBusy();
|
freeHandler->setBusy();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// create a new handler, if necessary
|
// create a new handler, if necessary
|
||||||
if (!freeHandler) {
|
if (!freeHandler)
|
||||||
int maxConnectionHandlers=settings->value("maxThreads",1000).toInt();
|
{
|
||||||
if (pool.count()<maxConnectionHandlers) {
|
int maxConnectionHandlers=settings->value("maxThreads",100).toInt();
|
||||||
freeHandler=new HttpConnectionHandler(settings,requestHandler);
|
if (pool.count()<maxConnectionHandlers)
|
||||||
|
{
|
||||||
|
freeHandler=new HttpConnectionHandler(settings,requestHandler,sslConfiguration);
|
||||||
freeHandler->setBusy();
|
freeHandler->setBusy();
|
||||||
pool.append(freeHandler);
|
pool.append(freeHandler);
|
||||||
}
|
}
|
||||||
@ -44,21 +62,85 @@ HttpConnectionHandler* HttpConnectionHandlerPool::getConnectionHandler() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void HttpConnectionHandlerPool::cleanup()
|
||||||
void HttpConnectionHandlerPool::cleanup() {
|
{
|
||||||
int maxIdleHandlers=settings->value("minThreads",50).toInt();
|
int maxIdleHandlers=settings->value("minThreads",1).toInt();
|
||||||
int idleCounter=0;
|
int idleCounter=0;
|
||||||
mutex.lock();
|
mutex.lock();
|
||||||
foreach(HttpConnectionHandler* handler, pool) {
|
foreach(HttpConnectionHandler* handler, pool)
|
||||||
if (!handler->isBusy()) {
|
{
|
||||||
if (++idleCounter > maxIdleHandlers) {
|
if (!handler->isBusy())
|
||||||
|
{
|
||||||
|
if (++idleCounter > maxIdleHandlers)
|
||||||
|
{
|
||||||
|
delete handler;
|
||||||
pool.removeOne(handler);
|
pool.removeOne(handler);
|
||||||
qDebug("HttpConnectionHandlerPool: Removed connection handler (%p), pool size is now %i",handler,pool.size());
|
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
|
break; // remove only one handler in each interval
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mutex.unlock();
|
mutex.unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void HttpConnectionHandlerPool::loadSslConfig()
|
||||||
|
{
|
||||||
|
// If certificate and key files are configured, then load them
|
||||||
|
QString sslKeyFileName=settings->value("sslKeyFile","").toString();
|
||||||
|
QString sslCertFileName=settings->value("sslCertFile","").toString();
|
||||||
|
if (!sslKeyFileName.isEmpty() && !sslCertFileName.isEmpty())
|
||||||
|
{
|
||||||
|
#ifdef QT_NO_OPENSSL
|
||||||
|
qWarning("HttpConnectionHandlerPool: SSL is not supported");
|
||||||
|
#else
|
||||||
|
// Convert relative fileNames to absolute, based on the directory of the config file.
|
||||||
|
QFileInfo configFile(settings->fileName());
|
||||||
|
#ifdef Q_OS_WIN32
|
||||||
|
if (QDir::isRelativePath(sslKeyFileName) && settings->format()!=QSettings::NativeFormat)
|
||||||
|
#else
|
||||||
|
if (QDir::isRelativePath(sslKeyFileName))
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
sslKeyFileName=QFileInfo(configFile.absolutePath(),sslKeyFileName).absoluteFilePath();
|
||||||
|
}
|
||||||
|
#ifdef Q_OS_WIN32
|
||||||
|
if (QDir::isRelativePath(sslCertFileName) && settings->format()!=QSettings::NativeFormat)
|
||||||
|
#else
|
||||||
|
if (QDir::isRelativePath(sslCertFileName))
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
sslCertFileName=QFileInfo(configFile.absolutePath(),sslCertFileName).absoluteFilePath();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the SSL certificate
|
||||||
|
QFile certFile(sslCertFileName);
|
||||||
|
if (!certFile.open(QIODevice::ReadOnly))
|
||||||
|
{
|
||||||
|
qCritical("HttpConnectionHandlerPool: cannot open sslCertFile %s", qPrintable(sslCertFileName));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QSslCertificate certificate(&certFile, QSsl::Pem);
|
||||||
|
certFile.close();
|
||||||
|
|
||||||
|
// Load the key file
|
||||||
|
QFile keyFile(sslKeyFileName);
|
||||||
|
if (!keyFile.open(QIODevice::ReadOnly))
|
||||||
|
{
|
||||||
|
qCritical("HttpConnectionHandlerPool: cannot open sslKeyFile %s", qPrintable(sslKeyFileName));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QSslKey sslKey(&keyFile, QSsl::Rsa, QSsl::Pem);
|
||||||
|
keyFile.close();
|
||||||
|
|
||||||
|
// Create the SSL configuration
|
||||||
|
sslConfiguration=new QSslConfiguration();
|
||||||
|
sslConfiguration->setLocalCertificate(certificate);
|
||||||
|
sslConfiguration->setPrivateKey(sslKey);
|
||||||
|
sslConfiguration->setPeerVerifyMode(QSslSocket::VerifyNone);
|
||||||
|
sslConfiguration->setProtocol(QSsl::TlsV1SslV3);
|
||||||
|
|
||||||
|
qDebug("HttpConnectionHandlerPool: SSL settings loaded");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -5,29 +5,45 @@
|
|||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QMutex>
|
#include <QMutex>
|
||||||
|
#include "httpglobal.h"
|
||||||
#include "httpconnectionhandler.h"
|
#include "httpconnectionhandler.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Pool of http connection handlers. Connection handlers are created on demand and idle handlers are
|
Pool of http connection handlers. The size of the pool grows and
|
||||||
cleaned up in regular time intervals.
|
shrinks on demand.
|
||||||
<p>
|
<p>
|
||||||
Example for the required configuration settings:
|
Example for the required configuration settings:
|
||||||
<code><pre>
|
<code><pre>
|
||||||
minThreads=1
|
minThreads=4
|
||||||
maxThreads=100
|
maxThreads=100
|
||||||
cleanupInterval=1000
|
cleanupInterval=60000
|
||||||
|
readTimeout=60000
|
||||||
|
;sslKeyFile=ssl/my.key
|
||||||
|
;sslCertFile=ssl/my.cert
|
||||||
maxRequestSize=16000
|
maxRequestSize=16000
|
||||||
maxMultiPartSize=1000000
|
maxMultiPartSize=1000000
|
||||||
</pre></code>
|
</pre></code>
|
||||||
The pool is empty initially and grows with the number of concurrent
|
After server start, the size of the thread pool is always 0. Threads
|
||||||
connections. A timer removes one idle connection handler at each
|
are started on demand when requests come in. The cleanup timer reduces
|
||||||
interval, but it leaves some spare handlers in memory to improve
|
the number of idle threads slowly by closing one thread in each interval.
|
||||||
performance.
|
But the configured minimum number of threads are kept running.
|
||||||
@see HttpConnectionHandler for description of config settings readTimeout
|
<p>
|
||||||
|
For SSL support, you need an OpenSSL certificate file and a key file.
|
||||||
|
Both can be created with the command
|
||||||
|
<code><pre>
|
||||||
|
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout my.key -out my.cert
|
||||||
|
</pre></code>
|
||||||
|
<p>
|
||||||
|
Visit http://slproweb.com/products/Win32OpenSSL.html to download the Light version of OpenSSL for Windows.
|
||||||
|
<p>
|
||||||
|
Please note that a listener with SSL settings can only handle HTTPS protocol. To
|
||||||
|
support both HTTP and HTTPS simultaneously, you need to start two listeners on different ports -
|
||||||
|
one with SLL and one without SSL.
|
||||||
|
@see HttpConnectionHandler for description of the readTimeout
|
||||||
@see HttpRequest for description of config settings maxRequestSize and maxMultiPartSize
|
@see HttpRequest for description of config settings maxRequestSize and maxMultiPartSize
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class HttpConnectionHandlerPool : public QObject {
|
class DECLSPEC HttpConnectionHandlerPool : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_DISABLE_COPY(HttpConnectionHandlerPool)
|
Q_DISABLE_COPY(HttpConnectionHandlerPool)
|
||||||
public:
|
public:
|
||||||
@ -63,6 +79,12 @@ private:
|
|||||||
/** Used to synchronize threads */
|
/** Used to synchronize threads */
|
||||||
QMutex mutex;
|
QMutex mutex;
|
||||||
|
|
||||||
|
/** The SSL configuration (certificate, key and other settings) */
|
||||||
|
QSslConfiguration* sslConfiguration;
|
||||||
|
|
||||||
|
/** Load SSL configuration */
|
||||||
|
void loadSslConfig();
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
|
|
||||||
/** Received from the clean-up timer. */
|
/** Received from the clean-up timer. */
|
||||||
|
@ -5,13 +5,15 @@
|
|||||||
|
|
||||||
#include "httpcookie.h"
|
#include "httpcookie.h"
|
||||||
|
|
||||||
HttpCookie::HttpCookie() {
|
HttpCookie::HttpCookie()
|
||||||
|
{
|
||||||
version=1;
|
version=1;
|
||||||
maxAge=0;
|
maxAge=0;
|
||||||
secure=false;
|
secure=false;
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpCookie::HttpCookie(const QByteArray name, const QByteArray value, const int maxAge, const QByteArray path, const QByteArray comment, const QByteArray domain, const bool secure) {
|
HttpCookie::HttpCookie(const QByteArray name, const QByteArray value, const int maxAge, const QByteArray path, const QByteArray comment, const QByteArray domain, const bool secure, const bool httpOnly)
|
||||||
|
{
|
||||||
this->name=name;
|
this->name=name;
|
||||||
this->value=value;
|
this->value=value;
|
||||||
this->maxAge=maxAge;
|
this->maxAge=maxAge;
|
||||||
@ -19,171 +21,230 @@ HttpCookie::HttpCookie(const QByteArray name, const QByteArray value, const int
|
|||||||
this->comment=comment;
|
this->comment=comment;
|
||||||
this->domain=domain;
|
this->domain=domain;
|
||||||
this->secure=secure;
|
this->secure=secure;
|
||||||
|
this->httpOnly=httpOnly;
|
||||||
this->version=1;
|
this->version=1;
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpCookie::HttpCookie(const QByteArray source) {
|
HttpCookie::HttpCookie(const QByteArray source)
|
||||||
|
{
|
||||||
version=1;
|
version=1;
|
||||||
maxAge=0;
|
maxAge=0;
|
||||||
secure=false;
|
secure=false;
|
||||||
QList<QByteArray> list=splitCSV(source);
|
QList<QByteArray> list=splitCSV(source);
|
||||||
foreach(QByteArray part, list) {
|
foreach(QByteArray part, list)
|
||||||
|
{
|
||||||
|
|
||||||
// Split the part into name and value
|
// Split the part into name and value
|
||||||
QByteArray name;
|
QByteArray name;
|
||||||
QByteArray value;
|
QByteArray value;
|
||||||
int posi=part.indexOf('=');
|
int posi=part.indexOf('=');
|
||||||
if (posi) {
|
if (posi)
|
||||||
|
{
|
||||||
name=part.left(posi).trimmed();
|
name=part.left(posi).trimmed();
|
||||||
value=part.mid(posi+1).trimmed();
|
value=part.mid(posi+1).trimmed();
|
||||||
}
|
}
|
||||||
else {
|
else
|
||||||
|
{
|
||||||
name=part.trimmed();
|
name=part.trimmed();
|
||||||
value="";
|
value="";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set fields
|
// Set fields
|
||||||
if (name=="Comment") {
|
if (name=="Comment")
|
||||||
|
{
|
||||||
comment=value;
|
comment=value;
|
||||||
}
|
}
|
||||||
else if (name=="Domain") {
|
else if (name=="Domain")
|
||||||
|
{
|
||||||
domain=value;
|
domain=value;
|
||||||
}
|
}
|
||||||
else if (name=="Max-Age") {
|
else if (name=="Max-Age")
|
||||||
|
{
|
||||||
maxAge=value.toInt();
|
maxAge=value.toInt();
|
||||||
}
|
}
|
||||||
else if (name=="Path") {
|
else if (name=="Path")
|
||||||
|
{
|
||||||
path=value;
|
path=value;
|
||||||
}
|
}
|
||||||
else if (name=="Secure") {
|
else if (name=="Secure")
|
||||||
|
{
|
||||||
secure=true;
|
secure=true;
|
||||||
}
|
}
|
||||||
else if (name=="Version") {
|
else if (name=="HttpOnly")
|
||||||
|
{
|
||||||
|
httpOnly=true;
|
||||||
|
}
|
||||||
|
else if (name=="Version")
|
||||||
|
{
|
||||||
version=value.toInt();
|
version=value.toInt();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (this->name.isEmpty()) {
|
if (this->name.isEmpty())
|
||||||
|
{
|
||||||
this->name=name;
|
this->name=name;
|
||||||
this->value=value;
|
this->value=value;
|
||||||
}
|
}
|
||||||
else {
|
else
|
||||||
|
{
|
||||||
qWarning("HttpCookie: Ignoring unknown %s=%s",name.data(),value.data());
|
qWarning("HttpCookie: Ignoring unknown %s=%s",name.data(),value.data());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray HttpCookie::toByteArray() const {
|
QByteArray HttpCookie::toByteArray() const
|
||||||
|
{
|
||||||
QByteArray buffer(name);
|
QByteArray buffer(name);
|
||||||
buffer.append('=');
|
buffer.append('=');
|
||||||
buffer.append(value);
|
buffer.append(value);
|
||||||
if (!comment.isEmpty()) {
|
if (!comment.isEmpty())
|
||||||
|
{
|
||||||
buffer.append("; Comment=");
|
buffer.append("; Comment=");
|
||||||
buffer.append(comment);
|
buffer.append(comment);
|
||||||
}
|
}
|
||||||
if (!domain.isEmpty()) {
|
if (!domain.isEmpty())
|
||||||
|
{
|
||||||
buffer.append("; Domain=");
|
buffer.append("; Domain=");
|
||||||
buffer.append(domain);
|
buffer.append(domain);
|
||||||
}
|
}
|
||||||
if (maxAge!=0) {
|
if (maxAge!=0)
|
||||||
|
{
|
||||||
buffer.append("; Max-Age=");
|
buffer.append("; Max-Age=");
|
||||||
buffer.append(QByteArray::number(maxAge));
|
buffer.append(QByteArray::number(maxAge));
|
||||||
}
|
}
|
||||||
if (!path.isEmpty()) {
|
if (!path.isEmpty())
|
||||||
|
{
|
||||||
buffer.append("; Path=");
|
buffer.append("; Path=");
|
||||||
buffer.append(path);
|
buffer.append(path);
|
||||||
}
|
}
|
||||||
if (secure) {
|
if (secure) {
|
||||||
buffer.append("; Secure");
|
buffer.append("; Secure");
|
||||||
}
|
}
|
||||||
|
if (httpOnly) {
|
||||||
|
buffer.append("; HttpOnly");
|
||||||
|
}
|
||||||
buffer.append("; Version=");
|
buffer.append("; Version=");
|
||||||
buffer.append(QByteArray::number(version));
|
buffer.append(QByteArray::number(version));
|
||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpCookie::setName(const QByteArray name){
|
void HttpCookie::setName(const QByteArray name)
|
||||||
|
{
|
||||||
this->name=name;
|
this->name=name;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpCookie::setValue(const QByteArray value){
|
void HttpCookie::setValue(const QByteArray value)
|
||||||
|
{
|
||||||
this->value=value;
|
this->value=value;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpCookie::setComment(const QByteArray comment){
|
void HttpCookie::setComment(const QByteArray comment)
|
||||||
|
{
|
||||||
this->comment=comment;
|
this->comment=comment;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpCookie::setDomain(const QByteArray domain){
|
void HttpCookie::setDomain(const QByteArray domain)
|
||||||
|
{
|
||||||
this->domain=domain;
|
this->domain=domain;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpCookie::setMaxAge(const int maxAge){
|
void HttpCookie::setMaxAge(const int maxAge)
|
||||||
|
{
|
||||||
this->maxAge=maxAge;
|
this->maxAge=maxAge;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpCookie::setPath(const QByteArray path){
|
void HttpCookie::setPath(const QByteArray path)
|
||||||
|
{
|
||||||
this->path=path;
|
this->path=path;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpCookie::setSecure(const bool secure){
|
void HttpCookie::setSecure(const bool secure)
|
||||||
|
{
|
||||||
this->secure=secure;
|
this->secure=secure;
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray HttpCookie::getName() const {
|
void HttpCookie::setHttpOnly(const bool httpOnly)
|
||||||
|
{
|
||||||
|
this->httpOnly=httpOnly;
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray HttpCookie::getName() const
|
||||||
|
{
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray HttpCookie::getValue() const {
|
QByteArray HttpCookie::getValue() const
|
||||||
|
{
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray HttpCookie::getComment() const {
|
QByteArray HttpCookie::getComment() const
|
||||||
|
{
|
||||||
return comment;
|
return comment;
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray HttpCookie::getDomain() const {
|
QByteArray HttpCookie::getDomain() const
|
||||||
|
{
|
||||||
return domain;
|
return domain;
|
||||||
}
|
}
|
||||||
|
|
||||||
int HttpCookie::getMaxAge() const {
|
int HttpCookie::getMaxAge() const
|
||||||
|
{
|
||||||
return maxAge;
|
return maxAge;
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray HttpCookie::getPath() const {
|
QByteArray HttpCookie::getPath() const
|
||||||
|
{
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HttpCookie::getSecure() const {
|
bool HttpCookie::getSecure() const
|
||||||
|
{
|
||||||
return secure;
|
return secure;
|
||||||
}
|
}
|
||||||
|
|
||||||
int HttpCookie::getVersion() const {
|
bool HttpCookie::getHttpOnly() const
|
||||||
|
{
|
||||||
|
return httpOnly;
|
||||||
|
}
|
||||||
|
|
||||||
|
int HttpCookie::getVersion() const
|
||||||
|
{
|
||||||
return version;
|
return version;
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<QByteArray> HttpCookie::splitCSV(const QByteArray source) {
|
QList<QByteArray> HttpCookie::splitCSV(const QByteArray source)
|
||||||
|
{
|
||||||
bool inString=false;
|
bool inString=false;
|
||||||
QList<QByteArray> list;
|
QList<QByteArray> list;
|
||||||
QByteArray buffer;
|
QByteArray buffer;
|
||||||
for (int i=0; i<source.size(); ++i) {
|
for (int i=0; i<source.size(); ++i)
|
||||||
|
{
|
||||||
char c=source.at(i);
|
char c=source.at(i);
|
||||||
if (inString==false) {
|
if (inString==false)
|
||||||
if (c=='\"') {
|
{
|
||||||
|
if (c=='\"')
|
||||||
|
{
|
||||||
inString=true;
|
inString=true;
|
||||||
}
|
}
|
||||||
else if (c==';') {
|
else if (c==';')
|
||||||
|
{
|
||||||
QByteArray trimmed=buffer.trimmed();
|
QByteArray trimmed=buffer.trimmed();
|
||||||
if (!trimmed.isEmpty()) {
|
if (!trimmed.isEmpty())
|
||||||
|
{
|
||||||
list.append(trimmed);
|
list.append(trimmed);
|
||||||
}
|
}
|
||||||
buffer.clear();
|
buffer.clear();
|
||||||
}
|
}
|
||||||
else {
|
else
|
||||||
|
{
|
||||||
buffer.append(c);
|
buffer.append(c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else
|
||||||
if (c=='\"') {
|
{
|
||||||
|
if (c=='\"')
|
||||||
|
{
|
||||||
inString=false;
|
inString=false;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -192,7 +253,8 @@ QList<QByteArray> HttpCookie::splitCSV(const QByteArray source) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
QByteArray trimmed=buffer.trimmed();
|
QByteArray trimmed=buffer.trimmed();
|
||||||
if (!trimmed.isEmpty()) {
|
if (!trimmed.isEmpty())
|
||||||
|
{
|
||||||
list.append(trimmed);
|
list.append(trimmed);
|
||||||
}
|
}
|
||||||
return list;
|
return list;
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
#include <QList>
|
#include <QList>
|
||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
|
#include "httpglobal.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
HTTP cookie as defined in RFC 2109. This class can also parse
|
HTTP cookie as defined in RFC 2109. This class can also parse
|
||||||
@ -15,7 +16,7 @@
|
|||||||
2109.
|
2109.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class HttpCookie
|
class DECLSPEC HttpCookie
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
||||||
@ -30,9 +31,10 @@ public:
|
|||||||
@param path Path for that the cookie will be sent, default="/" which means the whole domain
|
@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 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 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
|
@param secure If true, the cookie will be sent by the browser to the server only on secure connections
|
||||||
|
@param httpOnly If true, the browser does not allow client-side scripts to access the cookie
|
||||||
*/
|
*/
|
||||||
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);
|
HttpCookie(const QByteArray name, const QByteArray value, const int maxAge, const QByteArray path="/", const QByteArray comment=QByteArray(), const QByteArray domain=QByteArray(), const bool secure=false, const bool httpOnly=false);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Create a cookie from a string.
|
Create a cookie from a string.
|
||||||
@ -40,7 +42,7 @@ public:
|
|||||||
*/
|
*/
|
||||||
HttpCookie(const QByteArray source);
|
HttpCookie(const QByteArray source);
|
||||||
|
|
||||||
/** Convert this cookie to a string that may be used in a Set-Cookie2 header. */
|
/** Convert this cookie to a string that may be used in a Set-Cookie header. */
|
||||||
QByteArray toByteArray() const ;
|
QByteArray toByteArray() const ;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -67,9 +69,12 @@ public:
|
|||||||
/** Set the path for that the cookie will be sent, default="/" which means the whole domain */
|
/** Set the path for that the cookie will be sent, default="/" which means the whole domain */
|
||||||
void setPath(const QByteArray path);
|
void setPath(const QByteArray path);
|
||||||
|
|
||||||
/** Set secure mode, so that the cokkie will only be sent on secure connections */
|
/** Set secure mode, so that the cookie will be sent by the browser to the server only on secure connections */
|
||||||
void setSecure(const bool secure);
|
void setSecure(const bool secure);
|
||||||
|
|
||||||
|
/** Set secure mode, so that he browser does not allow client-side scripts to access the cookie */
|
||||||
|
void setHttpOnly(const bool httpOnly);
|
||||||
|
|
||||||
/** Get the name of this cookie */
|
/** Get the name of this cookie */
|
||||||
QByteArray getName() const;
|
QByteArray getName() const;
|
||||||
|
|
||||||
@ -91,6 +96,9 @@ public:
|
|||||||
/** Get the secure flag of this cookie */
|
/** Get the secure flag of this cookie */
|
||||||
bool getSecure() const;
|
bool getSecure() const;
|
||||||
|
|
||||||
|
/** Get the httpOnly of this cookie */
|
||||||
|
bool getHttpOnly() const;
|
||||||
|
|
||||||
/** Returns always 1 */
|
/** Returns always 1 */
|
||||||
int getVersion() const;
|
int getVersion() const;
|
||||||
|
|
||||||
@ -103,6 +111,7 @@ private:
|
|||||||
int maxAge;
|
int maxAge;
|
||||||
QByteArray path;
|
QByteArray path;
|
||||||
bool secure;
|
bool secure;
|
||||||
|
bool httpOnly;
|
||||||
int version;
|
int version;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -2,6 +2,6 @@
|
|||||||
|
|
||||||
const char* getQtWebAppLibVersion()
|
const char* getQtWebAppLibVersion()
|
||||||
{
|
{
|
||||||
return "1.5.3";
|
return "1.6.5";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,53 +12,76 @@ HttpListener::HttpListener(QSettings* settings, HttpRequestHandler* requestHandl
|
|||||||
: QTcpServer(parent)
|
: QTcpServer(parent)
|
||||||
{
|
{
|
||||||
Q_ASSERT(settings!=0);
|
Q_ASSERT(settings!=0);
|
||||||
|
Q_ASSERT(requestHandler!=0);
|
||||||
|
pool=NULL;
|
||||||
|
this->settings=settings;
|
||||||
|
this->requestHandler=requestHandler;
|
||||||
// Reqister type of socketDescriptor for signal/slot handling
|
// Reqister type of socketDescriptor for signal/slot handling
|
||||||
qRegisterMetaType<tSocketDescriptor>("tSocketDescriptor");
|
qRegisterMetaType<tSocketDescriptor>("tSocketDescriptor");
|
||||||
// Create connection handler pool
|
|
||||||
this->settings=settings;
|
|
||||||
pool=new HttpConnectionHandlerPool(settings,requestHandler);
|
|
||||||
// Start listening
|
// Start listening
|
||||||
int port=settings->value("port",8080).toInt();
|
listen();
|
||||||
listen(QHostAddress::Any, port);
|
}
|
||||||
//Cambiado
|
|
||||||
int i = 0;
|
|
||||||
while (!isListening() && i < 1000) {
|
HttpListener::~HttpListener()
|
||||||
listen(QHostAddress::Any, (rand() % 45535)+20000);
|
{
|
||||||
i++;
|
close();
|
||||||
|
qDebug("HttpListener: destroyed");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void HttpListener::listen()
|
||||||
|
{
|
||||||
|
if (!pool)
|
||||||
|
{
|
||||||
|
pool=new HttpConnectionHandlerPool(settings,requestHandler);
|
||||||
|
}
|
||||||
|
QString host = settings->value("host").toString();
|
||||||
|
int port=settings->value("port").toInt();
|
||||||
|
QTcpServer::listen(host.isEmpty() ? QHostAddress::Any : QHostAddress(host), port);
|
||||||
|
if (!isListening())
|
||||||
|
{
|
||||||
|
qCritical("HttpListener: Cannot bind on port %i: %s",port,qPrintable(errorString()));
|
||||||
}
|
}
|
||||||
if(!isListening())
|
|
||||||
{
|
|
||||||
qCritical("HttpListener: Cannot bind on port %i: %s",port,qPrintable(errorString()));
|
|
||||||
}
|
|
||||||
else {
|
else {
|
||||||
qDebug("HttpListener: Listening on port %i",port);
|
qDebug("HttpListener: Listening on port %i",port);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpListener::~HttpListener() {
|
|
||||||
close();
|
void HttpListener::close() {
|
||||||
|
QTcpServer::close();
|
||||||
qDebug("HttpListener: closed");
|
qDebug("HttpListener: closed");
|
||||||
delete pool;
|
if (pool) {
|
||||||
qDebug("HttpListener: destroyed");
|
delete pool;
|
||||||
|
pool=NULL;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpListener::incomingConnection(tSocketDescriptor socketDescriptor) {
|
void HttpListener::incomingConnection(tSocketDescriptor socketDescriptor) {
|
||||||
#ifdef SUPERVERBOSE
|
#ifdef SUPERVERBOSE
|
||||||
qDebug("HttpListener: New connection");
|
qDebug("HttpListener: New connection");
|
||||||
#endif
|
#endif
|
||||||
HttpConnectionHandler* freeHandler=pool->getConnectionHandler();
|
|
||||||
|
HttpConnectionHandler* freeHandler=NULL;
|
||||||
|
if (pool)
|
||||||
|
{
|
||||||
|
freeHandler=pool->getConnectionHandler();
|
||||||
|
}
|
||||||
|
|
||||||
// Let the handler process the new connection.
|
// Let the handler process the new connection.
|
||||||
if (freeHandler) {
|
if (freeHandler)
|
||||||
|
{
|
||||||
// The descriptor is passed via signal/slot because the handler lives in another
|
// The descriptor is passed via signal/slot because the handler lives in another
|
||||||
// thread and cannot open the socket when called by another thread.
|
// thread and cannot open the socket when directly called by another thread.
|
||||||
connect(this,SIGNAL(handleConnection(tSocketDescriptor)),freeHandler,SLOT(handleConnection(tSocketDescriptor)));
|
connect(this,SIGNAL(handleConnection(tSocketDescriptor)),freeHandler,SLOT(handleConnection(tSocketDescriptor)));
|
||||||
emit handleConnection(socketDescriptor);
|
emit handleConnection(socketDescriptor);
|
||||||
disconnect(this,SIGNAL(handleConnection(tSocketDescriptor)),freeHandler,SLOT(handleConnection(tSocketDescriptor)));
|
disconnect(this,SIGNAL(handleConnection(tSocketDescriptor)),freeHandler,SLOT(handleConnection(tSocketDescriptor)));
|
||||||
}
|
}
|
||||||
else {
|
else
|
||||||
|
{
|
||||||
// Reject the connection
|
// Reject the connection
|
||||||
qDebug("HttpListener: Too many connections");
|
qDebug("HttpListener: Too many incoming connections");
|
||||||
QTcpSocket* socket=new QTcpSocket(this);
|
QTcpSocket* socket=new QTcpSocket(this);
|
||||||
socket->setSocketDescriptor(socketDescriptor);
|
socket->setSocketDescriptor(socketDescriptor);
|
||||||
connect(socket, SIGNAL(disconnected()), socket, SLOT(deleteLater()));
|
connect(socket, SIGNAL(disconnected()), socket, SLOT(deleteLater()));
|
||||||
|
@ -3,12 +3,13 @@
|
|||||||
@author Stefan Frings
|
@author Stefan Frings
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef LISTENER_H
|
#ifndef HTTPLISTENER_H
|
||||||
#define LISTENER_H
|
#define HTTPLISTENER_H
|
||||||
|
|
||||||
#include <QTcpServer>
|
#include <QTcpServer>
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
#include <QBasicTimer>
|
#include <QBasicTimer>
|
||||||
|
#include "httpglobal.h"
|
||||||
#include "httpconnectionhandler.h"
|
#include "httpconnectionhandler.h"
|
||||||
#include "httpconnectionhandlerpool.h"
|
#include "httpconnectionhandlerpool.h"
|
||||||
#include "httprequesthandler.h"
|
#include "httprequesthandler.h"
|
||||||
@ -19,36 +20,54 @@
|
|||||||
<p>
|
<p>
|
||||||
Example for the required settings in the config file:
|
Example for the required settings in the config file:
|
||||||
<code><pre>
|
<code><pre>
|
||||||
|
;host=192.168.0.100
|
||||||
port=8080
|
port=8080
|
||||||
minThreads=1
|
minThreads=1
|
||||||
maxThreads=10
|
maxThreads=10
|
||||||
cleanupInterval=1000
|
cleanupInterval=1000
|
||||||
readTimeout=60000
|
readTimeout=60000
|
||||||
|
;sslKeyFile=ssl/my.key
|
||||||
|
;sslCertFile=ssl/my.cert
|
||||||
maxRequestSize=16000
|
maxRequestSize=16000
|
||||||
maxMultiPartSize=1000000
|
maxMultiPartSize=1000000
|
||||||
</pre></code>
|
</pre></code>
|
||||||
The port number is the incoming TCP port that this listener listens to.
|
The optional host parameter binds the listener to one network interface.
|
||||||
@see HttpConnectionHandlerPool for description of config settings minThreads, maxThreads and cleanupInterval
|
The listener handles all network interfaces if no host is configured.
|
||||||
@see HttpConnectionHandler for description of config settings readTimeout
|
The port number specifies the incoming TCP port that this listener listens to.
|
||||||
|
@see HttpConnectionHandlerPool for description of config settings minThreads, maxThreads, cleanupInterval and ssl settings
|
||||||
|
@see HttpConnectionHandler for description of the readTimeout
|
||||||
@see HttpRequest for description of config settings maxRequestSize and maxMultiPartSize
|
@see HttpRequest for description of config settings maxRequestSize and maxMultiPartSize
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class HttpListener : public QTcpServer {
|
class DECLSPEC HttpListener : public QTcpServer {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_DISABLE_COPY(HttpListener)
|
Q_DISABLE_COPY(HttpListener)
|
||||||
public:
|
public:
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Constructor.
|
Constructor.
|
||||||
|
Creates a connection pool and starts listening on the configured host and port.
|
||||||
@param settings Configuration settings for the HTTP server. Must not be 0.
|
@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 requestHandler Processes each received HTTP request, usually by dispatching to controller classes.
|
||||||
@param parent Parent object.
|
@param parent Parent object.
|
||||||
|
@warning Ensure to close or delete the listener before deleting the request handler.
|
||||||
*/
|
*/
|
||||||
HttpListener(QSettings* settings, HttpRequestHandler* requestHandler, QObject* parent = 0);
|
HttpListener(QSettings* settings, HttpRequestHandler* requestHandler, QObject* parent = NULL);
|
||||||
|
|
||||||
/** Destructor */
|
/** Destructor */
|
||||||
virtual ~HttpListener();
|
virtual ~HttpListener();
|
||||||
|
|
||||||
|
/**
|
||||||
|
Restart listeing after close().
|
||||||
|
*/
|
||||||
|
void listen();
|
||||||
|
|
||||||
|
/**
|
||||||
|
Closes the listener, waits until all pending requests are processed,
|
||||||
|
then closes the connection pool.
|
||||||
|
*/
|
||||||
|
void close();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
/** Serves new incoming connection requests */
|
/** Serves new incoming connection requests */
|
||||||
@ -59,13 +78,16 @@ private:
|
|||||||
/** Configuration settings for the HTTP server */
|
/** Configuration settings for the HTTP server */
|
||||||
QSettings* settings;
|
QSettings* settings;
|
||||||
|
|
||||||
|
/** Point to the reuqest handler which processes all HTTP requests */
|
||||||
|
HttpRequestHandler* requestHandler;
|
||||||
|
|
||||||
/** Pool of connection handlers */
|
/** Pool of connection handlers */
|
||||||
HttpConnectionHandlerPool* pool;
|
HttpConnectionHandlerPool* pool;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Emitted when the connection handler shall process a new incoming onnection.
|
Sent to the connection handler to process a new incoming connection.
|
||||||
@param socketDescriptor references the accepted connection.
|
@param socketDescriptor references the accepted connection.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -73,4 +95,4 @@ signals:
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // LISTENER_H
|
#endif // HTTPLISTENER_H
|
||||||
|
@ -8,144 +8,189 @@
|
|||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include "httpcookie.h"
|
#include "httpcookie.h"
|
||||||
|
|
||||||
HttpRequest::HttpRequest(QSettings* settings) {
|
HttpRequest::HttpRequest(QSettings* settings)
|
||||||
|
{
|
||||||
status=waitForRequest;
|
status=waitForRequest;
|
||||||
currentSize=0;
|
currentSize=0;
|
||||||
expectedBodySize=0;
|
expectedBodySize=0;
|
||||||
maxSize=settings->value("maxRequestSize","32000000").toInt();
|
maxSize=settings->value("maxRequestSize","16000").toInt();
|
||||||
maxMultiPartSize=settings->value("maxMultiPartSize","32000000").toInt();
|
maxMultiPartSize=settings->value("maxMultiPartSize","1000000").toInt();
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpRequest::readRequest(QTcpSocket& socket) {
|
void HttpRequest::readRequest(QTcpSocket* socket)
|
||||||
#ifdef SUPERVERBOSE
|
{
|
||||||
qDebug("HttpRequest: read request");
|
#ifdef SUPERVERBOSE
|
||||||
#endif
|
qDebug("HttpRequest: read request");
|
||||||
|
#endif
|
||||||
int toRead=maxSize-currentSize+1; // allow one byte more to be able to detect overflow
|
int toRead=maxSize-currentSize+1; // allow one byte more to be able to detect overflow
|
||||||
QByteArray newData=socket.readLine(toRead).trimmed();
|
lineBuffer.append(socket->readLine(toRead));
|
||||||
currentSize+=newData.size();
|
currentSize+=lineBuffer.size();
|
||||||
if (!newData.isEmpty()) {
|
if (!lineBuffer.contains('\r') && !lineBuffer.contains('\n'))
|
||||||
|
{
|
||||||
|
#ifdef SUPERVERBOSE
|
||||||
|
qDebug("HttpRequest: collecting more parts until line break");
|
||||||
|
#endif
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QByteArray newData=lineBuffer.trimmed();
|
||||||
|
lineBuffer.clear();
|
||||||
|
if (!newData.isEmpty())
|
||||||
|
{
|
||||||
QList<QByteArray> list=newData.split(' ');
|
QList<QByteArray> list=newData.split(' ');
|
||||||
if (list.count()!=3 || !list.at(2).contains("HTTP")) {
|
if (list.count()!=3 || !list.at(2).contains("HTTP"))
|
||||||
|
{
|
||||||
qWarning("HttpRequest: received broken HTTP request, invalid first line");
|
qWarning("HttpRequest: received broken HTTP request, invalid first line");
|
||||||
status=abort;
|
status=abort;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
method=list.at(0);
|
method=list.at(0).trimmed();
|
||||||
path=list.at(1);
|
path=list.at(1);
|
||||||
version=list.at(2);
|
version=list.at(2);
|
||||||
|
peerAddress = socket->peerAddress();
|
||||||
status=waitForHeader;
|
status=waitForHeader;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpRequest::readHeader(QTcpSocket& socket) {
|
void HttpRequest::readHeader(QTcpSocket* socket)
|
||||||
#ifdef SUPERVERBOSE
|
{
|
||||||
qDebug("HttpRequest: read header");
|
#ifdef SUPERVERBOSE
|
||||||
#endif
|
qDebug("HttpRequest: read header");
|
||||||
|
#endif
|
||||||
int toRead=maxSize-currentSize+1; // allow one byte more to be able to detect overflow
|
int toRead=maxSize-currentSize+1; // allow one byte more to be able to detect overflow
|
||||||
QByteArray newData=socket.readLine(toRead).trimmed();
|
lineBuffer.append(socket->readLine(toRead));
|
||||||
currentSize+=newData.size();
|
currentSize+=lineBuffer.size();
|
||||||
|
if (!lineBuffer.contains('\r') && !lineBuffer.contains('\n'))
|
||||||
|
{
|
||||||
|
#ifdef SUPERVERBOSE
|
||||||
|
qDebug("HttpRequest: collecting more parts until line break");
|
||||||
|
#endif
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QByteArray newData=lineBuffer.trimmed();
|
||||||
|
lineBuffer.clear();
|
||||||
int colon=newData.indexOf(':');
|
int colon=newData.indexOf(':');
|
||||||
if (colon>0) {
|
if (colon>0)
|
||||||
|
{
|
||||||
// Received a line with a colon - a header
|
// Received a line with a colon - a header
|
||||||
currentHeader=newData.left(colon);
|
currentHeader=newData.left(colon).toLower();
|
||||||
QByteArray value=newData.mid(colon+1).trimmed();
|
QByteArray value=newData.mid(colon+1).trimmed();
|
||||||
headers.insert(currentHeader,value);
|
headers.insert(currentHeader,value);
|
||||||
#ifdef SUPERVERBOSE
|
#ifdef SUPERVERBOSE
|
||||||
qDebug("HttpRequest: received header %s: %s",currentHeader.data(),value.data());
|
qDebug("HttpRequest: received header %s: %s",currentHeader.data(),value.data());
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
else if (!newData.isEmpty()) {
|
else if (!newData.isEmpty())
|
||||||
|
{
|
||||||
// received another line - belongs to the previous header
|
// received another line - belongs to the previous header
|
||||||
#ifdef SUPERVERBOSE
|
#ifdef SUPERVERBOSE
|
||||||
qDebug("HttpRequest: read additional line of header");
|
qDebug("HttpRequest: read additional line of header");
|
||||||
#endif
|
#endif
|
||||||
// Received additional line of previous header
|
// Received additional line of previous header
|
||||||
if (headers.contains(currentHeader)) {
|
if (headers.contains(currentHeader)) {
|
||||||
headers.insert(currentHeader,headers.value(currentHeader)+" "+newData);
|
headers.insert(currentHeader,headers.value(currentHeader)+" "+newData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else
|
||||||
|
{
|
||||||
// received an empty line - end of headers reached
|
// received an empty line - end of headers reached
|
||||||
#ifdef SUPERVERBOSE
|
#ifdef SUPERVERBOSE
|
||||||
qDebug("HttpRequest: headers completed");
|
qDebug("HttpRequest: headers completed");
|
||||||
#endif
|
#endif
|
||||||
// Empty line received, that means all headers have been received
|
// Empty line received, that means all headers have been received
|
||||||
// Check for multipart/form-data
|
// Check for multipart/form-data
|
||||||
QByteArray contentType=headers.value("Content-Type");
|
QByteArray contentType=headers.value("content-type");
|
||||||
if (contentType.startsWith("multipart/form-data")) {
|
if (contentType.startsWith("multipart/form-data"))
|
||||||
|
{
|
||||||
int posi=contentType.indexOf("boundary=");
|
int posi=contentType.indexOf("boundary=");
|
||||||
if (posi>=0) {
|
if (posi>=0) {
|
||||||
boundary=contentType.mid(posi+9);
|
boundary=contentType.mid(posi+9);
|
||||||
|
if (boundary.startsWith('"') && boundary.endsWith('"'))
|
||||||
|
{
|
||||||
|
boundary = boundary.mid(1,boundary.length()-2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
QByteArray contentLength=getHeader("Content-Length");
|
QByteArray contentLength=headers.value("content-length");
|
||||||
if (!contentLength.isEmpty()) {
|
if (!contentLength.isEmpty())
|
||||||
|
{
|
||||||
expectedBodySize=contentLength.toInt();
|
expectedBodySize=contentLength.toInt();
|
||||||
}
|
}
|
||||||
if (expectedBodySize==0) {
|
if (expectedBodySize==0)
|
||||||
#ifdef SUPERVERBOSE
|
{
|
||||||
qDebug("HttpRequest: expect no body");
|
#ifdef SUPERVERBOSE
|
||||||
#endif
|
qDebug("HttpRequest: expect no body");
|
||||||
|
#endif
|
||||||
status=complete;
|
status=complete;
|
||||||
}
|
}
|
||||||
else if (boundary.isEmpty() && expectedBodySize+currentSize>maxSize) {
|
else if (boundary.isEmpty() && expectedBodySize+currentSize>maxSize)
|
||||||
|
{
|
||||||
qWarning("HttpRequest: expected body is too large");
|
qWarning("HttpRequest: expected body is too large");
|
||||||
status=abort;
|
status=abort;
|
||||||
}
|
}
|
||||||
else if (!boundary.isEmpty() && expectedBodySize>maxMultiPartSize) {
|
else if (!boundary.isEmpty() && expectedBodySize>maxMultiPartSize)
|
||||||
|
{
|
||||||
qWarning("HttpRequest: expected multipart body is too large");
|
qWarning("HttpRequest: expected multipart body is too large");
|
||||||
status=abort;
|
status=abort;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
#ifdef SUPERVERBOSE
|
#ifdef SUPERVERBOSE
|
||||||
qDebug("HttpRequest: expect %i bytes body",expectedBodySize);
|
qDebug("HttpRequest: expect %i bytes body",expectedBodySize);
|
||||||
#endif
|
#endif
|
||||||
status=waitForBody;
|
status=waitForBody;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpRequest::readBody(QTcpSocket& socket) {
|
void HttpRequest::readBody(QTcpSocket* socket)
|
||||||
|
{
|
||||||
Q_ASSERT(expectedBodySize!=0);
|
Q_ASSERT(expectedBodySize!=0);
|
||||||
if (boundary.isEmpty()) {
|
if (boundary.isEmpty())
|
||||||
|
{
|
||||||
// normal body, no multipart
|
// normal body, no multipart
|
||||||
#ifdef SUPERVERBOSE
|
#ifdef SUPERVERBOSE
|
||||||
qDebug("HttpRequest: receive body");
|
qDebug("HttpRequest: receive body");
|
||||||
#endif
|
#endif
|
||||||
int toRead=expectedBodySize-bodyData.size();
|
int toRead=expectedBodySize-bodyData.size();
|
||||||
QByteArray newData=socket.read(toRead);
|
QByteArray newData=socket->read(toRead);
|
||||||
currentSize+=newData.size();
|
currentSize+=newData.size();
|
||||||
bodyData.append(newData);
|
bodyData.append(newData);
|
||||||
if (bodyData.size()>=expectedBodySize) {
|
if (bodyData.size()>=expectedBodySize)
|
||||||
|
{
|
||||||
status=complete;
|
status=complete;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else
|
||||||
|
{
|
||||||
// multipart body, store into temp file
|
// multipart body, store into temp file
|
||||||
#ifdef SUPERVERBOSE
|
#ifdef SUPERVERBOSE
|
||||||
qDebug("HttpRequest: receiving multipart body");
|
qDebug("HttpRequest: receiving multipart body");
|
||||||
#endif
|
#endif
|
||||||
if (!tempFile.isOpen()) {
|
if (!tempFile.isOpen())
|
||||||
|
{
|
||||||
tempFile.open();
|
tempFile.open();
|
||||||
}
|
}
|
||||||
// Transfer data in 64kb blocks
|
// Transfer data in 64kb blocks
|
||||||
int fileSize=tempFile.size();
|
int fileSize=tempFile.size();
|
||||||
int toRead=expectedBodySize-fileSize;
|
int toRead=expectedBodySize-fileSize;
|
||||||
if (toRead>65536) {
|
if (toRead>65536)
|
||||||
|
{
|
||||||
toRead=65536;
|
toRead=65536;
|
||||||
}
|
}
|
||||||
fileSize+=tempFile.write(socket.read(toRead));
|
fileSize+=tempFile.write(socket->read(toRead));
|
||||||
if (fileSize>=maxMultiPartSize) {
|
if (fileSize>=maxMultiPartSize)
|
||||||
|
{
|
||||||
qWarning("HttpRequest: received too many multipart bytes");
|
qWarning("HttpRequest: received too many multipart bytes");
|
||||||
status=abort;
|
status=abort;
|
||||||
}
|
}
|
||||||
else if (fileSize>=expectedBodySize) {
|
else if (fileSize>=expectedBodySize)
|
||||||
#ifdef SUPERVERBOSE
|
{
|
||||||
|
#ifdef SUPERVERBOSE
|
||||||
qDebug("HttpRequest: received whole multipart body");
|
qDebug("HttpRequest: received whole multipart body");
|
||||||
#endif
|
#endif
|
||||||
tempFile.flush();
|
tempFile.flush();
|
||||||
if (tempFile.error()) {
|
if (tempFile.error())
|
||||||
|
{
|
||||||
qCritical("HttpRequest: Error writing temp file for multipart body");
|
qCritical("HttpRequest: Error writing temp file for multipart body");
|
||||||
}
|
}
|
||||||
parseMultiPartFile();
|
parseMultiPartFile();
|
||||||
@ -155,87 +200,106 @@ void HttpRequest::readBody(QTcpSocket& socket) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpRequest::decodeRequestParams() {
|
void HttpRequest::decodeRequestParams()
|
||||||
#ifdef SUPERVERBOSE
|
{
|
||||||
qDebug("HttpRequest: extract and decode request parameters");
|
#ifdef SUPERVERBOSE
|
||||||
#endif
|
qDebug("HttpRequest: extract and decode request parameters");
|
||||||
|
#endif
|
||||||
// Get URL parameters
|
// Get URL parameters
|
||||||
QByteArray rawParameters;
|
QByteArray rawParameters;
|
||||||
int questionMark=path.indexOf('?');
|
int questionMark=path.indexOf('?');
|
||||||
if (questionMark>=0) {
|
if (questionMark>=0)
|
||||||
|
{
|
||||||
rawParameters=path.mid(questionMark+1);
|
rawParameters=path.mid(questionMark+1);
|
||||||
path=path.left(questionMark);
|
path=path.left(questionMark);
|
||||||
}
|
}
|
||||||
// Get request body parameters
|
// Get request body parameters
|
||||||
QByteArray contentType=headers.value("Content-Type");
|
QByteArray contentType=headers.value("content-type");
|
||||||
if (!bodyData.isEmpty() && (contentType.isEmpty() || contentType.startsWith("application/x-www-form-urlencoded"))) {
|
if (!bodyData.isEmpty() && (contentType.isEmpty() || contentType.startsWith("application/x-www-form-urlencoded")))
|
||||||
if (rawParameters.isEmpty()) {
|
{
|
||||||
|
if (!rawParameters.isEmpty())
|
||||||
|
{
|
||||||
rawParameters.append('&');
|
rawParameters.append('&');
|
||||||
rawParameters.append(bodyData);
|
rawParameters.append(bodyData);
|
||||||
}
|
}
|
||||||
else {
|
else
|
||||||
|
{
|
||||||
rawParameters=bodyData;
|
rawParameters=bodyData;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Split the parameters into pairs of value and name
|
// Split the parameters into pairs of value and name
|
||||||
QList<QByteArray> list=rawParameters.split('&');
|
QList<QByteArray> list=rawParameters.split('&');
|
||||||
foreach (QByteArray part, list) {
|
foreach (QByteArray part, list)
|
||||||
|
{
|
||||||
int equalsChar=part.indexOf('=');
|
int equalsChar=part.indexOf('=');
|
||||||
if (equalsChar>=0) {
|
if (equalsChar>=0)
|
||||||
|
{
|
||||||
QByteArray name=part.left(equalsChar).trimmed();
|
QByteArray name=part.left(equalsChar).trimmed();
|
||||||
QByteArray value=part.mid(equalsChar+1).trimmed();
|
QByteArray value=part.mid(equalsChar+1).trimmed();
|
||||||
parameters.insert(urlDecode(name),urlDecode(value));
|
parameters.insert(urlDecode(name),urlDecode(value));
|
||||||
}
|
}
|
||||||
else if (!part.isEmpty()){
|
else if (!part.isEmpty())
|
||||||
|
{
|
||||||
// Name without value
|
// Name without value
|
||||||
parameters.insert(urlDecode(part),"");
|
parameters.insert(urlDecode(part),"");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpRequest::extractCookies() {
|
void HttpRequest::extractCookies()
|
||||||
#ifdef SUPERVERBOSE
|
{
|
||||||
qDebug("HttpRequest: extract cookies");
|
#ifdef SUPERVERBOSE
|
||||||
#endif
|
qDebug("HttpRequest: extract cookies");
|
||||||
foreach(QByteArray cookieStr, headers.values("Cookie")) {
|
#endif
|
||||||
|
foreach(QByteArray cookieStr, headers.values("cookie"))
|
||||||
|
{
|
||||||
QList<QByteArray> list=HttpCookie::splitCSV(cookieStr);
|
QList<QByteArray> list=HttpCookie::splitCSV(cookieStr);
|
||||||
foreach(QByteArray part, list) {
|
foreach(QByteArray part, list)
|
||||||
#ifdef SUPERVERBOSE
|
{
|
||||||
qDebug("HttpRequest: found cookie %s",part.data());
|
#ifdef SUPERVERBOSE
|
||||||
#endif // Split the part into name and value
|
qDebug("HttpRequest: found cookie %s",part.data());
|
||||||
|
#endif // Split the part into name and value
|
||||||
QByteArray name;
|
QByteArray name;
|
||||||
QByteArray value;
|
QByteArray value;
|
||||||
int posi=part.indexOf('=');
|
int posi=part.indexOf('=');
|
||||||
if (posi) {
|
if (posi)
|
||||||
|
{
|
||||||
name=part.left(posi).trimmed();
|
name=part.left(posi).trimmed();
|
||||||
value=part.mid(posi+1).trimmed();
|
value=part.mid(posi+1).trimmed();
|
||||||
}
|
}
|
||||||
else {
|
else
|
||||||
|
{
|
||||||
name=part.trimmed();
|
name=part.trimmed();
|
||||||
value="";
|
value="";
|
||||||
}
|
}
|
||||||
cookies.insert(name,value);
|
cookies.insert(name,value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
headers.remove("Cookie");
|
headers.remove("cookie");
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpRequest::readFromSocket(QTcpSocket& socket) {
|
void HttpRequest::readFromSocket(QTcpSocket* socket)
|
||||||
|
{
|
||||||
Q_ASSERT(status!=complete);
|
Q_ASSERT(status!=complete);
|
||||||
if (status==waitForRequest) {
|
if (status==waitForRequest)
|
||||||
|
{
|
||||||
readRequest(socket);
|
readRequest(socket);
|
||||||
}
|
}
|
||||||
else if (status==waitForHeader) {
|
else if (status==waitForHeader)
|
||||||
|
{
|
||||||
readHeader(socket);
|
readHeader(socket);
|
||||||
}
|
}
|
||||||
else if (status==waitForBody) {
|
else if (status==waitForBody)
|
||||||
|
{
|
||||||
readBody(socket);
|
readBody(socket);
|
||||||
}
|
}
|
||||||
if (currentSize>maxSize) {
|
if ((boundary.isEmpty() && currentSize>maxSize) || (!boundary.isEmpty() && currentSize>maxMultiPartSize))
|
||||||
|
{
|
||||||
qWarning("HttpRequest: received too many bytes");
|
qWarning("HttpRequest: received too many bytes");
|
||||||
status=abort;
|
status=abort;
|
||||||
}
|
}
|
||||||
if (status==complete) {
|
if (status==complete)
|
||||||
|
{
|
||||||
// Extract and decode request parameters from url and body
|
// Extract and decode request parameters from url and body
|
||||||
decodeRequestParams();
|
decodeRequestParams();
|
||||||
// Extract cookies from headers
|
// Extract cookies from headers
|
||||||
@ -244,62 +308,82 @@ void HttpRequest::readFromSocket(QTcpSocket& socket) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
HttpRequest::RequestStatus HttpRequest::getStatus() const {
|
HttpRequest::RequestStatus HttpRequest::getStatus() const
|
||||||
|
{
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
QByteArray HttpRequest::getMethod() const {
|
QByteArray HttpRequest::getMethod() const
|
||||||
|
{
|
||||||
return method;
|
return method;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
QByteArray HttpRequest::getPath() const {
|
QByteArray HttpRequest::getPath() const
|
||||||
|
{
|
||||||
return urlDecode(path);
|
return urlDecode(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
QByteArray HttpRequest::getVersion() const {
|
const QByteArray& HttpRequest::getRawPath() const
|
||||||
|
{
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
QByteArray HttpRequest::getVersion() const
|
||||||
|
{
|
||||||
return version;
|
return version;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
QByteArray HttpRequest::getHeader(const QByteArray& name) const {
|
QByteArray HttpRequest::getHeader(const QByteArray& name) const
|
||||||
return headers.value(name);
|
{
|
||||||
|
return headers.value(name.toLower());
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<QByteArray> HttpRequest::getHeaders(const QByteArray& name) const {
|
QList<QByteArray> HttpRequest::getHeaders(const QByteArray& name) const
|
||||||
return headers.values(name);
|
{
|
||||||
|
return headers.values(name.toLower());
|
||||||
}
|
}
|
||||||
|
|
||||||
QMultiMap<QByteArray,QByteArray> HttpRequest::getHeaderMap() const {
|
QMultiMap<QByteArray,QByteArray> HttpRequest::getHeaderMap() const
|
||||||
|
{
|
||||||
return headers;
|
return headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray HttpRequest::getParameter(const QByteArray& name) const {
|
QByteArray HttpRequest::getParameter(const QByteArray& name) const
|
||||||
|
{
|
||||||
return parameters.value(name);
|
return parameters.value(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<QByteArray> HttpRequest::getParameters(const QByteArray& name) const {
|
QList<QByteArray> HttpRequest::getParameters(const QByteArray& name) const
|
||||||
|
{
|
||||||
return parameters.values(name);
|
return parameters.values(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
QMultiMap<QByteArray,QByteArray> HttpRequest::getParameterMap() const {
|
QMultiMap<QByteArray,QByteArray> HttpRequest::getParameterMap() const
|
||||||
|
{
|
||||||
return parameters;
|
return parameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray HttpRequest::getBody() const {
|
QByteArray HttpRequest::getBody() const
|
||||||
|
{
|
||||||
return bodyData;
|
return bodyData;
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray HttpRequest::urlDecode(const QByteArray source) {
|
QByteArray HttpRequest::urlDecode(const QByteArray source)
|
||||||
|
{
|
||||||
QByteArray buffer(source);
|
QByteArray buffer(source);
|
||||||
buffer.replace('+',' ');
|
buffer.replace('+',' ');
|
||||||
int percentChar=buffer.indexOf('%');
|
int percentChar=buffer.indexOf('%');
|
||||||
while (percentChar>=0) {
|
while (percentChar>=0)
|
||||||
|
{
|
||||||
bool ok;
|
bool ok;
|
||||||
char byte=buffer.mid(percentChar+1,2).toInt(&ok,16);
|
char byte=buffer.mid(percentChar+1,2).toInt(&ok,16);
|
||||||
if (ok) {
|
if (ok)
|
||||||
|
{
|
||||||
buffer.replace(percentChar,3,(char*)&byte,1);
|
buffer.replace(percentChar,3,(char*)&byte,1);
|
||||||
}
|
}
|
||||||
percentChar=buffer.indexOf('%',percentChar+1);
|
percentChar=buffer.indexOf('%',percentChar+1);
|
||||||
@ -308,65 +392,77 @@ QByteArray HttpRequest::urlDecode(const QByteArray source) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void HttpRequest::parseMultiPartFile() {
|
void HttpRequest::parseMultiPartFile()
|
||||||
|
{
|
||||||
qDebug("HttpRequest: parsing multipart temp file");
|
qDebug("HttpRequest: parsing multipart temp file");
|
||||||
tempFile.seek(0);
|
tempFile.seek(0);
|
||||||
bool finished=false;
|
bool finished=false;
|
||||||
while (!tempFile.atEnd() && !finished && !tempFile.error()) {
|
while (!tempFile.atEnd() && !finished && !tempFile.error())
|
||||||
|
{
|
||||||
#ifdef SUPERVERBOSE
|
#ifdef SUPERVERBOSE
|
||||||
qDebug("HttpRequest: reading multpart headers");
|
qDebug("HttpRequest: reading multpart headers");
|
||||||
#endif
|
#endif
|
||||||
QByteArray fieldName;
|
QByteArray fieldName;
|
||||||
QByteArray fileName;
|
QByteArray fileName;
|
||||||
while (!tempFile.atEnd() && !finished && !tempFile.error()) {
|
while (!tempFile.atEnd() && !finished && !tempFile.error())
|
||||||
|
{
|
||||||
QByteArray line=tempFile.readLine(65536).trimmed();
|
QByteArray line=tempFile.readLine(65536).trimmed();
|
||||||
if (line.startsWith("Content-Disposition:")) {
|
if (line.startsWith("Content-Disposition:"))
|
||||||
if (line.contains("form-data")) {
|
{
|
||||||
|
if (line.contains("form-data"))
|
||||||
|
{
|
||||||
int start=line.indexOf(" name=\"");
|
int start=line.indexOf(" name=\"");
|
||||||
int end=line.indexOf("\"",start+7);
|
int end=line.indexOf("\"",start+7);
|
||||||
if (start>=0 && end>=start) {
|
if (start>=0 && end>=start)
|
||||||
|
{
|
||||||
fieldName=line.mid(start+7,end-start-7);
|
fieldName=line.mid(start+7,end-start-7);
|
||||||
}
|
}
|
||||||
start=line.indexOf(" filename=\"");
|
start=line.indexOf(" filename=\"");
|
||||||
end=line.indexOf("\"",start+11);
|
end=line.indexOf("\"",start+11);
|
||||||
if (start>=0 && end>=start) {
|
if (start>=0 && end>=start)
|
||||||
|
{
|
||||||
fileName=line.mid(start+11,end-start-11);
|
fileName=line.mid(start+11,end-start-11);
|
||||||
}
|
}
|
||||||
#ifdef SUPERVERBOSE
|
#ifdef SUPERVERBOSE
|
||||||
qDebug("HttpRequest: multipart field=%s, filename=%s",fieldName.data(),fileName.data());
|
qDebug("HttpRequest: multipart field=%s, filename=%s",fieldName.data(),fileName.data());
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
else {
|
else
|
||||||
|
{
|
||||||
qDebug("HttpRequest: ignoring unsupported content part %s",line.data());
|
qDebug("HttpRequest: ignoring unsupported content part %s",line.data());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (line.isEmpty()) {
|
else if (line.isEmpty())
|
||||||
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef SUPERVERBOSE
|
#ifdef SUPERVERBOSE
|
||||||
qDebug("HttpRequest: reading multpart data");
|
qDebug("HttpRequest: reading multpart data");
|
||||||
#endif
|
#endif
|
||||||
QTemporaryFile* uploadedFile=0;
|
QTemporaryFile* uploadedFile=0;
|
||||||
QByteArray fieldValue;
|
QByteArray fieldValue;
|
||||||
while (!tempFile.atEnd() && !finished && !tempFile.error()) {
|
while (!tempFile.atEnd() && !finished && !tempFile.error())
|
||||||
|
{
|
||||||
QByteArray line=tempFile.readLine(65536);
|
QByteArray line=tempFile.readLine(65536);
|
||||||
if (line.startsWith("--"+boundary)) {
|
if (line.startsWith("--"+boundary))
|
||||||
|
{
|
||||||
// Boundary found. Until now we have collected 2 bytes too much,
|
// Boundary found. Until now we have collected 2 bytes too much,
|
||||||
// so remove them from the last result
|
// so remove them from the last result
|
||||||
if (fileName.isEmpty() && !fieldName.isEmpty()) {
|
if (fileName.isEmpty() && !fieldName.isEmpty())
|
||||||
|
{
|
||||||
// last field was a form field
|
// last field was a form field
|
||||||
fieldValue.remove(fieldValue.size()-2,2);
|
fieldValue.remove(fieldValue.size()-2,2);
|
||||||
parameters.insert(fieldName,fieldValue);
|
parameters.insert(fieldName,fieldValue);
|
||||||
qDebug("HttpRequest: set parameter %s=%s",fieldName.data(),fieldValue.data());
|
qDebug("HttpRequest: set parameter %s=%s",fieldName.data(),fieldValue.data());
|
||||||
}
|
}
|
||||||
else if (!fileName.isEmpty() && !fieldName.isEmpty()) {
|
else if (!fileName.isEmpty() && !fieldName.isEmpty())
|
||||||
|
{
|
||||||
// last field was a file
|
// last field was a file
|
||||||
#ifdef SUPERVERBOSE
|
#ifdef SUPERVERBOSE
|
||||||
qDebug("HttpRequest: finishing writing to uploaded file");
|
qDebug("HttpRequest: finishing writing to uploaded file");
|
||||||
#endif
|
#endif
|
||||||
uploadedFile->resize(uploadedFile->size()-2);
|
uploadedFile->resize(uploadedFile->size()-2);
|
||||||
uploadedFile->flush();
|
uploadedFile->flush();
|
||||||
uploadedFile->seek(0);
|
uploadedFile->seek(0);
|
||||||
@ -375,57 +471,79 @@ void HttpRequest::parseMultiPartFile() {
|
|||||||
uploadedFiles.insert(fieldName,uploadedFile);
|
uploadedFiles.insert(fieldName,uploadedFile);
|
||||||
qDebug("HttpRequest: uploaded file size is %i",(int) uploadedFile->size());
|
qDebug("HttpRequest: uploaded file size is %i",(int) uploadedFile->size());
|
||||||
}
|
}
|
||||||
if (line.contains(boundary+"--")) {
|
if (line.contains(boundary+"--"))
|
||||||
|
{
|
||||||
finished=true;
|
finished=true;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
else {
|
else
|
||||||
if (fileName.isEmpty() && !fieldName.isEmpty()) {
|
{
|
||||||
|
if (fileName.isEmpty() && !fieldName.isEmpty())
|
||||||
|
{
|
||||||
// this is a form field.
|
// this is a form field.
|
||||||
currentSize+=line.size();
|
currentSize+=line.size();
|
||||||
fieldValue.append(line);
|
fieldValue.append(line);
|
||||||
}
|
}
|
||||||
else if (!fileName.isEmpty() && !fieldName.isEmpty()) {
|
else if (!fileName.isEmpty() && !fieldName.isEmpty())
|
||||||
|
{
|
||||||
// this is a file
|
// this is a file
|
||||||
if (!uploadedFile) {
|
if (!uploadedFile)
|
||||||
|
{
|
||||||
uploadedFile=new QTemporaryFile();
|
uploadedFile=new QTemporaryFile();
|
||||||
uploadedFile->open();
|
uploadedFile->open();
|
||||||
}
|
}
|
||||||
uploadedFile->write(line);
|
uploadedFile->write(line);
|
||||||
if (uploadedFile->error()) {
|
if (uploadedFile->error())
|
||||||
|
{
|
||||||
qCritical("HttpRequest: error writing temp file, %s",qPrintable(uploadedFile->errorString()));
|
qCritical("HttpRequest: error writing temp file, %s",qPrintable(uploadedFile->errorString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (tempFile.error()) {
|
if (tempFile.error())
|
||||||
|
{
|
||||||
qCritical("HttpRequest: cannot read temp file, %s",qPrintable(tempFile.errorString()));
|
qCritical("HttpRequest: cannot read temp file, %s",qPrintable(tempFile.errorString()));
|
||||||
}
|
}
|
||||||
#ifdef SUPERVERBOSE
|
#ifdef SUPERVERBOSE
|
||||||
qDebug("HttpRequest: finished parsing multipart temp file");
|
qDebug("HttpRequest: finished parsing multipart temp file");
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpRequest::~HttpRequest() {
|
HttpRequest::~HttpRequest()
|
||||||
foreach(QByteArray key, uploadedFiles.keys()) {
|
{
|
||||||
|
foreach(QByteArray key, uploadedFiles.keys())
|
||||||
|
{
|
||||||
QTemporaryFile* file=uploadedFiles.value(key);
|
QTemporaryFile* file=uploadedFiles.value(key);
|
||||||
file->close();
|
file->close();
|
||||||
delete file;
|
delete file;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QTemporaryFile* HttpRequest::getUploadedFile(const QByteArray fieldName) {
|
QTemporaryFile* HttpRequest::getUploadedFile(const QByteArray fieldName) const
|
||||||
|
{
|
||||||
return uploadedFiles.value(fieldName);
|
return uploadedFiles.value(fieldName);
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray HttpRequest::getCookie(const QByteArray& name) const {
|
QByteArray HttpRequest::getCookie(const QByteArray& name) const
|
||||||
|
{
|
||||||
return cookies.value(name);
|
return cookies.value(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get the map of cookies */
|
/** Get the map of cookies */
|
||||||
QMap<QByteArray,QByteArray>& HttpRequest::getCookieMap() {
|
QMap<QByteArray,QByteArray>& HttpRequest::getCookieMap()
|
||||||
|
{
|
||||||
return cookies;
|
return cookies;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Get the address of the connected client.
|
||||||
|
Note that multiple clients may have the same IP address, if they
|
||||||
|
share an internet connection (which is very common).
|
||||||
|
*/
|
||||||
|
QHostAddress HttpRequest::getPeerAddress() const
|
||||||
|
{
|
||||||
|
return peerAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -7,12 +7,14 @@
|
|||||||
#define HTTPREQUEST_H
|
#define HTTPREQUEST_H
|
||||||
|
|
||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
|
#include <QHostAddress>
|
||||||
#include <QTcpSocket>
|
#include <QTcpSocket>
|
||||||
#include <QMap>
|
#include <QMap>
|
||||||
#include <QMultiMap>
|
#include <QMultiMap>
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
#include <QTemporaryFile>
|
#include <QTemporaryFile>
|
||||||
#include <QUuid>
|
#include <QUuid>
|
||||||
|
#include "httpglobal.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
This object represents a single HTTP request. It reads the request
|
This object represents a single HTTP request. It reads the request
|
||||||
@ -31,9 +33,10 @@
|
|||||||
The body is always a little larger than the file itself.
|
The body is always a little larger than the file itself.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class HttpRequest {
|
class DECLSPEC HttpRequest {
|
||||||
Q_DISABLE_COPY(HttpRequest)
|
Q_DISABLE_COPY(HttpRequest)
|
||||||
friend class HttpSessionStore;
|
friend class HttpSessionStore;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
/** Values for getStatus() */
|
/** Values for getStatus() */
|
||||||
@ -51,11 +54,12 @@ public:
|
|||||||
virtual ~HttpRequest();
|
virtual ~HttpRequest();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Read the request from a socket. This method must be called repeatedly
|
Read the HTTP request from a socket.
|
||||||
|
This method is called by the connection handler repeatedly
|
||||||
until the status is RequestStatus::complete or RequestStatus::abort.
|
until the status is RequestStatus::complete or RequestStatus::abort.
|
||||||
@param socket Source of the data
|
@param socket Source of the data
|
||||||
*/
|
*/
|
||||||
void readFromSocket(QTcpSocket& socket);
|
void readFromSocket(QTcpSocket* socket);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Get the status of this reqeust.
|
Get the status of this reqeust.
|
||||||
@ -69,12 +73,15 @@ public:
|
|||||||
/** Get the decoded path of the HTPP request (e.g. "/index.html") */
|
/** Get the decoded path of the HTPP request (e.g. "/index.html") */
|
||||||
QByteArray getPath() const;
|
QByteArray getPath() const;
|
||||||
|
|
||||||
|
/** Get the raw path of the HTTP request (e.g. "/file%20with%20spaces.html") */
|
||||||
|
const QByteArray& getRawPath() const;
|
||||||
|
|
||||||
/** Get the version of the HTPP request (e.g. "HTTP/1.1") */
|
/** Get the version of the HTPP request (e.g. "HTTP/1.1") */
|
||||||
QByteArray getVersion() const;
|
QByteArray getVersion() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Get the value of a HTTP request header.
|
Get the value of a HTTP request header.
|
||||||
@param name Name of the header
|
@param name Name of the header, not case-senitive.
|
||||||
@return If the header occurs multiple times, only the last
|
@return If the header occurs multiple times, only the last
|
||||||
one is returned.
|
one is returned.
|
||||||
*/
|
*/
|
||||||
@ -82,16 +89,19 @@ public:
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
Get the values of a HTTP request header.
|
Get the values of a HTTP request header.
|
||||||
@param name Name of the header
|
@param name Name of the header, not case-senitive.
|
||||||
*/
|
*/
|
||||||
QList<QByteArray> getHeaders(const QByteArray& name) const;
|
QList<QByteArray> getHeaders(const QByteArray& name) const;
|
||||||
|
|
||||||
/** Get all HTTP request headers */
|
/**
|
||||||
|
* Get all HTTP request headers. Note that the header names
|
||||||
|
* are returned in lower-case.
|
||||||
|
*/
|
||||||
QMultiMap<QByteArray,QByteArray> getHeaderMap() const;
|
QMultiMap<QByteArray,QByteArray> getHeaderMap() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Get the value of a HTTP request parameter.
|
Get the value of a HTTP request parameter.
|
||||||
@param name Name of the parameter
|
@param name Name of the parameter, case-sensitive.
|
||||||
@return If the parameter occurs multiple times, only the last
|
@return If the parameter occurs multiple times, only the last
|
||||||
one is returned.
|
one is returned.
|
||||||
*/
|
*/
|
||||||
@ -99,14 +109,14 @@ public:
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
Get the values of a HTTP request parameter.
|
Get the values of a HTTP request parameter.
|
||||||
@param name Name of the parameter
|
@param name Name of the parameter, case-sensitive.
|
||||||
*/
|
*/
|
||||||
QList<QByteArray> getParameters(const QByteArray& name) const;
|
QList<QByteArray> getParameters(const QByteArray& name) const;
|
||||||
|
|
||||||
/** Get all HTTP request parameters */
|
/** Get all HTTP request parameters. */
|
||||||
QMultiMap<QByteArray,QByteArray> getParameterMap() const;
|
QMultiMap<QByteArray,QByteArray> getParameterMap() const;
|
||||||
|
|
||||||
/** Get the HTTP request body */
|
/** Get the HTTP request body. */
|
||||||
QByteArray getBody() const;
|
QByteArray getBody() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -125,17 +135,24 @@ public:
|
|||||||
For uploaded files, the method getParameters() returns
|
For uploaded files, the method getParameters() returns
|
||||||
the original fileName as provided by the calling web browser.
|
the original fileName as provided by the calling web browser.
|
||||||
*/
|
*/
|
||||||
QTemporaryFile* getUploadedFile(const QByteArray fieldName);
|
QTemporaryFile* getUploadedFile(const QByteArray fieldName) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Get the value of a cookie
|
Get the value of a cookie.
|
||||||
@param name Name of the cookie
|
@param name Name of the cookie
|
||||||
*/
|
*/
|
||||||
QByteArray getCookie(const QByteArray& name) const;
|
QByteArray getCookie(const QByteArray& name) const;
|
||||||
|
|
||||||
/** Get the map of cookies */
|
/** Get all cookies. */
|
||||||
QMap<QByteArray,QByteArray>& getCookieMap();
|
QMap<QByteArray,QByteArray>& getCookieMap();
|
||||||
|
|
||||||
|
/**
|
||||||
|
Get the address of the connected client.
|
||||||
|
Note that multiple clients may have the same IP address, if they
|
||||||
|
share an internet connection (which is very common).
|
||||||
|
*/
|
||||||
|
QHostAddress getPeerAddress() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
/** Request headers */
|
/** Request headers */
|
||||||
@ -163,11 +180,14 @@ private:
|
|||||||
QByteArray version;
|
QByteArray version;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Status of this request.
|
Status of this request. For the state engine.
|
||||||
@see RequestStatus
|
@see RequestStatus
|
||||||
*/
|
*/
|
||||||
RequestStatus status;
|
RequestStatus status;
|
||||||
|
|
||||||
|
/** Address of the connected peer. */
|
||||||
|
QHostAddress peerAddress;
|
||||||
|
|
||||||
/** Maximum size of requests in bytes. */
|
/** Maximum size of requests in bytes. */
|
||||||
int maxSize;
|
int maxSize;
|
||||||
|
|
||||||
@ -193,13 +213,13 @@ private:
|
|||||||
void parseMultiPartFile();
|
void parseMultiPartFile();
|
||||||
|
|
||||||
/** Sub-procedure of readFromSocket(), read the first line of a request. */
|
/** Sub-procedure of readFromSocket(), read the first line of a request. */
|
||||||
void readRequest(QTcpSocket& socket);
|
void readRequest(QTcpSocket* socket);
|
||||||
|
|
||||||
/** Sub-procedure of readFromSocket(), read header lines. */
|
/** Sub-procedure of readFromSocket(), read header lines. */
|
||||||
void readHeader(QTcpSocket& socket);
|
void readHeader(QTcpSocket* socket);
|
||||||
|
|
||||||
/** Sub-procedure of readFromSocket(), read the request body. */
|
/** Sub-procedure of readFromSocket(), read the request body. */
|
||||||
void readBody(QTcpSocket& socket);
|
void readBody(QTcpSocket* socket);
|
||||||
|
|
||||||
/** Sub-procedure of readFromSocket(), extract and decode request parameters. */
|
/** Sub-procedure of readFromSocket(), extract and decode request parameters. */
|
||||||
void decodeRequestParams();
|
void decodeRequestParams();
|
||||||
@ -207,6 +227,9 @@ private:
|
|||||||
/** Sub-procedure of readFromSocket(), extract cookies from headers */
|
/** Sub-procedure of readFromSocket(), extract cookies from headers */
|
||||||
void extractCookies();
|
void extractCookies();
|
||||||
|
|
||||||
|
/** Buffer for collecting characters of request and header lines */
|
||||||
|
QByteArray lineBuffer;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // HTTPREQUEST_H
|
#endif // HTTPREQUEST_H
|
||||||
|
@ -9,10 +9,12 @@ HttpRequestHandler::HttpRequestHandler(QObject* parent)
|
|||||||
: QObject(parent)
|
: QObject(parent)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
HttpRequestHandler::~HttpRequestHandler() {}
|
HttpRequestHandler::~HttpRequestHandler()
|
||||||
|
{}
|
||||||
|
|
||||||
void HttpRequestHandler::service(HttpRequest& request, HttpResponse& response) {
|
void HttpRequestHandler::service(HttpRequest& request, HttpResponse& response)
|
||||||
qCritical("HttpRequestHandler: you need to override the dispatch() function");
|
{
|
||||||
|
qCritical("HttpRequestHandler: you need to override the service() function");
|
||||||
qDebug("HttpRequestHandler: request=%s %s %s",request.getMethod().data(),request.getPath().data(),request.getVersion().data());
|
qDebug("HttpRequestHandler: request=%s %s %s",request.getMethod().data(),request.getPath().data(),request.getVersion().data());
|
||||||
response.setStatus(501,"not implemented");
|
response.setStatus(501,"not implemented");
|
||||||
response.write("501 not implemented",true);
|
response.write("501 not implemented",true);
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
#ifndef HTTPREQUESTHANDLER_H
|
#ifndef HTTPREQUESTHANDLER_H
|
||||||
#define HTTPREQUESTHANDLER_H
|
#define HTTPREQUESTHANDLER_H
|
||||||
|
|
||||||
|
#include "httpglobal.h"
|
||||||
#include "httprequest.h"
|
#include "httprequest.h"
|
||||||
#include "httpresponse.h"
|
#include "httpresponse.h"
|
||||||
|
|
||||||
@ -21,13 +22,16 @@
|
|||||||
@see StaticFileController which delivers static local files.
|
@see StaticFileController which delivers static local files.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class HttpRequestHandler : public QObject {
|
class DECLSPEC HttpRequestHandler : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_DISABLE_COPY(HttpRequestHandler)
|
Q_DISABLE_COPY(HttpRequestHandler)
|
||||||
public:
|
public:
|
||||||
|
|
||||||
/** Constructor */
|
/**
|
||||||
HttpRequestHandler(QObject* parent=0);
|
* Constructor.
|
||||||
|
* @param parent Parent object.
|
||||||
|
*/
|
||||||
|
HttpRequestHandler(QObject* parent=NULL);
|
||||||
|
|
||||||
/** Destructor */
|
/** Destructor */
|
||||||
virtual ~HttpRequestHandler();
|
virtual ~HttpRequestHandler();
|
||||||
|
@ -5,48 +5,62 @@
|
|||||||
|
|
||||||
#include "httpresponse.h"
|
#include "httpresponse.h"
|
||||||
|
|
||||||
HttpResponse::HttpResponse(QTcpSocket* socket) {
|
HttpResponse::HttpResponse(QTcpSocket* socket)
|
||||||
|
{
|
||||||
this->socket=socket;
|
this->socket=socket;
|
||||||
statusCode=200;
|
statusCode=200;
|
||||||
statusText="OK";
|
statusText="OK";
|
||||||
sentHeaders=false;
|
sentHeaders=false;
|
||||||
sentLastPart=false;
|
sentLastPart=false;
|
||||||
|
chunkedMode=false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpResponse::setHeader(QByteArray name, QByteArray value) {
|
void HttpResponse::setHeader(QByteArray name, QByteArray value)
|
||||||
//Q_ASSERT(sentHeaders==false);
|
{
|
||||||
|
Q_ASSERT(sentHeaders==false);
|
||||||
headers.insert(name,value);
|
headers.insert(name,value);
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpResponse::setHeader(QByteArray name, int value) {
|
void HttpResponse::setHeader(QByteArray name, int value)
|
||||||
//Q_ASSERT(sentHeaders==false);
|
{
|
||||||
|
Q_ASSERT(sentHeaders==false);
|
||||||
headers.insert(name,QByteArray::number(value));
|
headers.insert(name,QByteArray::number(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
QMap<QByteArray,QByteArray>& HttpResponse::getHeaders() {
|
QMap<QByteArray,QByteArray>& HttpResponse::getHeaders()
|
||||||
|
{
|
||||||
return headers;
|
return headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpResponse::setStatus(int statusCode, QByteArray description) {
|
void HttpResponse::setStatus(int statusCode, QByteArray description)
|
||||||
|
{
|
||||||
this->statusCode=statusCode;
|
this->statusCode=statusCode;
|
||||||
statusText=description;
|
statusText=description;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpResponse::writeHeaders() {
|
int HttpResponse::getStatusCode() const
|
||||||
//Q_ASSERT(sentHeaders==false);
|
{
|
||||||
|
return this->statusCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpResponse::writeHeaders()
|
||||||
|
{
|
||||||
|
Q_ASSERT(sentHeaders==false);
|
||||||
QByteArray buffer;
|
QByteArray buffer;
|
||||||
buffer.append("HTTP/1.1 ");
|
buffer.append("HTTP/1.1 ");
|
||||||
buffer.append(QByteArray::number(statusCode));
|
buffer.append(QByteArray::number(statusCode));
|
||||||
buffer.append(' ');
|
buffer.append(' ');
|
||||||
buffer.append(statusText);
|
buffer.append(statusText);
|
||||||
buffer.append("\r\n");
|
buffer.append("\r\n");
|
||||||
foreach(QByteArray name, headers.keys()) {
|
foreach(QByteArray name, headers.keys())
|
||||||
|
{
|
||||||
buffer.append(name);
|
buffer.append(name);
|
||||||
buffer.append(": ");
|
buffer.append(": ");
|
||||||
buffer.append(headers.value(name));
|
buffer.append(headers.value(name));
|
||||||
buffer.append("\r\n");
|
buffer.append("\r\n");
|
||||||
}
|
}
|
||||||
foreach(HttpCookie cookie,cookies.values()) {
|
foreach(HttpCookie cookie,cookies.values())
|
||||||
|
{
|
||||||
buffer.append("Set-Cookie: ");
|
buffer.append("Set-Cookie: ");
|
||||||
buffer.append(cookie.toByteArray());
|
buffer.append(cookie.toByteArray());
|
||||||
buffer.append("\r\n");
|
buffer.append("\r\n");
|
||||||
@ -56,14 +70,21 @@ void HttpResponse::writeHeaders() {
|
|||||||
sentHeaders=true;
|
sentHeaders=true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HttpResponse::writeToSocket(QByteArray data) {
|
bool HttpResponse::writeToSocket(QByteArray data)
|
||||||
|
{
|
||||||
int remaining=data.size();
|
int remaining=data.size();
|
||||||
char* ptr=data.data();
|
char* ptr=data.data();
|
||||||
while (socket->isOpen() && remaining>0) {
|
while (socket->isOpen() && remaining>0)
|
||||||
// Wait until the previous buffer content is written out, otherwise it could become very large
|
{
|
||||||
socket->waitForBytesWritten(-1);
|
// If the output buffer has become large, then wait until it has been sent.
|
||||||
|
if (socket->bytesToWrite()>16384)
|
||||||
|
{
|
||||||
|
socket->waitForBytesWritten(-1);
|
||||||
|
}
|
||||||
|
|
||||||
int written=socket->write(ptr,remaining);
|
int written=socket->write(ptr,remaining);
|
||||||
if (written==-1) {
|
if (written==-1)
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
ptr+=written;
|
ptr+=written;
|
||||||
@ -72,61 +93,106 @@ bool HttpResponse::writeToSocket(QByteArray data) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpResponse::write(QByteArray data, bool lastPart) {
|
void HttpResponse::write(QByteArray data, bool lastPart)
|
||||||
//Q_ASSERT(sentLastPart==false);
|
{
|
||||||
if (sentHeaders==false) {
|
Q_ASSERT(sentLastPart==false);
|
||||||
QByteArray connectionMode=headers.value("Connection");
|
|
||||||
if (!headers.contains("Content-Length") && !headers.contains("Transfer-Encoding") && connectionMode!="close" && connectionMode!="Close") {
|
// Send HTTP headers, if not already done (that happens only on the first call to write())
|
||||||
if (!lastPart) {
|
if (sentHeaders==false)
|
||||||
|
{
|
||||||
|
// If the whole response is generated with a single call to write(), then we know the total
|
||||||
|
// size of the response and therefore can set the Content-Length header automatically.
|
||||||
|
if (lastPart)
|
||||||
|
{
|
||||||
|
// Automatically set the Content-Length header
|
||||||
|
headers.insert("Content-Length",QByteArray::number(data.size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// else if we will not close the connection at the end, them we must use the chunked mode.
|
||||||
|
else
|
||||||
|
{
|
||||||
|
QByteArray connectionValue=headers.value("Connection",headers.value("connection"));
|
||||||
|
bool connectionClose=QString::compare(connectionValue,"close",Qt::CaseInsensitive)==0;
|
||||||
|
if (!connectionClose)
|
||||||
|
{
|
||||||
headers.insert("Transfer-Encoding","chunked");
|
headers.insert("Transfer-Encoding","chunked");
|
||||||
}
|
chunkedMode=true;
|
||||||
else {
|
|
||||||
headers.insert("Content-Length",QByteArray::number(data.size()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
writeHeaders();
|
writeHeaders();
|
||||||
}
|
}
|
||||||
bool chunked=headers.value("Transfer-Encoding")=="chunked" || headers.value("Transfer-Encoding")=="Chunked";
|
|
||||||
if (chunked) {
|
// Send data
|
||||||
if (data.size()>0) {
|
if (data.size()>0)
|
||||||
QByteArray buffer=QByteArray::number(data.size(),16);
|
{
|
||||||
buffer.append("\r\n");
|
if (chunkedMode)
|
||||||
writeToSocket(buffer);
|
{
|
||||||
|
if (data.size()>0)
|
||||||
|
{
|
||||||
|
QByteArray size=QByteArray::number(data.size(),16);
|
||||||
|
writeToSocket(size);
|
||||||
|
writeToSocket("\r\n");
|
||||||
|
writeToSocket(data);
|
||||||
|
writeToSocket("\r\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
writeToSocket(data);
|
writeToSocket(data);
|
||||||
writeToSocket("\r\n");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
writeToSocket(data);
|
// Only for the last chunk, send the terminating marker and flush the buffer.
|
||||||
}
|
if (lastPart)
|
||||||
if (lastPart) {
|
{
|
||||||
if (chunked) {
|
if (chunkedMode)
|
||||||
|
{
|
||||||
writeToSocket("0\r\n\r\n");
|
writeToSocket("0\r\n\r\n");
|
||||||
}
|
}
|
||||||
else if (!headers.contains("Content-Length")) {
|
socket->flush();
|
||||||
socket->disconnectFromHost();
|
|
||||||
}
|
|
||||||
sentLastPart=true;
|
sentLastPart=true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpResponse::writeText(QString text, bool lastPart)
|
|
||||||
{
|
|
||||||
write(QByteArray(text.toUtf8()),lastPart);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool HttpResponse::hasSentLastPart() const {
|
bool HttpResponse::hasSentLastPart() const
|
||||||
|
{
|
||||||
return sentLastPart;
|
return sentLastPart;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void HttpResponse::setCookie(const HttpCookie& cookie) {
|
void HttpResponse::setCookie(const HttpCookie& cookie)
|
||||||
//Q_ASSERT(sentHeaders==false);
|
{
|
||||||
if (!cookie.getName().isEmpty()) {
|
Q_ASSERT(sentHeaders==false);
|
||||||
|
if (!cookie.getName().isEmpty())
|
||||||
|
{
|
||||||
cookies.insert(cookie.getName(),cookie);
|
cookies.insert(cookie.getName(),cookie);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QMap<QByteArray,HttpCookie>& HttpResponse::getCookies() {
|
|
||||||
|
QMap<QByteArray,HttpCookie>& HttpResponse::getCookies()
|
||||||
|
{
|
||||||
return cookies;
|
return cookies;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void HttpResponse::redirect(const QByteArray& url)
|
||||||
|
{
|
||||||
|
setStatus(303,"See Other");
|
||||||
|
setHeader("Location",url);
|
||||||
|
write("Redirect",true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void HttpResponse::flush()
|
||||||
|
{
|
||||||
|
socket->flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool HttpResponse::isConnected() const
|
||||||
|
{
|
||||||
|
return socket->isOpen();
|
||||||
|
}
|
||||||
|
@ -9,12 +9,12 @@
|
|||||||
#include <QMap>
|
#include <QMap>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QTcpSocket>
|
#include <QTcpSocket>
|
||||||
|
#include "httpglobal.h"
|
||||||
#include "httpcookie.h"
|
#include "httpcookie.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
This object represents a HTTP response, in particular the response headers.
|
This object represents a HTTP response, used to return something to the web client.
|
||||||
<p>
|
<p>
|
||||||
Example code for proper response generation:
|
|
||||||
<code><pre>
|
<code><pre>
|
||||||
response.setStatus(200,"OK"); // optional, because this is the default
|
response.setStatus(200,"OK"); // optional, because this is the default
|
||||||
response.writeBody("Hello");
|
response.writeBody("Hello");
|
||||||
@ -27,108 +27,132 @@
|
|||||||
response.write("The request cannot be processed because the servers is broken",true);
|
response.write("The request cannot be processed because the servers is broken",true);
|
||||||
</pre></code>
|
</pre></code>
|
||||||
<p>
|
<p>
|
||||||
For performance reason, writing a single or few large packets is better than writing
|
In case of large responses (e.g. file downloads), a Content-Length header should be set
|
||||||
many small packets. In case of large responses (e.g. file downloads), a Content-Length
|
before calling write(). Web Browsers use that information to display a progress bar.
|
||||||
header should be set before calling write(). Web Browsers use that information to display
|
|
||||||
a progress bar.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class HttpResponse {
|
class DECLSPEC HttpResponse {
|
||||||
Q_DISABLE_COPY(HttpResponse)
|
Q_DISABLE_COPY(HttpResponse)
|
||||||
public:
|
public:
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Constructor.
|
Constructor.
|
||||||
@param socket used to write the response
|
@param socket used to write the response
|
||||||
*/
|
*/
|
||||||
HttpResponse(QTcpSocket* socket);
|
HttpResponse(QTcpSocket* socket);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Set a HTTP response header
|
Set a HTTP response header.
|
||||||
@param name name of the header
|
You must call this method before the first write().
|
||||||
@param value value of the header
|
@param name name of the header
|
||||||
*/
|
@param value value of the header
|
||||||
void setHeader(QByteArray name, QByteArray value);
|
*/
|
||||||
|
void setHeader(QByteArray name, QByteArray value);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Set a HTTP response header
|
Set a HTTP response header.
|
||||||
@param name name of the header
|
You must call this method before the first write().
|
||||||
@param value value of the header
|
@param name name of the header
|
||||||
*/
|
@param value value of the header
|
||||||
void setHeader(QByteArray name, int value);
|
*/
|
||||||
|
void setHeader(QByteArray name, int value);
|
||||||
|
|
||||||
/** Get the map of HTTP response headers */
|
/** Get the map of HTTP response headers */
|
||||||
QMap<QByteArray,QByteArray>& getHeaders();
|
QMap<QByteArray,QByteArray>& getHeaders();
|
||||||
|
|
||||||
/** Get the map of cookies */
|
/** Get the map of cookies */
|
||||||
QMap<QByteArray,HttpCookie>& getCookies();
|
QMap<QByteArray,HttpCookie>& getCookies();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Set status code and description. The default is 200,OK.
|
Set status code and description. The default is 200,OK.
|
||||||
*/
|
You must call this method before the first write().
|
||||||
void setStatus(int statusCode, QByteArray description=QByteArray());
|
*/
|
||||||
|
void setStatus(int statusCode, QByteArray description=QByteArray());
|
||||||
|
|
||||||
/**
|
/** Return the status code. */
|
||||||
Write body data to the socket.
|
int getStatusCode() const;
|
||||||
<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);
|
|
||||||
void writeText(QString text, bool lastPart=false);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Indicates wheter the body has been sent completely. Used by the connection
|
Write body data to the socket.
|
||||||
handler to terminate the body automatically when necessary.
|
<p>
|
||||||
*/
|
The HTTP status line, headers and cookies are sent automatically before the body.
|
||||||
bool hasSentLastPart() const;
|
<p>
|
||||||
|
If the response contains only a single chunk (indicated by lastPart=true),
|
||||||
|
then a Content-Length header is automatically set.
|
||||||
|
<p>
|
||||||
|
Chunked mode is automatically selected if there is no Content-Length header
|
||||||
|
and also no Connection:close header.
|
||||||
|
@param data Data bytes of the body
|
||||||
|
@param lastPart Indicates that this is the last chunk of data and flushes the output buffer.
|
||||||
|
*/
|
||||||
|
void write(QByteArray data, bool lastPart=false);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Set a cookie. Cookies are sent together with the headers when the first
|
Indicates whether the body has been sent completely (write() has been called with lastPart=true).
|
||||||
call to write() occurs.
|
*/
|
||||||
*/
|
bool hasSentLastPart() const;
|
||||||
void setCookie(const HttpCookie& cookie);
|
|
||||||
|
/**
|
||||||
|
Set a cookie.
|
||||||
|
You must call this method before the first write().
|
||||||
|
*/
|
||||||
|
void setCookie(const HttpCookie& cookie);
|
||||||
|
|
||||||
|
/**
|
||||||
|
Send a redirect response to the browser.
|
||||||
|
Cannot be combined with write().
|
||||||
|
@param url Destination URL
|
||||||
|
*/
|
||||||
|
void redirect(const QByteArray& url);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flush the output buffer (of the underlying socket).
|
||||||
|
* You normally don't need to call this method because flush is
|
||||||
|
* automatically called after HttpRequestHandler::service() returns.
|
||||||
|
*/
|
||||||
|
void flush();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* May be used to check whether the connection to the web client has been lost.
|
||||||
|
* This might be useful to cancel the generation of large or slow responses.
|
||||||
|
*/
|
||||||
|
bool isConnected() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
/** Request headers */
|
/** Request headers */
|
||||||
QMap<QByteArray,QByteArray> headers;
|
QMap<QByteArray,QByteArray> headers;
|
||||||
|
|
||||||
/** Socket for writing output */
|
/** Socket for writing output */
|
||||||
QTcpSocket* socket;
|
QTcpSocket* socket;
|
||||||
|
|
||||||
/** HTTP status code*/
|
/** HTTP status code*/
|
||||||
int statusCode;
|
int statusCode;
|
||||||
|
|
||||||
/** HTTP status code description */
|
/** HTTP status code description */
|
||||||
QByteArray statusText;
|
QByteArray statusText;
|
||||||
|
|
||||||
/** Indicator whether headers have been sent */
|
/** Indicator whether headers have been sent */
|
||||||
bool sentHeaders;
|
bool sentHeaders;
|
||||||
|
|
||||||
/** Indicator whether the body has been sent completely */
|
/** Indicator whether the body has been sent completely */
|
||||||
bool sentLastPart;
|
bool sentLastPart;
|
||||||
|
|
||||||
/** Cookies */
|
/** Whether the response is sent in chunked mode */
|
||||||
QMap<QByteArray,HttpCookie> cookies;
|
bool chunkedMode;
|
||||||
|
|
||||||
/** Write raw data to the socket. This method blocks until all bytes have been passed to the TCP buffer */
|
/** Cookies */
|
||||||
bool writeToSocket(QByteArray data);
|
QMap<QByteArray,HttpCookie> cookies;
|
||||||
|
|
||||||
/**
|
/** Write raw data to the socket. This method blocks until all bytes have been passed to the TCP buffer */
|
||||||
Write the response HTTP status and headers to the socket.
|
bool writeToSocket(QByteArray data);
|
||||||
Calling this method is optional, because writeBody() calls
|
|
||||||
it automatically when required.
|
/**
|
||||||
*/
|
Write the response HTTP status and headers to the socket.
|
||||||
void writeHeaders();
|
Calling this method is optional, because writeBody() calls
|
||||||
|
it automatically when required.
|
||||||
|
*/
|
||||||
|
void writeHeaders();
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,12 +1,33 @@
|
|||||||
INCLUDEPATH += $$PWD
|
INCLUDEPATH += $$PWD
|
||||||
DEPENDPATH += $$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
|
QT += network
|
||||||
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
|
# Enable very detailed debug messages when compiling the debug version
|
||||||
SOURCES += $$PWD/httpsession.cpp $$PWD/httpsessionstore.cpp
|
CONFIG(debug, debug|release) {
|
||||||
SOURCES += $$PWD/staticfilecontroller.cpp
|
DEFINES += SUPERVERBOSE
|
||||||
|
}
|
||||||
|
|
||||||
OTHER_FILES += $$PWD/../doc/readme.txt
|
HEADERS += $$PWD/httpglobal.h \
|
||||||
|
$$PWD/httplistener.h \
|
||||||
|
$$PWD/httpconnectionhandler.h \
|
||||||
|
$$PWD/httpconnectionhandlerpool.h \
|
||||||
|
$$PWD/httprequest.h \
|
||||||
|
$$PWD/httpresponse.h \
|
||||||
|
$$PWD/httpcookie.h \
|
||||||
|
$$PWD/httprequesthandler.h \
|
||||||
|
$$PWD/httpsession.h \
|
||||||
|
$$PWD/httpsessionstore.h \
|
||||||
|
$$PWD/staticfilecontroller.h
|
||||||
|
|
||||||
|
SOURCES += $$PWD/httpglobal.cpp \
|
||||||
|
$$PWD/httplistener.cpp \
|
||||||
|
$$PWD/httpconnectionhandler.cpp \
|
||||||
|
$$PWD/httpconnectionhandlerpool.cpp \
|
||||||
|
$$PWD/httprequest.cpp \
|
||||||
|
$$PWD/httpresponse.cpp \
|
||||||
|
$$PWD/httpcookie.cpp \
|
||||||
|
$$PWD/httprequesthandler.cpp \
|
||||||
|
$$PWD/httpsession.cpp \
|
||||||
|
$$PWD/httpsessionstore.cpp \
|
||||||
|
$$PWD/staticfilecontroller.cpp
|
||||||
|
@ -8,374 +8,179 @@
|
|||||||
#include <QUuid>
|
#include <QUuid>
|
||||||
|
|
||||||
|
|
||||||
HttpSession::HttpSession(bool canStore) {
|
HttpSession::HttpSession(bool canStore)
|
||||||
if (canStore) {
|
{
|
||||||
dataPtr=new HttpSessionData();
|
if (canStore)
|
||||||
dataPtr->refCount=1;
|
{
|
||||||
dataPtr->lastAccess=QDateTime::currentMSecsSinceEpoch();
|
dataPtr=new HttpSessionData();
|
||||||
dataPtr->id=QUuid::createUuid().toString().toLatin1();
|
dataPtr->refCount=1;
|
||||||
dataPtr->yacreaderSessionData.comic = 0;
|
dataPtr->lastAccess=QDateTime::currentMSecsSinceEpoch();
|
||||||
dataPtr->yacreaderSessionData.comicId = 0;
|
dataPtr->id=QUuid::createUuid().toString().toLocal8Bit();
|
||||||
dataPtr->yacreaderSessionData.remoteComic = 0;
|
|
||||||
dataPtr->yacreaderSessionData.remoteComicId = 0;
|
|
||||||
#ifdef SUPERVERBOSE
|
#ifdef SUPERVERBOSE
|
||||||
qDebug("HttpSession: created new session data with id %s",dataPtr->id.data());
|
qDebug("HttpSession: created new session data with id %s",dataPtr->id.data());
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
else {
|
else
|
||||||
dataPtr=0;
|
{
|
||||||
}
|
dataPtr=0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpSession::HttpSession(const HttpSession& other) {
|
HttpSession::HttpSession(const HttpSession& other)
|
||||||
dataPtr=other.dataPtr;
|
{
|
||||||
if (dataPtr) {
|
dataPtr=other.dataPtr;
|
||||||
dataPtr->lock.lockForWrite();
|
if (dataPtr)
|
||||||
dataPtr->refCount++;
|
{
|
||||||
|
dataPtr->lock.lockForWrite();
|
||||||
|
dataPtr->refCount++;
|
||||||
#ifdef SUPERVERBOSE
|
#ifdef SUPERVERBOSE
|
||||||
qDebug("HttpSession: refCount of %s is %i",dataPtr->id.data(),dataPtr->refCount);
|
qDebug("HttpSession: refCount of %s is %i",dataPtr->id.data(),dataPtr->refCount);
|
||||||
#endif
|
#endif
|
||||||
dataPtr->lock.unlock();
|
dataPtr->lock.unlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpSession& HttpSession::operator= (const HttpSession& other) {
|
HttpSession& HttpSession::operator= (const HttpSession& other)
|
||||||
HttpSessionData* oldPtr=dataPtr;
|
{
|
||||||
dataPtr=other.dataPtr;
|
HttpSessionData* oldPtr=dataPtr;
|
||||||
if (dataPtr) {
|
dataPtr=other.dataPtr;
|
||||||
dataPtr->lock.lockForWrite();
|
if (dataPtr)
|
||||||
dataPtr->refCount++;
|
{
|
||||||
|
dataPtr->lock.lockForWrite();
|
||||||
|
dataPtr->refCount++;
|
||||||
#ifdef SUPERVERBOSE
|
#ifdef SUPERVERBOSE
|
||||||
qDebug("HttpSession: refCount of %s is %i",dataPtr->id.data(),dataPtr->refCount);
|
qDebug("HttpSession: refCount of %s is %i",dataPtr->id.data(),dataPtr->refCount);
|
||||||
#endif
|
#endif
|
||||||
dataPtr->lastAccess=QDateTime::currentMSecsSinceEpoch();
|
dataPtr->lastAccess=QDateTime::currentMSecsSinceEpoch();
|
||||||
dataPtr->lock.unlock();
|
dataPtr->lock.unlock();
|
||||||
}
|
}
|
||||||
if (oldPtr) {
|
if (oldPtr)
|
||||||
int refCount;
|
{
|
||||||
oldPtr->lock.lockForRead();
|
int refCount;
|
||||||
refCount=oldPtr->refCount--;
|
oldPtr->lock.lockForRead();
|
||||||
|
refCount=oldPtr->refCount--;
|
||||||
#ifdef SUPERVERBOSE
|
#ifdef SUPERVERBOSE
|
||||||
qDebug("HttpSession: refCount of %s is %i",oldPtr->id.data(),oldPtr->refCount);
|
qDebug("HttpSession: refCount of %s is %i",oldPtr->id.data(),oldPtr->refCount);
|
||||||
#endif
|
#endif
|
||||||
oldPtr->lock.unlock();
|
oldPtr->lock.unlock();
|
||||||
if (refCount==0) {
|
if (refCount==0)
|
||||||
delete oldPtr;
|
{
|
||||||
}
|
delete oldPtr;
|
||||||
}
|
}
|
||||||
return *this;
|
}
|
||||||
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpSession::~HttpSession() {
|
HttpSession::~HttpSession()
|
||||||
if (dataPtr) {
|
{
|
||||||
int refCount;
|
if (dataPtr) {
|
||||||
dataPtr->lock.lockForRead();
|
int refCount;
|
||||||
refCount=--dataPtr->refCount;
|
dataPtr->lock.lockForRead();
|
||||||
|
refCount=--dataPtr->refCount;
|
||||||
#ifdef SUPERVERBOSE
|
#ifdef SUPERVERBOSE
|
||||||
qDebug("HttpSession: refCount of %s is %i",dataPtr->id.data(),dataPtr->refCount);
|
qDebug("HttpSession: refCount of %s is %i",dataPtr->id.data(),dataPtr->refCount);
|
||||||
#endif
|
#endif
|
||||||
dataPtr->lock.unlock();
|
dataPtr->lock.unlock();
|
||||||
if (refCount==0) {
|
if (refCount==0)
|
||||||
qDebug("HttpSession: deleting data");
|
{
|
||||||
delete dataPtr;
|
qDebug("HttpSession: deleting data");
|
||||||
}
|
delete dataPtr;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
QByteArray HttpSession::getId() const {
|
QByteArray HttpSession::getId() const
|
||||||
if (dataPtr) {
|
{
|
||||||
return dataPtr->id;
|
if (dataPtr)
|
||||||
}
|
{
|
||||||
else {
|
return dataPtr->id;
|
||||||
return QByteArray();
|
}
|
||||||
}
|
else
|
||||||
|
{
|
||||||
|
return QByteArray();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HttpSession::isNull() const {
|
bool HttpSession::isNull() const {
|
||||||
return dataPtr==0;
|
return dataPtr==0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpSession::set(const QByteArray& key, const QVariant& value) {
|
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//AÑADIDO
|
|
||||||
//sets
|
|
||||||
bool HttpSession::isComicOnDevice(const QString & hash)
|
|
||||||
{
|
{
|
||||||
if(dataPtr)
|
if (dataPtr)
|
||||||
return dataPtr->yacreaderSessionData.comicsOnDevice.contains(hash);
|
|
||||||
else
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
bool HttpSession::isComicDownloaded(const QString & hash)
|
|
||||||
{
|
|
||||||
if(dataPtr)
|
|
||||||
return dataPtr->yacreaderSessionData.downloadedComics.contains(hash);
|
|
||||||
else
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
void HttpSession::setComicOnDevice(const QString & hash)
|
|
||||||
{
|
|
||||||
if(dataPtr)
|
|
||||||
{
|
|
||||||
dataPtr->yacreaderSessionData.comicsOnDevice.insert(hash);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void HttpSession::setComicsOnDevice(const QSet<QString> & set)
|
|
||||||
{
|
|
||||||
if(dataPtr)
|
|
||||||
{
|
|
||||||
dataPtr->yacreaderSessionData.comicsOnDevice = set;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void HttpSession::setDownloadedComic(const QString & hash)
|
|
||||||
{
|
|
||||||
if(dataPtr)
|
|
||||||
{
|
|
||||||
dataPtr->yacreaderSessionData.downloadedComics.insert(hash);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
QSet<QString> HttpSession::getComicsOnDevice()
|
|
||||||
{
|
|
||||||
if(dataPtr)
|
|
||||||
return dataPtr->yacreaderSessionData.comicsOnDevice ;
|
|
||||||
else
|
|
||||||
return QSet<QString>();
|
|
||||||
}
|
|
||||||
QSet<QString> HttpSession::getDownloadedComics()
|
|
||||||
{
|
|
||||||
if(dataPtr)
|
|
||||||
return dataPtr->yacreaderSessionData.downloadedComics ;
|
|
||||||
else
|
|
||||||
return QSet<QString>();
|
|
||||||
}
|
|
||||||
|
|
||||||
void HttpSession::clearComics()
|
|
||||||
{
|
|
||||||
if(dataPtr)
|
|
||||||
{
|
{
|
||||||
dataPtr->yacreaderSessionData.comicsOnDevice.clear();
|
dataPtr->lock.lockForWrite();
|
||||||
dataPtr->yacreaderSessionData.downloadedComics.clear();
|
dataPtr->values.insert(key,value);
|
||||||
}
|
dataPtr->lock.unlock();
|
||||||
}
|
|
||||||
//current comic (import)
|
|
||||||
qulonglong HttpSession::getCurrentComicId()
|
|
||||||
{
|
|
||||||
if(dataPtr)
|
|
||||||
return dataPtr->yacreaderSessionData.comicId ;
|
|
||||||
else
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
Comic* HttpSession::getCurrentComic()
|
|
||||||
{
|
|
||||||
if(dataPtr)
|
|
||||||
{
|
|
||||||
return dataPtr->yacreaderSessionData.comic ;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
void HttpSession::dismissCurrentComic()
|
|
||||||
{
|
|
||||||
if(dataPtr)
|
|
||||||
{
|
|
||||||
if(dataPtr->yacreaderSessionData.comic != 0)
|
|
||||||
{
|
|
||||||
dataPtr->yacreaderSessionData.comic->deleteLater();
|
|
||||||
dataPtr->yacreaderSessionData.comic = 0;
|
|
||||||
}
|
|
||||||
dataPtr->yacreaderSessionData.comicId = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void HttpSession::setCurrentComic(qulonglong id, Comic * comic)
|
|
||||||
{
|
|
||||||
if(dataPtr)
|
|
||||||
{
|
|
||||||
dismissCurrentComic();
|
|
||||||
dataPtr->yacreaderSessionData.comicId = id;
|
|
||||||
dataPtr->yacreaderSessionData.comic = comic;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//current comic (read)
|
|
||||||
qulonglong HttpSession::getCurrentRemoteComicId()
|
|
||||||
{
|
|
||||||
if(dataPtr)
|
|
||||||
return dataPtr->yacreaderSessionData.remoteComicId ;
|
|
||||||
else
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
Comic* HttpSession::getCurrentRemoteComic()
|
|
||||||
{
|
|
||||||
if(dataPtr)
|
|
||||||
{
|
|
||||||
return dataPtr->yacreaderSessionData.remoteComic ;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
void HttpSession::dismissCurrentRemoteComic()
|
|
||||||
{
|
|
||||||
if(dataPtr)
|
|
||||||
{
|
|
||||||
if(dataPtr->yacreaderSessionData.remoteComic != 0)
|
|
||||||
{
|
|
||||||
dataPtr->yacreaderSessionData.remoteComic->deleteLater();
|
|
||||||
dataPtr->yacreaderSessionData.remoteComic = 0;
|
|
||||||
}
|
|
||||||
dataPtr->yacreaderSessionData.remoteComicId = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void HttpSession::setCurrentRemoteComic(qulonglong id, Comic * comic)
|
|
||||||
{
|
|
||||||
if(dataPtr)
|
|
||||||
{
|
|
||||||
dismissCurrentRemoteComic();
|
|
||||||
dataPtr->yacreaderSessionData.remoteComicId = id;
|
|
||||||
dataPtr->yacreaderSessionData.remoteComic = comic;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void HttpSession::remove(const QByteArray& key)
|
||||||
QString HttpSession::getDeviceType()
|
|
||||||
{
|
{
|
||||||
if(dataPtr)
|
if (dataPtr)
|
||||||
{
|
{
|
||||||
return dataPtr->yacreaderSessionData.device;
|
dataPtr->lock.lockForWrite();
|
||||||
}
|
dataPtr->values.remove(key);
|
||||||
return "";
|
dataPtr->lock.unlock();
|
||||||
}
|
}
|
||||||
QString HttpSession::getDisplayType()
|
|
||||||
{
|
|
||||||
if(dataPtr)
|
|
||||||
{
|
|
||||||
return dataPtr->yacreaderSessionData.display;
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
void HttpSession::setDeviceType(const QString & device)
|
|
||||||
{
|
|
||||||
if(dataPtr)
|
|
||||||
{
|
|
||||||
//dataPtr->yacreaderSessionData.comicsOnDevice.clear(); //TODO crear un método clear que limpie la sesión completamente
|
|
||||||
//dataPtr->yacreaderSessionData.downloadedComics.clear();
|
|
||||||
dataPtr->yacreaderSessionData.device = device;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void HttpSession::setDisplayType(const QString & display)
|
|
||||||
{
|
|
||||||
if(dataPtr)
|
|
||||||
{
|
|
||||||
dataPtr->yacreaderSessionData.display = display;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpSession::clearNavigationPath()
|
QVariant HttpSession::get(const QByteArray& key) const
|
||||||
{
|
{
|
||||||
if(dataPtr)
|
QVariant value;
|
||||||
dataPtr->yacreaderSessionData.navigationPath.clear();
|
if (dataPtr)
|
||||||
|
{
|
||||||
|
dataPtr->lock.lockForRead();
|
||||||
|
value=dataPtr->values.value(key);
|
||||||
|
dataPtr->lock.unlock();
|
||||||
|
}
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
QPair<qulonglong, quint32> HttpSession::popNavigationItem()
|
bool HttpSession::contains(const QByteArray& key) const
|
||||||
{
|
{
|
||||||
if(dataPtr && !(dataPtr->yacreaderSessionData.navigationPath.isEmpty()))
|
bool found=false;
|
||||||
return dataPtr->yacreaderSessionData.navigationPath.pop();
|
if (dataPtr)
|
||||||
return QPair<qulonglong, quint32>();
|
{
|
||||||
|
dataPtr->lock.lockForRead();
|
||||||
|
found=dataPtr->values.contains(key);
|
||||||
|
dataPtr->lock.unlock();
|
||||||
|
}
|
||||||
|
return found;
|
||||||
}
|
}
|
||||||
|
|
||||||
QPair<qulonglong, quint32> HttpSession::topNavigationItem()
|
QMap<QByteArray,QVariant> HttpSession::getAll() const
|
||||||
{
|
{
|
||||||
if(dataPtr && !(dataPtr->yacreaderSessionData.navigationPath.isEmpty()))
|
QMap<QByteArray,QVariant> values;
|
||||||
return dataPtr->yacreaderSessionData.navigationPath.top();
|
if (dataPtr)
|
||||||
return QPair<qulonglong, quint32>();
|
{
|
||||||
|
dataPtr->lock.lockForRead();
|
||||||
|
values=dataPtr->values;
|
||||||
|
dataPtr->lock.unlock();
|
||||||
|
}
|
||||||
|
return values;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpSession::pushNavigationItem(const QPair<qulonglong, quint32> &item)
|
qint64 HttpSession::getLastAccess() const
|
||||||
{
|
{
|
||||||
if(dataPtr)
|
qint64 value=0;
|
||||||
dataPtr->yacreaderSessionData.navigationPath.push(item);
|
if (dataPtr)
|
||||||
|
{
|
||||||
|
dataPtr->lock.lockForRead();
|
||||||
|
value=dataPtr->lastAccess;
|
||||||
|
dataPtr->lock.unlock();
|
||||||
|
}
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpSession::updateTopItem(const QPair<qulonglong, quint32> &item)
|
|
||||||
{
|
|
||||||
if(dataPtr && !(dataPtr->yacreaderSessionData.navigationPath.isEmpty()))
|
|
||||||
{
|
|
||||||
dataPtr->yacreaderSessionData.navigationPath.pop();
|
|
||||||
dataPtr->yacreaderSessionData.navigationPath.push(item);
|
|
||||||
} else if(dataPtr)
|
|
||||||
{
|
|
||||||
dataPtr->yacreaderSessionData.navigationPath.push(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QStack<QPair<qulonglong, quint32> > HttpSession::getNavigationPath()
|
void HttpSession::setLastAccess()
|
||||||
{
|
{
|
||||||
if(dataPtr)
|
if (dataPtr)
|
||||||
return dataPtr->yacreaderSessionData.navigationPath;
|
{
|
||||||
else
|
dataPtr->lock.lockForRead();
|
||||||
return QStack<QPair<qulonglong, quint32> >();
|
dataPtr->lastAccess=QDateTime::currentMSecsSinceEpoch();
|
||||||
|
dataPtr->lock.unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,10 +9,7 @@
|
|||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
#include <QVariant>
|
#include <QVariant>
|
||||||
#include <QReadWriteLock>
|
#include <QReadWriteLock>
|
||||||
|
#include "httpglobal.h"
|
||||||
#include <QSet>
|
|
||||||
#include <QString>
|
|
||||||
#include "comic.h"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
This class stores data for a single HTTP session.
|
This class stores data for a single HTTP session.
|
||||||
@ -21,172 +18,100 @@
|
|||||||
@see HttpSessionStore should be used to create and get instances of this class.
|
@see HttpSessionStore should be used to create and get instances of this class.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class HttpSession {
|
class DECLSPEC HttpSession {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Constructor.
|
Constructor.
|
||||||
@param canStore The session can store data, if this parameter is true.
|
@param canStore The session can store data, if this parameter is true.
|
||||||
Otherwise all calls to set() and remove() do not have any effect.
|
Otherwise all calls to set() and remove() do not have any effect.
|
||||||
*/
|
*/
|
||||||
HttpSession(bool canStore=false);
|
HttpSession(bool canStore=false);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Copy constructor. Creates another HttpSession object that shares the
|
Copy constructor. Creates another HttpSession object that shares the
|
||||||
data of the other object.
|
data of the other object.
|
||||||
*/
|
*/
|
||||||
HttpSession(const HttpSession& other);
|
HttpSession(const HttpSession& other);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Copy operator. Detaches from the current shared data and attaches to
|
Copy operator. Detaches from the current shared data and attaches to
|
||||||
the data of the other object.
|
the data of the other object.
|
||||||
*/
|
*/
|
||||||
HttpSession& operator= (const HttpSession& other);
|
HttpSession& operator= (const HttpSession& other);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Destructor. Detaches from the shared data.
|
Destructor. Detaches from the shared data.
|
||||||
*/
|
*/
|
||||||
virtual ~HttpSession();
|
virtual ~HttpSession();
|
||||||
|
|
||||||
/** Get the unique ID of this session. This method is thread safe. */
|
/** Get the unique ID of this session. This method is thread safe. */
|
||||||
QByteArray getId() const;
|
QByteArray getId() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Null sessions cannot store data. All calls to set() and remove()
|
Null sessions cannot store data. All calls to set() and remove()
|
||||||
do not have any effect.This method is thread safe.
|
do not have any effect.This method is thread safe.
|
||||||
*/
|
*/
|
||||||
bool isNull() const;
|
bool isNull() const;
|
||||||
|
|
||||||
/** Set a value. This method is thread safe. */
|
/** Set a value. This method is thread safe. */
|
||||||
void set(const QByteArray& key, const QVariant& value);
|
void set(const QByteArray& key, const QVariant& value);
|
||||||
|
|
||||||
/** Remove a value. This method is thread safe. */
|
/** Remove a value. This method is thread safe. */
|
||||||
void remove(const QByteArray& key);
|
void remove(const QByteArray& key);
|
||||||
|
|
||||||
/** Get a value. This method is thread safe. */
|
/** Get a value. This method is thread safe. */
|
||||||
QVariant get(const QByteArray& key) const;
|
QVariant get(const QByteArray& key) const;
|
||||||
|
|
||||||
/** Check if a key exists. This method is thread safe. */
|
/** Check if a key exists. This method is thread safe. */
|
||||||
bool contains(const QByteArray& key) const;
|
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();
|
|
||||||
|
|
||||||
//AÑADIDO
|
|
||||||
//sets
|
|
||||||
void setComicsOnDevice(const QSet<QString> & set);
|
|
||||||
void setComicOnDevice(const QString & hash);
|
|
||||||
void setDownloadedComic(const QString & hash);
|
|
||||||
bool isComicOnDevice(const QString & hash);
|
|
||||||
bool isComicDownloaded(const QString & hash);
|
|
||||||
QSet<QString> getComicsOnDevice();
|
|
||||||
QSet<QString> getDownloadedComics();
|
|
||||||
void clearComics();
|
|
||||||
|
|
||||||
//current comic (import)
|
|
||||||
qulonglong getCurrentComicId();
|
|
||||||
Comic * getCurrentComic();
|
|
||||||
void dismissCurrentComic();
|
|
||||||
void setCurrentComic(qulonglong id, Comic * comic);
|
|
||||||
|
|
||||||
//current comic (read)
|
|
||||||
qulonglong getCurrentRemoteComicId();
|
|
||||||
Comic * getCurrentRemoteComic();
|
|
||||||
void dismissCurrentRemoteComic();
|
|
||||||
void setCurrentRemoteComic(qulonglong id, Comic * comic);
|
|
||||||
|
|
||||||
//device identification
|
|
||||||
QString getDeviceType();
|
|
||||||
QString getDisplayType();
|
|
||||||
void setDeviceType(const QString & device);
|
|
||||||
void setDisplayType(const QString & display);
|
|
||||||
|
|
||||||
|
|
||||||
/*int popPage();
|
|
||||||
void pushPage(int page);
|
|
||||||
int topPage();
|
|
||||||
|
|
||||||
void clearFoldersPath();
|
|
||||||
int popFolder();
|
|
||||||
void pushFolder(int page);
|
|
||||||
int topFolder();
|
|
||||||
QStack<int> getFoldersPath();*/
|
|
||||||
|
|
||||||
void clearNavigationPath();
|
|
||||||
QPair<qulonglong, quint32> popNavigationItem();
|
|
||||||
QPair<qulonglong, quint32> topNavigationItem();
|
|
||||||
void pushNavigationItem(const QPair<qulonglong, quint32> & item);
|
|
||||||
void updateTopItem(const QPair<qulonglong, quint32> & item);
|
|
||||||
|
|
||||||
//TODO replace QPair by a custom class for storing folderId, page and folderName(save some DB accesses)
|
|
||||||
QStack<QPair<qulonglong, quint32> > getNavigationPath();
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
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:
|
private:
|
||||||
|
|
||||||
struct YACReaderSessionData {
|
struct HttpSessionData {
|
||||||
//cómics disponibles en dispositivo
|
|
||||||
QSet<QString> comicsOnDevice;
|
|
||||||
//cómics que han sido descargados o están siendo descargados en esta sesión
|
|
||||||
QSet<QString> downloadedComics;
|
|
||||||
//cómic actual que está siendo descargado
|
|
||||||
QString device;
|
|
||||||
QString display;
|
|
||||||
qulonglong comicId;
|
|
||||||
qulonglong remoteComicId;
|
|
||||||
|
|
||||||
//folder_id, page_number
|
/** Unique ID */
|
||||||
QStack<QPair<qulonglong, quint32> > navigationPath;
|
QByteArray id;
|
||||||
|
|
||||||
Comic * comic;
|
/** Timestamp of last access, set by the HttpSessionStore */
|
||||||
Comic * remoteComic;
|
qint64 lastAccess;
|
||||||
};
|
|
||||||
|
|
||||||
struct HttpSessionData {
|
/** Reference counter */
|
||||||
|
int refCount;
|
||||||
|
|
||||||
/** Unique ID */
|
/** Used to synchronize threads */
|
||||||
QByteArray id;
|
QReadWriteLock lock;
|
||||||
|
|
||||||
/** Timestamp of last access, set by the HttpSessionStore */
|
/** Storage for the key/value pairs; */
|
||||||
qint64 lastAccess;
|
QMap<QByteArray,QVariant> values;
|
||||||
|
|
||||||
/** Reference counter */
|
};
|
||||||
int refCount;
|
|
||||||
|
|
||||||
/** Used to synchronize threads */
|
/** Pointer to the shared data. */
|
||||||
QReadWriteLock lock;
|
HttpSessionData* dataPtr;
|
||||||
|
|
||||||
/** Storage for the key/value pairs; */
|
|
||||||
QMap<QByteArray,QVariant> values;
|
|
||||||
|
|
||||||
YACReaderSessionData yacreaderSessionData;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Pointer to the shared data. */
|
|
||||||
HttpSessionData* dataPtr;
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -11,10 +11,10 @@ HttpSessionStore::HttpSessionStore(QSettings* settings, QObject* parent)
|
|||||||
:QObject(parent)
|
:QObject(parent)
|
||||||
{
|
{
|
||||||
this->settings=settings;
|
this->settings=settings;
|
||||||
connect(&cleanupTimer,SIGNAL(timeout()),this,SLOT(timerEvent()));
|
connect(&cleanupTimer,SIGNAL(timeout()),this,SLOT(sessionTimerEvent()));
|
||||||
cleanupTimer.start(60000);
|
cleanupTimer.start(60000);
|
||||||
cookieName=settings->value("cookieName","sessionid").toByteArray();
|
cookieName=settings->value("cookieName","sessionid").toByteArray();
|
||||||
expirationTime=settings->value("expirationTime",864000000).toInt();
|
expirationTime=settings->value("expirationTime",3600000).toInt();
|
||||||
qDebug("HttpSessionStore: Sessions expire after %i milliseconds",expirationTime);
|
qDebug("HttpSessionStore: Sessions expire after %i milliseconds",expirationTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -23,18 +23,22 @@ HttpSessionStore::~HttpSessionStore()
|
|||||||
cleanupTimer.stop();
|
cleanupTimer.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray HttpSessionStore::getSessionId(HttpRequest& request, HttpResponse& response) {
|
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.
|
// The session ID in the response has priority because this one will be used in the next request.
|
||||||
mutex.lock();
|
mutex.lock();
|
||||||
// Get the session ID from the response cookie
|
// Get the session ID from the response cookie
|
||||||
QByteArray sessionId=response.getCookies().value(cookieName).getValue();
|
QByteArray sessionId=response.getCookies().value(cookieName).getValue();
|
||||||
if (sessionId.isEmpty()) {
|
if (sessionId.isEmpty())
|
||||||
|
{
|
||||||
// Get the session ID from the request cookie
|
// Get the session ID from the request cookie
|
||||||
sessionId=request.getCookie(cookieName);
|
sessionId=request.getCookie(cookieName);
|
||||||
}
|
}
|
||||||
// Clear the session ID if there is no such session in the storage.
|
// Clear the session ID if there is no such session in the storage.
|
||||||
if (!sessionId.isEmpty()) {
|
if (!sessionId.isEmpty())
|
||||||
if (!sessions.contains(sessionId)) {
|
{
|
||||||
|
if (!sessions.contains(sessionId))
|
||||||
|
{
|
||||||
qDebug("HttpSessionStore: received invalid session cookie with ID %s",sessionId.data());
|
qDebug("HttpSessionStore: received invalid session cookie with ID %s",sessionId.data());
|
||||||
sessionId.clear();
|
sessionId.clear();
|
||||||
}
|
}
|
||||||
@ -43,21 +47,31 @@ QByteArray HttpSessionStore::getSessionId(HttpRequest& request, HttpResponse& re
|
|||||||
return sessionId;
|
return sessionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpSession HttpSessionStore::getSession(HttpRequest& request, HttpResponse& response, bool allowCreate) {
|
HttpSession HttpSessionStore::getSession(HttpRequest& request, HttpResponse& response, bool allowCreate)
|
||||||
|
{
|
||||||
QByteArray sessionId=getSessionId(request,response);
|
QByteArray sessionId=getSessionId(request,response);
|
||||||
mutex.lock();
|
mutex.lock();
|
||||||
if (!sessionId.isEmpty()) {
|
if (!sessionId.isEmpty())
|
||||||
|
{
|
||||||
HttpSession session=sessions.value(sessionId);
|
HttpSession session=sessions.value(sessionId);
|
||||||
if (!session.isNull()) {
|
if (!session.isNull())
|
||||||
|
{
|
||||||
mutex.unlock();
|
mutex.unlock();
|
||||||
|
// Refresh the session cookie
|
||||||
|
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();
|
||||||
|
response.setCookie(HttpCookie(cookieName,session.getId(),expirationTime/1000,cookiePath,cookieComment,cookieDomain));
|
||||||
session.setLastAccess();
|
session.setLastAccess();
|
||||||
return session;
|
return session;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Need to create a new session
|
// Need to create a new session
|
||||||
if (allowCreate) {
|
if (allowCreate)
|
||||||
|
{
|
||||||
QByteArray cookieName=settings->value("cookieName","sessionid").toByteArray();
|
QByteArray cookieName=settings->value("cookieName","sessionid").toByteArray();
|
||||||
QByteArray cookiePath=settings->value("cookiePath","/").toByteArray();
|
QByteArray cookiePath=settings->value("cookiePath").toByteArray();
|
||||||
QByteArray cookieComment=settings->value("cookieComment").toByteArray();
|
QByteArray cookieComment=settings->value("cookieComment").toByteArray();
|
||||||
QByteArray cookieDomain=settings->value("cookieDomain").toByteArray();
|
QByteArray cookieDomain=settings->value("cookieDomain").toByteArray();
|
||||||
HttpSession session(true);
|
HttpSession session(true);
|
||||||
@ -72,7 +86,8 @@ HttpSession HttpSessionStore::getSession(HttpRequest& request, HttpResponse& res
|
|||||||
return HttpSession();
|
return HttpSession();
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpSession HttpSessionStore::getSession(const QByteArray id) {
|
HttpSession HttpSessionStore::getSession(const QByteArray id)
|
||||||
|
{
|
||||||
mutex.lock();
|
mutex.lock();
|
||||||
HttpSession session=sessions.value(id);
|
HttpSession session=sessions.value(id);
|
||||||
mutex.unlock();
|
mutex.unlock();
|
||||||
@ -80,19 +95,19 @@ HttpSession HttpSessionStore::getSession(const QByteArray id) {
|
|||||||
return session;
|
return session;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HttpSessionStore::timerEvent() {
|
void HttpSessionStore::sessionTimerEvent()
|
||||||
// Todo: find a way to delete sessions only if no controller is accessing them
|
{
|
||||||
mutex.lock();
|
mutex.lock();
|
||||||
qint64 now=QDateTime::currentMSecsSinceEpoch();
|
qint64 now=QDateTime::currentMSecsSinceEpoch();
|
||||||
QMap<QByteArray,HttpSession>::iterator i = sessions.begin();
|
QMap<QByteArray,HttpSession>::iterator i = sessions.begin();
|
||||||
while (i != sessions.end()) {
|
while (i != sessions.end())
|
||||||
|
{
|
||||||
QMap<QByteArray,HttpSession>::iterator prev = i;
|
QMap<QByteArray,HttpSession>::iterator prev = i;
|
||||||
++i;
|
++i;
|
||||||
HttpSession session=prev.value();
|
HttpSession session=prev.value();
|
||||||
qint64 lastAccess=session.getLastAccess();
|
qint64 lastAccess=session.getLastAccess();
|
||||||
if (now-lastAccess>expirationTime) { //TODO cleaning up will cause current opened comic to be deleted, so clients won't be able to download it
|
if (now-lastAccess>expirationTime)
|
||||||
//If the cleaning occurs in the midle of a download it going to cause issues
|
{
|
||||||
//Temporal fix: use a big expirationTime = 10 days
|
|
||||||
qDebug("HttpSessionStore: session %s expired",session.getId().data());
|
qDebug("HttpSessionStore: session %s expired",session.getId().data());
|
||||||
sessions.erase(prev);
|
sessions.erase(prev);
|
||||||
}
|
}
|
||||||
@ -102,7 +117,8 @@ void HttpSessionStore::timerEvent() {
|
|||||||
|
|
||||||
|
|
||||||
/** Delete a session */
|
/** Delete a session */
|
||||||
void HttpSessionStore::removeSession(HttpSession session) {
|
void HttpSessionStore::removeSession(HttpSession session)
|
||||||
|
{
|
||||||
mutex.lock();
|
mutex.lock();
|
||||||
sessions.remove(session.getId());
|
sessions.remove(session.getId());
|
||||||
mutex.unlock();
|
mutex.unlock();
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
#include <QMap>
|
#include <QMap>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QMutex>
|
#include <QMutex>
|
||||||
|
#include "httpglobal.h"
|
||||||
#include "httpsession.h"
|
#include "httpsession.h"
|
||||||
#include "httpresponse.h"
|
#include "httpresponse.h"
|
||||||
#include "httprequest.h"
|
#include "httprequest.h"
|
||||||
@ -25,17 +26,17 @@
|
|||||||
<code><pre>
|
<code><pre>
|
||||||
cookiePath=/
|
cookiePath=/
|
||||||
cookieComment=Session ID
|
cookieComment=Session ID
|
||||||
cookieDomain=stefanfrings.de
|
;cookieDomain=stefanfrings.de
|
||||||
</pre></code>
|
</pre></code>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class HttpSessionStore : public QObject {
|
class DECLSPEC HttpSessionStore : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_DISABLE_COPY(HttpSessionStore)
|
Q_DISABLE_COPY(HttpSessionStore)
|
||||||
public:
|
public:
|
||||||
|
|
||||||
/** Constructor. */
|
/** Constructor. */
|
||||||
HttpSessionStore(QSettings* settings, QObject* parent);
|
HttpSessionStore(QSettings* settings, QObject* parent=NULL);
|
||||||
|
|
||||||
/** Destructor */
|
/** Destructor */
|
||||||
virtual ~HttpSessionStore();
|
virtual ~HttpSessionStore();
|
||||||
@ -75,14 +76,15 @@ public:
|
|||||||
/** Delete a session */
|
/** Delete a session */
|
||||||
void removeSession(HttpSession session);
|
void removeSession(HttpSession session);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/** Storage for the sessions */
|
||||||
|
QMap<QByteArray,HttpSession> sessions;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
/** Configuration settings */
|
/** Configuration settings */
|
||||||
QSettings* settings;
|
QSettings* settings;
|
||||||
|
|
||||||
/** Storage for the sessions */
|
|
||||||
QMap<QByteArray,HttpSession> sessions;
|
|
||||||
|
|
||||||
/** Timer to remove expired sessions */
|
/** Timer to remove expired sessions */
|
||||||
QTimer cleanupTimer;
|
QTimer cleanupTimer;
|
||||||
|
|
||||||
@ -98,7 +100,7 @@ private:
|
|||||||
private slots:
|
private slots:
|
||||||
|
|
||||||
/** Called every minute to cleanup expired sessions. */
|
/** Called every minute to cleanup expired sessions. */
|
||||||
void timerEvent();
|
void sessionTimerEvent();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // HTTPSESSIONSTORE_H
|
#endif // HTTPSESSIONSTORE_H
|
||||||
|
@ -7,31 +7,25 @@
|
|||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include "httpsession.h"
|
|
||||||
#include "static.h"
|
|
||||||
#include <QCoreApplication>
|
|
||||||
|
|
||||||
|
|
||||||
StaticFileController::StaticFileController(QSettings* settings, QObject* parent)
|
StaticFileController::StaticFileController(QSettings* settings, QObject* parent)
|
||||||
:HttpRequestHandler(parent)
|
:HttpRequestHandler(parent)
|
||||||
{
|
{
|
||||||
maxAge=settings->value("maxAge","60000").toInt();
|
maxAge=settings->value("maxAge","60000").toInt();
|
||||||
encoding=settings->value("encoding","UTF-8").toString();
|
encoding=settings->value("encoding","UTF-8").toString();
|
||||||
docroot=settings->value("path","./server/docroot").toString();
|
docroot=settings->value("path",".").toString();
|
||||||
// Convert relative path to absolute, based on the directory of the config file.
|
if(!(docroot.startsWith(":/") || docroot.startsWith("qrc://")))
|
||||||
#ifdef Q_OS_WIN32
|
{
|
||||||
if (QDir::isRelativePath(docroot) && settings->format()!=QSettings::NativeFormat)
|
// Convert relative path to absolute, based on the directory of the config file.
|
||||||
#else
|
#ifdef Q_OS_WIN32
|
||||||
if (QDir::isRelativePath(docroot))
|
if (QDir::isRelativePath(docroot) && settings->format()!=QSettings::NativeFormat)
|
||||||
#endif
|
#else
|
||||||
|
if (QDir::isRelativePath(docroot))
|
||||||
|
#endif
|
||||||
{
|
{
|
||||||
#if defined Q_OS_UNIX && ! defined Q_OS_MAC
|
QFileInfo configFile(settings->fileName());
|
||||||
QFileInfo configFile(QString(DATADIR)+"/yacreader");
|
docroot=QFileInfo(configFile.absolutePath(),docroot).absoluteFilePath();
|
||||||
docroot=QFileInfo(QString(DATADIR)+"/yacreader",docroot).absoluteFilePath();
|
}
|
||||||
#else
|
|
||||||
QFileInfo configFile(QCoreApplication::applicationDirPath());
|
|
||||||
docroot=QFileInfo(QCoreApplication::applicationDirPath(),docroot).absoluteFilePath();
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
qDebug("StaticFileController: docroot=%s, encoding=%s, maxAge=%i",qPrintable(docroot),qPrintable(encoding),maxAge);
|
qDebug("StaticFileController: docroot=%s, encoding=%s, maxAge=%i",qPrintable(docroot),qPrintable(encoding),maxAge);
|
||||||
maxCachedFileSize=settings->value("maxCachedFileSize","65536").toInt();
|
maxCachedFileSize=settings->value("maxCachedFileSize","65536").toInt();
|
||||||
@ -41,195 +35,151 @@ StaticFileController::StaticFileController(QSettings* settings, QObject* parent)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void StaticFileController::service(HttpRequest& request, HttpResponse& response) {
|
void StaticFileController::service(HttpRequest& request, HttpResponse& response)
|
||||||
|
{
|
||||||
QByteArray path=request.getPath();
|
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO(DONE) carga sensible al dispositivo y a la localización
|
|
||||||
QString stringPath = path;
|
|
||||||
QStringList paths = QString(path).split('/');
|
|
||||||
QString fileName = paths.last();
|
|
||||||
stringPath.remove(fileName);
|
|
||||||
HttpSession session=Static::sessionStore->getSession(request,response,false);
|
|
||||||
QString device = session.getDeviceType();
|
|
||||||
QString display = session.getDisplayType();
|
|
||||||
if(fileName.endsWith(".png"))
|
|
||||||
fileName = getDeviceAwareFileName(fileName, device, display, request.getHeader("Accept-Language"), stringPath);
|
|
||||||
else
|
|
||||||
fileName = getDeviceAwareFileName(fileName, device, request.getHeader("Accept-Language"), stringPath);
|
|
||||||
QString newPath = stringPath.append(fileName);
|
|
||||||
path = newPath.toLocal8Bit();
|
|
||||||
|
|
||||||
//CAMBIADO
|
|
||||||
response.setHeader("Connection","close");
|
|
||||||
//END_TODO
|
|
||||||
|
|
||||||
// Check if we have the file in cache
|
// Check if we have the file in cache
|
||||||
//qint64 now=QDateTime::currentMSecsSinceEpoch();
|
qint64 now=QDateTime::currentMSecsSinceEpoch();
|
||||||
// mutex.lock();
|
mutex.lock();
|
||||||
// CacheEntry* entry=cache.object(path);
|
CacheEntry* entry=cache.object(path);
|
||||||
//if (entry && (cacheTimeout==0 || entry->created>now-cacheTimeout)) {
|
if (entry && (cacheTimeout==0 || entry->created>now-cacheTimeout))
|
||||||
// QByteArray document=entry->document; //copy the cached document, because other threads may destroy the cached entry immediately after mutex unlock.
|
{
|
||||||
// mutex.unlock();
|
QByteArray document=entry->document; //copy the cached document, because other threads may destroy the cached entry immediately after mutex unlock.
|
||||||
// qDebug("StaticFileController: Cache hit for %s",path.data());
|
QByteArray filename=entry->filename;
|
||||||
// setContentType(path,response);
|
mutex.unlock();
|
||||||
// response.setHeader("Cache-Control","max-age="+QByteArray::number(maxAge/1000));
|
qDebug("StaticFileController: Cache hit for %s",path.data());
|
||||||
// response.write(document);
|
setContentType(filename,response);
|
||||||
//}
|
response.setHeader("Cache-Control","max-age="+QByteArray::number(maxAge/1000));
|
||||||
//else {
|
response.write(document);
|
||||||
|
}
|
||||||
// mutex.unlock();
|
else
|
||||||
//qDebug("StaticFileController: Cache miss for %s",path.data());
|
{
|
||||||
|
mutex.unlock();
|
||||||
// The file is not in cache.
|
// The file is not in cache.
|
||||||
|
qDebug("StaticFileController: Cache miss for %s",path.data());
|
||||||
|
// Forbid access to files outside the docroot directory
|
||||||
|
if (path.contains("/.."))
|
||||||
|
{
|
||||||
|
qWarning("StaticFileController: detected forbidden characters in path %s",path.data());
|
||||||
|
response.setStatus(403,"forbidden");
|
||||||
|
response.write("403 forbidden",true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
// If the filename is a directory, append index.html.
|
// If the filename is a directory, append index.html.
|
||||||
if (QFileInfo(docroot+path).isDir()) {
|
if (QFileInfo(docroot+path).isDir())
|
||||||
|
{
|
||||||
path+="/index.html";
|
path+="/index.html";
|
||||||
}
|
}
|
||||||
|
// Try to open the file
|
||||||
|
|
||||||
QFile file(docroot+path);
|
QFile file(docroot+path);
|
||||||
if (file.exists()) {
|
qDebug("StaticFileController: Open file %s",qPrintable(file.fileName()));
|
||||||
qDebug("StaticFileController: Open file %s",qPrintable(file.fileName()));
|
if (file.open(QIODevice::ReadOnly))
|
||||||
if (file.open(QIODevice::ReadOnly)) {
|
{
|
||||||
setContentType(path,response);
|
setContentType(path,response);
|
||||||
//response.setHeader("Cache-Control","max-age="+QByteArray::number(maxAge/1000));
|
response.setHeader("Cache-Control","max-age="+QByteArray::number(maxAge/1000));
|
||||||
//if (file.size()<=maxCachedFileSize) {
|
if (file.size()<=maxCachedFileSize)
|
||||||
// // Return the file content and store it also in the cache
|
{
|
||||||
// entry=new CacheEntry();
|
// Return the file content and store it also in the cache
|
||||||
// while (!file.atEnd() && !file.error()) {
|
entry=new CacheEntry();
|
||||||
// QByteArray buffer=file.read(65536);
|
while (!file.atEnd() && !file.error())
|
||||||
// response.write(buffer);
|
{
|
||||||
// entry->document.append(buffer);
|
QByteArray buffer=file.read(65536);
|
||||||
// }
|
response.write(buffer);
|
||||||
// entry->created=now;
|
entry->document.append(buffer);
|
||||||
// mutex.lock();
|
|
||||||
// cache.insert(request.getPath(),entry,entry->document.size());
|
|
||||||
// mutex.unlock();
|
|
||||||
//}
|
|
||||||
//else {
|
|
||||||
// Return the file content, do not store in cache*/
|
|
||||||
while (!file.atEnd() && !file.error()) {
|
|
||||||
response.write(file.read(131072));
|
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
file.close();
|
entry->created=now;
|
||||||
|
entry->filename=path;
|
||||||
|
mutex.lock();
|
||||||
|
cache.insert(request.getPath(),entry,entry->document.size());
|
||||||
|
mutex.unlock();
|
||||||
}
|
}
|
||||||
else {
|
else
|
||||||
|
{
|
||||||
|
// Return the file content, do not store in cache
|
||||||
|
while (!file.atEnd() && !file.error())
|
||||||
|
{
|
||||||
|
response.write(file.read(65536));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
file.close();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (file.exists())
|
||||||
|
{
|
||||||
qWarning("StaticFileController: Cannot open existing file %s for reading",qPrintable(file.fileName()));
|
qWarning("StaticFileController: Cannot open existing file %s for reading",qPrintable(file.fileName()));
|
||||||
response.setStatus(403,"forbidden");
|
response.setStatus(403,"forbidden");
|
||||||
response.write("403 forbidden",true);
|
response.write("403 forbidden",true);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
response.setStatus(404,"not found");
|
||||||
|
response.write("404 not found",true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
}
|
||||||
response.setStatus(404,"not found");
|
|
||||||
response.write("404 not found",true);
|
|
||||||
}
|
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void StaticFileController::setContentType(QString fileName, HttpResponse& response) const {
|
void StaticFileController::setContentType(QString fileName, HttpResponse& response) const
|
||||||
if (fileName.endsWith(".png")) {
|
{
|
||||||
|
if (fileName.endsWith(".png"))
|
||||||
|
{
|
||||||
response.setHeader("Content-Type", "image/png");
|
response.setHeader("Content-Type", "image/png");
|
||||||
}
|
}
|
||||||
else if (fileName.endsWith(".jpg")) {
|
else if (fileName.endsWith(".jpg"))
|
||||||
|
{
|
||||||
response.setHeader("Content-Type", "image/jpeg");
|
response.setHeader("Content-Type", "image/jpeg");
|
||||||
}
|
}
|
||||||
else if (fileName.endsWith(".gif")) {
|
else if (fileName.endsWith(".gif"))
|
||||||
|
{
|
||||||
response.setHeader("Content-Type", "image/gif");
|
response.setHeader("Content-Type", "image/gif");
|
||||||
}
|
}
|
||||||
else if (fileName.endsWith(".pdf")) {
|
else if (fileName.endsWith(".pdf"))
|
||||||
|
{
|
||||||
response.setHeader("Content-Type", "application/pdf");
|
response.setHeader("Content-Type", "application/pdf");
|
||||||
}
|
}
|
||||||
else if (fileName.endsWith(".txt")) {
|
else if (fileName.endsWith(".txt"))
|
||||||
|
{
|
||||||
response.setHeader("Content-Type", qPrintable("text/plain; charset="+encoding));
|
response.setHeader("Content-Type", qPrintable("text/plain; charset="+encoding));
|
||||||
}
|
}
|
||||||
else if (fileName.endsWith(".html") || fileName.endsWith(".htm")) {
|
else if (fileName.endsWith(".html") || fileName.endsWith(".htm"))
|
||||||
|
{
|
||||||
response.setHeader("Content-Type", qPrintable("text/html; charset="+encoding));
|
response.setHeader("Content-Type", qPrintable("text/html; charset="+encoding));
|
||||||
}
|
}
|
||||||
else if (fileName.endsWith(".css")) {
|
else if (fileName.endsWith(".css"))
|
||||||
|
{
|
||||||
response.setHeader("Content-Type", "text/css");
|
response.setHeader("Content-Type", "text/css");
|
||||||
}
|
}
|
||||||
else if (fileName.endsWith(".js")) {
|
else if (fileName.endsWith(".js"))
|
||||||
|
{
|
||||||
response.setHeader("Content-Type", "text/javascript");
|
response.setHeader("Content-Type", "text/javascript");
|
||||||
}
|
}
|
||||||
|
else if (fileName.endsWith(".svg"))
|
||||||
|
{
|
||||||
|
response.setHeader("Content-Type", "image/svg+xml");
|
||||||
|
}
|
||||||
|
else if (fileName.endsWith(".woff"))
|
||||||
|
{
|
||||||
|
response.setHeader("Content-Type", "font/woff");
|
||||||
|
}
|
||||||
|
else if (fileName.endsWith(".woff2"))
|
||||||
|
{
|
||||||
|
response.setHeader("Content-Type", "font/woff2");
|
||||||
|
}
|
||||||
|
else if (fileName.endsWith(".ttf"))
|
||||||
|
{
|
||||||
|
response.setHeader("Content-Type", "application/x-font-ttf");
|
||||||
|
}
|
||||||
|
else if (fileName.endsWith(".eot"))
|
||||||
|
{
|
||||||
|
response.setHeader("Content-Type", "application/vnd.ms-fontobject");
|
||||||
|
}
|
||||||
|
else if (fileName.endsWith(".otf"))
|
||||||
|
{
|
||||||
|
response.setHeader("Content-Type", "application/font-otf");
|
||||||
|
}
|
||||||
// Todo: add all of your content types
|
// Todo: add all of your content types
|
||||||
}
|
|
||||||
|
|
||||||
bool StaticFileController::exists(QString localizedName, QString path) const
|
|
||||||
{
|
|
||||||
QString fileName=docroot+"/"+path + localizedName;
|
|
||||||
QFile file(fileName);
|
|
||||||
return file.exists();
|
|
||||||
}
|
|
||||||
|
|
||||||
//retorna fileName si no se encontró alternativa traducida ó fileName-locale.extensión si se encontró
|
|
||||||
QString StaticFileController::getLocalizedFileName(QString fileName, QString locales, QString path) const
|
|
||||||
{
|
|
||||||
QSet<QString> tried; // used to suppress duplicate attempts
|
|
||||||
QStringList locs=locales.split(',',QString::SkipEmptyParts);
|
|
||||||
QStringList fileNameParts = fileName.split('.');
|
|
||||||
QString file = fileNameParts.first();
|
|
||||||
QString extension = fileNameParts.last();
|
|
||||||
// Search for exact match
|
|
||||||
foreach (QString loc,locs) {
|
|
||||||
loc.replace(QRegExp(";.*"),"");
|
|
||||||
loc.replace('-','_');
|
|
||||||
QString localizedName=file+"-"+loc.trimmed()+"."+extension;
|
|
||||||
if (!tried.contains(localizedName)) {
|
|
||||||
if(exists(localizedName, path))
|
|
||||||
return localizedName;
|
|
||||||
tried.insert(localizedName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Search for correct language but any country
|
|
||||||
foreach (QString loc,locs) {
|
|
||||||
loc.replace(QRegExp("[;_-].*"),"");
|
|
||||||
QString localizedName=file+"-"+loc.trimmed()+"."+extension;
|
|
||||||
if (!tried.contains(localizedName)) {
|
|
||||||
if(exists(localizedName, path))
|
|
||||||
return localizedName;
|
|
||||||
tried.insert(localizedName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return fileName;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString StaticFileController::getDeviceAwareFileName(QString fileName, QString device, QString locales, QString path) const
|
|
||||||
{
|
|
||||||
QFileInfo fi(fileName);
|
|
||||||
QString baseName = fi.baseName();
|
|
||||||
QString extension = fi.completeSuffix();
|
|
||||||
|
|
||||||
QString completeFileName = getLocalizedFileName(baseName+"_"+device+"."+extension,locales,path);
|
|
||||||
|
|
||||||
if(QFile(docroot+"/"+path+completeFileName).exists())
|
|
||||||
return completeFileName; //existe un archivo específico para este dispositivo y locales
|
|
||||||
else
|
|
||||||
return getLocalizedFileName(fileName,locales,path); //no hay archivo específico para el dispositivo, pero puede haberlo para estas locales
|
|
||||||
}
|
|
||||||
|
|
||||||
QString StaticFileController::getDeviceAwareFileName(QString fileName, QString device, QString display, QString locales, QString path) const
|
|
||||||
{
|
|
||||||
QFileInfo fi(fileName);
|
|
||||||
QString baseName = fi.baseName();
|
|
||||||
QString extension = fi.completeSuffix();
|
|
||||||
|
|
||||||
QString completeFileName = baseName+display+"."+extension;
|
|
||||||
if(QFile(docroot+"/"+path+completeFileName).exists())
|
|
||||||
return completeFileName;
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
completeFileName = baseName+"_"+device+display+"."+extension;
|
qDebug("StaticFileController: unknown MIME type for filename '%s'", qPrintable(fileName));
|
||||||
if((QFile(docroot+"/"+path+completeFileName).exists()))
|
|
||||||
return completeFileName;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return fileName;
|
|
||||||
}
|
}
|
||||||
|
@ -6,19 +6,20 @@
|
|||||||
#ifndef STATICFILECONTROLLER_H
|
#ifndef STATICFILECONTROLLER_H
|
||||||
#define STATICFILECONTROLLER_H
|
#define STATICFILECONTROLLER_H
|
||||||
|
|
||||||
|
#include <QCache>
|
||||||
|
#include <QMutex>
|
||||||
|
#include "httpglobal.h"
|
||||||
#include "httprequest.h"
|
#include "httprequest.h"
|
||||||
#include "httpresponse.h"
|
#include "httpresponse.h"
|
||||||
#include "httprequesthandler.h"
|
#include "httprequesthandler.h"
|
||||||
#include <QCache>
|
|
||||||
#include <QMutex>
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Delivers static files. It is usually called by the applications main request handler when
|
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.
|
the caller requests a path that is mapped to static files.
|
||||||
<p>
|
<p>
|
||||||
The following settings are required in the config file:
|
The following settings are required in the config file:
|
||||||
<code><pre>
|
<code><pre>
|
||||||
path=docroot
|
path=../docroot
|
||||||
encoding=UTF-8
|
encoding=UTF-8
|
||||||
maxAge=60000
|
maxAge=60000
|
||||||
cacheTime=60000
|
cacheTime=60000
|
||||||
@ -39,54 +40,48 @@
|
|||||||
received a related HTTP request.
|
received a related HTTP request.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class StaticFileController : public HttpRequestHandler {
|
class DECLSPEC StaticFileController : public HttpRequestHandler {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_DISABLE_COPY(StaticFileController);
|
Q_DISABLE_COPY(StaticFileController)
|
||||||
public:
|
public:
|
||||||
|
|
||||||
/** Constructor */
|
/** Constructor */
|
||||||
StaticFileController(QSettings* settings, QObject* parent = 0);
|
StaticFileController(QSettings* settings, QObject* parent = NULL);
|
||||||
|
|
||||||
/** Generates the response */
|
/** Generates the response */
|
||||||
void service(HttpRequest& request, HttpResponse& response);
|
void service(HttpRequest& request, HttpResponse& response);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
/** Encoding of text files */
|
/** Encoding of text files */
|
||||||
QString encoding;
|
QString encoding;
|
||||||
|
|
||||||
/** Root directory of documents */
|
/** Root directory of documents */
|
||||||
QString docroot;
|
QString docroot;
|
||||||
|
|
||||||
/** Maximum age of files in the browser cache */
|
/** Maximum age of files in the browser cache */
|
||||||
int maxAge;
|
int maxAge;
|
||||||
|
|
||||||
struct CacheEntry {
|
struct CacheEntry {
|
||||||
QByteArray document;
|
QByteArray document;
|
||||||
qint64 created;
|
qint64 created;
|
||||||
};
|
QByteArray filename;
|
||||||
|
};
|
||||||
|
|
||||||
/** Timeout for each cached file */
|
/** Timeout for each cached file */
|
||||||
int cacheTimeout;
|
int cacheTimeout;
|
||||||
|
|
||||||
|
/** Maximum size of files in cache, larger files are not cached */
|
||||||
|
int maxCachedFileSize;
|
||||||
|
|
||||||
/** Maximum size of files in cache, larger files are not cached */
|
/** Cache storage */
|
||||||
int maxCachedFileSize;
|
QCache<QString,CacheEntry> cache;
|
||||||
|
|
||||||
/** Cache storage */
|
/** Used to synchronize cache access for threads */
|
||||||
QCache<QString,CacheEntry> cache;
|
QMutex mutex;
|
||||||
|
|
||||||
/** Used to synchronize cache access for threads */
|
/** Set a content-type header in the response depending on the ending of the filename */
|
||||||
QMutex mutex;
|
void setContentType(QString file, HttpResponse& response) const;
|
||||||
|
|
||||||
/** Set a content-type header in the response depending on the ending of the filename */
|
|
||||||
void setContentType(QString file, HttpResponse& response) const;
|
|
||||||
|
|
||||||
QString getLocalizedFileName(QString fileName, QString locales, QString path) const;
|
|
||||||
QString getDeviceAwareFileName(QString fileName, QString device, QString locales, QString path) const;
|
|
||||||
QString getDeviceAwareFileName(QString fileName, QString device, QString display, QString locales, QString path) const;
|
|
||||||
|
|
||||||
bool exists(QString localizedName, QString path) const;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // STATICFILECONTROLLER_H
|
#endif // STATICFILECONTROLLER_H
|
||||||
|
Loading…
x
Reference in New Issue
Block a user