]> git.mxchange.org Git - flightgear.git/commitdiff
Update of the httpd implementation
authorTorsten Dreyer <torsten@t3r.de>
Mon, 31 Mar 2014 20:31:30 +0000 (22:31 +0200)
committerTorsten Dreyer <torsten@t3r.de>
Thu, 22 May 2014 08:28:54 +0000 (10:28 +0200)
- Add a screenshot uri handler
- Enable persistent http connections
- Simple CORS implementatation for the
  JSON property uri handler

many changes for the httpd

16 files changed:
src/Network/http/JsonUriHandler.cxx
src/Network/http/JsonUriHandler.hxx
src/Network/http/NavdbUriHandler.cxx
src/Network/http/NavdbUriHandler.hxx
src/Network/http/PropertyChangeObserver.cxx
src/Network/http/PropertyChangeWebsocket.cxx
src/Network/http/PropertyChangeWebsocket.hxx
src/Network/http/PropertyUriHandler.cxx
src/Network/http/PropertyUriHandler.hxx
src/Network/http/RunUriHandler.cxx
src/Network/http/RunUriHandler.hxx
src/Network/http/ScreenshotUriHandler.cxx
src/Network/http/ScreenshotUriHandler.hxx
src/Network/http/Websocket.hxx
src/Network/http/httpd.cxx
src/Network/http/urihandler.hxx

