2017-08-23 16:47:07 +00:00
|
|
|
/**
|
|
|
|
@file
|
|
|
|
@author Stefan Frings
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "httpconnectionhandler.h"
|
|
|
|
#include "httpresponse.h"
|
|
|
|
|
2017-11-11 08:32:15 +00:00
|
|
|
using namespace qtwebapp;
|
2017-08-23 16:47:07 +00:00
|
|
|
|
|
|
|
HttpConnectionHandler::HttpConnectionHandler(QSettings* settings, HttpRequestHandler* requestHandler, QSslConfiguration* sslConfiguration)
|
2017-11-13 00:36:00 +00:00
|
|
|
: QThread(), useQtSettings(true)
|
2017-08-23 16:47:07 +00:00
|
|
|
{
|
2017-11-18 09:15:29 +00:00
|
|
|
Q_ASSERT(settings != 0);
|
|
|
|
Q_ASSERT(requestHandler != 0);
|
|
|
|
this->settings = settings;
|
|
|
|
this->listenerSettings = 0;
|
|
|
|
this->requestHandler = requestHandler;
|
|
|
|
this->sslConfiguration = sslConfiguration;
|
|
|
|
currentRequest = 0;
|
2017-11-13 00:36:00 +00:00
|
|
|
busy=false;
|
|
|
|
|
|
|
|
// Create TCP or SSL socket
|
|
|
|
createSocket();
|
|
|
|
|
|
|
|
// execute signals in my own thread
|
|
|
|
moveToThread(this);
|
|
|
|
socket->moveToThread(this);
|
|
|
|
readTimer.moveToThread(this);
|
|
|
|
|
|
|
|
// Connect signals
|
|
|
|
connect(socket, SIGNAL(readyRead()), SLOT(read()));
|
|
|
|
connect(socket, SIGNAL(disconnected()), SLOT(disconnected()));
|
|
|
|
connect(&readTimer, SIGNAL(timeout()), SLOT(readTimeout()));
|
|
|
|
readTimer.setSingleShot(true);
|
|
|
|
|
|
|
|
qDebug("HttpConnectionHandler (%p): constructed", this);
|
|
|
|
this->start();
|
|
|
|
}
|
|
|
|
|
2017-11-18 09:15:29 +00:00
|
|
|
HttpConnectionHandler::HttpConnectionHandler(const HttpListenerSettings* settings, HttpRequestHandler* requestHandler, QSslConfiguration* sslConfiguration)
|
2017-11-13 00:36:00 +00:00
|
|
|
: QThread(), useQtSettings(false)
|
|
|
|
{
|
2017-11-18 09:15:29 +00:00
|
|
|
Q_ASSERT(settings != 0);
|
|
|
|
Q_ASSERT(requestHandler != 0);
|
|
|
|
this->settings = 0;
|
|
|
|
this->listenerSettings = settings;
|
|
|
|
this->requestHandler = requestHandler;
|
|
|
|
this->sslConfiguration = sslConfiguration;
|
|
|
|
currentRequest = 0;
|
|
|
|
busy = false;
|
2017-08-23 16:47:07 +00:00
|
|
|
|
|
|
|
// Create TCP or SSL socket
|
|
|
|
createSocket();
|
|
|
|
|
|
|
|
// execute signals in my own thread
|
|
|
|
moveToThread(this);
|
|
|
|
socket->moveToThread(this);
|
|
|
|
readTimer.moveToThread(this);
|
|
|
|
|
|
|
|
// Connect signals
|
|
|
|
connect(socket, SIGNAL(readyRead()), SLOT(read()));
|
|
|
|
connect(socket, SIGNAL(disconnected()), SLOT(disconnected()));
|
|
|
|
connect(&readTimer, SIGNAL(timeout()), SLOT(readTimeout()));
|
|
|
|
readTimer.setSingleShot(true);
|
|
|
|
|
|
|
|
qDebug("HttpConnectionHandler (%p): constructed", this);
|
|
|
|
this->start();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
HttpConnectionHandler::~HttpConnectionHandler()
|
|
|
|
{
|
|
|
|
quit();
|
|
|
|
wait();
|
|
|
|
qDebug("HttpConnectionHandler (%p): destroyed", this);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void HttpConnectionHandler::createSocket()
|
|
|
|
{
|
|
|
|
// If SSL is supported and configured, then create an instance of QSslSocket
|
|
|
|
#ifndef QT_NO_OPENSSL
|
|
|
|
if (sslConfiguration)
|
|
|
|
{
|
|
|
|
QSslSocket* sslSocket=new QSslSocket();
|
|
|
|
sslSocket->setSslConfiguration(*sslConfiguration);
|
|
|
|
socket=sslSocket;
|
|
|
|
qDebug("HttpConnectionHandler (%p): SSL is enabled", this);
|
|
|
|
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
|
2017-11-18 09:15:29 +00:00
|
|
|
int readTimeout = useQtSettings ? settings->value("readTimeout",10000).toInt() : listenerSettings->readTimeout;
|
2017-08-23 16:47:07 +00:00
|
|
|
readTimer.start(readTimeout);
|
|
|
|
// delete previous request
|
|
|
|
delete currentRequest;
|
|
|
|
currentRequest=0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool HttpConnectionHandler::isBusy()
|
|
|
|
{
|
|
|
|
return busy;
|
|
|
|
}
|
|
|
|
|
|
|
|
void HttpConnectionHandler::setBusy()
|
|
|
|
{
|
|
|
|
this->busy = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void HttpConnectionHandler::readTimeout()
|
|
|
|
{
|
|
|
|
qDebug("HttpConnectionHandler (%p): read timeout occured",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");
|
|
|
|
|
|
|
|
while(socket->bytesToWrite()) socket->waitForBytesWritten();
|
|
|
|
socket->disconnectFromHost();
|
|
|
|
delete currentRequest;
|
|
|
|
currentRequest=0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void HttpConnectionHandler::disconnected()
|
|
|
|
{
|
|
|
|
qDebug("HttpConnectionHandler (%p): disconnected", this);
|
|
|
|
socket->close();
|
|
|
|
readTimer.stop();
|
|
|
|
busy = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void HttpConnectionHandler::read()
|
|
|
|
{
|
|
|
|
// The loop adds support for HTTP pipelinig
|
|
|
|
while (socket->bytesAvailable())
|
|
|
|
{
|
|
|
|
#ifdef SUPERVERBOSE
|
|
|
|
qDebug("HttpConnectionHandler (%p): read input",this);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// Create new HttpRequest object if necessary
|
|
|
|
if (!currentRequest)
|
|
|
|
{
|
2017-11-18 09:15:29 +00:00
|
|
|
if (useQtSettings) {
|
|
|
|
currentRequest = new HttpRequest(settings);
|
|
|
|
} else {
|
|
|
|
currentRequest = new HttpRequest(listenerSettings);
|
|
|
|
}
|
2017-08-23 16:47:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Collect data for the request object
|
|
|
|
while (socket->bytesAvailable() && currentRequest->getStatus()!=HttpRequest::complete && currentRequest->getStatus()!=HttpRequest::abort)
|
|
|
|
{
|
|
|
|
currentRequest->readFromSocket(socket);
|
|
|
|
if (currentRequest->getStatus()==HttpRequest::waitForBody)
|
|
|
|
{
|
|
|
|
// Restart timer for read timeout, otherwise it would
|
|
|
|
// expire during large file uploads.
|
2017-11-18 09:15:29 +00:00
|
|
|
int readTimeout = useQtSettings ? settings->value("readTimeout",10000).toInt() : listenerSettings->readTimeout;
|
2017-08-23 16:47:07 +00:00
|
|
|
readTimer.start(readTimeout);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the request is aborted, return error message and close the connection
|
|
|
|
if (currentRequest->getStatus()==HttpRequest::abort)
|
|
|
|
{
|
|
|
|
socket->write("HTTP/1.1 413 entity too large\r\nConnection: close\r\n\r\n413 Entity too large\r\n");
|
|
|
|
while(socket->bytesToWrite()) socket->waitForBytesWritten();
|
|
|
|
socket->disconnectFromHost();
|
|
|
|
delete currentRequest;
|
|
|
|
currentRequest=0;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the request is complete, let the request mapper dispatch it
|
|
|
|
if (currentRequest->getStatus()==HttpRequest::complete)
|
|
|
|
{
|
|
|
|
readTimer.stop();
|
|
|
|
qDebug("HttpConnectionHandler (%p): received request",this);
|
|
|
|
|
|
|
|
// Copy the Connection:close header to the response
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
catch (...)
|
|
|
|
{
|
|
|
|
qCritical("HttpConnectionHandler (%p): An uncatched exception occured in the request handler",this);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Finalize sending the response if not already done
|
|
|
|
if (!response.hasSentLastPart())
|
|
|
|
{
|
|
|
|
response.write(QByteArray(),true);
|
|
|
|
}
|
|
|
|
|
|
|
|
qDebug("HttpConnectionHandler (%p): finished request",this);
|
|
|
|
|
|
|
|
// Find out whether the connection must be closed
|
|
|
|
if (!closeConnection)
|
|
|
|
{
|
|
|
|
// Maybe the request handler or mapper added a Connection:close header in the meantime
|
|
|
|
bool closeResponse=QString::compare(response.getHeaders().value("Connection"),"close",Qt::CaseInsensitive)==0;
|
|
|
|
if (closeResponse==true)
|
|
|
|
{
|
|
|
|
closeConnection=true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// If we have no Content-Length header and did not use chunked mode, then we have to close the
|
|
|
|
// connection to tell the HTTP client that the end of the response has been reached.
|
|
|
|
bool hasContentLength=response.getHeaders().contains("Content-Length");
|
|
|
|
if (!hasContentLength)
|
|
|
|
{
|
|
|
|
bool hasChunkedMode=QString::compare(response.getHeaders().value("Transfer-Encoding"),"chunked",Qt::CaseInsensitive)==0;
|
|
|
|
if (!hasChunkedMode)
|
|
|
|
{
|
|
|
|
closeConnection=true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Close the connection or prepare for the next request on the same connection.
|
|
|
|
if (closeConnection)
|
|
|
|
{
|
|
|
|
while(socket->bytesToWrite()) socket->waitForBytesWritten();
|
|
|
|
socket->disconnectFromHost();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Start timer for next request
|
2017-11-18 09:15:29 +00:00
|
|
|
int readTimeout = useQtSettings ? settings->value("readTimeout",10000).toInt() : listenerSettings->readTimeout;
|
2017-08-23 16:47:07 +00:00
|
|
|
readTimer.start(readTimeout);
|
|
|
|
}
|
|
|
|
delete currentRequest;
|
|
|
|
currentRequest=0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|