mirror of
				https://github.com/f4exb/sdrangel.git
				synced 2025-10-31 04:50:29 -04:00 
			
		
		
		
	
		
			
	
	
		
			278 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
		
		
			
		
	
	
			278 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
|  | /**
 | ||
|  |   @file | ||
|  |   @author Stefan Frings | ||
|  | */ | ||
|  | 
 | ||
|  | #include "httpconnectionhandler.h"
 | ||
|  | #include "httpresponse.h"
 | ||
|  | 
 | ||
|  | using namespace stefanfrings; | ||
|  | 
 | ||
|  | HttpConnectionHandler::HttpConnectionHandler(QSettings* settings, HttpRequestHandler* requestHandler, QSslConfiguration* sslConfiguration) | ||
|  |     : QThread() | ||
|  | { | ||
|  |     Q_ASSERT(settings!=0); | ||
|  |     Q_ASSERT(requestHandler!=0); | ||
|  |     this->settings=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() | ||
|  | { | ||
|  |     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
 | ||
|  |     int readTimeout=settings->value("readTimeout",10000).toInt(); | ||
|  |     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) | ||
|  |         { | ||
|  |             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=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
 | ||
|  |                 int readTimeout=settings->value("readTimeout",10000).toInt(); | ||
|  |                 readTimer.start(readTimeout); | ||
|  |             } | ||
|  |             delete currentRequest; | ||
|  |             currentRequest=0; | ||
|  |         } | ||
|  |     } | ||
|  | } |