From Stephan Huber, RestHttpDevice plugin for support of remote application control via Rest http.
This commit is contained in:
parent
d879cd7715
commit
fa2fb07609
@ -516,6 +516,7 @@ ELSE()
|
||||
FIND_PACKAGE(GtkGl)
|
||||
FIND_PACKAGE(DirectInput)
|
||||
FIND_PACKAGE(NVTT)
|
||||
FIND_PACKAGE(Asio)
|
||||
ENDIF()
|
||||
|
||||
IF(CMAKE_MAJOR_VERSION EQUAL 2 AND CMAKE_MINOR_VERSION LESS 8)
|
||||
|
20
CMakeModules/FindAsio.cmake
Executable file
20
CMakeModules/FindAsio.cmake
Executable file
@ -0,0 +1,20 @@
|
||||
# Locate ASIO-headers (http://think-async.com/Asio)
|
||||
# This module defines
|
||||
# ASIO_FOUND, if false, do not try to link to gdal
|
||||
# ASIO_INCLUDE_DIR, where to find the headers
|
||||
#
|
||||
# Created by Stephan Maximilian Huber
|
||||
|
||||
FIND_PATH(ASIO_INCLUDE_DIR
|
||||
NAMES
|
||||
asio.hpp
|
||||
PATHS
|
||||
/usr/include
|
||||
/usr/local/include
|
||||
)
|
||||
|
||||
SET(ASIO_FOUND "NO")
|
||||
FIND_PACKAGE( Boost 1.37 )
|
||||
IF(Boost_FOUND AND ASIO_INCLUDE_DIR)
|
||||
SET(ASIO_FOUND "YES")
|
||||
ENDIF()
|
@ -28,6 +28,8 @@
|
||||
#include <osgGA/TerrainManipulator>
|
||||
#include <osgGA/SphericalManipulator>
|
||||
|
||||
#include <osgGA/Device>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <osg/GLExtensions>
|
||||
@ -147,6 +149,7 @@ int main(int argc, char** argv)
|
||||
arguments.getApplicationUsage()->addCommandLineOption("--image <filename>","Load an image and render it on a quad");
|
||||
arguments.getApplicationUsage()->addCommandLineOption("--dem <filename>","Load an image/DEM and render it on a HeightField");
|
||||
arguments.getApplicationUsage()->addCommandLineOption("--login <url> <username> <password>","Provide authentication information for http file access.");
|
||||
arguments.getApplicationUsage()->addCommandLineOption("--device <device-name>","add named device to the viewer");
|
||||
|
||||
osgViewer::Viewer viewer(arguments);
|
||||
|
||||
@ -182,6 +185,16 @@ int main(int argc, char** argv)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
std::string device;
|
||||
while(arguments.read("--device", device))
|
||||
{
|
||||
osg::ref_ptr<osgGA::Device> dev = osgDB::readFile<osgGA::Device>(device);
|
||||
if (dev.valid())
|
||||
{
|
||||
viewer.addDevice(dev.get());
|
||||
}
|
||||
}
|
||||
|
||||
// set up the camera manipulators.
|
||||
{
|
||||
|
@ -401,6 +401,36 @@ int main( int argc, char **argv )
|
||||
viewer.readConfiguration(configurationFile);
|
||||
doSetViewer = false;
|
||||
}
|
||||
|
||||
std::string device;
|
||||
while (arguments.read("--device", device))
|
||||
{
|
||||
osg::ref_ptr<osgGA::Device> dev = osgDB::readFile<osgGA::Device>(device);
|
||||
if (dev.valid())
|
||||
{
|
||||
viewer.addDevice(dev.get());
|
||||
}
|
||||
}
|
||||
|
||||
if (arguments.read("--http-control"))
|
||||
{
|
||||
|
||||
std::string server_address = "localhost";
|
||||
std::string server_port = "8080";
|
||||
std::string document_root = "htdocs";
|
||||
|
||||
while (arguments.read("--http-server-address", server_address)) {}
|
||||
while (arguments.read("--http-server-port", server_port)) {}
|
||||
while (arguments.read("--http-document-root", document_root)) {}
|
||||
|
||||
osg::ref_ptr<osgDB::Options> device_options = new osgDB::Options("documentRegisteredHandlers");
|
||||
|
||||
osg::ref_ptr<osgGA::Device> rest_http_device = osgDB::readFile<osgGA::Device>(server_address+":"+server_port+"/"+document_root+".resthttp", device_options);
|
||||
if (rest_http_device.valid())
|
||||
{
|
||||
viewer.addDevice(rest_http_device.get());
|
||||
}
|
||||
}
|
||||
|
||||
// set up stereo masks
|
||||
viewer.getCamera()->setCullMask(0xffffffff);
|
||||
|
@ -264,6 +264,9 @@ IF (SDL_FOUND)
|
||||
ADD_SUBDIRECTORY(sdl)
|
||||
ENDIF(SDL_FOUND)
|
||||
|
||||
IF(ASIO_FOUND)
|
||||
ADD_SUBDIRECTORY(RestHttpDevice)
|
||||
ENDIF(ASIO_FOUND)
|
||||
|
||||
##########to get all the variables of Cmake
|
||||
#GET_CMAKE_PROPERTY(MYVARS VARIABLES)
|
||||
|
32
src/osgPlugins/RestHttpDevice/CMakeLists.txt
Executable file
32
src/osgPlugins/RestHttpDevice/CMakeLists.txt
Executable file
@ -0,0 +1,32 @@
|
||||
INCLUDE_DIRECTORIES(${Boost_INCLUDE_DIRS})
|
||||
INCLUDE_DIRECTORIES(${ASIO_INCLUDE_DIR})
|
||||
|
||||
SET(TARGET_SRC
|
||||
connection.cpp
|
||||
io_service_pool.cpp
|
||||
mime_types.cpp
|
||||
reply.cpp
|
||||
request_handler.cpp
|
||||
request_parser.cpp
|
||||
server.cpp
|
||||
RestHttpDevice.cpp
|
||||
ReaderWriterRestHttpDevice.cpp
|
||||
)
|
||||
|
||||
SET(TARGET_H
|
||||
connection.hpp
|
||||
header.hpp
|
||||
io_service_pool.hpp
|
||||
mime_types.hpp
|
||||
reply.hpp
|
||||
request_handler.hpp
|
||||
request_parser.hpp
|
||||
request.hpp
|
||||
server.hpp
|
||||
RestHttpDevice.hpp
|
||||
)
|
||||
|
||||
SET(TARGET_ADDED_LIBRARIES osgPresentation )
|
||||
|
||||
#### end var setup ###
|
||||
SETUP_PLUGIN(resthttp)
|
126
src/osgPlugins/RestHttpDevice/ReaderWriterRestHttpDevice.cpp
Executable file
126
src/osgPlugins/RestHttpDevice/ReaderWriterRestHttpDevice.cpp
Executable file
@ -0,0 +1,126 @@
|
||||
/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2008 Robert Osfield
|
||||
*
|
||||
* This library is open source and may be redistributed and/or modified under
|
||||
* the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or
|
||||
* (at your option) any later version. The full license is in LICENSE file
|
||||
* included with this distribution, and on the openscenegraph.org website.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* OpenSceneGraph Public License for more details.
|
||||
*/
|
||||
|
||||
|
||||
/* README:
|
||||
*
|
||||
* This code is loosely based on the QTKit implementation of Eric Wing, I removed
|
||||
* some parts and added other parts.
|
||||
*
|
||||
* What's new:
|
||||
* - it can handle URLs currently http and rtsp
|
||||
* - it supports OS X's CoreVideo-technology, this will render the movie-frames
|
||||
* into a bunch of textures. If you load your movie via readImageFile you'll
|
||||
* get the standard behaviour, an ImageStream, where the data gets updated on
|
||||
* every new video-frame. This may be slow.
|
||||
* To get CoreVideo, you'll need to use readObjectFile and cast the result (if any)
|
||||
* to an osg::Texture and use that as your video-texture. If you need access to the
|
||||
* imagestream, just cast getImage to an image-stream. Please note, the data-
|
||||
* property of the image-stream does NOT store the current frame, instead it's empty.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#include <osgDB/Registry>
|
||||
#include <osgDB/FileNameUtils>
|
||||
#include <osgDB/FileUtils>
|
||||
#include "RestHttpDevice.hpp"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class ReaderWriterRestHttp : public osgDB::ReaderWriter
|
||||
{
|
||||
public:
|
||||
|
||||
ReaderWriterRestHttp()
|
||||
{
|
||||
supportsExtension("resthttp", "Virtual Device Integration via a HTTP-Server and a REST-interface");
|
||||
|
||||
supportsOption("documentRoot", "document root of asset files to server via the http-server");
|
||||
supportsOption("serverAddress", "server address to listen for incoming requests");
|
||||
supportsOption("serverPort", "server port to listen for incoming requests");
|
||||
supportsOption("documentRegisteredHandlers", "dump a documentation of all registered REST-handler to the console");
|
||||
|
||||
}
|
||||
|
||||
virtual const char* className() const { return "Rest/HTTP Virtual Device Integration plugin"; }
|
||||
|
||||
virtual ReadResult readObject(const std::string& file, const osgDB::ReaderWriter::Options* options =NULL) const
|
||||
{
|
||||
if (osgDB::getFileExtension(file) == "resthttp")
|
||||
{
|
||||
std::string document_root = options ? options->getPluginStringData("documentRoot") : "htdocs/";
|
||||
std::string server_address = options ? options->getPluginStringData("serverAddress") : "localhost";
|
||||
std::string server_port = options ? options->getPluginStringData("serverPort") : "8080";
|
||||
|
||||
// supported file-name scheme to get address, port and document-root: <server-address>:<server-port>/<document-root>
|
||||
// example: '192.168.1.1:88888/var/www/htdocs'
|
||||
|
||||
std::string file_wo_ext = osgDB::getNameLessAllExtensions(file);
|
||||
if ((file_wo_ext.find('/') != std::string::npos) && (file_wo_ext.find(':') != std::string::npos)) {
|
||||
std::string server_part = file_wo_ext.substr(0, file_wo_ext.find('/'));
|
||||
document_root = file_wo_ext.substr(file_wo_ext.find('/'));
|
||||
server_address = server_part.substr(0,server_part.find(':'));
|
||||
server_port = server_part.substr(server_part.find(':') + 1);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
osg::ref_ptr<RestHttpDevice> device = new RestHttpDevice(server_address, server_port, document_root);
|
||||
|
||||
device->addRequestHandler(new SendKeystrokeRequestHandler("/slide/first", osgGA::GUIEventAdapter::KEY_Home));
|
||||
device->addRequestHandler(new SendKeystrokeRequestHandler("/slide/last", osgGA::GUIEventAdapter::KEY_End));
|
||||
|
||||
device->addRequestHandler(new SendKeystrokeRequestHandler("/slide/next", osgGA::GUIEventAdapter::KEY_Right));
|
||||
device->addRequestHandler(new SendKeystrokeRequestHandler("/slide/previous", osgGA::GUIEventAdapter::KEY_Left));
|
||||
|
||||
device->addRequestHandler(new SendKeystrokeRequestHandler("/layer/next", osgGA::GUIEventAdapter::KEY_Down));
|
||||
device->addRequestHandler(new SendKeystrokeRequestHandler("/layer/previous", osgGA::GUIEventAdapter::KEY_Up));
|
||||
|
||||
device->addRequestHandler(new SendKeystrokeRequestHandler("/slideorlayer/next", osgGA::GUIEventAdapter::KEY_Page_Down));
|
||||
device->addRequestHandler(new SendKeystrokeRequestHandler("/slideorlayer/previous", osgGA::GUIEventAdapter::KEY_Page_Up));
|
||||
|
||||
device->addRequestHandler(new SendKeystrokeRequestHandler("/unpause", 'o'));
|
||||
device->addRequestHandler(new SendKeystrokeRequestHandler("/pause", 'p'));
|
||||
|
||||
|
||||
|
||||
if (options && (options->getPluginStringData("documentRegisteredHandlers") == "true"))
|
||||
{
|
||||
std::cout << *device << std::endl;
|
||||
}
|
||||
return device.release();
|
||||
}
|
||||
catch(std::exception& e)
|
||||
{
|
||||
OSG_WARN << "ReaderWriterRestHttpDevice : could not create http-server! Reason: " << e.what() << std::endl;
|
||||
return ReadResult::ERROR_IN_READING_FILE;
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
OSG_WARN << "ReaderWriterRestHttpDevice : could not create http-server, unknown excpetion thrown " << std::endl;
|
||||
return ReadResult::ERROR_IN_READING_FILE;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return ReadResult::FILE_NOT_HANDLED;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// now register with Registry to instantiate the above
|
||||
// reader/writer.
|
||||
REGISTER_OSGPLUGIN(resthttp, ReaderWriterRestHttp)
|
375
src/osgPlugins/RestHttpDevice/RestHttpDevice.cpp
Executable file
375
src/osgPlugins/RestHttpDevice/RestHttpDevice.cpp
Executable file
@ -0,0 +1,375 @@
|
||||
/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield
|
||||
*
|
||||
* This library is open source and may be redistributed and/or modified under
|
||||
* the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or
|
||||
* (at your option) any later version. The full license is in LICENSE file
|
||||
* included with this distribution, and on the openscenegraph.org website.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* OpenSceneGraph Public License for more details.
|
||||
*/
|
||||
|
||||
#include "RestHttpDevice.hpp"
|
||||
#include <OpenThreads/Thread>
|
||||
#include <osgDB/FileUtils>
|
||||
#include "request_handler.hpp"
|
||||
|
||||
|
||||
class StandardRequestHandler : public RestHttpDevice::RequestHandler {
|
||||
public:
|
||||
StandardRequestHandler() : RestHttpDevice::RequestHandler("") {}
|
||||
virtual bool operator()(const std::string& request_path, const std::string& full_request_path, const Arguments& arguments, http::server::reply& reply)
|
||||
{
|
||||
std::cout << "unhandled request: " << full_request_path << std::endl;
|
||||
for(Arguments::const_iterator i = arguments.begin(); i != arguments.end(); ++i)
|
||||
{
|
||||
std::cout << " " << i->first << ": " << i->second << std::endl;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual void describeTo(std::ostream& out) const
|
||||
{
|
||||
out << getRequestPath() << ": fall-through request-handler, catches all requests w/o registered handler and report them to the console" << std::dec;
|
||||
}
|
||||
};
|
||||
|
||||
class HomeRequestHandler : public RestHttpDevice::RequestHandler {
|
||||
public:
|
||||
HomeRequestHandler()
|
||||
: RestHttpDevice::RequestHandler("/home")
|
||||
{
|
||||
}
|
||||
|
||||
virtual bool operator()(const std::string& request_path, const std::string& full_request_path, const Arguments& arguments, http::server::reply& reply)
|
||||
{
|
||||
double time = getLocalTime(arguments, reply);
|
||||
getDevice()->getEventQueue()->keyPress(' ', time);
|
||||
getDevice()->getEventQueue()->keyRelease(' ', time);
|
||||
|
||||
return sendOkReply(reply);
|
||||
}
|
||||
|
||||
virtual void describeTo(std::ostream& out) const
|
||||
{
|
||||
out << getRequestPath() << ": sets the mouse-input-range arguments: 'x_min','y_min', 'x_max' and 'y_max'" << std::dec;
|
||||
}
|
||||
};
|
||||
|
||||
class SetMouseInputRangeRequestHandler : public RestHttpDevice::RequestHandler {
|
||||
public:
|
||||
SetMouseInputRangeRequestHandler()
|
||||
: RestHttpDevice::RequestHandler("/mouse/set_input_range")
|
||||
{
|
||||
}
|
||||
|
||||
virtual bool operator()(const std::string& request_path, const std::string& full_request_path, const Arguments& arguments, http::server::reply& reply)
|
||||
{
|
||||
int x_min, y_min, x_max, y_max;
|
||||
|
||||
if ( getIntArgument(arguments, "x_min", reply, x_min)
|
||||
&& getIntArgument(arguments, "y_min", reply, y_min)
|
||||
&& getIntArgument(arguments, "x_max", reply, x_max)
|
||||
&& getIntArgument(arguments, "y_max", reply, y_max))
|
||||
{
|
||||
getDevice()->getEventQueue()->setMouseInputRange(x_min, y_min, x_max, y_max);
|
||||
}
|
||||
|
||||
return sendOkReply(reply);
|
||||
}
|
||||
|
||||
virtual void describeTo(std::ostream& out) const
|
||||
{
|
||||
out << getRequestPath() << ": sets the mouse-input-range arguments: 'x_min','y_min', 'x_max' and 'y_max'" << std::dec;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class KeyCodeRequestHandler : public RestHttpDevice::RequestHandler {
|
||||
public:
|
||||
KeyCodeRequestHandler(bool handle_key_press)
|
||||
: RestHttpDevice::RequestHandler(std::string("/key/") + ((handle_key_press) ? "press" : "release"))
|
||||
, _handleKeyPress(handle_key_press)
|
||||
{
|
||||
}
|
||||
|
||||
virtual bool operator()(const std::string& request_path, const std::string& full_request_path, const Arguments& arguments, http::server::reply& reply)
|
||||
{
|
||||
int keycode;
|
||||
|
||||
if (getHexArgument(arguments, "code", reply, keycode))
|
||||
{
|
||||
if (_handleKeyPress)
|
||||
getDevice()->getEventQueue()->keyPress(keycode, getLocalTime(arguments, reply));
|
||||
else
|
||||
getDevice()->getEventQueue()->keyRelease(keycode, getLocalTime(arguments, reply));
|
||||
}
|
||||
|
||||
return sendOkReply(reply);
|
||||
}
|
||||
|
||||
virtual void describeTo(std::ostream& out) const
|
||||
{
|
||||
out << getRequestPath() << ": send KEY_" << (_handleKeyPress ? "DOWN" : "UP") <<", using hex-argument 'code' as keycode" << std::dec;
|
||||
}
|
||||
private:
|
||||
bool _handleKeyPress;
|
||||
};
|
||||
|
||||
|
||||
class MouseMotionRequestHandler : public RestHttpDevice::RequestHandler {
|
||||
public:
|
||||
MouseMotionRequestHandler()
|
||||
: RestHttpDevice::RequestHandler("/mouse/motion")
|
||||
{
|
||||
}
|
||||
|
||||
virtual bool operator()(const std::string& request_path, const std::string& full_request_path, const Arguments& arguments, http::server::reply& reply)
|
||||
{
|
||||
int x,y;
|
||||
if (getIntArgument(arguments, "x", reply, x) && getIntArgument(arguments, "y", reply, y))
|
||||
{
|
||||
double time_stamp = getTimeStamp(arguments, reply);
|
||||
|
||||
if (getDevice()->isNewer(time_stamp))
|
||||
getDevice()->getEventQueue()->mouseMotion(x,y, getLocalTime(time_stamp));
|
||||
}
|
||||
|
||||
return sendOkReply(reply);
|
||||
}
|
||||
|
||||
virtual void describeTo(std::ostream& out) const
|
||||
{
|
||||
out << getRequestPath() << ": send mouse motion using arguments 'x' and 'y' as coordinates" << std::dec;
|
||||
}
|
||||
private:
|
||||
|
||||
};
|
||||
|
||||
|
||||
class MouseButtonRequestHandler : public RestHttpDevice::RequestHandler {
|
||||
public:
|
||||
enum Mode { PRESS, RELEASE, DOUBLE_PRESS};
|
||||
|
||||
MouseButtonRequestHandler(Mode mode)
|
||||
: RestHttpDevice::RequestHandler("")
|
||||
, _mode(mode)
|
||||
{
|
||||
switch(mode) {
|
||||
case PRESS:
|
||||
setRequestPath("/mouse/press");
|
||||
break;
|
||||
case RELEASE:
|
||||
setRequestPath("/mouse/release");
|
||||
break;
|
||||
case DOUBLE_PRESS:
|
||||
setRequestPath("/mouse/doublepress");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
virtual bool operator()(const std::string& request_path, const std::string& full_request_path, const Arguments& arguments, http::server::reply& reply)
|
||||
{
|
||||
int x,y, button;
|
||||
|
||||
if (getIntArgument(arguments, "x", reply, x)
|
||||
&& getIntArgument(arguments, "y", reply, y)
|
||||
&& getIntArgument(arguments, "button", reply, button))
|
||||
{
|
||||
switch (_mode) {
|
||||
case PRESS:
|
||||
getDevice()->getEventQueue()->mouseButtonPress(x,y, button, getLocalTime(arguments, reply));
|
||||
break;
|
||||
case RELEASE:
|
||||
getDevice()->getEventQueue()->mouseButtonRelease(x,y, button, getLocalTime(arguments, reply));
|
||||
break;
|
||||
case DOUBLE_PRESS:
|
||||
getDevice()->getEventQueue()->mouseDoubleButtonPress(x,y, button, getLocalTime(arguments, reply));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return sendOkReply(reply);
|
||||
}
|
||||
|
||||
virtual void describeTo(std::ostream& out) const
|
||||
{
|
||||
out << getRequestPath() << ": send mouse ";
|
||||
switch (_mode) {
|
||||
case PRESS:
|
||||
out << "press"; break;
|
||||
case RELEASE:
|
||||
out << "release"; break;
|
||||
case DOUBLE_PRESS:
|
||||
out << "double press"; break;
|
||||
}
|
||||
out << " using arguments 'x', 'y' and 'button' as coordinates" << std::dec;
|
||||
}
|
||||
private:
|
||||
Mode _mode;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class RequestHandlerDispatcherCallback: public http::server::request_handler::Callback {
|
||||
public:
|
||||
RequestHandlerDispatcherCallback(RestHttpDevice* parent)
|
||||
: http::server::request_handler::Callback()
|
||||
, _parent(parent)
|
||||
{
|
||||
}
|
||||
|
||||
virtual bool operator()(const std::string& request_path, http::server::reply& rep);
|
||||
|
||||
virtual std::string applyTemplateVars(const std::string& txt) { return txt; }
|
||||
private:
|
||||
RestHttpDevice* _parent;
|
||||
};
|
||||
|
||||
|
||||
bool RequestHandlerDispatcherCallback::operator()(const std::string& request_path, http::server::reply& reply)
|
||||
{
|
||||
return _parent->handleRequest(request_path, reply);
|
||||
}
|
||||
|
||||
|
||||
RestHttpDevice::RestHttpDevice(const std::string& listening_address, const std::string& listening_port, const std::string& doc_root)
|
||||
: osgGA::Device()
|
||||
, OpenThreads::Thread()
|
||||
, _server(listening_address, listening_port, osgDB::findDataFile(doc_root), std::max(OpenThreads::GetNumberOfProcessors() - 1, 1))
|
||||
, _serverAddress(listening_address)
|
||||
, _serverPort(listening_port)
|
||||
, _documentRoot(doc_root)
|
||||
, _firstEventLocalTimeStamp()
|
||||
, _firstEventRemoteTimeStamp(-1)
|
||||
, _lastEventRemoteTimeStamp(0)
|
||||
{
|
||||
OSG_INFO << "RestHttpDevice :: listening on " << listening_address << ":" << listening_port << ", document root: " << doc_root << std::endl;
|
||||
|
||||
if (osgDB::findDataFile(doc_root).empty())
|
||||
{
|
||||
OSG_WARN << "RestHttpDevice :: warning, can't locate document-root '" << doc_root << "'for the http-server, starting anyway" << std::endl;
|
||||
}
|
||||
_server.setCallback(new RequestHandlerDispatcherCallback(this));
|
||||
|
||||
addRequestHandler(new KeyCodeRequestHandler(false));
|
||||
addRequestHandler(new KeyCodeRequestHandler(true));
|
||||
|
||||
addRequestHandler(new SetMouseInputRangeRequestHandler());
|
||||
addRequestHandler(new MouseMotionRequestHandler());
|
||||
addRequestHandler(new MouseButtonRequestHandler(MouseButtonRequestHandler::PRESS));
|
||||
addRequestHandler(new MouseButtonRequestHandler(MouseButtonRequestHandler::RELEASE));
|
||||
addRequestHandler(new MouseButtonRequestHandler(MouseButtonRequestHandler::DOUBLE_PRESS));
|
||||
|
||||
addRequestHandler(new HomeRequestHandler());
|
||||
|
||||
addRequestHandler(new StandardRequestHandler());
|
||||
|
||||
// start the thread
|
||||
start();
|
||||
}
|
||||
|
||||
|
||||
void RestHttpDevice::run()
|
||||
{
|
||||
_server.run();
|
||||
}
|
||||
|
||||
|
||||
RestHttpDevice::~RestHttpDevice()
|
||||
{
|
||||
_server.stop();
|
||||
join();
|
||||
}
|
||||
|
||||
|
||||
void RestHttpDevice::addRequestHandler(RequestHandler* handler)
|
||||
{
|
||||
if (handler)
|
||||
{
|
||||
_map.insert(std::make_pair(handler->getRequestPath(), handler));
|
||||
handler->setDevice(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void RestHttpDevice::parseArguments(const std::string request_path, RequestHandler::Arguments& arguments)
|
||||
{
|
||||
std::size_t pos = request_path.find('?');
|
||||
if (pos == std::string::npos)
|
||||
return;
|
||||
|
||||
std::vector<std::string> list;
|
||||
osgDB::split(request_path.substr(pos+1, std::string::npos), list, '&');
|
||||
for(std::vector<std::string>::iterator i = list.begin(); i != list.end(); ++i)
|
||||
{
|
||||
std::vector<std::string> sub_list;
|
||||
osgDB::split(*i, sub_list, '=');
|
||||
if (sub_list.size() == 2)
|
||||
arguments[sub_list[0]] = sub_list[1];
|
||||
else if (sub_list.size() == 1)
|
||||
arguments[sub_list[0]] = "";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool RestHttpDevice::handleRequest(const std::string& in_request_path, http::server::reply& reply)
|
||||
{
|
||||
std::string request_path = in_request_path.substr(0, in_request_path.find('?'));
|
||||
request_path += "/";
|
||||
RequestHandler::Arguments arguments;
|
||||
bool arguments_parsed(false);
|
||||
|
||||
std::size_t pos(std::string::npos);
|
||||
bool handled(false);
|
||||
do {
|
||||
pos = request_path.find_last_of('/', pos-1);
|
||||
if (pos != std::string::npos)
|
||||
{
|
||||
std::string mangled_path = request_path.substr(0, pos);
|
||||
|
||||
std::pair<RequestHandlerMap::iterator,RequestHandlerMap::iterator> range = _map.equal_range(mangled_path);
|
||||
if (!arguments_parsed && (range.first != range.second))
|
||||
{
|
||||
// parse arguments
|
||||
parseArguments(in_request_path, arguments);
|
||||
arguments_parsed = true;
|
||||
}
|
||||
for(RequestHandlerMap::iterator i = range.first; i != range.second; ++i)
|
||||
{
|
||||
if (i->second->operator()(mangled_path, in_request_path, arguments, reply) && !handled)
|
||||
handled = true;
|
||||
}
|
||||
|
||||
}
|
||||
} while ((pos != std::string::npos) && (pos > 0) && !handled);
|
||||
|
||||
return handled;
|
||||
}
|
||||
|
||||
|
||||
void RestHttpDevice::describeTo(std::ostream& out) const
|
||||
{
|
||||
out << "Server: " << _serverAddress << std::endl;
|
||||
out << "Port: " << _serverPort << std::endl;
|
||||
out << "Document-Root: " << _documentRoot << std::endl;
|
||||
out << std::endl;
|
||||
|
||||
for(RequestHandlerMap::const_iterator i = _map.begin(); i != _map.end(); ++i)
|
||||
{
|
||||
const RequestHandler* handler(i->second.get());
|
||||
handler->describeTo(out);
|
||||
out << std::endl;
|
||||
}
|
||||
|
||||
}
|
||||
|
204
src/osgPlugins/RestHttpDevice/RestHttpDevice.hpp
Executable file
204
src/osgPlugins/RestHttpDevice/RestHttpDevice.hpp
Executable file
@ -0,0 +1,204 @@
|
||||
/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield
|
||||
*
|
||||
* This library is open source and may be redistributed and/or modified under
|
||||
* the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or
|
||||
* (at your option) any later version. The full license is in LICENSE file
|
||||
* included with this distribution, and on the openscenegraph.org website.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* OpenSceneGraph Public License for more details.
|
||||
*/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <osg/Referenced>
|
||||
#include <OpenThreads/Thread>
|
||||
#include <osgGA/Device>
|
||||
#include "server.hpp"
|
||||
|
||||
class RestHttpDevice : public osgGA::Device, OpenThreads::Thread {
|
||||
|
||||
public:
|
||||
|
||||
class RequestHandler : public osg::Referenced {
|
||||
public:
|
||||
typedef std::map<std::string, std::string> Arguments;
|
||||
RequestHandler(const std::string& request_path)
|
||||
: osg::Referenced()
|
||||
, _requestPath(request_path)
|
||||
, _device(NULL)
|
||||
{
|
||||
}
|
||||
|
||||
virtual bool operator()(const std::string& request_path, const std::string& full_request_path, const Arguments& arguments, http::server::reply& reply) = 0;
|
||||
|
||||
const std::string& getRequestPath() const { return _requestPath; }
|
||||
|
||||
virtual void describeTo(std::ostream& out) const
|
||||
{
|
||||
out << getRequestPath() << ": no description available";
|
||||
}
|
||||
|
||||
protected:
|
||||
void setDevice(RestHttpDevice* device) { _device = device; }
|
||||
RestHttpDevice* getDevice() { return _device; }
|
||||
|
||||
void reportMissingArgument(const std::string& argument, http::server::reply& reply) const
|
||||
{
|
||||
OSG_WARN << "RequestHandler :: missing argument '" << argument << "' for " << getRequestPath() << std::endl;
|
||||
reply.content = "{ \"result\": 0, \"error\": \"missing argument '"+argument+"'\"}";
|
||||
reply.status = http::server::reply::ok;
|
||||
}
|
||||
|
||||
bool sendOkReply(http::server::reply& reply)
|
||||
{
|
||||
if (reply.content.empty())
|
||||
{
|
||||
reply.status = http::server::reply::no_content;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool getStringArgument(const Arguments& arguments, const std::string& argument, http::server::reply& reply, std::string& result) const
|
||||
{
|
||||
Arguments::const_iterator itr = arguments.find(argument);
|
||||
if (itr == arguments.end()) {
|
||||
reportMissingArgument(argument, reply);
|
||||
return false;
|
||||
}
|
||||
result = itr->second;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool getHexArgument(const Arguments& arguments, const std::string& argument, http::server::reply& reply, int& value) const
|
||||
{
|
||||
std::string hex_str;
|
||||
if (!getStringArgument(arguments, argument, reply, hex_str))
|
||||
return false;
|
||||
value = strtoul(hex_str.c_str(), NULL, 16);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool getIntArgument(const Arguments& arguments, const std::string& argument, http::server::reply& reply, int& value) const
|
||||
{
|
||||
std::string str;
|
||||
if (!getStringArgument(arguments, argument, reply, str))
|
||||
return false;
|
||||
value = strtol(str.c_str(), NULL, 10);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool getDoubleArgument(const Arguments& arguments, const std::string& argument, http::server::reply& reply, double& value) const
|
||||
{
|
||||
std::string str;
|
||||
if (!getStringArgument(arguments, argument, reply, str))
|
||||
return false;
|
||||
value = strtod(str.c_str(), NULL);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// set the request-path, works only from the constructor
|
||||
void setRequestPath(const std::string& request_path) { _requestPath = request_path; }
|
||||
protected:
|
||||
|
||||
double getTimeStamp(const Arguments& arguments, http::server::reply& reply)
|
||||
{
|
||||
double time_stamp;
|
||||
getDoubleArgument(arguments, "time", reply, time_stamp);
|
||||
return time_stamp;
|
||||
}
|
||||
|
||||
double getLocalTime(const Arguments& arguments, http::server::reply& reply)
|
||||
{
|
||||
return getLocalTime(getTimeStamp(arguments, reply));
|
||||
}
|
||||
|
||||
double getLocalTime(double time_stamp)
|
||||
{
|
||||
return getDevice()->getLocalTime(time_stamp);
|
||||
}
|
||||
|
||||
private:
|
||||
std::string _requestPath;
|
||||
RestHttpDevice* _device;
|
||||
friend class RestHttpDevice;
|
||||
};
|
||||
|
||||
|
||||
typedef std::multimap<std::string, osg::ref_ptr<RequestHandler> > RequestHandlerMap;
|
||||
|
||||
RestHttpDevice(const std::string& listening_address, const std::string& listening_port, const std::string& doc_path);
|
||||
~RestHttpDevice();
|
||||
|
||||
void addRequestHandler(RequestHandler* handler);
|
||||
|
||||
bool handleRequest(const std::string& request_path, http::server::reply& reply);
|
||||
|
||||
virtual void run();
|
||||
|
||||
void describeTo(std::ostream& out) const;
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& out, const RestHttpDevice& device)
|
||||
{
|
||||
device.describeTo(out);
|
||||
return out;
|
||||
}
|
||||
|
||||
double getLocalTime(double time_stamp)
|
||||
{
|
||||
if (_firstEventRemoteTimeStamp < 0)
|
||||
{
|
||||
_firstEventLocalTimeStamp = osg::Timer::instance()->time_s();
|
||||
_firstEventRemoteTimeStamp = time_stamp;
|
||||
}
|
||||
|
||||
return _firstEventLocalTimeStamp + (time_stamp - _firstEventRemoteTimeStamp) / 1000.0;;
|
||||
}
|
||||
|
||||
bool isNewer(double time_stamp)
|
||||
{
|
||||
bool is_newer(time_stamp > _lastEventRemoteTimeStamp);
|
||||
if (is_newer)
|
||||
_lastEventRemoteTimeStamp = time_stamp;
|
||||
return is_newer;
|
||||
}
|
||||
|
||||
|
||||
|
||||
virtual void checkEvents() {}
|
||||
|
||||
private:
|
||||
void parseArguments(const std::string request_path, RequestHandler::Arguments& arguments);
|
||||
http::server::server _server;
|
||||
RequestHandlerMap _map;
|
||||
std::string _serverAddress, _serverPort, _documentRoot;
|
||||
double _firstEventLocalTimeStamp;
|
||||
double _firstEventRemoteTimeStamp;
|
||||
double _lastEventRemoteTimeStamp;
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
class SendKeystrokeRequestHandler : public RestHttpDevice::RequestHandler {
|
||||
public:
|
||||
SendKeystrokeRequestHandler(const std::string& request_path, int key) : RestHttpDevice::RequestHandler(request_path), _key(key) {}
|
||||
virtual bool operator()(const std::string& request_path, const std::string& full_request_path, const Arguments& arguments, http::server::reply& reply)
|
||||
{
|
||||
getDevice()->getEventQueue()->keyPress(_key);
|
||||
getDevice()->getEventQueue()->keyRelease(_key);
|
||||
|
||||
return sendOkReply(reply);
|
||||
}
|
||||
|
||||
virtual void describeTo(std::ostream& out) const
|
||||
{
|
||||
out << getRequestPath() << ": send KEY_DOWN + KEY_UP, code: 0x" << std::hex << _key << std::dec;
|
||||
}
|
||||
private:
|
||||
int _key;
|
||||
};
|
||||
|
93
src/osgPlugins/RestHttpDevice/connection.cpp
Executable file
93
src/osgPlugins/RestHttpDevice/connection.cpp
Executable file
@ -0,0 +1,93 @@
|
||||
//
|
||||
// connection.cpp
|
||||
// ~~~~~~~~~~~~~~
|
||||
//
|
||||
// Copyright (c) 2003-2011 Christopher M. Kohlhoff (chris at kohlhoff dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#include "connection.hpp"
|
||||
#include <vector>
|
||||
#include <boost/bind.hpp>
|
||||
#include "request_handler.hpp"
|
||||
|
||||
namespace http {
|
||||
namespace server {
|
||||
|
||||
connection::connection(asio::io_service& io_service,
|
||||
request_handler& handler)
|
||||
: socket_(io_service),
|
||||
request_handler_(handler)
|
||||
{
|
||||
}
|
||||
|
||||
asio::ip::tcp::socket& connection::socket()
|
||||
{
|
||||
return socket_;
|
||||
}
|
||||
|
||||
void connection::start()
|
||||
{
|
||||
socket_.async_read_some(asio::buffer(buffer_),
|
||||
boost::bind(&connection::handle_read, shared_from_this(),
|
||||
asio::placeholders::error,
|
||||
asio::placeholders::bytes_transferred));
|
||||
}
|
||||
|
||||
void connection::handle_read(const asio::error_code& e,
|
||||
std::size_t bytes_transferred)
|
||||
{
|
||||
if (!e)
|
||||
{
|
||||
boost::tribool result;
|
||||
boost::tie(result, boost::tuples::ignore) = request_parser_.parse(
|
||||
request_, buffer_.data(), buffer_.data() + bytes_transferred);
|
||||
|
||||
if (result)
|
||||
{
|
||||
request_handler_.handle_request(request_, reply_);
|
||||
asio::async_write(socket_, reply_.to_buffers(),
|
||||
boost::bind(&connection::handle_write, shared_from_this(),
|
||||
asio::placeholders::error));
|
||||
}
|
||||
else if (!result)
|
||||
{
|
||||
reply_ = reply::stock_reply(reply::bad_request);
|
||||
asio::async_write(socket_, reply_.to_buffers(),
|
||||
boost::bind(&connection::handle_write, shared_from_this(),
|
||||
asio::placeholders::error));
|
||||
}
|
||||
else
|
||||
{
|
||||
socket_.async_read_some(asio::buffer(buffer_),
|
||||
boost::bind(&connection::handle_read, shared_from_this(),
|
||||
asio::placeholders::error,
|
||||
asio::placeholders::bytes_transferred));
|
||||
}
|
||||
}
|
||||
|
||||
// If an error occurs then no new asynchronous operations are started. This
|
||||
// means that all shared_ptr references to the connection object will
|
||||
// disappear and the object will be destroyed automatically after this
|
||||
// handler returns. The connection class's destructor closes the socket.
|
||||
}
|
||||
|
||||
void connection::handle_write(const asio::error_code& e)
|
||||
{
|
||||
if (!e)
|
||||
{
|
||||
// Initiate graceful connection closure.
|
||||
asio::error_code ignored_ec;
|
||||
socket_.shutdown(asio::ip::tcp::socket::shutdown_both, ignored_ec);
|
||||
}
|
||||
|
||||
// No new asynchronous operations are started. This means that all shared_ptr
|
||||
// references to the connection object will disappear and the object will be
|
||||
// destroyed automatically after this handler returns. The connection class's
|
||||
// destructor closes the socket.
|
||||
}
|
||||
|
||||
} // namespace server
|
||||
} // namespace http
|
75
src/osgPlugins/RestHttpDevice/connection.hpp
Executable file
75
src/osgPlugins/RestHttpDevice/connection.hpp
Executable file
@ -0,0 +1,75 @@
|
||||
//
|
||||
// connection.hpp
|
||||
// ~~~~~~~~~~~~~~
|
||||
//
|
||||
// Copyright (c) 2003-2011 Christopher M. Kohlhoff (chris at kohlhoff dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef HTTP_SERVER_CONNECTION_HPP
|
||||
#define HTTP_SERVER_CONNECTION_HPP
|
||||
|
||||
#include <asio.hpp>
|
||||
#include <boost/array.hpp>
|
||||
#include <boost/noncopyable.hpp>
|
||||
#include <boost/shared_ptr.hpp>
|
||||
#include <boost/enable_shared_from_this.hpp>
|
||||
#include "reply.hpp"
|
||||
#include "request.hpp"
|
||||
#include "request_handler.hpp"
|
||||
#include "request_parser.hpp"
|
||||
|
||||
namespace http {
|
||||
namespace server {
|
||||
|
||||
/// Represents a single connection from a client.
|
||||
class connection
|
||||
: public boost::enable_shared_from_this<connection>,
|
||||
private boost::noncopyable
|
||||
{
|
||||
public:
|
||||
/// Construct a connection with the given io_service.
|
||||
explicit connection(asio::io_service& io_service,
|
||||
request_handler& handler);
|
||||
|
||||
/// Get the socket associated with the connection.
|
||||
asio::ip::tcp::socket& socket();
|
||||
|
||||
/// Start the first asynchronous operation for the connection.
|
||||
void start();
|
||||
|
||||
private:
|
||||
/// Handle completion of a read operation.
|
||||
void handle_read(const asio::error_code& e,
|
||||
std::size_t bytes_transferred);
|
||||
|
||||
/// Handle completion of a write operation.
|
||||
void handle_write(const asio::error_code& e);
|
||||
|
||||
/// Socket for the connection.
|
||||
asio::ip::tcp::socket socket_;
|
||||
|
||||
/// The handler used to process the incoming request.
|
||||
request_handler& request_handler_;
|
||||
|
||||
/// Buffer for incoming data.
|
||||
boost::array<char, 8192> buffer_;
|
||||
|
||||
/// The incoming request.
|
||||
request request_;
|
||||
|
||||
/// The parser for the incoming request.
|
||||
request_parser request_parser_;
|
||||
|
||||
/// The reply to be sent back to the client.
|
||||
reply reply_;
|
||||
};
|
||||
|
||||
typedef boost::shared_ptr<connection> connection_ptr;
|
||||
|
||||
} // namespace server
|
||||
} // namespace http
|
||||
|
||||
#endif // HTTP_SERVER_CONNECTION_HPP
|
28
src/osgPlugins/RestHttpDevice/header.hpp
Executable file
28
src/osgPlugins/RestHttpDevice/header.hpp
Executable file
@ -0,0 +1,28 @@
|
||||
//
|
||||
// header.hpp
|
||||
// ~~~~~~~~~~
|
||||
//
|
||||
// Copyright (c) 2003-2011 Christopher M. Kohlhoff (chris at kohlhoff dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef HTTP_SERVER_HEADER_HPP
|
||||
#define HTTP_SERVER_HEADER_HPP
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace http {
|
||||
namespace server {
|
||||
|
||||
struct header
|
||||
{
|
||||
std::string name;
|
||||
std::string value;
|
||||
};
|
||||
|
||||
} // namespace server
|
||||
} // namespace http
|
||||
|
||||
#endif // HTTP_SERVER_HEADER_HPP
|
70
src/osgPlugins/RestHttpDevice/io_service_pool.cpp
Executable file
70
src/osgPlugins/RestHttpDevice/io_service_pool.cpp
Executable file
@ -0,0 +1,70 @@
|
||||
//
|
||||
// io_service_pool.cpp
|
||||
// ~~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
// Copyright (c) 2003-2011 Christopher M. Kohlhoff (chris at kohlhoff dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#include "server.hpp"
|
||||
#include <stdexcept>
|
||||
#include <boost/bind.hpp>
|
||||
#include <boost/shared_ptr.hpp>
|
||||
|
||||
namespace http {
|
||||
namespace server {
|
||||
|
||||
io_service_pool::io_service_pool(std::size_t pool_size)
|
||||
: next_io_service_(0)
|
||||
{
|
||||
if (pool_size == 0)
|
||||
throw std::runtime_error("io_service_pool size is 0");
|
||||
|
||||
// Give all the io_services work to do so that their run() functions will not
|
||||
// exit until they are explicitly stopped.
|
||||
for (std::size_t i = 0; i < pool_size; ++i)
|
||||
{
|
||||
io_service_ptr io_service(new asio::io_service);
|
||||
work_ptr work(new asio::io_service::work(*io_service));
|
||||
io_services_.push_back(io_service);
|
||||
work_.push_back(work);
|
||||
}
|
||||
}
|
||||
|
||||
void io_service_pool::run()
|
||||
{
|
||||
// Create a pool of threads to run all of the io_services.
|
||||
std::vector<boost::shared_ptr<asio::thread> > threads;
|
||||
for (std::size_t i = 0; i < io_services_.size(); ++i)
|
||||
{
|
||||
boost::shared_ptr<asio::thread> thread(new asio::thread(
|
||||
boost::bind(&asio::io_service::run, io_services_[i])));
|
||||
threads.push_back(thread);
|
||||
}
|
||||
|
||||
// Wait for all threads in the pool to exit.
|
||||
for (std::size_t i = 0; i < threads.size(); ++i)
|
||||
threads[i]->join();
|
||||
}
|
||||
|
||||
void io_service_pool::stop()
|
||||
{
|
||||
// Explicitly stop all io_services.
|
||||
for (std::size_t i = 0; i < io_services_.size(); ++i)
|
||||
io_services_[i]->stop();
|
||||
}
|
||||
|
||||
asio::io_service& io_service_pool::get_io_service()
|
||||
{
|
||||
// Use a round-robin scheme to choose the next io_service to use.
|
||||
asio::io_service& io_service = *io_services_[next_io_service_];
|
||||
++next_io_service_;
|
||||
if (next_io_service_ == io_services_.size())
|
||||
next_io_service_ = 0;
|
||||
return io_service;
|
||||
}
|
||||
|
||||
} // namespace server
|
||||
} // namespace http
|
56
src/osgPlugins/RestHttpDevice/io_service_pool.hpp
Executable file
56
src/osgPlugins/RestHttpDevice/io_service_pool.hpp
Executable file
@ -0,0 +1,56 @@
|
||||
//
|
||||
// io_service_pool.hpp
|
||||
// ~~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
// Copyright (c) 2003-2011 Christopher M. Kohlhoff (chris at kohlhoff dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef HTTP_SERVER_IO_SERVICE_POOL_HPP
|
||||
#define HTTP_SERVER_IO_SERVICE_POOL_HPP
|
||||
|
||||
#include <asio.hpp>
|
||||
#include <vector>
|
||||
#include <boost/noncopyable.hpp>
|
||||
#include <boost/shared_ptr.hpp>
|
||||
|
||||
namespace http {
|
||||
namespace server {
|
||||
|
||||
/// A pool of io_service objects.
|
||||
class io_service_pool
|
||||
: private boost::noncopyable
|
||||
{
|
||||
public:
|
||||
/// Construct the io_service pool.
|
||||
explicit io_service_pool(std::size_t pool_size);
|
||||
|
||||
/// Run all io_service objects in the pool.
|
||||
void run();
|
||||
|
||||
/// Stop all io_service objects in the pool.
|
||||
void stop();
|
||||
|
||||
/// Get an io_service to use.
|
||||
asio::io_service& get_io_service();
|
||||
|
||||
private:
|
||||
typedef boost::shared_ptr<asio::io_service> io_service_ptr;
|
||||
typedef boost::shared_ptr<asio::io_service::work> work_ptr;
|
||||
|
||||
/// The pool of io_services.
|
||||
std::vector<io_service_ptr> io_services_;
|
||||
|
||||
/// The work that keeps the io_services running.
|
||||
std::vector<work_ptr> work_;
|
||||
|
||||
/// The next io_service to use for a connection.
|
||||
std::size_t next_io_service_;
|
||||
};
|
||||
|
||||
} // namespace server
|
||||
} // namespace http
|
||||
|
||||
#endif // HTTP_SERVER_IO_SERVICE_POOL_HPP
|
48
src/osgPlugins/RestHttpDevice/mime_types.cpp
Executable file
48
src/osgPlugins/RestHttpDevice/mime_types.cpp
Executable file
@ -0,0 +1,48 @@
|
||||
//
|
||||
// mime_types.cpp
|
||||
// ~~~~~~~~~~~~~~
|
||||
//
|
||||
// Copyright (c) 2003-2011 Christopher M. Kohlhoff (chris at kohlhoff dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#include "mime_types.hpp"
|
||||
|
||||
namespace http {
|
||||
namespace server {
|
||||
namespace mime_types {
|
||||
|
||||
struct mapping
|
||||
{
|
||||
const char* extension;
|
||||
const char* mime_type;
|
||||
} mappings[] =
|
||||
{
|
||||
{ "gif", "image/gif" },
|
||||
{ "htm", "text/html" },
|
||||
{ "html", "text/html" },
|
||||
{ "jpg", "image/jpeg" },
|
||||
{ "png", "image/png" },
|
||||
{ "js", "text/javascript" },
|
||||
{ "css", "text/css" },
|
||||
{ 0, 0 } // Marks end of list.
|
||||
};
|
||||
|
||||
std::string extension_to_type(const std::string& extension)
|
||||
{
|
||||
for (mapping* m = mappings; m->extension; ++m)
|
||||
{
|
||||
if (m->extension == extension)
|
||||
{
|
||||
return m->mime_type;
|
||||
}
|
||||
}
|
||||
|
||||
return "text/plain";
|
||||
}
|
||||
|
||||
} // namespace mime_types
|
||||
} // namespace server
|
||||
} // namespace http
|
27
src/osgPlugins/RestHttpDevice/mime_types.hpp
Executable file
27
src/osgPlugins/RestHttpDevice/mime_types.hpp
Executable file
@ -0,0 +1,27 @@
|
||||
//
|
||||
// mime_types.hpp
|
||||
// ~~~~~~~~~~~~~~
|
||||
//
|
||||
// Copyright (c) 2003-2011 Christopher M. Kohlhoff (chris at kohlhoff dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef HTTP_SERVER_MIME_TYPES_HPP
|
||||
#define HTTP_SERVER_MIME_TYPES_HPP
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace http {
|
||||
namespace server {
|
||||
namespace mime_types {
|
||||
|
||||
/// Convert a file extension into a MIME type.
|
||||
std::string extension_to_type(const std::string& extension);
|
||||
|
||||
} // namespace mime_types
|
||||
} // namespace server
|
||||
} // namespace http
|
||||
|
||||
#endif // HTTP_SERVER_MIME_TYPES_HPP
|
256
src/osgPlugins/RestHttpDevice/reply.cpp
Executable file
256
src/osgPlugins/RestHttpDevice/reply.cpp
Executable file
@ -0,0 +1,256 @@
|
||||
//
|
||||
// reply.cpp
|
||||
// ~~~~~~~~~
|
||||
//
|
||||
// Copyright (c) 2003-2011 Christopher M. Kohlhoff (chris at kohlhoff dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#include "reply.hpp"
|
||||
#include <string>
|
||||
#include <boost/lexical_cast.hpp>
|
||||
|
||||
namespace http {
|
||||
namespace server {
|
||||
|
||||
namespace status_strings {
|
||||
|
||||
const std::string ok =
|
||||
"HTTP/1.0 200 OK\r\n";
|
||||
const std::string created =
|
||||
"HTTP/1.0 201 Created\r\n";
|
||||
const std::string accepted =
|
||||
"HTTP/1.0 202 Accepted\r\n";
|
||||
const std::string no_content =
|
||||
"HTTP/1.0 204 No Content\r\n";
|
||||
const std::string multiple_choices =
|
||||
"HTTP/1.0 300 Multiple Choices\r\n";
|
||||
const std::string moved_permanently =
|
||||
"HTTP/1.0 301 Moved Permanently\r\n";
|
||||
const std::string moved_temporarily =
|
||||
"HTTP/1.0 302 Moved Temporarily\r\n";
|
||||
const std::string not_modified =
|
||||
"HTTP/1.0 304 Not Modified\r\n";
|
||||
const std::string bad_request =
|
||||
"HTTP/1.0 400 Bad Request\r\n";
|
||||
const std::string unauthorized =
|
||||
"HTTP/1.0 401 Unauthorized\r\n";
|
||||
const std::string forbidden =
|
||||
"HTTP/1.0 403 Forbidden\r\n";
|
||||
const std::string not_found =
|
||||
"HTTP/1.0 404 Not Found\r\n";
|
||||
const std::string internal_server_error =
|
||||
"HTTP/1.0 500 Internal Server Error\r\n";
|
||||
const std::string not_implemented =
|
||||
"HTTP/1.0 501 Not Implemented\r\n";
|
||||
const std::string bad_gateway =
|
||||
"HTTP/1.0 502 Bad Gateway\r\n";
|
||||
const std::string service_unavailable =
|
||||
"HTTP/1.0 503 Service Unavailable\r\n";
|
||||
|
||||
asio::const_buffer to_buffer(reply::status_type status)
|
||||
{
|
||||
switch (status)
|
||||
{
|
||||
case reply::ok:
|
||||
return asio::buffer(ok);
|
||||
case reply::created:
|
||||
return asio::buffer(created);
|
||||
case reply::accepted:
|
||||
return asio::buffer(accepted);
|
||||
case reply::no_content:
|
||||
return asio::buffer(no_content);
|
||||
case reply::multiple_choices:
|
||||
return asio::buffer(multiple_choices);
|
||||
case reply::moved_permanently:
|
||||
return asio::buffer(moved_permanently);
|
||||
case reply::moved_temporarily:
|
||||
return asio::buffer(moved_temporarily);
|
||||
case reply::not_modified:
|
||||
return asio::buffer(not_modified);
|
||||
case reply::bad_request:
|
||||
return asio::buffer(bad_request);
|
||||
case reply::unauthorized:
|
||||
return asio::buffer(unauthorized);
|
||||
case reply::forbidden:
|
||||
return asio::buffer(forbidden);
|
||||
case reply::not_found:
|
||||
return asio::buffer(not_found);
|
||||
case reply::internal_server_error:
|
||||
return asio::buffer(internal_server_error);
|
||||
case reply::not_implemented:
|
||||
return asio::buffer(not_implemented);
|
||||
case reply::bad_gateway:
|
||||
return asio::buffer(bad_gateway);
|
||||
case reply::service_unavailable:
|
||||
return asio::buffer(service_unavailable);
|
||||
default:
|
||||
return asio::buffer(internal_server_error);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace status_strings
|
||||
|
||||
namespace misc_strings {
|
||||
|
||||
const char name_value_separator[] = { ':', ' ' };
|
||||
const char crlf[] = { '\r', '\n' };
|
||||
|
||||
} // namespace misc_strings
|
||||
|
||||
std::vector<asio::const_buffer> reply::to_buffers()
|
||||
{
|
||||
std::vector<asio::const_buffer> buffers;
|
||||
buffers.push_back(status_strings::to_buffer(status));
|
||||
for (std::size_t i = 0; i < headers.size(); ++i)
|
||||
{
|
||||
header& h = headers[i];
|
||||
buffers.push_back(asio::buffer(h.name));
|
||||
buffers.push_back(asio::buffer(misc_strings::name_value_separator));
|
||||
buffers.push_back(asio::buffer(h.value));
|
||||
buffers.push_back(asio::buffer(misc_strings::crlf));
|
||||
}
|
||||
buffers.push_back(asio::buffer(misc_strings::crlf));
|
||||
buffers.push_back(asio::buffer(content));
|
||||
return buffers;
|
||||
}
|
||||
|
||||
namespace stock_replies {
|
||||
|
||||
const char ok[] = "";
|
||||
const char created[] =
|
||||
"<html>"
|
||||
"<head><title>Created</title></head>"
|
||||
"<body><h1>201 Created</h1></body>"
|
||||
"</html>";
|
||||
const char accepted[] =
|
||||
"<html>"
|
||||
"<head><title>Accepted</title></head>"
|
||||
"<body><h1>202 Accepted</h1></body>"
|
||||
"</html>";
|
||||
const char no_content[] =
|
||||
"<html>"
|
||||
"<head><title>No Content</title></head>"
|
||||
"<body><h1>204 Content</h1></body>"
|
||||
"</html>";
|
||||
const char multiple_choices[] =
|
||||
"<html>"
|
||||
"<head><title>Multiple Choices</title></head>"
|
||||
"<body><h1>300 Multiple Choices</h1></body>"
|
||||
"</html>";
|
||||
const char moved_permanently[] =
|
||||
"<html>"
|
||||
"<head><title>Moved Permanently</title></head>"
|
||||
"<body><h1>301 Moved Permanently</h1></body>"
|
||||
"</html>";
|
||||
const char moved_temporarily[] =
|
||||
"<html>"
|
||||
"<head><title>Moved Temporarily</title></head>"
|
||||
"<body><h1>302 Moved Temporarily</h1></body>"
|
||||
"</html>";
|
||||
const char not_modified[] =
|
||||
"<html>"
|
||||
"<head><title>Not Modified</title></head>"
|
||||
"<body><h1>304 Not Modified</h1></body>"
|
||||
"</html>";
|
||||
const char bad_request[] =
|
||||
"<html>"
|
||||
"<head><title>Bad Request</title></head>"
|
||||
"<body><h1>400 Bad Request</h1></body>"
|
||||
"</html>";
|
||||
const char unauthorized[] =
|
||||
"<html>"
|
||||
"<head><title>Unauthorized</title></head>"
|
||||
"<body><h1>401 Unauthorized</h1></body>"
|
||||
"</html>";
|
||||
const char forbidden[] =
|
||||
"<html>"
|
||||
"<head><title>Forbidden</title></head>"
|
||||
"<body><h1>403 Forbidden</h1></body>"
|
||||
"</html>";
|
||||
const char not_found[] =
|
||||
"<html>"
|
||||
"<head><title>Not Found</title></head>"
|
||||
"<body><h1>404 Not Found</h1></body>"
|
||||
"</html>";
|
||||
const char internal_server_error[] =
|
||||
"<html>"
|
||||
"<head><title>Internal Server Error</title></head>"
|
||||
"<body><h1>500 Internal Server Error</h1></body>"
|
||||
"</html>";
|
||||
const char not_implemented[] =
|
||||
"<html>"
|
||||
"<head><title>Not Implemented</title></head>"
|
||||
"<body><h1>501 Not Implemented</h1></body>"
|
||||
"</html>";
|
||||
const char bad_gateway[] =
|
||||
"<html>"
|
||||
"<head><title>Bad Gateway</title></head>"
|
||||
"<body><h1>502 Bad Gateway</h1></body>"
|
||||
"</html>";
|
||||
const char service_unavailable[] =
|
||||
"<html>"
|
||||
"<head><title>Service Unavailable</title></head>"
|
||||
"<body><h1>503 Service Unavailable</h1></body>"
|
||||
"</html>";
|
||||
|
||||
std::string to_string(reply::status_type status)
|
||||
{
|
||||
switch (status)
|
||||
{
|
||||
case reply::ok:
|
||||
return ok;
|
||||
case reply::created:
|
||||
return created;
|
||||
case reply::accepted:
|
||||
return accepted;
|
||||
case reply::no_content:
|
||||
return no_content;
|
||||
case reply::multiple_choices:
|
||||
return multiple_choices;
|
||||
case reply::moved_permanently:
|
||||
return moved_permanently;
|
||||
case reply::moved_temporarily:
|
||||
return moved_temporarily;
|
||||
case reply::not_modified:
|
||||
return not_modified;
|
||||
case reply::bad_request:
|
||||
return bad_request;
|
||||
case reply::unauthorized:
|
||||
return unauthorized;
|
||||
case reply::forbidden:
|
||||
return forbidden;
|
||||
case reply::not_found:
|
||||
return not_found;
|
||||
case reply::internal_server_error:
|
||||
return internal_server_error;
|
||||
case reply::not_implemented:
|
||||
return not_implemented;
|
||||
case reply::bad_gateway:
|
||||
return bad_gateway;
|
||||
case reply::service_unavailable:
|
||||
return service_unavailable;
|
||||
default:
|
||||
return internal_server_error;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace stock_replies
|
||||
|
||||
reply reply::stock_reply(reply::status_type status)
|
||||
{
|
||||
reply rep;
|
||||
rep.status = status;
|
||||
rep.content = stock_replies::to_string(status);
|
||||
rep.headers.resize(2);
|
||||
rep.headers[0].name = "Content-Length";
|
||||
rep.headers[0].value = boost::lexical_cast<std::string>(rep.content.size());
|
||||
rep.headers[1].name = "Content-Type";
|
||||
rep.headers[1].value = "text/html";
|
||||
return rep;
|
||||
}
|
||||
|
||||
} // namespace server
|
||||
} // namespace http
|
64
src/osgPlugins/RestHttpDevice/reply.hpp
Executable file
64
src/osgPlugins/RestHttpDevice/reply.hpp
Executable file
@ -0,0 +1,64 @@
|
||||
//
|
||||
// reply.hpp
|
||||
// ~~~~~~~~~
|
||||
//
|
||||
// Copyright (c) 2003-2011 Christopher M. Kohlhoff (chris at kohlhoff dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef HTTP_SERVER_REPLY_HPP
|
||||
#define HTTP_SERVER_REPLY_HPP
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <asio.hpp>
|
||||
#include "header.hpp"
|
||||
|
||||
namespace http {
|
||||
namespace server {
|
||||
|
||||
/// A reply to be sent to a client.
|
||||
struct reply
|
||||
{
|
||||
/// The status of the reply.
|
||||
enum status_type
|
||||
{
|
||||
ok = 200,
|
||||
created = 201,
|
||||
accepted = 202,
|
||||
no_content = 204,
|
||||
multiple_choices = 300,
|
||||
moved_permanently = 301,
|
||||
moved_temporarily = 302,
|
||||
not_modified = 304,
|
||||
bad_request = 400,
|
||||
unauthorized = 401,
|
||||
forbidden = 403,
|
||||
not_found = 404,
|
||||
internal_server_error = 500,
|
||||
not_implemented = 501,
|
||||
bad_gateway = 502,
|
||||
service_unavailable = 503
|
||||
} status;
|
||||
|
||||
/// The headers to be included in the reply.
|
||||
std::vector<header> headers;
|
||||
|
||||
/// The content to be sent in the reply.
|
||||
std::string content;
|
||||
|
||||
/// Convert the reply into a vector of buffers. The buffers do not own the
|
||||
/// underlying memory blocks, therefore the reply object must remain valid and
|
||||
/// not be changed until the write operation has completed.
|
||||
std::vector<asio::const_buffer> to_buffers();
|
||||
|
||||
/// Get a stock reply.
|
||||
static reply stock_reply(status_type status);
|
||||
};
|
||||
|
||||
} // namespace server
|
||||
} // namespace http
|
||||
|
||||
#endif // HTTP_SERVER_REPLY_HPP
|
34
src/osgPlugins/RestHttpDevice/request.hpp
Executable file
34
src/osgPlugins/RestHttpDevice/request.hpp
Executable file
@ -0,0 +1,34 @@
|
||||
//
|
||||
// request.hpp
|
||||
// ~~~~~~~~~~~
|
||||
//
|
||||
// Copyright (c) 2003-2011 Christopher M. Kohlhoff (chris at kohlhoff dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef HTTP_SERVER_REQUEST_HPP
|
||||
#define HTTP_SERVER_REQUEST_HPP
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "header.hpp"
|
||||
|
||||
namespace http {
|
||||
namespace server {
|
||||
|
||||
/// A request received from a client.
|
||||
struct request
|
||||
{
|
||||
std::string method;
|
||||
std::string uri;
|
||||
int http_version_major;
|
||||
int http_version_minor;
|
||||
std::vector<header> headers;
|
||||
};
|
||||
|
||||
} // namespace server
|
||||
} // namespace http
|
||||
|
||||
#endif // HTTP_SERVER_REQUEST_HPP
|
127
src/osgPlugins/RestHttpDevice/request_handler.cpp
Executable file
127
src/osgPlugins/RestHttpDevice/request_handler.cpp
Executable file
@ -0,0 +1,127 @@
|
||||
//
|
||||
// request_handler.cpp
|
||||
// ~~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
// Copyright (c) 2003-2011 Christopher M. Kohlhoff (chris at kohlhoff dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#include "request_handler.hpp"
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include "mime_types.hpp"
|
||||
#include "reply.hpp"
|
||||
#include "request.hpp"
|
||||
|
||||
namespace http {
|
||||
namespace server {
|
||||
|
||||
request_handler::request_handler(const std::string& doc_root)
|
||||
: doc_root_(doc_root)
|
||||
{
|
||||
}
|
||||
|
||||
void request_handler::handle_request(const request& req, reply& rep)
|
||||
{
|
||||
// Decode url to path.
|
||||
std::string request_path;
|
||||
if (!url_decode(req.uri, request_path))
|
||||
{
|
||||
rep = reply::stock_reply(reply::bad_request);
|
||||
return;
|
||||
}
|
||||
|
||||
// Request path must be absolute and not contain "..".
|
||||
if (request_path.empty() || request_path[0] != '/'
|
||||
|| request_path.find("..") != std::string::npos)
|
||||
{
|
||||
rep = reply::stock_reply(reply::bad_request);
|
||||
return;
|
||||
}
|
||||
|
||||
// If path ends in slash (i.e. is a directory) then add "index.html".
|
||||
if (request_path[request_path.size() - 1] == '/')
|
||||
{
|
||||
request_path += "index.html";
|
||||
}
|
||||
|
||||
// Determine the file extension.
|
||||
std::size_t last_slash_pos = request_path.find_last_of("/");
|
||||
std::size_t last_dot_pos = request_path.find_last_of(".");
|
||||
std::string extension;
|
||||
if (last_dot_pos != std::string::npos && last_dot_pos > last_slash_pos)
|
||||
{
|
||||
extension = request_path.substr(last_dot_pos + 1);
|
||||
}
|
||||
|
||||
// Open the file to send back.
|
||||
std::string full_path = doc_root_ + request_path;
|
||||
std::ifstream is(full_path.c_str(), std::ios::in | std::ios::binary);
|
||||
if (!is )
|
||||
{
|
||||
if (!_cb.valid() || (_cb->operator()(request_path, rep) == false)) {
|
||||
rep = reply::stock_reply(reply::not_found);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Fill out the reply to be sent to the client.
|
||||
rep.status = reply::ok;
|
||||
char buf[512];
|
||||
while (is.read(buf, sizeof(buf)).gcount() > 0)
|
||||
rep.content.append(buf, is.gcount());
|
||||
|
||||
if (_cb.valid() && (mime_types::extension_to_type(extension) == "text/html"))
|
||||
rep.content = _cb->applyTemplateVars(rep.content);
|
||||
rep.headers.resize(2);
|
||||
rep.headers[0].name = "Content-Length";
|
||||
rep.headers[0].value = boost::lexical_cast<std::string>(rep.content.size());
|
||||
rep.headers[1].name = "Content-Type";
|
||||
rep.headers[1].value = mime_types::extension_to_type(extension);
|
||||
}
|
||||
|
||||
bool request_handler::url_decode(const std::string& in, std::string& out)
|
||||
{
|
||||
out.clear();
|
||||
out.reserve(in.size());
|
||||
for (std::size_t i = 0; i < in.size(); ++i)
|
||||
{
|
||||
if (in[i] == '%')
|
||||
{
|
||||
if (i + 3 <= in.size())
|
||||
{
|
||||
int value = 0;
|
||||
std::istringstream is(in.substr(i + 1, 2));
|
||||
if (is >> std::hex >> value)
|
||||
{
|
||||
out += static_cast<char>(value);
|
||||
i += 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (in[i] == '+')
|
||||
{
|
||||
out += ' ';
|
||||
}
|
||||
else
|
||||
{
|
||||
out += in[i];
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace server
|
||||
} // namespace http
|
59
src/osgPlugins/RestHttpDevice/request_handler.hpp
Executable file
59
src/osgPlugins/RestHttpDevice/request_handler.hpp
Executable file
@ -0,0 +1,59 @@
|
||||
//
|
||||
// request_handler.hpp
|
||||
// ~~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
// Copyright (c) 2003-2011 Christopher M. Kohlhoff (chris at kohlhoff dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef HTTP_SERVER_REQUEST_HANDLER_HPP
|
||||
#define HTTP_SERVER_REQUEST_HANDLER_HPP
|
||||
|
||||
#include <string>
|
||||
#include <boost/noncopyable.hpp>
|
||||
|
||||
#include <osg/observer_ptr>
|
||||
|
||||
namespace http {
|
||||
namespace server {
|
||||
|
||||
struct reply;
|
||||
struct request;
|
||||
|
||||
/// The common handler for all incoming requests.
|
||||
class request_handler
|
||||
: private boost::noncopyable
|
||||
{
|
||||
public:
|
||||
class Callback : public osg::Referenced {
|
||||
public:
|
||||
Callback() : osg::Referenced() {}
|
||||
|
||||
virtual bool operator()(const std::string& request_path, reply& rep) = 0;
|
||||
virtual std::string applyTemplateVars(const std::string& txt) = 0;
|
||||
};
|
||||
|
||||
/// Construct with a directory containing files to be served.
|
||||
explicit request_handler(const std::string& doc_root);
|
||||
|
||||
/// Handle a request and produce a reply.
|
||||
void handle_request(const request& req, reply& rep);
|
||||
|
||||
void setCallback(Callback* cb) { _cb = cb; }
|
||||
|
||||
private:
|
||||
/// The directory containing the files to be served.
|
||||
std::string doc_root_;
|
||||
|
||||
/// Perform URL-decoding on a string. Returns false if the encoding was
|
||||
/// invalid.
|
||||
static bool url_decode(const std::string& in, std::string& out);
|
||||
osg::observer_ptr<Callback> _cb;
|
||||
};
|
||||
|
||||
} // namespace server
|
||||
} // namespace http
|
||||
|
||||
#endif // HTTP_SERVER_REQUEST_HANDLER_HPP
|
326
src/osgPlugins/RestHttpDevice/request_parser.cpp
Executable file
326
src/osgPlugins/RestHttpDevice/request_parser.cpp
Executable file
@ -0,0 +1,326 @@
|
||||
//
|
||||
// request_parser.cpp
|
||||
// ~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
// Copyright (c) 2003-2011 Christopher M. Kohlhoff (chris at kohlhoff dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#include "request_parser.hpp"
|
||||
#include "request.hpp"
|
||||
|
||||
namespace http {
|
||||
namespace server {
|
||||
|
||||
request_parser::request_parser()
|
||||
: state_(method_start)
|
||||
{
|
||||
}
|
||||
|
||||
void request_parser::reset()
|
||||
{
|
||||
state_ = method_start;
|
||||
}
|
||||
|
||||
boost::tribool request_parser::consume(request& req, char input)
|
||||
{
|
||||
switch (state_)
|
||||
{
|
||||
case method_start:
|
||||
if (!is_char(input) || is_ctl(input) || is_tspecial(input))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
state_ = method;
|
||||
req.method.push_back(input);
|
||||
return boost::indeterminate;
|
||||
}
|
||||
case method:
|
||||
if (input == ' ')
|
||||
{
|
||||
state_ = uri;
|
||||
return boost::indeterminate;
|
||||
}
|
||||
else if (!is_char(input) || is_ctl(input) || is_tspecial(input))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
req.method.push_back(input);
|
||||
return boost::indeterminate;
|
||||
}
|
||||
case uri_start:
|
||||
if (is_ctl(input))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
state_ = uri;
|
||||
req.uri.push_back(input);
|
||||
return boost::indeterminate;
|
||||
}
|
||||
case uri:
|
||||
if (input == ' ')
|
||||
{
|
||||
state_ = http_version_h;
|
||||
return boost::indeterminate;
|
||||
}
|
||||
else if (is_ctl(input))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
req.uri.push_back(input);
|
||||
return boost::indeterminate;
|
||||
}
|
||||
case http_version_h:
|
||||
if (input == 'H')
|
||||
{
|
||||
state_ = http_version_t_1;
|
||||
return boost::indeterminate;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
case http_version_t_1:
|
||||
if (input == 'T')
|
||||
{
|
||||
state_ = http_version_t_2;
|
||||
return boost::indeterminate;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
case http_version_t_2:
|
||||
if (input == 'T')
|
||||
{
|
||||
state_ = http_version_p;
|
||||
return boost::indeterminate;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
case http_version_p:
|
||||
if (input == 'P')
|
||||
{
|
||||
state_ = http_version_slash;
|
||||
return boost::indeterminate;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
case http_version_slash:
|
||||
if (input == '/')
|
||||
{
|
||||
req.http_version_major = 0;
|
||||
req.http_version_minor = 0;
|
||||
state_ = http_version_major_start;
|
||||
return boost::indeterminate;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
case http_version_major_start:
|
||||
if (is_digit(input))
|
||||
{
|
||||
req.http_version_major = req.http_version_major * 10 + input - '0';
|
||||
state_ = http_version_major;
|
||||
return boost::indeterminate;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
case http_version_major:
|
||||
if (input == '.')
|
||||
{
|
||||
state_ = http_version_minor_start;
|
||||
return boost::indeterminate;
|
||||
}
|
||||
else if (is_digit(input))
|
||||
{
|
||||
req.http_version_major = req.http_version_major * 10 + input - '0';
|
||||
return boost::indeterminate;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
case http_version_minor_start:
|
||||
if (is_digit(input))
|
||||
{
|
||||
req.http_version_minor = req.http_version_minor * 10 + input - '0';
|
||||
state_ = http_version_minor;
|
||||
return boost::indeterminate;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
case http_version_minor:
|
||||
if (input == '\r')
|
||||
{
|
||||
state_ = expecting_newline_1;
|
||||
return boost::indeterminate;
|
||||
}
|
||||
else if (is_digit(input))
|
||||
{
|
||||
req.http_version_minor = req.http_version_minor * 10 + input - '0';
|
||||
return boost::indeterminate;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
case expecting_newline_1:
|
||||
if (input == '\n')
|
||||
{
|
||||
state_ = header_line_start;
|
||||
return boost::indeterminate;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
case header_line_start:
|
||||
if (input == '\r')
|
||||
{
|
||||
state_ = expecting_newline_3;
|
||||
return boost::indeterminate;
|
||||
}
|
||||
else if (!req.headers.empty() && (input == ' ' || input == '\t'))
|
||||
{
|
||||
state_ = header_lws;
|
||||
return boost::indeterminate;
|
||||
}
|
||||
else if (!is_char(input) || is_ctl(input) || is_tspecial(input))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
req.headers.push_back(header());
|
||||
req.headers.back().name.push_back(input);
|
||||
state_ = header_name;
|
||||
return boost::indeterminate;
|
||||
}
|
||||
case header_lws:
|
||||
if (input == '\r')
|
||||
{
|
||||
state_ = expecting_newline_2;
|
||||
return boost::indeterminate;
|
||||
}
|
||||
else if (input == ' ' || input == '\t')
|
||||
{
|
||||
return boost::indeterminate;
|
||||
}
|
||||
else if (is_ctl(input))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
state_ = header_value;
|
||||
req.headers.back().value.push_back(input);
|
||||
return boost::indeterminate;
|
||||
}
|
||||
case header_name:
|
||||
if (input == ':')
|
||||
{
|
||||
state_ = space_before_header_value;
|
||||
return boost::indeterminate;
|
||||
}
|
||||
else if (!is_char(input) || is_ctl(input) || is_tspecial(input))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
req.headers.back().name.push_back(input);
|
||||
return boost::indeterminate;
|
||||
}
|
||||
case space_before_header_value:
|
||||
if (input == ' ')
|
||||
{
|
||||
state_ = header_value;
|
||||
return boost::indeterminate;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
case header_value:
|
||||
if (input == '\r')
|
||||
{
|
||||
state_ = expecting_newline_2;
|
||||
return boost::indeterminate;
|
||||
}
|
||||
else if (is_ctl(input))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
req.headers.back().value.push_back(input);
|
||||
return boost::indeterminate;
|
||||
}
|
||||
case expecting_newline_2:
|
||||
if (input == '\n')
|
||||
{
|
||||
state_ = header_line_start;
|
||||
return boost::indeterminate;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
case expecting_newline_3:
|
||||
return (input == '\n');
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool request_parser::is_char(int c)
|
||||
{
|
||||
return c >= 0 && c <= 127;
|
||||
}
|
||||
|
||||
bool request_parser::is_ctl(int c)
|
||||
{
|
||||
return (c >= 0 && c <= 31) || (c == 127);
|
||||
}
|
||||
|
||||
bool request_parser::is_tspecial(int c)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case '(': case ')': case '<': case '>': case '@':
|
||||
case ',': case ';': case ':': case '\\': case '"':
|
||||
case '/': case '[': case ']': case '?': case '=':
|
||||
case '{': case '}': case ' ': case '\t':
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool request_parser::is_digit(int c)
|
||||
{
|
||||
return c >= '0' && c <= '9';
|
||||
}
|
||||
|
||||
} // namespace server
|
||||
} // namespace http
|
96
src/osgPlugins/RestHttpDevice/request_parser.hpp
Executable file
96
src/osgPlugins/RestHttpDevice/request_parser.hpp
Executable file
@ -0,0 +1,96 @@
|
||||
//
|
||||
// request_parser.hpp
|
||||
// ~~~~~~~~~~~~~~~~~~
|
||||
//
|
||||
// Copyright (c) 2003-2011 Christopher M. Kohlhoff (chris at kohlhoff dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef HTTP_SERVER_REQUEST_PARSER_HPP
|
||||
#define HTTP_SERVER_REQUEST_PARSER_HPP
|
||||
|
||||
#include <boost/logic/tribool.hpp>
|
||||
#include <boost/tuple/tuple.hpp>
|
||||
|
||||
namespace http {
|
||||
namespace server {
|
||||
|
||||
struct request;
|
||||
|
||||
/// Parser for incoming requests.
|
||||
class request_parser
|
||||
{
|
||||
public:
|
||||
/// Construct ready to parse the request method.
|
||||
request_parser();
|
||||
|
||||
/// Reset to initial parser state.
|
||||
void reset();
|
||||
|
||||
/// Parse some data. The tribool return value is true when a complete request
|
||||
/// has been parsed, false if the data is invalid, indeterminate when more
|
||||
/// data is required. The InputIterator return value indicates how much of the
|
||||
/// input has been consumed.
|
||||
template <typename InputIterator>
|
||||
boost::tuple<boost::tribool, InputIterator> parse(request& req,
|
||||
InputIterator begin, InputIterator end)
|
||||
{
|
||||
while (begin != end)
|
||||
{
|
||||
boost::tribool result = consume(req, *begin++);
|
||||
if (result || !result)
|
||||
return boost::make_tuple(result, begin);
|
||||
}
|
||||
boost::tribool result = boost::indeterminate;
|
||||
return boost::make_tuple(result, begin);
|
||||
}
|
||||
|
||||
private:
|
||||
/// Handle the next character of input.
|
||||
boost::tribool consume(request& req, char input);
|
||||
|
||||
/// Check if a byte is an HTTP character.
|
||||
static bool is_char(int c);
|
||||
|
||||
/// Check if a byte is an HTTP control character.
|
||||
static bool is_ctl(int c);
|
||||
|
||||
/// Check if a byte is defined as an HTTP tspecial character.
|
||||
static bool is_tspecial(int c);
|
||||
|
||||
/// Check if a byte is a digit.
|
||||
static bool is_digit(int c);
|
||||
|
||||
/// The current state of the parser.
|
||||
enum state
|
||||
{
|
||||
method_start,
|
||||
method,
|
||||
uri_start,
|
||||
uri,
|
||||
http_version_h,
|
||||
http_version_t_1,
|
||||
http_version_t_2,
|
||||
http_version_p,
|
||||
http_version_slash,
|
||||
http_version_major_start,
|
||||
http_version_major,
|
||||
http_version_minor_start,
|
||||
http_version_minor,
|
||||
expecting_newline_1,
|
||||
header_line_start,
|
||||
header_lws,
|
||||
header_name,
|
||||
space_before_header_value,
|
||||
header_value,
|
||||
expecting_newline_2,
|
||||
expecting_newline_3
|
||||
} state_;
|
||||
};
|
||||
|
||||
} // namespace server
|
||||
} // namespace http
|
||||
|
||||
#endif // HTTP_SERVER_REQUEST_PARSER_HPP
|
62
src/osgPlugins/RestHttpDevice/server.cpp
Executable file
62
src/osgPlugins/RestHttpDevice/server.cpp
Executable file
@ -0,0 +1,62 @@
|
||||
//
|
||||
// server.cpp
|
||||
// ~~~~~~~~~~
|
||||
//
|
||||
// Copyright (c) 2003-2011 Christopher M. Kohlhoff (chris at kohlhoff dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#include "server.hpp"
|
||||
#include <boost/bind.hpp>
|
||||
|
||||
namespace http {
|
||||
namespace server {
|
||||
|
||||
server::server(const std::string& address, const std::string& port,
|
||||
const std::string& doc_root, std::size_t io_service_pool_size)
|
||||
: io_service_pool_(io_service_pool_size),
|
||||
acceptor_(io_service_pool_.get_io_service()),
|
||||
new_connection_(new connection(
|
||||
io_service_pool_.get_io_service(), request_handler_)),
|
||||
request_handler_(doc_root)
|
||||
{
|
||||
// Open the acceptor with the option to reuse the address (i.e. SO_REUSEADDR).
|
||||
asio::ip::tcp::resolver resolver(acceptor_.get_io_service());
|
||||
asio::ip::tcp::resolver::query query(address, port);
|
||||
asio::ip::tcp::endpoint endpoint = *resolver.resolve(query);
|
||||
acceptor_.open(endpoint.protocol());
|
||||
acceptor_.set_option(asio::ip::tcp::acceptor::reuse_address(true));
|
||||
acceptor_.bind(endpoint);
|
||||
acceptor_.listen();
|
||||
acceptor_.async_accept(new_connection_->socket(),
|
||||
boost::bind(&server::handle_accept, this,
|
||||
asio::placeholders::error));
|
||||
}
|
||||
|
||||
void server::run()
|
||||
{
|
||||
io_service_pool_.run();
|
||||
}
|
||||
|
||||
void server::stop()
|
||||
{
|
||||
io_service_pool_.stop();
|
||||
}
|
||||
|
||||
void server::handle_accept(const asio::error_code& e)
|
||||
{
|
||||
if (!e)
|
||||
{
|
||||
new_connection_->start();
|
||||
new_connection_.reset(new connection(
|
||||
io_service_pool_.get_io_service(), request_handler_));
|
||||
acceptor_.async_accept(new_connection_->socket(),
|
||||
boost::bind(&server::handle_accept, this,
|
||||
asio::placeholders::error));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace server
|
||||
} // namespace http
|
64
src/osgPlugins/RestHttpDevice/server.hpp
Executable file
64
src/osgPlugins/RestHttpDevice/server.hpp
Executable file
@ -0,0 +1,64 @@
|
||||
//
|
||||
// server.hpp
|
||||
// ~~~~~~~~~~
|
||||
//
|
||||
// Copyright (c) 2003-2011 Christopher M. Kohlhoff (chris at kohlhoff dot com)
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef HTTP_SERVER_SERVER_HPP
|
||||
#define HTTP_SERVER_SERVER_HPP
|
||||
|
||||
#include <asio.hpp>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <boost/noncopyable.hpp>
|
||||
#include <boost/shared_ptr.hpp>
|
||||
#include "connection.hpp"
|
||||
#include "io_service_pool.hpp"
|
||||
#include "request_handler.hpp"
|
||||
|
||||
namespace http {
|
||||
namespace server {
|
||||
|
||||
/// The top-level class of the HTTP server.
|
||||
class server
|
||||
: private boost::noncopyable
|
||||
{
|
||||
public:
|
||||
/// Construct the server to listen on the specified TCP address and port, and
|
||||
/// serve up files from the given directory.
|
||||
explicit server(const std::string& address, const std::string& port,
|
||||
const std::string& doc_root, std::size_t io_service_pool_size);
|
||||
|
||||
/// Run the server's io_service loop.
|
||||
void run();
|
||||
|
||||
/// Stop the server.
|
||||
void stop();
|
||||
|
||||
void setCallback(request_handler::Callback* cb) { request_handler_.setCallback(cb); }
|
||||
|
||||
private:
|
||||
/// Handle completion of an asynchronous accept operation.
|
||||
void handle_accept(const asio::error_code& e);
|
||||
|
||||
/// The pool of io_service objects used to perform asynchronous operations.
|
||||
io_service_pool io_service_pool_;
|
||||
|
||||
/// Acceptor used to listen for incoming connections.
|
||||
asio::ip::tcp::acceptor acceptor_;
|
||||
|
||||
/// The next connection to be accepted.
|
||||
connection_ptr new_connection_;
|
||||
|
||||
/// The handler for all incoming requests.
|
||||
request_handler request_handler_;
|
||||
};
|
||||
|
||||
} // namespace server
|
||||
} // namespace http
|
||||
|
||||
#endif // HTTP_SERVER_SERVER_HPP
|
Loading…
Reference in New Issue
Block a user