1
0
Fork 0
flightgear/src/Network/http/httpd.cxx
Torsten Dreyer 3d33aff7bc Add a http handler to access the nav database
see the browsermap for an example
2014-03-19 17:23:53 +01:00

373 lines
12 KiB
C++

// httpd.cxx -- a http daemon subsystem based on Mongoose http
//
// 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 "httpd.hxx"
#include "HTTPRequest.hxx"
#include "PropertyChangeWebsocket.hxx"
#include "ScreenshotUriHandler.hxx"
#include "PropertyUriHandler.hxx"
#include "JsonUriHandler.hxx"
#include "RunUriHandler.hxx"
#include "NavdbUriHandler.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>
using std::string;
using std::vector;
namespace flightgear {
namespace http {
const char * PROPERTY_ROOT = "/sim/http";
class MongooseHttpd: public FGHttpd {
public:
MongooseHttpd(SGPropertyNode_ptr);
~MongooseHttpd();
void init();
void bind();
void unbind();
void shutdown();
void update(double dt);
private:
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 vector<SGSharedPtr<URIHandler> > URIHandlerMap;
URIHandlerMap _uriHandlers;
PropertyChangeObserver _propertyChangeObserver;
};
MongooseHttpd::MongooseHttpd(SGPropertyNode_ptr configNode)
: _server(NULL), _configNode(configNode)
{
}
MongooseHttpd::~MongooseHttpd()
{
mg_destroy_server(&_server);
}
void MongooseHttpd::init()
{
SGPropertyNode_ptr n = _configNode->getNode("uri-handler");
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("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("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("navdb"))[0] != 0) {
SG_LOG(SG_NETWORK, SG_INFO, "httpd: adding navdb handler at " << uri);
_uriHandlers.push_back(new flightgear::http::NavdbUriHandler(uri));
}
}
_server = mg_create_server(this, MongooseHttpd::staticRequestHandler);
n = _configNode->getNode("options");
if (n.valid()) {
const string fgRoot = fgGetString("/sim/fg-root");
string docRoot = n->getStringValue("document-root", fgRoot.c_str() );
if (docRoot[0] != '/') docRoot.insert(0, "/").insert(0, fgRoot );
mg_set_option(_server, "document_root", docRoot.c_str());
mg_set_option(_server, "listening_port", n->getStringValue("listening-port", "8080"));
{
// build url rewrites relative to fg-root
string rewrites = n->getStringValue( "url-rewrites", "" );
string_list rwl = simgear::strutils::split( rewrites, "," );
rewrites.clear();
for( string_list::iterator it = rwl.begin(); it != rwl.end(); ++it ) {
string_list rw_entries = simgear::strutils::split( *it, "=" );
if( rw_entries.size() != 2 ) {
SG_LOG( SG_NETWORK,SG_WARN, "invalid entry '" << *it << "' in url-rewrites ignored." );
continue;
}
string & lhs = rw_entries[0];
string & rhs = rw_entries[1];
if( false == rewrites.empty()) rewrites.append(1,',');
rewrites.append( lhs ).append(1,'=');
if( rhs[0] == '/' ) {
rewrites.append( rhs );
} else {
rewrites.append( fgRoot ).append( 1, '/' ).append( rhs );
}
}
if( false == rewrites.empty() )
mg_set_option(_server, "url_rewrites", rewrites.c_str() );
}
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"));
}
}
void MongooseHttpd::bind()
{
}
void MongooseHttpd::unbind()
{
mg_destroy_server(&_server);
_uriHandlers.clear();
_propertyChangeObserver.clear();
}
void MongooseHttpd::shutdown()
{
}
void MongooseHttpd::update(double dt)
{
_propertyChangeObserver.check();
mg_poll_server(_server, 0);
mg_iterate_over_connections(_server, &MongooseHttpd::staticIterateCallback);
_propertyChangeObserver.uncheck();
}
class MongooseHTTPRequest: public HTTPRequest {
private:
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)
{
Method = NotNull(connection->request_method);
Uri = urlDecode(NotNull(connection->uri));
HttpVersion = NotNull(connection->http_version);
QueryString = urlDecode(NotNull(connection->query_string));
remoteAddress = NotNull(connection->remote_ip);
remotePort = connection->remote_port;
localAddress = NotNull(connection->local_ip);
localPort = connection->local_port;
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 (int i = 0; i < connection->num_headers; i++)
HeaderVariables[connection->http_headers[i].name] = connection->http_headers[i].value;
Content = NotNull(connection->content, connection->content_len);
}
string urlDecode(const string & s)
{
string r = "";
int max = s.length();
int a, b;
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])) {
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);
} else {
r += s[i];
}
}
return r;
}
};
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_INFO, "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));
response.Header["Date"] = buf;
}
bool processed = false;
for (URIHandlerMap::iterator it = _uriHandlers.begin(); it != _uriHandlers.end(); ++it) {
URIHandler * handler = *it;
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) {
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());
}
mg_send_status(connection, response.StatusCode);
mg_send_data(connection, response.Content.c_str(), response.Content.length());
}
return processed ? MG_TRUE : MG_FALSE;
}
int MongooseHttpd::iterateCallback(struct mg_connection * connection, mg_event event)
{
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
}
int MongooseHttpd::staticIterateCallback(struct mg_connection * connection, mg_event event)
{
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
} // namespace flightgear