diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 26d0d5a33f..eb5801197a 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -33,7 +33,7 @@ const quint16 DOMAIN_SERVER_HTTP_PORT = 8080; DomainServer::DomainServer(int argc, char* argv[]) : QCoreApplication(argc, argv), - _httpManager(DOMAIN_SERVER_HTTP_PORT), + _httpManager(DOMAIN_SERVER_HTTP_PORT, QString("%1/resources/web/").arg(QCoreApplication::applicationDirPath())), _assignmentQueueMutex(), _assignmentQueue(), _staticAssignmentFile(QString("%1/config.ds").arg(QCoreApplication::applicationDirPath())), @@ -60,8 +60,7 @@ DomainServer::DomainServer(int argc, char* argv[]) : const char METAVOXEL_CONFIG_OPTION[] = "--metavoxelServerConfig"; _metavoxelServerConfig = getCmdOption(argc, (const char**)argv, METAVOXEL_CONFIG_OPTION); - -// QString documentRootString = QString("%1/resources/web").arg(QCoreApplication::applicationDirPath()); + // // char* documentRoot = new char[documentRootString.size() + 1]; // strcpy(documentRoot, documentRootString.toLocal8Bit().constData()); diff --git a/libraries/embedded-webserver/src/HttpConnection.cpp b/libraries/embedded-webserver/src/HttpConnection.cpp index 6bddf594be..7923e8ec40 100755 --- a/libraries/embedded-webserver/src/HttpConnection.cpp +++ b/libraries/embedded-webserver/src/HttpConnection.cpp @@ -22,11 +22,8 @@ HttpConnection::HttpConnection (QTcpSocket* socket, HttpManager* parentManager) QObject(parentManager), _parentManager(parentManager), _socket(socket), - _unmasker(new MaskFilter(socket, this)), _stream(socket), - _address(socket->peerAddress()), - _webSocketPaused(false), - _closeSent(false) + _address(socket->peerAddress()) { // take over ownership of the socket _socket->setParent(this); @@ -50,11 +47,6 @@ HttpConnection::~HttpConnection () } } -bool HttpConnection::isWebSocketRequest () -{ - return _requestHeaders.value("Upgrade") == "websocket"; -} - QList HttpConnection::parseFormData () const { // make sure we have the correct MIME type @@ -111,9 +103,7 @@ QList HttpConnection::parseFormData () const return data; } -void HttpConnection::respond ( - const char* code, const QByteArray& content, const char* contentType, const Headers& headers) -{ +void HttpConnection::respond (const char* code, const QByteArray& content, const char* contentType, const Headers& headers) { _socket->write("HTTP/1.1 "); _socket->write(code); _socket->write("\r\n"); @@ -139,6 +129,7 @@ void HttpConnection::respond ( _socket->write("Connection: close\r\n\r\n"); if (csize > 0) { + qDebug() << "Writing" << QString(content) << "\n"; _socket->write(content); } @@ -148,55 +139,6 @@ void HttpConnection::respond ( _socket->disconnectFromHost(); } -void HttpConnection::switchToWebSocket (const char* protocol) -{ - _socket->write("HTTP/1.1 101 Switching Protocols\r\n"); - _socket->write("Upgrade: websocket\r\n"); - _socket->write("Connection: Upgrade\r\n"); - _socket->write("Sec-WebSocket-Accept: "); - - QCryptographicHash hash(QCryptographicHash::Sha1); - hash.addData(_requestHeaders.value("Sec-WebSocket-Key")); - hash.addData("258EAFA5-E914-47DA-95CA-C5AB0DC85B11"); // from WebSocket draft RFC - _socket->write(hash.result().toBase64()); - - if (protocol != 0) { - _socket->write("\r\nSec-WebSocket-Protocol: "); - _socket->write(protocol); - } - _socket->write("\r\n\r\n"); - - // connect socket, start reading frames - setWebSocketPaused(false); -} - -void HttpConnection::setWebSocketPaused (bool paused) -{ - if ((_webSocketPaused = paused)) { - _socket->disconnect(this, SLOT(readFrames())); - - } else { - connect(_socket, SIGNAL(readyRead()), SLOT(readFrames())); - readFrames(); - } -} - -void HttpConnection::closeWebSocket (quint16 reasonCode, const char* reason) -{ - if (reasonCode == NoReason) { - writeFrameHeader(ConnectionClose); - - } else { - int rlen = (reason == 0) ? 0 : qstrlen(reason); - writeFrameHeader(ConnectionClose, 2 + rlen); - _stream << reasonCode; - if (rlen > 0) { - _socket->write(reason); - } - } - _closeSent = true; -} - void HttpConnection::readRequest () { if (!_socket->canReadLine()) { @@ -245,7 +187,7 @@ void HttpConnection::readHeaders () QByteArray clength = _requestHeaders.value("Content-Length"); if (clength.isEmpty()) { - _parentManager->handleRequest(this, "", _requestUrl.path()); + _parentManager->handleRequest(this, _requestUrl.path()); } else { _requestContent.resize(clength.toInt()); @@ -276,8 +218,7 @@ void HttpConnection::readHeaders () } } -void HttpConnection::readContent () -{ +void HttpConnection::readContent() { int size = _requestContent.size(); if (_socket->bytesAvailable() < size) { return; @@ -285,201 +226,5 @@ void HttpConnection::readContent () _socket->read(_requestContent.data(), size); _socket->disconnect(this, SLOT(readContent())); - _parentManager->handleRequest(this, "", _requestUrl.path()); -} - -void HttpConnection::readFrames() -{ - // read as many messages as are available - while (maybeReadFrame()); -} - -void unget (QIODevice* device, quint32 value) { - device->ungetChar(value & 0xFF); - device->ungetChar((value >> 8) & 0xFF); - device->ungetChar((value >> 16) & 0xFF); - device->ungetChar(value >> 24); -} - -bool HttpConnection::maybeReadFrame () -{ - // make sure we have at least the first two bytes - qint64 available = _socket->bytesAvailable(); - if (available < 2 || _webSocketPaused) { - return false; - } - // read the first two, which tell us whether we need more for the length - quint8 finalOpcode, maskLength; - _stream >> finalOpcode; - _stream >> maskLength; - available -= 2; - - int byteLength = maskLength & 0x7F; - bool masked = (maskLength & 0x80) != 0; - int baseLength = (masked ? 4 : 0); - int length = -1; - if (byteLength == 127) { - if (available >= 8) { - quint64 longLength; - _stream >> longLength; - if (available >= baseLength + 8 + longLength) { - length = longLength; - } else { - unget(_socket, longLength & 0xFFFFFFFF); - unget(_socket, longLength >> 32); - } - } - } else if (byteLength == 126) { - if (available >= 2) { - quint16 shortLength; - _stream >> shortLength; - if (available >= baseLength + 2 + shortLength) { - length = shortLength; - } else { - _socket->ungetChar(shortLength & 0xFF); - _socket->ungetChar(shortLength >> 8); - } - } - } else if (available >= baseLength + byteLength) { - length = byteLength; - } - if (length == -1) { - _socket->ungetChar(maskLength); - _socket->ungetChar(finalOpcode); - return false; - } - - // read the mask and set it in the filter - quint32 mask = 0; - if (masked) { - _stream >> mask; - } - _unmasker->setMask(mask); - - // if not final, add to continuing message - FrameOpcode opcode = (FrameOpcode)(finalOpcode & 0x0F); - if ((finalOpcode & 0x80) == 0) { - if (opcode != ContinuationFrame) { - _continuingOpcode = opcode; - } - _continuingMessage += _unmasker->read(length); - return true; - } - - // if continuing, add to and read from buffer - QIODevice* device = _unmasker; - FrameOpcode copcode = opcode; - if (opcode == ContinuationFrame) { - _continuingMessage += _unmasker->read(length); - device = new QBuffer(&_continuingMessage, this); - device->open(QIODevice::ReadOnly); - copcode = _continuingOpcode; - } - - // act according to opcode - switch (copcode) { - case TextFrame: - emit webSocketMessageAvailable(device, length, true); - break; - - case BinaryFrame: - emit webSocketMessageAvailable(device, length, false); - break; - - case ConnectionClose: - // if this is not a response to our own close request, send a close reply - if (!_closeSent) { - closeWebSocket(GoingAway); - } - if (length >= 2) { - QDataStream stream(device); - quint16 reasonCode; - stream >> reasonCode; - emit webSocketClosed(reasonCode, device->read(length - 2)); - } else { - emit webSocketClosed(0, QByteArray()); - } - _socket->disconnectFromHost(); - break; - - case Ping: - // send the pong out immediately - writeFrameHeader(Pong, length, true); - _socket->write(device->read(length)); - break; - - case Pong: - qWarning() << "Got unsolicited WebSocket pong." << _address << device->read(length); - break; - - default: - qWarning() << "Received unknown WebSocket opcode." << _address << opcode << - device->read(length); - break; - } - - // clear the continuing message buffer - if (opcode == ContinuationFrame) { - _continuingMessage.clear(); - delete device; - } - - return true; -} - -void HttpConnection::writeFrameHeader (FrameOpcode opcode, int size, bool final) -{ - if (_closeSent) { - qWarning() << "Writing frame header after close message." << _address << opcode; - return; - } - _socket->putChar((final ? 0x80 : 0x0) | opcode); - if (size < 126) { - _socket->putChar(size); - - } else if (size < 65536) { - _socket->putChar(126); - _stream << (quint16)size; - - } else { - _socket->putChar(127); - _stream << (quint64)size; - } -} - -MaskFilter::MaskFilter (QIODevice* device, QObject* parent) : - QIODevice(parent), - _device(device) -{ - open(ReadOnly); -} - -void MaskFilter::setMask (quint32 mask) -{ - _mask[0] = (mask >> 24); - _mask[1] = (mask >> 16) & 0xFF; - _mask[2] = (mask >> 8) & 0xFF; - _mask[3] = mask & 0xFF; - _position = 0; - reset(); -} - -qint64 MaskFilter::bytesAvailable () const -{ - return _device->bytesAvailable() + QIODevice::bytesAvailable(); -} - -qint64 MaskFilter::readData (char* data, qint64 maxSize) -{ - qint64 bytes = _device->read(data, maxSize); - for (char* end = data + bytes; data < end; data++) { - *data ^= _mask[_position]; - _position = (_position + 1) % 4; - } - return bytes; -} - -qint64 MaskFilter::writeData (const char* data, qint64 maxSize) -{ - return _device->write(data, maxSize); + _parentManager->handleRequest(this, _requestUrl.path()); } diff --git a/libraries/embedded-webserver/src/HttpConnection.h b/libraries/embedded-webserver/src/HttpConnection.h index 346b436a8d..22ab0ddbb5 100755 --- a/libraries/embedded-webserver/src/HttpConnection.h +++ b/libraries/embedded-webserver/src/HttpConnection.h @@ -81,11 +81,6 @@ public: */ const QByteArray& requestContent () const { return _requestContent; } - /** - * Checks whether the request is asking to switch to a WebSocket. - */ - bool isWebSocketRequest (); - /** * Parses the request content as form data, returning a list of header/content pairs. */ @@ -97,28 +92,6 @@ public: void respond (const char* code, const QByteArray& content = QByteArray(), const char* contentType = "text/plain; charset=ISO-8859-1", const Headers& headers = Headers()); - - /** - * Switches to a WebSocket. - */ - void switchToWebSocket (const char* protocol = 0); - - /** - * Writes a header for a WebSocket message of the specified size. The body of the message - * should be written through the socket. - */ - void writeWebSocketHeader (int size) { writeFrameHeader(BinaryFrame, size); } - - /** - * Pauses or unpauses the WebSocket. A paused WebSocket buffers messages until unpaused. - */ - void setWebSocketPaused (bool paused); - - /** - * Closes the WebSocket. - */ - void closeWebSocket (quint16 reasonCode = NormalClosure, const char* reason = 0); - signals: /** @@ -148,36 +121,14 @@ protected slots: */ void readContent (); - /** - * Reads any incoming WebSocket frames. - */ - void readFrames (); - protected: - /** The available WebSocket frame opcodes. */ - enum FrameOpcode { ContinuationFrame, TextFrame, BinaryFrame, - ConnectionClose = 0x08, Ping, Pong }; - - /** - * Attempts to read a single WebSocket frame, returning true if successful. - */ - bool maybeReadFrame (); - - /** - * Writes a WebSocket frame header. - */ - void writeFrameHeader (FrameOpcode opcode, int size = 0, bool final = true); - /** The parent HTTP manager. */ HttpManager* _parentManager; /** The underlying socket. */ QTcpSocket* _socket; - - /** The mask filter for WebSocket frames. */ - MaskFilter* _unmasker; - + /** The data stream for writing to the socket. */ QDataStream _stream; @@ -198,64 +149,6 @@ protected: /** The content of the request. */ QByteArray _requestContent; - - /** The opcode for the WebSocket message being continued. */ - FrameOpcode _continuingOpcode; - - /** The WebSocket message being continued. */ - QByteArray _continuingMessage; - - /** Whether or not the WebSocket is paused (buffering messages for future processing). */ - bool _webSocketPaused; - - /** Whether or not we've sent a WebSocket close message. */ - bool _closeSent; -}; - -/** - * A filter device that applies a 32-bit mask. - */ -class MaskFilter : public QIODevice -{ - Q_OBJECT - -public: - - /** - * Creates a new masker to filter the supplied device. - */ - MaskFilter (QIODevice* device, QObject* parent = 0); - - /** - * Sets the mask to apply. - */ - void setMask (quint32 mask); - - /** - * Returns the number of bytes available to read. - */ - virtual qint64 bytesAvailable () const; - -protected: - - /** - * Reads masked data from the underlying device. - */ - virtual qint64 readData (char* data, qint64 maxSize); - - /** - * Writes masked data to the underlying device. - */ - virtual qint64 writeData (const char* data, qint64 maxSize); - - /** The underlying device. */ - QIODevice* _device; - - /** The current mask. */ - char _mask[4]; - - /** The current position within the mask. */ - int _position; }; #endif // HTTP_CONNECTION diff --git a/libraries/embedded-webserver/src/HttpManager.cpp b/libraries/embedded-webserver/src/HttpManager.cpp index 140529e74a..0d7f4b3286 100755 --- a/libraries/embedded-webserver/src/HttpManager.cpp +++ b/libraries/embedded-webserver/src/HttpManager.cpp @@ -10,40 +10,59 @@ // (https://github.com/ey6es/witgap/tree/master/src/cpp/server/http) // -#include -#include +#include +#include +#include +#include +#include #include "HttpConnection.h" #include "HttpManager.h" -void HttpSubrequestHandler::registerSubhandler (const QString& name, HttpRequestHandler* handler) { - _subhandlers.insert(name, handler); -} - -bool HttpSubrequestHandler::handleRequest ( - HttpConnection* connection, const QString& name, const QString& path) { - QString subpath = path; - if (subpath.startsWith('/')) { - subpath.remove(0, 1); +bool HttpManager::handleRequest(HttpConnection* connection, const QString& path) { + QString subPath = path; + + // remove any slash at the beginning of the path + if (subPath.startsWith('/')) { + subPath.remove(0, 1); } - QString subname; - int idx = subpath.indexOf('/'); - if (idx == -1) { - subname = subpath; - subpath = ""; + + QString filePath; + + // if the last thing is a trailing slash then we want to look for index file + if (subPath.endsWith('/') || subPath.size() == 0) { + QStringList possibleIndexFiles = QStringList() << "index.html" << "index.shtml"; + + foreach (const QString& possibleIndexFilename, possibleIndexFiles) { + if (QFileInfo(_documentRoot + subPath + possibleIndexFilename).exists()) { + filePath = _documentRoot + subPath + possibleIndexFilename; + break; + } + } + } else if (QFileInfo(_documentRoot + subPath).isFile()) { + filePath = _documentRoot + subPath; + } + + if (!filePath.isEmpty()) { + static QMimeDatabase mimeDatabase; + + qDebug() << "Serving file at" << filePath; + + QFile localFile(filePath); + localFile.open(QIODevice::ReadOnly); + + connection->respond("200 OK", localFile.readAll(), qPrintable(mimeDatabase.mimeTypeForFile(filePath).name())); } else { - subname = subpath.left(idx); - subpath = subpath.mid(idx + 1); - } - HttpRequestHandler* handler = _subhandlers.value(subname); - if (handler == 0 || !handler->handleRequest(connection, subname, subpath)) { + // respond with a 404 connection->respond("404 Not Found", "Resource not found."); } + return true; } -HttpManager::HttpManager(quint16 port, QObject* parent) : - QTcpServer(parent) { +HttpManager::HttpManager(quint16 port, const QString& documentRoot, QObject* parent) : + QTcpServer(parent), + _documentRoot(documentRoot) { // start listening on the passed port if (!listen(QHostAddress("0.0.0.0"), port)) { qDebug() << "Failed to open HTTP server socket:" << errorString(); @@ -54,7 +73,7 @@ HttpManager::HttpManager(quint16 port, QObject* parent) : connect(this, SIGNAL(newConnection()), SLOT(acceptConnections())); } -void HttpManager::acceptConnections () { +void HttpManager::acceptConnections() { QTcpSocket* socket; while ((socket = nextPendingConnection()) != 0) { new HttpConnection(socket, this); diff --git a/libraries/embedded-webserver/src/HttpManager.h b/libraries/embedded-webserver/src/HttpManager.h index 8751169271..5dbfefc8df 100755 --- a/libraries/embedded-webserver/src/HttpManager.h +++ b/libraries/embedded-webserver/src/HttpManager.h @@ -20,48 +20,10 @@ class HttpConnection; class HttpRequestHandler; -/** - * Interface for HTTP request handlers. - */ -class HttpRequestHandler -{ -public: - - /** - * Handles an HTTP request. - */ - virtual bool handleRequest ( - HttpConnection* connection, const QString& name, const QString& path) = 0; -}; - -/** - * Handles requests by forwarding them to subhandlers. - */ -class HttpSubrequestHandler : public HttpRequestHandler -{ -public: - - /** - * Registers a subhandler with the given name. - */ - void registerSubhandler (const QString& name, HttpRequestHandler* handler); - - /** - * Handles an HTTP request. - */ - virtual bool handleRequest ( - HttpConnection* connection, const QString& name, const QString& path); - -protected: - - /** Subhandlers mapped by name. */ - QHash _subhandlers; -}; - /** * Handles HTTP connections. */ -class HttpManager : public QTcpServer, public HttpSubrequestHandler +class HttpManager : public QTcpServer { Q_OBJECT @@ -70,7 +32,13 @@ public: /** * Initializes the manager. */ - HttpManager(quint16 port, QObject* parent = 0); + HttpManager(quint16 port, const QString& documentRoot, QObject* parent = 0); + + /** + * Handles an HTTP request. + */ + virtual bool handleRequest (HttpConnection* connection, const QString& path); + protected slots: @@ -78,6 +46,8 @@ protected slots: * Accepts all pending connections. */ void acceptConnections (); +protected: + QString _documentRoot; }; #endif // HTTP_MANAGER