Update httpserver to 1.6.5

This commit is contained in:
Luis Ángel San Martín
2016-06-22 19:09:04 +02:00
parent 44d7c892c1
commit 706e0921f3
22 changed files with 1491 additions and 1198 deletions

View File

@ -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());
}
void HttpConnectionHandler::handleConnection(tSocketDescriptor socketDescriptor) {
qDebug("HttpConnectionHandler (%p): handle new connection", this);
busy = true;
Q_ASSERT(socket.isOpen()==false); // if not, then the handler is already busy
if (!socket.setSocketDescriptor(socketDescriptor)) {
qCritical("HttpConnectionHandler (%p): cannot initialize socket: %s", this,qPrintable(socket.errorString()));
return; return;
} }
#endif
// else create an instance of QTcpSocket
socket=new QTcpSocket();
}
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);
busy = true;
Q_ASSERT(socket->isOpen()==false); // if not, then the handler is already busy
//UGLY workaround - we need to clear writebuffer before reusing this socket
//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;
}
#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()) { {
// The loop adds support for HTTP pipelinig
while (socket->bytesAvailable())
{
#ifdef SUPERVERBOSE #ifdef SUPERVERBOSE
qDebug("HttpConnectionHandler (%p): read input",this); qDebug("HttpConnectionHandler (%p): read input",this);
#endif #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 { 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;
}
}
}
}
// 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);
}
} }
} }

View File

@ -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();
@ -62,7 +73,7 @@ private:
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:
/** /**

View File

@ -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
}
}

View File

@ -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. */

View File

@ -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;

View File

@ -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;
}; };

View File

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

View File

@ -12,20 +12,33 @@ 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) {
listen(QHostAddress::Any, (rand() % 45535)+20000);
i++;
} }
HttpListener::~HttpListener()
{
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()) if (!isListening())
{ {
qCritical("HttpListener: Cannot bind on port %i: %s",port,qPrintable(errorString())); qCritical("HttpListener: Cannot bind on port %i: %s",port,qPrintable(errorString()));
@ -35,30 +48,40 @@ HttpListener::HttpListener(QSettings* settings, HttpRequestHandler* requestHandl
} }
} }
HttpListener::~HttpListener() {
close(); void HttpListener::close() {
QTcpServer::close();
qDebug("HttpListener: closed"); qDebug("HttpListener: closed");
if (pool) {
delete pool; delete pool;
qDebug("HttpListener: destroyed"); 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()));

View File

@ -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

View File

@ -8,54 +8,80 @@
#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 #ifdef SUPERVERBOSE
qDebug("HttpRequest: read request"); qDebug("HttpRequest: read request");
#endif #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 #ifdef SUPERVERBOSE
qDebug("HttpRequest: read header"); qDebug("HttpRequest: read header");
#endif #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");
@ -65,35 +91,45 @@ void HttpRequest::readHeader(QTcpSocket& socket) {
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"); }
if (!contentLength.isEmpty()) { QByteArray contentLength=headers.value("content-length");
if (!contentLength.isEmpty())
{
expectedBodySize=contentLength.toInt(); expectedBodySize=contentLength.toInt();
} }
if (expectedBodySize==0) { if (expectedBodySize==0)
{
#ifdef SUPERVERBOSE #ifdef SUPERVERBOSE
qDebug("HttpRequest: expect no body"); qDebug("HttpRequest: expect no body");
#endif #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;
} }
@ -106,46 +142,55 @@ void HttpRequest::readHeader(QTcpSocket& socket) {
} }
} }
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 #ifdef SUPERVERBOSE
qDebug("HttpRequest: extract and decode request parameters"); qDebug("HttpRequest: extract and decode request parameters");
#endif #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 #ifdef SUPERVERBOSE
qDebug("HttpRequest: extract cookies"); qDebug("HttpRequest: extract cookies");
#endif #endif
foreach(QByteArray cookieStr, headers.values("Cookie")) { 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 #ifdef SUPERVERBOSE
qDebug("HttpRequest: found cookie %s",part.data()); qDebug("HttpRequest: found cookie %s",part.data());
#endif // Split the part into name and value #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,40 +392,48 @@ 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;
} }
} }
@ -351,18 +443,22 @@ void HttpRequest::parseMultiPartFile() {
#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");
@ -375,32 +471,39 @@ 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
@ -408,24 +511,39 @@ void HttpRequest::parseMultiPartFile() {
#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;
}

View File

@ -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

View File

@ -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);

