Make API handling more extensible, and a lot of refactoring. #847 #730

This commit is contained in:
Penn, John M 047828115 2019-08-16 18:39:45 -05:00
parent ba4c86858a
commit fee9b89a07
8 changed files with 268 additions and 228 deletions

View File

@ -6,44 +6,37 @@ LIBRARY DEPENDENCIES:
#ifndef WSSESSION_HH
#define WSSESSION_HH
#include <time.h>
#include <vector>
#include <string>
#include <mongoose.h>
#include "WSSessionVariable.hh"
inline uint64_t to_nanoseconds(struct timespec* t) {
return t->tv_sec * (uint64_t)1000000000L + t->tv_nsec;
}
class WSsession {
public:
WSsession( struct mg_connection *nc);
void setTimeInterval(unsigned int milliseconds);
void addVariable(char* vname);
void stageValuesSynchronously();
void stageValues();
void sendValues();
void pause();
void unpause();
void clear();
void exit();
int handle_msg (std::string);
int emitError(const char* fmt, ... );
public:
WSsession( struct mg_connection *nc);
void setTimeInterval(unsigned int milliseconds);
void addVariable(char* vname);
void stageValuesSynchronously();
void stageValues();
void sendValues();
void pause();
void unpause();
void clear();
void exit();
int handle_msg (const char* client_msg);
int emitError(const char* fmt, ... );
static int bad_ref_int ;
private:
WSsession() {}
REF2* make_error_ref(const char* in_name);
struct mg_connection* connection;
std::vector<WSsessionVariable*> sessionVariables;
bool cyclicSendEnabled;
double stageTime;
bool valuesStaged;
long long nextTime;
long long intervalTimeTics;
static int bad_ref_int ;
private:
WSsession() {}
REF2* make_error_ref(const char* in_name);
struct mg_connection* connection;
std::vector<WSsessionVariable*> sessionVariables;
bool cyclicSendEnabled;
double stageTime;
bool valuesStaged;
long long nextTime;
long long intervalTimeTics;
};
#endif

View File

@ -16,20 +16,19 @@ LIBRARY DEPENDENCIES:
class WSsessionVariable {
public:
WSsessionVariable( REF2* variableType);
~WSsessionVariable();
const char* getName();
void stageValue();
void writeValue( std::ostream& chkpnt_os );
public:
WSsessionVariable( REF2* variableType);
~WSsessionVariable();
const char* getName();
void stageValue();
void writeValue( std::ostream& chkpnt_os );
private:
WSsessionVariable() {}
REF2 *varInfo;
void *address;
int size;
void *stageBuffer;
bool deref;
};
private:
WSsessionVariable() {}
REF2 *varInfo;
void *address;
int size;
void *stageBuffer;
bool deref;
};
#endif

View 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.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

View File

@ -11,27 +11,40 @@ LIBRARY DEPENDENCIES:
#include <sys/socket.h>
#include <mongoose.h>
#include <pthread.h>
#include <string>
#include <map>
#include "../include/WSSession.hh"
typedef struct {
typedef void (*httpMethodHandler)(struct mg_connection *, struct http_message *);
struct mg_mgr mgr; /* ** mongoose */
struct mg_connection *nc; /* ** mongoose */
const char* port;
const char* document_root;
pthread_t server_thread; /* ** */
bool shutting_down;
class HTTP_Server {
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;
} HTTP_Server ;
#ifdef __cplusplus
extern "C" {
#endif
int http_default_data(HTTP_Server * S) ;
int http_init(HTTP_Server * S) ;
int http_top_of_frame(HTTP_Server * S) ;
int http_shutdown(HTTP_Server * S) ;
#ifdef __cplusplus
}
#endif
std::map< std::string, httpMethodHandler> httpMethodHandlerMap; /* ** */
pthread_mutex_t APIMapLock; /* ** */
std::map<mg_connection*, WSsession*> sessionMap; /* ** */
pthread_mutex_t sessionMapLock; /* ** */
struct mg_serve_http_opts http_server_options; /* ** mongoose*/
struct mg_bind_opts bind_opts; /* ** mongoose*/
pthread_cond_t serviceConnections; /* ** */
// Trick Job-functions
int http_default_data();
int http_init();
int http_top_of_frame();
int http_shutdown();
void sendSessionValues(struct mg_connection *nc);
void handleClientMessage(struct mg_connection *nc, std::string msg);
void addSession(struct mg_connection *nc, WSsession* session);
void deleteSession(struct mg_connection *nc);
void install_API_GET_handler(std::string APIname, httpMethodHandler handler);
void handle_API_GET_request(struct mg_connection *nc, http_message *hm, std::string handlerName);
};
#endif

