From 40cdba203d9a27e5ba85b0823748cf46906d865e Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 17 Jan 2014 11:29:20 -0800 Subject: [PATCH] handle programmatic DS requests except for script creation --- domain-server/src/DomainServer.cpp | 255 ++++++++---------- domain-server/src/DomainServer.h | 4 +- .../embedded-webserver/src/HttpConnection.cpp | 12 +- .../embedded-webserver/src/HttpConnection.h | 5 +- .../embedded-webserver/src/HttpManager.cpp | 26 +- .../embedded-webserver/src/HttpManager.h | 21 +- 6 files changed, 159 insertions(+), 164 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index eb5801197a..4d15a98b33 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include #include @@ -33,7 +34,7 @@ const quint16 DOMAIN_SERVER_HTTP_PORT = 8080; DomainServer::DomainServer(int argc, char* argv[]) : QCoreApplication(argc, argv), - _httpManager(DOMAIN_SERVER_HTTP_PORT, QString("%1/resources/web/").arg(QCoreApplication::applicationDirPath())), + _httpManager(DOMAIN_SERVER_HTTP_PORT, QString("%1/resources/web/").arg(QCoreApplication::applicationDirPath()), this), _assignmentQueueMutex(), _assignmentQueue(), _staticAssignmentFile(QString("%1/config.ds").arg(QCoreApplication::applicationDirPath())), @@ -293,142 +294,122 @@ QJsonObject jsonObjectForNode(Node* node) { return nodeJson; } -//int DomainServer::civetwebRequestHandler(struct mg_connection *connection) { -// const struct mg_request_info* ri = mg_get_request_info(connection); -// -// const char RESPONSE_200[] = "HTTP/1.0 200 OK\r\n\r\n"; -// const char RESPONSE_400[] = "HTTP/1.0 400 Bad Request\r\n\r\n"; -// -// const char URI_ASSIGNMENT[] = "/assignment"; -// const char URI_NODE[] = "/node"; -// -// if (strcmp(ri->request_method, "GET") == 0) { -// if (strcmp(ri->uri, "/assignments.json") == 0) { -// // user is asking for json list of assignments -// -// // start with a 200 response -// mg_printf(connection, "%s", RESPONSE_200); -// -// // setup the JSON -// QJsonObject assignmentJSON; -// QJsonObject assignedNodesJSON; -// -// // enumerate the NodeList to find the assigned nodes -// foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) { -// if (node->getLinkedData()) { -// // add the node using the UUID as the key -// QString uuidString = uuidStringWithoutCurlyBraces(node->getUUID()); -// assignedNodesJSON[uuidString] = jsonObjectForNode(node.data()); -// } -// } -// -// assignmentJSON["fulfilled"] = assignedNodesJSON; -// -// QJsonObject queuedAssignmentsJSON; -// -// // add the queued but unfilled assignments to the json -// std::deque::iterator assignment = domainServerInstance->_assignmentQueue.begin(); -// -// while (assignment != domainServerInstance->_assignmentQueue.end()) { -// QJsonObject queuedAssignmentJSON; -// -// QString uuidString = uuidStringWithoutCurlyBraces((*assignment)->getUUID()); -// queuedAssignmentJSON[JSON_KEY_TYPE] = QString((*assignment)->getTypeName()); -// -// // if the assignment has a pool, add it -// if ((*assignment)->hasPool()) { -// queuedAssignmentJSON[JSON_KEY_POOL] = QString((*assignment)->getPool()); -// } -// -// // add this queued assignment to the JSON -// queuedAssignmentsJSON[uuidString] = queuedAssignmentJSON; -// -// // push forward the iterator to check the next assignment -// assignment++; -// } -// -// assignmentJSON["queued"] = queuedAssignmentsJSON; -// -// // print out the created JSON -// QJsonDocument assignmentDocument(assignmentJSON); -// mg_printf(connection, "%s", assignmentDocument.toJson().constData()); -// -// // we've processed this request -// return 1; -// } else if (strcmp(ri->uri, "/nodes.json") == 0) { -// // start with a 200 response -// mg_printf(connection, "%s", RESPONSE_200); -// -// // setup the JSON -// QJsonObject rootJSON; -// QJsonObject nodesJSON; -// -// // enumerate the NodeList to find the assigned nodes -// NodeList* nodeList = NodeList::getInstance(); -// -// foreach (const SharedNodePointer& node, nodeList->getNodeHash()) { -// // add the node using the UUID as the key -// QString uuidString = uuidStringWithoutCurlyBraces(node->getUUID()); -// nodesJSON[uuidString] = jsonObjectForNode(node.data()); -// } -// -// rootJSON["nodes"] = nodesJSON; -// -// // print out the created JSON -// QJsonDocument nodesDocument(rootJSON); -// mg_printf(connection, "%s", nodesDocument.toJson().constData()); -// -// // we've processed this request -// return 1; -// } -// -// // not processed, pass to document root -// return 0; -// } else if (strcmp(ri->request_method, "POST") == 0) { -// if (strcmp(ri->uri, URI_ASSIGNMENT) == 0) { -// // return a 200 -// mg_printf(connection, "%s", RESPONSE_200); -// // upload the file -// mg_upload(connection, "/tmp"); -// -// return 1; -// } -// -// return 0; -// } else if (strcmp(ri->request_method, "DELETE") == 0) { -// // this is a DELETE request -// -// // check if it is for an assignment -// if (memcmp(ri->uri, URI_NODE, strlen(URI_NODE)) == 0) { -// // pull the UUID from the url -// QUuid deleteUUID = QUuid(QString(ri->uri + strlen(URI_NODE) + sizeof('/'))); -// -// if (!deleteUUID.isNull()) { -// SharedNodePointer nodeToKill = NodeList::getInstance()->nodeWithUUID(deleteUUID); -// -// if (nodeToKill) { -// // start with a 200 response -// mg_printf(connection, "%s", RESPONSE_200); -// -// // we have a valid UUID and node - kill the node that has this assignment -// QMetaObject::invokeMethod(NodeList::getInstance(), "killNodeWithUUID", Q_ARG(const QUuid&, deleteUUID)); -// -// // successfully processed request -// return 1; -// } -// } -// } -// -// // request not processed - bad request -// mg_printf(connection, "%s", RESPONSE_400); -// -// // this was processed by civetweb -// return 1; -// } else { -// // have mongoose process this request from the document_root -// return 0; -// } -//} +bool DomainServer::handleHTTPRequest(HttpConnection* connection, const QString& path) { + const QString JSON_MIME_TYPE = "application/json"; + + const QString URI_ASSIGNMENT = "/assignment"; + const QString URI_NODE = "/node"; + + if (connection->requestOperation() == QNetworkAccessManager::GetOperation) { + if (path == "/assignments.json") { + // user is asking for json list of assignments + + // setup the JSON + QJsonObject assignmentJSON; + QJsonObject assignedNodesJSON; + + // enumerate the NodeList to find the assigned nodes + foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) { + if (node->getLinkedData()) { + // add the node using the UUID as the key + QString uuidString = uuidStringWithoutCurlyBraces(node->getUUID()); + assignedNodesJSON[uuidString] = jsonObjectForNode(node.data()); + } + } + + assignmentJSON["fulfilled"] = assignedNodesJSON; + + QJsonObject queuedAssignmentsJSON; + + // add the queued but unfilled assignments to the json + std::deque::iterator assignment = domainServerInstance->_assignmentQueue.begin(); + + while (assignment != domainServerInstance->_assignmentQueue.end()) { + QJsonObject queuedAssignmentJSON; + + QString uuidString = uuidStringWithoutCurlyBraces((*assignment)->getUUID()); + queuedAssignmentJSON[JSON_KEY_TYPE] = QString((*assignment)->getTypeName()); + + // if the assignment has a pool, add it + if ((*assignment)->hasPool()) { + queuedAssignmentJSON[JSON_KEY_POOL] = QString((*assignment)->getPool()); + } + + // add this queued assignment to the JSON + queuedAssignmentsJSON[uuidString] = queuedAssignmentJSON; + + // push forward the iterator to check the next assignment + assignment++; + } + + assignmentJSON["queued"] = queuedAssignmentsJSON; + + // print out the created JSON + QJsonDocument assignmentDocument(assignmentJSON); + connection->respond(HttpConnection::StatusCode200, assignmentDocument.toJson(), qPrintable(JSON_MIME_TYPE)); + + // we've processed this request + return true; + } else if (path == "/nodes.json") { + // setup the JSON + QJsonObject rootJSON; + QJsonObject nodesJSON; + + // enumerate the NodeList to find the assigned nodes + NodeList* nodeList = NodeList::getInstance(); + + foreach (const SharedNodePointer& node, nodeList->getNodeHash()) { + // add the node using the UUID as the key + QString uuidString = uuidStringWithoutCurlyBraces(node->getUUID()); + nodesJSON[uuidString] = jsonObjectForNode(node.data()); + } + + rootJSON["nodes"] = nodesJSON; + + // print out the created JSON + QJsonDocument nodesDocument(rootJSON); + + // send the response + connection->respond(HttpConnection::StatusCode200, nodesDocument.toJson(), qPrintable(JSON_MIME_TYPE)); + } + } else if (connection->requestOperation() == QNetworkAccessManager::PostOperation) { + if (path == URI_ASSIGNMENT) { + // this is a script upload - ask the HttpConnection to parse the form data + QList formData = connection->parseFormData(); + qDebug() << formData; + } + } else if (connection->requestOperation() == QNetworkAccessManager::DeleteOperation) { + if (path.startsWith(URI_NODE)) { + // this is a request to DELETE a node by UUID + + // pull the UUID from the url + QUuid deleteUUID = QUuid(path.mid(URI_NODE.size() + sizeof('/'))); + + if (!deleteUUID.isNull()) { + SharedNodePointer nodeToKill = NodeList::getInstance()->nodeWithUUID(deleteUUID); + + if (nodeToKill) { + // start with a 200 response + connection->respond(HttpConnection::StatusCode200); + + // we have a valid UUID and node - kill the node that has this assignment + QMetaObject::invokeMethod(NodeList::getInstance(), "killNodeWithUUID", Q_ARG(const QUuid&, deleteUUID)); + + // successfully processed request + return true; + } + } + + // bad request, couldn't pull a node ID + connection->respond(HttpConnection::StatusCode400); + + return true; + } + } + + // didn't process the request, let the HTTPManager try and handle + return false; +} const char ASSIGNMENT_SCRIPT_HOST_LOCATION[] = "resources/web/assignment"; diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index 56cb1225b8..96a33eb267 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -21,11 +21,13 @@ const int MAX_STATIC_ASSIGNMENT_FILE_ASSIGNMENTS = 1000; -class DomainServer : public QCoreApplication { +class DomainServer : public QCoreApplication, public HttpRequestHandler { Q_OBJECT public: DomainServer(int argc, char* argv[]); + bool handleHTTPRequest(HttpConnection* connection, const QString& path); + void exit(int retCode = 0); static void setDomainServerInstance(DomainServer* domainServer); diff --git a/libraries/embedded-webserver/src/HttpConnection.cpp b/libraries/embedded-webserver/src/HttpConnection.cpp index 82660c8473..0ea14ea82b 100755 --- a/libraries/embedded-webserver/src/HttpConnection.cpp +++ b/libraries/embedded-webserver/src/HttpConnection.cpp @@ -5,10 +5,6 @@ // Created by Stephen Birarda on 1/16/14. // Copyright (c) 2014 HighFidelity, Inc. All rights reserved. // -// Heavily based on Andrzej Kapolka's original HttpConnection class -// found from another one of his projects. -// (https://github.com/ey6es/witgap/tree/master/src/cpp/server/http) -// #include @@ -18,6 +14,10 @@ #include "HttpConnection.h" #include "HttpManager.h" +const char* HttpConnection::StatusCode200 = "200 OK"; +const char* HttpConnection::StatusCode400 = "400 Bad Request"; +const char* HttpConnection::StatusCode404 = "404 Not Found"; + HttpConnection::HttpConnection (QTcpSocket* socket, HttpManager* parentManager) : QObject(parentManager), _parentManager(parentManager), @@ -178,7 +178,7 @@ void HttpConnection::readHeaders() { QByteArray clength = _requestHeaders.value("Content-Length"); if (clength.isEmpty()) { - _parentManager->handleRequest(this, _requestUrl.path()); + _parentManager->handleHTTPRequest(this, _requestUrl.path()); } else { _requestContent.resize(clength.toInt()); @@ -217,5 +217,5 @@ void HttpConnection::readContent() { _socket->read(_requestContent.data(), size); _socket->disconnect(this, SLOT(readContent())); - _parentManager->handleRequest(this, _requestUrl.path()); + _parentManager->handleHTTPRequest(this, _requestUrl.path()); } diff --git a/libraries/embedded-webserver/src/HttpConnection.h b/libraries/embedded-webserver/src/HttpConnection.h index c791a93a07..23f210218f 100755 --- a/libraries/embedded-webserver/src/HttpConnection.h +++ b/libraries/embedded-webserver/src/HttpConnection.h @@ -7,7 +7,7 @@ // // Heavily based on Andrzej Kapolka's original HttpConnection class // found from another one of his projects. -// (https://github.com/ey6es/witgap/tree/master/src/cpp/server/http) +// https://github.com/ey6es/witgap/tree/master/src/cpp/server/http // #ifndef __hifi__HttpConnection__ @@ -38,6 +38,9 @@ class HttpConnection : public QObject { Q_OBJECT public: + static const char* StatusCode200; + static const char* StatusCode400; + static const char* StatusCode404; /// WebSocket close status codes. enum ReasonCode { NoReason = 0, NormalClosure = 1000, GoingAway = 1001 }; diff --git a/libraries/embedded-webserver/src/HttpManager.cpp b/libraries/embedded-webserver/src/HttpManager.cpp index 07dd376c8a..58c6574db9 100755 --- a/libraries/embedded-webserver/src/HttpManager.cpp +++ b/libraries/embedded-webserver/src/HttpManager.cpp @@ -5,10 +5,6 @@ // Created by Stephen Birarda on 1/16/14. // Copyright (c) 2014 HighFidelity, Inc. All rights reserved. // -// Heavily based on Andrzej Kapolka's original HttpManager class -// found from another one of his projects. -// (https://github.com/ey6es/witgap/tree/master/src/cpp/server/http) -// #include #include @@ -19,7 +15,15 @@ #include "HttpConnection.h" #include "HttpManager.h" -bool HttpManager::handleRequest(HttpConnection* connection, const QString& path) { +bool HttpManager::handleHTTPRequest(HttpConnection* connection, const QString& path) { + if (_requestHandler && _requestHandler->handleHTTPRequest(connection, path)) { + // this request was handled by our _requestHandler object + // so we don't need to attempt to do so in the document root + return true; + } + + // check to see if there is a file to serve from the document root for this path + QString subPath = path; // remove any slash at the beginning of the path @@ -45,6 +49,8 @@ bool HttpManager::handleRequest(HttpConnection* connection, const QString& path) if (!filePath.isEmpty()) { + // file exists, serve it + static QMimeDatabase mimeDatabase; QFile localFile(filePath); @@ -93,18 +99,20 @@ bool HttpManager::handleRequest(HttpConnection* connection, const QString& path) } } - connection->respond("200 OK", localFileString.toLocal8Bit(), qPrintable(mimeDatabase.mimeTypeForFile(filePath).name())); + connection->respond(HttpConnection::StatusCode200, localFileString.toLocal8Bit(), qPrintable(mimeDatabase.mimeTypeForFile(filePath).name())); } else { // respond with a 404 - connection->respond("404 Not Found", "Resource not found."); + connection->respond(HttpConnection::StatusCode404, "Resource not found."); } return true; } -HttpManager::HttpManager(quint16 port, const QString& documentRoot, QObject* parent) : +HttpManager::HttpManager(quint16 port, const QString& documentRoot, HttpRequestHandler* requestHandler, QObject* parent) : QTcpServer(parent), - _documentRoot(documentRoot) { + _documentRoot(documentRoot), + _requestHandler(requestHandler) +{ // start listening on the passed port if (!listen(QHostAddress("0.0.0.0"), port)) { qDebug() << "Failed to open HTTP server socket:" << errorString(); diff --git a/libraries/embedded-webserver/src/HttpManager.h b/libraries/embedded-webserver/src/HttpManager.h index 1c5cb7ee65..80a2c5ba2c 100755 --- a/libraries/embedded-webserver/src/HttpManager.h +++ b/libraries/embedded-webserver/src/HttpManager.h @@ -7,36 +7,37 @@ // // Heavily based on Andrzej Kapolka's original HttpManager class // found from another one of his projects. -// (https://github.com/ey6es/witgap/tree/master/src/cpp/server/http) +// https://github.com/ey6es/witgap/tree/master/src/cpp/server/http // #ifndef __hifi__HttpManager__ #define __hifi__HttpManager__ -#include -#include #include class HttpConnection; -class HttpRequestHandler; + +class HttpRequestHandler { +public: + /// Handles an HTTP request. + virtual bool handleHTTPRequest(HttpConnection* connection, const QString& path) = 0; +}; /// Handles HTTP connections -class HttpManager : public QTcpServer { +class HttpManager : public QTcpServer, public HttpRequestHandler { Q_OBJECT - public: - /// Initializes the manager. - HttpManager(quint16 port, const QString& documentRoot, QObject* parent = 0); + HttpManager(quint16 port, const QString& documentRoot, HttpRequestHandler* requestHandler = NULL, QObject* parent = 0); - /// Handles an HTTP request. - virtual bool handleRequest (HttpConnection* connection, const QString& path); + bool handleHTTPRequest(HttpConnection* connection, const QString& path); protected slots: /// Accepts all pending connections void acceptConnections(); protected: QString _documentRoot; + HttpRequestHandler* _requestHandler; }; #endif /* defined(__hifi__HttpManager__) */