index 6d7b08f9d2e8e4787e926d9c1b12de6d8ec03048..1a29f319f2b8e3c5083e9a318aa55d94e15ce58a 100644 (file)
@@ -29,9 +29,23 @@ using std::string;
 namespace flightgear {
 namespace http {
 
-bool JsonUriHandler::handleGetRequest( const HTTPRequest & request, HTTPResponse & response )
+bool JsonUriHandler::handleRequest( const HTTPRequest & request, HTTPResponse & response, Connection * connection )
 {
   response.Header["Content-Type"] = "application/json; charset=UTF-8";
+  response.Header["Access-Control-Allow-Origin"] = "*";
+  response.Header["Access-Control-Allow-Methods"] = "OPTIONS, GET";
+  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
+  }
+
+  if( request.Method != "GET" ){
+    response.Header["Allow"] = "OPTIONS, GET";
+    response.StatusCode = 405;
+    response.Content = "{}";
+    return true; 
+  }
 
   string propertyPath = request.Uri;
 
@@ -58,7 +72,7 @@ bool JsonUriHandler::handleGetRequest( const HTTPRequest & request, HTTPResponse
 
   SGPropertyNode_ptr node = fgGetNode( string("/") + propertyPath );
   if( false == node.valid() ) {
-    response.StatusCode = 400;
+    response.StatusCode = 404;
     response.Content = "{}";
     SG_LOG(SG_NETWORK,SG_WARN, "Node not found: '" << propertyPath << "'");
     return true;
index 463c5f2273b4697ce4831cbc4d516ef59815f5cd..a0c672f91ec7bf75826e7bd22e6ec6111db1bf33 100644 (file)
@@ -29,7 +29,7 @@ namespace http {
 class JsonUriHandler : public URIHandler {
 public:
   JsonUriHandler( const char * uri = "/json/" ) : URIHandler( uri  ) {}
-  virtual bool handleGetRequest( const HTTPRequest & request, HTTPResponse & response );
+  virtual bool handleRequest( const HTTPRequest & request, HTTPResponse & response, Connection * connection );
 };
 
 } // namespace http
index 2b568dc1c3895f5c996174e3d28a3e31d68709b8..5e73d7affe120480417557d64cd2fce9cc455d74 100644 (file)
@@ -218,10 +218,24 @@ static cJSON * createFeatureFor(FGPositionedRef positioned)
   return feature;
 }
 
-bool NavdbUriHandler::handleRequest(const HTTPRequest & request, HTTPResponse & response)
+bool NavdbUriHandler::handleRequest(const HTTPRequest & request, HTTPResponse & response, Connection * connection)
 {
 
   response.Header["Content-Type"] = "application/json; charset=UTF-8";
+  response.Header["Access-Control-Allow-Origin"] = "*";
+  response.Header["Access-Control-Allow-Methods"] = "OPTIONS, GET";
+  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
+  }
+
+  if( request.Method != "GET" ){
+    response.Header["Allow"] = "OPTIONS, GET";
+    response.StatusCode = 405;
+    response.Content = "{}";
+    return true; 
+  }
 
   bool indent = request.RequestVariables.get("i") == "y";
 
index 949ed52d0940e4b002bc6de635f0161d002966e7..40f6f71cea57ab231d8d9e35d5756bbe0bda03a6 100644 (file)
@@ -29,7 +29,7 @@ namespace http {
 class NavdbUriHandler : public URIHandler {
 public:
   NavdbUriHandler( const char * uri = "/navdb" ) : URIHandler( uri  ) {}
-  virtual bool handleRequest( const HTTPRequest & request, HTTPResponse & response );
+  virtual bool handleRequest( const HTTPRequest & request, HTTPResponse & response, Connection * connection );
 };
 
 } // namespace http
index 6bb555c623dc98fb6a1b1537f9574ec8b83f4169..b15a18947ce54aa70d22643e7d476743447e4733 100644 (file)
@@ -81,7 +81,7 @@ const SGPropertyNode_ptr PropertyChangeObserver::addObservation( const string pr
     return entry->_node;
   }
   catch( string & s ) {
-    SG_LOG(SG_NETWORK,SG_ALERT,"httpd: can't observer '" << propertyName << "'. Invalid name." );
+    SG_LOG(SG_NETWORK,SG_WARN,"httpd: can't observer '" << propertyName << "'. Invalid name." );
   }
 
   SGPropertyNode_ptr empty;
index c0576455f91ae592e261ba3da568f27b6692d832..ecfdefbb4b1ec0e3bd898016165be314c039e2c4 100644 (file)
@@ -90,15 +90,16 @@ void PropertyChangeWebsocket::handleRequest(const HTTPRequest & request, Websock
   }
 }
 
-void PropertyChangeWebsocket::update(WebsocketWriter & writer)
+void PropertyChangeWebsocket::poll(WebsocketWriter & writer)
 {
   for (WatchedNodesList::iterator it = _watchedNodes.begin(); it != _watchedNodes.end(); ++it) {
     SGPropertyNode_ptr node = *it;
 
     string newValue;
     if (_propertyChangeObserver->isChangedValue(node)) {
-      SG_LOG(SG_NETWORK, SG_DEBUG, "httpd: new Value for " << node->getPath(true) << " '" << node->getStringValue() << "' #" << id);
-      writer.writeText( JSON::toJsonString( false, node, 0, fgGetDouble("/sim/time/elapsed-sec") ) );
+      string out = JSON::toJsonString( false, node, 0, fgGetDouble("/sim/time/elapsed-sec") );
+      SG_LOG(SG_NETWORK, SG_DEBUG, "PropertyChangeWebsocket::poll() new Value for " << node->getPath(true) << " '" << node->getStringValue() << "' #" << id << ": " << out );
+      writer.writeText( out );
     }
   }
 }
@@ -115,11 +116,13 @@ void PropertyChangeWebsocket::WatchedNodesList::handleCommand(const string & com
     }
     SGPropertyNode_ptr n = propertyChangeObserver->addObservation(node);
     if (n.valid()) push_back(n);
+    SG_LOG(SG_NETWORK, SG_INFO, "httpd: " << command << " '" << node << "' success");
 
   } else if (command == "removeListener") {
     for (iterator it = begin(); it != end(); ++it) {
       if (node == (*it)->getPath(true)) {
         this->erase(it);
+        SG_LOG(SG_NETWORK, SG_INFO, "httpd: " << command << " '" << node << "' success");
         return;
       }
       SG_LOG(SG_NETWORK, SG_WARN, "httpd: " << command << " '" << node << "' ignored (not found)");
index 752df188876456ffc033a4b14813e996673dd7b5..f195c4917ed45ed68622c5a50e757a1b96b9b517 100644 (file)
@@ -37,7 +37,7 @@ public:
   virtual ~PropertyChangeWebsocket();
   virtual void close();
   virtual void handleRequest(const HTTPRequest & request, WebsocketWriter & writer);
-  virtual void update(WebsocketWriter & writer);
+  virtual void poll(WebsocketWriter & writer);
 
 private:
   unsigned id;
index edbc0f676006a2dcf5c021af1a170777c40eb728..c9ebda7d85369cc0bfce793f70402126138ae2c7 100644 (file)
@@ -218,7 +218,7 @@ static DOMElement * renderPropertyValueElement( SGPropertyNode_ptr node )
     return root;
 }
 
-bool PropertyUriHandler::handleGetRequest( const HTTPRequest & request, HTTPResponse & response )
+bool PropertyUriHandler::handleGetRequest( const HTTPRequest & request, HTTPResponse & response, Connection * connection )
 {
 
   string propertyPath = request.Uri;
@@ -298,6 +298,7 @@ bool PropertyUriHandler::handleGetRequest( const HTTPRequest & request, HTTPResp
     e->addChild( new DOMTextElement( propertyPath ) );
     // does not exist
     body->addChild( e );
+    response.StatusCode = 404;
 
   } else if( node->nChildren() > 0 ) {
     // Render the list of children
index 4557552f540b06903e2da30d6996a9ab284530fb..6faa159950ee657eb8e4999d57a8c8d2099855b8 100644 (file)
@@ -29,7 +29,7 @@ namespace http {
 class PropertyUriHandler : public URIHandler {
 public:
   PropertyUriHandler( const char * uri = "/prop/" ) : URIHandler( uri ) {}
-  virtual bool handleGetRequest( const HTTPRequest & request, HTTPResponse & response );
+  virtual bool handleGetRequest( const HTTPRequest & request, HTTPResponse & response, Connection * connection );
 };
 
 } // namespace http
index d682e65b7ce9fa6a8a24072016930c981ffb9c3f..09252fdfd2297e49b7f78485083eb7381c9dfcde 100644 (file)
@@ -33,7 +33,7 @@ namespace flightgear {
 namespace http {
 
 
-bool RunUriHandler::handleRequest( const HTTPRequest & request, HTTPResponse & response )
+bool RunUriHandler::handleRequest( const HTTPRequest & request, HTTPResponse & response, Connection * connection )
 {
   response.Header["Content-Type"] = "text/plain";
   string command = request.RequestVariables.get("value");
@@ -54,6 +54,7 @@ bool RunUriHandler::handleRequest( const HTTPRequest & request, HTTPResponse & r
   } 
 
   response.Content = "command '" + command + "' failed.";
+  response.StatusCode = 501; // Not implemented probably suits best
   SG_LOG( SG_NETWORK, SG_WARN, response.Content );
   return true;
 }
index 0d7b0ecfe088be3ef0be83e03cd017db3031edef..ccc12a106119efc1e66e92c5482e218791cc1d62 100644 (file)
@@ -29,7 +29,7 @@ namespace http {
 class RunUriHandler : public URIHandler {
 public:
   RunUriHandler( const char * uri = "/run.cgi" ) : URIHandler( uri  ) {}
-  virtual bool handleRequest( const HTTPRequest & request, HTTPResponse & response );
+  virtual bool handleRequest( const HTTPRequest & request, HTTPResponse & response, Connection * connection );
 };
 
 } // namespace http
index 42204e13661545cbc52fd3b95a061724d54f6212..e856ba6efd0becc34ee93299a67cc1da660f63b2 100644 (file)
@@ -1,6 +1,8 @@
 // ScreenshotUriHandler.cxx -- Provide screenshots via http
 //
-// Written by Torsten Dreyer, started April 2014.
+// Started by Curtis Olson, started June 2001.
+// osg support written by James Turner
+// Ported to new httpd infrastructure by Torsten Dreyer
 //
 // Copyright (C) 2014  Torsten Dreyer
 //
 // along with this program; if not, write to the Free Software
 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
-
 #include "ScreenshotUriHandler.hxx"
 
+#include <osgDB/Registry>
+#include <osgDB/ReaderWriter>
+#include <osgUtil/SceneView>
+#include <osgViewer/Viewer>
+
+#include <simgear/threads/SGQueue.hxx>
+#include <simgear/structure/Singleton.hxx>
+#include <Main/globals.hxx>
+#include <Viewer/renderer.hxx>
+
+#include <queue>
+#include <boost/lexical_cast.hpp>
+
 using std::string;
+using std::vector;
+using std::list;
 
 namespace flightgear {
 namespace http {
 
-bool ScreenshotUriHandler::handleRequest( const HTTPRequest & request, HTTPResponse & response )
+///////////////////////////////////////////////////////////////////////////
+
+class ImageReadyListener {
+public:
+  virtual void imageReady(osg::ref_ptr<osg::Image>) = 0;
+  virtual ~ImageReadyListener()
+  {
+  }
+};
+
+class StringReadyListener {
+public:
+  virtual void stringReady(const std::string &) = 0;
+  virtual ~StringReadyListener()
+  {
+  }
+};
+
+struct ImageCompressionTask {
+  StringReadyListener * stringReadyListener;
+  string format;
+  osg::ref_ptr<osg::Image> image;
+
+  ImageCompressionTask()
+  {
+    stringReadyListener = NULL;
+  }
+
+  ImageCompressionTask(const ImageCompressionTask & other)
+  {
+    stringReadyListener = other.stringReadyListener;
+    format = other.format;
+    image = other.image;
+  }
+
+  ImageCompressionTask & operator =(const ImageCompressionTask & other)
+  {
+    stringReadyListener = other.stringReadyListener;
+    format = other.format;
+    image = other.image;
+    return *this;
+  }
+
+};
+
+class ImageCompressor: public OpenThreads::Thread {
+public:
+  ImageCompressor()
+  {
+  }
+  virtual void run();
+  void addTask(ImageCompressionTask & task);
+  private:
+  typedef SGBlockingQueue<ImageCompressionTask> TaskList;
+  TaskList _tasks;
+};
+
+typedef simgear::Singleton<ImageCompressor> ImageCompressorSingleton;
+
+void ImageCompressor::run()
+{
+  osg::ref_ptr<osgDB::ReaderWriter::Options> options = new osgDB::ReaderWriter::Options("JPEG_QUALITY 80 PNG_COMPRESSION 9");
+
+  SG_LOG(SG_NETWORK, SG_DEBUG, "ImageCompressor is running");
+  for (;;) {
+    ImageCompressionTask task = _tasks.pop();
+    SG_LOG(SG_NETWORK, SG_DEBUG, "ImageCompressor has an image");
+    if ( NULL != task.stringReadyListener) {
+      SG_LOG(SG_NETWORK, SG_DEBUG, "ImageCompressor checking for writer for " << task.format);
+      osgDB::ReaderWriter* writer = osgDB::Registry::instance()->getReaderWriterForExtension(task.format);
+      if (!writer)
+      continue;
+
+      SG_LOG(SG_NETWORK, SG_DEBUG, "ImageCompressor compressing to " << task.format);
+      std::stringstream outputStream;
+      osgDB::ReaderWriter::WriteResult wr;
+      wr = writer->writeImage(*task.image, outputStream, options);
+
+      if (wr.success()) {
+        SG_LOG(SG_NETWORK, SG_DEBUG, "ImageCompressor compressed to  " << task.format);
+        task.stringReadyListener->stringReady(outputStream.str());
+      }
+      SG_LOG(SG_NETWORK, SG_DEBUG, "ImageCompressor done for this image" << task.format);
+    }
+  }
+  SG_LOG(SG_NETWORK, SG_DEBUG, "ImageCompressor exiting");
+}
+
+void ImageCompressor::addTask(ImageCompressionTask & task)
 {
-  response.Header["Content-Type"] = "image/jpeg";
-//  string response = "Screenshot - not yet implemented :-(";
-//  mg_send_data( connection, response.c_str(), response.length() );
-  return true;
+  _tasks.push(task);
+}
+
+/**
+ * Based on <a href="http://code.google.com/p/osgworks">osgworks</a> ScreenCapture.cpp
+ *
+ */
+class ScreenshotCallback: public osg::Camera::DrawCallback {
+public:
+  ScreenshotCallback()
+      : _min_delta_tick(1.0/8.0)
+  {
+    _previousFrameTick = osg::Timer::instance()->tick();
+  }
+
+  virtual void operator ()(osg::RenderInfo& renderInfo) const
+  {
+    osg::Timer_t n = osg::Timer::instance()->tick();
+    double dt = osg::Timer::instance()->delta_s(_previousFrameTick,n);
+    if (dt < _min_delta_tick)
+        return;
+    _previousFrameTick = n;
+
+    bool hasSubscribers = false;
+    {
+      OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_lock);
+      hasSubscribers = !_subscribers.empty();
+
+    }
+    if (hasSubscribers) {
+      osg::ref_ptr<osg::Image> image = new osg::Image;
+      const osg::Viewport* vp = renderInfo.getState()->getCurrentViewport();
+      image->readPixels(vp->x(), vp->y(), vp->width(), vp->height(), GL_RGB, GL_UNSIGNED_BYTE);
+      {
+        OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_lock);
+        while (!_subscribers.empty()) {
+          try {
+            _subscribers.back()->imageReady(image);
+          }
+          catch (...) {
+          }
+          _subscribers.pop_back();
+
+        }
+      }
+    }
+  }
+
+  void subscribe(ImageReadyListener * subscriber)
+  {
+    OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_lock);
+    _subscribers.push_back(subscriber);
+  }
+
+  void unsubscribe(ImageReadyListener * subscriber)
+  {
+    OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_lock);
+    _subscribers.remove( subscriber );
+  }
+
+private:
+  mutable list<ImageReadyListener*> _subscribers;
+  mutable OpenThreads::Mutex _lock;
+  mutable double _previousFrameTick;
+  double _min_delta_tick;
+};
+
+///////////////////////////////////////////////////////////////////////////
+
+class ScreenshotRequest: public ConnectionData, public ImageReadyListener, StringReadyListener {
+public:
+  ScreenshotRequest(const string & window, const string & type, bool stream)
+      : _type(type), _stream(stream)
+  {
+    if ( NULL == osgDB::Registry::instance()->getReaderWriterForExtension(_type))
+    throw sg_format_exception("Unsupported image type: " + type, type);
+
+    osg::Camera * camera = findLastCamera(globals->get_renderer()->getViewer(), window);
+    if ( NULL == camera)
+    throw sg_error("Can't find a camera for window '" + window + "'");
+
+    // add our ScreenshotCallback to the camera
+    if ( NULL == camera->getFinalDrawCallback()) {
+      //TODO: are we leaking the Callback on reinit?
+      camera->setFinalDrawCallback(new ScreenshotCallback());
+    }
+
+    _screenshotCallback = dynamic_cast<ScreenshotCallback*>(camera->getFinalDrawCallback());
+    if ( NULL == _screenshotCallback)
+    throw sg_error("Can't find ScreenshotCallback");
+
+    requestScreenshot();
+  }
+
+  virtual ~ScreenshotRequest()
+  {
+    _screenshotCallback->unsubscribe(this);
+  }
+
+  virtual void imageReady(osg::ref_ptr<osg::Image> rawImage)
+  {
+    // called from a rendering thread, not from the main loop
+    ImageCompressionTask task;
+    task.image = rawImage;
+    task.format = _type;
+    task.stringReadyListener = this;
+    ImageCompressorSingleton::instance()->addTask(task);
+  }
+
+  void requestScreenshot()
+  {
+    _screenshotCallback->subscribe(this);
+  }
+
+  mutable OpenThreads::Mutex _lock;
+
+  virtual void stringReady(const string & s)
+  {
+    // called from the compressor thread
+    OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_lock);
+    _compressedData = s;
+  }
+
+  string getScreenshot()
+  {
+    string reply;
+    {
+      // called from the main loop
+      OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_lock);
+      reply = _compressedData;
+      _compressedData.clear();
+    }
+    return reply;
+  }
+
+  osg::Camera* findLastCamera(osgViewer::ViewerBase * viewer, const string & windowName)
+  {
+    osgViewer::ViewerBase::Windows windows;
+    viewer->getWindows(windows);
+
+    osgViewer::GraphicsWindow* window = NULL;
+
+    if (false == windowName.empty()) {
+      for (osgViewer::ViewerBase::Windows::iterator itr = windows.begin(); itr != windows.end(); ++itr) {
+        if ((*itr)->getTraits()->windowName == windowName) {
+          window = *itr;
+          break;
+        }
+      }
+    }
+
+    if ( NULL == window) {
+      if (false == windowName.empty()) {
+        SG_LOG(SG_NETWORK, SG_INFO, "requested window " << windowName << " not found, using first window");
+      }
+      window = *windows.begin();
+    }
+
+    SG_LOG(SG_NETWORK, SG_DEBUG, "Looking for last Camera of window '" << window->getTraits()->windowName << "'");
+
+    osg::GraphicsContext::Cameras& cameras = window->getCameras();
+    osg::Camera* lastCamera = 0;
+    for (osg::GraphicsContext::Cameras::iterator cam_itr = cameras.begin(); cam_itr != cameras.end(); ++cam_itr) {
+      if (lastCamera) {
+        if ((*cam_itr)->getRenderOrder() > lastCamera->getRenderOrder()) {
+          lastCamera = (*cam_itr);
+        }
+        if ((*cam_itr)->getRenderOrder() == lastCamera->getRenderOrder()
+            && (*cam_itr)->getRenderOrderNum() >= lastCamera->getRenderOrderNum()) {
+          lastCamera = (*cam_itr);
+        }
+      } else {
+        lastCamera = *cam_itr;
+      }
+    }
+
+    return lastCamera;
+  }
+
+  bool isStream() const
+  {
+    return _stream;
+  }
+
+  const string & getType() const
+  {
+    return _type;
+  }
+
+private:
+  string _type;
+  bool _stream;
+  string _compressedData;
+  ScreenshotCallback * _screenshotCallback;
+};
+
+ScreenshotUriHandler::ScreenshotUriHandler(const char * uri)
+    : URIHandler(uri)
+{
+}
+
+ScreenshotUriHandler::~ScreenshotUriHandler()
+{
+  ImageCompressorSingleton::instance()->cancel();
+  //ImageCompressorSingleton::instance()->join();
+}
+
+const static string KEY("ScreenshotUriHandler::ScreenshotRequest");
+#define BOUNDARY "--fgfs-screenshot-boundary"
+
+bool ScreenshotUriHandler::handleGetRequest(const HTTPRequest & request, HTTPResponse & response, Connection * connection)
+{
+  if (!ImageCompressorSingleton::instance()->isRunning())
+  ImageCompressorSingleton::instance()->start();
+
+  string type = request.RequestVariables.get("type");
+  if (type.empty()) type = "jpg";
+
+  //  string camera = request.RequestVariables.get("camera");
+  string window = request.RequestVariables.get("window");
+
+  bool stream = (false == request.RequestVariables.get("stream").empty());
+
+  SGSharedPtr<ScreenshotRequest> screenshotRequest;
+  try {
+    SG_LOG(SG_NETWORK, SG_DEBUG, "new ScreenshotRequest("<<window<<","<<type<<"," << stream << ")");
+    screenshotRequest = new ScreenshotRequest(window, type, stream);
+  }
+  catch (sg_format_exception & ex)
+  {
+    SG_LOG(SG_NETWORK, SG_INFO, ex.getFormattedMessage());
+    response.Header["Content-Type"] = "text/plain";
+    response.StatusCode = 410;
+    response.Content = ex.getFormattedMessage();
+    return true;
+  }
+  catch (sg_error & ex)
+  {
+    SG_LOG(SG_NETWORK, SG_INFO, ex.getFormattedMessage());
+    response.Header["Content-Type"] = "text/plain";
+    response.StatusCode = 500;
+    response.Content = ex.getFormattedMessage();
+    return true;
+  }
+
+  if (false == stream) {
+    response.Header["Content-Type"] = string("image/").append(type);
+    response.Header["Content-Disposition"] = string("inline; filename=\"fgfs-screen.").append(type).append("\"");
+  } else {
+    response.Header["Content-Type"] = string("multipart/x-mixed-replace; boundary=" BOUNDARY);
+
+  }
+
+  connection->put(KEY, screenshotRequest);
+  return false; // call me again thru poll
+}
+
+bool ScreenshotUriHandler::poll(Connection * connection)
+{
+
+  SGSharedPtr<ConnectionData> data = connection->get(KEY);
+  ScreenshotRequest * screenshotRequest = dynamic_cast<ScreenshotRequest*>(data.get());
+  if ( NULL == screenshotRequest) return true; // Should not happen, kill the connection
+
+  const string & screenshot = screenshotRequest->getScreenshot();
+  if (screenshot.empty()) {
+    SG_LOG(SG_NETWORK, SG_DEBUG, "No screenshot available.");
+    return false; // not ready yet, call again.
+  }
+
+  SG_LOG(SG_NETWORK, SG_DEBUG, "Screenshot is ready, size=" << screenshot.size());
+
+  if (screenshotRequest->isStream()) {
+    string s("\r\n" BOUNDARY "\r\nContent-Type: image/");
+    s.append(screenshotRequest->getType()).append("\r\nContent-Length:");
+    s += boost::lexical_cast<string>(screenshot.size());
+    s += "\r\n\r\n";
+    connection->write(s.c_str(), s.length());
+  }
+
+  connection->write(screenshot.data(), screenshot.size());
+
+  if (screenshotRequest->isStream()) {
+    screenshotRequest->requestScreenshot();
+    // continue until user closes connection
+    return false;
+  }
+
+  // single screenshot, send terminating chunk
+  connection->remove(KEY);
+  connection->write("", 0);
+  return true; // done.
 }
 
 } // namespace http
index 954dbb30620104e7bb47b68b8f901944469b599c..858a328613d3592d165cfa4ce89c26fe3321f06c 100644 (file)
@@ -28,8 +28,10 @@ namespace http {
 
 class ScreenshotUriHandler : public URIHandler {
 public:
-  ScreenshotUriHandler( const char * uri = "/screenshot/" ) : URIHandler( uri  ) {}
-  virtual bool handleRequest( const HTTPRequest & request, HTTPResponse & response );
+  ScreenshotUriHandler( const char * uri = "/screenshot/" );
+  ~ScreenshotUriHandler();
+  virtual bool handleGetRequest( const HTTPRequest & request, HTTPResponse & response, Connection * connection );
+  virtual bool poll( Connection * connection );
 };
 
 } // namespace http
index c37470797fe26f3efe99bceab30f564b6dba30b1..679d33a68ea6becc75cd398252bf8e977d645572 100644 (file)
@@ -70,7 +70,7 @@ public:
   }
   virtual void close() = 0;
   virtual void handleRequest(const HTTPRequest & request, WebsocketWriter & writer) = 0;
