mirror of
https://github.com/nasa/trick.git
synced 2024-12-18 20:57:55 +00:00
Incorporate Webserver into Trick, so one only has to include HttpServ… (#886)
* Incorporate Webserver into Trick, so one only has to include HttpServer.sm * Tweaks in default index.html file * Rename HTTPServer.sm to WebServer.sm * Rename http_server to WebServer * Add --retry to curl invocations in HttpServer makefile. * Fix #include in VariableServerVariable.hh * Include cleanup and curl tweaks in the hopes of making Jenkins happy. * Doh! problem in makefile masked by preinstalled mongoose in usr/local/lib * DIE Make Bug DIE * Fix include in WebServer.sm * WebServer.sm constructor name * Don't SWIG mongoose.h * Compile with -std=c++11 * Attempt to fix race condition in makefile * makefie tweek * Fix trick library name problem for Centos and Redhat
This commit is contained in:
parent
34833be0de
commit
95c6659733
8
Makefile
8
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 "[32mTrick compilation complete:[00m" ; 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:
|
||||
|
63
include/trick/WebServer.hh
Normal file
63
include/trick/WebServer.hh
Normal file
@ -0,0 +1,63 @@
|
||||
/*************************************************************************
|
||||
PURPOSE: (Represent the state and initial conditions of an http server.)
|
||||
**************************************************************************/
|
||||
#ifndef WEB_SERVER_H
|
||||
#define WEB_SERVER_H
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <pthread.h>
|
||||
#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<mg_connection*, WebSocketSession*> 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
|
28
include/trick/WebSocketSession.hh
Normal file
28
include/trick/WebSocketSession.hh
Normal file
@ -0,0 +1,28 @@
|
||||
/*************************************************************************
|
||||
PURPOSE: (Represent Websocket connection.)
|
||||
**************************************************************************/
|
||||
#ifndef WEB_SOCKET_SESSION_HH
|
||||
#define WEB_SOCKET_SESSION_HH
|
||||
|
||||
#include <string>
|
||||
#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
|
@ -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"
|
||||
|
@ -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)
|
||||
|
20
share/trick/sim_objects/WebServer.sm
Normal file
20
share/trick/sim_objects/WebServer.sm
Normal file
@ -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;
|
@ -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:
|
||||
|
@ -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
|
||||
|
45
trick_source/web/HttpServer/include/VariableServerSession.hh
Normal file
45
trick_source/web/HttpServer/include/VariableServerSession.hh
Normal file
@ -0,0 +1,45 @@
|
||||
/*************************************************************************
|
||||
PURPOSE: (Represent the state of a variable server websocket connection.)
|
||||
**************************************************************************/
|
||||
|
||||
#ifndef WSSESSION_HH
|
||||
#define WSSESSION_HH
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#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<VariableServerVariable*> sessionVariables;
|
||||
bool cyclicSendEnabled;
|
||||
long long nextTime;
|
||||
long long intervalTimeTics;
|
||||
};
|
||||
|
||||
WebSocketSession* makeVariableServerSession( struct mg_connection *nc );
|
||||
#endif
|
@ -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 <time.h>
|
||||
#include <vector>
|
||||
#include "mongoose/mongoose.h"
|
||||
#include <iostream>
|
||||
#include <trick/reference.h>
|
||||
|
||||
#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
|
14
trick_source/web/HttpServer/include/http_GET_handlers.hh
Normal file
14
trick_source/web/HttpServer/include/http_GET_handlers.hh
Normal file
@ -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
|
21
trick_source/web/HttpServer/include/simpleJSON.hh
Normal file
21
trick_source/web/HttpServer/include/simpleJSON.hh
Normal file
@ -0,0 +1,21 @@
|
||||
/*************************************************************************
|
||||
PURPOSE: (Represent Websocket variable server connection.)
|
||||
LIBRARY DEPENDENCIES:
|
||||
( (../src/WSSession.o))
|
||||
**************************************************************************/
|
||||
#ifndef SIMPLEJSON_HH
|
||||
#define SIMPLEJSON_HH
|
||||
|
||||
#include <vector>
|
||||
|
||||
class Member {
|
||||
public:
|
||||
const char* key;
|
||||
const char* valText;
|
||||
int type;
|
||||
Member(const char *k, const char *v, int t);
|
||||
};
|
||||
|
||||
std::vector<Member*> parseJSON( const char *json_s);
|
||||
|
||||
#endif
|
88
trick_source/web/HttpServer/makefile
Normal file
88
trick_source/web/HttpServer/makefile
Normal file
@ -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
|
||||
|
225
trick_source/web/HttpServer/src/VariableServerSession.cpp
Normal file
225
trick_source/web/HttpServer/src/VariableServerSession.cpp
Normal file
@ -0,0 +1,225 @@
|
||||
/************************************************************************
|
||||
PURPOSE: (Represent the state and initial conditions of an http server)
|
||||
LIBRARY DEPENDENCIES:
|
||||
((simpleJSON.o)
|
||||
(VariableServerVariable.o)
|
||||
)
|
||||
**************************************************************************/
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <iomanip> // for setprecision
|
||||
#include <algorithm>
|
||||
#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<VariableServerVariable*>::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<Member*> members = parseJSON(client_msg.c_str());
|
||||
std::vector<Member*>::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<VariableServerVariable*>::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<VariableServerVariable*>::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);
|
||||
}
|
184
trick_source/web/HttpServer/src/VariableServerVariable.cpp
Normal file
184
trick_source/web/HttpServer/src/VariableServerVariable.cpp
Normal file
@ -0,0 +1,184 @@
|
||||
#include "trick/memorymanager_c_intf.h" // for get_size.
|
||||
#include "../include/VariableServerVariable.hh"
|
||||
#include <math.h> // for fpclassify
|
||||
#include <iomanip> // 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<len ; ii++) {
|
||||
switch ((s)[ii]) {
|
||||
case '\n': os << "\\n"; break;
|
||||
case '\t': os << "\\t"; break;
|
||||
case '\b': os << "\\b"; break;
|
||||
case '\"': os << "\\\""; break;
|
||||
default : os << s[ii] ; break;
|
||||
}
|
||||
}
|
||||
os << "\"" ;
|
||||
}
|
||||
|
||||
void VariableServerVariable::stageValue() {
|
||||
// Copy <size> bytes from <address> 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;
|
||||
}
|
||||
}
|
415
trick_source/web/HttpServer/src/WebServer.cpp
Normal file
415
trick_source/web/HttpServer/src/WebServer.cpp
Normal file
@ -0,0 +1,415 @@
|
||||
/************************************************************************
|
||||
PURPOSE: (Represent the state and initial conditions of an http server)
|
||||
**************************************************************************/
|
||||
#include <sys/stat.h> // for mkdir()
|
||||
#include <unistd.h> // for symlink(), access()
|
||||
#include <stdlib.h> // for getenv()
|
||||
#include <dirent.h> // for opendir(), readdir()
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include "trick/WebServer.hh"
|
||||
#include "../include/http_GET_handlers.hh"
|
||||
#include "../include/VariableServerSession.hh"
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
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 =
|
||||
"<!DOCTYPE html>\n"
|
||||
"<html>\n"
|
||||
"<head>\n"
|
||||
"<link rel=\"stylesheet\" type=\"text/css\" href=\"style.css\">\n"
|
||||
"<title>Trick Simulation</title>\n"
|
||||
"<div class=\"header\">\n"
|
||||
"<table>\n"
|
||||
"<th><img src=\"images/trick_icon.png\" height=\"64\" width=\"64\"></th>\n"
|
||||
"<th><h1>Trick Simulation</h1></th>\n"
|
||||
"</table>\n"
|
||||
"</div>\n"
|
||||
"</head>\n"
|
||||
"<body>\n"
|
||||
"<div style=\"background:#efefef\">\n"
|
||||
"<ul>\n"
|
||||
"<li><a href=\"http://github.com/nasa/trick\">Trick on GitHub</a></li>\n"
|
||||
"<li><a href=\"http://github.com/nasa/trick/wiki/Tutorial\">Trick Tutorial</a></li>\n"
|
||||
"<li><a href=\"http://github.com/nasa/trick/wiki/Documentation-Home\">Trick Documentation</a></li>\n"
|
||||
"</ul>\n"
|
||||
"</div>\n"
|
||||
"<div style=\"background:#efefef\">\n"
|
||||
"<ul>\n"
|
||||
"<li><a href=\"/apps\">Applications</a></li>\n"
|
||||
"</ul>\n"
|
||||
"</div>\n"
|
||||
"</body>\n"
|
||||
"</html>";
|
||||
|
||||
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<std::string, WebSocketSessionMaker>(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<std::string, WebSocketSessionMaker>::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<std::string, httpMethodHandler>(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<std::string, httpMethodHandler>::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<mg_connection*, WebSocketSession*>::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<mg_connection*, WebSocketSession*>::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<mg_connection*, WebSocketSession*>::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<mg_connection*, WebSocketSession*>::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<mg_connection*, WebSocketSession*>(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;
|
||||
}
|
65
trick_source/web/HttpServer/src/http_GET_handlers.cpp
Normal file
65
trick_source/web/HttpServer/src/http_GET_handlers.cpp
Normal file
@ -0,0 +1,65 @@
|
||||
/*************************************************************************
|
||||
PURPOSE: ( HTTP-GET-method-handlers )
|
||||
LIBRARY DEPENDENCIES:
|
||||
( (../src/http_GET_handlers.o))
|
||||
**************************************************************************/
|
||||
|
||||
#include <sstream>
|
||||
#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 <HTTP_Server-object>.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);
|
||||
}
|
216
trick_source/web/HttpServer/src/simpleJSON.cpp
Normal file
216
trick_source/web/HttpServer/src/simpleJSON.cpp
Normal file
@ -0,0 +1,216 @@
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <iostream>
|
||||
#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<Member*> parseJSON( const char *json_s) {
|
||||
|
||||
std::vector<Member*> 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<Member*>::iterator it;
|
||||
it = members.begin();
|
||||
while (it != members.end()) {
|
||||
delete *it;
|
||||
it = members.erase(it);
|
||||
}
|
||||
}
|
||||
return members;
|
||||
}
|
156
trick_source/web/apps/alloc_info.html
Normal file
156
trick_source/web/apps/alloc_info.html
Normal file
@ -0,0 +1,156 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>Trick Memory Allocations</title>
|
||||
</head>
|
||||
|
||||
<style>
|
||||
table { border-collapse: collapse; width: 100%; }
|
||||
th, td { text-align: left; padding: 8px; }
|
||||
tr:nth-child(even){background-color: #f2f2f2}
|
||||
th { background-color: #562399; color: white; }
|
||||
</style>
|
||||
|
||||
<body>
|
||||
<header>
|
||||
</header>
|
||||
<div class="trickMemoryAllocations">
|
||||
</div>
|
||||
<div class="navigation" style="background:#562399">
|
||||
<button class="previous" style="font-size:20px" onClick="previous_page()">Previous</button>
|
||||
<button class="next" style="font-size:20px" onClick="next_page()">Next</button>
|
||||
|
||||
<select id="countSelect" style="font-size:20px" onChange="updatePageWithSelectedCount()">
|
||||
<option value="10" >10 per page</option>
|
||||
<option value="20" >20 per page</option>
|
||||
<option value="50" >50 per page</option>
|
||||
<option value="100">100 per page</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
function showHeader(allocData) {
|
||||
|
||||
let para = document.createElement('p');
|
||||
para.textContent = `URL: ${document.URL}`;
|
||||
header.appendChild(para);
|
||||
|
||||
let label = document.createElement('h2');
|
||||
label.textContent = 'Trick Memory Allocations';
|
||||
header.appendChild(label);
|
||||
}
|
||||
|
||||
function updateHeader(allocData) {
|
||||
let first = allocData.chunk_start + 1;
|
||||
let last = allocData.chunk_start + allocData.alloc_list.length;
|
||||
let label = header.getElementsByTagName("h2")[0];
|
||||
label.textContent = `Trick Memory Allocations (${first}..${last}) of ${allocData.alloc_total}`;
|
||||
}
|
||||
|
||||
function createAllocInfoTable(allocData) {
|
||||
let mytable = document.createElement('table');
|
||||
let trow = document.createElement('tr');
|
||||
let table_headings = ['Name', 'Address', 'Num', 'Size', 'Type Specifier', 'StorageClass'];
|
||||
for (let i = 0; i < table_headings.length ; i++) {
|
||||
let th = document.createElement('th');
|
||||
th.textContent = table_headings[i];
|
||||
trow.appendChild(th);
|
||||
}
|
||||
mytable.appendChild(trow);
|
||||
for (let i = 0; i < allocData.alloc_list.length; i++) {
|
||||
let trow = document.createElement('tr');
|
||||
let td1 = document.createElement('td');
|
||||
td1.textContent = allocData.alloc_list[i].name;
|
||||
let td2 = document.createElement('td');
|
||||
td2.textContent = allocData.alloc_list[i].start;
|
||||
let td3 = document.createElement('td');
|
||||
td3.textContent = allocData.alloc_list[i].num;
|
||||
let td4 = document.createElement('td');
|
||||
td4.textContent = allocData.alloc_list[i].size;
|
||||
let td5 = document.createElement('td');
|
||||
td5.textContent = allocData.alloc_list[i].type;
|
||||
let td6 = document.createElement('td');
|
||||
td6.textContent = allocData.alloc_list[i].stcl;
|
||||
trow.appendChild(td1);
|
||||
trow.appendChild(td2);
|
||||
trow.appendChild(td3);
|
||||
trow.appendChild(td4);
|
||||
trow.appendChild(td5);
|
||||
trow.appendChild(td6);
|
||||
mytable.appendChild(trow);
|
||||
}
|
||||
return mytable;
|
||||
}
|
||||
|
||||
function updateNavButtons(allocData) {
|
||||
nextButton.style.opacity = "1.0"
|
||||
if (allocData.chunk_start + allocData.chunk_size >= allocData.alloc_total) {
|
||||
nextButton.style.opacity = "0.6";
|
||||
}
|
||||
prevButton.style.opacity = "1.0";
|
||||
if (allocData.chunk_start - allocData.chunk_size < 0) {
|
||||
prevButton.style.opacity = "0.6";
|
||||
}
|
||||
}
|
||||
|
||||
function updatePage(start, count) {
|
||||
let xhr = new XMLHttpRequest();
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState == XMLHttpRequest.DONE && xhr.status == 200) {
|
||||
allocData = JSON.parse(xhr.responseText);
|
||||
let newTable = createAllocInfoTable(allocData);
|
||||
let oldTable = trickMemoryAllocations.getElementsByTagName("table")[0];
|
||||
trickMemoryAllocations.replaceChild(newTable, oldTable);
|
||||
updateNavButtons(allocData);
|
||||
updateHeader(allocData);
|
||||
}
|
||||
}
|
||||
xhr.open('GET', `/api/http/alloc_info?start=${start}&count=${count}`);
|
||||
xhr.send(null);
|
||||
}
|
||||
|
||||
function updatePageWithSelectedCount() {
|
||||
let newCount = parseInt( document.getElementById("countSelect").value, 10);
|
||||
updatePage(allocData.chunk_start, newCount);
|
||||
}
|
||||
|
||||
function next_page() {
|
||||
let nextStart = allocData.chunk_start + allocData.chunk_size;
|
||||
if (nextStart < allocData.alloc_total) {
|
||||
updatePage(nextStart, allocData.chunk_size);
|
||||
}
|
||||
}
|
||||
|
||||
function previous_page() {
|
||||
if (allocData.chunk_start > 0) {
|
||||
let prevStart = allocData.chunk_start - allocData.chunk_size;
|
||||
if (prevStart < 0) prevStart = 0;
|
||||
updatePage(prevStart, allocData.chunk_size);
|
||||
}
|
||||
}
|
||||
|
||||
var myOrigin = new URL(document.URL);
|
||||
var header = document.querySelector('header');
|
||||
var trickMemoryAllocations = document.querySelector('div.trickMemoryAllocations');
|
||||
var prevButton = document.querySelector('button.previous');
|
||||
var nextButton = document.querySelector('button.next');
|
||||
var allocData;
|
||||
let xhr = new XMLHttpRequest();
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState == XMLHttpRequest.DONE && xhr.status == 200) {
|
||||
allocData = JSON.parse(xhr.responseText);
|
||||
showHeader();
|
||||
let allocInfoTable = createAllocInfoTable(allocData);
|
||||
trickMemoryAllocations.appendChild(allocInfoTable);
|
||||
updateNavButtons(allocData);
|
||||
updateHeader(allocData);
|
||||
}
|
||||
}
|
||||
xhr.open('GET', '/api/http/alloc_info?start=0&count=10');
|
||||
xhr.send(null);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
74
trick_source/web/apps/vs_connections.html
Normal file
74
trick_source/web/apps/vs_connections.html
Normal file
@ -0,0 +1,74 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Variable Server Connections</title>
|
||||
</head>
|
||||
<style>
|
||||
table { border-collapse: collapse; width: 100%; }
|
||||
th, td { text-align: left; padding: 8px; }
|
||||
tr:nth-child(even){background-color: #f2f2f2}
|
||||
th { background-color: #562399; color: white; }
|
||||
</style>
|
||||
<body>
|
||||
<header>
|
||||
</header>
|
||||
<section>
|
||||
</section>
|
||||
<script type="text/javascript">
|
||||
function showHeader() {
|
||||
|
||||
let para = document.createElement('p');
|
||||
para.textContent = `URL: ${document.URL}`;
|
||||
header.appendChild(para);
|
||||
|
||||
let label = document.createElement('h2');
|
||||
label.textContent = 'Variable Server Connections';
|
||||
header.appendChild(label);
|
||||
}
|
||||
function showVSConnections(myObj) {
|
||||
let mytable = document.createElement('table');
|
||||
let trow = document.createElement('tr');
|
||||
let table_headings = ['Name', 'Address', 'Port', 'Format', 'Rate'];
|
||||
for (let i = 0; i < table_headings.length ; i++) {
|
||||
let th = document.createElement('th');
|
||||
th.textContent = table_headings[i];
|
||||
trow.appendChild(th);
|
||||
}
|
||||
mytable.appendChild(trow);
|
||||
for (let i = 0; i < myObj.variable_server_connections.length; i++) {
|
||||
let trow = document.createElement('tr');
|
||||
let td1 = document.createElement('td');
|
||||
td1.textContent = myObj.variable_server_connections[i].connection.client_tag;
|
||||
let td2 = document.createElement('td');
|
||||
td2.textContent = myObj.variable_server_connections[i].connection.client_IP_address;
|
||||
let td3 = document.createElement('td');
|
||||
td3.textContent = myObj.variable_server_connections[i].connection.client_port;
|
||||
let td4 = document.createElement('td');
|
||||
td4.textContent = myObj.variable_server_connections[i].connection.format;
|
||||
let td5 = document.createElement('td');
|
||||
td5.textContent = myObj.variable_server_connections[i].connection.update_rate;
|
||||
trow.appendChild(td1);
|
||||
trow.appendChild(td2);
|
||||
trow.appendChild(td3);
|
||||
trow.appendChild(td4);
|
||||
trow.appendChild(td5);
|
||||
mytable.appendChild(trow);
|
||||
}
|
||||
section.appendChild(mytable);
|
||||
}
|
||||
|
||||
var header = document.querySelector('header');
|
||||
var section = document.querySelector('section');
|
||||
let xhr = new XMLHttpRequest();
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState == XMLHttpRequest.DONE && xhr.status == 200) {
|
||||
var myObj = JSON.parse(xhr.responseText);
|
||||
showHeader();
|
||||
showVSConnections(myObj);
|
||||
}
|
||||
}
|
||||
xhr.open('GET', '/api/http/vs_connections');
|
||||
xhr.send(null);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
98
trick_source/web/apps/wsexp.html
Normal file
98
trick_source/web/apps/wsexp.html
Normal file
@ -0,0 +1,98 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>WS Experiments</title>
|
||||
</head>
|
||||
<body>
|
||||
<style>
|
||||
table { border-collapse: collapse; width: 100%; }
|
||||
th, td { text-align: left; padding: 8px; }
|
||||
tr:nth-child(even){background-color: #f2f2f2}
|
||||
th { background-color: #562399; color: white; }
|
||||
</style>
|
||||
<header>
|
||||
</header>
|
||||
|
||||
<div class="variableDisplay"></div>
|
||||
<table class="variables">
|
||||
<tr>
|
||||
<th>Variable</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div id="output"></div>
|
||||
<script type="text/javascript">
|
||||
|
||||
function log(s) {
|
||||
var p = document.createElement("p");
|
||||
p.style.wordWrap = "break-word";
|
||||
p.textContent = s;
|
||||
output.appendChild(p);
|
||||
}
|
||||
function sendMessage(msg) {
|
||||
ws.send(msg);
|
||||
//log("Sent : " + msg);
|
||||
}
|
||||
|
||||
// Interface to Trick WebSocket Variable Server
|
||||
function setPeriod(period) {
|
||||
sendMessage(`{"cmd":"var_cycle","period":${period}}`);
|
||||
}
|
||||
function addVarTableRow(name, value) {
|
||||
// create a row in the table that contains two <td>s, one for the var_name and one for its value.
|
||||
let tr = document.createElement('tr');
|
||||
let td1 = document.createElement('td');
|
||||
td1.textContent = `${name}`;
|
||||
let td2 = document.createElement('td');
|
||||
td2.textContent = `${value}`;
|
||||
td2.className = "values";
|
||||
tr.appendChild(td1);
|
||||
tr.appendChild(td2);
|
||||
varTable.appendChild(tr);
|
||||
}
|
||||
function addVariable(name, value) {
|
||||
sendMessage(`{"cmd":"var_add","var_name": "${name}"}`);
|
||||
addVarTableRow(name, value);
|
||||
}
|
||||
|
||||
var varTable = document.querySelector('table.variables');
|
||||
var ws = new WebSocket("ws://localhost:8888/api/ws/VariableServer", "myProtocol");
|
||||
|
||||
// WebSocket Event Handlers
|
||||
ws.onopen = function(e) {
|
||||
//log("Connection established");
|
||||
setPeriod(100);
|
||||
addVarTableRow("Time", 0.0);
|
||||
addVariable("dyn.cannon.pos[0]", 0.0);
|
||||
addVariable("dyn.cannon.pos[1]", 0.0);
|
||||
addVariable("dyn.cannon.vel[0]", 0.0);
|
||||
addVariable("dyn.cannon.vel[1]", 0.0);
|
||||
addVariable("dyn.cannon.time", 0.0);
|
||||
addVariable("dyn.cannon.timeRate", 0.0);
|
||||
addVariable("dyn.cannon.impact", 0.0);
|
||||
addVariable("I.dont.exist", 0.0);
|
||||
sendMessage("{\"cmd\":\"var_unpause\"}");
|
||||
};
|
||||
ws.onmessage = function(e) {
|
||||
//log("Recieved : " + e.data);
|
||||
let msg = JSON.parse(e.data);
|
||||
if (msg.msg_type == "values") {
|
||||
let valueNodes = varTable.getElementsByClassName("values");
|
||||
valueNodes[0].textContent = msg.time;
|
||||
for (let i = 0; i < msg.values.length; i++ ) {
|
||||
valueNodes[i+1].textContent = msg.values[i];
|
||||
}
|
||||
}
|
||||
};
|
||||
ws.onerror = function(e) {
|
||||
console.log("WebSocket Error: " , e);
|
||||
handleErrors(e);
|
||||
};
|
||||
ws.onclose = function(e) {
|
||||
console.log("Connection closed", e);
|
||||
};
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue
Block a user