From cc6178a9f350cabd601befb852b6d06012691116 Mon Sep 17 00:00:00 2001 From: Torsten Dreyer Date: Mon, 23 Feb 2015 16:33:20 +0100 Subject: [PATCH] Expose FlightHistory as a http service usage: GET http://localhost:8080/flighthistory/track.json retrieves track as JSON data { flightHistory: [ { latitude: (number), longitude: (number), altitude: (number) } ] } GET http://localhost:8080/flighthistory/track.kml retrieves track as KML path optional request parameter: LineColor=(hex encoded rgba color) LineWidth=(line width in pixel) PolyColor=(hex encoded rgba color) interval=(number of seconds to auto-refresh) --- src/Network/http/CMakeLists.txt | 4 + src/Network/http/FlightHistoryUriHandler.cxx | 275 +++++++++++++++++++ src/Network/http/FlightHistoryUriHandler.hxx | 40 +++ src/Network/http/PropertyUriHandler.cxx | 79 +----- src/Network/http/SimpleDOM.cxx | 54 ++++ src/Network/http/SimpleDOM.hxx | 64 +++++ src/Network/http/httpd.cxx | 6 + 7 files changed, 444 insertions(+), 78 deletions(-) create mode 100644 src/Network/http/FlightHistoryUriHandler.cxx create mode 100644 src/Network/http/FlightHistoryUriHandler.hxx create mode 100644 src/Network/http/SimpleDOM.cxx create mode 100644 src/Network/http/SimpleDOM.hxx diff --git a/src/Network/http/CMakeLists.txt b/src/Network/http/CMakeLists.txt index 44078faf1..9e913d0cc 100644 --- a/src/Network/http/CMakeLists.txt +++ b/src/Network/http/CMakeLists.txt @@ -5,12 +5,14 @@ set(SOURCES ScreenshotUriHandler.cxx PropertyUriHandler.cxx JsonUriHandler.cxx + FlightHistoryUriHandler.cxx PkgUriHandler.cxx RunUriHandler.cxx NavdbUriHandler.cxx PropertyChangeWebsocket.cxx PropertyChangeObserver.cxx jsonprops.cxx + SimpleDOM.cxx ) set(HEADERS @@ -19,6 +21,7 @@ set(HEADERS ScreenshotUriHandler.hxx PropertyUriHandler.hxx JsonUriHandler.hxx + FlightHistoryUriHandler.hxx PkgUriHandler.hxx RunUriHandler.hxx NavdbUriHandler.hxx @@ -27,6 +30,7 @@ set(HEADERS PropertyChangeWebsocket.hxx PropertyChangeObserver.hxx jsonprops.hxx + SimpleDOM.hxx ) flightgear_component(Http "${SOURCES}" "${HEADERS}") diff --git a/src/Network/http/FlightHistoryUriHandler.cxx b/src/Network/http/FlightHistoryUriHandler.cxx new file mode 100644 index 000000000..b92b90ba9 --- /dev/null +++ b/src/Network/http/FlightHistoryUriHandler.cxx @@ -0,0 +1,275 @@ +// FlightHistoryUriHandler.cxx -- FlightHistory service +// +// Written by Torsten Dreyer, started February 2015. +// +// Copyright (C) 2015 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 "FlightHistoryUriHandler.hxx" +#include "SimpleDOM.hxx" +#include <3rdparty/cjson/cJSON.h> + +#include +#include
+#include + +using std::string; +using std::stringstream; + +namespace flightgear { +namespace http { + +static cJSON * GeodToJson(const SGGeod & geod) { + cJSON * json = cJSON_CreateObject(); + + cJSON_AddItemToObject(json, "latitude", + cJSON_CreateNumber(geod.getLatitudeDeg())); + cJSON_AddItemToObject(json, "longitude", + cJSON_CreateNumber(geod.getLongitudeDeg())); + cJSON_AddItemToObject(json, "altitude", + cJSON_CreateNumber(geod.getElevationFt())); + + return json; +} + +static string FlightHistoryToJson(const SGGeodVec & history) { + cJSON * json = cJSON_CreateObject(); + + cJSON * jsonArray = cJSON_CreateArray(); + for (SGGeodVec::const_iterator it = history.begin(); it != history.end(); + ++it) { + cJSON_AddItemToArray(jsonArray, GeodToJson(*it)); + } + cJSON_AddItemToObject(json, "flightHistory", jsonArray); + + char * jsonString = cJSON_PrintUnformatted(json); + string reply(jsonString); + free(jsonString); + cJSON_Delete(json); + return reply; +} + +static string AutoUpdateResponse(const HTTPRequest & request, + const string & base, const string & interval) { + + string url = "http://"; + url.append(request.HeaderVariables.get("Host")).append(base); + + std::string reply(""); + DOMNode * kml = new DOMNode("kml"); + kml->setAttribute("xmlns", "http://www.opengis.net/kml/2.2"); + + DOMNode * document = kml->addChild(new DOMNode("Document")); + + DOMNode * networkLink = document->addChild(new DOMNode("NetworkLink")); + DOMNode * link = networkLink->addChild(new DOMNode("Link")); + link->addChild(new DOMNode("href"))->addChild(new DOMTextElement(url)); + link->addChild(new DOMNode("refreshMode"))->addChild( + new DOMTextElement("onInterval")); + link->addChild(new DOMNode("refreshInterval"))->addChild( + new DOMTextElement(interval.empty() ? "10" : interval)); + + reply.append(kml->render()); + delete kml; + return reply; +} + +static string FlightHistoryToKml(const SGGeodVec & history, + const HTTPRequest & request) { + string interval = request.RequestVariables.get("interval"); + if (false == interval.empty()) { + return AutoUpdateResponse(request, "/flighthistory/track.kml", interval); + } + + std::string reply(""); + DOMNode * kml = new DOMNode("kml"); + kml->setAttribute("xmlns", "http://www.opengis.net/kml/2.2"); + + DOMNode * document = kml->addChild(new DOMNode("Document")); + + document->addChild(new DOMNode("name"))->addChild( + new DOMTextElement("FlightGear")); + document->addChild(new DOMNode("description"))->addChild( + new DOMTextElement("FlightGear Flight History")); + + DOMNode * style = document->addChild(new DOMNode("Style"))->setAttribute( + "id", "flight-history"); + DOMNode * lineStyle = style->addChild(new DOMNode("LineStyle")); + + string lineColor = request.RequestVariables.get("LineColor"); + string lineWidth = request.RequestVariables.get("LineWidth"); + string polyColor = request.RequestVariables.get("PolyColor"); + + lineStyle->addChild(new DOMNode("color"))->addChild( + new DOMTextElement(lineColor.empty() ? "427ebfff" : lineColor)); + lineStyle->addChild(new DOMNode("width"))->addChild( + new DOMTextElement(lineWidth.empty() ? "4" : lineWidth)); + + lineStyle = style->addChild(new DOMNode("PolyStyle")); + lineStyle->addChild(new DOMNode("color"))->addChild( + new DOMTextElement(polyColor.empty() ? "fbfc4600" : polyColor)); + + DOMNode * placemark = document->addChild(new DOMNode("Placemark")); + placemark->addChild(new DOMNode("name"))->addChild( + new DOMTextElement("Flight Path")); + placemark->addChild(new DOMNode("styleUrl"))->addChild( + new DOMTextElement("#flight-history")); + + DOMNode * linestring = placemark->addChild(new DOMNode("LineString")); + linestring->addChild(new DOMNode("extrude"))->addChild( + new DOMTextElement("1")); + linestring->addChild(new DOMNode("tessalate"))->addChild( + new DOMTextElement("1")); + linestring->addChild(new DOMNode("altitudeMode"))->addChild( + new DOMTextElement("absolute")); + + stringstream ss; + + for (SGGeodVec::const_iterator it = history.begin(); it != history.end(); + ++it) { + ss << (*it).getLongitudeDeg() << "," << (*it).getLatitudeDeg() << "," + << it->getElevationM() << " "; + } + + linestring->addChild(new DOMNode("coordinates"))->addChild( + new DOMTextElement(ss.str())); + + reply.append(kml->render()); + delete kml; + return reply; +} + +static bool GetJsonDouble(cJSON * json, const char * item, double & out) { + cJSON * cj = cJSON_GetObjectItem(json, item); + if (NULL == cj) + return false; + + if (cj->type != cJSON_Number) + return false; + + out = cj->valuedouble; + + return true; +} + +static bool GetJsonBool(cJSON * json, const char * item, bool & out) { + cJSON * cj = cJSON_GetObjectItem(json, item); + if (NULL == cj) + return false; + + if (cj->type == cJSON_True) { + out = true; + return true; + } + if (cj->type == cJSON_False) { + out = true; + return true; + + } + return false; +} + +bool FlightHistoryUriHandler::handleRequest(const HTTPRequest & request, + HTTPResponse & response, Connection * connection) { + response.Header["Access-Control-Allow-Origin"] = "*"; + response.Header["Access-Control-Allow-Methods"] = "OPTIONS, GET, POST"; + response.Header["Access-Control-Allow-Headers"] = + "Origin, Accept, Content-Type, X-Requested-With, X-CSRF-Token"; + + if (request.Method == "OPTIONS") { + return true; // OPTIONS only needs the headers + } + + FGFlightHistory* history = + static_cast(globals->get_subsystem("history")); + + double minEdgeLengthM = 50; + string requestPath = request.Uri.substr(getUri().length()); + + if (request.Method == "GET") { + } else if (request.Method == "POST") { + /* + * { + * sampleIntervalSec: (number), + * maxMemoryUseBytes: (number), + * clearOnTakeoff: (bool), + * enabled: (bool), + * } + */ + cJSON * json = cJSON_Parse(request.Content.c_str()); + if ( NULL != json) { + double d = .0; + bool b = false; + bool doReinit = false; + if (GetJsonDouble(json, "sampleIntervalSec", d)) { + fgSetDouble("/sim/history/sample-interval-sec", d); + doReinit = true; + } + if (GetJsonDouble(json, "maxMemoryUseBytes", d)) { + fgSetDouble("/sim/history/max-memory-use-bytes", d); + doReinit = true; + } + + if (GetJsonBool(json, "clearOnTakeoff", b)) { + fgSetBool("/sim/history/clear-on-takeoff", b); + doReinit = true; + } + if (GetJsonBool(json, "enabled", b)) { + fgSetBool("/sim/history/enabled", b); + } + + if (doReinit) { + history->reinit(); + } + + cJSON_Delete(json); + } + + response.Content = "{}"; + return true; + + } else { + SG_LOG(SG_NETWORK, SG_INFO, + "PkgUriHandler: invalid request method '" << request.Method << "'"); + response.Header["Allow"] = "OPTIONS, GET, POST"; + response.StatusCode = 405; + response.Content = "{}"; + return true; + } + + if (requestPath == "track.kml") { + response.Header["Content-Type"] = + "application/vnd.google-earth.kml+xml; charset=UTF-8"; + + response.Content = FlightHistoryToKml( + history->pathForHistory(minEdgeLengthM), request); + + } else if (requestPath == "track.json") { + response.Header["Content-Type"] = "application/json; charset=UTF-8"; + response.Content = FlightHistoryToJson( + history->pathForHistory(minEdgeLengthM)); + + } else { + response.StatusCode = 404; + response.Content = "{}"; + } + + return true; +} + +} // namespace http +} // namespace flightgear + diff --git a/src/Network/http/FlightHistoryUriHandler.hxx b/src/Network/http/FlightHistoryUriHandler.hxx new file mode 100644 index 000000000..9c17829a4 --- /dev/null +++ b/src/Network/http/FlightHistoryUriHandler.hxx @@ -0,0 +1,40 @@ +// FlightHistoryUriHandler.hxx -- FlightHistory service +// +// Written by Torsten Dreyer, started February 2015. +// +// Copyright (C) 2015 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 __FG_FLIGHTHISTORY_URI_HANDLER_HXX +#define __FG_FLIGHTHISTORY_URI_HANDLER_HXX + +#include "urihandler.hxx" +#include + +namespace flightgear { +namespace http { + +class FlightHistoryUriHandler : public URIHandler { +public: + FlightHistoryUriHandler( const char * uri = "/flighthistory/" ) : URIHandler( uri ) {} + virtual bool handleRequest( const HTTPRequest & request, HTTPResponse & response, Connection * connection ); +private: +}; + +} // namespace http +} // namespace flightgear + +#endif //#define __FG_FLIGHTHISTORY_URI_HANDLER_HXX diff --git a/src/Network/http/PropertyUriHandler.cxx b/src/Network/http/PropertyUriHandler.cxx index 3fe9c33b9..01b28c0f2 100644 --- a/src/Network/http/PropertyUriHandler.cxx +++ b/src/Network/http/PropertyUriHandler.cxx @@ -20,6 +20,7 @@ #include "PropertyUriHandler.hxx" +#include "SimpleDOM.hxx" #include
#include #include @@ -63,84 +64,6 @@ static inline std::string htmlSpecialChars( const std::string & s ) return reply; } -class DOMElement { -public: - virtual ~DOMElement() {} - virtual std::string render() const = 0; -}; - -class DOMTextElement : public DOMElement { -public: - DOMTextElement( const string & text ) : _text(text) {} - virtual std::string render() const { return _text; } - -private: - string _text; -}; - - -class DOMNode : public DOMElement { -public: - DOMNode( const string & name ) : _name(name) {} - virtual ~DOMNode(); - - virtual std::string render() const; - virtual DOMNode * addChild( DOMElement * child ); - virtual DOMNode * setAttribute( const string & name, const string & value ); -protected: - string _name; - typedef vector Children_t; - typedef map Attributes_t; - Children_t _children; - Attributes_t _attributes; -}; - -DOMNode::~DOMNode() -{ - for( Children_t::const_iterator it = _children.begin(); it != _children.end(); ++it ) - delete *it; -} - -string DOMNode::render() const -{ - string reply; - reply.append( "<" ).append( _name ); - for( Attributes_t::const_iterator it = _attributes.begin(); it != _attributes.end(); ++it ) { - reply.append( " " ); - reply.append( it->first ); - reply.append( "=\"" ); - reply.append( it->second ); - reply.append( "\"" ); - } - - if( _children.empty() ) { - reply.append( " />\r" ); - return reply; - } else { - reply.append( ">" ); - } - - for( Children_t::const_iterator it = _children.begin(); it != _children.end(); ++it ) { - reply.append( (*it)->render() ); - } - - reply.append( "\r" ); - - return reply; -} - -DOMNode * DOMNode::addChild( DOMElement * child ) -{ - _children.push_back( child ); - return dynamic_cast(child); -} - -DOMNode * DOMNode::setAttribute( const string & name, const string & value ) -{ - _attributes[name] = value; - return this; -} - class SortedChilds : public simgear::PropertyList { public: SortedChilds( SGPropertyNode_ptr node ) { diff --git a/src/Network/http/SimpleDOM.cxx b/src/Network/http/SimpleDOM.cxx new file mode 100644 index 000000000..bee2e29e5 --- /dev/null +++ b/src/Network/http/SimpleDOM.cxx @@ -0,0 +1,54 @@ +#include "SimpleDOM.hxx" +using std::string; + +namespace flightgear { +namespace http { + +DOMNode::~DOMNode() +{ + for( Children_t::const_iterator it = _children.begin(); it != _children.end(); ++it ) + delete *it; +} + +string DOMNode::render() const +{ + string reply; + reply.append( "<" ).append( _name ); + for( Attributes_t::const_iterator it = _attributes.begin(); it != _attributes.end(); ++it ) { + reply.append( " " ); + reply.append( it->first ); + reply.append( "=\"" ); + reply.append( it->second ); + reply.append( "\"" ); + } + + if( _children.empty() ) { + reply.append( " />\r" ); + return reply; + } else { + reply.append( ">" ); + } + + for( Children_t::const_iterator it = _children.begin(); it != _children.end(); ++it ) { + reply.append( (*it)->render() ); + } + + reply.append( "\r" ); + + return reply; +} + +DOMNode * DOMNode::addChild( DOMElement * child ) +{ + _children.push_back( child ); + return dynamic_cast(child); +} + +DOMNode * DOMNode::setAttribute( const string & name, const string & value ) +{ + _attributes[name] = value; + return this; +} + +} +} diff --git a/src/Network/http/SimpleDOM.hxx b/src/Network/http/SimpleDOM.hxx new file mode 100644 index 000000000..fc5126aa0 --- /dev/null +++ b/src/Network/http/SimpleDOM.hxx @@ -0,0 +1,64 @@ +// SimpleDOM.hxx -- poor man's DOM +// +// Written by Torsten Dreyer +// +// 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 __FG_SIMPLE_DOM_HXX +#define __FG_SIMPLE_DOM_HXX + +#include +#include +#include + +namespace flightgear { +namespace http { + +class DOMElement { +public: + virtual ~DOMElement() {} + virtual std::string render() const = 0; +}; + +class DOMTextElement : public DOMElement { +public: + DOMTextElement( const std::string & text ) : _text(text) {} + virtual std::string render() const { return _text; } + +private: + std::string _text; +}; + +class DOMNode : public DOMElement { +public: + DOMNode( const std::string & name ) : _name(name) {} + virtual ~DOMNode(); + + virtual std::string render() const; + virtual DOMNode * addChild( DOMElement * child ); + virtual DOMNode * setAttribute( const std::string & name, const std::string & value ); +protected: + std::string _name; + typedef std::vector Children_t; + typedef std::map Attributes_t; + Children_t _children; + Attributes_t _attributes; +}; + +} +} +#endif // __FG_SIMPLE_DOM_HXX diff --git a/src/Network/http/httpd.cxx b/src/Network/http/httpd.cxx index 829acf69a..3f38be832 100644 --- a/src/Network/http/httpd.cxx +++ b/src/Network/http/httpd.cxx @@ -24,6 +24,7 @@ #include "ScreenshotUriHandler.hxx" #include "PropertyUriHandler.hxx" #include "JsonUriHandler.hxx" +#include "FlightHistoryUriHandler.hxx" #include "PkgUriHandler.hxx" #include "RunUriHandler.hxx" #include "NavdbUriHandler.hxx" @@ -459,6 +460,11 @@ void MongooseHttpd::init() } //#endif + if ((uri = n->getStringValue("flighthistory"))[0] != 0) { + SG_LOG(SG_NETWORK, SG_INFO, "httpd: adding flighthistory uri handler at " << uri); + _uriHandler.push_back(new flightgear::http::FlightHistoryUriHandler(uri)); + } + if ((uri = n->getStringValue("run"))[0] != 0) { SG_LOG(SG_NETWORK, SG_INFO, "httpd: adding run uri handler at " << uri); _uriHandler.push_back(new flightgear::http::RunUriHandler(uri));