-  virtual void update(WebsocketWriter & writer) = 0;
+  virtual void poll(WebsocketWriter & writer) = 0;
 
 };
 
index b1e9cbb589bdacc34c8e5aa9a9964957f392961c..881ce51706787e0fae583dda029dc530184b17cf 100644 (file)
@@ -43,35 +43,383 @@ namespace http {
 
 const char * PROPERTY_ROOT = "/sim/http";
 
+/**
+ * A Helper class for URI Handlers
+ *
+ * This class stores a list of URI Handlers and provides a lookup
+ * method for find the handler by it's URI prefix
+ */
+class URIHandlerMap: public vector<SGSharedPtr<URIHandler> > {
+public:
+  /**
+   * Find a URI Handler for a given URI
+   *
+   * Look for the first handler with a uri matching the beginning
+   * of the given uri parameter.
+   *
+   * @param uri The uri to find the handler for
+   * @return a SGSharedPtr of the URIHandler or an invalid SGSharedPtr if not found
+   */
+  SGSharedPtr<URIHandler> findHandler(const std::string & uri)
+  {
+    for (iterator it = begin(); it != end(); ++it) {
+      SGSharedPtr<URIHandler> handler = *it;
+      // check if the request-uri starts with the registered uri-string
+      if (0 == uri.find(handler->getUri())) return handler;
+    }
+    return SGSharedPtr<URIHandler>();
+  }
+};
+
+/**
+ * A Helper class to create a HTTPRequest from a mongoose connection struct
+ */
+class MongooseHTTPRequest: public HTTPRequest {
+private:
+  /**
+   * Creates a std::string from a char pointer and an optionally given length
+   * If the pointer is NULL or the length is zero, return an empty string
+   * If no length is given, create a std::string from a c-string (up to the /0 terminator)
+   * If length is given, use as many chars as given in length (can exceed the /0 terminator)
+   *
+   * @param cp Points to the source of the string
+   * @param len The number of chars to copy to the new string (optional)
+   * @return a std::string containing a copy of the source
+   */
+  static inline string NotNull(const char * cp, size_t len = string::npos)
+  {
+    if ( NULL == cp || 0 == len) return string("");
+    if (string::npos == len) return string(cp);
+    return string(cp, len);
+  }
+
+public:
+  /**
+   * Constructs a HTTPRequest from a mongoose connection struct
+   * Copies all fields into STL compatible local elements, performs urlDecode etc.
+   *
+   * @param connection the mongoose connection struct with the source data
+   */
+  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);
+
+  }
+
+  /**
+   * Decodes a URL encoded string
+   * replaces '+' by ' '
+   * replaces %nn hexdigits
+   *
+   * @param s The source string do decode
+   * @return The decoded String
+   */
+  static 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;
+  }
+
+};
+
+/**
+ * A FGHttpd implementation based on mongoose httpd
+ *
+ * Mongoose API is documented here: http://cesanta.com/docs/API.shtml
+ */
 class MongooseHttpd: public FGHttpd {
 public:
+
+  /**
+   * Construct a MongooseHttpd object from options in a PropertyNode
+   */
   MongooseHttpd(SGPropertyNode_ptr);
+
+  /**
+   * Cleanup et.al.
+   */
   ~MongooseHttpd();
+
+  /**
+   * override SGSubsystem::init()
+   *
+   * Reads the configuration PropertyNode, installs URIHandlers and configures mongoose
+   */
   void init();
+
+  /**
+   * override SGSubsystem::bind()
+   *
+   * Currently a noop
+   */
   void bind();
+
+  /**
+   * override SGSubsystem::unbind()
+   * shutdown of mongoose, clear connections, unregister URIHandlers
+   */
   void unbind();
-  void shutdown();
+
+  /**
+   * overrride SGSubsystem::update()
+   * poll connections, check for changed properties
+   */
   void update(double dt);
 
+  /**
+   * Returns a URIHandler for the given uri
+   *
+   * @see URIHandlerMap::findHandler( const std::string & uri )
+   */
+  SGSharedPtr<URIHandler> findHandler(const std::string & uri)
+  {
+    return _uriHandler.findHandler(uri);
+  }
+
+  Websocket * newWebsocket(const string & uri);
+
 private:
-  int requestHandler(struct mg_connection *);
-  int websocketHandler(struct mg_connection *);
-  void closeWebsocket(struct mg_connection *);
-  int iterateCallback(struct mg_connection *, mg_event event);
+  int poll(struct mg_connection * connection);
+  int auth(struct mg_connection * connection);
+  int request(struct mg_connection * connection);
+  void close(struct mg_connection * connection);
 
   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;
+  URIHandlerMap _uriHandler;
 
   PropertyChangeObserver _propertyChangeObserver;
 };
 
