From Stephan Huber, RestHttpDevice plugin for support of remote application control via Rest http.

This commit is contained in:
Robert Osfield 2012-10-30 12:31:27 +00:00
parent d879cd7715
commit fa2fb07609
25 changed files with 2289 additions and 0 deletions

View File

@ -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
View 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()

View File

@ -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.
{

View File

@ -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);

View File

@ -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)

View 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)

View 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)

View 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;
}
}

View 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;
};

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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