]> git.mxchange.org Git - flightgear.git/blob - src/Network/http/httpd.cxx
724596976dc9f70663718728d5a17ddd4fade4b9
[flightgear.git] / src / Network / http / httpd.cxx
1 // httpd.cxx -- a http daemon subsystem based on Mongoose http
2 //
3 // Written by Torsten Dreyer, started April 2014.
4 //
5 // Copyright (C) 2014  Torsten Dreyer
6 //
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License as
9 // published by the Free Software Foundation; either version 2 of the
10 // License, or (at your option) any later version.
11 //
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 // General Public License for more details.
16 //
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
20
21 #include "httpd.hxx"
22 #include "HTTPRequest.hxx"
23 #include "PropertyChangeWebsocket.hxx"
24 #include "ScreenshotUriHandler.hxx"
25 #include "PropertyUriHandler.hxx"
26 #include "JsonUriHandler.hxx"
27 #include "FlightHistoryUriHandler.hxx"
28 #include "PkgUriHandler.hxx"
29 #include "RunUriHandler.hxx"
30 #include "NavdbUriHandler.hxx"
31 #include "PropertyChangeObserver.hxx"
32 #include <Main/fg_props.hxx>
33 #include <Include/version.h>
34 #include <3rdparty/mongoose/mongoose.h>
35 #include <3rdparty/cjson/cJSON.h>
36
37 #include <string>
38 #include <vector>
39
40 using std::string;
41 using std::vector;
42
43 namespace flightgear {
44 namespace http {
45
46 const char * PROPERTY_ROOT = "/sim/http";
47
48 /**
49  * A Helper class for URI Handlers
50  *
51  * This class stores a list of URI Handlers and provides a lookup
52  * method for find the handler by it's URI prefix
53  */
54 class URIHandlerMap: public vector<SGSharedPtr<URIHandler> > {
55 public:
56   /**
57    * Find a URI Handler for a given URI
58    *
59    * Look for the first handler with a uri matching the beginning
60    * of the given uri parameter.
61    *
62    * @param uri The uri to find the handler for
63    * @return a SGSharedPtr of the URIHandler or an invalid SGSharedPtr if not found
64    */
65   SGSharedPtr<URIHandler> findHandler(const std::string & uri)
66   {
67     for (iterator it = begin(); it != end(); ++it) {
68       SGSharedPtr<URIHandler> handler = *it;
69       // check if the request-uri starts with the registered uri-string
70       if (0 == uri.find(handler->getUri())) return handler;
71     }
72     return SGSharedPtr<URIHandler>();
73   }
74 };
75
76 /**
77  * A Helper class to create a HTTPRequest from a mongoose connection struct
78  */
79 class MongooseHTTPRequest: public HTTPRequest {
80 private:
81   /**
82    * Creates a std::string from a char pointer and an optionally given length
83    * If the pointer is NULL or the length is zero, return an empty string
84    * If no length is given, create a std::string from a c-string (up to the /0 terminator)
85    * If length is given, use as many chars as given in length (can exceed the /0 terminator)
86    *
87    * @param cp Points to the source of the string
88    * @param len The number of chars to copy to the new string (optional)
89    * @return a std::string containing a copy of the source
90    */
91   static inline string NotNull(const char * cp, size_t len = string::npos)
92   {
93     if ( NULL == cp || 0 == len) return string("");
94     if (string::npos == len) return string(cp);
95     return string(cp, len);
96   }
97
98 public:
99   /**
100    * Constructs a HTTPRequest from a mongoose connection struct
101    * Copies all fields into STL compatible local elements, performs urlDecode etc.
102    *
103    * @param connection the mongoose connection struct with the source data
104    */
105   MongooseHTTPRequest(struct mg_connection * connection)
106   {
107     Method = NotNull(connection->request_method);
108     Uri = urlDecode(NotNull(connection->uri));
109     HttpVersion = NotNull(connection->http_version);
110     QueryString = NotNull(connection->query_string);
111
112     remoteAddress = NotNull(connection->remote_ip);
113     remotePort = connection->remote_port;
114     localAddress = NotNull(connection->local_ip);
115     localPort = connection->local_port;
116
117     using namespace simgear::strutils;
118     string_list pairs = split(string(QueryString), "&");
119     for (string_list::iterator it = pairs.begin(); it != pairs.end(); ++it) {
120       string_list nvp = split(*it, "=");
121       if (nvp.size() != 2) continue;
122       RequestVariables.insert(make_pair(urlDecode(nvp[0]), urlDecode(nvp[1])));
123     }
124
125     for (int i = 0; i < connection->num_headers; i++)
126       HeaderVariables[connection->http_headers[i].name] = connection->http_headers[i].value;
127
128     Content = NotNull(connection->content, connection->content_len);
129
130   }
131
132   /**
133    * Decodes a URL encoded string
134    * replaces '+' by ' '
135    * replaces %nn hexdigits
136    *
137    * @param s The source string do decode
138    * @return The decoded String
139    */
140   static string urlDecode(const string & s)
141   {
142     string r = "";
143     int max = s.length();
144     int a, b;
145     for (int i = 0; i < max; i++) {
146       if (s[i] == '+') {
147         r += ' ';
148       } else if (s[i] == '%' && i + 2 < max && isxdigit(s[i + 1]) && isxdigit(s[i + 2])) {
149         i++;
150         a = isdigit(s[i]) ? s[i] - '0' : toupper(s[i]) - 'A' + 10;
151         i++;
152         b = isdigit(s[i]) ? s[i] - '0' : toupper(s[i]) - 'A' + 10;
153         r += (char) (a * 16 + b);
154       } else {
155         r += s[i];
156       }
157     }
158     return r;
159   }
160
161 };
162
163 /**
164  * A FGHttpd implementation based on mongoose httpd
165  *
166  * Mongoose API is documented here: http://cesanta.com/docs/API.shtml
167  */
168 class MongooseHttpd: public FGHttpd {
169 public:
170
171   /**
172    * Construct a MongooseHttpd object from options in a PropertyNode
173    */
174   MongooseHttpd(SGPropertyNode_ptr);
175
176   /**
177    * Cleanup et.al.
178    */
179   ~MongooseHttpd();
180
181   /**
182    * override SGSubsystem::init()
183    *
184    * Reads the configuration PropertyNode, installs URIHandlers and configures mongoose
185    */
186   void init();
187
188   /**
189    * override SGSubsystem::bind()
190    *
191    * Currently a noop
192    */
193   void bind();
194
195   /**
196    * override SGSubsystem::unbind()
197    * shutdown of mongoose, clear connections, unregister URIHandlers
198    */
199   void unbind();
200
201   /**
202    * overrride SGSubsystem::update()
203    * poll connections, check for changed properties
204    */
205   void update(double dt);
206
207   /**
208    * Returns a URIHandler for the given uri
209    *
210    * @see URIHandlerMap::findHandler( const std::string & uri )
211    */
212   SGSharedPtr<URIHandler> findHandler(const std::string & uri)
213   {
214     return _uriHandler.findHandler(uri);
215   }
216
217   Websocket * newWebsocket(const string & uri);
218
219 private:
220   int poll(struct mg_connection * connection);
221   int auth(struct mg_connection * connection);
222   int request(struct mg_connection * connection);
223   void close(struct mg_connection * connection);
224
225   static int staticRequestHandler(struct mg_connection *, mg_event event);
226
227   struct mg_server *_server;
228   SGPropertyNode_ptr _configNode;
229
230   typedef int (MongooseHttpd::*handler_t)(struct mg_connection *);
231   URIHandlerMap _uriHandler;
232
233   PropertyChangeObserver _propertyChangeObserver;
234 };
235
236 class MongooseConnection: public Connection {
237 public:
238   MongooseConnection(MongooseHttpd * httpd)
239       : _httpd(httpd)
240   {
241   }
242   virtual ~MongooseConnection();
243
244   virtual void close(struct mg_connection * connection) = 0;
245   virtual int poll(struct mg_connection * connection) = 0;
246   virtual int request(struct mg_connection * connection) = 0;
247   virtual void write(const char * data, size_t len)
248   {
249     if (_connection) mg_send_data(_connection, data, len);
250   }
251
252   static MongooseConnection * getConnection(MongooseHttpd * httpd, struct mg_connection * connection);
253
254 protected:
255   void setConnection(struct mg_connection * connection)
256   {
257     _connection = connection;
258   }
259   MongooseHttpd * _httpd;
260   struct mg_connection * _connection;
261
262 };
263
264 MongooseConnection::~MongooseConnection()
265 {
266 }
267
268 class RegularConnection: public MongooseConnection {
269 public:
270   RegularConnection(MongooseHttpd * httpd)
271       : MongooseConnection(httpd)
272   {
273   }
274   virtual ~RegularConnection()
275   {
276   }
277
278   virtual void close(struct mg_connection * connection);
279   virtual int poll(struct mg_connection * connection);
280   virtual int request(struct mg_connection * connection);
281
282 private:
283   SGSharedPtr<URIHandler> _handler;
284 };
285
286 class WebsocketConnection: public MongooseConnection {
287 public:
288   WebsocketConnection(MongooseHttpd * httpd)
289       : MongooseConnection(httpd), _websocket(NULL)
290   {
291   }
292   virtual ~WebsocketConnection()
293   {
294     delete _websocket;
295   }
296   virtual void close(struct mg_connection * connection);
297   virtual int poll(struct mg_connection * connection);
298   virtual int request(struct mg_connection * connection);
299
300 private:
301   class MongooseWebsocketWriter: public WebsocketWriter {
302   public:
303     MongooseWebsocketWriter(struct mg_connection * connection)
304         : _connection(connection)
305     {
306     }
307
308     virtual int writeToWebsocket(int opcode, const char * data, size_t len)
309     {
310       return mg_websocket_write(_connection, opcode, data, len);
311     }
312   private:
313     struct mg_connection * _connection;
314   };
315   Websocket * _websocket;
316 };
317
318 MongooseConnection * MongooseConnection::getConnection(MongooseHttpd * httpd, struct mg_connection * connection)
319 {
320   if (connection->connection_param) return static_cast<MongooseConnection*>(connection->connection_param);
321   MongooseConnection * c;
322   if (connection->is_websocket) c = new WebsocketConnection(httpd);
323   else c = new RegularConnection(httpd);
324
325   connection->connection_param = c;
326   return c;
327 }
328
329 int RegularConnection::request(struct mg_connection * connection)
330 {
331   setConnection(connection);
332   MongooseHTTPRequest request(connection);
333   SG_LOG(SG_NETWORK, SG_INFO, "RegularConnection::request for " << request.Uri);
334
335   // find a handler for the uri and remember it for possible polls on this connection
336   _handler = _httpd->findHandler(request.Uri);
337   if (false == _handler.valid()) {
338     // uri not registered - pass false to indicate we have not processed the request
339     return MG_FALSE;
340   }
341
342   // We handle this URI, prepare the response
343   HTTPResponse response;
344   response.Header["Server"] = "FlightGear/" FLIGHTGEAR_VERSION " Mongoose/" MONGOOSE_VERSION;
345   response.Header["Connection"] = "keep-alive";
346   response.Header["Cache-Control"] = "no-cache";
347   {
348     char buf[64];
349     time_t now = time(NULL);
350     strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&now));
351     response.Header["Date"] = buf;
352   }
353
354   // hand the request over to the handler, returns true if request is finished, 
355   // false the handler wants to get polled again (calling handlePoll() next time)
356   bool done = _handler->handleRequest(request, response, this);
357   // fill in the response header
358   mg_send_status(connection, response.StatusCode);
359   for (HTTPResponse::Header_t::const_iterator it = response.Header.begin(); it != response.Header.end(); ++it) {
360     const string name = it->first;
361     const string value = it->second;
362     if (name.empty() || value.empty()) continue;
363     mg_send_header(connection, name.c_str(), value.c_str());
364   }
365   if (done || false == response.Content.empty()) {
366     SG_LOG(SG_NETWORK, SG_INFO,
367         "RegularConnection::request() responding " << response.Content.length() << " Bytes, done=" << done);
368     mg_send_data(connection, response.Content.c_str(), response.Content.length());
369   }
370   return done ? MG_TRUE : MG_MORE;
371 }
372
373 int RegularConnection::poll(struct mg_connection * connection)
374 {
375   setConnection(connection);
376   if (false == _handler.valid()) return MG_FALSE;
377   // only return MG_TRUE if we handle this request
378   return _handler->poll(this) ? MG_TRUE : MG_MORE;
379 }
380
381 void RegularConnection::close(struct mg_connection * connection)
382 {
383   setConnection(connection);
384   // nothing to close
385 }
386
387 void WebsocketConnection::close(struct mg_connection * connection)
388 {
389   setConnection(connection);
390   if ( NULL != _websocket) _websocket->close();
391   delete _websocket;
392   _websocket = NULL;
393 }
394
395 int WebsocketConnection::poll(struct mg_connection * connection)
396 {
397   setConnection(connection);
398   // we get polled before the first request came in but we know 
399   // nothing about how to handle that before we know the URI.
400   // so simply ignore that poll
401   if ( NULL != _websocket) {
402     MongooseWebsocketWriter writer(connection);
403     _websocket->poll(writer);
404   }
405   return MG_MORE;
406 }
407
408 int WebsocketConnection::request(struct mg_connection * connection)
409 {
410   setConnection(connection);
411   MongooseHTTPRequest request(connection);
412   SG_LOG(SG_NETWORK, SG_INFO, "WebsocketConnection::request for " << request.Uri);
413
414   if ( NULL == _websocket) _websocket = _httpd->newWebsocket(request.Uri);
415   if ( NULL == _websocket) {
416     SG_LOG(SG_NETWORK, SG_WARN, "httpd: unhandled websocket uri: " << request.Uri);
417     return MG_TRUE; // close connection - good bye
418   }
419
420   MongooseWebsocketWriter writer(connection);
421   _websocket->handleRequest(request, writer);
422   return MG_MORE;
423 }
424
425 MongooseHttpd::MongooseHttpd(SGPropertyNode_ptr configNode)
426     : _server(NULL), _configNode(configNode)
427 {
428 }
429
430 MongooseHttpd::~MongooseHttpd()
431 {
432   mg_destroy_server(&_server);
433 }
434
435 void MongooseHttpd::init()
436 {
437   SGPropertyNode_ptr n = _configNode->getNode("uri-handler");
438   if (n.valid()) {
439     const char * uri;
440
441     if ((uri = n->getStringValue("screenshot"))[0] != 0) {
442       SG_LOG(SG_NETWORK, SG_INFO, "httpd: adding screenshot uri handler at " << uri);
443       _uriHandler.push_back(new flightgear::http::ScreenshotUriHandler(uri));
444     }
445
446     if ((uri = n->getStringValue("property"))[0] != 0) {
447       SG_LOG(SG_NETWORK, SG_INFO, "httpd: adding property uri handler at " << uri);
448       _uriHandler.push_back(new flightgear::http::PropertyUriHandler(uri));
449     }
450
451     if ((uri = n->getStringValue("json"))[0] != 0) {
452       SG_LOG(SG_NETWORK, SG_INFO, "httpd: adding json uri handler at " << uri);
453       _uriHandler.push_back(new flightgear::http::JsonUriHandler(uri));
454     }
455
456     if ((uri = n->getStringValue("pkg"))[0] != 0) {
457       SG_LOG(SG_NETWORK, SG_INFO, "httpd: adding pkg uri handler at " << uri);
458       _uriHandler.push_back(new flightgear::http::PkgUriHandler(uri));
459     }
460
461     if ((uri = n->getStringValue("flighthistory"))[0] != 0) {
462       SG_LOG(SG_NETWORK, SG_INFO, "httpd: adding flighthistory uri handler at " << uri);
463       _uriHandler.push_back(new flightgear::http::FlightHistoryUriHandler(uri));
464     }
465
466     if ((uri = n->getStringValue("run"))[0] != 0) {
467       SG_LOG(SG_NETWORK, SG_INFO, "httpd: adding run uri handler at " << uri);
468       _uriHandler.push_back(new flightgear::http::RunUriHandler(uri));
469     }
470
471     if ((uri = n->getStringValue("navdb"))[0] != 0) {
472       SG_LOG(SG_NETWORK, SG_INFO, "httpd: adding navdb uri handler at " << uri);
473       _uriHandler.push_back(new flightgear::http::NavdbUriHandler(uri));
474     }
475   }
476
477   _server = mg_create_server(this, MongooseHttpd::staticRequestHandler);
478
479   n = _configNode->getNode("options");
480   if (n.valid()) {
481
482     const string fgRoot = fgGetString("/sim/fg-root");
483     string docRoot = n->getStringValue("document-root", fgRoot.c_str());
484     if (docRoot[0] != '/') docRoot.insert(0, "/").insert(0, fgRoot);
485
486     mg_set_option(_server, "document_root", docRoot.c_str());
487
488     mg_set_option(_server, "listening_port", n->getStringValue("listening-port", "8080"));
489     {
490       // build url rewrites relative to fg-root
491       string rewrites = n->getStringValue("url-rewrites", "");
492       string_list rwl = simgear::strutils::split(rewrites, ",");
493       rwl.push_back(string("/aircraft-dir/=") + fgGetString("/sim/aircraft-dir") + "/" );
494       rewrites.clear();
495       for (string_list::iterator it = rwl.begin(); it != rwl.end(); ++it) {
496         string_list rw_entries = simgear::strutils::split(*it, "=");
497         if (rw_entries.size() != 2) {
498           SG_LOG(SG_NETWORK, SG_WARN, "invalid entry '" << *it << "' in url-rewrites ignored.");
499           continue;
500         }
501         string & lhs = rw_entries[0];
502         string & rhs = rw_entries[1];
503         if (false == rewrites.empty()) rewrites.append(1, ',');
504         rewrites.append(lhs).append(1, '=');
505         SGPath targetPath(rhs);
506         if (targetPath.isAbsolute() ) {
507           rewrites.append(rhs);
508         } else {
509           // don't use targetPath here because SGPath strips trailing '/'
510           rewrites.append(fgRoot).append(1, '/').append(rhs);
511         }
512       }
513       if (false == rewrites.empty()) mg_set_option(_server, "url_rewrites", rewrites.c_str());
514     }
515     mg_set_option(_server, "enable_directory_listing", n->getStringValue("enable-directory-listing", "yes"));
516     mg_set_option(_server, "idle_timeout_ms", n->getStringValue("idle-timeout-ms", "30000"));
517     mg_set_option(_server, "index_files", n->getStringValue("index-files", "index.html"));
518     mg_set_option(_server, "extra_mime_types", n->getStringValue("extra-mime-types", ""));
519     mg_set_option(_server, "access_log_file", n->getStringValue("access-log-file", ""));
520
521     if( sglog().would_log(SG_NETWORK,SG_INFO) ) {
522       SG_LOG(SG_NETWORK,SG_INFO,"starting mongoose with these options: ");
523       const char ** optionNames = mg_get_valid_option_names();
524       for( int i = 0; optionNames[i] != NULL; i+= 2 ) {
525         SG_LOG(SG_NETWORK,SG_INFO, "  > " << optionNames[i] << ": '" << mg_get_option(_server, optionNames[i]) << "'" );
526       }
527       SG_LOG(SG_NETWORK,SG_INFO,"end of mongoose options.");
528     }
529
530   }
531
532   _configNode->setBoolValue("running",true);
533
534 }
535
536 void MongooseHttpd::bind()
537 {
538 }
539
540 void MongooseHttpd::unbind()
541 {
542   _configNode->setBoolValue("running",false);
543   mg_destroy_server(&_server);
544   _uriHandler.clear();
545   _propertyChangeObserver.clear();
546 }
547
548 void MongooseHttpd::update(double dt)
549 {
550   _propertyChangeObserver.check();
551   mg_poll_server(_server, 0);
552   _propertyChangeObserver.uncheck();
553 }
554
555 int MongooseHttpd::poll(struct mg_connection * connection)
556 {
557   if ( NULL == connection->connection_param) return MG_FALSE; // connection not yet set up - ignore poll
558
559   return MongooseConnection::getConnection(this, connection)->poll(connection);
560 }
561
562 int MongooseHttpd::auth(struct mg_connection * connection)
563 {
564   // auth preceeds request for websockets and regular connections,
565   // and eventually the websocket has been already set up by mongoose
566   // use this to choose the connection type
567   MongooseConnection::getConnection(this, connection);
568   //return MongooseConnection::getConnection(this,connection)->auth(connection);
569   return MG_TRUE; // unrestricted access for now
570 }
571
572 int MongooseHttpd::request(struct mg_connection * connection)
573 {
574   return MongooseConnection::getConnection(this, connection)->request(connection);
575 }
576
577 void MongooseHttpd::close(struct mg_connection * connection)
578 {
579   MongooseConnection * c = MongooseConnection::getConnection(this, connection);
580   c->close(connection);
581   delete c;
582 }
583 Websocket * MongooseHttpd::newWebsocket(const string & uri)
584 {
585   if (uri.find("/PropertyListener") == 0) {
586     SG_LOG(SG_NETWORK, SG_INFO, "new PropertyChangeWebsocket for: " << uri);
587     return new PropertyChangeWebsocket(&_propertyChangeObserver);
588   }
589   return NULL;
590 }
591
592 int MongooseHttpd::staticRequestHandler(struct mg_connection * connection, mg_event event)
593 {
594   switch (event) {
595     case MG_POLL:        // MG_TRUE: finished sending data, MG_MORE, call again
596       return static_cast<MongooseHttpd*>(connection->server_param)->poll(connection);
597
598     case MG_AUTH:        // If callback returns MG_FALSE, authentication fails
599       return static_cast<MongooseHttpd*>(connection->server_param)->auth(connection);
600
601     case MG_REQUEST:     // If callback returns MG_FALSE, Mongoose continues with req
602       return static_cast<MongooseHttpd*>(connection->server_param)->request(connection);
603
604     case MG_CLOSE:       // Connection is closed, callback return value is ignored
605       static_cast<MongooseHttpd*>(connection->server_param)->close(connection);
606       return MG_TRUE;
607
608     case MG_HTTP_ERROR:  // If callback returns MG_FALSE, Mongoose continues with err 
609       return MG_FALSE;   // we don't handle errors - let mongoose do the work
610
611       // client services not used/implemented. Signal 'close connection' to be sure
612     case MG_CONNECT:     // If callback returns MG_FALSE, connect fails
613     case MG_REPLY:       // If callback returns MG_FALSE, Mongoose closes connection
614       return MG_FALSE;
615
616     default:
617       return MG_FALSE; // keep compiler happy..
618   }
619 }
620
621 FGHttpd * FGHttpd::createInstance(SGPropertyNode_ptr configNode)
622 {
623 // only create a server if a port has been configured
624   if (false == configNode.valid()) return NULL;
625   string port = configNode->getStringValue("options/listening-port", "");
626   if (port.empty()) return NULL;
627   return new MongooseHttpd(configNode);
628 }
629
630 } // namespace http
631 } // namespace flightgear