+class MongooseConnection: public Connection {
+public:
+  MongooseConnection(MongooseHttpd * httpd)
+      : _httpd(httpd)
+  {
+  }
+  virtual ~MongooseConnection();
+
+  virtual void close(struct mg_connection * connection) = 0;
+  virtual int poll(struct mg_connection * connection) = 0;
+  virtual int request(struct mg_connection * connection) = 0;
+  virtual void write(const char * data, size_t len)
+  {
+    if (_connection) mg_send_data(_connection, data, len);
+  }
+
+  static MongooseConnection * getConnection(MongooseHttpd * httpd, struct mg_connection * connection);
+
+protected:
+  void setConnection(struct mg_connection * connection)
+  {
+    _connection = connection;
+  }
+  MongooseHttpd * _httpd;
+  struct mg_connection * _connection;
+
+};
+
+MongooseConnection::~MongooseConnection()
+{
+}
+
+class RegularConnection: public MongooseConnection {
+public:
+  RegularConnection(MongooseHttpd * httpd)
+      : MongooseConnection(httpd)
+  {
+  }
+  virtual ~RegularConnection()
+  {
+  }
+
+  virtual void close(struct mg_connection * connection);
+  virtual int poll(struct mg_connection * connection);
+  virtual int request(struct mg_connection * connection);
+
+private:
+  SGSharedPtr<URIHandler> _handler;
+};
+
+class WebsocketConnection: public MongooseConnection {
+public:
+  WebsocketConnection(MongooseHttpd * httpd)
+      : MongooseConnection(httpd), _websocket(NULL)
+  {
+  }
+  virtual ~WebsocketConnection()
+  {
+    delete _websocket;
+  }
+  virtual void close(struct mg_connection * connection);
+  virtual int poll(struct mg_connection * connection);
+  virtual int request(struct mg_connection * connection);
+
+private:
+  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;
+  };
+  Websocket * _websocket;
+};
+
+MongooseConnection * MongooseConnection::getConnection(MongooseHttpd * httpd, struct mg_connection * connection)
+{
+  if (connection->connection_param) return static_cast<MongooseConnection*>(connection->connection_param);
+  MongooseConnection * c;
+  if (connection->is_websocket) c = new WebsocketConnection(httpd);
+  else c = new RegularConnection(httpd);
+
+  connection->connection_param = c;
+  return c;
+}
+
+int RegularConnection::request(struct mg_connection * connection)
+{
+  setConnection(connection);
+  MongooseHTTPRequest request(connection);
+  SG_LOG(SG_NETWORK, SG_INFO, "RegularConnection::request for " << request.Uri);
+
+  // find a handler for the uri and remember it for possible polls on this connection
+  _handler = _httpd->findHandler(request.Uri);
+  if (false == _handler.valid()) {
+    // uri not registered - pass false to indicate we have not processed the request
+    return MG_FALSE;
+  }
+
+  // We handle this URI, prepare the response
+  HTTPResponse response;
+  response.Header["Server"] = "FlightGear/" FLIGHTGEAR_VERSION " Mongoose/" MONGOOSE_VERSION;
+  response.Header["Connection"] = "keep-alive";
+  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;
+  }
+
+  // hand the request over to the handler, returns true if request is finished, 
+  // false the handler wants to get polled again (calling handlePoll() next time)
+  bool done = _handler->handleRequest(request, response, this);
+  // fill in the response header
+  mg_send_status(connection, response.StatusCode);
+  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 (done || false == response.Content.empty()) {
+    SG_LOG(SG_NETWORK, SG_ALERT,
+        "RegularConnection::request() responding " << response.Content.length() << " Bytes, done=" << done);
+    mg_send_data(connection, response.Content.c_str(), response.Content.length());
+  }
+  return done ? MG_TRUE : MG_MORE;
+}
+
+int RegularConnection::poll(struct mg_connection * connection)
+{
+  setConnection(connection);
+  if (false == _handler.valid()) return MG_FALSE;
+  // only return MG_TRUE if we handle this request
+  return _handler->poll(this) ? MG_TRUE : MG_MORE;
+}
+
+void RegularConnection::close(struct mg_connection * connection)
+{
+  setConnection(connection);
+  // nothing to close
+}
+
+void WebsocketConnection::close(struct mg_connection * connection)
+{
+  setConnection(connection);
+  if ( NULL != _websocket) _websocket->close();
+  delete _websocket;
+  _websocket = NULL;
+}
+
+int WebsocketConnection::poll(struct mg_connection * connection)
+{
+  setConnection(connection);
+  // we get polled before the first request came in but we know 
+  // nothing about how to handle that before we know the URI.
+  // so simply ignore that poll
+  if ( NULL != _websocket) {
+    MongooseWebsocketWriter writer(connection);
+    _websocket->poll(writer);
+  }
+  return MG_MORE;
+}
+
+int WebsocketConnection::request(struct mg_connection * connection)
+{
+  setConnection(connection);
+  MongooseHTTPRequest request(connection);
+  SG_LOG(SG_NETWORK, SG_INFO, "WebsocketConnection::request for " << request.Uri);
+
+  if ( NULL == _websocket) _websocket = _httpd->newWebsocket(request.Uri);
+  if ( NULL == _websocket) {
+    SG_LOG(SG_NETWORK, SG_WARN, "httpd: unhandled websocket uri: " << request.Uri);
+    return MG_TRUE; // close connection - good bye
+  }
+
+  MongooseWebsocketWriter writer(connection);
+  _websocket->handleRequest(request, writer);
+  return MG_MORE;
+}
+
 MongooseHttpd::MongooseHttpd(SGPropertyNode_ptr configNode)
     : _server(NULL), _configNode(configNode)
 {
@@ -90,27 +438,27 @@ void MongooseHttpd::init()
 
     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));
