1
0
Fork 0

httpd: update mongoose and websockets

* mongoose updated to 5.3
* first stab at implementing websockets, here a property change listener
  websocket. This websocket is at ws://yourhost:yourport/PropertyListener
  see FGDATA/Docs/gui/radio.html for an example
This commit is contained in:
Torsten Dreyer 2014-03-12 22:39:37 +01:00
parent 275d2dc7fa
commit 7132947d16
13 changed files with 2345 additions and 1105 deletions

File diff suppressed because it is too large Load diff

View file

@ -20,7 +20,7 @@
#ifndef MONGOOSE_HEADER_INCLUDED
#define MONGOOSE_HEADER_INCLUDED
#define MONGOOSE_VERSION "5.2"
#define MONGOOSE_VERSION "5.3"
#include <stdio.h> // required for FILE
#include <stddef.h> // required for size_t
@ -58,21 +58,31 @@ struct mg_connection {
};
struct mg_server; // Opaque structure describing server instance
typedef int (*mg_handler_t)(struct mg_connection *);
enum mg_result { MG_FALSE, MG_TRUE, MG_MORE };
enum mg_event {
MG_POLL = 100, // Callback return value is ignored
MG_CONNECT, // If callback returns MG_FALSE, connect fails
MG_AUTH, // If callback returns MG_FALSE, authentication fails
MG_REQUEST, // If callback returns MG_FALSE, Mongoose continues with req
MG_REPLY, // If callback returns MG_FALSE, Mongoose closes connection
MG_CLOSE, // Connection is closed
MG_LUA, // Called before LSP page invoked
MG_HTTP_ERROR // If callback returns MG_FALSE, Mongoose continues with err
};
typedef int (*mg_handler_t)(struct mg_connection *, enum mg_event);
// Server management functions
struct mg_server *mg_create_server(void *server_param);
struct mg_server *mg_create_server(void *server_param, mg_handler_t handler);
void mg_destroy_server(struct mg_server **);
const char *mg_set_option(struct mg_server *, const char *opt, const char *val);
unsigned int mg_poll_server(struct mg_server *, int milliseconds);
void mg_set_request_handler(struct mg_server *, mg_handler_t);
void mg_set_http_error_handler(struct mg_server *, mg_handler_t);
void mg_set_auth_handler(struct mg_server *, mg_handler_t);
int mg_poll_server(struct mg_server *, int milliseconds);
const char **mg_get_valid_option_names(void);
const char *mg_get_option(const struct mg_server *server, const char *name);
void mg_set_listening_socket(struct mg_server *, int sock);
int mg_get_listening_socket(struct mg_server *);
void mg_iterate_over_connections(struct mg_server *, mg_handler_t, void *);
void mg_iterate_over_connections(struct mg_server *, mg_handler_t);
void mg_wakeup_server(struct mg_server *);
struct mg_connection *mg_connect(struct mg_server *, const char *, int, int);
// Connection management functions
void mg_send_status(struct mg_connection *, int status_code);
@ -102,19 +112,15 @@ void *mg_start_thread(void *(*func)(void *), void *param);
char *mg_md5(char buf[33], ...);
int mg_authorize_digest(struct mg_connection *c, FILE *fp);
// Callback function return codes
enum { MG_REQUEST_NOT_PROCESSED, MG_REQUEST_PROCESSED, MG_REQUEST_CALL_AGAIN };
enum { MG_AUTH_FAIL, MG_AUTH_OK };
enum { MG_ERROR_NOT_PROCESSED, MG_ERROR_PROCESSED };
enum { MG_CLIENT_CONTINUE, MG_CLIENT_CLOSE };
// HTTP client events
enum {
MG_CONNECT_SUCCESS, MG_CONNECT_FAILURE,
MG_DOWNLOAD_SUCCESS, MG_DOWNLOAD_FAILURE
};
int mg_connect(struct mg_server *, const char *host, int port, int use_ssl,
mg_handler_t handler, void *param);
// Lua utility functions
#ifdef MONGOOSE_USE_LUA
#include <lua.h>
#include <lauxlib.h>
void reg_string(lua_State *L, const char *name, const char *val);
void reg_int(lua_State *L, const char *name, int val);
void reg_function(lua_State *L, const char *,
lua_CFunction, struct mg_connection *);
#endif
#ifdef __cplusplus
}

View file