View File

@ -9,13 +9,13 @@ LIBRARY DEPENDENCIES:
class HttpSimObject : public Trick::SimObject {
public:
HTTP_Server http_server ;
HTTP_Server server ;
HttpSimObject() {
("default_data") http_default_data( &http_server ) ;
("initialization") http_init( &http_server ) ;
("top_of_frame") http_top_of_frame( &http_server ) ;
("shutdown") http_shutdown( &http_server ) ;
("default_data") server.http_default_data() ;
("initialization") server.http_init() ;
("top_of_frame") server.http_top_of_frame() ;
("shutdown") server.http_shutdown() ;
}
};

View File

@ -3,12 +3,12 @@ PURPOSE: (Represent the state and initial conditions of an http server)
LIBRARY DEPENDENCIES:
((simpleJSON.o))
**************************************************************************/
#include <sstream>
#include <iomanip> // for setprecision
#include "trick/memorymanager_c_intf.h"
#include "trick/exec_proto.h"
#include "../include/WSSession.hh"
#include "../include/simpleJSON.hh"
#include <sstream>
#include <iomanip> // for setprecision
WSsession::WSsession( struct mg_connection *nc ) {
connection = nc;
@ -141,10 +141,10 @@ void WSsession::clear() {
void WSsession::exit() {}
int WSsession::handle_msg (const char* client_msg) {
int WSsession::handle_msg (std::string client_msg) {
int status = 0;
std::vector<Member*> members = parseJSON(client_msg);
std::vector<Member*> members = parseJSON(client_msg.c_str());
std::vector<Member*>::iterator it;
const char *cmd;
const char *var_name;

View 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);
}

View File

@ -4,6 +4,7 @@ LIBRARY DEPENDENCIES:
(
(WSSession.o)
(WSSessionVariable.o)
(http_GET_handlers.o)
)
**************************************************************************/
@ -33,158 +34,58 @@ Messages sent from Server to Client
}
*/
#include <sstream>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include "../include/http_server.h"
#include "../include/WSSession.hh"
#include "trick/exec_proto.h"
#include "trick/VariableServer.hh"
extern Trick::VariableServer * the_vs ;
#include "trick/MemoryManager.hh"
extern Trick::MemoryManager* trick_MM;
#include "../include/http_GET_handlers.hh"
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 api_prefix = MG_MK_STR("/api/v1");
// ============================================================================
// HTTP GET Handlers
// ============================================================================
// Respond to HTTP GET method with URI="/api/v1/vs_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 tmp = ss.str();
mg_printf_http_chunk(nc, "%s", tmp.c_str());
mg_send_http_chunk(nc, "", 0);
}
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;
}
}
// Respond to HTTP GET method with URI="/api/v1/alloc_info".
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 tmp = ss.str();
mg_printf_http_chunk(nc, "%s", tmp.c_str());
mg_send_http_chunk(nc, "", 0);
}
#define DEBUG
static struct mg_serve_http_opts http_server_options;
std::map<mg_connection*, WSsession*> sessionMap;
pthread_mutex_t sessionMapLock;
pthread_cond_t serviceConnections;
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;
HTTP_Server* hs = (HTTP_Server *)nc->user_data;
switch(ev) {
case MG_EV_WEBSOCKET_HANDSHAKE_DONE: {
#ifdef DEBUG
char * s = strndup(hm->uri.p, hm->uri.len);
printf("WEBSOCKET[%p] OPENED. URI=\"%s\"\n", nc, s);
free(s);
#endif
std::string uri(hm->uri.p, hm->uri.len);
std::cout << "WEBSOCKET[" << (void*)nc << "] OPENED. URI=\"" << uri << "\"." << std::endl;
// Create a session object to store information about this web-socket connection.
WSsession* session = new WSsession(nc);
pthread_mutex_lock(&sessionMapLock);
sessionMap.insert( std::pair<mg_connection*, WSsession*>(nc, session) );
pthread_mutex_unlock(&sessionMapLock);
hs->addSession(nc, session);
} break;
case MG_EV_WEBSOCKET_FRAME: {
// --------------------------------------------------------
// Process websocket messages from the client (web browser).
// --------------------------------------------------------
case MG_EV_WEBSOCKET_FRAME: { // Process websocket messages from the client (web browser).
struct websocket_message *wm = (struct websocket_message *) ev_data;
char* msg = strndup((char*)wm->data, wm->size);
#ifdef DEBUG
printf("WEBSOCKET[%p] RECIEVED: %s\n", nc, msg);
#endif
std::string msg ((char*)wm->data, wm->size);
std::cout << "WEBSOCKET[" << (void*)nc << "] RECIEVED: " << msg << std::endl;
if (nc->flags & MG_F_IS_WEBSOCKET) {
// Find the session that goes with this connection.
std::map<mg_connection*, WSsession*>::iterator iter;
iter = sessionMap.find(nc);
if (iter != sessionMap.end()) {
WSsession* session = iter->second;
session->handle_msg(msg);
}
hs->handleClientMessage(nc, msg);
}
free(msg);
} break;
case MG_EV_CLOSE: {
if (nc->flags & MG_F_IS_WEBSOCKET) {
std::map<mg_connection*, WSsession*>::iterator iter;
iter = sessionMap.find(nc);
if (iter != sessionMap.end()) {
WSsession* session = iter->second;
delete session;
sessionMap.erase(iter);
}
#ifdef DEBUG
printf("WEBSOCKET[%p] CLOSED.\n", nc);
#endif
hs->deleteSession(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().
// The threaded function connectionAttendant() [below] periodically calls mg_mgr_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].
// Send websocket messages to the client (web browser).
if (nc->flags & MG_F_IS_WEBSOCKET) {
// Find the session that goes with the given websocket connection,
// and tell it to send its values to the client (web browser).
std::map<mg_connection*, WSsession*>::iterator iter;
iter = sessionMap.find(nc);
if (iter != sessionMap.end()) {
WSsession* session = iter->second;
session->sendValues();
}
hs->sendSessionValues(nc);
}
} break;
case MG_EV_HTTP_REQUEST: {
#ifdef DEBUG
char * s = strndup(hm->uri.p, hm->uri.len);
printf("HTTP_REQUEST: URI = \"%s\"\n", s);
free(s);
#endif
std::string uri(hm->uri.p, hm->uri.len);
std::cout << "HTTP_REQUEST: URI = \"" << uri << "\"" << std::endl;
if (mg_str_starts_with(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 (mg_strcmp(hm->method, s_get_method)==0) {
if (mg_vcmp(&key, "/vs_connections") == 0) {
handle_HTTP_GET_vs_connections(nc, hm);
} else if (mg_vcmp(&key, "/alloc_info") == 0) {
handle_HTTP_GET_alloc_info(nc, hm);
} else {
mg_http_send_error(nc, 404, "No such API.");
}
std::string handlerName (hm->uri.p + api_prefix.len, hm->uri.len - api_prefix.len);
hs->handle_API_GET_request(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) {
@ -192,7 +93,7 @@ static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
}
} else {
// Serve the files in the document-root directory, as specified by the URI.
mg_serve_http(nc, (struct http_message *) ev_data, http_server_options);
mg_serve_http(nc, (struct http_message *) ev_data, hs->http_server_options);
}
} break;
default: {
@ -203,62 +104,118 @@ static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
// =========================================================================
// This function runs in its own pthread to operate the webserver.
// =========================================================================
void* connectionAttendant (void* arg) {
static void* connectionAttendant (void* arg) {
HTTP_Server *S = (HTTP_Server*)arg;
while(1) {
pthread_mutex_lock(&sessionMapLock);
pthread_mutex_lock(&S->sessionMapLock);
// Wait here until the serviceConnections condition is signaled by the top_of_frame job.
pthread_cond_wait(&serviceConnections, &sessionMapLock);
pthread_cond_wait(&S->serviceConnections, &S->sessionMapLock);
if (S->shutting_down) {
pthread_mutex_unlock(&sessionMapLock);
pthread_mutex_unlock(&S->sessionMapLock);
return NULL;
} else {
mg_mgr_poll(&S->mgr, 50);
}
pthread_mutex_unlock(&sessionMapLock);
pthread_mutex_unlock(&S->sessionMapLock);
}
return NULL;
}
void HTTP_Server::install_API_GET_handler(std::string APIname, httpMethodHandler handler) {
pthread_mutex_lock(&APIMapLock);
httpMethodHandlerMap.insert(std::pair<std::string, httpMethodHandler>(APIname, handler));
pthread_mutex_unlock(&APIMapLock);
}
void HTTP_Server::handle_API_GET_request(struct mg_connection *nc, http_message *hm, std::string handlerName) {
std::map<std::string, httpMethodHandler>::iterator iter;
iter = httpMethodHandlerMap.find(handlerName);
if (iter != httpMethodHandlerMap.end()) {
httpMethodHandler handler = iter->second;
handler(nc, hm);
} else {
mg_http_send_error(nc, 404, "No such API.");
}
}
void HTTP_Server::sendSessionValues(struct mg_connection *nc) {
// Find the session that goes with the given websocket connection,
// and tell it to send its values to the client (web browser).
std::map<mg_connection*, WSsession*>::iterator iter;
iter = sessionMap.find(nc);
if (iter != sessionMap.end()) {
WSsession* session = iter->second;
session->sendValues();
}
}
void HTTP_Server::deleteSession(struct mg_connection *nc) {
std::map<mg_connection*, WSsession*>::iterator iter;
iter = sessionMap.find(nc);
if (iter != sessionMap.end()) {
WSsession* session = iter->second;
delete session;
sessionMap.erase(iter);
}
}
void HTTP_Server::handleClientMessage(struct mg_connection *nc, std::string msg) {
std::map<mg_connection*, WSsession*>::iterator iter;
iter = sessionMap.find(nc);
if (iter != sessionMap.end()) {
WSsession* session = iter->second;
session->handle_msg(msg);
}
}
void HTTP_Server::addSession(struct mg_connection *nc, WSsession* session) {
pthread_mutex_lock(&sessionMapLock);
sessionMap.insert( std::pair<mg_connection*, WSsession*>(nc, session) );
pthread_mutex_unlock(&sessionMapLock);
}
// =========================================================================
// Trick Sim Interface Functions
// =========================================================================
int http_default_data(HTTP_Server *S) {
S->port = "8888";
S->shutting_down = false;
S->document_root = "www";
int HTTP_Server::http_default_data() {
port = "8888";
document_root = "www";
shutting_down = false;
return 0;
}
int http_init(HTTP_Server *S) {
int HTTP_Server::http_init() {
http_server_options.document_root = S->document_root;
http_server_options.document_root = document_root;
http_server_options.enable_directory_listing = "yes";
mg_mgr_init(&S->mgr, NULL);
install_API_GET_handler("vs_connections", &handle_HTTP_GET_vs_connections);
install_API_GET_handler("alloc_info", &handle_HTTP_GET_alloc_info);
std::cout << "Trick Webserver: Starting, and listening on port " << S->port << ".\n"
<< "Trick Webserver: Document root = \"" << S->document_root << "\""
<< std::endl;
mg_mgr_init( &mgr, NULL );
S->nc = mg_bind(&S->mgr, S->port, ev_handler);
if (S->nc == NULL) {
std::cerr << "Trick Webserver: ERROR: Failed to create listener.\n"
<< "Perhaps another program is already using port " << S->port << "."
<< std::endl;
return 1;
memset(&bind_opts, 0, sizeof(bind_opts));
bind_opts.user_data = this;
listener = mg_bind_opt( &mgr, port, ev_handler, bind_opts);
if (listener != NULL) {
std::cout << "Trick Webserver: Starting, and 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(S->nc);
mg_set_protocol_http_websocket( listener );
pthread_cond_init(&serviceConnections, NULL);
pthread_create( &S->server_thread, NULL, connectionAttendant, (void*)S);
pthread_create( &server_thread, NULL, connectionAttendant, (void*)this );
return 0;
}
int http_top_of_frame(HTTP_Server * S) {
if (S->nc != NULL) {
int HTTP_Server::http_top_of_frame() {
if (listener != NULL) {
// Have all of the sessions stage their data. We do this here, in a
// top_of_frame job, so that all of the data is time-homogeneous.
std::map<mg_connection*, WSsession*>::iterator iter;
@ -268,21 +225,20 @@ int http_top_of_frame(HTTP_Server * S) {
session->stageValuesSynchronously();
}
pthread_mutex_unlock(&sessionMapLock);
// Signal the server thread to construct and send the values-message to the client.
pthread_cond_signal( &serviceConnections );
}
return 0;
}
int http_shutdown(HTTP_Server *S) {
if (S->nc != NULL) {
std::cout << "Trick Webserver: Shutting down on port " << S->port << "." << std::endl;
S->shutting_down = true;
int HTTP_Server::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(S->server_thread, NULL);
pthread_join(server_thread, NULL);
}
return 0;
}