+      _uriHandler.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));
+      _uriHandler.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));
+      _uriHandler.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));
+      _uriHandler.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));
+      _uriHandler.push_back(new flightgear::http::NavdbUriHandler(uri));
     }
   }
 
@@ -120,35 +468,34 @@ void MongooseHttpd::init()
   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 );
+    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, "," );
+      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." );
+      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 );
+        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 );
+          rewrites.append(fgRoot).append(1, '/').append(rhs);
         }
       }
-      if( false == rewrites.empty() )
-        mg_set_option(_server, "url_rewrites", rewrites.c_str() );
+      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"));
@@ -165,198 +512,82 @@ void MongooseHttpd::bind()
 void MongooseHttpd::unbind()
 {
   mg_destroy_server(&_server);
-  _uriHandlers.clear();
+  _uriHandler.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)
+int MongooseHttpd::poll(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)
-  {
-  }
+  if ( NULL == connection->connection_param) return MG_FALSE; // connection not yet set up - ignore poll
 
-  virtual int writeToWebsocket(int opcode, const char * data, size_t len)
-  {
-    return mg_websocket_write( _connection, opcode, data, len );
-  }
-private:
-  struct mg_connection * _connection;
-};
+  return MongooseConnection::getConnection(this, connection)->poll(connection);
+}
 