@ -6,6 +6,9 @@ set(SOURCES
PropertyUriHandler.cxx
JsonUriHandler.cxx
RunUriHandler.cxx
PropertyChangeWebsocket.cxx
PropertyChangeObserver.cxx
jsonprops.cxx
)
set(HEADERS
@ -16,6 +19,10 @@ set(HEADERS
JsonUriHandler.hxx
RunUriHandler.hxx
HTTPRequest.hxx
Websocket.hxx
PropertyChangeWebsocket.hxx
PropertyChangeObserver.hxx
jsonprops.hxx
)
flightgear_component(Http "${SOURCES}" "${HEADERS}")

View file

@ -20,52 +20,15 @@
#include "JsonUriHandler.hxx"
#include "jsonprops.hxx"
#include <Main/fg_props.hxx>
#include <simgear/props/props.hxx>
#include <3rdparty/cjson/cJSON.h>
using std::string;
namespace flightgear {
namespace http {
static const char * getPropertyTypeString( simgear::props::Type type )
{
switch( type ) {
case simgear::props::NONE: return "-";
case simgear::props::ALIAS: return "alias";
case simgear::props::BOOL: return "bool";
case simgear::props::INT: return "int";
case simgear::props::LONG: return "long";
case simgear::props::FLOAT: return "float";
case simgear::props::DOUBLE: return "double";
case simgear::props::STRING: return "string";
case simgear::props::UNSPECIFIED: return "unspecified";
case simgear::props::EXTENDED: return "extended";
case simgear::props::VEC3D: return "vec3d";
case simgear::props::VEC4D: return "vec4d";
default: return "?";
}
}
static cJSON * PropToJson( SGPropertyNode_ptr n, int depth )
{
cJSON * json = cJSON_CreateObject();
cJSON_AddItemToObject(json, "path", cJSON_CreateString(n->getPath(true).c_str()));
cJSON_AddItemToObject(json, "name", cJSON_CreateString(n->getName()));
cJSON_AddItemToObject(json, "value", cJSON_CreateString(n->getStringValue()));
cJSON_AddItemToObject(json, "type", cJSON_CreateString(getPropertyTypeString(n->getType())));
cJSON_AddItemToObject(json, "index", cJSON_CreateNumber(n->getIndex()));
if( depth > 0 && n->nChildren() > 0 ) {
cJSON * jsonArray = cJSON_CreateArray();
for( int i = 0; i < n->nChildren(); i++ )
cJSON_AddItemToArray( jsonArray, PropToJson( n->getChild(i), depth-1 ) );
cJSON_AddItemToObject( json, "children", jsonArray );
}
return json;
}
bool JsonUriHandler::handleGetRequest( const HTTPRequest & request, HTTPResponse & response )
{
response.Header["Content-Type"] = "application/json; charset=UTF-8";
@ -101,13 +64,7 @@ bool JsonUriHandler::handleGetRequest( const HTTPRequest & request, HTTPResponse
}
cJSON * json = PropToJson( node, depth );
char * jsonString = indent ? cJSON_Print( json ) : cJSON_PrintUnformatted( json );
response.Content = jsonString;
free( jsonString );
cJSON_Delete( json );
response.Content = JSON::toJsonString( indent, node, depth );
return true;

View file

@ -0,0 +1,91 @@
// PropertyChangeObserver.cxx -- Watch properties for changes
//
// Written by Torsten Dreyer, started April 2014.
//
// Copyright (C) 2014 Torsten Dreyer
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include "PropertyChangeObserver.hxx"
#include <Main/fg_props.hxx>
using std::string;
namespace flightgear {
namespace http {
PropertyChangeObserver::PropertyChangeObserver()
{
}
PropertyChangeObserver::~PropertyChangeObserver()
{
//
}
void PropertyChangeObserver::check()
{
for (Entries_t::iterator it = _entries.begin(); it != _entries.end(); ++it) {
if (false == (*it)->_node.isShared()) {
// node is no longer used but by us - remove the entry
it = _entries.erase(it);
continue;
}
(*it)->_changed = (*it)->_prevValue != (*it)->_node->getStringValue();
if ((*it)->_changed) (*it)->_prevValue = (*it)->_node->getStringValue();
}
}
const SGPropertyNode_ptr PropertyChangeObserver::addObservation( const string propertyName)
{
for (Entries_t::iterator it = _entries.begin(); it != _entries.end(); ++it) {
if (propertyName == (*it)->_node->getPath(true) ) {
return (*it)->_node;
}
}
try {
PropertyChangeObserverEntryRef entry = new PropertyChangeObserverEntry();
entry->_node = fgGetNode( propertyName, true );
_entries.push_back( entry );
return entry->_node;
}
catch( string & s ) {
SG_LOG(SG_NETWORK,SG_ALERT,"httpd: can't observer '" << propertyName << "'. Invalid name." );
}
SGPropertyNode_ptr empty;
return empty;
}
bool PropertyChangeObserver::getChangedValue(const SGPropertyNode_ptr node, string & out)
{
for (Entries_t::iterator it = _entries.begin(); it != _entries.end(); ++it) {
PropertyChangeObserverEntryRef entry = *it;
if( entry->_node == node && entry->_changed ) {
out = entry->_node->getStringValue();
entry->_changed = false;
return true;
}
}
return false;
}
} // namespace http
} // namespace flightgear

View file

@ -0,0 +1,61 @@
// PropertyChangeObserver.hxx -- Watch properties for changes
//
// Written by Torsten Dreyer, started April 2014.
//
// Copyright (C) 2014 Torsten Dreyer
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifndef PROPERTYCHANGEOBSERVER_HXX_
#define PROPERTYCHANGEOBSERVER_HXX_
#include <simgear/props/props.hxx>
#include <string>
#include <vector>
namespace flightgear {
namespace http {
struct PropertyChangeObserverEntry : public SGReferenced {
PropertyChangeObserverEntry()
: _changed(true)
{
}
SGPropertyNode_ptr _node;
std::string _prevValue;
bool _changed;
};
typedef SGSharedPtr<PropertyChangeObserverEntry> PropertyChangeObserverEntryRef;
class PropertyChangeObserver {
public:
PropertyChangeObserver();
virtual ~PropertyChangeObserver();
const SGPropertyNode_ptr addObservation( const std::string propertyName);
bool getChangedValue(const SGPropertyNode_ptr node, std::string & out);
void check();
private:
typedef std::vector<PropertyChangeObserverEntryRef> Entries_t;
Entries_t _entries;
};
} // namespace http
} // namespace flightgear
#endif /* PROPERTYCHANGEOBSERVER_HXX_ */

View file

@ -0,0 +1,132 @@
// PropertyChangeWebsocket.cxx -- A websocket for propertychangelisteners
//
// Written by Torsten Dreyer, started April 2014.
//
// Copyright (C) 2014 Torsten Dreyer
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include "PropertyChangeWebsocket.hxx"
#include "PropertyChangeObserver.hxx"
#include "jsonprops.hxx"
#include <simgear/debug/logstream.hxx>
#include <3rdparty/cjson/cJSON.h>
namespace flightgear {
namespace http {
using std::string;
static unsigned nextid = 0;
PropertyChangeWebsocket::PropertyChangeWebsocket(PropertyChangeObserver * propertyChangeObserver)
: id(++nextid), _propertyChangeObserver(propertyChangeObserver)
{
}
PropertyChangeWebsocket::~PropertyChangeWebsocket()
{
}
void PropertyChangeWebsocket::close()
{
SG_LOG(SG_NETWORK, SG_ALERT, "closing PropertyChangeWebsocket #" << id);
_watchedNodes.clear();
}
void PropertyChangeWebsocket::handleRequest(const HTTPRequest & request, WebsocketWriter &)
{
if (request.Content.empty()) return;
/*
* allowed JSON is
{
command : 'addListener',
nodes : [
'/bar/baz',
'/foo/bar'
],
node: '/bax/foo'
}
*/
cJSON * json = cJSON_Parse(request.Content.c_str());
if ( NULL != json) {
string command;
cJSON * j = cJSON_GetObjectItem(json, "command");
if ( NULL != j && NULL != j->valuestring) {
command = j->valuestring;
}
string nodeName;
j = cJSON_GetObjectItem(json, "node");
if ( NULL != j && NULL != j->valuestring) nodeName = j->valuestring;
_watchedNodes.handleCommand(command, nodeName, _propertyChangeObserver);
cJSON * nodes = cJSON_GetObjectItem(json, "nodes");
if ( NULL != nodes) {
for (int i = 0; i < cJSON_GetArraySize(nodes); i++) {
cJSON * node = cJSON_GetArrayItem(nodes, i);
if ( NULL == node) continue;
if ( NULL == node->valuestring) continue;
nodeName = node->valuestring;
_watchedNodes.handleCommand(command, nodeName, _propertyChangeObserver);
}
}
cJSON_Delete(json);
}
}
void PropertyChangeWebsocket::update(WebsocketWriter & writer)
{
for (WatchedNodesList::iterator it = _watchedNodes.begin(); it != _watchedNodes.end(); ++it) {
SGPropertyNode_ptr node = *it;
string newValue;
if (_propertyChangeObserver->getChangedValue(node, newValue)) {
SG_LOG(SG_NETWORK, SG_ALERT, "httpd: new Value for " << node->getPath(true) << " '" << newValue << "' #" << id);
writer.writeText( JSON::toJsonString( false, node, 0 ) );
}
}
}
void PropertyChangeWebsocket::WatchedNodesList::handleCommand(const string & command, const string & node,
PropertyChangeObserver * propertyChangeObserver)
{
if (command == "addListener") {
for (iterator it = begin(); it != end(); ++it) {
if (node == (*it)->getPath(true)) {
SG_LOG(SG_NETWORK, SG_ALERT, "httpd: " << command << " '" << node << "' ignored (duplicate)");
return; // dupliate
}
}
SGPropertyNode_ptr n = propertyChangeObserver->addObservation(node);
if (n.valid()) push_back(n);
SG_LOG(SG_NETWORK, SG_ALERT, "httpd: " << command << " '" << node << "'");
} else if (command == "removeListener") {
for (iterator it = begin(); it != end(); ++it) {
if (node == (*it)->getPath(true)) {
this->erase(it);
SG_LOG(SG_NETWORK, SG_ALERT, "httpd: " << command << " '" << node << "'");
return;
}
SG_LOG(SG_NETWORK, SG_ALERT, "httpd: " << command << " '" << node << "' ignored (not found)");
}
}
}
} // namespace http
} // namespace flightgear

View file

@ -0,0 +1,57 @@
// PropertyChangeWebsocket.hxx -- A websocket for propertychangelisteners
//
// Written by Torsten Dreyer, started April 2014.
//
// Copyright (C) 2014 Torsten Dreyer
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifndef PROPERTYCHANGEWEBSOCKET_HXX_
#define PROPERTYCHANGEWEBSOCKET_HXX_
#include "Websocket.hxx"
#include <simgear/props/props.hxx>
#include <vector>
namespace flightgear {
namespace http {
class PropertyChangeObserver;
class PropertyChangeWebsocket: public Websocket {
public:
PropertyChangeWebsocket(PropertyChangeObserver * propertyChangeObserver);
virtual ~PropertyChangeWebsocket();
virtual void close();
virtual void handleRequest(const HTTPRequest & request, WebsocketWriter & writer);
virtual void update(WebsocketWriter & writer);
private:
unsigned id;
PropertyChangeObserver * _propertyChangeObserver;
class WatchedNodesList: public std::vector<SGPropertyNode_ptr> {
public:
void handleCommand(const std::string & command, const std::string & node, PropertyChangeObserver * propertyChangeObserver);
};
WatchedNodesList _watchedNodes;
};
}
}
#endif /* PROPERTYCHANGEWEBSOCKET_HXX_ */

View file

@ -20,6 +20,7 @@
#include "RunUriHandler.hxx"
#include "jsonprops.hxx"
#include <simgear/props/props.hxx>
#include <simgear/structure/commands.hxx>
#include <Main/globals.hxx>
@ -31,43 +32,6 @@ using std::string;
namespace flightgear {
namespace http {
static void JsonToProp( cJSON * json, SGPropertyNode_ptr base )
{
if( NULL == json ) return;
cJSON * cj = cJSON_GetObjectItem( json, "name" );
if( NULL == cj ) return; // a node with no name?
char * name = cj->valuestring;
if( NULL == name ) return; // still no name?
//TODO: check valid name
int index = 0;
cj = cJSON_GetObjectItem( json, "index" );
if( NULL != cj ) index = cj->valueint;
if( index < 0 ) return;
SGPropertyNode_ptr n = base->getNode( name, index, true );
cJSON * children = cJSON_GetObjectItem( json, "children" );
if( NULL != children ) {
for( int i = 0; i < cJSON_GetArraySize( children ); i++ ) {
JsonToProp( cJSON_GetArrayItem( children, i ), n );
}
} else {
//TODO: set correct type
/*
char * type = "";
cj = cJSON_GetObjectItem( json, "type" );
if( NULL != cj ) type = cj->valuestring;
*/
char * value = NULL;
cj = cJSON_GetObjectItem( json, "value" );
if( NULL != cj ) value = cj->valuestring;
if( NULL != value )
n->setUnspecifiedValue( value );
}
}
bool RunUriHandler::handleRequest( const HTTPRequest & request, HTTPResponse & response )
{
@ -81,7 +45,7 @@ bool RunUriHandler::handleRequest( const HTTPRequest & request, HTTPResponse & r
SGPropertyNode_ptr args = new SGPropertyNode();
cJSON * json = cJSON_Parse( request.Content.c_str() );
JsonToProp( json, args );
JSON::toProp( json, args );
cJSON_Delete( json );
if ( globals->get_commands()->execute(command.c_str(), args) ) {

View file

@ -0,0 +1,80 @@
// Websocket.cxx -- a base class for websockets
//
// Written by Torsten Dreyer, started April 2014.
//
// Copyright (C) 2014 Torsten Dreyer
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifndef WEBSOCKET_HXX_
#define WEBSOCKET_HXX_
#include "HTTPRequest.hxx"
#include <string>
namespace flightgear {
namespace http {
class WebsocketWriter {
public:
virtual ~WebsocketWriter()
{
}
virtual int writeToWebsocket(int opcode, const char * data, size_t len) = 0;
// ref: http://tools.ietf.org/html/rfc6455#section-5.2
int writeContinuation(const char * data, size_t len); // { return writeToWebsocket( 0, data, len ); }
int writeText(const char * data, size_t len)
{
return writeToWebsocket(1, data, len);
}
inline int writeText(const std::string & text)
{
return writeText(text.c_str(), text.length());
}
inline int writeBinary(const char * data, size_t len)
{
return writeToWebsocket(2, data, len);
}
inline int writeConnectionClose(const char * data, size_t len)
{
return writeToWebsocket(8, data, len);
}
inline int writePing(const char * data, size_t len)
{
return writeToWebsocket(9, data, len);
}
inline int writePong(const char * data, size_t len)
{
return writeToWebsocket(0xa, data, len);
}
};
class Websocket {
public:
virtual ~Websocket()
{
}
virtual void close() = 0;
virtual void handleRequest(const HTTPRequest & request, WebsocketWriter & writer) = 0;
virtual void update(WebsocketWriter & writer) = 0;
};
} // namespace http
} // namespace flightgear
#endif /* WEBSOCKET_HXX_ */

View file

@ -20,13 +20,16 @@
#include "httpd.hxx"
#include "HTTPRequest.hxx"
#include "PropertyChangeWebsocket.hxx"
#include "ScreenshotUriHandler.hxx"
#include "PropertyUriHandler.hxx"
#include "JsonUriHandler.hxx"
#include "RunUriHandler.hxx"
#include "PropertyChangeObserver.hxx"
#include <Main/fg_props.hxx>
#include <Include/version.h>
#include <3rdparty/mongoose/mongoose.h>
#include <3rdparty/cjson/cJSON.h>
#include <string>
#include <vector>
@ -39,31 +42,37 @@ namespace http {
const char * PROPERTY_ROOT = "/sim/http";
class MongooseHttpd : public FGHttpd {
class MongooseHttpd: public FGHttpd {
public:
MongooseHttpd( SGPropertyNode_ptr );
~MongooseHttpd();
MongooseHttpd(SGPropertyNode_ptr);
~MongooseHttpd();
void init();
void bind();
void unbind();
void shutdown();
void update( double dt );
void update(double dt);
private:
int requestHandler( struct mg_connection * );
static int staticRequestHandler( struct mg_connection * );
int requestHandler(struct mg_connection *);
int websocketHandler(struct mg_connection *);
void closeWebsocket(struct mg_connection *);
int iterateCallback(struct mg_connection *, mg_event event);
static int staticRequestHandler(struct mg_connection *, mg_event event);
static int staticIterateCallback(struct mg_connection *, mg_event event);
struct mg_server *_server;
SGPropertyNode_ptr _configNode;
typedef int (MongooseHttpd::*handler_t)( struct mg_connection * );
typedef int (MongooseHttpd::*handler_t)(struct mg_connection *);
typedef vector<SGSharedPtr<URIHandler> > URIHandlerMap;
URIHandlerMap _uriHandlers;
PropertyChangeObserver _propertyChangeObserver;
};
MongooseHttpd::MongooseHttpd( SGPropertyNode_ptr configNode ) :
_server(NULL),
_configNode(configNode)
MongooseHttpd::MongooseHttpd(SGPropertyNode_ptr configNode)
: _server(NULL), _configNode(configNode)
{
}
@ -75,48 +84,47 @@ MongooseHttpd::~MongooseHttpd()
void MongooseHttpd::init()
{
SGPropertyNode_ptr n = _configNode->getNode("uri-handler");
if( n.valid() ) {
if (n.valid()) {
const char * uri;
if( (uri=n->getStringValue( "screenshot" ))[0] != 0 ) {
SG_LOG(SG_NETWORK,SG_INFO,"httpd: adding screenshot uri handler at " << uri );
_uriHandlers.push_back( new flightgear::http::ScreenshotUriHandler( uri ) );
if ((uri = n->getStringValue("screenshot"))[0] != 0) {
SG_LOG(SG_NETWORK, SG_INFO, "httpd: adding screenshot uri handler at " << uri);
_uriHandlers.push_back(new flightgear::http::ScreenshotUriHandler(uri));
}
if( (uri=n->getStringValue( "property" ))[0] != 0 ) {
SG_LOG(SG_NETWORK,SG_INFO,"httpd: adding screenshot property handler at " << uri );
_uriHandlers.push_back( new flightgear::http::PropertyUriHandler( uri ) );
if ((uri = n->getStringValue("property"))[0] != 0) {
SG_LOG(SG_NETWORK, SG_INFO, "httpd: adding screenshot property handler at " << uri);
_uriHandlers.push_back(new flightgear::http::PropertyUriHandler(uri));
}
if( (uri=n->getStringValue( "json" ))[0] != 0 ) {
SG_LOG(SG_NETWORK,SG_INFO,"httpd: adding json property handler at " << uri );
_uriHandlers.push_back( new flightgear::http::JsonUriHandler( uri ) );
if ((uri = n->getStringValue("json"))[0] != 0) {
SG_LOG(SG_NETWORK, SG_INFO, "httpd: adding json property handler at " << uri);
_uriHandlers.push_back(new flightgear::http::JsonUriHandler(uri));
}
if( (uri=n->getStringValue( "run" ))[0] != 0 ) {
SG_LOG(SG_NETWORK,SG_INFO,"httpd: adding run handler at " << uri );
_uriHandlers.push_back( new flightgear::http::RunUriHandler( uri ) );
if ((uri = n->getStringValue("run"))[0] != 0) {
SG_LOG(SG_NETWORK, SG_INFO, "httpd: adding run handler at " << uri);
_uriHandlers.push_back(new flightgear::http::RunUriHandler(uri));
}
}
_server = mg_create_server(this);
_server = mg_create_server(this, MongooseHttpd::staticRequestHandler);
n = _configNode->getNode("options");
if( n.valid() ) {
if (n.valid()) {
string docRoot = n->getStringValue( "document-root", fgGetString("/sim/fg-root") );
if( docRoot[0] != '/' ) docRoot.insert(0,"/").insert(0,fgGetString("/sim/fg-root"));
mg_set_option(_server, "document_root", docRoot.c_str() );
string docRoot = n->getStringValue("document-root", fgGetString("/sim/fg-root"));
if (docRoot[0] != '/') docRoot.insert(0, "/").insert(0, fgGetString("/sim/fg-root"));
mg_set_option(_server, "listening_port", n->getStringValue( "listening-port", "8080" ) );
mg_set_option(_server, "document_root", docRoot.c_str());
mg_set_option(_server, "listening_port", n->getStringValue("listening-port", "8080"));
// mg_set_option(_server, "url_rewrites", n->getStringValue( "url-rewrites", "" ) );
mg_set_option(_server, "enable_directory_listing", n->getStringValue( "enable-directory-listing", "yes" ) );
mg_set_option(_server, "idle_timeout_ms", n->getStringValue( "idle-timeout-ms", "30000" ) );
mg_set_option(_server, "index_files", n->getStringValue( "index-files", "index.html" ) );
mg_set_option(_server, "enable_directory_listing", n->getStringValue("enable-directory-listing", "yes"));
mg_set_option(_server, "idle_timeout_ms", n->getStringValue("idle-timeout-ms", "30000"));
mg_set_option(_server, "index_files", n->getStringValue("index-files", "index.html"));
}
mg_set_request_handler( _server, MongooseHttpd::staticRequestHandler );
}
void MongooseHttpd::bind()
@ -133,19 +141,27 @@ void MongooseHttpd::shutdown()
{
}
void MongooseHttpd::update( double dt )
void MongooseHttpd::update(double dt)
{
mg_poll_server( _server, 0 );
_propertyChangeObserver.check();
mg_poll_server(_server, 0);
mg_iterate_over_connections(_server, &MongooseHttpd::staticIterateCallback);
}
class MongooseHTTPRequest : public HTTPRequest {
class MongooseHTTPRequest: public HTTPRequest {
private:
inline string NotNull( const char * cp ) { return NULL == cp ? "" : cp; }
inline string NotNull(const char * cp, size_t n = string::npos)
{
if ( NULL == cp || 0 == n) return string("");
if (string::npos == n) return string(cp);
return string(cp, n);
}
public:
MongooseHTTPRequest( struct mg_connection * connection ) {
MongooseHTTPRequest(struct mg_connection * connection)
{
Method = NotNull(connection->request_method);
Uri = urlDecode( NotNull(connection->uri));
Uri = urlDecode(NotNull(connection->uri));
HttpVersion = NotNull(connection->http_version);
QueryString = urlDecode(NotNull(connection->query_string));
@ -156,50 +172,94 @@ public:
using namespace simgear::strutils;
string_list pairs = split(string(QueryString), "&");
for( string_list::iterator it = pairs.begin(); it != pairs.end(); ++it ) {
string_list nvp = split( *it, "=" );
if( nvp.size() != 2 ) continue;
RequestVariables.insert( make_pair( nvp[0], nvp[1] ) );
for (string_list::iterator it = pairs.begin(); it != pairs.end(); ++it) {
string_list nvp = split(*it, "=");
if (nvp.size() != 2) continue;
RequestVariables.insert(make_pair(nvp[0], nvp[1]));
}
for( int i = 0; i < connection->num_headers; i++ )
for (int i = 0; i < connection->num_headers; i++)
HeaderVariables[connection->http_headers[i].name] = connection->http_headers[i].value;
Content = NotNull(connection->content);
Content = NotNull(connection->content, connection->content_len);
}
string urlDecode( const string & s ) {
string urlDecode(const string & s)
{
string r = "";
int max = s.length();
int a, b;
for ( int i = 0; i < max; i++ ) {
if ( s[i] == '+' ) {
for (int i = 0; i < max; i++) {
if (s[i] == '+') {
r += ' ';
} else if ( s[i] == '%' && i + 2 < max
&& isxdigit(s[i + 1])
&& isxdigit(s[i + 2]) ) {
} else if (s[i] == '%' && i + 2 < max && isxdigit(s[i + 1]) && isxdigit(s[i + 2])) {
i++;
a = isdigit(s[i]) ? s[i] - '0' : toupper(s[i]) - 'A' + 10;
i++;
b = isdigit(s[i]) ? s[i] - '0' : toupper(s[i]) - 'A' + 10;
r += (char)(a * 16 + b);
r += (char) (a * 16 + b);
} else {
r += s[i];
}
}
return r;
}
};
int MongooseHttpd::requestHandler( struct mg_connection * connection )
void MongooseHttpd::closeWebsocket(struct mg_connection * connection)
{
Websocket * websocket = static_cast<Websocket*>(connection->connection_param);
if ( NULL != websocket) websocket->close();
delete websocket;
}
class MongooseWebsocketWriter: public WebsocketWriter {
public:
MongooseWebsocketWriter(struct mg_connection * connection)
: _connection(connection)
{
}
virtual int writeToWebsocket(int opcode, const char * data, size_t len)
{
return mg_websocket_write( _connection, opcode, data, len );
}
private:
struct mg_connection * _connection;
};
int MongooseHttpd::websocketHandler(struct mg_connection * connection)
{
MongooseHTTPRequest request(connection);
MongooseWebsocketWriter writer(connection);
Websocket * websocket = static_cast<Websocket*>(connection->connection_param);
if ( NULL == websocket) {
if (request.Uri.find("/PropertyListener") == 0) {
SG_LOG(SG_ALL, SG_ALERT, "new PropertyChangeWebsocket for: " << request.Uri);
websocket = new PropertyChangeWebsocket(&_propertyChangeObserver);
connection->connection_param = websocket;
} else {
SG_LOG(SG_ALL, SG_ALERT, "httpd: unhandled websocket uri: " << request.Uri);
return MG_FALSE;
}
}
websocket->handleRequest(request, writer);
return MG_TRUE;
}
int MongooseHttpd::requestHandler(struct mg_connection * connection)
{
MongooseHTTPRequest request(connection);
HTTPResponse response;
response.Header["Server"] = "FlightGear/" FLIGHTGEAR_VERSION " Mongoose/" MONGOOSE_VERSION;
response.Header["Connection"] = "close";
response.Header["Cache-Control"] = "no-cache";
{
{
char buf[64];
time_t now = time(NULL);
strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&now));
@ -207,41 +267,73 @@ int MongooseHttpd::requestHandler( struct mg_connection * connection )
}
bool processed = false;
for( URIHandlerMap::iterator it = _uriHandlers.begin(); it != _uriHandlers.end(); ++it ) {
for (URIHandlerMap::iterator it = _uriHandlers.begin(); it != _uriHandlers.end(); ++it) {
URIHandler * handler = *it;
const string uri = connection->uri;
if( uri.find( handler->getUri() ) == 0 ) {
processed = handler->handleRequest( request, response );
if (request.Uri.find(handler->getUri()) == 0) {
processed = handler->handleRequest(request, response);
break;
}
}
if( processed ) {
for( HTTPResponse::Header_t::const_iterator it = response.Header.begin(); it != response.Header.end(); ++it ) {
if (processed) {
for (HTTPResponse::Header_t::const_iterator it = response.Header.begin(); it != response.Header.end(); ++it) {
const string name = it->first;
const string value = it->second;
if( name.empty() || value.empty() ) continue;
mg_send_header( connection, name.c_str(), value.c_str() );
if (name.empty() || value.empty()) continue;
mg_send_header(connection, name.c_str(), value.c_str());
}
mg_send_status( connection, response.StatusCode );
mg_send_data( connection, response.Content.c_str(), response.Content.length() );
mg_send_status(connection, response.StatusCode);
mg_send_data(connection, response.Content.c_str(), response.Content.length());
}
return processed ? MG_REQUEST_PROCESSED : MG_REQUEST_NOT_PROCESSED;
return processed ? MG_TRUE : MG_FALSE;
}
int MongooseHttpd::staticRequestHandler( struct mg_connection * connection )
int MongooseHttpd::iterateCallback(struct mg_connection * connection, mg_event event)
{
return static_cast<MongooseHttpd*>(connection->server_param)->requestHandler( connection );
if (connection->is_websocket && event == MG_POLL) {
Websocket * websocket = static_cast<Websocket*>(connection->connection_param);
if ( NULL == websocket) return MG_TRUE;
MongooseWebsocketWriter writer( connection );
websocket->update( writer );
}
return MG_TRUE; // return value is ignored
}
FGHttpd * FGHttpd::createInstance( SGPropertyNode_ptr configNode )
int MongooseHttpd::staticIterateCallback(struct mg_connection * connection, mg_event event)
{
// only create a server if a port has been configured
if( false == configNode.valid() ) return NULL;
string port = configNode->getStringValue( "options/listening-port", "" );
if( port.empty() ) return NULL;
return new MongooseHttpd( configNode );
return static_cast<MongooseHttpd*>(connection->server_param)->iterateCallback(connection, event);
}
int MongooseHttpd::staticRequestHandler(struct mg_connection * connection, mg_event event)
{
switch (event) {
case MG_REQUEST:
return
connection->is_websocket ?
static_cast<MongooseHttpd*>(connection->server_param)->websocketHandler(connection) :
static_cast<MongooseHttpd*>(connection->server_param)->requestHandler(connection);
case MG_CLOSE:
if (connection->is_websocket) static_cast<MongooseHttpd*>(connection->server_param)->closeWebsocket(connection);
return MG_TRUE;
case MG_AUTH:
return MG_TRUE; // allow anybody (for now)
default:
return MG_FALSE;
}
}
FGHttpd * FGHttpd::createInstance(SGPropertyNode_ptr configNode)
{
// only create a server if a port has been configured
if (false == configNode.valid()) return NULL;
string port = configNode->getStringValue("options/listening-port", "");
if (port.empty()) return NULL;
return new MongooseHttpd(configNode);
}
} // namespace http

View file

@ -0,0 +1,139 @@
// jsonprops.cxx -- convert properties from/to json
//
// Written by Torsten Dreyer, started April 2014.
//
// Copyright (C) 2014 Torsten Dreyer
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include "jsonprops.hxx"
namespace flightgear {
namespace http {
using std::string;
static const char * getPropertyTypeString(simgear::props::Type type)
{
switch (type) {
case simgear::props::NONE:
return "-";
case simgear::props::ALIAS:
return "alias";
case simgear::props::BOOL:
return "bool";
case simgear::props::INT:
return "int";
case simgear::props::LONG:
return "long";
case simgear::props::FLOAT:
return "float";
case simgear::props::DOUBLE:
return "double";
case simgear::props::STRING:
return "string";
case simgear::props::UNSPECIFIED:
return "unspecified";
case simgear::props::EXTENDED:
return "extended";
case simgear::props::VEC3D:
return "vec3d";
case simgear::props::VEC4D:
return "vec4d";
default:
return "?";
}
}
cJSON * JSON::toJson(SGPropertyNode_ptr n, int depth)
{
cJSON * json = cJSON_CreateObject();
cJSON_AddItemToObject(json, "path", cJSON_CreateString(n->getPath(true).c_str()));
cJSON_AddItemToObject(json, "name", cJSON_CreateString(n->getName()));
cJSON_AddItemToObject(json, "value", cJSON_CreateString(n->getStringValue()));
cJSON_AddItemToObject(json, "type", cJSON_CreateString(getPropertyTypeString(n->getType())));
cJSON_AddItemToObject(json, "index", cJSON_CreateNumber(n->getIndex()));
if (depth > 0 && n->nChildren() > 0) {
cJSON * jsonArray = cJSON_CreateArray();
for (int i = 0; i < n->nChildren(); i++)
cJSON_AddItemToArray(jsonArray, toJson(n->getChild(i), depth - 1));
cJSON_AddItemToObject(json, "children", jsonArray);
}
return json;
}
void JSON::toProp(cJSON * json, SGPropertyNode_ptr base)
{
if (NULL == json) return;
cJSON * cj = cJSON_GetObjectItem(json, "name");
if (NULL == cj) return; // a node with no name?
char * name = cj->valuestring;
if (NULL == name) return; // still no name?
//TODO: check valid name
int index = 0;
cj = cJSON_GetObjectItem(json, "index");
if (NULL != cj) index = cj->valueint;
if (index < 0) return;
SGPropertyNode_ptr n = base->getNode(name, index, true);
cJSON * children = cJSON_GetObjectItem(json, "children");
if (NULL != children) {
for (int i = 0; i < cJSON_GetArraySize(children); i++) {
toProp(cJSON_GetArrayItem(children, i), n);
}
} else {
//TODO: set correct type
/*
char * type = "";
cj = cJSON_GetObjectItem( json, "type" );
if( NULL != cj ) type = cj->valuestring;
*/
char * value = NULL;
cj = cJSON_GetObjectItem(json, "value");
if (NULL != cj) value = cj->valuestring;
if (NULL != value) n->setUnspecifiedValue(value);
}
}
string JSON::toJsonString(bool indent, SGPropertyNode_ptr n, int depth)
{
cJSON * json = toJson( n, depth );
char * jsonString = indent ? cJSON_Print( json ) : cJSON_PrintUnformatted( json );
string reply(jsonString);
free( jsonString );
cJSON_Delete( json );
return reply;
}
} // namespace http
} // namespace flightgear

View file

@ -0,0 +1,40 @@
// jsonprops.hxx -- convert properties from/to json
//
// Written by Torsten Dreyer, started April 2014.
//
// Copyright (C) 2014 Torsten Dreyer
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#ifndef JSONPROPS_HXX_
#define JSONPROPS_HXX_
#include <simgear/props/props.hxx>
#include <3rdparty/cjson/cJSON.h>
#include <string>
namespace flightgear {
namespace http {
class JSON {
public:
static cJSON * toJson(SGPropertyNode_ptr n, int depth);
static std::string toJsonString(bool indent, SGPropertyNode_ptr n, int depth);
static void toProp(cJSON * json, SGPropertyNode_ptr base);
};
} // namespace http
} // namespace flightgear
#endif /* JSONPROPS_HXX_ */