kopia lustrzana https://github.com/f4exb/sdrangel
				
				
				
			
		
			
				
	
	
		
			325 wiersze
		
	
	
		
			10 KiB
		
	
	
	
		
			C++
		
	
	
			
		
		
	
	
			325 wiersze
		
	
	
		
			10 KiB
		
	
	
	
		
			C++
		
	
	
/**
 | 
						|
  @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 supported 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;
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 |