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 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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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";
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)");
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
//
|
||||
|
@ -18,20 +20,410 @@
|
|||
// 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()
|
||||
{
|
||||
response.Header["Content-Type"] = "image/jpeg";
|
||||
// string response = "Screenshot - not yet implemented :-(";
|
||||
// mg_send_data( connection, response.c_str(), response.length() );
|
||||
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)
|
||||
{
|
||||
_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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -43,154 +43,63 @@ namespace 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:
|
||||
MongooseHttpd(SGPropertyNode_ptr);
|
||||
~MongooseHttpd();
|
||||
void init();
|
||||
void bind();
|
||||
void unbind();
|
||||
void shutdown();
|
||||
void update(double dt);
|
||||
|
||||
private:
|
||||
int requestHandler(struct mg_connection *);
|
||||
int websocketHandler(struct mg_connection *);
|
||||
void closeWebsocket(struct mg_connection *);
|
||||
int iterateCallback(struct mg_connection *, mg_event event);
|
||||
|
||||
static int staticRequestHandler(struct mg_connection *, mg_event event);
|
||||
static int staticIterateCallback(struct mg_connection *, mg_event event);
|
||||
|
||||
struct mg_server *_server;
|
||||
SGPropertyNode_ptr _configNode;
|
||||
|
||||
typedef int (MongooseHttpd::*handler_t)(struct mg_connection *);
|
||||
typedef vector<SGSharedPtr<URIHandler> > URIHandlerMap;
|
||||
URIHandlerMap _uriHandlers;
|
||||
|
||||
PropertyChangeObserver _propertyChangeObserver;
|
||||
/**
|
||||
* 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>();
|
||||
}
|
||||
};
|
||||
|
||||
MongooseHttpd::MongooseHttpd(SGPropertyNode_ptr configNode)
|
||||
: _server(NULL), _configNode(configNode)
|
||||
{
|
||||
}
|
||||
|
||||
MongooseHttpd::~MongooseHttpd()
|
||||
{
|
||||
mg_destroy_server(&_server);
|
||||
}
|
||||
|
||||
void MongooseHttpd::init()
|
||||
{
|
||||
SGPropertyNode_ptr n = _configNode->getNode("uri-handler");
|
||||
if (n.valid()) {
|
||||
const char * uri;
|
||||
|
||||
if ((uri = n->getStringValue("screenshot"))[0] != 0) {
|
||||
SG_LOG(SG_NETWORK, SG_INFO, "httpd: adding screenshot uri handler at " << uri);
|
||||
_uriHandlers.push_back(new flightgear::http::ScreenshotUriHandler(uri));
|
||||
}
|
||||
|
||||
if ((uri = n->getStringValue("property"))[0] != 0) {
|
||||
SG_LOG(SG_NETWORK, SG_INFO, "httpd: adding screenshot property handler at " << uri);
|
||||
_uriHandlers.push_back(new flightgear::http::PropertyUriHandler(uri));
|
||||
}
|
||||
|
||||
if ((uri = n->getStringValue("json"))[0] != 0) {
|
||||
SG_LOG(SG_NETWORK, SG_INFO, "httpd: adding json property handler at " << uri);
|
||||
_uriHandlers.push_back(new flightgear::http::JsonUriHandler(uri));
|
||||
}
|
||||
|
||||
if ((uri = n->getStringValue("run"))[0] != 0) {
|
||||
SG_LOG(SG_NETWORK, SG_INFO, "httpd: adding run handler at " << uri);
|
||||
_uriHandlers.push_back(new flightgear::http::RunUriHandler(uri));
|
||||
}
|
||||
|
||||
if ((uri = n->getStringValue("navdb"))[0] != 0) {
|
||||
SG_LOG(SG_NETWORK, SG_INFO, "httpd: adding navdb handler at " << uri);
|
||||
_uriHandlers.push_back(new flightgear::http::NavdbUriHandler(uri));
|
||||
}
|
||||
}
|
||||
|
||||
_server = mg_create_server(this, MongooseHttpd::staticRequestHandler);
|
||||
|
||||
n = _configNode->getNode("options");
|
||||
if (n.valid()) {
|
||||
|
||||
const string fgRoot = fgGetString("/sim/fg-root");
|
||||
string docRoot = n->getStringValue("document-root", fgRoot.c_str() );
|
||||
if (docRoot[0] != '/') docRoot.insert(0, "/").insert(0, fgRoot );
|
||||
|
||||
mg_set_option(_server, "document_root", docRoot.c_str());
|
||||
|
||||
mg_set_option(_server, "listening_port", n->getStringValue("listening-port", "8080"));
|
||||
{
|
||||
// build url rewrites relative to fg-root
|
||||
string rewrites = n->getStringValue( "url-rewrites", "" );
|
||||
string_list rwl = simgear::strutils::split( rewrites, "," );
|
||||
rewrites.clear();
|
||||
for( string_list::iterator it = rwl.begin(); it != rwl.end(); ++it ) {
|
||||
string_list rw_entries = simgear::strutils::split( *it, "=" );
|
||||
if( rw_entries.size() != 2 ) {
|
||||
SG_LOG( SG_NETWORK,SG_WARN, "invalid entry '" << *it << "' in url-rewrites ignored." );
|
||||
continue;
|
||||
}
|
||||
string & lhs = rw_entries[0];
|
||||
string & rhs = rw_entries[1];
|
||||
if( false == rewrites.empty()) rewrites.append(1,',');
|
||||
rewrites.append( lhs ).append(1,'=');
|
||||
if( rhs[0] == '/' ) {
|
||||
rewrites.append( rhs );
|
||||
} else {
|
||||
rewrites.append( fgRoot ).append( 1, '/' ).append( rhs );
|
||||
}
|
||||
}
|
||||
if( false == rewrites.empty() )
|
||||
mg_set_option(_server, "url_rewrites", rewrites.c_str() );
|
||||
}
|
||||
mg_set_option(_server, "enable_directory_listing", n->getStringValue("enable-directory-listing", "yes"));
|
||||
mg_set_option(_server, "idle_timeout_ms", n->getStringValue("idle-timeout-ms", "30000"));
|
||||
mg_set_option(_server, "index_files", n->getStringValue("index-files", "index.html"));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void MongooseHttpd::bind()
|
||||
{
|
||||
}
|
||||
|
||||
void MongooseHttpd::unbind()
|
||||
{
|
||||
mg_destroy_server(&_server);
|
||||
_uriHandlers.clear();
|
||||
_propertyChangeObserver.clear();
|
||||
}
|
||||
|
||||
void MongooseHttpd::shutdown()
|
||||
{
|
||||
}
|
||||
|
||||
void MongooseHttpd::update(double dt)
|
||||
{
|
||||
_propertyChangeObserver.check();
|
||||
mg_poll_server(_server, 0);
|
||||
mg_iterate_over_connections(_server, &MongooseHttpd::staticIterateCallback);
|
||||
_propertyChangeObserver.uncheck();
|
||||
}
|
||||
|
||||
/**
|
||||
* A Helper class to create a HTTPRequest from a mongoose connection struct
|
||||
*/
|
||||
class MongooseHTTPRequest: public HTTPRequest {
|
||||
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 (string::npos == n) return string(cp);
|
||||
return string(cp, n);
|
||||
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);
|
||||
|
@ -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 = "";
|
||||
int max = s.length();
|
||||
|
@ -241,15 +158,146 @@ public:
|
|||
|
||||
};
|
||||
|
||||
void MongooseHttpd::closeWebsocket(struct mg_connection * connection)
|
||||
/**
|
||||
* 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();
|
||||
|
||||
/**
|
||||
* 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 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;
|
||||
};
|
||||
|
||||
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()
|
||||
{
|
||||
Websocket * websocket = static_cast<Websocket*>(connection->connection_param);
|
||||
if ( NULL != websocket) websocket->close();
|
||||
delete websocket;
|
||||
}
|
||||
|
||||
class MongooseWebsocketWriter: public WebsocketWriter {
|
||||
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)
|
||||
{
|
||||
|
@ -257,40 +305,42 @@ public:
|
|||
|
||||
virtual int writeToWebsocket(int opcode, const char * data, size_t len)
|
||||
{
|
||||
return mg_websocket_write( _connection, opcode, data, len );
|
||||
return mg_websocket_write(_connection, opcode, data, len);
|
||||
}
|
||||
private:
|
||||
private:
|
||||
struct mg_connection * _connection;
|
||||
};
|
||||
Websocket * _websocket;
|
||||
};
|
||||
|
||||
int MongooseHttpd::websocketHandler(struct mg_connection * connection)
|
||||
MongooseConnection * MongooseConnection::getConnection(MongooseHttpd * httpd, 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;
|
||||
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 MongooseHttpd::requestHandler(struct mg_connection * connection)
|
||||
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"] = "close";
|
||||
response.Header["Connection"] = "keep-alive";
|
||||
response.Header["Cache-Control"] = "no-cache";
|
||||
{
|
||||
char buf[64];
|
||||
|
@ -299,64 +349,245 @@ int MongooseHttpd::requestHandler(struct mg_connection * connection)
|
|||
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) {
|
||||
// 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());
|
||||
}
|
||||
mg_send_status(connection, response.StatusCode);
|
||||
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 processed ? MG_TRUE : MG_FALSE;
|
||||
return done ? MG_TRUE : MG_MORE;
|
||||
}
|
||||
|
||||
int MongooseHttpd::iterateCallback(struct mg_connection * connection, mg_event event)
|
||||
int RegularConnection::poll(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 );
|
||||
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_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)
|
||||
{
|
||||
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_POLL: // MG_TRUE: finished sending data, MG_MORE, call again
|
||||
return static_cast<MongooseHttpd*>(connection->server_param)->poll(connection);
|
||||
|
||||
case MG_CLOSE:
|
||||
if (connection->is_websocket) static_cast<MongooseHttpd*>(connection->server_param)->closeWebsocket(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..
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -24,31 +24,97 @@
|
|||
#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:
|
||||
|
|
Loading…
Reference in a new issue