-int MongooseHttpd::websocketHandler(struct mg_connection * connection)
+int MongooseHttpd::auth(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;
-
+  // auth preceeds request for websockets and regular connections,
+  // and eventually the websocket has been already set up by mongoose
+  // use this to choose the connection type
+  MongooseConnection::getConnection(this, connection);
+  //return MongooseConnection::getConnection(this,connection)->auth(connection);
+  return MG_TRUE; // unrestricted access for now
 }
 
-int MongooseHttpd::requestHandler(struct mg_connection * connection)
+int MongooseHttpd::request(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;
+  return MongooseConnection::getConnection(this, connection)->request(connection);
 }
 
-int MongooseHttpd::iterateCallback(struct mg_connection * connection, mg_event event)
+void MongooseHttpd::close(struct mg_connection * 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
+  MongooseConnection * c = MongooseConnection::getConnection(this, connection);
+  c->close(connection);
+  delete c;
 }
-
-int MongooseHttpd::staticIterateCallback(struct mg_connection * connection, mg_event event)
+Websocket * MongooseHttpd::newWebsocket(const string & uri)
 {
-  return static_cast<MongooseHttpd*>(connection->server_param)->iterateCallback(connection, event);
+  if (uri.find("/PropertyListener") == 0) {
+    SG_LOG(SG_NETWORK, SG_INFO, "new PropertyChangeWebsocket for: " << uri);
+    return new PropertyChangeWebsocket(&_propertyChangeObserver);
+  }
+  return NULL;
 }
 
 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);
