Update of the httpd implementation
- Add a screenshot uri handler - Enable persistent http connections - Simple CORS implementatation for the JSON property uri handler many changes for the httpd
This commit is contained in:
parent
a5c39e3009
commit
2e76a2c72a
16 changed files with 966 additions and 242 deletions
|
@ -29,9 +29,23 @@ using std::string;
|
||||||
namespace flightgear {
|
namespace flightgear {
|
||||||
namespace http {
|
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["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;
|
string propertyPath = request.Uri;
|
||||||
|
|
||||||
|
@ -58,7 +72,7 @@ bool JsonUriHandler::handleGetRequest( const HTTPRequest & request, HTTPResponse
|
||||||
|
|
||||||
SGPropertyNode_ptr node = fgGetNode( string("/") + propertyPath );
|
SGPropertyNode_ptr node = fgGetNode( string("/") + propertyPath );
|
||||||
if( false == node.valid() ) {
|
if( false == node.valid() ) {
|
||||||
response.StatusCode = 400;
|
response.StatusCode = 404;
|
||||||
response.Content = "{}";
|
response.Content = "{}";
|
||||||
SG_LOG(SG_NETWORK,SG_WARN, "Node not found: '" << propertyPath << "'");
|
SG_LOG(SG_NETWORK,SG_WARN, "Node not found: '" << propertyPath << "'");
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -29,7 +29,7 @@ namespace http {
|
||||||
class JsonUriHandler : public URIHandler {
|
class JsonUriHandler : public URIHandler {
|
||||||
public:
|
public:
|
||||||
JsonUriHandler( const char * uri = "/json/" ) : URIHandler( uri ) {}
|
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
|
} // namespace http
|
||||||
|
|
|
@ -218,10 +218,24 @@ static cJSON * createFeatureFor(FGPositionedRef positioned)
|
||||||
return feature;
|
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["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";
|
bool indent = request.RequestVariables.get("i") == "y";
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,7 @@ namespace http {
|
||||||
class NavdbUriHandler : public URIHandler {
|
class NavdbUriHandler : public URIHandler {
|
||||||
public:
|
public:
|
||||||
NavdbUriHandler( const char * uri = "/navdb" ) : URIHandler( uri ) {}
|
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
|
} // namespace http
|
||||||
|
|
|
@ -81,7 +81,7 @@ const SGPropertyNode_ptr PropertyChangeObserver::addObservation( const string pr
|
||||||
return entry->_node;
|
return entry->_node;
|
||||||
}
|
}
|
||||||
catch( string & s ) {
|
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;
|
SGPropertyNode_ptr empty;
|
||||||
|
|
|
@ -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) {
|
for (WatchedNodesList::iterator it = _watchedNodes.begin(); it != _watchedNodes.end(); ++it) {
|
||||||
SGPropertyNode_ptr node = *it;
|
SGPropertyNode_ptr node = *it;
|
||||||
|
|
||||||
string newValue;
|
string newValue;
|
||||||
if (_propertyChangeObserver->isChangedValue(node)) {
|
if (_propertyChangeObserver->isChangedValue(node)) {
|
||||||
SG_LOG(SG_NETWORK, SG_DEBUG, "httpd: new Value for " << node->getPath(true) << " '" << node->getStringValue() << "' #" << id);
|
string out = JSON::toJsonString( false, node, 0, fgGetDouble("/sim/time/elapsed-sec") );
|
||||||
writer.writeText( 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);
|
SGPropertyNode_ptr n = propertyChangeObserver->addObservation(node);
|
||||||
if (n.valid()) push_back(n);
|
if (n.valid()) push_back(n);
|
||||||
|
SG_LOG(SG_NETWORK, SG_INFO, "httpd: " << command << " '" << node << "' success");
|
||||||
|
|
||||||
} else if (command == "removeListener") {
|
} else if (command == "removeListener") {
|
||||||
for (iterator it = begin(); it != end(); ++it) {
|
for (iterator it = begin(); it != end(); ++it) {
|
||||||
if (node == (*it)->getPath(true)) {
|
if (node == (*it)->getPath(true)) {
|
||||||
this->erase(it);
|
this->erase(it);
|
||||||
|
SG_LOG(SG_NETWORK, SG_INFO, "httpd: " << command << " '" << node << "' success");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
SG_LOG(SG_NETWORK, SG_WARN, "httpd: " << command << " '" << node << "' ignored (not found)");
|
SG_LOG(SG_NETWORK, SG_WARN, "httpd: " << command << " '" << node << "' ignored (not found)");
|
||||||
|
|
|
@ -37,7 +37,7 @@ public:
|
||||||
virtual ~PropertyChangeWebsocket();
|
virtual ~PropertyChangeWebsocket();
|
||||||
virtual void close();
|
virtual void close();
|
||||||
virtual void handleRequest(const HTTPRequest & request, WebsocketWriter & writer);
|
virtual void handleRequest(const HTTPRequest & request, WebsocketWriter & writer);
|
||||||
virtual void update(WebsocketWriter & writer);
|
virtual void poll(WebsocketWriter & writer);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
unsigned id;
|
unsigned id;
|
||||||
|
|
|
@ -218,7 +218,7 @@ static DOMElement * renderPropertyValueElement( SGPropertyNode_ptr node )
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PropertyUriHandler::handleGetRequest( const HTTPRequest & request, HTTPResponse & response )
|
bool PropertyUriHandler::handleGetRequest( const HTTPRequest & request, HTTPResponse & response, Connection * connection )
|
||||||
{
|
{
|
||||||
|
|
||||||
string propertyPath = request.Uri;
|
string propertyPath = request.Uri;
|
||||||
|
@ -298,6 +298,7 @@ bool PropertyUriHandler::handleGetRequest( const HTTPRequest & request, HTTPResp
|
||||||
e->addChild( new DOMTextElement( propertyPath ) );
|
e->addChild( new DOMTextElement( propertyPath ) );
|
||||||
// does not exist
|
// does not exist
|
||||||
body->addChild( e );
|
body->addChild( e );
|
||||||
|
response.StatusCode = 404;
|
||||||
|
|
||||||
} else if( node->nChildren() > 0 ) {
|
} else if( node->nChildren() > 0 ) {
|
||||||
// Render the list of children
|
// Render the list of children
|
||||||
|
|
|
@ -29,7 +29,7 @@ namespace http {
|
||||||
class PropertyUriHandler : public URIHandler {
|
class PropertyUriHandler : public URIHandler {
|
||||||
public:
|
public:
|
||||||
PropertyUriHandler( const char * uri = "/prop/" ) : URIHandler( uri ) {}
|
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
|
} // namespace http
|
||||||
|
|
|
@ -33,7 +33,7 @@ namespace flightgear {
|
||||||
namespace http {
|
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";
|
response.Header["Content-Type"] = "text/plain";
|
||||||
string command = request.RequestVariables.get("value");
|
string command = request.RequestVariables.get("value");
|
||||||
|
@ -54,6 +54,7 @@ bool RunUriHandler::handleRequest( const HTTPRequest & request, HTTPResponse & r
|
||||||
}
|
}
|
||||||
|
|
||||||
response.Content = "command '" + command + "' failed.";
|
response.Content = "command '" + command + "' failed.";
|
||||||
|
response.StatusCode = 501; // Not implemented probably suits best
|
||||||
SG_LOG( SG_NETWORK, SG_WARN, response.Content );
|
SG_LOG( SG_NETWORK, SG_WARN, response.Content );
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ namespace http {
|
||||||
class RunUriHandler : public URIHandler {
|
class RunUriHandler : public URIHandler {
|
||||||
public:
|
public:
|
||||||
RunUriHandler( const char * uri = "/run.cgi" ) : URIHandler( uri ) {}
|
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
|
} // namespace http
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
// ScreenshotUriHandler.cxx -- Provide screenshots via http
|
// 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
|
// Copyright (C) 2014 Torsten Dreyer
|
||||||
//
|
//
|
||||||
|
@ -18,20 +20,410 @@
|
||||||
// along with this program; if not, write to the Free Software
|
// along with this program; if not, write to the Free Software
|
||||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
|
||||||
|
|
||||||
#include "ScreenshotUriHandler.hxx"
|
#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::string;
|
||||||
|
using std::vector;
|
||||||
|
using std::list;
|
||||||
|
|
||||||
namespace flightgear {
|
namespace flightgear {
|
||||||
namespace http {
|
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()
|
||||||
{
|
{
|
||||||
response.Header["Content-Type"] = "image/jpeg";
|
osg::ref_ptr<osgDB::ReaderWriter::Options> options = new osgDB::ReaderWriter::Options("JPEG_QUALITY 80 PNG_COMPRESSION 9");
|
||||||
// string response = "Screenshot - not yet implemented :-(";
|
|
||||||
// mg_send_data( connection, response.c_str(), response.length() );
|
SG_LOG(SG_NETWORK, SG_DEBUG, "ImageCompressor is running");
|
||||||
return true;
|
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)
|
||||||
|
{
|
||||||
|
_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
|
} // namespace http
|
||||||
|
|
|
@ -28,8 +28,10 @@ namespace http {
|
||||||
|
|
||||||
class ScreenshotUriHandler : public URIHandler {
|
class ScreenshotUriHandler : public URIHandler {
|
||||||
public:
|
public:
|
||||||
ScreenshotUriHandler( const char * uri = "/screenshot/" ) : URIHandler( uri ) {}
|
ScreenshotUriHandler( const char * uri = "/screenshot/" );
|
||||||
virtual bool handleRequest( const HTTPRequest & request, HTTPResponse & response );
|
~ScreenshotUriHandler();
|
||||||
|
virtual bool handleGetRequest( const HTTPRequest & request, HTTPResponse & response, Connection * connection );
|
||||||
|
virtual bool poll( Connection * connection );
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace http
|
} // namespace http
|
||||||
|
|
|
@ -70,7 +70,7 @@ public:
|
||||||
}
|
}
|
||||||
virtual void close() = 0;
|
virtual void close() = 0;
|
||||||
virtual void handleRequest(const HTTPRequest & request, WebsocketWriter & writer) = 0;
|
virtual void handleRequest(const HTTPRequest & request, WebsocketWriter & writer) = 0;
|
||||||
virtual void update(WebsocketWriter & writer) = 0;
|
virtual void poll(WebsocketWriter & writer) = 0;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -43,154 +43,63 @@ namespace http {
|
||||||
|
|
||||||
const char * PROPERTY_ROOT = "/sim/http";
|
const char * PROPERTY_ROOT = "/sim/http";
|
||||||
|
|
||||||
class MongooseHttpd: public FGHttpd {
|
/**
|
||||||
|
* 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:
|
public:
|
||||||
MongooseHttpd(SGPropertyNode_ptr);
|
/**
|
||||||
~MongooseHttpd();
|
* Find a URI Handler for a given URI
|
||||||
void init();
|
*
|
||||||
void bind();
|
* Look for the first handler with a uri matching the beginning
|
||||||
void unbind();
|
* of the given uri parameter.
|
||||||
void shutdown();
|
*
|
||||||
void update(double dt);
|
* @param uri The uri to find the handler for
|
||||||
|
* @return a SGSharedPtr of the URIHandler or an invalid SGSharedPtr if not found
|
||||||
private:
|
*/
|
||||||
int requestHandler(struct mg_connection *);
|
SGSharedPtr<URIHandler> findHandler(const std::string & uri)
|
||||||
int websocketHandler(struct mg_connection *);
|
{
|
||||||
void closeWebsocket(struct mg_connection *);
|
for (iterator it = begin(); it != end(); ++it) {
|
||||||
int iterateCallback(struct mg_connection *, mg_event event);
|
SGSharedPtr<URIHandler> handler = *it;
|
||||||
|
// check if the request-uri starts with the registered uri-string
|
||||||
static int staticRequestHandler(struct mg_connection *, mg_event event);
|
if (0 == uri.find(handler->getUri())) return handler;
|
||||||
static int staticIterateCallback(struct mg_connection *, mg_event event);
|
}
|
||||||
|
return SGSharedPtr<URIHandler>();
|
||||||
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)
|
* A Helper class to create a HTTPRequest from a mongoose connection struct
|
||||||
{
|
*/
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
class MongooseHTTPRequest: public HTTPRequest {
|
||||||
private:
|
private:
|
||||||
inline string NotNull(const char * cp, size_t n = string::npos)
|
/**
|
||||||
|
* 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 == n) return string("");
|
if ( NULL == cp || 0 == len) return string("");
|
||||||
if (string::npos == n) return string(cp);
|
if (string::npos == len) return string(cp);
|
||||||
return string(cp, n);
|
return string(cp, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
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)
|
MongooseHTTPRequest(struct mg_connection * connection)
|
||||||
{
|
{
|
||||||
Method = NotNull(connection->request_method);
|
Method = NotNull(connection->request_method);
|
||||||
|
@ -218,7 +127,15 @@ public:
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
string urlDecode(const string & s)
|
/**
|
||||||
|
* 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 = "";
|
string r = "";
|
||||||
int max = s.length();
|
int max = s.length();
|
||||||
|
@ -241,56 +158,189 @@ public:
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
void MongooseHttpd::closeWebsocket(struct mg_connection * connection)
|
/**
|
||||||
{
|
* A FGHttpd implementation based on mongoose httpd
|
||||||
Websocket * websocket = static_cast<Websocket*>(connection->connection_param);
|
*
|
||||||
if ( NULL != websocket) websocket->close();
|
* Mongoose API is documented here: http://cesanta.com/docs/API.shtml
|
||||||
delete websocket;
|
*/
|
||||||
}
|
class MongooseHttpd: public FGHttpd {
|
||||||
|
|
||||||
class MongooseWebsocketWriter: public WebsocketWriter {
|
|
||||||
public:
|
public:
|
||||||
MongooseWebsocketWriter(struct mg_connection * connection)
|
|
||||||
: _connection(connection)
|
/**
|
||||||
|
* 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();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual int writeToWebsocket(int opcode, const char * data, size_t len)
|
Websocket * newWebsocket(const string & uri);
|
||||||
{
|
|
||||||
return mg_websocket_write( _connection, opcode, data, len );
|
|
||||||
}
|
|
||||||
private:
|
private:
|
||||||
struct mg_connection * _connection;
|
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);
|
||||||
|
|
||||||
|
struct mg_server *_server;
|
||||||
|
SGPropertyNode_ptr _configNode;
|
||||||
|
|
||||||
|
typedef int (MongooseHttpd::*handler_t)(struct mg_connection *);
|
||||||
|
URIHandlerMap _uriHandler;
|
||||||
|
|
||||||
|
PropertyChangeObserver _propertyChangeObserver;
|
||||||
};
|
};
|
||||||
|
|
||||||
int MongooseHttpd::websocketHandler(struct mg_connection * connection)
|
class MongooseConnection: public Connection {
|
||||||
{
|
public:
|
||||||
MongooseHTTPRequest request(connection);
|
MongooseConnection(MongooseHttpd * httpd)
|
||||||
MongooseWebsocketWriter writer(connection);
|
: _httpd(httpd)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
virtual ~MongooseConnection();
|
||||||
|
|
||||||
Websocket * websocket = static_cast<Websocket*>(connection->connection_param);
|
virtual void close(struct mg_connection * connection) = 0;
|
||||||
if ( NULL == websocket) {
|
virtual int poll(struct mg_connection * connection) = 0;
|
||||||
if (request.Uri.find("/PropertyListener") == 0) {
|
virtual int request(struct mg_connection * connection) = 0;
|
||||||
SG_LOG(SG_ALL, SG_INFO, "new PropertyChangeWebsocket for: " << request.Uri);
|
virtual void write(const char * data, size_t len)
|
||||||
websocket = new PropertyChangeWebsocket(&_propertyChangeObserver);
|
{
|
||||||
connection->connection_param = websocket;
|
if (_connection) mg_send_data(_connection, data, len);
|
||||||
} else {
|
|
||||||
SG_LOG(SG_ALL, SG_ALERT, "httpd: unhandled websocket uri: " << request.Uri);
|
|
||||||
return MG_FALSE;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
websocket->handleRequest(request, writer);
|
static MongooseConnection * getConnection(MongooseHttpd * httpd, struct mg_connection * connection);
|
||||||
return MG_TRUE;
|
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void setConnection(struct mg_connection * connection)
|
||||||
|
{
|
||||||
|
_connection = connection;
|
||||||
|
}
|
||||||
|
MongooseHttpd * _httpd;
|
||||||
|
struct mg_connection * _connection;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
MongooseConnection::~MongooseConnection()
|
||||||
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
int MongooseHttpd::requestHandler(struct mg_connection * connection)
|
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);
|
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;
|
HTTPResponse response;
|
||||||
response.Header["Server"] = "FlightGear/" FLIGHTGEAR_VERSION " Mongoose/" MONGOOSE_VERSION;
|
response.Header["Server"] = "FlightGear/" FLIGHTGEAR_VERSION " Mongoose/" MONGOOSE_VERSION;
|
||||||
response.Header["Connection"] = "close";
|
response.Header["Connection"] = "keep-alive";
|
||||||
response.Header["Cache-Control"] = "no-cache";
|
response.Header["Cache-Control"] = "no-cache";
|
||||||
{
|
{
|
||||||
char buf[64];
|
char buf[64];
|
||||||
|
@ -299,64 +349,245 @@ int MongooseHttpd::requestHandler(struct mg_connection * connection)
|
||||||
response.Header["Date"] = buf;
|
response.Header["Date"] = buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool processed = false;
|
// hand the request over to the handler, returns true if request is finished,
|
||||||
for (URIHandlerMap::iterator it = _uriHandlers.begin(); it != _uriHandlers.end(); ++it) {
|
// false the handler wants to get polled again (calling handlePoll() next time)
|
||||||
URIHandler * handler = *it;
|
bool done = _handler->handleRequest(request, response, this);
|
||||||
if (request.Uri.find(handler->getUri()) == 0) {
|
// fill in the response header
|
||||||
processed = handler->handleRequest(request, response);
|
mg_send_status(connection, response.StatusCode);
|
||||||
break;
|
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()) {
|
||||||
if (processed) {
|
SG_LOG(SG_NETWORK, SG_ALERT,
|
||||||
for (HTTPResponse::Header_t::const_iterator it = response.Header.begin(); it != response.Header.end(); ++it) {
|
"RegularConnection::request() responding " << response.Content.length() << " Bytes, done=" << done);
|
||||||
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());
|
mg_send_data(connection, response.Content.c_str(), response.Content.length());
|
||||||
}
|
}
|
||||||
|
return done ? MG_TRUE : MG_MORE;
|
||||||
return processed ? MG_TRUE : MG_FALSE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int MongooseHttpd::iterateCallback(struct mg_connection * connection, mg_event event)
|
int RegularConnection::poll(struct mg_connection * connection)
|
||||||
{
|
{
|
||||||
if (connection->is_websocket && event == MG_POLL) {
|
setConnection(connection);
|
||||||
Websocket * websocket = static_cast<Websocket*>(connection->connection_param);
|
if (false == _handler.valid()) return MG_FALSE;
|
||||||
if ( NULL == websocket) return MG_TRUE;
|
// only return MG_TRUE if we handle this request
|
||||||
MongooseWebsocketWriter writer( connection );
|
return _handler->poll(this) ? MG_TRUE : MG_MORE;
|
||||||
websocket->update( writer );
|
}
|
||||||
|
|
||||||
|
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_TRUE; // return value is ignored
|
return MG_MORE;
|
||||||
}
|
}
|
||||||
|
|
||||||
int MongooseHttpd::staticIterateCallback(struct mg_connection * connection, mg_event event)
|
int WebsocketConnection::request(struct mg_connection * connection)
|
||||||
{
|
{
|
||||||
return static_cast<MongooseHttpd*>(connection->server_param)->iterateCallback(connection, event);
|
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)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
_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);
|
||||||
|
_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);
|
||||||
|
_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);
|
||||||
|
_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);
|
||||||
|
_uriHandler.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);
|
||||||
|
_uriHandler.clear();
|
||||||
|
_propertyChangeObserver.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MongooseHttpd::update(double dt)
|
||||||
|
{
|
||||||
|
_propertyChangeObserver.check();
|
||||||
|
mg_poll_server(_server, 0);
|
||||||
|
_propertyChangeObserver.uncheck();
|
||||||
|
}
|
||||||
|
|
||||||
|
int MongooseHttpd::poll(struct mg_connection * connection)
|
||||||
|
{
|
||||||
|
if ( NULL == connection->connection_param) return MG_FALSE; // connection not yet set up - ignore poll
|
||||||
|
|
||||||
|
return MongooseConnection::getConnection(this, connection)->poll(connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
int MongooseHttpd::auth(struct mg_connection * connection)
|
||||||
|
{
|
||||||
|
// 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::request(struct mg_connection * connection)
|
||||||
|
{
|
||||||
|
return MongooseConnection::getConnection(this, connection)->request(connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MongooseHttpd::close(struct mg_connection * connection)
|
||||||
|
{
|
||||||
|
MongooseConnection * c = MongooseConnection::getConnection(this, connection);
|
||||||
|
c->close(connection);
|
||||||
|
delete c;
|
||||||
|
}
|
||||||
|
Websocket * MongooseHttpd::newWebsocket(const string & uri)
|
||||||
|
{
|
||||||
|
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)
|
int MongooseHttpd::staticRequestHandler(struct mg_connection * connection, mg_event event)
|
||||||
{
|
{
|
||||||
switch (event) {
|
switch (event) {
|
||||||
case MG_REQUEST:
|
case MG_POLL: // MG_TRUE: finished sending data, MG_MORE, call again
|
||||||
return
|
return static_cast<MongooseHttpd*>(connection->server_param)->poll(connection);
|
||||||
connection->is_websocket ?
|
|
||||||
static_cast<MongooseHttpd*>(connection->server_param)->websocketHandler(connection) :
|
|
||||||
static_cast<MongooseHttpd*>(connection->server_param)->requestHandler(connection);
|
|
||||||
|
|
||||||
case MG_CLOSE:
|
case MG_AUTH: // If callback returns MG_FALSE, authentication fails
|
||||||
if (connection->is_websocket) static_cast<MongooseHttpd*>(connection->server_param)->closeWebsocket(connection);
|
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;
|
return MG_TRUE;
|
||||||
|
|
||||||
case MG_AUTH:
|
case MG_HTTP_ERROR: // If callback returns MG_FALSE, Mongoose continues with err
|
||||||
return MG_TRUE; // allow anybody (for now)
|
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;
|
return MG_FALSE;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return MG_FALSE; // keep compiler happy..
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,31 +24,97 @@
|
||||||
#include "HTTPRequest.hxx"
|
#include "HTTPRequest.hxx"
|
||||||
#include "HTTPResponse.hxx"
|
#include "HTTPResponse.hxx"
|
||||||
#include <simgear/structure/SGReferenced.hxx>
|
#include <simgear/structure/SGReferenced.hxx>
|
||||||
|
#include <simgear/structure/SGSharedPtr.hxx>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
|
||||||
namespace flightgear {
|
namespace flightgear {
|
||||||
namespace http {
|
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 {
|
class URIHandler : public SGReferenced {
|
||||||
public:
|
public:
|
||||||
URIHandler( const char * uri ) : _uri(uri) {}
|
URIHandler( const char * uri ) : _uri(uri) {}
|
||||||
virtual ~URIHandler() {}
|
virtual ~URIHandler() {}
|
||||||
|
|
||||||
virtual bool handleRequest( const HTTPRequest & request, HTTPResponse & response ) {
|
/**
|
||||||
if( request.Method == "GET" ) return handleGetRequest( request, response );
|
* This method gets called from the httpd if a request has been detected on the connection
|
||||||
if( request.Method == "PUT" ) return handlePutRequest( request, response );
|
* @param request The HTTP Request filled in by the httpd
|
||||||
return false;
|
* @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; }
|
const std::string & getUri() const { return _uri; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
Loading…
Reference in a new issue