Merge pull request #1579 from birarda/master

replace civetweb with a QTcpSocket subclass
This commit is contained in:
ZappoMan 2014-01-20 09:05:41 -08:00
commit 3945bef0e9
24 changed files with 927 additions and 7782 deletions

View file

@ -21,7 +21,7 @@ qt5_use_modules(${TARGET_NAME} Network Script Widgets)
include(${MACRO_DIR}/IncludeGLM.cmake)
include_glm(${TARGET_NAME} ${ROOT_DIR})
# link in the shared library
# link in the shared libraries
include(${MACRO_DIR}/LinkHifiLibrary.cmake)
link_hifi_library(shared ${TARGET_NAME} ${ROOT_DIR})
link_hifi_library(audio ${TARGET_NAME} ${ROOT_DIR})
@ -34,9 +34,7 @@ link_hifi_library(octree-server ${TARGET_NAME} ${ROOT_DIR})
link_hifi_library(particle-server ${TARGET_NAME} ${ROOT_DIR})
link_hifi_library(voxel-server ${TARGET_NAME} ${ROOT_DIR})
link_hifi_library(script-engine ${TARGET_NAME} ${ROOT_DIR})
#testing
include_directories(${ROOT_DIR}/externals/civetweb/include)
link_hifi_library(embedded-webserver ${TARGET_NAME} ${ROOT_DIR})
if (UNIX)
target_link_libraries(${TARGET_NAME} ${CMAKE_DL_LIBS})

View file

