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(GtkGl)
|
||||||
FIND_PACKAGE(DirectInput)
|
FIND_PACKAGE(DirectInput)
|
||||||
FIND_PACKAGE(NVTT)
|
FIND_PACKAGE(NVTT)
|
||||||
|
FIND_PACKAGE(Asio)
|
||||||
ENDIF()
|
ENDIF()
|
||||||
|
|
||||||
IF(CMAKE_MAJOR_VERSION EQUAL 2 AND CMAKE_MINOR_VERSION LESS 8)
|
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/TerrainManipulator>
|
||||||
#include <osgGA/SphericalManipulator>
|
#include <osgGA/SphericalManipulator>
|
||||||
|
|
||||||
|
#include <osgGA/Device>
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
#include <osg/GLExtensions>
|
#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("--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("--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("--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);
|
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.
|
// set up the camera manipulators.
|
||||||
{
|
{
|
||||||
|
@ -401,6 +401,36 @@ int main( int argc, char **argv )
|
|||||||
viewer.readConfiguration(configurationFile);
|
viewer.readConfiguration(configurationFile);
|
||||||
doSetViewer = false;
|
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
|
// set up stereo masks
|
||||||
viewer.getCamera()->setCullMask(0xffffffff);
|
viewer.getCamera()->setCullMask(0xffffffff);
|
||||||
|
@ -264,6 +264,9 @@ IF (SDL_FOUND)
|
|||||||
ADD_SUBDIRECTORY(sdl)
|
ADD_SUBDIRECTORY(sdl)
|
||||||
ENDIF(SDL_FOUND)
|
ENDIF(SDL_FOUND)
|
||||||
|
|
||||||
|
IF(ASIO_FOUND)
|
||||||
|
ADD_SUBDIRECTORY(RestHttpDevice)
|
||||||
|
ENDIF(ASIO_FOUND)
|
||||||
|
|
||||||
##########to get all the variables of Cmake
|
##########to get all the variables of Cmake
|
||||||
#GET_CMAKE_PROPERTY(MYVARS VARIABLES)
|
#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