/** @file @author Stefan Frings */ #include "httpconnectionhandler.h" #include "httpresponse.h" using namespace qtwebapp; HttpConnectionHandler::HttpConnectionHandler(QSettings* settings, HttpRequestHandler* requestHandler, QSslConfiguration* sslConfiguration) : QThread(), useQtSettings(true) { Q_ASSERT(settings != 0); Q_ASSERT(requestHandler != 0); this->settings = settings; this->listenerSettings = 0; this->requestHandler = requestHandler; this->sslConfiguration = sslConfiguration; currentRequest = 0; 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(); } HttpConnectionHandler::HttpConnectionHandler(const HttpListenerSettings* settings, HttpRequestHandler* requestHandler, QSslConfiguration* sslConfiguration) : QThread(), useQtSettings(false) { Q_ASSERT(settings != 0); Q_ASSERT(requestHandler != 0); this->settings = 0; this->listenerSettings = settings; this->requestHandler = requestHandler; this->sslConfiguration = sslConfiguration; currentRequest = 0; 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(); } 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() { #ifdef SUPERVERBOSE qDebug("HttpConnectionHandler (%p): thread started", this); #endif try { exec(); } catch (...) { qCritical("HttpConnectionHandler (%p): an uncatched exception occurred in the thread",this); } socket->close(); delete socket; readTimer.stop(); #ifdef SUPERVERBOSE qDebug("HttpConnectionHandler (%p): thread stopped", this); #endif } void HttpConnectionHandler::handleConnection(tSocketDescriptor socketDescriptor) { #ifdef SUPERVERBOSE qDebug("HttpConnectionHandler (%p): handle new connection", this); #endif 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 int readTimeout = useQtSettings ? settings->value("readTimeout",10000).toInt() : listenerSettings->readTimeout; 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 occurred",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) { if (useQtSettings) { currentRequest = new HttpRequest(settings); } else { currentRequest = new HttpRequest(listenerSettings); } } // 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 = useQtSettings ? settings->value("readTimeout",10000).toInt() : listenerSettings->readTimeout; 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 from %s (%s) %s", this, qPrintable(currentRequest->getPeerAddress().toString()), currentRequest->getMethod().toStdString().c_str(), currentRequest->getPath().toStdString().c_str()); // 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 occurred in the request handler",this); } // Finalize sending the response if not already done if (!response.hasSentLastPart()) { response.write(QByteArray(),true); } #ifdef SUPERVERBOSE qDebug("HttpConnectionHandler (%p): finished request",this); #endif // 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 = useQtSettings ? settings->value("readTimeout",10000).toInt() : listenerSettings->readTimeout; readTimer.start(readTimeout); } delete currentRequest; currentRequest=0; } } }