yacreader/third_party/QtWebApp/httpserver/httpconnectionhandler.cpp
Luis Ángel San Martín 986450eb20 Update QtWebApp to 1.8.3
nullptr redefinition for old __cplusplus versions has been removed, the rest is vanilla
2021-10-19 00:00:09 +02:00

275 lines
9.1 KiB
C++

/**
@file
@author Stefan Frings
*/
#include "httpconnectionhandler.h"
#include "httpresponse.h"
using namespace stefanfrings;
HttpConnectionHandler::HttpConnectionHandler(const QSettings *settings, HttpRequestHandler *requestHandler, const QSslConfiguration* sslConfiguration)
: QObject()
{
Q_ASSERT(settings!=nullptr);
Q_ASSERT(requestHandler!=nullptr);
this->settings=settings;
this->requestHandler=requestHandler;
this->sslConfiguration=sslConfiguration;
currentRequest=nullptr;
busy=false;
// execute signals in a new thread
thread = new QThread();
thread->start();
qDebug("HttpConnectionHandler (%p): thread started", static_cast<void*>(this));
moveToThread(thread);
readTimer.moveToThread(thread);
readTimer.setSingleShot(true);
// Create TCP or SSL socket
createSocket();
socket->moveToThread(thread);
// Connect signals
connect(socket, SIGNAL(readyRead()), SLOT(read()));
connect(socket, SIGNAL(disconnected()), SLOT(disconnected()));
connect(&readTimer, SIGNAL(timeout()), SLOT(readTimeout()));
connect(thread, SIGNAL(finished()), this, SLOT(thread_done()));
qDebug("HttpConnectionHandler (%p): constructed", static_cast<void*>(this));
}
void HttpConnectionHandler::thread_done()
{
readTimer.stop();
socket->close();
delete socket;
qDebug("HttpConnectionHandler (%p): thread stopped", static_cast<void*>(this));
}
HttpConnectionHandler::~HttpConnectionHandler()
{
thread->quit();
thread->wait();
thread->deleteLater();
qDebug("HttpConnectionHandler (%p): destroyed", static_cast<void*>(this));
}
void HttpConnectionHandler::createSocket()
{
// If SSL is supported and configured, then create an instance of QSslSocket
#ifndef QT_NO_SSL
if (sslConfiguration)
{
QSslSocket* sslSocket=new QSslSocket();
sslSocket->setSslConfiguration(*sslConfiguration);
socket=sslSocket;
qDebug("HttpConnectionHandler (%p): SSL is enabled", static_cast<void*>(this));
return;
}
#endif
// else create an instance of QTcpSocket
socket=new QTcpSocket();
}
void HttpConnectionHandler::handleConnection(tSocketDescriptor socketDescriptor)
{
qDebug("HttpConnectionHandler (%p): handle new connection", static_cast<void*>(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",
static_cast<void*>(this),qPrintable(socket->errorString()));
return;
}
#ifndef QT_NO_SSL
// Switch on encryption, if SSL is configured
if (sslConfiguration)
{
qDebug("HttpConnectionHandler (%p): Starting encryption", static_cast<void*>(this));
(static_cast<QSslSocket*>(socket))->startServerEncryption();
}
#endif
// Start timer for read timeout
int readTimeout=settings->value("readTimeout",10000).toInt();
readTimer.start(readTimeout);
// delete previous request
delete currentRequest;
currentRequest=nullptr;
}
bool HttpConnectionHandler::isBusy()
{
return busy;
}
void HttpConnectionHandler::setBusy()
{
this->busy = true;
}
void HttpConnectionHandler::readTimeout()
{
qDebug("HttpConnectionHandler (%p): read timeout occured",static_cast<void*>(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=nullptr;
}
void HttpConnectionHandler::disconnected()
{
qDebug("HttpConnectionHandler (%p): disconnected", static_cast<void*>(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",static_cast<void*>(this));
#endif
// Create new HttpRequest object if necessary
if (!currentRequest)
{
currentRequest=new HttpRequest(settings);
}
// Collect data for the request object
while (socket->bytesAvailable() && currentRequest->getStatus()!=HttpRequest::complete && currentRequest->getStatus()!=HttpRequest::abort)
{
currentRequest->readFromSocket(socket);
if (currentRequest->getStatus()==HttpRequest::waitForBody)
{
// Restart timer for read timeout, otherwise it would
// expire during large file uploads.
int readTimeout=settings->value("readTimeout",10000).toInt();
readTimer.start(readTimeout);
}
}
// If the request is aborted, return error message and close the connection
if (currentRequest->getStatus()==HttpRequest::abort)
{
socket->write("HTTP/1.1 413 entity too large\r\nConnection: close\r\n\r\n413 Entity too large\r\n");
while(socket->bytesToWrite()) socket->waitForBytesWritten();
socket->disconnectFromHost();
delete currentRequest;
currentRequest=nullptr;
return;
}
// If the request is complete, let the request mapper dispatch it
if (currentRequest->getStatus()==HttpRequest::complete)
{
readTimer.stop();
qDebug("HttpConnectionHandler (%p): received request",static_cast<void*>(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",
static_cast<void*>(this));
}
// Finalize sending the response if not already done
if (!response.hasSentLastPart())
{
response.write(QByteArray(),true);
}
qDebug("HttpConnectionHandler (%p): finished request",static_cast<void*>(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
int readTimeout=settings->value("readTimeout",10000).toInt();
readTimer.start(readTimeout);
}
delete currentRequest;
currentRequest=nullptr;
}
}
}