View File

@ -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();

View File

@ -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 {
// If the output buffer has become large, then wait until it has been sent.
if (socket->bytesToWrite()>16384)
{
socket->waitForBytesWritten(-1); 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)
headers.insert("Transfer-Encoding","chunked"); {
} // If the whole response is generated with a single call to write(), then we know the total
else { // 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())); 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");
chunkedMode=true;
} }
}
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(data);
writeToSocket("\r\n"); writeToSocket("\r\n");
} }
} }
else { else
{
writeToSocket(data); writeToSocket(data);
} }
if (lastPart) { }
if (chunked) {
// Only for the last chunk, send the terminating marker and flush the buffer.
if (lastPart)
{
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();
}

View File

@ -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,13 +27,11 @@
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:
@ -44,14 +42,16 @@ public:
HttpResponse(QTcpSocket* socket); HttpResponse(QTcpSocket* socket);
/** /**
Set a HTTP response header Set a HTTP response header.
You must call this method before the first write().
@param name name of the header @param name name of the header
@param value value 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.
You must call this method before the first write().
@param name name of the header @param name name of the header
@param value value of the header @param value value of the header
*/ */
@ -65,38 +65,59 @@ public:
/** /**
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. */
int getStatusCode() const;
/** /**
Write body data to the socket. Write body data to the socket.
<p> <p>
The HTTP status line and headers are sent automatically before the first The HTTP status line, headers and cookies are sent automatically before the body.
byte of the body gets sent.
<p> <p>
If the response contains only a single chunk (indicated by lastPart=true), If the response contains only a single chunk (indicated by lastPart=true),
the response is transferred in traditional mode with a Content-Length then a Content-Length header is automatically set.
header, which is automatically added if not already set before.
<p> <p>
Otherwise, each part is transferred in chunked mode. 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 data Data bytes of the body
@param lastPart Indicator, if this is the last part of the response. @param lastPart Indicates that this is the last chunk of data and flushes the output buffer.
*/ */
void write(QByteArray data, bool lastPart=false); 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 Indicates whether the body has been sent completely (write() has been called with lastPart=true).
handler to terminate the body automatically when necessary.
*/ */
bool hasSentLastPart() const; bool hasSentLastPart() const;
/** /**
Set a cookie. Cookies are sent together with the headers when the first Set a cookie.
call to write() occurs. You must call this method before the first write().
*/ */
void setCookie(const HttpCookie& cookie); 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 */
@ -117,6 +138,9 @@ private:
/** Indicator whether the body has been sent completely */ /** Indicator whether the body has been sent completely */
bool sentLastPart; bool sentLastPart;
/** Whether the response is sent in chunked mode */
bool chunkedMode;
/** Cookies */ /** Cookies */
QMap<QByteArray,HttpCookie> cookies; QMap<QByteArray,HttpCookie> cookies;

View File

@ -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

View File

@ -8,28 +8,29 @@
#include <QUuid> #include <QUuid>
HttpSession::HttpSession(bool canStore) { HttpSession::HttpSession(bool canStore)
if (canStore) { {
if (canStore)
{
dataPtr=new HttpSessionData(); dataPtr=new HttpSessionData();
dataPtr->refCount=1; dataPtr->refCount=1;
dataPtr->lastAccess=QDateTime::currentMSecsSinceEpoch(); dataPtr->lastAccess=QDateTime::currentMSecsSinceEpoch();
dataPtr->id=QUuid::createUuid().toString().toLatin1(); dataPtr->id=QUuid::createUuid().toString().toLocal8Bit();
dataPtr->yacreaderSessionData.comic = 0;
dataPtr->yacreaderSessionData.comicId = 0;
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; dataPtr=other.dataPtr;
if (dataPtr) { if (dataPtr)
{
dataPtr->lock.lockForWrite(); dataPtr->lock.lockForWrite();
dataPtr->refCount++; dataPtr->refCount++;
#ifdef SUPERVERBOSE #ifdef SUPERVERBOSE
@ -39,10 +40,12 @@ HttpSession::HttpSession(const HttpSession& other) {
} }
} }
HttpSession& HttpSession::operator= (const HttpSession& other) { HttpSession& HttpSession::operator= (const HttpSession& other)
{
HttpSessionData* oldPtr=dataPtr; HttpSessionData* oldPtr=dataPtr;
dataPtr=other.dataPtr; dataPtr=other.dataPtr;
if (dataPtr) { if (dataPtr)
{
dataPtr->lock.lockForWrite(); dataPtr->lock.lockForWrite();
dataPtr->refCount++; dataPtr->refCount++;
#ifdef SUPERVERBOSE #ifdef SUPERVERBOSE
@ -51,7 +54,8 @@ HttpSession& HttpSession::operator= (const HttpSession& other) {
dataPtr->lastAccess=QDateTime::currentMSecsSinceEpoch(); dataPtr->lastAccess=QDateTime::currentMSecsSinceEpoch();
dataPtr->lock.unlock(); dataPtr->lock.unlock();
} }
if (oldPtr) { if (oldPtr)
{
int refCount; int refCount;
oldPtr->lock.lockForRead(); oldPtr->lock.lockForRead();
refCount=oldPtr->refCount--; refCount=oldPtr->refCount--;
@ -59,14 +63,16 @@ HttpSession& HttpSession::operator= (const HttpSession& other) {
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) { if (dataPtr) {
int refCount; int refCount;
dataPtr->lock.lockForRead(); dataPtr->lock.lockForRead();
@ -75,7 +81,8 @@ HttpSession::~HttpSession() {
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"); qDebug("HttpSession: deleting data");
delete dataPtr; delete dataPtr;
} }
@ -83,11 +90,14 @@ HttpSession::~HttpSession() {
} }
QByteArray HttpSession::getId() const { QByteArray HttpSession::getId() const
if (dataPtr) { {
if (dataPtr)
{
return dataPtr->id; return dataPtr->id;
} }
else { else
{
return QByteArray(); return QByteArray();
} }
} }
@ -96,25 +106,31 @@ 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) { {
if (dataPtr)
{
dataPtr->lock.lockForWrite(); dataPtr->lock.lockForWrite();
dataPtr->values.insert(key,value); dataPtr->values.insert(key,value);
dataPtr->lock.unlock(); dataPtr->lock.unlock();
} }
} }
void HttpSession::remove(const QByteArray& key) { void HttpSession::remove(const QByteArray& key)
if (dataPtr) { {
if (dataPtr)
{
dataPtr->lock.lockForWrite(); dataPtr->lock.lockForWrite();
dataPtr->values.remove(key); dataPtr->values.remove(key);
dataPtr->lock.unlock(); dataPtr->lock.unlock();
} }
} }
QVariant HttpSession::get(const QByteArray& key) const { QVariant HttpSession::get(const QByteArray& key) const
{
QVariant value; QVariant value;
if (dataPtr) { if (dataPtr)
{
dataPtr->lock.lockForRead(); dataPtr->lock.lockForRead();
value=dataPtr->values.value(key); value=dataPtr->values.value(key);
dataPtr->lock.unlock(); dataPtr->lock.unlock();
@ -122,9 +138,11 @@ QVariant HttpSession::get(const QByteArray& key) const {
return value; return value;
} }
bool HttpSession::contains(const QByteArray& key) const { bool HttpSession::contains(const QByteArray& key) const
{
bool found=false; bool found=false;
if (dataPtr) { if (dataPtr)
{
dataPtr->lock.lockForRead(); dataPtr->lock.lockForRead();
found=dataPtr->values.contains(key); found=dataPtr->values.contains(key);
dataPtr->lock.unlock(); dataPtr->lock.unlock();
@ -132,9 +150,11 @@ bool HttpSession::contains(const QByteArray& key) const {
return found; return found;
} }
QMap<QByteArray,QVariant> HttpSession::getAll() const { QMap<QByteArray,QVariant> HttpSession::getAll() const
{
QMap<QByteArray,QVariant> values; QMap<QByteArray,QVariant> values;
if (dataPtr) { if (dataPtr)
{
dataPtr->lock.lockForRead(); dataPtr->lock.lockForRead();
values=dataPtr->values; values=dataPtr->values;
dataPtr->lock.unlock(); dataPtr->lock.unlock();
@ -142,9 +162,11 @@ QMap<QByteArray,QVariant> HttpSession::getAll() const {
return values; return values;
} }
qint64 HttpSession::getLastAccess() const { qint64 HttpSession::getLastAccess() const
{
qint64 value=0; qint64 value=0;
if (dataPtr) { if (dataPtr)
{
dataPtr->lock.lockForRead(); dataPtr->lock.lockForRead();
value=dataPtr->lastAccess; value=dataPtr->lastAccess;
dataPtr->lock.unlock(); dataPtr->lock.unlock();
@ -153,229 +175,12 @@ qint64 HttpSession::getLastAccess() const {
} }
void HttpSession::setLastAccess() { void HttpSession::setLastAccess()
if (dataPtr) { {
if (dataPtr)
{
dataPtr->lock.lockForRead(); dataPtr->lock.lockForRead();
dataPtr->lastAccess=QDateTime::currentMSecsSinceEpoch(); dataPtr->lastAccess=QDateTime::currentMSecsSinceEpoch();
dataPtr->lock.unlock(); dataPtr->lock.unlock();
} }
} }
//A<>ADIDO
//sets
bool HttpSession::isComicOnDevice(const QString & hash)
{
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->yacreaderSessionData.downloadedComics.clear();
}
}
//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;
}
}
QString HttpSession::getDeviceType()
{
if(dataPtr)
{
return dataPtr->yacreaderSessionData.device;
}
return "";
}
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<73>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()
{
if(dataPtr)
dataPtr->yacreaderSessionData.navigationPath.clear();
}
QPair<qulonglong, quint32> HttpSession::popNavigationItem()
{
if(dataPtr && !(dataPtr->yacreaderSessionData.navigationPath.isEmpty()))
return dataPtr->yacreaderSessionData.navigationPath.pop();
return QPair<qulonglong, quint32>();
}
QPair<qulonglong, quint32> HttpSession::topNavigationItem()
{
if(dataPtr && !(dataPtr->yacreaderSessionData.navigationPath.isEmpty()))
return dataPtr->yacreaderSessionData.navigationPath.top();
return QPair<qulonglong, quint32>();
}
void HttpSession::pushNavigationItem(const QPair<qulonglong, quint32> &item)
{
if(dataPtr)
dataPtr->yacreaderSessionData.navigationPath.push(item);
}
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()
{
if(dataPtr)
return dataPtr->yacreaderSessionData.navigationPath;
else
return QStack<QPair<qulonglong, quint32> >();
}

View File

@ -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,7 +18,7 @@
@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:
@ -92,78 +89,8 @@ public:
*/ */
void setLastAccess(); 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();
private: private:
struct YACReaderSessionData {
//c<>mics disponibles en dispositivo
QSet<QString> comicsOnDevice;
//c<>mics que han sido descargados o est<73>n siendo descargados en esta sesi<73>n
QSet<QString> downloadedComics;
//c<>mic actual que est<73> siendo descargado
QString device;
QString display;
qulonglong comicId;
qulonglong remoteComicId;
//folder_id, page_number
QStack<QPair<qulonglong, quint32> > navigationPath;
Comic * comic;
Comic * remoteComic;
};
struct HttpSessionData { struct HttpSessionData {
/** Unique ID */ /** Unique ID */
@ -181,8 +108,6 @@ private:
/** Storage for the key/value pairs; */ /** Storage for the key/value pairs; */
QMap<QByteArray,QVariant> values; QMap<QByteArray,QVariant> values;
YACReaderSessionData yacreaderSessionData;
}; };
/** Pointer to the shared data. */ /** Pointer to the shared data. */

View File

@ -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();

View File

@ -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

View File

@ -7,17 +7,15 @@
#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();
if(!(docroot.startsWith(":/") || docroot.startsWith("qrc://")))
{
// Convert relative path to absolute, based on the directory of the config file. // Convert relative path to absolute, based on the directory of the config file.
#ifdef Q_OS_WIN32 #ifdef Q_OS_WIN32
if (QDir::isRelativePath(docroot) && settings->format()!=QSettings::NativeFormat) if (QDir::isRelativePath(docroot) && settings->format()!=QSettings::NativeFormat)
@ -25,13 +23,9 @@ StaticFileController::StaticFileController(QSettings* settings, QObject* parent)
if (QDir::isRelativePath(docroot)) if (QDir::isRelativePath(docroot))
#endif #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();
// Check if we have the file in cache
qint64 now=QDateTime::currentMSecsSinceEpoch();
mutex.lock();
CacheEntry* entry=cache.object(path);
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.
QByteArray filename=entry->filename;
mutex.unlock();
qDebug("StaticFileController: Cache hit for %s",path.data());
setContentType(filename,response);
response.setHeader("Cache-Control","max-age="+QByteArray::number(maxAge/1000));
response.write(document);
}
else
{
mutex.unlock();
// The file is not in cache.
qDebug("StaticFileController: Cache miss for %s",path.data());
// Forbid access to files outside the docroot directory // Forbid access to files outside the docroot directory
if (path.startsWith("/..")) { if (path.contains("/.."))
qWarning("StaticFileController: somebody attempted to access a file outside the docroot directory"); {
qWarning("StaticFileController: detected forbidden characters in path %s",path.data());
response.setStatus(403,"forbidden"); response.setStatus(403,"forbidden");
response.write("403 forbidden",true); response.write("403 forbidden",true);
return;
} }
//TODO(DONE) carga sensible al dispositivo y a la localizaci<63>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
//qint64 now=QDateTime::currentMSecsSinceEpoch();
// mutex.lock();
// CacheEntry* entry=cache.object(path);
//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();
// qDebug("StaticFileController: Cache hit for %s",path.data());
// setContentType(path,response);
// response.setHeader("Cache-Control","max-age="+QByteArray::number(maxAge/1000));
// response.write(document);
//}
//else {
// mutex.unlock();
//qDebug("StaticFileController: Cache miss for %s",path.data());
// The file is not in cache.
// 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()); entry->created=now;
// mutex.unlock(); entry->filename=path;
//} mutex.lock();
//else { cache.insert(request.getPath(),entry,entry->document.size());
// Return the file content, do not store in cache*/ mutex.unlock();
while (!file.atEnd() && !file.error()) { }
response.write(file.read(131072)); else
//} {
// Return the file content, do not store in cache
while (!file.atEnd() && !file.error())
{
response.write(file.read(65536));
}
} }
file.close(); file.close();
} }
else { 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
else { {
response.setStatus(404,"not found"); response.setStatus(404,"not found");
response.write("404 not found",true); 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<74> alternativa traducida <20> fileName-locale.extensi<73>n si se encontr<74>
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<65>fico para este dispositivo y locales
else
return getLocalizedFileName(fileName,locales,path); //no hay archivo espec<65>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;
} }

View File

@ -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,13 +40,13 @@
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);
@ -64,12 +65,12 @@ private:
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 */ /** Maximum size of files in cache, larger files are not cached */
int maxCachedFileSize; int maxCachedFileSize;
@ -81,12 +82,6 @@ private:
/** Set a content-type header in the response depending on the ending of the filename */ /** Set a content-type header in the response depending on the ending of the filename */
void setContentType(QString file, HttpResponse& response) const; 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