+    case MG_POLL:        // MG_TRUE: finished sending data, MG_MORE, call again
+      return static_cast<MongooseHttpd*>(connection->server_param)->poll(connection);
+
+    case MG_AUTH:        // If callback returns MG_FALSE, authentication fails
+      return static_cast<MongooseHttpd*>(connection->server_param)->auth(connection);
+
+    case MG_REQUEST:     // If callback returns MG_FALSE, Mongoose continues with req
+      return static_cast<MongooseHttpd*>(connection->server_param)->request(connection);
+
+    case MG_CLOSE:       // Connection is closed, callback return value is ignored
+      static_cast<MongooseHttpd*>(connection->server_param)->close(connection);
       return MG_TRUE;
 
-    case MG_AUTH:
-      return MG_TRUE; // allow anybody (for now)
+    case MG_HTTP_ERROR:  // If callback returns MG_FALSE, Mongoose continues with err 
+      return MG_FALSE;   // we don't handle errors - let mongoose do the work
 
-    default:
+      // LUA not implemented - and will go away anyway
+    case MG_LUA:         // Called before LSP page invoked   
+      // client services not used/implemented. Signal 'close connection' to be sure
+    case MG_CONNECT:     // If callback returns MG_FALSE, connect fails
+    case MG_REPLY:       // If callback returns MG_FALSE, Mongoose closes connection
       return MG_FALSE;
 