@ -12,14 +12,13 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/../cmake
include(${MACRO_DIR}/IncludeGLM.cmake)
include_glm(${TARGET_NAME} ${ROOT_DIR})
find_package(Qt5Network REQUIRED)
include(${MACRO_DIR}/SetupHifiProject.cmake)
# grab cJSON and civetweb sources to pass as OPTIONAL_SRCS
FILE(GLOB OPTIONAL_SRCS ${ROOT_DIR}/externals/civetweb/src/*)
setup_hifi_project(${TARGET_NAME} TRUE)
setup_hifi_project(${TARGET_NAME} TRUE ${OPTIONAL_SRCS})
include_directories(SYSTEM ${ROOT_DIR}/externals/civetweb/include)
qt5_use_modules(${TARGET_NAME} Network)
# remove and then copy the files for the webserver
add_custom_command(TARGET ${TARGET_NAME} POST_BUILD
@ -33,12 +32,8 @@ add_custom_command(TARGET ${TARGET_NAME} POST_BUILD
# link the shared hifi library
include(${MACRO_DIR}/LinkHifiLibrary.cmake)
link_hifi_library(shared ${TARGET_NAME} ${ROOT_DIR})
# link dl library on UNIX for civetweb
if (UNIX AND NOT APPLE)
target_link_libraries(${TARGET_NAME} ${CMAKE_DL_LIBS})
endif (UNIX AND NOT APPLE)
link_hifi_library(embedded-webserver ${TARGET_NAME} ${ROOT_DIR})
IF (WIN32)
target_link_libraries(${TARGET_NAME} Winmm)
ENDIF(WIN32)
target_link_libraries(${TARGET_NAME} Winmm)
ENDIF(WIN32)

View file

@ -5,7 +5,7 @@
<link href='css/style.css' rel='stylesheet'>
</head>
<body>
<pre id='editor' style='font-size: 14px;'><!--#include "placeholder.js"--></pre>
<pre id='editor' style='font-size: 14px;'><!--#include file="placeholder.js"--></object></pre>
<script src='../js/jquery-2.0.3.min.js'></script>
<script src='js/ace/ace.js' type='text/javascript'></script>
<script src='js/assignment.js' type='text/javascript'></script>

View file

@ -19,7 +19,7 @@ $(document).ready(function(){
+ 'Content-type: application/javascript\r\n\r\n'
// add the script
+ script + '\r\n'
+ '--' + boundary + '--';
+ '--' + boundary + '--\r\n';
var headers = {};
if ($('#instance-field input').val()) {

View file

@ -1,4 +1,4 @@
<!--#include file="header.shtml"-->
<!--#include file="header.html"-->
<div id="nodes-lead" class="table-lead"><h3>Nodes</h3><div class="lead-line"></div></div>
<table id="nodes-table" class="table table-striped">
<thead>
@ -27,6 +27,6 @@
<tbody>
</tbody>
</table>
<!--#include file="footer.shtml"-->
<!--#include file="footer.html"-->
<script src='js/tables.js'></script>
<!--#include file="page-end.shtml"-->
<!--#include file="page-end.html"-->

View file

@ -13,6 +13,7 @@
#include <QtCore/QStringList>
#include <QtCore/QTimer>
#include <HTTPConnection.h>
#include <PacketHeaders.h>
#include <SharedUtil.h>
#include <UUID.h>
@ -27,10 +28,11 @@ void signalhandler(int sig){
}
}
DomainServer* DomainServer::domainServerInstance = NULL;
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()), this),
_assignmentQueueMutex(),
_assignmentQueue(),
_staticAssignmentFile(QString("%1/config.ds").arg(QCoreApplication::applicationDirPath())),
@ -39,8 +41,6 @@ DomainServer::DomainServer(int argc, char* argv[]) :
_metavoxelServerConfig(NULL),
_hasCompletedRestartHold(false)
{
DomainServer::setDomainServerInstance(this);
signal(SIGINT, signalhandler);
const char CUSTOM_PORT_OPTION[] = "-p";
@ -57,25 +57,7 @@ DomainServer::DomainServer(int argc, char* argv[]) :
const char METAVOXEL_CONFIG_OPTION[] = "--metavoxelServerConfig";
_metavoxelServerConfig = getCmdOption(argc, (const char**)argv, METAVOXEL_CONFIG_OPTION);
// setup the mongoose web server
struct mg_callbacks callbacks = {};
QString documentRootString = QString("%1/resources/web").arg(QCoreApplication::applicationDirPath());
char* documentRoot = new char[documentRootString.size() + 1];
strcpy(documentRoot, documentRootString.toLocal8Bit().constData());
// list of options. Last element must be NULL.
const char* options[] = {"listening_ports", "8080",
"document_root", documentRoot, NULL};
callbacks.begin_request = civetwebRequestHandler;
callbacks.upload = civetwebUploadHandler;
// Start the web server.
mg_start(&callbacks, NULL, options);
connect(nodeList, SIGNAL(nodeKilled(SharedNodePointer)), this, SLOT(nodeKilled(SharedNodePointer)));
if (!_staticAssignmentFile.exists() || _voxelServerConfig) {
@ -104,8 +86,6 @@ DomainServer::DomainServer(int argc, char* argv[]) :
QTimer::singleShot(RESTART_HOLD_TIME_MSECS, this, SLOT(addStaticAssignmentsBackToQueueAfterRestart()));
connect(this, SIGNAL(aboutToQuit()), SLOT(cleanup()));
delete[] documentRoot;
}
void DomainServer::readAvailableDatagrams() {
@ -258,10 +238,6 @@ void DomainServer::readAvailableDatagrams() {
}
}
void DomainServer::setDomainServerInstance(DomainServer* domainServer) {
domainServerInstance = domainServer;
}
QJsonObject jsonForSocket(const HifiSockAddr& socket) {
QJsonObject socketJSON;
@ -299,26 +275,20 @@ 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) {
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
// 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()) {
@ -327,150 +297,141 @@ int DomainServer::civetwebRequestHandler(struct mg_connection *connection) {
assignedNodesJSON[uuidString] = jsonObjectForNode(node.data());
}
}
assignmentJSON["fulfilled"] = assignedNodesJSON;
QJsonObject queuedAssignmentsJSON;
// add the queued but unfilled assignments to the json
std::deque<Assignment*>::iterator assignment = domainServerInstance->_assignmentQueue.begin();
while (assignment != domainServerInstance->_assignmentQueue.end()) {
std::deque<Assignment*>::iterator assignment = _assignmentQueue.begin();
while (assignment != _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());
connection->respond(HTTPConnection::StatusCode200, assignmentDocument.toJson(), qPrintable(JSON_MIME_TYPE));
// 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);
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);
mg_printf(connection, "%s", nodesDocument.toJson().constData());
// we've processed this request
return 1;
// send the response
connection->respond(HTTPConnection::StatusCode200, nodesDocument.toJson(), qPrintable(JSON_MIME_TYPE));
}
// 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;
} 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> formData = connection->parseFormData();
// create an assignment for this saved script, for now make it local only
Assignment* scriptAssignment = new Assignment(Assignment::CreateCommand,
Assignment::AgentType,
NULL,
Assignment::LocalLocation);
// check how many instances of this assignment the user wants by checking the ASSIGNMENT-INSTANCES header
const QString ASSIGNMENT_INSTANCES_HEADER = "ASSIGNMENT-INSTANCES";
QByteArray assignmentInstancesValue = connection->requestHeaders().value(ASSIGNMENT_INSTANCES_HEADER.toLocal8Bit());
if (!assignmentInstancesValue.isEmpty()) {
// the user has requested a specific number of instances
// so set that on the created assignment
int numInstances = assignmentInstancesValue.toInt();
if (numInstances > 0) {
qDebug() << numInstances;
scriptAssignment->setNumberOfInstances(numInstances);
}
}
const char ASSIGNMENT_SCRIPT_HOST_LOCATION[] = "resources/web/assignment";
QString newPath(ASSIGNMENT_SCRIPT_HOST_LOCATION);
newPath += "/";
// append the UUID for this script as the new filename, remove the curly braces
newPath += uuidStringWithoutCurlyBraces(scriptAssignment->getUUID());
// create a file with the GUID of the assignment in the script host locaiton
QFile scriptFile(newPath);
scriptFile.open(QIODevice::WriteOnly);
scriptFile.write(formData[0].second);
qDebug("Saved a script for assignment at %s", qPrintable(newPath));
// respond with a 200 code for successful upload
connection->respond(HTTPConnection::StatusCode200);
// add the script assigment to the assignment queue
// lock the assignment queue mutex since we're operating on a different thread than DS main
_assignmentQueueMutex.lock();
_assignmentQueue.push_back(scriptAssignment);
_assignmentQueueMutex.unlock();
}
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) {
} 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(QString(ri->uri + strlen(URI_NODE) + sizeof('/')));
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
mg_printf(connection, "%s", RESPONSE_200);
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 1;
return true;
}
}
// bad request, couldn't pull a node ID
connection->respond(HTTPConnection::StatusCode400);
return true;
}
// 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;
}
}
const char ASSIGNMENT_SCRIPT_HOST_LOCATION[] = "resources/web/assignment";
void DomainServer::civetwebUploadHandler(struct mg_connection *connection, const char *path) {
// create an assignment for this saved script, for now make it local only
Assignment* scriptAssignment = new Assignment(Assignment::CreateCommand,
Assignment::AgentType,
NULL,
Assignment::LocalLocation);
// check how many instances of this assignment the user wants by checking the ASSIGNMENT-INSTANCES header
const char ASSIGNMENT_INSTANCES_HTTP_HEADER[] = "ASSIGNMENT-INSTANCES";
const char* requestInstancesHeader = mg_get_header(connection, ASSIGNMENT_INSTANCES_HTTP_HEADER);
if (requestInstancesHeader) {
// the user has requested a number of instances greater than 1
// so set that on the created assignment
scriptAssignment->setNumberOfInstances(atoi(requestInstancesHeader));
}
QString newPath(ASSIGNMENT_SCRIPT_HOST_LOCATION);
newPath += "/";
// append the UUID for this script as the new filename, remove the curly braces
newPath += uuidStringWithoutCurlyBraces(scriptAssignment->getUUID());
// rename the saved script to the GUID of the assignment and move it to the script host locaiton
rename(path, newPath.toLocal8Bit().constData());
qDebug("Saved a script for assignment at %s", newPath.toLocal8Bit().constData());
// add the script assigment to the assignment queue
// lock the assignment queue mutex since we're operating on a different thread than DS main
domainServerInstance->_assignmentQueueMutex.lock();
domainServerInstance->_assignmentQueue.push_back(scriptAssignment);
domainServerInstance->_assignmentQueueMutex.unlock();
// didn't process the request, let the HTTPManager try and handle
return false;
}
void DomainServer::addReleasedAssignmentBackToQueue(Assignment* releasedAssignment) {

View file

@ -16,31 +16,25 @@
#include <QtCore/QMutex>
#include <Assignment.h>
#include <HTTPManager.h>
#include <NodeList.h>
#include "civetweb.h"
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);
public slots:
/// Called by NodeList to inform us that a node has been killed.
void nodeKilled(SharedNodePointer node);
private:
static int civetwebRequestHandler(struct mg_connection *connection);
static void civetwebUploadHandler(struct mg_connection *connection, const char *path);
static DomainServer* domainServerInstance;
private:
void prepopulateStaticAssignmentFile();
Assignment* matchingStaticAssignmentForCheckIn(const QUuid& checkInUUID, NODE_TYPE nodeType);
Assignment* deployableAssignmentForRequest(Assignment& requestAssignment);
@ -52,6 +46,8 @@ private:
unsigned char* addNodeToBroadcastPacket(unsigned char* currentPosition, Node* nodeToAdd);
HTTPManager _HTTPManager;
QMutex _assignmentQueueMutex;
std::deque<Assignment*> _assignmentQueue;

View file

@ -1,98 +0,0 @@
ALL LICENSES
=====
This document includes several copyright licenses for different
aspects of the software. Not all licenses may apply depending
on the features chosen.
Civetweb License
-----
### Included with all features.
> Copyright (c) 2004-2013 Sergey Lyubka
>
> Copyright (c) 2013 No Face Press, LLC (Thomas Davis)
>
> Copyright (c) 2013 F-Secure Corporation
>
> Permission is hereby granted, free of charge, to any person obtaining a copy
> of this software and associated documentation files (the "Software"), to deal
> in the Software without restriction, including without limitation the rights
> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
> copies of the Software, and to permit persons to whom the Software is
> furnished to do so, subject to the following conditions:
>
> The above copyright notice and this permission notice shall be included in
> all copies or substantial portions of the Software.
>
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
> THE SOFTWARE.
Lua License
------
### Included only if built with Lua support.
http://www.lua.org/license.html
> Copyright <20> 1994-2013 Lua.org, PUC-Rio.
> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
>
> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
>
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
SQLite3 License
------
### Included only if built with Lua support.
http://www.sqlite.org/copyright.html
> 2001 September 15
>
> The author disclaims copyright to this source code. In place of
> a legal notice, here is a blessing:
>
> May you do good and not evil.
> May you find forgiveness for yourself and forgive others.
> May you share freely, never taking more than you give.
lsqlite3 License
------
### Included only if built with Lua support.
> lsqlite3
> Copyright (C) 2002-2007 Tiago Dionizio, Doug Currie
> All rights reserved.
> Author : Tiago Dionizio <tiago.dionizio@ist.utl.pt>
> Author : Doug Currie <doug.currie@alum.mit.edu>
> Library : lsqlite3 - a SQLite 3 database binding for Lua 5
>
> Permission is hereby granted, free of charge, to any person obtaining
> a copy of this software and associated documentation files (the
> "Software"), to deal in the Software without restriction, including
> without limitation the rights to use, copy, modify, merge, publish,
> distribute, sublicense, and/or sell copies of the Software, and to
> permit persons to whom the Software is furnished to do so, subject to
> the following conditions:
>
> The above copyright notice and this permission notice shall be
> included in all copies or substantial portions of the Software.
>
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
> EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -1,488 +0,0 @@
/* Copyright (c) 2004-2013 Sergey Lyubka
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef CIVETWEB_HEADER_INCLUDED
#define CIVETWEB_HEADER_INCLUDED
#ifndef CIVETWEB_VERSION
#define CIVETWEB_VERSION "1.6"
#endif
#include <stdio.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
struct mg_context; /* Handle for the HTTP service itself */
struct mg_connection; /* Handle for the individual connection */
/* This structure contains information about the HTTP request. */
struct mg_request_info {
const char *request_method; /* "GET", "POST", etc */
const char *uri; /* URL-decoded URI */
const char *http_version; /* E.g. "1.0", "1.1" */
const char *query_string; /* URL part after '?', not including '?', or
NULL */
const char *remote_user; /* Authenticated user, or NULL if no auth
used */
long remote_ip; /* Client's IP address */
int remote_port; /* Client's port */
int is_ssl; /* 1 if SSL-ed, 0 if not */
void *user_data; /* User data pointer passed to mg_start() */
void *conn_data; /* Connection-specific user data */
int num_headers; /* Number of HTTP headers */
struct mg_header {
const char *name; /* HTTP header name */
const char *value; /* HTTP header value */
} http_headers[64]; /* Maximum 64 headers */
};
/* This structure needs to be passed to mg_start(), to let civetweb know
which callbacks to invoke. For detailed description, see
https://github.com/sunsetbrew/civetweb/blob/master/docs/UserManual.md */
struct mg_callbacks {
/* Called when civetweb has received new HTTP request.
If callback returns non-zero,
callback must process the request by sending valid HTTP headers and
body, and civetweb will not do any further processing.
If callback returns 0, civetweb processes the request itself. In this
case, callback must not send any data to the client. */
int (*begin_request)(struct mg_connection *);
/* Called when civetweb has finished processing request. */
void (*end_request)(const struct mg_connection *, int reply_status_code);
/* Called when civetweb is about to log a message. If callback returns
non-zero, civetweb does not log anything. */
int (*log_message)(const struct mg_connection *, const char *message);
/* Called when civetweb initializes SSL library. */
int (*init_ssl)(void *ssl_context, void *user_data);
/* Called when websocket request is received, before websocket handshake.
If callback returns 0, civetweb proceeds with handshake, otherwise
cinnection is closed immediately. */
int (*websocket_connect)(const struct mg_connection *);
/* Called when websocket handshake is successfully completed, and
connection is ready for data exchange. */
void (*websocket_ready)(struct mg_connection *);
/* Called when data frame has been received from the client.
Parameters:
bits: first byte of the websocket frame, see websocket RFC at
http://tools.ietf.org/html/rfc6455, section 5.2
data, data_len: payload, with mask (if any) already applied.
Return value:
non-0: keep this websocket connection opened.
0: close this websocket connection. */
int (*websocket_data)(struct mg_connection *, int bits,
char *data, size_t data_len);
/* Called when civetweb is closing a connection. The per-context mutex is
locked when this is invoked. This is primarily useful for noting when
a websocket is closing and removing it from any application-maintained
list of clients. */
void (*connection_close)(struct mg_connection *);
/* Called when civetweb tries to open a file. Used to intercept file open
calls, and serve file data from memory instead.
Parameters:
path: Full path to the file to open.
data_len: Placeholder for the file size, if file is served from
memory.
Return value:
NULL: do not serve file from memory, proceed with normal file open.
non-NULL: pointer to the file contents in memory. data_len must be
initilized with the size of the memory block. */
const char * (*open_file)(const struct mg_connection *,
const char *path, size_t *data_len);
/* Called when civetweb is about to serve Lua server page (.lp file), if
Lua support is enabled.
Parameters:
lua_context: "lua_State *" pointer. */
void (*init_lua)(struct mg_connection *, void *lua_context);
/* Called when civetweb has uploaded a file to a temporary directory as a
result of mg_upload() call.
Parameters:
file_file: full path name to the uploaded file. */
void (*upload)(struct mg_connection *, const char *file_name);
/* Called when civetweb is about to send HTTP error to the client.
Implementing this callback allows to create custom error pages.
Parameters:
status: HTTP error status code. */
int (*http_error)(struct mg_connection *, int status);
};
/* Start web server.
Parameters:
callbacks: mg_callbacks structure with user-defined callbacks.
options: NULL terminated list of option_name, option_value pairs that
specify Civetweb configuration parameters.
Side-effects: on UNIX, ignores SIGCHLD and SIGPIPE signals. If custom
processing is required for these, signal handlers must be set up
after calling mg_start().
Example:
const char *options[] = {
"document_root", "/var/www",
"listening_ports", "80,443s",
NULL
};
struct mg_context *ctx = mg_start(&my_func, NULL, options);
Refer to https://github.com/sunsetbrew/civetweb/blob/master/docs/UserManual.md
for the list of valid option and their possible values.
Return:
web server context, or NULL on error. */
struct mg_context *mg_start(const struct mg_callbacks *callbacks,
void *user_data,
const char **configuration_options);
/* Stop the web server.
Must be called last, when an application wants to stop the web server and
release all associated resources. This function blocks until all Civetweb
threads are stopped. Context pointer becomes invalid. */
void mg_stop(struct mg_context *);
/* mg_request_handler
Called when a new request comes in. This callback is URI based
and configured with mg_set_request_handler().
Parameters:
conn: current connection information.
cbdata: the callback data configured with mg_set_request_handler().
Returns:
0: the handler could not handle the request, so fall through.
1: the handler processed the request. */
typedef int (* mg_request_handler)(struct mg_connection *conn, void *cbdata);
/* mg_set_request_handler
Sets or removes a URI mapping for a request handler.
URI's are ordered and prefixed URI's are supported. For example,
consider two URIs: /a/b and /a
/a matches /a
/a/b matches /a/b
/a/c matches /a
Parameters:
ctx: server context
uri: the URI to configure
handler: the callback handler to use when the URI is requested.
If NULL, the URI will be removed.
cbdata: the callback data to give to the handler when it s requested. */
void mg_set_request_handler(struct mg_context *ctx, const char *uri, mg_request_handler handler, void *cbdata);
/* Get the value of particular configuration parameter.
The value returned is read-only. Civetweb does not allow changing
configuration at run time.
If given parameter name is not valid, NULL is returned. For valid
names, return value is guaranteed to be non-NULL. If parameter is not
set, zero-length string is returned. */
const char *mg_get_option(const struct mg_context *ctx, const char *name);
/* Return array of strings that represent valid configuration options.
For each option, option name and default value is returned, i.e. the
number of entries in the array equals to number_of_options x 2.
Array is NULL terminated. */
const char **mg_get_valid_option_names(void);
/* Add, edit or delete the entry in the passwords file.
This function allows an application to manipulate .htpasswd files on the
fly by adding, deleting and changing user records. This is one of the
several ways of implementing authentication on the server side. For another,
cookie-based way please refer to the examples/chat in the source tree.
If password is not NULL, entry is added (or modified if already exists).
If password is NULL, entry is deleted.
Return:
1 on success, 0 on error. */
int mg_modify_passwords_file(const char *passwords_file_name,
const char *domain,
const char *user,
const char *password);
/* Return information associated with the request. */
struct mg_request_info *mg_get_request_info(struct mg_connection *);
/* Send data to the client.
Return:
0 when the connection has been closed
-1 on error
>0 number of bytes written on success */
int mg_write(struct mg_connection *, const void *buf, size_t len);
/* Send data to a websocket client wrapped in a websocket frame. Uses mg_lock
to ensure that the transmission is not interrupted, i.e., when the
application is proactively communicating and responding to a request
simultaneously.
Send data to a websocket client wrapped in a websocket frame.
This function is available when civetweb is compiled with -DUSE_WEBSOCKET
Return:
0 when the connection has been closed
-1 on error
>0 number of bytes written on success */
int mg_websocket_write(struct mg_connection* conn, int opcode,
const char *data, size_t data_len);
/* Blocks until unique access is obtained to this connection. Intended for use
with websockets only.
Invoke this before mg_write or mg_printf when communicating with a
websocket if your code has server-initiated communication as well as
communication in direct response to a message. */
void mg_lock(struct mg_connection* conn);
void mg_unlock(struct mg_connection* conn);
/* Opcodes, from http://tools.ietf.org/html/rfc6455 */
enum {
WEBSOCKET_OPCODE_CONTINUATION = 0x0,
WEBSOCKET_OPCODE_TEXT = 0x1,
WEBSOCKET_OPCODE_BINARY = 0x2,
WEBSOCKET_OPCODE_CONNECTION_CLOSE = 0x8,
WEBSOCKET_OPCODE_PING = 0x9,
WEBSOCKET_OPCODE_PONG = 0xa
};
/* Macros for enabling compiler-specific checks for printf-like arguments. */
#undef PRINTF_FORMAT_STRING
#if defined(_MSC_VER) && _MSC_VER >= 1400
#include <sal.h>
#if defined(_MSC_VER) && _MSC_VER > 1400
#define PRINTF_FORMAT_STRING(s) _Printf_format_string_ s
#else
#define PRINTF_FORMAT_STRING(s) __format_string s
#endif
#else
#define PRINTF_FORMAT_STRING(s) s
#endif
#ifdef __GNUC__
#define PRINTF_ARGS(x, y) __attribute__((format(printf, x, y)))
#else
#define PRINTF_ARGS(x, y)
#endif
/* Send data to the client using printf() semantics.
Works exactly like mg_write(), but allows to do message formatting. */
int mg_printf(struct mg_connection *,
PRINTF_FORMAT_STRING(const char *fmt), ...) PRINTF_ARGS(2, 3);
/* Send contents of the entire file together with HTTP headers. */
void mg_send_file(struct mg_connection *conn, const char *path);
/* Read data from the remote end, return number of bytes read.
Return:
0 connection has been closed by peer. No more data could be read.
< 0 read error. No more data could be read from the connection.
> 0 number of bytes read into the buffer. */
int mg_read(struct mg_connection *, void *buf, size_t len);
/* Get the value of particular HTTP header.
This is a helper function. It traverses request_info->http_headers array,
and if the header is present in the array, returns its value. If it is
not present, NULL is returned. */
const char *mg_get_header(const struct mg_connection *, const char *name);
/* Get a value of particular form variable.
Parameters:
data: pointer to form-uri-encoded buffer. This could be either POST data,
or request_info.query_string.
data_len: length of the encoded data.
var_name: variable name to decode from the buffer
dst: destination buffer for the decoded variable
dst_len: length of the destination buffer
Return:
On success, length of the decoded variable.
On error:
-1 (variable not found).
-2 (destination buffer is NULL, zero length or too small to hold the
decoded variable).
Destination buffer is guaranteed to be '\0' - terminated if it is not
NULL or zero length. */
int mg_get_var(const char *data, size_t data_len,
const char *var_name, char *dst, size_t dst_len);
/* Get a value of particular form variable.
Parameters:
data: pointer to form-uri-encoded buffer. This could be either POST data,
or request_info.query_string.
data_len: length of the encoded data.
var_name: variable name to decode from the buffer
dst: destination buffer for the decoded variable
dst_len: length of the destination buffer
occurrence: which occurrence of the variable, 0 is the first, 1 the
second...
this makes it possible to parse a query like
b=x&a=y&a=z which will have occurrence values b:0, a:0 and a:1
Return:
On success, length of the decoded variable.
On error:
-1 (variable not found).
-2 (destination buffer is NULL, zero length or too small to hold the
decoded variable).
Destination buffer is guaranteed to be '\0' - terminated if it is not
NULL or zero length. */
int mg_get_var2(const char *data, size_t data_len,
const char *var_name, char *dst, size_t dst_len, size_t occurrence);
/* Fetch value of certain cookie variable into the destination buffer.
Destination buffer is guaranteed to be '\0' - terminated. In case of
failure, dst[0] == '\0'. Note that RFC allows many occurrences of the same
parameter. This function returns only first occurrence.
Return:
On success, value length.
On error:
-1 (either "Cookie:" header is not present at all or the requested
parameter is not found).
-2 (destination buffer is NULL, zero length or too small to hold the
value). */
int mg_get_cookie(const char *cookie, const char *var_name,
char *buf, size_t buf_len);
/* Download data from the remote web server.
host: host name to connect to, e.g. "foo.com", or "10.12.40.1".
port: port number, e.g. 80.
use_ssl: wether to use SSL connection.
error_buffer, error_buffer_size: error message placeholder.
request_fmt,...: HTTP request.
Return:
On success, valid pointer to the new connection, suitable for mg_read().
On error, NULL. error_buffer contains error message.
Example:
char ebuf[100];
struct mg_connection *conn;
conn = mg_download("google.com", 80, 0, ebuf, sizeof(ebuf),
"%s", "GET / HTTP/1.0\r\nHost: google.com\r\n\r\n");
*/
struct mg_connection *mg_download(const char *host, int port, int use_ssl,
char *error_buffer, size_t error_buffer_size,
PRINTF_FORMAT_STRING(const char *request_fmt),
...) PRINTF_ARGS(6, 7);
/* Close the connection opened by mg_download(). */
void mg_close_connection(struct mg_connection *conn);
/* File upload functionality. Each uploaded file gets saved into a temporary
file and MG_UPLOAD event is sent.
Return number of uploaded files. */
int mg_upload(struct mg_connection *conn, const char *destination_dir);
/* Convenience function -- create detached thread.
Return: 0 on success, non-0 on error. */
typedef void * (*mg_thread_func_t)(void *);
int mg_start_thread(mg_thread_func_t f, void *p);
/* Return builtin mime type for the given file name.
For unrecognized extensions, "text/plain" is returned. */
const char *mg_get_builtin_mime_type(const char *file_name);
/* Return Civetweb version. */
const char *mg_version(void);
/* URL-decode input buffer into destination buffer.
0-terminate the destination buffer.
form-url-encoded data differs from URI encoding in a way that it
uses '+' as character for space, see RFC 1866 section 8.2.1
http://ftp.ics.uci.edu/pub/ietf/html/rfc1866.txt
Return: length of the decoded data, or -1 if dst buffer is too small. */
int mg_url_decode(const char *src, int src_len, char *dst,
int dst_len, int is_form_url_encoded);
/* URL-encode input buffer into destination buffer.
returns the length of the resulting buffer or -1
is the buffer is too small. */
int mg_url_encode(const char *src, char *dst, size_t dst_len);
/* MD5 hash given strings.
Buffer 'buf' must be 33 bytes long. Varargs is a NULL terminated list of
ASCIIz strings. When function returns, buf will contain human-readable
MD5 hash. Example:
char buf[33];
mg_md5(buf, "aa", "bb", NULL); */
char *mg_md5(char buf[33], ...);
/* Print error message to the opened error log stream.
This utilizes the provided logging configuration.
conn: connection
fmt: format string without the line return
...: variable argument list
Example:
mg_cry(conn,"i like %s", "logging"); */
void mg_cry(struct mg_connection *conn,
PRINTF_FORMAT_STRING(const char *fmt), ...) PRINTF_ARGS(2, 3);
/* utility method to compare two buffers, case incensitive. */
int mg_strncasecmp(const char *s1, const char *s2, size_t len);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* CIVETWEB_HEADER_INCLUDED */

View file

@ -1,461 +0,0 @@
/*
* This an amalgamation of md5.c and md5.h into a single file
* with all static declaration to reduce linker conflicts
* in Civetweb.
*
* The MD5_STATIC declaration was added to facilitate static
* inclusion.
* No Face Press, LLC
*/
/* $Id: md5.h,v 1.4 2002/04/13 19:20:28 lpd Exp $ */
/*
Independent implementation of MD5 (RFC 1321).
This code implements the MD5 Algorithm defined in RFC 1321, whose
text is available at
http://www.ietf.org/rfc/rfc1321.txt
The code is derived from the text of the RFC, including the test suite
(section A.5) but excluding the rest of Appendix A. It does not include
any code or documentation that is identified in the RFC as being
copyrighted.
The original and principal author of md5.h is L. Peter Deutsch
<ghost@aladdin.com>. Other authors are noted in the change history
that follows (in reverse chronological order):
2002-04-13 lpd Removed support for non-ANSI compilers; removed
references to Ghostscript; clarified derivation from RFC 1321;
now handles byte order either statically or dynamically.
1999-11-04 lpd Edited comments slightly for automatic TOC extraction.
1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5);
added conditionalization for C++ compilation from Martin
Purschke <purschke@bnl.gov>.
1999-05-03 lpd Original version.
*/
#ifndef md5_INCLUDED
# define md5_INCLUDED
/*
* This package supports both compile-time and run-time determination of CPU
* byte order. If ARCH_IS_BIG_ENDIAN is defined as 0, the code will be
* compiled to run only on little-endian CPUs; if ARCH_IS_BIG_ENDIAN is
* defined as non-zero, the code will be compiled to run only on big-endian
* CPUs; if ARCH_IS_BIG_ENDIAN is not defined, the code will be compiled to
* run on either big- or little-endian CPUs, but will run slightly less
* efficiently on either one than if ARCH_IS_BIG_ENDIAN is defined.
*/
typedef unsigned char md5_byte_t; /* 8-bit byte */
typedef unsigned int md5_word_t; /* 32-bit word */
/* Define the state of the MD5 Algorithm. */
typedef struct md5_state_s {
md5_word_t count[2]; /* message length in bits, lsw first */
md5_word_t abcd[4]; /* digest buffer */
md5_byte_t buf[64]; /* accumulate block */
} md5_state_t;
#ifdef __cplusplus
extern "C"
{
#endif
/* Initialize the algorithm. */
MD5_STATIC void md5_init(md5_state_t *pms);
/* Append a string to the message. */
MD5_STATIC void md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes);
/* Finish the message and return the digest. */
MD5_STATIC void md5_finish(md5_state_t *pms, md5_byte_t digest[16]);
#ifdef __cplusplus
} /* end extern "C" */
#endif
#endif /* md5_INCLUDED */
/*
Copyright (C) 1999, 2000, 2002 Aladdin Enterprises. All rights reserved.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
L. Peter Deutsch
ghost@aladdin.com
*/
/* $Id: md5.c,v 1.6 2002/04/13 19:20:28 lpd Exp $ */
/*
Independent implementation of MD5 (RFC 1321).
This code implements the MD5 Algorithm defined in RFC 1321, whose
text is available at
http://www.ietf.org/rfc/rfc1321.txt
The code is derived from the text of the RFC, including the test suite
(section A.5) but excluding the rest of Appendix A. It does not include
any code or documentation that is identified in the RFC as being
copyrighted.
The original and principal author of md5.c is L. Peter Deutsch
<ghost@aladdin.com>. Other authors are noted in the change history
that follows (in reverse chronological order):
2002-04-13 lpd Clarified derivation from RFC 1321; now handles byte order
either statically or dynamically; added missing #include <string.h>
in library.
2002-03-11 lpd Corrected argument list for main(), and added int return
type, in test program and T value program.
2002-02-21 lpd Added missing #include <stdio.h> in test program.
2000-07-03 lpd Patched to eliminate warnings about "constant is
unsigned in ANSI C, signed in traditional"; made test program
self-checking.
1999-11-04 lpd Edited comments slightly for automatic TOC extraction.
1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5).
1999-05-03 lpd Original version.
*/
#ifndef MD5_STATIC
#include <string.h>
#endif
#undef BYTE_ORDER /* 1 = big-endian, -1 = little-endian, 0 = unknown */
#ifdef ARCH_IS_BIG_ENDIAN
# define BYTE_ORDER (ARCH_IS_BIG_ENDIAN ? 1 : -1)
#else
# define BYTE_ORDER 0
#endif
#define T_MASK ((md5_word_t)~0)
#define T1 /* 0xd76aa478 */ (T_MASK ^ 0x28955b87)
#define T2 /* 0xe8c7b756 */ (T_MASK ^ 0x173848a9)
#define T3 0x242070db
#define T4 /* 0xc1bdceee */ (T_MASK ^ 0x3e423111)
#define T5 /* 0xf57c0faf */ (T_MASK ^ 0x0a83f050)
#define T6 0x4787c62a
#define T7 /* 0xa8304613 */ (T_MASK ^ 0x57cfb9ec)
#define T8 /* 0xfd469501 */ (T_MASK ^ 0x02b96afe)
#define T9 0x698098d8
#define T10 /* 0x8b44f7af */ (T_MASK ^ 0x74bb0850)
#define T11 /* 0xffff5bb1 */ (T_MASK ^ 0x0000a44e)
#define T12 /* 0x895cd7be */ (T_MASK ^ 0x76a32841)
#define T13 0x6b901122
#define T14 /* 0xfd987193 */ (T_MASK ^ 0x02678e6c)
#define T15 /* 0xa679438e */ (T_MASK ^ 0x5986bc71)
#define T16 0x49b40821
#define T17 /* 0xf61e2562 */ (T_MASK ^ 0x09e1da9d)
#define T18 /* 0xc040b340 */ (T_MASK ^ 0x3fbf4cbf)
#define T19 0x265e5a51
#define T20 /* 0xe9b6c7aa */ (T_MASK ^ 0x16493855)
#define T21 /* 0xd62f105d */ (T_MASK ^ 0x29d0efa2)
#define T22 0x02441453
#define T23 /* 0xd8a1e681 */ (T_MASK ^ 0x275e197e)
#define T24 /* 0xe7d3fbc8 */ (T_MASK ^ 0x182c0437)
#define T25 0x21e1cde6
#define T26 /* 0xc33707d6 */ (T_MASK ^ 0x3cc8f829)
#define T27 /* 0xf4d50d87 */ (T_MASK ^ 0x0b2af278)
#define T28 0x455a14ed
#define T29 /* 0xa9e3e905 */ (T_MASK ^ 0x561c16fa)
#define T30 /* 0xfcefa3f8 */ (T_MASK ^ 0x03105c07)
#define T31 0x676f02d9
#define T32 /* 0x8d2a4c8a */ (T_MASK ^ 0x72d5b375)
#define T33 /* 0xfffa3942 */ (T_MASK ^ 0x0005c6bd)
#define T34 /* 0x8771f681 */ (T_MASK ^ 0x788e097e)
#define T35 0x6d9d6122
#define T36 /* 0xfde5380c */ (T_MASK ^ 0x021ac7f3)
#define T37 /* 0xa4beea44 */ (T_MASK ^ 0x5b4115bb)
#define T38 0x4bdecfa9
#define T39 /* 0xf6bb4b60 */ (T_MASK ^ 0x0944b49f)
#define T40 /* 0xbebfbc70 */ (T_MASK ^ 0x4140438f)
#define T41 0x289b7ec6
#define T42 /* 0xeaa127fa */ (T_MASK ^ 0x155ed805)
#define T43 /* 0xd4ef3085 */ (T_MASK ^ 0x2b10cf7a)
#define T44 0x04881d05
#define T45 /* 0xd9d4d039 */ (T_MASK ^ 0x262b2fc6)
#define T46 /* 0xe6db99e5 */ (T_MASK ^ 0x1924661a)
#define T47 0x1fa27cf8
#define T48 /* 0xc4ac5665 */ (T_MASK ^ 0x3b53a99a)
#define T49 /* 0xf4292244 */ (T_MASK ^ 0x0bd6ddbb)
#define T50 0x432aff97
#define T51 /* 0xab9423a7 */ (T_MASK ^ 0x546bdc58)
#define T52 /* 0xfc93a039 */ (T_MASK ^ 0x036c5fc6)
#define T53 0x655b59c3
#define T54 /* 0x8f0ccc92 */ (T_MASK ^ 0x70f3336d)
#define T55 /* 0xffeff47d */ (T_MASK ^ 0x00100b82)
#define T56 /* 0x85845dd1 */ (T_MASK ^ 0x7a7ba22e)
#define T57 0x6fa87e4f
#define T58 /* 0xfe2ce6e0 */ (T_MASK ^ 0x01d3191f)
#define T59 /* 0xa3014314 */ (T_MASK ^ 0x5cfebceb)
#define T60 0x4e0811a1
#define T61 /* 0xf7537e82 */ (T_MASK ^ 0x08ac817d)
#define T62 /* 0xbd3af235 */ (T_MASK ^ 0x42c50dca)
#define T63 0x2ad7d2bb
#define T64 /* 0xeb86d391 */ (T_MASK ^ 0x14792c6e)
static void
md5_process(md5_state_t *pms, const md5_byte_t *data /*[64]*/)
{
md5_word_t
a = pms->abcd[0], b = pms->abcd[1],
c = pms->abcd[2], d = pms->abcd[3];
md5_word_t t;
#if BYTE_ORDER > 0
/* Define storage only for big-endian CPUs. */
md5_word_t X[16];
#else
/* Define storage for little-endian or both types of CPUs. */
md5_word_t xbuf[16];
const md5_word_t *X;
#endif
{
#if BYTE_ORDER == 0
/*
* Determine dynamically whether this is a big-endian or
* little-endian machine, since we can use a more efficient
* algorithm on the latter.
*/
static const int w = 1;
if (*((const md5_byte_t *)&w)) /* dynamic little-endian */
#endif
#if BYTE_ORDER <= 0 /* little-endian */
{
/*
* On little-endian machines, we can process properly aligned
* data without copying it.
*/
if (!((data - (const md5_byte_t *)0) & 3)) {
/* data are properly aligned */
X = (const md5_word_t *)data;
} else {
/* not aligned */
memcpy(xbuf, data, 64);
X = xbuf;
}
}
#endif
#if BYTE_ORDER == 0
else /* dynamic big-endian */
#endif
#if BYTE_ORDER >= 0 /* big-endian */
{
/*
* On big-endian machines, we must arrange the bytes in the
* right order.
*/
const md5_byte_t *xp = data;
int i;
# if BYTE_ORDER == 0
X = xbuf; /* (dynamic only) */
# else
# define xbuf X /* (static only) */
# endif
for (i = 0; i < 16; ++i, xp += 4)
xbuf[i] = xp[0] + (xp[1] << 8) + (xp[2] << 16) + (xp[3] << 24);
}
#endif
}
#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n))))
/* Round 1. */
/* Let [abcd k s i] denote the operation
a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s). */
#define F(x, y, z) (((x) & (y)) | (~(x) & (z)))
#define SET(a, b, c, d, k, s, Ti)\
t = a + F(b,c,d) + X[k] + Ti;\
a = ROTATE_LEFT(t, s) + b
/* Do the following 16 operations. */
SET(a, b, c, d, 0, 7, T1);
SET(d, a, b, c, 1, 12, T2);
SET(c, d, a, b, 2, 17, T3);
SET(b, c, d, a, 3, 22, T4);
SET(a, b, c, d, 4, 7, T5);
SET(d, a, b, c, 5, 12, T6);
SET(c, d, a, b, 6, 17, T7);
SET(b, c, d, a, 7, 22, T8);
SET(a, b, c, d, 8, 7, T9);
SET(d, a, b, c, 9, 12, T10);
SET(c, d, a, b, 10, 17, T11);
SET(b, c, d, a, 11, 22, T12);
SET(a, b, c, d, 12, 7, T13);
SET(d, a, b, c, 13, 12, T14);
SET(c, d, a, b, 14, 17, T15);
SET(b, c, d, a, 15, 22, T16);
#undef SET
/* Round 2. */
/* Let [abcd k s i] denote the operation
a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s). */
#define G(x, y, z) (((x) & (z)) | ((y) & ~(z)))
#define SET(a, b, c, d, k, s, Ti)\
t = a + G(b,c,d) + X[k] + Ti;\
a = ROTATE_LEFT(t, s) + b
/* Do the following 16 operations. */
SET(a, b, c, d, 1, 5, T17);
SET(d, a, b, c, 6, 9, T18);
SET(c, d, a, b, 11, 14, T19);
SET(b, c, d, a, 0, 20, T20);
SET(a, b, c, d, 5, 5, T21);
SET(d, a, b, c, 10, 9, T22);
SET(c, d, a, b, 15, 14, T23);
SET(b, c, d, a, 4, 20, T24);
SET(a, b, c, d, 9, 5, T25);
SET(d, a, b, c, 14, 9, T26);
SET(c, d, a, b, 3, 14, T27);
SET(b, c, d, a, 8, 20, T28);
SET(a, b, c, d, 13, 5, T29);
SET(d, a, b, c, 2, 9, T30);
SET(c, d, a, b, 7, 14, T31);
SET(b, c, d, a, 12, 20, T32);
#undef SET
/* Round 3. */
/* Let [abcd k s t] denote the operation
a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s). */
#define H(x, y, z) ((x) ^ (y) ^ (z))
#define SET(a, b, c, d, k, s, Ti)\
t = a + H(b,c,d) + X[k] + Ti;\
a = ROTATE_LEFT(t, s) + b
/* Do the following 16 operations. */
SET(a, b, c, d, 5, 4, T33);
SET(d, a, b, c, 8, 11, T34);
SET(c, d, a, b, 11, 16, T35);
SET(b, c, d, a, 14, 23, T36);
SET(a, b, c, d, 1, 4, T37);
SET(d, a, b, c, 4, 11, T38);
SET(c, d, a, b, 7, 16, T39);
SET(b, c, d, a, 10, 23, T40);
SET(a, b, c, d, 13, 4, T41);
SET(d, a, b, c, 0, 11, T42);
SET(c, d, a, b, 3, 16, T43);
SET(b, c, d, a, 6, 23, T44);
SET(a, b, c, d, 9, 4, T45);
SET(d, a, b, c, 12, 11, T46);
SET(c, d, a, b, 15, 16, T47);
SET(b, c, d, a, 2, 23, T48);
#undef SET
/* Round 4. */
/* Let [abcd k s t] denote the operation
a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s). */
#define I(x, y, z) ((y) ^ ((x) | ~(z)))
#define SET(a, b, c, d, k, s, Ti)\
t = a + I(b,c,d) + X[k] + Ti;\
a = ROTATE_LEFT(t, s) + b
/* Do the following 16 operations. */
SET(a, b, c, d, 0, 6, T49);
SET(d, a, b, c, 7, 10, T50);
SET(c, d, a, b, 14, 15, T51);
SET(b, c, d, a, 5, 21, T52);
SET(a, b, c, d, 12, 6, T53);
SET(d, a, b, c, 3, 10, T54);
SET(c, d, a, b, 10, 15, T55);
SET(b, c, d, a, 1, 21, T56);
SET(a, b, c, d, 8, 6, T57);
SET(d, a, b, c, 15, 10, T58);
SET(c, d, a, b, 6, 15, T59);
SET(b, c, d, a, 13, 21, T60);
SET(a, b, c, d, 4, 6, T61);
SET(d, a, b, c, 11, 10, T62);
SET(c, d, a, b, 2, 15, T63);
SET(b, c, d, a, 9, 21, T64);
#undef SET
/* Then perform the following additions. (That is increment each
of the four registers by the value it had before this block
was started.) */
pms->abcd[0] += a;
pms->abcd[1] += b;
pms->abcd[2] += c;
pms->abcd[3] += d;
}
MD5_STATIC void
md5_init(md5_state_t *pms)
{
pms->count[0] = pms->count[1] = 0;
pms->abcd[0] = 0x67452301;
pms->abcd[1] = /*0xefcdab89*/ T_MASK ^ 0x10325476;
pms->abcd[2] = /*0x98badcfe*/ T_MASK ^ 0x67452301;
pms->abcd[3] = 0x10325476;
}
MD5_STATIC void
md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes)
{
const md5_byte_t *p = data;
int left = nbytes;
int offset = (pms->count[0] >> 3) & 63;
md5_word_t nbits = (md5_word_t)(nbytes << 3);
if (nbytes <= 0)
return;
/* Update the message length. */
pms->count[1] += nbytes >> 29;
pms->count[0] += nbits;
if (pms->count[0] < nbits)
pms->count[1]++;
/* Process an initial partial block. */
if (offset) {
int copy = (offset + nbytes > 64 ? 64 - offset : nbytes);
memcpy(pms->buf + offset, p, copy);
if (offset + copy < 64)
return;
p += copy;
left -= copy;
md5_process(pms, pms->buf);
}
/* Process full blocks. */
for (; left >= 64; p += 64, left -= 64)
md5_process(pms, p);
/* Process a final partial block. */
if (left)
memcpy(pms->buf, p, left);
}
MD5_STATIC void
md5_finish(md5_state_t *pms, md5_byte_t digest[16])
{
static const md5_byte_t pad[64] = {
0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
md5_byte_t data[8];
int i;
/* Save the length before padding. */
for (i = 0; i < 8; ++i)
data[i] = (md5_byte_t)(pms->count[i >> 2] >> ((i & 3) << 3));
/* Pad to 56 bytes mod 64. */
md5_append(pms, pad, ((55 - (pms->count[0] >> 3)) & 63) + 1);
/* Append the length. */
md5_append(pms, data, 8);
for (i = 0; i < 16; ++i)
digest[i] = (md5_byte_t)(pms->abcd[i >> 2] >> ((i & 3) << 3));
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,16 @@
cmake_minimum_required(VERSION 2.8)
set(ROOT_DIR ../..)
set(MACRO_DIR ${ROOT_DIR}/cmake/macros)
# setup for find modules
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/../../cmake/modules/")
set(TARGET_NAME embedded-webserver)
find_package(Qt5Network REQUIRED)
include(${MACRO_DIR}/SetupHifiLibrary.cmake)
setup_hifi_library(${TARGET_NAME})
qt5_use_modules(${TARGET_NAME} Network)

View file

@ -0,0 +1,221 @@
//
// HTTPConnection.cpp
// hifi
//
// Created by Stephen Birarda on 1/16/14.
// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
//
#include <QBuffer>
#include <QCryptographicHash>
#include <QTcpSocket>
#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),
_socket(socket),
_stream(socket),
_address(socket->peerAddress()) {
// take over ownership of the socket
_socket->setParent(this);
// connect initial slots
connect(socket, SIGNAL(readyRead()), SLOT(readRequest()));
connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), SLOT(deleteLater()));
connect(socket, SIGNAL(disconnected()), SLOT(deleteLater()));
}
HTTPConnection::~HTTPConnection() {
// log the destruction
if (_socket->error() != QAbstractSocket::UnknownSocketError) {
qDebug() << _socket->errorString();
}
}
QList<FormData> HTTPConnection::parseFormData() const {
// make sure we have the correct MIME type
QList<QByteArray> elements = _requestHeaders.value("Content-Type").split(';');
if (elements.at(0).trimmed() != "multipart/form-data") {
return QList<FormData>();
}
// retrieve the boundary marker
QByteArray boundary;
for (int ii = 1, nn = elements.size(); ii < nn; ii++) {
QByteArray element = elements.at(ii).trimmed();
if (element.startsWith("boundary")) {
boundary = element.mid(element.indexOf('=') + 1).trimmed();
break;
}
}
QByteArray start = "--" + boundary;
QByteArray end = "\r\n--" + boundary + "--\r\n";
QList<FormData> data;
QBuffer buffer(const_cast<QByteArray*>(&_requestContent));
buffer.open(QIODevice::ReadOnly);
while (buffer.canReadLine()) {
QByteArray line = buffer.readLine().trimmed();
if (line == start) {
FormData datum;
while (buffer.canReadLine()) {
QByteArray line = buffer.readLine().trimmed();
if (line.isEmpty()) {
// content starts after this line
int idx = _requestContent.indexOf(end, buffer.pos());
if (idx == -1) {
qWarning() << "Missing end boundary." << _address;
return data;
}
datum.second = _requestContent.mid(buffer.pos(), idx - buffer.pos());
data.append(datum);
buffer.seek(idx + end.length());
} else {
// it's a header element
int idx = line.indexOf(':');
if (idx == -1) {
qWarning() << "Invalid header line." << _address << line;
continue;
}
datum.first.insert(line.left(idx).trimmed(), line.mid(idx + 1).trimmed());
}
}
}
}
return data;
}
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");
int csize = content.size();
for (Headers::const_iterator it = headers.constBegin(), end = headers.constEnd();
it != end; it++) {
_socket->write(it.key());
_socket->write(": ");
_socket->write(it.value());
_socket->write("\r\n");
}
if (csize > 0) {
_socket->write("Content-Length: ");
_socket->write(QByteArray::number(csize));
_socket->write("\r\n");
_socket->write("Content-Type: ");
_socket->write(contentType);
_socket->write("\r\n");
}
_socket->write("Connection: close\r\n\r\n");
if (csize > 0) {
_socket->write(content);
}
// make sure we receive no further read notifications
_socket->disconnect(SIGNAL(readyRead()), this);
_socket->disconnectFromHost();
}
void HTTPConnection::readRequest() {
if (!_socket->canReadLine()) {
return;
}
// parse out the method and resource
QByteArray line = _socket->readLine().trimmed();
if (line.startsWith("HEAD")) {
_requestOperation = QNetworkAccessManager::HeadOperation;
} else if (line.startsWith("GET")) {
_requestOperation = QNetworkAccessManager::GetOperation;
} else if (line.startsWith("PUT")) {
_requestOperation = QNetworkAccessManager::PutOperation;
} else if (line.startsWith("POST")) {
_requestOperation = QNetworkAccessManager::PostOperation;
} else if (line.startsWith("DELETE")) {
_requestOperation = QNetworkAccessManager::DeleteOperation;
} else {
qWarning() << "Unrecognized HTTP operation." << _address << line;
respond("400 Bad Request", "Unrecognized operation.");
return;
}
int idx = line.indexOf(' ') + 1;
_requestUrl.setUrl(line.mid(idx, line.lastIndexOf(' ') - idx));
// switch to reading the header
_socket->disconnect(this, SLOT(readRequest()));
connect(_socket, SIGNAL(readyRead()), SLOT(readHeaders()));
// read any headers immediately available
readHeaders();
}
void HTTPConnection::readHeaders() {
while (_socket->canReadLine()) {
QByteArray line = _socket->readLine();
QByteArray trimmed = line.trimmed();
if (trimmed.isEmpty()) {
_socket->disconnect(this, SLOT(readHeaders()));
QByteArray clength = _requestHeaders.value("Content-Length");
if (clength.isEmpty()) {
_parentManager->handleHTTPRequest(this, _requestUrl.path());
} else {
_requestContent.resize(clength.toInt());
connect(_socket, SIGNAL(readyRead()), SLOT(readContent()));
// read any content immediately available
readContent();
}
return;
}
char first = line.at(0);
if (first == ' ' || first == '\t') { // continuation
_requestHeaders[_lastRequestHeader].append(trimmed);
continue;
}
int idx = trimmed.indexOf(':');
if (idx == -1) {
qWarning() << "Invalid header." << _address << trimmed;
respond("400 Bad Request", "The header was malformed.");
return;
}
_lastRequestHeader = trimmed.left(idx);
QByteArray& value = _requestHeaders[_lastRequestHeader];
if (!value.isEmpty()) {
value.append(", ");
}
value.append(trimmed.mid(idx + 1).trimmed());
}
}
void HTTPConnection::readContent() {
int size = _requestContent.size();
if (_socket->bytesAvailable() < size) {
return;
}
_socket->read(_requestContent.data(), size);
_socket->disconnect(this, SLOT(readContent()));
_parentManager->handleHTTPRequest(this, _requestUrl.path());
}

