Add an experimental mongoose-based embedded web server to SIM_cannon_numeric #756

This commit is contained in:
Penn, John M 047828115 2019-04-17 17:26:48 -05:00
parent 0f037e6817
commit b029fae692
8 changed files with 502 additions and 2 deletions

View File

@ -5,11 +5,25 @@ LIBRARY DEPENDENCIES:
(
(cannon/gravity/src/cannon_init.c)
(cannon/gravity/src/cannon_numeric.c)
(mongoose_httpd/src/http_server.cpp)
)
*************************************************************/
#include "sim_objects/default_trick_sys.sm"
##include "cannon/gravity/include/cannon_numeric.h"
##include "mongoose_httpd/include/http_server.h"
class HttpSimObject : public Trick::SimObject {
public:
HTTP_Server http_server ;
HttpSimObject() {
("default_data") http_default_data( &http_server ) ;
("initialization") http_init( &http_server ) ;
("shutdown") http_shutdown( &http_server ) ;
}
} ;
class CannonSimObject : public Trick::SimObject {
@ -27,6 +41,7 @@ class CannonSimObject : public Trick::SimObject {
// Instantiations
CannonSimObject dyn ;
HttpSimObject http ;
IntegLoop dyn_integloop (0.01) dyn;

View File

@ -1,4 +1,7 @@
TRICK_CFLAGS += -I../models
TRICK_CXXFLAGS += -I../models
TRICK_CFLAGS += -I/usr/include -I../models
TRICK_CXXFLAGS += -I/usr/include -I../models
TRICK_LDFLAGS += -L/usr/local/lib
TRICK_USER_LINK_LIBS += -lmongoose

View 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/v1/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/v1/alloc_info?start=0&count=10');
xhr.send(null);
</script>
</body>
</html>

View 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/v1/vs_connections');
xhr.send(null);
</script>
</body>
</html>

View File

@ -0,0 +1,41 @@
<!DOCTYPE html>
<html>
<head>
<title>WS Experiments</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">
var ws = new WebSocket("ws://localhost:8888", "myProtocol");
// Event handler for the WebSocket connection opening
ws.onopen = function(e) {
console.log("Connection established");
};
// Event handler for receiving text messages
ws.onmessage = function(e) {
console.log("Message received", e, e.data);
};
// Event handler for errors in the WebSocket object
ws.onerror = function(e) {
console.log("WebSocket Error: " , e);
//Custom function for handling errors
handleErrors(e);
};
// Event handler for closed connections
ws.onclose = function(e) {
console.log("Connection closed", e);
};
</script>
</body>
</html>

View File

@ -0,0 +1,28 @@
<!DOCTYPE html>
<html>
<head>
<title>Trick Simulation Pages</title>
</head>
<body>
<div style="background:#efefff">
<h2>SIM_cannon_numeric</h2>
</div>
<div style="background:#efefef">
<ul>
<li><a href="http://github.com/nasa/trick">Trick on GitHub</a></li>
<li><a href="http://github.com/nasa/trick/wiki/Tutorial">Trick Tutorial</a></li>
<li><a href="http://github.com/nasa/trick/wiki/Documentation-Home">Trick Documentation</a></li>
</ul>
</div>
<div style="background:#efefef">
<ul>
<li><a href="/apps/vs_connections.html">Variable Server Connections</a></li>
<li><a href="/apps/alloc_info.html">Trick Memory Allocations</a></li>
</ul>
</div>
</body>
</html>

View File

@ -0,0 +1,33 @@
/*************************************************************************
PURPOSE: (Represent the state and initial conditions of an http server)
**************************************************************************/
#ifndef HTTP_SERVER_H
#define HTTP_SERVER_H
#include <sys/types.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <mongoose.h>
#include <pthread.h>
typedef struct {
struct mg_mgr mgr; /* ** mongoose */
struct mg_connection *nc; /* ** mongoose */
const char* port;
pthread_t server_thread; /* ** */
bool shutting_down;
} HTTP_Server ;
#ifdef __cplusplus
extern "C" {
#endif
int http_default_data(HTTP_Server * S) ;
int http_init(HTTP_Server * S) ;
int http_shutdown(HTTP_Server * S) ;
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,150 @@
/************************************************************************
PURPOSE: (Represent the state and initial conditions of an http server)
**************************************************************************/
#include "../include/http_server.h"
#include <sstream>
#include "trick/VariableServer.hh"
extern Trick::VariableServer * the_vs ;
#include "trick/MemoryManager.hh"
extern Trick::MemoryManager* trick_MM;
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_delele_method = MG_MK_STR("DELETE");
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;
}
}
void handle_vs_connections_call(struct mg_connection *nc, struct http_message *hm) {
/* Send headers */
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 tmp = ss.str();
mg_printf_http_chunk(nc, "%s", tmp.c_str());
mg_send_http_chunk(nc, "", 0);
}
void handle_alloc_info_call(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 tmp = ss.str();
mg_printf_http_chunk(nc, "%s", tmp.c_str());
mg_send_http_chunk(nc, "", 0);
}
static int has_prefix(const struct mg_str *uri, const struct mg_str *prefix) {
return uri->len > prefix->len && memcmp(uri->p, prefix->p, prefix->len) == 0;
}
static int is_equal(const struct mg_str *s1, const struct mg_str *s2) {
return s1->len == s2->len && memcmp(s1->p, s2->p, s2->len) == 0;
}
static struct mg_serve_http_opts http_server_options;
static const struct mg_str api_prefix = MG_MK_STR("/api/v1");
static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
http_message *hm = (struct http_message *)ev_data;
switch(ev) {
case MG_EV_WEBSOCKET_HANDSHAKE_DONE: {
printf("DEBUG: Event MG_EV_WEBSOCKET_HANDSHAKE_DONE.\n");
const char* s = "Bingo boingo.";
mg_send_websocket_frame(nc, WEBSOCKET_OP_TEXT, s, strlen(s));
} break;
case MG_EV_WEBSOCKET_FRAME: {
printf("DEBUG: Event MG_EV_WEBSOCKET_FRAME.\n");
} break;
case MG_EV_CONNECT: {
printf("DEBUG: Event MG_EV_CONNECT.\n");
} break;
// case MG_EV_RECV: {
// printf("DEBUG: Event MG_EV_RECV.\n");
// } break;
// case MG_EV_SEND: {
// printf("DEBUG: Event MG_EV_SEND.\n");
// } break;
case MG_EV_CLOSE: {
printf("DEBUG: Event MG_EV_CLOSE.\n");
} break;
// case MG_EV_POLL: {
// printf("DEBUG: Event MG_EV_POLL.\n");
// } break;
case MG_EV_HTTP_REQUEST: {
printf("DEBUG: Event MG_EV_HTTP_REQUEST.\n");
char * s = strndup(hm->uri.p, hm->uri.len);
printf("DEBUG: URI = \"%s\"\n", s);
free(s);
if (has_prefix(&hm->uri, &api_prefix)) {
struct mg_str key;
key.p = hm->uri.p + api_prefix.len;
key.len = hm->uri.len - api_prefix.len;
if (is_equal(&hm->method, &s_get_method)) {
printf("DEBUG: HTTP GET method.\n");
if (mg_vcmp(&key, "/vs_connections") == 0) {
handle_vs_connections_call(nc, hm);
} else if (mg_vcmp(&key, "/alloc_info") == 0) {
handle_alloc_info_call(nc, hm);
}
} else if (is_equal(&hm->method, &s_put_method)) {
printf("DEBUG: HTTP PUT method.\n");
} else if (is_equal(&hm->method, &s_delele_method)) {
printf("DEBUG: HTTP DELETE method.\n");
}
} else {
mg_serve_http(nc, (struct http_message *) ev_data, http_server_options);
}
} break;
default: {
} break;
}
}
void* service_connections (void* arg) {
HTTP_Server *S = (HTTP_Server*)arg;
while(!S->shutting_down) {
mg_mgr_poll(&S->mgr, 1000);
}
return NULL;
}
int http_default_data(HTTP_Server *S) {
S->port = "8888";
S->shutting_down = false;
return 0;
}
int http_init(HTTP_Server *S) {
http_server_options.document_root = "www";
http_server_options.enable_directory_listing = "yes";
mg_mgr_init(&S->mgr, NULL);
printf("Starting web server on port %s\n", S->port);
S->nc = mg_bind(&S->mgr, S->port, ev_handler);
if (S->nc == NULL) {
printf("Failed to create listener.\n");
return 1;
}
mg_set_protocol_http_websocket(S->nc);
pthread_create( &S->server_thread, NULL, service_connections, (void*)S);
return 0;
}
int http_shutdown(HTTP_Server *S) {
printf("Shutting down web server on port %s\n", S->port);
S->shutting_down = true;
pthread_join(S->server_thread, NULL);
return 0;
}