+    default:
+      return MG_FALSE; // keep compiler happy..
   }
 }
 
index ec6d5611d879f7b2a0d6f9293aace98beda4229e..1072e90e7b5a7d3bc0f43e64d7f83525c271ce83 100644 (file)
 #include "HTTPRequest.hxx"
 #include "HTTPResponse.hxx"
 #include <simgear/structure/SGReferenced.hxx>
+#include <simgear/structure/SGSharedPtr.hxx>
 #include <string>
 #include <map>
 
 namespace flightgear {
 namespace http {
 
+class ConnectionData : public SGReferenced {
+public:
+  // make this polymorphic
+  virtual ~ConnectionData() {}
+};
+
+class Connection {
+public:
+  void put( const std::string & key, SGSharedPtr<ConnectionData> value ) {
+    connectionData[key] = value;
+  }
+  SGSharedPtr<ConnectionData> get(const std::string & key ) {
+    return connectionData[key];
+  }
+
+  void remove( const std::string & key ) {
+    connectionData.erase(key);
+  }
+
+  virtual void write(const char * data, size_t len) = 0;
+
+private:
+  std::map<std::string,SGSharedPtr<ConnectionData> > connectionData;
+};
+
+/**
+ * A Base class for URI Handlers.
+ * Provides methods for handling a request and handling subsequent polls.
+ * All methods are implemented as noops and should be overridden by deriving classes
+ */
 class URIHandler : public SGReferenced {
 public:
   URIHandler( const char * uri ) : _uri(uri) {}
   virtual ~URIHandler()  {}
 
-  virtual bool handleRequest( const HTTPRequest & request, HTTPResponse & response ) {
-    if( request.Method == "GET" ) return handleGetRequest( request, response );
-    if( request.Method == "PUT" ) return handlePutRequest( request, response );
-    return false;
+  /**
+   * This method gets called from the httpd if a request has been detected on the connection
+   * @param request The HTTP Request filled in by the httpd
+   * @param response the HTTP Response to be filled in by the hander
+   * @param connection Connection specific information, can be used to store persistent state
+   * @return true if the request has been completely answered, false to get poll()ed
+   */
+  virtual bool handleRequest( const HTTPRequest & request, HTTPResponse & response, Connection * connection = NULL ) {
+    if( request.Method == "GET" ) return handleGetRequest( request, response, connection );
+    if( request.Method == "PUT" ) return handlePutRequest( request, response, connection );
+    return true;
   }
 
-  virtual bool handleGetRequest( const HTTPRequest & request, HTTPResponse & response ) {
-    return false;
+  /**
+   * Convenience method for GET Requests, gets called by handleRequest if not overridden
+   * @param request @see handleRequest()
+   * @param response @see handleRequest()
+   * @param connection @see handleRequest()
+   * @return @see handleRequest()
+   *
+   */
+  virtual bool handleGetRequest( const HTTPRequest & request, HTTPResponse & response, Connection * connection ) {
+    return true;
   }
 
-  virtual bool handlePutRequest( const HTTPRequest & request, HTTPResponse & response ) {
-    return false;
+  /**
+   * Convenience method for PUT Requests, gets called by handleRequest if not overridden
+   * @param request @see handleRequest()
+   * @param response @see handleRequest()
+   * @param connection @see handleRequest()
+   * @return @see handleRequest()
+   *
+   */
+  virtual bool handlePutRequest( const HTTPRequest & request, HTTPResponse & response, Connection * connection ) {
+    return true;
   }
 
+  /**
+   * This method gets called from the httpd if the preceding handleRequest() or poll() method returned false.
+   * @param connection @see handleRequest()
+   * @return @see handleRequest()
+   */
+  virtual bool poll( Connection * connection ) { return false; }
+
+  /**
+   * Getter for the URI this handler serves
+   *
+   * @return the URI this handler serves
+   */
   const std::string & getUri() const { return _uri; }
 
 private: