diff --git a/Makefile b/Makefile index 4fbf99cc..b6d8d3dc 100644 --- a/Makefile +++ b/Makefile @@ -140,7 +140,7 @@ ICG_EXE := ${TRICK_HOME}/bin/trick-ICG ################################################################################ # DEFAULT TARGET # 1 Build Trick-core and Trick Data-products. -all: no_dp dp +all: no_dp webserver dp @ echo ; echo "Trick compilation complete:" ; date ifeq ($(USE_JAVA), 1) @@ -208,6 +208,12 @@ $(SWIG_DIRS): icg_sim_serv $(TRICK_LIB_DIR) dp: ${TRICK_HOME}/trick_source/trick_utils/units @ $(MAKE) -C ${TRICK_HOME}/trick_source/data_products +#------------------------------------------------------------------------------- +# +.PHONY: webserver +webserver: + @ $(MAKE) -C ${TRICK_HOME}/trick_source/web/HttpServer + #------------------------------------------------------------------------------- # 1.3 Build Trick's Java Tools java: diff --git a/include/trick/WebServer.hh b/include/trick/WebServer.hh new file mode 100644 index 00000000..495623f4 --- /dev/null +++ b/include/trick/WebServer.hh @@ -0,0 +1,63 @@ +/************************************************************************* +PURPOSE: (Represent the state and initial conditions of an http server.) +**************************************************************************/ +#ifndef WEB_SERVER_H +#define WEB_SERVER_H + +#include +#include +#include +#ifndef SWIG +#include "mongoose/mongoose.h" +#endif +#include "trick/WebSocketSession.hh" + +typedef void (*httpMethodHandler)(struct mg_connection *, struct http_message *); +typedef WebSocketSession* (*WebSocketSessionMaker)(struct mg_connection *nc); + +class WebServer { + public: + const char* port; + const char* document_root; + struct mg_mgr mgr; /* ** mongoose */ + struct mg_connection *listener; /* ** mongoose */ + pthread_t server_thread; /* ** */ + bool shutting_down; + + std::map< std::string, httpMethodHandler> httpGETHandlerMap; /* ** */ + pthread_mutex_t httpGETHandlerMapLock; /* ** */ + + std::map< std::string, WebSocketSessionMaker> WebSocketSessionMakerMap; /* ** */ + pthread_mutex_t WebSocketSessionMakerMapLock; /* ** */ + + std::map webSocketSessionMap; /* ** */ + pthread_mutex_t webSocketSessionMapLock; /* ** */ + + pthread_mutex_t serviceLock; /* ** */ + struct mg_serve_http_opts http_server_options; /* ** */ + struct mg_bind_opts bind_opts; /* ** */ + pthread_cond_t serviceConnections; /* ** */ + bool service_websocket; + bool time_homogeneous; + bool sessionDataMarshalled; + + // Trick Job-functions + int http_default_data(); + int http_init(); + int http_top_of_frame(); + int http_shutdown(); + + void installWebSocketSessionMaker(std::string name, WebSocketSessionMaker maker); + void installHTTPGEThandler(std::string handlerName, httpMethodHandler handler); + + // These are internals, and should not be considered public. They are not private only + // because they need to be callable from the servers event handler. + void sendWebSocketSessionMessages(struct mg_connection *nc); + void handleWebSocketClientMessage(struct mg_connection *nc, std::string msg); + void addWebSocketSession(struct mg_connection *nc, WebSocketSession* session); + void deleteWebSocketSession(struct mg_connection *nc); + WebSocketSession* makeWebSocketSession(struct mg_connection *nc, std::string name); + void handleHTTPGETrequest(struct mg_connection *nc, http_message *hm, std::string handlerName); + void marshallWebSocketSessionData(); +}; +#endif diff --git a/include/trick/WebSocketSession.hh b/include/trick/WebSocketSession.hh new file mode 100644 index 00000000..e9421aae --- /dev/null +++ b/include/trick/WebSocketSession.hh @@ -0,0 +1,28 @@ +/************************************************************************* +PURPOSE: (Represent Websocket connection.) +**************************************************************************/ +#ifndef WEB_SOCKET_SESSION_HH +#define WEB_SOCKET_SESSION_HH + +#include +#ifndef SWIG +#include "mongoose/mongoose.h" +#endif + +class WebSocketSession { + public: + WebSocketSession(struct mg_connection *nc):connection(nc){}; + virtual ~WebSocketSession() {}; + + /** + When HTTP_Server::time_homogeneous is set, WebSocketSession::marshallData() is called from the main + sim thread in a "top_of_frame" job, so that all of the data can be staged at + the same sim-time, in other words it's time-homogeneous. + */ + virtual void marshallData()=0; + virtual void sendMessage()=0; + virtual int handleMessage(std::string)=0; + + struct mg_connection* connection; +}; +#endif diff --git a/include/trick/files_to_ICG.hh b/include/trick/files_to_ICG.hh index 9de3bd22..0218f192 100644 --- a/include/trick/files_to_ICG.hh +++ b/include/trick/files_to_ICG.hh @@ -36,6 +36,8 @@ #include "trick/RealtimeSync.hh" #include "trick/ITimer.hh" #include "trick/VariableServer.hh" +#include "trick/WebServer.hh" +#include "trick/WebSocketSession.hh" #include "trick/regula_falsi.h" #include "trick/Integrator.hh" #include "trick/IntegAlgorithms.hh" diff --git a/share/trick/makefiles/Makefile.common b/share/trick/makefiles/Makefile.common index f44d5484..8780da15 100644 --- a/share/trick/makefiles/Makefile.common +++ b/share/trick/makefiles/Makefile.common @@ -65,7 +65,7 @@ export TRICK_PYTHON_PATH := $(TRICK_PYTHON_PATH) export TRICK_GTE_EXT := $(TRICK_GTE_EXT) export TRICK_HOST_CPU := $(shell TRICK_FORCE_32BIT=$(TRICK_FORCE_32BIT) $(TRICK_HOME)/bin/trick-gte TRICK_HOST_CPU) export TRICK_EXEC_LINK_LIBS := ${PTHREAD_LIBS} $(PYTHON_LIB) $(UDUNITS_LDFLAGS) $(PLATFORM_LIBS) -lm -ldl -export TRICK_LIBS := ${RPATH} -L${TRICK_LIB_DIR} -ltrick -ltrick_pyip -ltrick_comm -ltrick_math -ltrick_units -ltrick_mm +export TRICK_LIBS := ${RPATH} -L${TRICK_LIB_DIR} -ltrick -ltrick_pyip -ltrick_comm -ltrick_math -ltrick_units -ltrick_mm -ltrickHTTP -lmongoose export TRICK_SYSTEM_LDFLAGS := $(TRICK_SYSTEM_LDFLAGS) export TRICK_SWIG_FLAGS := $(TRICK_SWIG_FLAGS) export TRICK_SWIG_CFLAGS := $(TRICK_SWIG_CFLAGS) diff --git a/share/trick/sim_objects/WebServer.sm b/share/trick/sim_objects/WebServer.sm new file mode 100644 index 00000000..66774b5f --- /dev/null +++ b/share/trick/sim_objects/WebServer.sm @@ -0,0 +1,20 @@ +/************************TRICK HEADER************************* +PURPOSE: (Trick HTTP Server) +*************************************************************/ +##include "trick/WebServer.hh" + +class WebServerSimObject : public Trick::SimObject { + + public: + WebServer server ; + + WebServerSimObject() { + ("default_data") server.http_default_data() ; + ("initialization") server.http_init() ; + ("freeze") server.http_top_of_frame() ; + ("top_of_frame") server.http_top_of_frame() ; + ("shutdown") server.http_shutdown() ; + } +}; + +WebServerSimObject web; diff --git a/trick_sims/Cannon/SIM_cannon_numeric/S_define b/trick_sims/Cannon/SIM_cannon_numeric/S_define index 3ba5e762..6ca52f4a 100644 --- a/trick_sims/Cannon/SIM_cannon_numeric/S_define +++ b/trick_sims/Cannon/SIM_cannon_numeric/S_define @@ -1,4 +1,4 @@ -/************************TRICK HEADER************************* +/***********************TRICK HEADER************************* PURPOSE: (Cannon Numeric) LIBRARY DEPENDENCIES: @@ -9,12 +9,9 @@ LIBRARY DEPENDENCIES: *************************************************************/ #include "sim_objects/default_trick_sys.sm" - -#ifdef TRICK_HTTP -#include "mongoose_httpd/mongoose_httpd.sm" -#endif - +#include "sim_objects/WebServer.sm" ##include "cannon/gravity/include/cannon_numeric.h" + class CannonSimObject : public Trick::SimObject { public: diff --git a/trick_sims/Cannon/SIM_cannon_numeric/S_overrides.mk b/trick_sims/Cannon/SIM_cannon_numeric/S_overrides.mk index 7a61ba06..b9f6c9af 100644 --- a/trick_sims/Cannon/SIM_cannon_numeric/S_overrides.mk +++ b/trick_sims/Cannon/SIM_cannon_numeric/S_overrides.mk @@ -1,8 +1,2 @@ TRICK_CFLAGS += -I../models TRICK_CXXFLAGS += -I../models - -# Uncomment these for an embedded webserver. -#TRICK_SFLAGS += -DTRICK_HTTP -#TRICK_SFLAGS += -I../models -#TRICK_LDFLAGS += -L/usr/local/lib -#TRICK_USER_LINK_LIBS += -lmongoose diff --git a/trick_source/web/HttpServer/include/VariableServerSession.hh b/trick_source/web/HttpServer/include/VariableServerSession.hh new file mode 100644 index 00000000..0c0a13eb --- /dev/null +++ b/trick_source/web/HttpServer/include/VariableServerSession.hh @@ -0,0 +1,45 @@ +/************************************************************************* +PURPOSE: (Represent the state of a variable server websocket connection.) +**************************************************************************/ + +#ifndef WSSESSION_HH +#define WSSESSION_HH + +#include +#include +#include "mongoose/mongoose.h" +#include "trick/WebSocketSession.hh" +#include "VariableServerVariable.hh" + +class VariableServerSession : public WebSocketSession { + public: + VariableServerSession(struct mg_connection *nc); + ~VariableServerSession(); + void marshallData(); /* -- base */ + void sendMessage(); /* -- base */ + int handleMessage(std::string); /* -- base */ + + void setTimeInterval(unsigned int milliseconds); + void addVariable(char* vname); + void stageValues(); + void pause(); + void unpause(); + void clear(); + void exit(); + + static int bad_ref_int ; + + private: + int sendErrorMessage(const char* fmt, ... ); + REF2* make_error_ref(const char* in_name); + double stageTime; + bool dataStaged; + + std::vector sessionVariables; + bool cyclicSendEnabled; + long long nextTime; + long long intervalTimeTics; +}; + +WebSocketSession* makeVariableServerSession( struct mg_connection *nc ); +#endif diff --git a/trick_source/web/HttpServer/include/VariableServerVariable.hh b/trick_source/web/HttpServer/include/VariableServerVariable.hh new file mode 100644 index 00000000..415cfde4 --- /dev/null +++ b/trick_source/web/HttpServer/include/VariableServerVariable.hh @@ -0,0 +1,34 @@ +/************************************************************************* +PURPOSE: (Represent Websocket variable server variable.) +LIBRARY DEPENDENCIES: + ( (../src/VariableServerVariable.o)) +**************************************************************************/ +#ifndef VARIABLE_SERVER_VARIABLE_HH +#define VARIABLE_SERVER_VARIABLE_HH + +#include +#include +#include "mongoose/mongoose.h" +#include +#include + +#define MAX_ARRAY_LENGTH 4096 + +class VariableServerVariable { + + public: + VariableServerVariable( REF2* variableType); + ~VariableServerVariable(); + const char* getName(); + void stageValue(); + void writeValue( std::ostream& chkpnt_os ); + + private: + VariableServerVariable() {} + REF2 *varInfo; + void *address; + int size; + void *stageBuffer; + bool deref; + }; +#endif diff --git a/trick_source/web/HttpServer/include/http_GET_handlers.hh b/trick_source/web/HttpServer/include/http_GET_handlers.hh new file mode 100644 index 00000000..671584b2 --- /dev/null +++ b/trick_source/web/HttpServer/include/http_GET_handlers.hh @@ -0,0 +1,14 @@ +/************************************************************************* +PURPOSE: (Represent Websocket variable server connection.) +LIBRARY DEPENDENCIES: + ( (../src/http_GET_handlers.o)) +**************************************************************************/ +#ifndef HANDLE_HTTP_GET_HANDLERS_HH +#define HANDLE_HTTP_GET_HANDLERS_HH + +#include "mongoose/mongoose.h" + +void handle_HTTP_GET_vs_connections(struct mg_connection *nc, struct http_message *hm); +void handle_HTTP_GET_alloc_info(struct mg_connection *nc, struct http_message *hm); + +#endif diff --git a/trick_source/web/HttpServer/include/simpleJSON.hh b/trick_source/web/HttpServer/include/simpleJSON.hh new file mode 100644 index 00000000..b8c4a1e1 --- /dev/null +++ b/trick_source/web/HttpServer/include/simpleJSON.hh @@ -0,0 +1,21 @@ +/************************************************************************* +PURPOSE: (Represent Websocket variable server connection.) +LIBRARY DEPENDENCIES: + ( (../src/WSSession.o)) +**************************************************************************/ +#ifndef SIMPLEJSON_HH +#define SIMPLEJSON_HH + +#include + +class Member { + public: + const char* key; + const char* valText; + int type; + Member(const char *k, const char *v, int t); +}; + +std::vector parseJSON( const char *json_s); + +#endif diff --git a/trick_source/web/HttpServer/makefile b/trick_source/web/HttpServer/makefile new file mode 100644 index 00000000..7a02298e --- /dev/null +++ b/trick_source/web/HttpServer/makefile @@ -0,0 +1,88 @@ +include ${TRICK_HOME}/share/trick/makefiles/Makefile.common + +RM = rm -rf +CC = cc +CPP = c++ +CURL = curl +MV = mv +CP = cp +MKDIR = mkdir + +CFLAGS = -g -Wall +CPPFLAGS = -g -Wall -std=c++11 + +INCLUDE_DIRS = -Iinclude -I${TRICK_HOME}/include + +OBJDIR = obj +LIBDIR = lib +INCDIR = include + +#TRICK_LIB_DIR comes from Makefile.common + +MONGOOSE_OBJS = ${OBJDIR}/mongoose.o +MONGOOSE_INCDIR = ${TRICK_HOME}/include/mongoose + +TRICK_HTTP_OBJS = \ + ${OBJDIR}/VariableServerSession.o \ + ${OBJDIR}/VariableServerVariable.o \ + ${OBJDIR}/http_GET_handlers.o \ + ${OBJDIR}/WebServer.o \ + ${OBJDIR}/simpleJSON.o + +############################################################################# +## MODEL TARGETS ## +############################################################################# + +all: build_libs + +build_libs: ${TRICK_LIB_DIR}/libmongoose.a ${TRICK_LIB_DIR}/libtrickHTTP.a + +${TRICK_LIB_DIR}/libmongoose.a: ${LIBDIR}/libmongoose.a ${MONGOOSE_INCDIR}/mongoose.h + $(MV) ${LIBDIR}/libmongoose.a ${TRICK_LIB_DIR} + +${TRICK_LIB_DIR}/libtrickHTTP.a: ${LIBDIR}/libtrickHTTP.a + $(MV) ${LIBDIR}/libtrickHTTP.a ${TRICK_LIB_DIR} + +${MONGOOSE_INCDIR}/mongoose.h: mongoose.h | ${MONGOOSE_INCDIR} + $(CP) mongoose.h ${MONGOOSE_INCDIR}/mongoose.h + +${MONGOOSE_INCDIR}: + $(MKDIR) -p ${MONGOOSE_INCDIR} + +# Build Mongoose Library +mongoose.h: + $(CURL) --retry 4 -O https://raw.githubusercontent.com/cesanta/mongoose/master/mongoose.h + +mongoose.c: + $(CURL) --retry 4 -O https://raw.githubusercontent.com/cesanta/mongoose/master/mongoose.c + +${MONGOOSE_OBJS}: mongoose.h mongoose.c | $(OBJDIR) + $(CC) $(CFLAGS) -c -o ${MONGOOSE_OBJS} mongoose.c + +${LIBDIR}/libmongoose.a: ${MONGOOSE_OBJS} | ${LIBDIR} + ar crs $@ ${MONGOOSE_OBJS} + + +# Build Trick HTTP Server Library +$(TRICK_HTTP_OBJS): $(OBJDIR)/%.o : src/%.cpp ${MONGOOSE_INCDIR}/mongoose.h | $(OBJDIR) + $(CPP) $(CPPFLAGS) ${INCLUDE_DIRS} -c $< -o $@ + +${LIBDIR}/libtrickHTTP.a: ${TRICK_HTTP_OBJS} | ${LIBDIR} + ar crs $@ ${TRICK_HTTP_OBJS} + +# --------------------------------- + +${OBJDIR}: + mkdir -p ${OBJDIR} + +${LIBDIR}: + mkdir -p ${LIBDIR} + +clean: + ${RM} *~ + ${RM} ${OBJDIR} + +spotless: clean + ${RM} ${LIBDIR} + ${RM} mongoose.h mongoose.c + diff --git a/trick_source/web/HttpServer/src/VariableServerSession.cpp b/trick_source/web/HttpServer/src/VariableServerSession.cpp new file mode 100644 index 00000000..ab39a765 --- /dev/null +++ b/trick_source/web/HttpServer/src/VariableServerSession.cpp @@ -0,0 +1,225 @@ +/************************************************************************ +PURPOSE: (Represent the state and initial conditions of an http server) +LIBRARY DEPENDENCIES: + ((simpleJSON.o) + (VariableServerVariable.o) + ) +**************************************************************************/ +#include +#include +#include +#include // for setprecision +#include +#include "trick/memorymanager_c_intf.h" +#include "trick/input_processor_proto.h" +#include "trick/exec_proto.h" +#include "../include/VariableServerSession.hh" +#include "../include/simpleJSON.hh" + +// CONSTRUCTOR +VariableServerSession::VariableServerSession( struct mg_connection *nc ) : WebSocketSession(nc) { + intervalTimeTics = exec_get_time_tic_value(); // Default time interval is one second. + nextTime = 0; + cyclicSendEnabled = false; +} + +// DESTRUCTOR +VariableServerSession::~VariableServerSession() { + clear(); +} + +/* Base class virtual function: marshallData + When HTTP_Server::time_homogeneous is set, WebSocketSession::marshallData() is + called from the main sim thread in a "top_of_frame" job, to ensure that all of + the data is staged at the same sim-time, in other words that it's time-homogeneous. +*/ +/* VariableServerSession::marshallData() conditionally stages message data when + sim_time has reached the next integer multiple of intervalTimeTics + (The specified period between messages). +*/ +void VariableServerSession::marshallData() { + long long simulation_time_tics = exec_get_time_tics(); + if ( cyclicSendEnabled && ( simulation_time_tics >= nextTime )) { + stageValues(); + nextTime = (simulation_time_tics - (simulation_time_tics % intervalTimeTics) + intervalTimeTics); + } +} + +/* Base class virtual function: sendMessage + if data is staged/marshalled, then compose and send a message containing that data. + */ +void VariableServerSession::sendMessage() { + std::vector::iterator it; + std::stringstream ss; + + if (dataStaged) { + ss << "{ \"msg_type\" : \"values\",\n"; + ss << " \"time\" : " << std::setprecision(16) << stageTime << ",\n"; + ss << " \"values\" : [\n"; + + for (it = sessionVariables.begin(); it != sessionVariables.end(); it++ ) { + if (it != sessionVariables.begin()) ss << ",\n"; + (*it)->writeValue(ss); + } + ss << "]}" << std::endl; + std::string tmp = ss.str(); + const char * message = tmp.c_str(); + mg_send_websocket_frame(connection, WEBSOCKET_OP_TEXT, message, strlen(message)); + dataStaged = false; + } +} + +// Base class virtual function. +int VariableServerSession::handleMessage(std::string client_msg) { + + int status = 0; + std::vector members = parseJSON(client_msg.c_str()); + std::vector::iterator it; + std::string cmd; + std::string var_name; + std::string pycode; + int period; + + for (it = members.begin(); it != members.end(); it++ ) { + if (strcmp((*it)->key, "cmd") == 0) { + cmd = (*it)->valText; + } else if (strcmp((*it)->key, "var_name") == 0) { + var_name = (*it)->valText; + } else if (strcmp((*it)->key, "period") == 0) { + period = atoi((*it)->valText); + } else if (strcmp((*it)->key, "pycode") == 0) { + pycode = (*it)->valText; + } + } + + if (cmd.empty()) { + printf ("No \"cmd\" member found in client message.\n"); + status = 1; + } else if (cmd == "var_add") { + addVariable( strdup( var_name.c_str())); + } else if (cmd == "var_cycle") { + setTimeInterval(period); + } else if (cmd == "var_pause") { + pause(); + } else if (cmd == "var_unpause") { + unpause(); + } else if (cmd == "var_send") { + // var_send responses are not guarenteed to be time-consistent. + stageValues(); + sendMessage(); + } else if (cmd == "var_clear") { + clear(); + } else if (cmd == "var_exit") { + //TODO + // nc->flags |= MG_F_SEND_AND_CLOSE; + } else if (cmd == "python") { + // Remove carriage-returns from pycode. + pycode.erase(std::remove(pycode.begin(), pycode.end(), '\r'), pycode.end()); + // Call the Trick input processor. + ip_parse(pycode.c_str()); + } else { + sendErrorMessage("Unknown Command: \"%s\".\n", cmd.c_str()); + status = 1; + } + return status; +} + +void VariableServerSession::setTimeInterval(unsigned int milliseconds) { + // CONSIDER: should we compare this with the realtime frame, and limit accordingly. + intervalTimeTics = exec_get_time_tic_value() * milliseconds / 1000; +} + +void VariableServerSession::addVariable(char* vname){ + REF2 * new_ref ; + new_ref = ref_attributes(vname); + if ( new_ref == NULL ) { + sendErrorMessage("Variable Server could not find variable %s.\n", vname); + new_ref = make_error_ref(vname); + } else if ( new_ref->attr ) { + if ( new_ref->attr->type == TRICK_STRUCTURED ) { + sendErrorMessage("Variable Server: var_add cant add \"%s\" because its a composite variable.\n", vname); + free(new_ref); + new_ref = make_error_ref(vname); + + } else if ( new_ref->attr->type == TRICK_STL ) { + sendErrorMessage("Variable Server: var_add cant add \"%s\" because its an STL variable.\n", vname); + free(new_ref); + new_ref = make_error_ref(vname); + } + } else { + sendErrorMessage("Variable Server: BAD MOJO - Missing ATTRIBUTES."); + free(new_ref); + new_ref = make_error_ref(vname); + } + + if ( new_ref != NULL ) { + // This REF2 object will "belong" to the VariableServerSessionVariable, so it has + // the right and responsibility to free() it in its destructor. + VariableServerVariable *sessionVariable = new VariableServerVariable( new_ref ) ; + sessionVariables.push_back( sessionVariable ) ; + } +} + +void VariableServerSession::stageValues() { + stageTime = (double)exec_get_time_tics() / exec_get_time_tic_value(); + std::vector::iterator it; + for (it = sessionVariables.begin(); it != sessionVariables.end(); it++ ) { + (*it)->stageValue(); + } + dataStaged = true; +} + +void VariableServerSession::pause() { cyclicSendEnabled = false; } + +void VariableServerSession::unpause() { cyclicSendEnabled = true; } + +void VariableServerSession::clear() { + std::vector::iterator it; + it = sessionVariables.begin(); + while (it != sessionVariables.end()) { + delete *it; + it = sessionVariables.erase(it); + } +} + +void VariableServerSession::exit() {} + +int VariableServerSession::bad_ref_int = 0 ; + +#define MAX_MSG_SIZE 4096 +int VariableServerSession::sendErrorMessage(const char* fmt, ... ) { + char errText[MAX_MSG_SIZE]; + char msgText[MAX_MSG_SIZE]; + va_list args; + + errText[0]=0; + msgText[0]=0; + + va_start(args, fmt); + (void) vsnprintf(errText, MAX_MSG_SIZE, fmt, args); + va_end(args); + + sprintf(msgText, "{ \"msg_type\" : \"error\",\n" + " \"error\" : \"%s\"}\n", errText); + + mg_send_websocket_frame(connection, WEBSOCKET_OP_TEXT, msgText, strlen(msgText)); + return (0); +} + +REF2* VariableServerSession::make_error_ref(const char* in_name) { + REF2* new_ref; + new_ref = (REF2*)calloc(1, sizeof(REF2)); + new_ref->reference = strdup(in_name) ; + new_ref->units = NULL ; + new_ref->address = (char *)&bad_ref_int ; + new_ref->attr = (ATTRIBUTES*)calloc(1, sizeof(ATTRIBUTES)) ; + new_ref->attr->type = TRICK_NUMBER_OF_TYPES ; + new_ref->attr->units = (char *)"--" ; + new_ref->attr->size = sizeof(int) ; + return new_ref; +} + +// WebSocketSessionMaker function for a VariableServerSession. +WebSocketSession* makeVariableServerSession( struct mg_connection *nc ) { + return new VariableServerSession(nc); +} diff --git a/trick_source/web/HttpServer/src/VariableServerVariable.cpp b/trick_source/web/HttpServer/src/VariableServerVariable.cpp new file mode 100644 index 00000000..4bfa50e7 --- /dev/null +++ b/trick_source/web/HttpServer/src/VariableServerVariable.cpp @@ -0,0 +1,184 @@ +#include "trick/memorymanager_c_intf.h" // for get_size. +#include "../include/VariableServerVariable.hh" +#include // for fpclassify +#include // for setprecision + +VariableServerVariable::VariableServerVariable(REF2 * ref ) { + varInfo = ref; + address = varInfo->address; + size = varInfo->attr->size ; + deref = false; + + TRICK_TYPE string_type = varInfo->attr->type ; + + if ( varInfo->num_index == varInfo->attr->num_index ) { + // single value + } else if ( varInfo->attr->index[varInfo->attr->num_index - 1].size != 0 ) { + // Constrained array + for ( int i = varInfo->attr->num_index-1; i > varInfo->num_index-1 ; i-- ) { + size *= varInfo->attr->index[i].size ; + } + } else { + // Unconstrained array + if ((varInfo->attr->num_index - varInfo->num_index) > 1 ) { + printf("Variable Server Error: var_add(%s) requests more than one dimension of dynamic array.\n", varInfo->reference); + printf("Data is not contiguous so returned values are unpredictable.\n") ; + } + if ( varInfo->attr->type == TRICK_CHARACTER ) { + string_type = TRICK_STRING ; + deref = true; + } else if ( varInfo->attr->type == TRICK_WCHAR ) { + string_type = TRICK_WSTRING ; + } else { + deref = true ; + size *= get_size((char*)address) ; + } + } + // handle strings: set a max buffer size, the copy size may vary so will be set in copy_sim_data + if (( string_type == TRICK_STRING ) || ( string_type == TRICK_WSTRING )) { + size = MAX_ARRAY_LENGTH ; + } + stageBuffer = calloc(size, 1) ; +} + +VariableServerVariable::~VariableServerVariable() { + if (varInfo != NULL) free( varInfo ); +} + + +const char* VariableServerVariable::getName() { + return varInfo->reference; +} + +static void write_quoted_str( std::ostream& os, const char* s) { + int ii; + int len = strlen(s); + os << "\"" ; + for (ii=0 ; ii bytes from
to staging_point. + + if ( varInfo->attr->type == TRICK_STRING ) { + if (address == NULL) { + size = 0 ; + } else { + size = strlen((char*)varInfo->address) + 1 ; + } + } + + if (address != NULL) { + memcpy(stageBuffer, address, size); + } +} + +void VariableServerVariable::writeValue( std::ostream& outs ) { + + switch(varInfo->attr->type) { + case TRICK_UNSIGNED_CHARACTER: + outs << std::dec << (int)*(unsigned char*)stageBuffer ; + break; + case TRICK_BOOLEAN: + if (*(bool*)stageBuffer) { + outs << "\"true\"" ; + } else { + outs << "\"false\"" ; + } + break; + case TRICK_CHARACTER: + if (isprint( *(char*)stageBuffer) ) { + outs << "'" << *(char*)stageBuffer << "'" ; + } else { + unsigned int ch = *(unsigned char*)stageBuffer; + outs << "'\\x" << std::hex << ch << "'" ; + } + break; + case TRICK_WCHAR: + outs << std::dec << *(wchar_t*)stageBuffer; + break; + case TRICK_SHORT: + outs << std::dec << *(short*)stageBuffer; + break; + case TRICK_UNSIGNED_SHORT: + outs << std::dec << *(unsigned short*)stageBuffer; + break; + case TRICK_INTEGER: + outs << std::dec << *(int*)stageBuffer; + break; + case TRICK_UNSIGNED_INTEGER: + outs << std::dec << *(unsigned int*)stageBuffer; + break; + case TRICK_LONG: + outs << std::dec << *(long*)stageBuffer; + break; + case TRICK_UNSIGNED_LONG: + outs << std::dec << *(unsigned long*)stageBuffer; + break; + case TRICK_FLOAT: + if (fpclassify( *(float*)stageBuffer) != FP_NAN) { + outs << std::setprecision(8) << *(float*)stageBuffer; + } else { + outs << "NAN"; + } + break; + case TRICK_DOUBLE: + if (fpclassify( *(double*)stageBuffer) != FP_NAN) { + outs << std::setprecision(16) << *(double*)stageBuffer; + } else { + outs << "NAN"; + } + break; +// case TRICK_BITFIELD: { +// int sbf = 0; +// src_addr = (char*)stageBuffer + offset * (size_t)attr->size; +// if (attr->size == sizeof(int)) { +// sbf = extract_bitfield_any( *(int*)src_addr, attr->size, attr->index[0].start, attr->index[0].size); +// } else if (attr->size == sizeof(short)) { +// sbf = extract_bitfield_any( *(short*)src_addr, attr->size, attr->index[0].start, attr->index[0].size); +// } else if (attr->size == sizeof(char)) { +// sbf = extract_bitfield_any( *(char*)src_addr, attr->size, attr->index[0].start, attr->index[0].size); +// } else { +// message_publish(MSG_ERROR, "Checkpoint Agent INTERNAL ERROR:\n" +// "Unsupported bitfield size (%d) bytes.\n", attr->size) ; +// } +// outs << std::dec << sbf; +// } break; +// case TRICK_UNSIGNED_BITFIELD: { +// int bf = 0; +// src_addr = (char*)stageBuffer + offset * (size_t)attr->size; +// if (attr->size == sizeof(int)) { +// bf = extract_unsigned_bitfield_any( *(unsigned int*)src_addr, attr->size, attr->index[0].start, attr->index[0].size); +// } else if (attr->size == sizeof(short)) { +// bf = extract_unsigned_bitfield_any( *(unsigned short*)src_addr, attr->size, attr->index[0].start, attr->index[0].size); +// } else if (attr->size == sizeof(char)) { +// bf = extract_unsigned_bitfield_any( *(unsigned char*)src_addr, attr->size, attr->index[0].start, attr->index[0].size); +// } else { +// message_publish(MSG_ERROR, "Checkpoint Agent INTERNAL ERROR:\n" +// "Unsupported bitfield size (%d) bytes.\n", attr->size) ; +// } +// outs << std::dec << bf; +// } break; + case TRICK_LONG_LONG: + outs << std::dec << *(long long*)stageBuffer; + break; + case TRICK_UNSIGNED_LONG_LONG: + outs << std::dec << *(unsigned long long*)stageBuffer; + break; + case TRICK_STRING: + write_quoted_str(outs, (*(std::string*)stageBuffer).c_str()); + break; + default: + outs << "\"Error\""; // ERROR + break; + } +} diff --git a/trick_source/web/HttpServer/src/WebServer.cpp b/trick_source/web/HttpServer/src/WebServer.cpp new file mode 100644 index 00000000..e6e9e17f --- /dev/null +++ b/trick_source/web/HttpServer/src/WebServer.cpp @@ -0,0 +1,415 @@ +/************************************************************************ +PURPOSE: (Represent the state and initial conditions of an http server) +**************************************************************************/ +#include // for mkdir() +#include // for symlink(), access() +#include // for getenv() +#include // for opendir(), readdir() +#include +#include +#include "trick/WebServer.hh" +#include "../include/http_GET_handlers.hh" +#include "../include/VariableServerSession.hh" +#include +#include +static const struct mg_str s_get_method = MG_MK_STR("GET"); +static const struct mg_str s_put_method = MG_MK_STR("PUT"); +static const struct mg_str s_delete_method = MG_MK_STR("DELETE"); +static const struct mg_str http_api_prefix = MG_MK_STR("/api/http/"); +static const struct mg_str ws_api_prefix = MG_MK_STR("/api/ws/"); + +static const char * style_css = + "h1 {" + "font-family: fantasy, cursive, serif;" + "font-size: 32px;" + "margin-left: 1em;" + "}" + "h2 {" + "font-family: sans-serif;" + "font-size: 18px;" + "margin-left: 1em;" + "}" + "a {" + "font-family: sans-serif;" + "font-size: 16px;" + "}" + "div.header { background-image: linear-gradient(#afafff, white); }"; + +static const char * index_html = + "\n" + "\n" + "\n" + "\n" + "Trick Simulation\n" + "
\n" + "\n" + "\n" + "\n" + "

Trick Simulation

\n" + "
\n" + "\n" + "\n" + "
\n" + "\n" + "
\n" + "
\n" + "\n" + "
\n" + "\n" + ""; + +static int confirmDocumentRoot ( std::string documentRoot ) { + + if ( access( documentRoot.c_str(), F_OK ) != -1 ) { + std::cout << "Trick Webserver: Document root \"" << documentRoot + << "\" already exists." << std::endl; + } else { + std::cout << "Trick Webserver: Document root \"" << documentRoot + << "\" doesn't exist so, we'll create it." + << std::endl; + + char* trick_home = getenv("TRICK_HOME"); + std::string trickHome = std::string(trick_home); + + if (trick_home != NULL) { + if ( mkdir( documentRoot.c_str(), 0700) == 0) { + + std::string styleFilePath = documentRoot + "/style.css"; + std::fstream style_fs (styleFilePath, std::fstream::out); + style_fs << style_css << std::endl; + style_fs.close(); + + std::string appsDirPath = documentRoot + "/apps"; + if ( mkdir( appsDirPath.c_str(), 0700) == 0) { + DIR *dr; + struct dirent * dir_entry; + std::string trickAppsDirPath = trickHome + "/trick_source/web/apps"; + if ( (dr = opendir(trickAppsDirPath.c_str())) != NULL) { + while (( dir_entry = readdir(dr)) != NULL) { + std::string fName = std::string( dir_entry->d_name); + std::string sPath = trickAppsDirPath + '/' + fName; + std::string dPath = appsDirPath + '/' + fName; + symlink(sPath.c_str(), dPath.c_str()); + } + } + } else { + std::cout << "Failed to create \"" << appsDirPath << "\"." << std::endl; + return 1; + } + + std::string imagesDirPath = documentRoot + "/images"; + if ( mkdir( imagesDirPath.c_str(), 0700) == 0) { + DIR *dr; + struct dirent * dir_entry; + std::string trickImagesDirPath = trickHome + "/trick_source/web/images"; + if ( (dr = opendir(trickImagesDirPath.c_str())) != NULL) { + while (( dir_entry = readdir(dr)) != NULL) { + std::string fName = std::string( dir_entry->d_name); + std::string sPath = trickImagesDirPath + '/' + fName; + std::string dPath = imagesDirPath + '/' + fName; + symlink(sPath.c_str(), dPath.c_str()); + } + } + } else { + std::cout << "Failed to create \"" << imagesDirPath << "\"." << std::endl; + return 1; + } + + std::string indexFilePath = documentRoot + "/index.html"; + std::fstream index_fs (indexFilePath, std::fstream::out); + index_fs << index_html << std::endl; + index_fs.close(); + + } else { + std::cout << "Failed to create documentRoot." << std::endl; + return 1; + } + } else { + std::cout << "TRICK_HOME is not set.\n" << std::endl; + return 1; + } + } + return 0; +} + +static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) { + + http_message *hm = (struct http_message *)ev_data; + WebServer* httpServer = (WebServer *)nc->user_data; + + switch(ev) { + case MG_EV_WEBSOCKET_HANDSHAKE_DONE: { // Process new websocket connection. + std::string uri(hm->uri.p, hm->uri.len); + std::cout << "WEBSOCKET_REQUEST: URI = \"" << uri << "\"" << std::endl; + if (mg_str_starts_with(hm->uri, ws_api_prefix)) { + std::string wsType (hm->uri.p + ws_api_prefix.len, hm->uri.len - ws_api_prefix.len); + WebSocketSession* session = httpServer->makeWebSocketSession(nc, wsType); + if (session != NULL) { + httpServer->addWebSocketSession(nc, session); + std::cout << "WEBSOCKET[" << (void*)nc << "] OPENED. URI=\"" << uri << "\"." << std::endl; + } else { + nc->flags |= MG_F_SEND_AND_CLOSE; + std::cout << "ERROR: No such web socket interface: \"" << uri << "\"." << std::endl; + } + } else { + std::cout << "ERROR: WEBSOCKET_REQUEST URI does not start with API prefix." << std::endl; + } + } break; + case MG_EV_WEBSOCKET_FRAME: { // Process websocket messages from the client (web browser). + struct websocket_message *wm = (struct websocket_message *) ev_data; + std::string msg ((char*)wm->data, wm->size); + std::cout << "WEBSOCKET[" << (void*)nc << "] RECIEVED: " << msg << std::endl; + if (nc->flags & MG_F_IS_WEBSOCKET) { + httpServer->handleWebSocketClientMessage(nc, msg); + } + } break; + case MG_EV_CLOSE: { // Process closed websocket connection. + if (nc->flags & MG_F_IS_WEBSOCKET) { + httpServer->deleteWebSocketSession(nc); + std::cout << "WEBSOCKET[" << (void*)nc << "] CLOSED." << std::endl; + } + } break; + case MG_EV_POLL: { + // The MG_EV_POLL event is sent to all connections for each invocation of mg_mgr_poll(), + // called periodically by the threaded function connectionAttendant() [below] when it is + // signaled (serviceConnections) from the http_top_of_frame job. + // This is when we send websocket messages to the client (web browser). + if (nc->flags & MG_F_IS_WEBSOCKET) { + httpServer->sendWebSocketSessionMessages(nc); + } + } break; + case MG_EV_HTTP_REQUEST: { // Process HTTP requests. + std::string uri(hm->uri.p, hm->uri.len); + std::cout << "HTTP_REQUEST: URI = \"" << uri << "\"" << std::endl; + if (mg_str_starts_with(hm->uri, http_api_prefix)) { + if (mg_strcmp(hm->method, s_get_method)==0) { + std::string handlerName (hm->uri.p + http_api_prefix.len, hm->uri.len - http_api_prefix.len); + httpServer->handleHTTPGETrequest(nc, hm, handlerName); + } else if (mg_strcmp(hm->method, s_put_method)==0) { + mg_http_send_error(nc, 405, "PUT method not allowed."); + } else if (mg_strcmp(hm->method, s_delete_method)==0) { + mg_http_send_error(nc, 405, "DELETE method not allowed."); + } + } else { + // Serve the files in the document-root directory, as specified by the URI. + mg_serve_http(nc, (struct http_message *) ev_data, httpServer->http_server_options); + } + } break; + default: { + } break; + } +} + +// ========================================================================= +// This function runs in its own pthread to operate the webserver. +// ========================================================================= +static void* connectionAttendant (void* arg) { + WebServer *S = (WebServer*)arg; + while(1) { + pthread_mutex_lock(&S->serviceLock); + // Wait here until the serviceConnections condition is signaled by the top_of_frame job. + while (!S->service_websocket && !S->shutting_down) { + pthread_cond_wait(&S->serviceConnections, &S->serviceLock); + } + if (S->shutting_down) { + pthread_mutex_unlock(&S->serviceLock); + return NULL; + } else { + if (!S->sessionDataMarshalled) { + S->marshallWebSocketSessionData(); + } + // mg_mgr_poll returns the number of connections that still need to be serviced. + while(mg_mgr_poll(&S->mgr, 50)); + } + S->service_websocket= false; + pthread_mutex_unlock(&S->serviceLock); + } + return NULL; +} + +// Install a WebSocketSessionMaker with a name (key) by which it can be retrieved. +void WebServer::installWebSocketSessionMaker(std::string name, WebSocketSessionMaker maker) { + pthread_mutex_lock(&WebSocketSessionMakerMapLock); + WebSocketSessionMakerMap.insert(std::pair(name, maker)); + pthread_mutex_unlock(&WebSocketSessionMakerMapLock); +} + +// Lookup and call the WebSocketSessionMaker function by name, end execute it to create and return +// (a pointer to) a WebSocketSession. +WebSocketSession* WebServer::makeWebSocketSession(struct mg_connection *nc, std::string name) { + std::map::iterator iter; + iter = WebSocketSessionMakerMap.find(name); + if (iter != WebSocketSessionMakerMap.end()) { + WebSocketSessionMaker maker = iter->second; + return maker(nc); + } else { + return NULL; + mg_http_send_error(nc, 404, "No such API."); + } +} + +// Install an httpMethodHandler with a name, the key by which it can be retrieved. +void WebServer::installHTTPGEThandler(std::string handlerName, httpMethodHandler handler) { + pthread_mutex_lock(&httpGETHandlerMapLock); + httpGETHandlerMap.insert(std::pair(handlerName, handler)); + pthread_mutex_unlock(&httpGETHandlerMapLock); +} + +/* Lookup the appropriate httpMethodHandler by name, and execute it for the + given connection and http_message. */ +void WebServer::handleHTTPGETrequest(struct mg_connection *nc, http_message *hm, std::string handlerName) { + std::map::iterator iter; + iter = httpGETHandlerMap.find(handlerName); + if (iter != httpGETHandlerMap.end()) { + httpMethodHandler handler = iter->second; + handler(nc, hm); + } else { + mg_http_send_error(nc, 404, "No such API."); + } +} + +/* Tell each of the sessions to marshall any data that they have to send. */ +void WebServer::marshallWebSocketSessionData() { + std::map::iterator iter; + pthread_mutex_lock(&webSocketSessionMapLock); + for (iter = webSocketSessionMap.begin(); iter != webSocketSessionMap.end(); iter++ ) { + WebSocketSession* session = iter->second; + session->marshallData(); + } + sessionDataMarshalled = true; + pthread_mutex_unlock(&webSocketSessionMapLock); +} + +// Find the session that goes with the given websocket connection, +// and tell it to send any messages it may have, to the client (web browser). +void WebServer::sendWebSocketSessionMessages(struct mg_connection *nc) { + + std::map::iterator iter; + pthread_mutex_lock(&webSocketSessionMapLock); + iter = webSocketSessionMap.find(nc); + if (iter != webSocketSessionMap.end()) { + WebSocketSession* session = iter->second; + session->sendMessage(); + } + sessionDataMarshalled = false; + pthread_mutex_unlock(&webSocketSessionMapLock); +} + +/* Delete the WebSocketSession associated with the given connection-pointer, + and erase its pointer from the webSocketSessionMap. */ +void WebServer::deleteWebSocketSession(struct mg_connection *nc) { + std::map::iterator iter; + iter = webSocketSessionMap.find(nc); + if (iter != webSocketSessionMap.end()) { + WebSocketSession* session = iter->second; + delete session; + webSocketSessionMap.erase(iter); + } +} + +// Lookup the WebSocketSession associated with the given connection-pointer, and pass +// the given message to it. +void WebServer::handleWebSocketClientMessage(struct mg_connection *nc, std::string msg) { + std::map::iterator iter; + iter = webSocketSessionMap.find(nc); + if (iter != webSocketSessionMap.end()) { + WebSocketSession* session = iter->second; + session->handleMessage(msg); + } +} + +// Install a WebSocketSession with a connection-pointer, the key by which it can be retrieved. +void WebServer::addWebSocketSession(struct mg_connection *nc, WebSocketSession* session) { + pthread_mutex_lock(&webSocketSessionMapLock); + webSocketSessionMap.insert( std::pair(nc, session) ); + pthread_mutex_unlock(&webSocketSessionMapLock); +} + +// ========================================================================= +// Trick Sim Interface Functions +// ========================================================================= + +// Trick "default_data" job. +int WebServer::http_default_data() { + port = "8888"; + //port = "0"; + document_root = "www"; + time_homogeneous = false; + service_websocket = false; + shutting_down = false; + sessionDataMarshalled = false; + + installHTTPGEThandler("vs_connections", &handle_HTTP_GET_vs_connections); + installHTTPGEThandler("alloc_info", &handle_HTTP_GET_alloc_info); + installWebSocketSessionMaker("VariableServer", &makeVariableServerSession); + + return 0; +} + +// Trick "initialization" job. +int WebServer::http_init() { + http_server_options.document_root = document_root; + http_server_options.enable_directory_listing = "yes"; + + confirmDocumentRoot( std::string(document_root)); + + mg_mgr_init( &mgr, NULL ); + + memset(&bind_opts, 0, sizeof(bind_opts)); + bind_opts.user_data = this; + listener = mg_bind_opt( &mgr, port, ev_handler, bind_opts); + + // Determine the ACTUAL listen port as opposed to the requested port. + // Note that if we specify port = "0" in the mg_bind_opt() call, then + // a port number will be chosen for us, and we have to find out what it actually is. + char buf[32]; + mg_conn_addr_to_str( listener, buf, 32, MG_SOCK_STRINGIFY_PORT); + port = strdup(buf); + + if (listener != NULL) { + std::cout << "Trick Webserver: Listening on port = " << port << ".\n" + << "Trick Webserver: Document root = \"" << document_root << "\"" + << std::endl; + } else { + std::cerr << "Trick Webserver: ERROR: Failed to create listener.\n" + << "Perhaps another program is already using port " << port << "." + << std::endl; + return 1; + } + mg_set_protocol_http_websocket( listener ); + pthread_cond_init(&serviceConnections, NULL); + pthread_create( &server_thread, NULL, connectionAttendant, (void*)this ); + return 0; +} + +int WebServer::http_top_of_frame() { + if (listener != NULL) { + if (time_homogeneous) { + /* Have all of the sessions stage their data in a top_of_frame job, so + that it's time-homogeneous. */ + marshallWebSocketSessionData(); + } + // Signal the server thread to construct and send the values-message to the client. + service_websocket= true; + pthread_cond_signal( &serviceConnections ); + } + return 0; +} + +int WebServer::http_shutdown() { + if (listener != NULL) { + std::cout << "Trick Webserver: Shutting down on port " << port << "." << std::endl; + shutting_down = true; + + // Send the serviceConnections signal one last time so the connectionAttendant thread can quit. + pthread_cond_signal( &serviceConnections ); + pthread_join(server_thread, NULL); + } + return 0; +} diff --git a/trick_source/web/HttpServer/src/http_GET_handlers.cpp b/trick_source/web/HttpServer/src/http_GET_handlers.cpp new file mode 100644 index 00000000..1de3fdc8 --- /dev/null +++ b/trick_source/web/HttpServer/src/http_GET_handlers.cpp @@ -0,0 +1,65 @@ +/************************************************************************* +PURPOSE: ( HTTP-GET-method-handlers ) +LIBRARY DEPENDENCIES: + ( (../src/http_GET_handlers.o)) +**************************************************************************/ + +#include +#include "../include/http_GET_handlers.hh" + +#include "trick/VariableServer.hh" +extern Trick::VariableServer * the_vs ; + +#include "trick/MemoryManager.hh" +extern Trick::MemoryManager* trick_MM; + +// In the Trick HTTP Server, a HTTP GET request whose URI starts with the API_PREFIX +// is processed by a http-handler-function of the following form: +// +// void HTTP_METHOD_HANDLER( struct mg_connection *, struct http_message *); +// +// The purpose of these functions are generally to produce dynamically generated +// HTTP responses, like JSON. These handler-functions are installed into the HTTP_Server +// with the member-function .install_API_GET_handler. For example: +// +// http.server.install_API_GET_handler("vs_connections", &handle_HTTP_GET_vs_connections); +// +// installs the function handle_HTTP_GET_vs_connections() with the key "vs_connections". +// So if, for example the host and port of the webserver is "localhost:8888", and the API_PREFIX is "/api/v1/", +// then loading the URL "localhost:8888/api/v1/vs_connections" in your browser will cause +// handle_HTTP_GET_vs_connections() to run and return its response, which in this case is a JSON object +// describing the variable server connections. + + +// Send a JSON object to the given mongoose HTTP connection that describes the +// Variable Server Connections. +void handle_HTTP_GET_vs_connections(struct mg_connection *nc, struct http_message *hm) { + mg_printf(nc, "%s", "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n"); + std::stringstream ss; + ss << *the_vs << std::endl; + std::string someJSON = ss.str(); + mg_printf_http_chunk(nc, "%s", someJSON.c_str()); + mg_send_http_chunk(nc, "", 0); +} + +static int getIntegerQueryValue(struct http_message *hm, const char* key, int defaultVal) { + char value_text[100]; + if ( mg_get_http_var(&(hm->query_string), key, value_text, sizeof(value_text)) > 0) { + return atoi(value_text); + } else { + return defaultVal; + } +} + +// Send a JSON object to the given mongoose HTTP connection that contains information +// about a range of memory allocations in the Trick Memory Manager. +void handle_HTTP_GET_alloc_info(struct mg_connection *nc, struct http_message *hm) { + int start = getIntegerQueryValue(hm, "start", 0); + int count = getIntegerQueryValue(hm, "count", 10); + mg_printf(nc, "%s", "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n"); + std::stringstream ss; + trick_MM->write_JSON_alloc_list(ss, start, count); + std::string someJSON = ss.str(); + mg_printf_http_chunk(nc, "%s", someJSON.c_str()); + mg_send_http_chunk(nc, "", 0); +} diff --git a/trick_source/web/HttpServer/src/simpleJSON.cpp b/trick_source/web/HttpServer/src/simpleJSON.cpp new file mode 100644 index 00000000..92aafebb --- /dev/null +++ b/trick_source/web/HttpServer/src/simpleJSON.cpp @@ -0,0 +1,216 @@ +#include +#include +#include +#include +#include "../include/simpleJSON.hh" + +class LexicalAnalyzer { +public: + enum Lexeme { + END_OF_INPUT, + ERROR, + LEFT_BRACE, + RIGHT_BRACE, + LEFT_SQUARE_BRACKET, + RIGHT_SQUARE_BRACKET, + COLON, + COMMA, + STRING, + INTEGER + }; + + LexicalAnalyzer(const char*); + int next_lexeme(); + char* getTokenText(); + +private: + const char * s; + const char * p; + const char * vs; + size_t vlen; + char getch(); + void ungetch() ; + LexicalAnalyzer(){} +}; + +LexicalAnalyzer::LexicalAnalyzer(const char* str) { + s = str; + p = str; + vs = NULL; + vlen = 0; +} + +char LexicalAnalyzer::getch() { + char ch; + if ((ch = *p) != 0) { p++; } + return ch; +} + +void LexicalAnalyzer::ungetch() { + if (p > s) { p--; } +} + +char* LexicalAnalyzer::getTokenText() { + if (vlen > 0) { + return strndup(vs, vlen); + } else { + return NULL; + } +} + +int LexicalAnalyzer::next_lexeme() { + int state = 0; + vlen = 0; + char ch; + while ((ch = getch()) != 0) { + switch (state) { + case 0 : { // Initial state. + if (ch == '{') { + return LEFT_BRACE ; + } else if (ch == '}') { + return RIGHT_BRACE ; + } else if (ch == '[') { + return LEFT_SQUARE_BRACKET ; + } else if (ch == ']') { + return RIGHT_SQUARE_BRACKET ; + } else if (ch == ':') { + return COLON ; + } else if (ch == ',') { + return COMMA ; + } else if ( ch == '"') { + state = 1; + vs = p; + } else if ( isdigit(ch) ) { + ungetch(); + state = 2; + vs = p; + } else if (isspace(ch)) { + state = 0; + } + } break; + case 1 : { // String literal accumulation state. + while ((ch != 0 ) && (ch != '"')) + ch = getch(); + if (ch == '"') { + vlen = p-vs-1 ; + return STRING ; + } else { + return ERROR ; + } + } break; + case 2 : { // Integer literal accumulation state. + while ((ch != 0 ) && (isdigit(ch))) + ch = getch(); + ungetch(); + vlen = p-vs; + return INTEGER ; + } break; + default: + return ERROR ; + } + } + return END_OF_INPUT; +} + +const char *token_description(int token) { + const char *text; + switch (token) { + case LexicalAnalyzer::END_OF_INPUT : text = "END_OF_INPUT"; break; + case LexicalAnalyzer::ERROR : text = "ERROR"; break; + case LexicalAnalyzer::LEFT_BRACE : text = "LEFT_BRACE"; break; + case LexicalAnalyzer::RIGHT_BRACE : text = "RIGHT_BRACE"; break; + case LexicalAnalyzer::LEFT_SQUARE_BRACKET : text = "LEFT_SQUARE_BRACKET"; break; + case LexicalAnalyzer::RIGHT_SQUARE_BRACKET : text = "RIGHT_SQUARE_BRACKET"; break; + case LexicalAnalyzer::COLON : text = "COLON"; break; + case LexicalAnalyzer::COMMA : text = "COMMA"; break; + case LexicalAnalyzer::STRING : text = "STRING"; break; + case LexicalAnalyzer::INTEGER : text = "INTEGER"; break; + default : text = "**UNKNOWN**"; break; + } + return text; +} + +Member::Member(const char *k, const char *v, int t) { + key=k; + valText=v; + type=t; +} + +Member* parseJSON_member(LexicalAnalyzer &lexan) { + + const char* key; + const char* valText; + int type; + + int token; + token = lexan.next_lexeme(); + if ( token == LexicalAnalyzer::STRING ) { + key = lexan.getTokenText(); + } else { + std::cout << "ERROR: Expected STRING. Found \"" << token_description(token) << "\"." << std::endl; + return NULL; + } + token = lexan.next_lexeme(); + if ( token != LexicalAnalyzer::COLON ) { + std::cout << "ERROR: Expected COLON. Found \"" << token_description(token) << "\"." << std::endl; + token_description(token); + delete key; + return NULL; + } + token = lexan.next_lexeme(); + if (( token == LexicalAnalyzer::STRING) || ( token == LexicalAnalyzer::INTEGER )) { + valText = lexan.getTokenText(); + type = token; + } else { + std::cout << "ERROR: Expected STRING or INTEGER. Found \"" << token_description(token) << "." << std::endl; + token_description(token); + return NULL; + } + Member *member = new Member(key, valText, type); + return member; +} + +std::vector parseJSON( const char *json_s) { + + std::vector members; + Member* member; + int token; + bool okiedokey = true; + LexicalAnalyzer lexan(json_s); + token = lexan.next_lexeme(); + if ( token == LexicalAnalyzer::LEFT_BRACE ) { + member = parseJSON_member(lexan); + if (member != NULL) { + members.push_back(member); + token = lexan.next_lexeme(); + while ( okiedokey && (token == LexicalAnalyzer::COMMA) ) { + member = parseJSON_member(lexan); + if (member != NULL) { + members.push_back(member); + } else { + okiedokey = false; + } + token = lexan.next_lexeme(); + } + } else { + okiedokey = false; + } + if ( token != LexicalAnalyzer::RIGHT_BRACE ) { + std::cout << "ERROR: Expected RIGHT_BRACE. Found \"" << token_description(token) << "\"." << std::endl; + token_description(token); + okiedokey = false; + } + } else { + std::cout << "ERROR: Expected LEFT_BRACE. Found \"" << token_description(token) << "\"." << std::endl; + okiedokey = false; + } + if (okiedokey == false) { + std::vector::iterator it; + it = members.begin(); + while (it != members.end()) { + delete *it; + it = members.erase(it); + } + } + return members; +} diff --git a/trick_source/web/apps/alloc_info.html b/trick_source/web/apps/alloc_info.html new file mode 100644 index 00000000..056cff03 --- /dev/null +++ b/trick_source/web/apps/alloc_info.html @@ -0,0 +1,156 @@ + + + + +Trick Memory Allocations + + + + + +
+
+
+
+ + + + + diff --git a/trick_source/web/apps/vs_connections.html b/trick_source/web/apps/vs_connections.html new file mode 100644 index 00000000..e6c9bd08 --- /dev/null +++ b/trick_source/web/apps/vs_connections.html @@ -0,0 +1,74 @@ + + + + Variable Server Connections + + + +
+
+
+
+ + + diff --git a/trick_source/web/apps/wsexp.html b/trick_source/web/apps/wsexp.html new file mode 100644 index 00000000..c866a9ef --- /dev/null +++ b/trick_source/web/apps/wsexp.html @@ -0,0 +1,98 @@ + + + + WS Experiments + + + +
+
+ +
+ + + + + +
VariableValue
+ +
+ + +