View file

@ -0,0 +1,118 @@
//
// HTTPConnection.h
// hifi
//
// 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
//
#ifndef __hifi__HTTPConnection__
#define __hifi__HTTPConnection__
#include <QHash>
#include <QHostAddress>
#include <QIODevice>
#include <QList>
#include <QNetworkAccessManager>
#include <QObject>
#include <QPair>
#include <QUrl>
class QTcpSocket;
class HTTPManager;
class MaskFilter;
class ServerApp;
/// Header hash
typedef QHash<QByteArray, QByteArray> Headers;
/// A form data element
typedef QPair<Headers, QByteArray> FormData;
/// Handles a single HTTP connection.
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 };
/// Initializes the connection.
HTTPConnection (QTcpSocket* socket, HTTPManager* parentManager);
/// Destroys the connection.
virtual ~HTTPConnection ();
/// Returns a pointer to the underlying socket, to which WebSocket message bodies should be written.
QTcpSocket* socket () const { return _socket; }
/// Returns the request operation.
QNetworkAccessManager::Operation requestOperation () const { return _requestOperation; }
/// Returns a reference to the request URL.
const QUrl& requestUrl () const { return _requestUrl; }
/// Returns a reference to the request headers.
const Headers& requestHeaders () const { return _requestHeaders; }
/// Returns a reference to the request content.
const QByteArray& requestContent () const { return _requestContent; }
/// Parses the request content as form data, returning a list of header/content pairs.
QList<FormData> parseFormData () const;
/// Sends a response and closes the connection.
void respond (const char* code, const QByteArray& content = QByteArray(),
const char* contentType = "text/plain; charset=ISO-8859-1",
const Headers& headers = Headers());
protected slots:
/// Reads the request line.
void readRequest ();
/// Reads the headers.
void readHeaders ();
/// Reads the content.
void readContent ();
protected:
/// The parent HTTP manager
HTTPManager* _parentManager;
/// The underlying socket.
QTcpSocket* _socket;
/// The data stream for writing to the socket.
QDataStream _stream;
/// The stored address.
QHostAddress _address;
/// The requested operation.
QNetworkAccessManager::Operation _requestOperation;
/// The requested URL.
QUrl _requestUrl;
/// The request headers.
Headers _requestHeaders;
/// The last request header processed (used for continuations).
QByteArray _lastRequestHeader;
/// The content of the request.
QByteArray _requestContent;
};
#endif /* defined(__hifi__HTTPConnection__) */

View file

@ -0,0 +1,131 @@
//
// HTTPManager.cpp
// hifi
//
// Created by Stephen Birarda on 1/16/14.
// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
//
#include <QtCore/QDebug>
#include <QtCore/QFile>
#include <QtCore/QFileInfo>
#include <QtCore/QMimeDatabase>
#include <QtNetwork/QTcpSocket>
#include "HTTPConnection.h"
#include "HTTPManager.h"
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
if (subPath.startsWith('/')) {
subPath.remove(0, 1);
}
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()) {
// file exists, serve it
static QMimeDatabase mimeDatabase;
QFile localFile(filePath);
localFile.open(QIODevice::ReadOnly);
QString localFileString(localFile.readAll());
QFileInfo localFileInfo(filePath);
if (localFileInfo.completeSuffix() == "shtml") {
// this is a file that may have some SSI statements
// the only thing we support is the include directive, but check the contents for that
// setup our static QRegExp that will catch <!--#include virtual ... --> and <!--#include file .. --> directives
const QString includeRegExpString = "<!--\\s*#include\\s+(virtual|file)\\s?=\\s?\"(\\S+)\"\\s*-->";
QRegExp includeRegExp(includeRegExpString);
int matchPosition = 0;
while ((matchPosition = includeRegExp.indexIn(localFileString, matchPosition)) != -1) {
// check if this is a file or vitual include
bool isFileInclude = includeRegExp.cap(1) == "file";
// setup the correct file path for the included file
QString includeFilePath = isFileInclude
? localFileInfo.canonicalPath() + "/" + includeRegExp.cap(2)
: _documentRoot + includeRegExp.cap(2);
QString replacementString;
if (QFileInfo(includeFilePath).isFile()) {
QFile includedFile(includeFilePath);
includedFile.open(QIODevice::ReadOnly);
replacementString = QString(includedFile.readAll());
} else {
qDebug() << "SSI include directive referenced a missing file:" << includeFilePath;
}
// replace the match with the contents of the file, or an empty string if the file was not found
localFileString.replace(matchPosition, includeRegExp.matchedLength(), replacementString);
// push the match position forward so we can check the next match
matchPosition += includeRegExp.matchedLength();
}
}
connection->respond(HTTPConnection::StatusCode200, localFileString.toLocal8Bit(), qPrintable(mimeDatabase.mimeTypeForFile(filePath).name()));
} else {
// respond with a 404
connection->respond(HTTPConnection::StatusCode404, "Resource not found.");
}
return true;
}
HTTPManager::HTTPManager(quint16 port, const QString& documentRoot, HTTPRequestHandler* requestHandler, QObject* parent) :
QTcpServer(parent),
_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();
return;
}
// connect the connection signal
connect(this, SIGNAL(newConnection()), SLOT(acceptConnections()));
}
void HTTPManager::acceptConnections() {
QTcpSocket* socket;
while ((socket = nextPendingConnection()) != 0) {
new HTTPConnection(socket, this);
}
}

