trick/trick_source/sim_services/JSONVariableServer/JSONVariableServerThread.cpp
jmpenn 5065d96a15
Pre-increment (rather than post-increment) STL iterators in for loops… (#1692)
* Pre-increment (rather than post-increment) STL iterators in for loops. #1594

* Fix a goof. #1594
2024-04-18 11:41:35 -05:00

315 lines
11 KiB
C++

#include <iostream>
#include <sstream>
#include <stdlib.h>
#include <string.h>
#include "trick/JSONVariableServerThread.hh"
#include "trick/MemoryManager.hh"
#include "trick/memorymanager_c_intf.h"
#include "trick/exec_proto.h"
#include "trick/PythonPrint.hh"
#include "trick/tc_proto.h"
#include "trick/TrickConstant.hh"
Trick::JSONVariableServerThread::JSONVariableServerThread(TCDevice * listen_dev) :
Trick::ThreadBase("JSONVarServer") {
connection.disable_handshaking = TC_COMM_TRUE ;
connection.blockio_type = TC_COMM_BLOCKIO ;
tc_accept(listen_dev, &connection);
// Save the hostname and port of the listen server
hostname = listen_dev->hostname ;
port = listen_dev->port ;
incoming_msg = new char[MAX_CMD_LEN] ;
}
/*
@details
-# delete the space for incominig_msg.
-# These classes are always allocated... free the memory on the way out.
*/
Trick::JSONVariableServerThread::~JSONVariableServerThread() {
delete [] incoming_msg ;
}
/*
@details
-# Loop until the connection is broken or timed_out
-# Call select to wait up to 15 seconds for a message.
-# If a message is found, read it into the incoming_msg buffer
-# Call parse_request on the incoming_msg buffer
-# Disconnect the socket connection
*/
void * Trick::JSONVariableServerThread::thread_body() {
int nbytes = -1 ;
socklen_t sock_size = sizeof(connection.remoteServAddr) ;
fd_set rfds;
struct timeval timeout_time = { 15, 0 };
bool timed_out = false ;
while (! timed_out and nbytes != 0 ) {
FD_ZERO(&rfds);
FD_SET(connection.socket, &rfds);
timeout_time.tv_sec = 15 ;
select(connection.socket + 1, &rfds, NULL, NULL, &timeout_time);
if ( FD_ISSET(connection.socket, &rfds)) {
nbytes = recvfrom( connection.socket, incoming_msg, MAX_CMD_LEN, 0 ,
(struct sockaddr *)&connection.remoteServAddr, &sock_size ) ;
//std::cout << "nbytes = " << nbytes << std::endl ;
if (nbytes != 0 ) {
//std::cout << incoming_msg << std::endl ;
parse_request() ;
}
} else {
timed_out = true ;
}
}
tc_disconnect(&connection) ;
//std::cout << "closed variable server connection" << std::endl ;
return NULL ;
}
/*
@details
-# scan the incoming message for the command and requested path
-# start the return message with a "{"
-# if both a command and request is present
-# if the command is "GET"
-# if the path is "/" call get_top_page
-# else if the path starts with "/vars" call get_vars
-# else if the path starts with "/commands" call get_commands
-# else create an error message reply
-# else if the command is "POST" do something at some point.
-# else create an error message reply
-# else create an error message reply
-# close the return message with a "}"
-# create the http header for the reply message
-# call tc_write to send the reply message
*/
void Trick::JSONVariableServerThread::parse_request() {
// Need to protect against buffer overflow
char command[32] ;
char path[512] ;
std::stringstream ss ;
std::stringstream body ;
int ret ;
ret = sscanf(incoming_msg, "%s %s", command , path) ;
body << "{" << std::endl ;
if ( ret == 2 ) {
//std::cout << "command = " << command << std::endl ;
//std::cout << "path = " << path << std::endl ;
if ( ! strcmp(command, "GET") ) {
if ( ! strcmp(path, "/") ) {
// send the top page
ret = get_top_page(body) ;
} else if ( ! strncmp(path, "/vars", 5)) {
// get list of variables or value
ret = get_vars(body, &path[5]) ;
} else if ( ! strncmp(path, "/commands", 9)) {
// get list of commands
ret = get_commands(body, &path[9]) ;
} else {
ret = 404 ;
body << " \"name\" : \"" << path << "\" ," << std::endl ;
body << " \"message\" : \"Not Found\"" << std::endl ;
}
} else if ( ! strcmp( command, "POST") ) {
//TODO: Allow setting variable values with POST.
ret = 405 ;
body << " \"verb\" : \"" << command << "\" ," << std::endl ;
body << " \"message\" : \"Method not allowed\"" << std::endl ;
} else {
ret = 405 ;
body << " \"verb\" : \"" << command << "\" ," << std::endl ;
body << " \"message\" : \"Method not allowed\"" << std::endl ;
}
} else {
ret = 400 ;
body << " \"message\" : \"Bad Request\"" << std::endl ;
}
body << "}" << std::endl ;
ss << "HTTP/1.1 " << ret ;
switch (ret) {
case 200:
ss << " OK" ;
break ;
case 400:
ss << " Bad Request" ;
break ;
case 404:
ss << " Not Found" ;
break ;
case 405:
ss << " Method Not Allowed" ;
break ;
default:
ss << " Unknown" ;
break ;
}
ss << std::endl ;
ss << "Content-Type: application/json" << std::endl ;
ss << "Content-Length: " << body.str().size() << std::endl << std::endl ;
ss << body.str() ;
//std::cout << ss.str() ;
tc_write(&connection, (char *)ss.str().c_str() , ss.str().size()) ;
}
/*
@details
-# create top page contents and return success
*/
int Trick::JSONVariableServerThread::get_top_page( std::stringstream & body ) {
body << " \"commands\": \"http://" << hostname << ":" << port << "/commands\"," << std::endl ;
body << " \"vars\": \"http://" << hostname << ":" << port << "/vars\"" << std::endl ;
return 200 ;
}
/*
@details
-# if the path is empty after /commands create commands top level page.
-# else if the path is exec_get_sim_time return create a message with the sim time.
-# else create an error message.
*/
int Trick::JSONVariableServerThread::get_commands( std::stringstream & body , char * path ) {
int ret = 200 ;
if ( path[strlen(path) - 1] == '/') {
path[strlen(path) - 1] = '\0' ;
}
if ( path[0] == '\0' ) {
body << " \"exec_get_sim_time\": \"exec_get_sim_time()\"" << std::endl ;
} else if ( ! strcmp( path , "/exec_get_sim_time" ) ) {
body << " \"sim_time\": \"" << exec_get_sim_time() << "\"" << std::endl ;
} else {
body << " \"message\" : \"Not Found\"" << std::endl ;
ret = 404 ;
}
return ret ;
}
/*
@details
-# if the path is empty after /vars
-# loop through all allocations in the memory manager
-# if the allocation has a name create an item in the reply message for that allocation.
-# else
-# convert "/" characters to "." in the path and strip trailing "/" characters.
-# add converted variable name to the reply message.
-# call ref_attributes to find the variable.
-# if the variable exists
-# if the variable type is a structure.
-# loop through the attributes and add each of the struct/class variables as links in the reply message.
-# else add the variables value and units to the reply message.
-# add the description text to the reply message
-# add the type information to the reply message
-# add the type io specification to the reply message
-# if the variable has dimensions add the dimension information to the reply message.
-# else add an error message to the reply message
*/
int Trick::JSONVariableServerThread::get_vars( std::stringstream & body , char * path ) {
int ret = 200 ;
//std::cout << "get_vars " << path << std::endl ;
if ( path[0] == '\0' ) {
Trick::ALLOC_INFO_MAP_ITER aim_it ;
bool first_item = true ;
for ( aim_it = trick_MM->alloc_info_map_begin() ; aim_it != trick_MM->alloc_info_map_end() ; ++aim_it ) {
ALLOC_INFO * alloc_info = (*aim_it).second ;
if ( alloc_info->name != NULL and alloc_info->user_type_name != NULL ) {
if ( first_item == true ) {
first_item = false ;
} else {
body << " ," << std::endl ;
}
body << " \"" << alloc_info->name << "\" : \"" << alloc_info->user_type_name << "\"" ;
}
}
body << std::endl ;
} else {
//TODO: More path character translations for characters like "[]"
if ( path[strlen(path) - 1] == '/') {
path[strlen(path) - 1] = '\0' ;
}
for ( unsigned int ii = 1 ; ii < strlen(path) ; ii++ ) {
if ( path[ii] == '/') {
path[ii] = '.' ;
}
}
body << " \"name\" : \"" << &path[1] << "\" ," << std::endl ;
//std::cout << "getting var " << path << std::endl ;
// skip leading "/"
REF2 * ref = ref_attributes( &path[1] ) ;
if ( ref != NULL and ref->attr != NULL ) {
ret = 200 ;
//std::cout << "num_index " << ref->num_index << std::endl ;
if ( ref->attr->type == TRICK_STRUCTURED ) {
ATTRIBUTES * attr = (ATTRIBUTES *)ref->attr->attr ;
body << " \"links\" : [" << std::endl ;
for ( int ii = 0 ; attr[ii].name[0] != '\0' ; ii++ ) {
if ( ii != 0 ) {
body << " ," << std::endl ;
}
body << " \"" << attr[ii].name << "\"" ;
}
body << std::endl << " ] ," << std::endl ;
} else {
body << " \"value\" : " ;
Trick::PythonPrint::write_rvalue(body, ref->address, ref->attr, ref->num_index, 0, false, true) ;
body << " ," << std::endl ;
if ( ref->attr->units != NULL ) {
body << " \"units\" : \"" << ref->attr->units << "\" ," << std::endl ;
}
}
if ( ref->attr->des != NULL ) {
body << " \"description\" : \"" << ref->attr->des << "\" ," << std::endl ;
}
body << " \"type_name\" : " ;
if ( ref->attr->type_name != NULL ) {
body << "\"" << ref->attr->type_name << "\" ," << std::endl ;
} else {
body << "\"(null)\" ," << std::endl ;
}
body << " \"type\" : " << ref->attr->type << " ," << std::endl ;
body << " \"io\" : " << ref->attr->io ;
if ( (ref->attr->num_index - ref->num_index) > 0 ) {
body << " ," << std::endl ;
body << " \"num_index\" : " << ref->attr->num_index - ref->num_index << " ," << std::endl ;
body << " \"index\" : [" << std::endl ;
for ( int ii = ref->num_index , jj = 0 ; ii < ref->attr->num_index ; ii++ , jj++ ) {
if ( jj != 0 ) {
body << " ," << std::endl ;
}
body << " {" << std::endl ;
body << " \"size\" : " << ref->attr->index[ii].size << " ," << std::endl ;
body << " \"start\" : " << ref->attr->index[ii].start << std::endl ;
body << " }" ;
}
body << std::endl << " ]" << std::endl ;
} else {
body << std::endl ;
}
free(ref) ;
} else {
body << " \"message\" : \"Not Found\"" << std::endl ;
ret = 404 ;
}
}
return ret ;
}