View file

@ -0,0 +1,43 @@
//
// HTTPManager.h
// hifi
//
// 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
//
#ifndef __hifi__HTTPManager__
#define __hifi__HTTPManager__
#include <QtNetwork/QTcpServer>
class HTTPConnection;
class HTTPRequestHandler {
public:
/// Handles an HTTP request.
virtual bool handleHTTPRequest(HTTPConnection* connection, const QString& path) = 0;
};
/// Handles HTTP connections
class HTTPManager : public QTcpServer, public HTTPRequestHandler {
Q_OBJECT
public:
/// Initializes the manager.
HTTPManager(quint16 port, const QString& documentRoot, HTTPRequestHandler* requestHandler = NULL, QObject* parent = 0);
bool handleHTTPRequest(HTTPConnection* connection, const QString& path);
protected slots:
/// Accepts all pending connections
void acceptConnections();
protected:
QString _documentRoot;
HTTPRequestHandler* _requestHandler;
};
#endif /* defined(__hifi__HTTPManager__) */

View file

@ -12,13 +12,8 @@ find_package(Qt5Widgets REQUIRED)
include(${MACRO_DIR}/SetupHifiLibrary.cmake)
# grab cJSON and civetweb sources to pass as OPTIONAL_SRCS
FILE(GLOB OPTIONAL_SRCS ${ROOT_DIR}/externals/civetweb/src/*)
setup_hifi_library(${TARGET_NAME} ${OPTIONAL_SRCS})
include_directories(${ROOT_DIR}/externals/civetweb/include)
qt5_use_modules(${TARGET_NAME} Widgets)
include(${MACRO_DIR}/IncludeGLM.cmake)
@ -36,7 +31,5 @@ link_hifi_library(shared ${TARGET_NAME} ${ROOT_DIR})
# link in the hifi octree library
link_hifi_library(octree ${TARGET_NAME} ${ROOT_DIR})
# link dl library on UNIX for civetweb
if (UNIX AND NOT APPLE)
target_link_libraries(${TARGET_NAME} ${CMAKE_DL_LIBS})
endif (UNIX AND NOT APPLE)
# link the embedded webserver
link_hifi_library(embedded-webserver ${TARGET_NAME} ${ROOT_DIR})

View file

@ -6,25 +6,25 @@
// Copyright (c) 2013 HighFidelity, Inc. All rights reserved.
//
#include "civetweb.h"
#include <QtCore/QTimer>
#include <QtCore/QUuid>
#include <QtNetwork/QNetworkAccessManager>
#include <time.h>
#include <HTTPConnection.h>
#include <Logging.h>
#include <UUID.h>
#include "OctreeServer.h"
#include "OctreeServerConsts.h"
OctreeServer* OctreeServer::_instance = NULL;
void OctreeServer::attachQueryNodeToNode(Node* newNode) {
if (newNode->getLinkedData() == NULL) {
if (GetInstance()) {
OctreeQueryNode* newQueryNodeData = GetInstance()->createOctreeQueryNode(newNode);
newQueryNodeData->resetOctreePacket(true); // don't bump sequence
newNode->setLinkedData(newQueryNodeData);
}
OctreeQueryNode* newQueryNodeData = _instance->createOctreeQueryNode(newNode);
newQueryNodeData->resetOctreePacket(true); // don't bump sequence
newNode->setLinkedData(newQueryNodeData);
}
}
@ -39,30 +39,26 @@ void OctreeServer::nodeKilled(SharedNodePointer node) {
}
};
OctreeServer* OctreeServer::_theInstance = NULL;
OctreeServer::OctreeServer(const unsigned char* dataBuffer, int numBytes) :
ThreadedAssignment(dataBuffer, numBytes)
ThreadedAssignment(dataBuffer, numBytes),
_argc(0),
_argv(NULL),
_parsedArgV(NULL),
_httpManager(NULL),
_packetsPerClientPerInterval(10),
_tree(NULL),
_wantPersist(true),
_debugSending(false),
_debugReceiving(false),
_verboseDebug(false),
_jurisdiction(NULL),
_jurisdictionSender(NULL),
_octreeInboundPacketProcessor(NULL),
_persistThread(NULL),
_started(time(0)),
_startedUSecs(usecTimestampNow())
{
_argc = 0;
_argv = NULL;
_tree = NULL;
_packetsPerClientPerInterval = 10;
_wantPersist = true;
_debugSending = false;
_debugReceiving = false;
_verboseDebug = false;
_jurisdiction = NULL;
_jurisdictionSender = NULL;
_octreeInboundPacketProcessor = NULL;
_persistThread = NULL;
_parsedArgV = NULL;
_started = time(0);
_startedUSecs = usecTimestampNow();
_theInstance = this;
_instance = this;
}
OctreeServer::~OctreeServer() {
@ -94,235 +90,220 @@ OctreeServer::~OctreeServer() {
qDebug() << "OctreeServer::run()... DONE";
}
void OctreeServer::initMongoose(int port) {
// setup the mongoose web server
struct mg_callbacks callbacks = {};
void OctreeServer::initHTTPManager(int port) {
// setup the embedded web server
QString documentRoot = QString("%1/resources/web").arg(QCoreApplication::applicationDirPath());
QString listenPort = QString("%1").arg(port);
// list of options. Last element must be NULL.
const char* options[] = {
"listening_ports", listenPort.toLocal8Bit().constData(),
"document_root", documentRoot.toLocal8Bit().constData(),
NULL };
callbacks.begin_request = civetwebRequestHandler;
// Start the web server.
mg_start(&callbacks, NULL, options);
// setup an httpManager with us as the request handler and the parent
_httpManager = new HTTPManager(port, documentRoot, this, this);
}
int OctreeServer::civetwebRequestHandler(struct mg_connection* connection) {
const struct mg_request_info* ri = mg_get_request_info(connection);
OctreeServer* theServer = GetInstance();
bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QString& path) {
#ifdef FORCE_CRASH
if (strcmp(ri->uri, "/force_crash") == 0 && strcmp(ri->request_method, "GET") == 0) {
if (connection->requestOperation() == QNetworkAccessManager::GetOperation
&& path == "/force_crash") {
qDebug() << "About to force a crash!";
int foo;
int* forceCrash = &foo;
mg_printf(connection, "%s", "HTTP/1.0 200 OK\r\n\r\n");
mg_printf(connection, "%s", "forcing a crash....\r\n");
QString responseString("forcing a crash...");
connection->respond(HTTPConnection::StatusCode200, qPrintable(responseString));
delete[] forceCrash;
mg_printf(connection, "%s", "did it crash....\r\n");
return 1;
return true;
}
#endif
bool showStats = false;
if (strcmp(ri->uri, "/") == 0 && strcmp(ri->request_method, "GET") == 0) {
showStats = true;
if (connection->requestOperation() == QNetworkAccessManager::GetOperation) {
if (path == "/") {
showStats = true;
} else if (path == "/resetStats") {
_octreeInboundPacketProcessor->resetStats();
showStats = true;
}
}
if (strcmp(ri->uri, "/resetStats") == 0 && strcmp(ri->request_method, "GET") == 0) {
theServer->_octreeInboundPacketProcessor->resetStats();
showStats = true;
}
if (showStats) {
uint64_t checkSum;
// return a 200
mg_printf(connection, "%s", "HTTP/1.0 200 OK\r\n");
mg_printf(connection, "%s", "Content-Type: text/html\r\n\r\n");
mg_printf(connection, "%s", "<html><doc>\r\n");
mg_printf(connection, "%s", "<pre>\r\n");
mg_printf(connection, "<b>Your %s Server is running... <a href='/'>[RELOAD]</a></b>\r\n", theServer->getMyServerName());
tm* localtm = localtime(&theServer->_started);
QString statsString("<html><doc>\r\n<pre>\r\n");
statsString += QString("<b>Your %1 Server is running... <a href='/'>[RELOAD]</a></b>\r\n").arg(getMyServerName());
tm* localtm = localtime(&_started);
const int MAX_TIME_LENGTH = 128;
char buffer[MAX_TIME_LENGTH];
strftime(buffer, MAX_TIME_LENGTH, "%m/%d/%Y %X", localtm);
mg_printf(connection, "Running since: %s", buffer);
statsString += QString("Running since: %1").arg(buffer);
// Convert now to tm struct for UTC
tm* gmtm = gmtime(&theServer->_started);
if (gmtm != NULL) {
tm* gmtm = gmtime(&_started);
if (gmtm) {
strftime(buffer, MAX_TIME_LENGTH, "%m/%d/%Y %X", gmtm);
mg_printf(connection, " [%s UTM] ", buffer);
statsString += (QString(" [%1 UTM] ").arg(buffer));
}
mg_printf(connection, "%s", "\r\n");
statsString += "\r\n";
uint64_t now = usecTimestampNow();
const int USECS_PER_MSEC = 1000;
uint64_t msecsElapsed = (now - theServer->_startedUSecs) / USECS_PER_MSEC;
uint64_t msecsElapsed = (now - _startedUSecs) / USECS_PER_MSEC;
const int MSECS_PER_SEC = 1000;
const int SECS_PER_MIN = 60;
const int MIN_PER_HOUR = 60;
const int MSECS_PER_MIN = MSECS_PER_SEC * SECS_PER_MIN;
float seconds = (msecsElapsed % MSECS_PER_MIN)/(float)MSECS_PER_SEC;
int minutes = (msecsElapsed/(MSECS_PER_MIN)) % MIN_PER_HOUR;
int hours = (msecsElapsed/(MSECS_PER_MIN * MIN_PER_HOUR));
mg_printf(connection, "%s", "Uptime: ");
statsString += "Uptime: ";
if (hours > 0) {
mg_printf(connection, "%d hour%s ", hours, (hours > 1) ? "s" : "" );
statsString += QString("%1 hour%2").arg(hours).arg((hours > 1) ? "s" : "");
}
if (minutes > 0) {
mg_printf(connection, "%d minute%s ", minutes, (minutes > 1) ? "s" : "");
statsString += QString("%1 minute%s").arg(minutes).arg((minutes > 1) ? "s" : "");
}
if (seconds > 0) {
mg_printf(connection, "%.3f seconds ", seconds);
statsString += QString().sprintf("%.3f seconds", seconds);
}
mg_printf(connection, "%s", "\r\n");
mg_printf(connection, "%s", "\r\n");
statsString += "\r\n\r\n";
// display voxel file load time
if (theServer->isInitialLoadComplete()) {
if (theServer->isPersistEnabled()) {
mg_printf(connection, "%s File Persist Enabled...\r\n", theServer->getMyServerName());
if (isInitialLoadComplete()) {
if (isPersistEnabled()) {
statsString += QString("%1 File Persist Enabled...\r\n").arg(getMyServerName());
} else {
mg_printf(connection, "%s File Persist Disabled...\r\n", theServer->getMyServerName());
statsString += QString("%1 File Persist Disabled...\r\n").arg(getMyServerName());
}
mg_printf(connection, "%s", "\r\n");
uint64_t msecsElapsed = theServer->getLoadElapsedTime() / USECS_PER_MSEC;;
statsString += "\r\n";
uint64_t msecsElapsed = getLoadElapsedTime() / USECS_PER_MSEC;;
float seconds = (msecsElapsed % MSECS_PER_MIN)/(float)MSECS_PER_SEC;
int minutes = (msecsElapsed/(MSECS_PER_MIN)) % MIN_PER_HOUR;
int hours = (msecsElapsed/(MSECS_PER_MIN * MIN_PER_HOUR));
mg_printf(connection, "%s File Load Took: ", theServer->getMyServerName());
statsString += QString("%1 File Load Took").arg(getMyServerName());
if (hours > 0) {
mg_printf(connection, "%d hour%s ", hours, (hours > 1) ? "s" : "" );
statsString += QString("%1 hour%2").arg(hours).arg((hours > 1) ? "s" : "");
}
if (minutes > 0) {
mg_printf(connection, "%d minute%s ", minutes, (minutes > 1) ? "s" : "");
statsString += QString("%1 minute%2").arg(minutes).arg((minutes > 1) ? "s" : "");
}
if (seconds >= 0) {
mg_printf(connection, "%.3f seconds", seconds);
statsString += QString().sprintf("%.3f seconds", seconds);
}
mg_printf(connection, "%s", "\r\n");
statsString += "\r\n";
} else {
mg_printf(connection, "%s", "Voxels not yet loaded...\r\n");
statsString += "Voxels not yet loaded...\r\n";
}
mg_printf(connection, "%s", "\r\n");
mg_printf(connection, "%s", "\r\n");
mg_printf(connection, "%s", "<b>Configuration:</b>\r\n");
for (int i = 1; i < theServer->_argc; i++) {
mg_printf(connection, "%s ", theServer->_argv[i]);
statsString += "\r\n\r\n";
statsString += "<b>Configuration:</b>\r\n";
for (int i = 1; i < _argc; i++) {
statsString += _argv[i];
}
mg_printf(connection, "%s", "\r\n"); // one to end the config line
mg_printf(connection, "%s", "\r\n"); // two more for spacing
mg_printf(connection, "%s", "\r\n");
statsString += "\r\n"; //one to end the config line
statsString += "\r\n\r\n"; // two more for spacing
// display scene stats
unsigned long nodeCount = OctreeElement::getNodeCount();
unsigned long internalNodeCount = OctreeElement::getInternalNodeCount();
unsigned long leafNodeCount = OctreeElement::getLeafNodeCount();
QLocale locale(QLocale::English);
const float AS_PERCENT = 100.0;
mg_printf(connection, "%s", "<b>Current Nodes in scene:</b>\r\n");
mg_printf(connection, " Total Nodes: %s nodes\r\n",
locale.toString((uint)nodeCount).rightJustified(16, ' ').toLocal8Bit().constData());
mg_printf(connection, " Internal Nodes: %s nodes (%5.2f%%)\r\n",
locale.toString((uint)internalNodeCount).rightJustified(16, ' ').toLocal8Bit().constData(),
((float)internalNodeCount / (float)nodeCount) * AS_PERCENT);
mg_printf(connection, " Leaf Nodes: %s nodes (%5.2f%%)\r\n",
locale.toString((uint)leafNodeCount).rightJustified(16, ' ').toLocal8Bit().constData(),
((float)leafNodeCount / (float)nodeCount) * AS_PERCENT);
mg_printf(connection, "%s", "\r\n");
mg_printf(connection, "%s", "\r\n");
statsString += "<b>Current Nodes in scene:</b>\r\n";
statsString += QString(" Total Nodes: %1 nodes\r\n").arg(locale.toString((uint)nodeCount).rightJustified(16, ' '));
statsString += QString().sprintf(" Internal Nodes: %s nodes (%5.2f%%)\r\n",
locale.toString((uint)internalNodeCount).rightJustified(16,
' ').toLocal8Bit().constData(),
((float)internalNodeCount / (float)nodeCount) * AS_PERCENT);
statsString += QString().sprintf(" Leaf Nodes: %s nodes (%5.2f%%)\r\n",
locale.toString((uint)leafNodeCount).rightJustified(16, ' ').toLocal8Bit().constData(),
((float)leafNodeCount / (float)nodeCount) * AS_PERCENT);
statsString += "\r\n";
statsString += "\r\n";
// display outbound packet stats
mg_printf(connection, "<b>%s Outbound Packet Statistics...</b>\r\n", theServer->getMyServerName());
statsString += QString("<b>%1 Outbound Packet Statistics...</b>\r\n").arg(getMyServerName());
uint64_t totalOutboundPackets = OctreeSendThread::_totalPackets;
uint64_t totalOutboundBytes = OctreeSendThread::_totalBytes;
uint64_t totalWastedBytes = OctreeSendThread::_totalWastedBytes;
uint64_t totalBytesOfOctalCodes = OctreePacketData::getTotalBytesOfOctalCodes();
uint64_t totalBytesOfBitMasks = OctreePacketData::getTotalBytesOfBitMasks();
uint64_t totalBytesOfColor = OctreePacketData::getTotalBytesOfColor();
const int COLUMN_WIDTH = 10;
mg_printf(connection, " Total Outbound Packets: %s packets\r\n",
locale.toString((uint)totalOutboundPackets).rightJustified(COLUMN_WIDTH, ' ').toLocal8Bit().constData());
mg_printf(connection, " Total Outbound Bytes: %s bytes\r\n",
locale.toString((uint)totalOutboundBytes).rightJustified(COLUMN_WIDTH, ' ').toLocal8Bit().constData());
mg_printf(connection, " Total Wasted Bytes: %s bytes\r\n",
locale.toString((uint)totalWastedBytes).rightJustified(COLUMN_WIDTH, ' ').toLocal8Bit().constData());
mg_printf(connection, " Total OctalCode Bytes: %s bytes (%5.2f%%)\r\n",
statsString += QString(" Total Outbound Packets: %1 packets\r\n")
.arg(locale.toString((uint)totalOutboundPackets).rightJustified(COLUMN_WIDTH, ' '));
statsString += QString(" Total Outbound Bytes: %1 bytes\r\n")
.arg(locale.toString((uint)totalOutboundBytes).rightJustified(COLUMN_WIDTH, ' '));
statsString += QString(" Total Wasted Bytes: %1 bytes\r\n")
.arg(locale.toString((uint)totalWastedBytes).rightJustified(COLUMN_WIDTH, ' '));
statsString += QString().sprintf(" Total OctalCode Bytes: %s bytes (%5.2f%%)\r\n",
locale.toString((uint)totalBytesOfOctalCodes).rightJustified(COLUMN_WIDTH, ' ').toLocal8Bit().constData(),
((float)totalBytesOfOctalCodes / (float)totalOutboundBytes) * AS_PERCENT);
mg_printf(connection, " Total BitMasks Bytes: %s bytes (%5.2f%%)\r\n",
statsString += QString().sprintf(" Total BitMasks Bytes: %s bytes (%5.2f%%)\r\n",
locale.toString((uint)totalBytesOfBitMasks).rightJustified(COLUMN_WIDTH, ' ').toLocal8Bit().constData(),
((float)totalBytesOfBitMasks / (float)totalOutboundBytes) * AS_PERCENT);
mg_printf(connection, " Total Color Bytes: %s bytes (%5.2f%%)\r\n",
statsString += QString().sprintf(" Total Color Bytes: %s bytes (%5.2f%%)\r\n",
locale.toString((uint)totalBytesOfColor).rightJustified(COLUMN_WIDTH, ' ').toLocal8Bit().constData(),
((float)totalBytesOfColor / (float)totalOutboundBytes) * AS_PERCENT);
mg_printf(connection, "%s", "\r\n");
mg_printf(connection, "%s", "\r\n");
statsString += "\r\n";
statsString += "\r\n";
// display inbound packet stats
mg_printf(connection, "<b>%s Edit Statistics... <a href='/resetStats'>[RESET]</a></b>\r\n",
theServer->getMyServerName());
uint64_t averageTransitTimePerPacket = theServer->_octreeInboundPacketProcessor->getAverageTransitTimePerPacket();
uint64_t averageProcessTimePerPacket = theServer->_octreeInboundPacketProcessor->getAverageProcessTimePerPacket();
uint64_t averageLockWaitTimePerPacket = theServer->_octreeInboundPacketProcessor->getAverageLockWaitTimePerPacket();
uint64_t averageProcessTimePerElement = theServer->_octreeInboundPacketProcessor->getAverageProcessTimePerElement();
uint64_t averageLockWaitTimePerElement = theServer->_octreeInboundPacketProcessor->getAverageLockWaitTimePerElement();
uint64_t totalElementsProcessed = theServer->_octreeInboundPacketProcessor->getTotalElementsProcessed();
uint64_t totalPacketsProcessed = theServer->_octreeInboundPacketProcessor->getTotalPacketsProcessed();
statsString += QString().sprintf("<b>%s Edit Statistics... <a href='/resetStats'>[RESET]</a></b>\r\n",
getMyServerName());
uint64_t averageTransitTimePerPacket = _octreeInboundPacketProcessor->getAverageTransitTimePerPacket();
uint64_t averageProcessTimePerPacket = _octreeInboundPacketProcessor->getAverageProcessTimePerPacket();
uint64_t averageLockWaitTimePerPacket = _octreeInboundPacketProcessor->getAverageLockWaitTimePerPacket();
uint64_t averageProcessTimePerElement = _octreeInboundPacketProcessor->getAverageProcessTimePerElement();
uint64_t averageLockWaitTimePerElement = _octreeInboundPacketProcessor->getAverageLockWaitTimePerElement();
uint64_t totalElementsProcessed = _octreeInboundPacketProcessor->getTotalElementsProcessed();
uint64_t totalPacketsProcessed = _octreeInboundPacketProcessor->getTotalPacketsProcessed();
float averageElementsPerPacket = totalPacketsProcessed == 0 ? 0 : totalElementsProcessed / totalPacketsProcessed;
mg_printf(connection, " Total Inbound Packets: %s packets\r\n",
locale.toString((uint)totalPacketsProcessed).rightJustified(COLUMN_WIDTH, ' ').toLocal8Bit().constData());
mg_printf(connection, " Total Inbound Elements: %s elements\r\n",
locale.toString((uint)totalElementsProcessed).rightJustified(COLUMN_WIDTH, ' ').toLocal8Bit().constData());
mg_printf(connection, " Average Inbound Elements/Packet: %f elements/packet\r\n", averageElementsPerPacket);
mg_printf(connection, " Average Transit Time/Packet: %s usecs\r\n",
locale.toString((uint)averageTransitTimePerPacket).rightJustified(COLUMN_WIDTH, ' ').toLocal8Bit().constData());
mg_printf(connection, " Average Process Time/Packet: %s usecs\r\n",
locale.toString((uint)averageProcessTimePerPacket).rightJustified(COLUMN_WIDTH, ' ').toLocal8Bit().constData());
mg_printf(connection, " Average Wait Lock Time/Packet: %s usecs\r\n",
locale.toString((uint)averageLockWaitTimePerPacket).rightJustified(COLUMN_WIDTH, ' ').toLocal8Bit().constData());
mg_printf(connection, " Average Process Time/Element: %s usecs\r\n",
locale.toString((uint)averageProcessTimePerElement).rightJustified(COLUMN_WIDTH, ' ').toLocal8Bit().constData());
mg_printf(connection, " Average Wait Lock Time/Element: %s usecs\r\n",
locale.toString((uint)averageLockWaitTimePerElement).rightJustified(COLUMN_WIDTH, ' ').toLocal8Bit().constData());
statsString += QString(" Total Inbound Packets: %1 packets\r\n")
.arg(locale.toString((uint)totalPacketsProcessed).rightJustified(COLUMN_WIDTH, ' '));
statsString += QString(" Total Inbound Elements: %1 elements\r\n")
.arg(locale.toString((uint)totalElementsProcessed).rightJustified(COLUMN_WIDTH, ' '));
statsString += QString().sprintf(" Average Inbound Elements/Packet: %f elements/packet\r\n", averageElementsPerPacket);
statsString += QString(" Average Transit Time/Packet: %1 usecs\r\n")
.arg(locale.toString((uint)averageTransitTimePerPacket).rightJustified(COLUMN_WIDTH, ' '));
statsString += QString(" Average Process Time/Packet: %1 usecs\r\n")
.arg(locale.toString((uint)averageProcessTimePerPacket).rightJustified(COLUMN_WIDTH, ' '));
statsString += QString(" Average Wait Lock Time/Packet: %1 usecs\r\n")
.arg(locale.toString((uint)averageLockWaitTimePerPacket).rightJustified(COLUMN_WIDTH, ' '));
statsString += QString(" Average Process Time/Element: %1 usecs\r\n")
.arg(locale.toString((uint)averageProcessTimePerElement).rightJustified(COLUMN_WIDTH, ' '));
statsString += QString(" Average Wait Lock Time/Element: %1 usecs\r\n")
.arg(locale.toString((uint)averageLockWaitTimePerElement).rightJustified(COLUMN_WIDTH, ' '));
int senderNumber = 0;
NodeToSenderStatsMap& allSenderStats = theServer->_octreeInboundPacketProcessor->getSingleSenderStats();
NodeToSenderStatsMap& allSenderStats = _octreeInboundPacketProcessor->getSingleSenderStats();
for (NodeToSenderStatsMapIterator i = allSenderStats.begin(); i != allSenderStats.end(); i++) {
senderNumber++;
QUuid senderID = i->first;
SingleSenderStats& senderStats = i->second;
mg_printf(connection, "\r\n Stats for sender %d uuid: %s\r\n", senderNumber,
senderID.toString().toLocal8Bit().constData());
statsString += QString("\r\n Stats for sender %1 uuid: %2\r\n")
.arg(senderNumber).arg(senderID.toString());
averageTransitTimePerPacket = senderStats.getAverageTransitTimePerPacket();
averageProcessTimePerPacket = senderStats.getAverageProcessTimePerPacket();
averageLockWaitTimePerPacket = senderStats.getAverageLockWaitTimePerPacket();
@ -330,36 +311,35 @@ int OctreeServer::civetwebRequestHandler(struct mg_connection* connection) {
averageLockWaitTimePerElement = senderStats.getAverageLockWaitTimePerElement();
totalElementsProcessed = senderStats.getTotalElementsProcessed();
totalPacketsProcessed = senderStats.getTotalPacketsProcessed();
averageElementsPerPacket = totalPacketsProcessed == 0 ? 0 : totalElementsProcessed / totalPacketsProcessed;
mg_printf(connection, " Total Inbound Packets: %s packets\r\n",
locale.toString((uint)totalPacketsProcessed).rightJustified(COLUMN_WIDTH, ' ').toLocal8Bit().constData());
mg_printf(connection, " Total Inbound Elements: %s elements\r\n",
locale.toString((uint)totalElementsProcessed).rightJustified(COLUMN_WIDTH, ' ').toLocal8Bit().constData());
mg_printf(connection, " Average Inbound Elements/Packet: %f elements/packet\r\n", averageElementsPerPacket);
mg_printf(connection, " Average Transit Time/Packet: %s usecs\r\n",
locale.toString((uint)averageTransitTimePerPacket).rightJustified(COLUMN_WIDTH, ' ').toLocal8Bit().constData());
mg_printf(connection, " Average Process Time/Packet: %s usecs\r\n",
locale.toString((uint)averageProcessTimePerPacket).rightJustified(COLUMN_WIDTH, ' ').toLocal8Bit().constData());
mg_printf(connection, " Average Wait Lock Time/Packet: %s usecs\r\n",
locale.toString((uint)averageLockWaitTimePerPacket).rightJustified(COLUMN_WIDTH, ' ').toLocal8Bit().constData());
mg_printf(connection, " Average Process Time/Element: %s usecs\r\n",
locale.toString((uint)averageProcessTimePerElement).rightJustified(COLUMN_WIDTH, ' ').toLocal8Bit().constData());
mg_printf(connection, " Average Wait Lock Time/Element: %s usecs\r\n",
locale.toString((uint)averageLockWaitTimePerElement).rightJustified(COLUMN_WIDTH, ' ').toLocal8Bit().constData());
statsString += QString(" Total Inbound Packets: %1 packets\r\n")
.arg(locale.toString((uint)totalPacketsProcessed).rightJustified(COLUMN_WIDTH, ' '));
statsString += QString(" Total Inbound Elements: %1 elements\r\n")
.arg(locale.toString((uint)totalElementsProcessed).rightJustified(COLUMN_WIDTH, ' '));
statsString += QString().sprintf(" Average Inbound Elements/Packet: %f elements/packet\r\n",
averageElementsPerPacket);
statsString += QString(" Average Transit Time/Packet: %1 usecs\r\n")
.arg(locale.toString((uint)averageTransitTimePerPacket).rightJustified(COLUMN_WIDTH, ' '));
statsString += QString(" Average Process Time/Packet: %1 usecs\r\n")
.arg(locale.toString((uint)averageProcessTimePerPacket).rightJustified(COLUMN_WIDTH, ' '));
statsString += QString(" Average Wait Lock Time/Packet: %1 usecs\r\n")
.arg(locale.toString((uint)averageLockWaitTimePerPacket).rightJustified(COLUMN_WIDTH, ' '));
statsString += QString(" Average Process Time/Element: %1 usecs\r\n")
.arg(locale.toString((uint)averageProcessTimePerElement).rightJustified(COLUMN_WIDTH, ' '));
statsString += QString(" Average Wait Lock Time/Element: %1 usecs\r\n")
.arg(locale.toString((uint)averageLockWaitTimePerElement).rightJustified(COLUMN_WIDTH, ' '));
}
mg_printf(connection, "%s", "\r\n");
mg_printf(connection, "%s", "\r\n");
statsString += "\r\n\r\n";
// display memory usage stats
mg_printf(connection, "%s", "<b>Current Memory Usage Statistics</b>\r\n");
mg_printf(connection, "\r\nOctreeElement size... %ld bytes\r\n", sizeof(OctreeElement));
mg_printf(connection, "%s", "\r\n");
statsString += "<b>Current Memory Usage Statistics</b>\r\n";
statsString += QString().sprintf("\r\nOctreeElement size... %ld bytes\r\n", sizeof(OctreeElement));
statsString += "\r\n";
const char* memoryScaleLabel;
const float MEGABYTES = 1000000.f;
const float GIGABYTES = 1000000000.f;
@ -371,82 +351,84 @@ int OctreeServer::civetwebRequestHandler(struct mg_connection* connection) {
memoryScaleLabel = "GB";
memoryScale = GIGABYTES;
}
mg_printf(connection, "Element Node Memory Usage: %8.2f %s\r\n",
OctreeElement::getVoxelMemoryUsage() / memoryScale, memoryScaleLabel);
mg_printf(connection, "Octcode Memory Usage: %8.2f %s\r\n",
OctreeElement::getOctcodeMemoryUsage() / memoryScale, memoryScaleLabel);
mg_printf(connection, "External Children Memory Usage: %8.2f %s\r\n",
OctreeElement::getExternalChildrenMemoryUsage() / memoryScale, memoryScaleLabel);
mg_printf(connection, "%s", " -----------\r\n");
mg_printf(connection, " Total: %8.2f %s\r\n",
OctreeElement::getTotalMemoryUsage() / memoryScale, memoryScaleLabel);
mg_printf(connection, "%s", "\r\n");
mg_printf(connection, "%s", "OctreeElement Children Population Statistics...\r\n");
statsString += QString().sprintf("Element Node Memory Usage: %8.2f %s\r\n",
OctreeElement::getVoxelMemoryUsage() / memoryScale, memoryScaleLabel);
statsString += QString().sprintf("Octcode Memory Usage: %8.2f %s\r\n",
OctreeElement::getOctcodeMemoryUsage() / memoryScale, memoryScaleLabel);
statsString += QString().sprintf("External Children Memory Usage: %8.2f %s\r\n",
OctreeElement::getExternalChildrenMemoryUsage() / memoryScale, memoryScaleLabel);
statsString += " -----------\r\n";
statsString += QString().sprintf(" Total: %8.2f %s\r\n",
OctreeElement::getTotalMemoryUsage() / memoryScale, memoryScaleLabel);
statsString += "\r\n";
statsString += "OctreeElement Children Population Statistics...\r\n";
checkSum = 0;
for (int i=0; i <= NUMBER_OF_CHILDREN; i++) {
checkSum += OctreeElement::getChildrenCount(i);
mg_printf(connection, " Nodes with %d children: %s nodes (%5.2f%%)\r\n", i,
locale.toString((uint)OctreeElement::getChildrenCount(i)).rightJustified(16, ' ').toLocal8Bit().constData(),
((float)OctreeElement::getChildrenCount(i) / (float)nodeCount) * AS_PERCENT);
statsString += QString().sprintf(" Nodes with %d children: %s nodes (%5.2f%%)\r\n", i,
locale.toString((uint)OctreeElement::getChildrenCount(i)).rightJustified(16, ' ').toLocal8Bit().constData(),
((float)OctreeElement::getChildrenCount(i) / (float)nodeCount) * AS_PERCENT);
}
mg_printf(connection, "%s", " ----------------------\r\n");
mg_printf(connection, " Total: %s nodes\r\n",
locale.toString((uint)checkSum).rightJustified(16, ' ').toLocal8Bit().constData());
statsString += " ----------------------\r\n";
statsString += QString(" Total: %1 nodes\r\n")
.arg(locale.toString((uint)checkSum).rightJustified(16, ' '));
#ifdef BLENDED_UNION_CHILDREN
mg_printf(connection, "%s", "\r\n");
mg_printf(connection, "%s", "OctreeElement Children Encoding Statistics...\r\n");
mg_printf(connection, " Single or No Children: %10.llu nodes (%5.2f%%)\r\n",
OctreeElement::getSingleChildrenCount(), ((float)OctreeElement::getSingleChildrenCount() / (float)nodeCount) * AS_PERCENT);
mg_printf(connection, " Two Children as Offset: %10.llu nodes (%5.2f%%)\r\n",
OctreeElement::getTwoChildrenOffsetCount(),
((float)OctreeElement::getTwoChildrenOffsetCount() / (float)nodeCount) * AS_PERCENT);
mg_printf(connection, " Two Children as External: %10.llu nodes (%5.2f%%)\r\n",
OctreeElement::getTwoChildrenExternalCount(),
((float)OctreeElement::getTwoChildrenExternalCount() / (float)nodeCount) * AS_PERCENT);
mg_printf(connection, " Three Children as Offset: %10.llu nodes (%5.2f%%)\r\n",
OctreeElement::getThreeChildrenOffsetCount(),
((float)OctreeElement::getThreeChildrenOffsetCount() / (float)nodeCount) * AS_PERCENT);
mg_printf(connection, " Three Children as External: %10.llu nodes (%5.2f%%)\r\n",
OctreeElement::getThreeChildrenExternalCount(),
((float)OctreeElement::getThreeChildrenExternalCount() / (float)nodeCount) * AS_PERCENT);
mg_printf(connection, " Children as External Array: %10.llu nodes (%5.2f%%)\r\n",
OctreeElement::getExternalChildrenCount(),
((float)OctreeElement::getExternalChildrenCount() / (float)nodeCount) * AS_PERCENT);
statsString += "\r\n";
statsString += "OctreeElement Children Encoding Statistics...\r\n";
statsString += QString().sprintf(" Single or No Children: %10.llu nodes (%5.2f%%)\r\n",
OctreeElement::getSingleChildrenCount(),
((float)OctreeElement::getSingleChildrenCount() / (float)nodeCount) * AS_PERCENT));
statsString += QString().sprintf(" Two Children as Offset: %10.llu nodes (%5.2f%%)\r\n",
OctreeElement::getTwoChildrenOffsetCount(),
((float)OctreeElement::getTwoChildrenOffsetCount() / (float)nodeCount) * AS_PERCENT));
statsString += QString().sprintf(" Two Children as External: %10.llu nodes (%5.2f%%)\r\n",
OctreeElement::getTwoChildrenExternalCount(),
((float)OctreeElement::getTwoChildrenExternalCount() / (float)nodeCount) * AS_PERCENT);
statsString += QString().sprintf(" Three Children as Offset: %10.llu nodes (%5.2f%%)\r\n",
OctreeElement::getThreeChildrenOffsetCount(),
((float)OctreeElement::getThreeChildrenOffsetCount() / (float)nodeCount) * AS_PERCENT);
statsString += QString().sprintf(" Three Children as External: %10.llu nodes (%5.2f%%)\r\n",
OctreeElement::getThreeChildrenExternalCount(),
((float)OctreeElement::getThreeChildrenExternalCount() / (float)nodeCount) * AS_PERCENT);
statsString += QString().sprintf(" Children as External Array: %10.llu nodes (%5.2f%%)\r\n",
OctreeElement::getExternalChildrenCount(),
((float)OctreeElement::getExternalChildrenCount() / (float)nodeCount) * AS_PERCENT);
checkSum = OctreeElement::getSingleChildrenCount() +
OctreeElement::getTwoChildrenOffsetCount() + OctreeElement::getTwoChildrenExternalCount() +
OctreeElement::getThreeChildrenOffsetCount() + OctreeElement::getThreeChildrenExternalCount() +
OctreeElement::getExternalChildrenCount();
mg_printf(connection, "%s", " ----------------\r\n");
mg_printf(connection, " Total: %10.llu nodes\r\n", checkSum);
mg_printf(connection, " Expected: %10.lu nodes\r\n", nodeCount);
mg_printf(connection, "%s", "\r\n");
mg_printf(connection, "%s", "In other news....\r\n");
mg_printf(connection, "could store 4 children internally: %10.llu nodes\r\n",
OctreeElement::getCouldStoreFourChildrenInternally());
mg_printf(connection, "could NOT store 4 children internally: %10.llu nodes\r\n",
OctreeElement::getCouldNotStoreFourChildrenInternally());
OctreeElement::getTwoChildrenOffsetCount() + OctreeElement::getTwoChildrenExternalCount() +
OctreeElement::getThreeChildrenOffsetCount() + OctreeElement::getThreeChildrenExternalCount() +
OctreeElement::getExternalChildrenCount();
statsString += " ----------------\r\n";
statsString += QString().sprintf(" Total: %10.llu nodes\r\n", checkSum);
statsString += QString().sprintf(" Expected: %10.lu nodes\r\n", nodeCount);
statsString += "\r\n";
statsString += "In other news....\r\n";
statsString += QString().sprintf("could store 4 children internally: %10.llu nodes\r\n",
OctreeElement::getCouldStoreFourChildrenInternally());
statsString += QString().sprintf("could NOT store 4 children internally: %10.llu nodes\r\n",
OctreeElement::getCouldNotStoreFourChildrenInternally());
#endif
mg_printf(connection, "%s", "\r\n");
mg_printf(connection, "%s", "\r\n");
mg_printf(connection, "%s", "</pre>\r\n");
mg_printf(connection, "%s", "</doc></html>");
return 1;
statsString += "\r\n\r\n";
statsString += "</pre>\r\n";
statsString += "</doc></html>";
connection->respond(HTTPConnection::StatusCode200, qPrintable(statsString), "text/html");
return true;
} else {
// have mongoose process this request from the document_root
return 0;
// have HTTPManager attempt to process this request from the document_root
return false;
}
}
}
void OctreeServer::setArguments(int argc, char** argv) {
_argc = argc;
@ -553,7 +535,7 @@ void OctreeServer::run() {
const char* statusPort = getCmdOption(_argc, _argv, STATUS_PORT);
if (statusPort) {
int statusPortNumber = atoi(statusPort);
initMongoose(statusPortNumber);
initHTTPManager(statusPortNumber);
}

View file

@ -14,6 +14,8 @@
#include <QDateTime>
#include <QtCore/QCoreApplication>
#include <HTTPManager.h>
#include <ThreadedAssignment.h>
#include <EnvironmentData.h>
@ -23,7 +25,7 @@
#include "OctreeInboundPacketProcessor.h"
/// Handles assignments of type OctreeServer - sending octrees to various clients.
class OctreeServer : public ThreadedAssignment {
class OctreeServer : public ThreadedAssignment, public HTTPRequestHandler {
Q_OBJECT
public:
OctreeServer(const unsigned char* dataBuffer, int numBytes);
@ -40,7 +42,6 @@ public:
JurisdictionMap* getJurisdiction() { return _jurisdiction; }
int getPacketsPerClientPerInterval() const { return _packetsPerClientPerInterval; }
static OctreeServer* GetInstance() { return _theInstance; }
bool isInitialLoadComplete() const { return (_persistThread) ? _persistThread->isInitialLoadComplete() : true; }
bool isPersistEnabled() const { return (_persistThread) ? true : false; }
@ -61,6 +62,8 @@ public:
virtual int sendSpecialPacket(Node* node) { return 0; }
static void attachQueryNodeToNode(Node* newNode);
bool handleHTTPRequest(HTTPConnection* connection, const QString& path);
public slots:
/// runs the voxel server assignment
void run();
@ -69,9 +72,14 @@ public slots:
void nodeKilled(SharedNodePointer node);
protected:
void parsePayload();
void initHTTPManager(int port);
int _argc;
const char** _argv;
char** _parsedArgV;
HTTPManager* _httpManager;
char _persistFilename[MAX_FILENAME_LENGTH];
int _packetsPerClientPerInterval;
@ -85,12 +93,10 @@ protected:
OctreeInboundPacketProcessor* _octreeInboundPacketProcessor;
OctreePersistThread* _persistThread;
void parsePayload();
void initMongoose(int port);
static int civetwebRequestHandler(struct mg_connection *connection);
static OctreeServer* _theInstance;
static OctreeServer* _instance;
time_t _started;
uint64_t _startedUSecs;
uint64_t _startedUSecs;
};
#endif // __octree_server__OctreeServer__

View file

@ -33,3 +33,5 @@ link_hifi_library(octree ${TARGET_NAME} ${ROOT_DIR})
link_hifi_library(octree-server ${TARGET_NAME} ${ROOT_DIR})
link_hifi_library(particles ${TARGET_NAME} ${ROOT_DIR})
# link in the embedded webserver
link_hifi_library(embedded-webserver ${TARGET_NAME} ${ROOT_DIR})

View file

@ -28,7 +28,10 @@ target_link_libraries(${TARGET_NAME} ${ZLIB_LIBRARIES})
include(${MACRO_DIR}/LinkHifiLibrary.cmake)
link_hifi_library(shared ${TARGET_NAME} ${ROOT_DIR})
# link in the embedded webserver
link_hifi_library(embedded-webserver ${TARGET_NAME} ${ROOT_DIR})
# link in the hifi octree library
link_hifi_library(octree ${TARGET_NAME} ${ROOT_DIR})
link_hifi_library(octree-server ${TARGET_NAME} ${ROOT_DIR})
link_hifi_library(voxels ${TARGET_NAME} ${ROOT_DIR})
link_hifi_library(voxels ${TARGET_NAME} ${ROOT_DIR})