├── .travis.yml ├── AUTHORS.md ├── CHANGELOG.rst ├── CMakeLists.txt ├── LICENSE ├── README.md ├── include └── async_web_server_cpp │ ├── http_connection.hpp │ ├── http_header.hpp │ ├── http_reply.hpp │ ├── http_request.hpp │ ├── http_request_handler.hpp │ ├── http_request_parser.hpp │ ├── http_server.hpp │ ├── websocket_connection.hpp │ ├── websocket_message.hpp │ └── websocket_request_handler.hpp ├── package.xml ├── src ├── http_connection.cpp ├── http_reply.cpp ├── http_request.cpp ├── http_request_handler.cpp ├── http_request_parser.cpp ├── http_server.cpp ├── websocket_connection.cpp ├── websocket_message.cpp └── websocket_request_handler.cpp └── test ├── CMakeLists.txt ├── simple_http_requests_test.py ├── test.html ├── test_dir └── test_file.txt ├── test_web_server.cpp ├── tests.test └── websocket_test.py /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | dist: trusty 3 | language: generic 4 | compiler: 5 | - gcc 6 | env: 7 | global: 8 | - CATKIN_WS=~/catkin_ws 9 | - CATKIN_WS_SRC=${CATKIN_WS}/src 10 | matrix: 11 | - CI_ROS_DISTRO="indigo" 12 | - CI_ROS_DISTRO="jade" 13 | 14 | branches: 15 | only: 16 | - master 17 | - develop 18 | 19 | install: 20 | - sudo sh -c 'echo "deb http://packages.ros.org/ros/ubuntu trusty main" > /etc/apt/sources.list.d/ros-latest.list' 21 | - wget http://packages.ros.org/ros.key -O - | sudo apt-key add - 22 | - sudo apt-get update -qq 23 | - sudo apt-get install -qq -y python-rosdep python-catkin-tools 24 | - sudo rosdep init 25 | - rosdep update 26 | - rosdep install --from-paths ./ -i -y --rosdistro $CI_ROS_DISTRO 27 | 28 | script: 29 | - source /opt/ros/$CI_ROS_DISTRO/setup.bash 30 | - mkdir -p $CATKIN_WS_SRC 31 | - ln -s $TRAVIS_BUILD_DIR $CATKIN_WS_SRC 32 | - cd $CATKIN_WS 33 | - catkin init 34 | - catkin config --install 35 | - catkin build --limit-status-rate 0.1 --no-notify -DCMAKE_BUILD_TYPE=Release 36 | - catkin build --limit-status-rate 0.1 --no-notify --make-args tests 37 | - catkin run_tests 38 | -------------------------------------------------------------------------------- /AUTHORS.md: -------------------------------------------------------------------------------- 1 | Original Authors 2 | ---------------- 3 | 4 | * Mitchell Wills (mwills@wpi.edu) 5 | 6 | Contributors 7 | ------------ 8 | 9 | * [Russell Toris](https://github.com/rctoris/) (russell.toris@gmail.com) 10 | -------------------------------------------------------------------------------- /CHANGELOG.rst: -------------------------------------------------------------------------------- 1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 2 | Changelog for package async_web_server_cpp 3 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 4 | 5 | 0.0.3 (2015-08-18) 6 | ------------------ 7 | * Merge pull request #6 from mitchellwills/develop 8 | Added some tests 9 | * Added filesystem tests 10 | * Added some more tests (including websocket tests) 11 | * Added some more http tests 12 | * Added an echo test and enabled tests on travis 13 | * Began working on some tests 14 | * Fixed missing boost dep 15 | * Merge pull request #5 from mitchellwills/develop 16 | Some more improvements 17 | * Added filesystem request handler 18 | This serves both files from a given root and directory listings (if requested) 19 | * Modified request handler signature so that it can reject requests (causing them to be pushed to the next handler in a handler group) 20 | * Now load a file to be served each time it is requested 21 | * Fix HTTP Server stop to allow it to be safe to call multiple times 22 | * Merge pull request #4 from mitchellwills/develop 23 | A few small improvements 24 | * Allow for a server to be created even if the system has no non-local IP 25 | See http://stackoverflow.com/questions/5971242/how-does-boost-asios-hostname-resolution-work-on-linux-is-it-possible-to-use-n 26 | * Changed write resource to be a const shared ptr 27 | * Contributors: Mitchell Wills, Russell Toris 28 | 29 | 0.0.2 (2015-01-06) 30 | ------------------ 31 | * Merge pull request #3 from mitchellwills/develop 32 | Added support for specifying additional headers when creating static request handlers 33 | * Added some comments to the HTTP reply methods 34 | * Added support for specifying additional headers when creating static request handlers 35 | * Contributors: Mitchell Wills, Russell Toris 36 | 37 | 0.0.1 (2014-12-02) 38 | ------------------ 39 | * OCD clenup 40 | * Merge pull request #2 from mitchellwills/develop 41 | Few small fixes 42 | * Fixed message processing so close message is actually passed through 43 | * Fixed potential memory leak from boost shared_ptr misuse 44 | * Merge pull request #1 from mitchellwills/develop 45 | Import of initial implementation from webrtc_ros repo 46 | * Fixed install directives 47 | * Added some documentation 48 | * Added more metadata files 49 | * Cleaned up file formatting 50 | * Fixed compilation error on 12.04 51 | * Added boost dependancy 52 | * Added travis configuration 53 | * Package cleanup 54 | * renamed package to async_web_server_cpp 55 | * Initial import of cpp_web_server from webrtc_ros 56 | * Initial commit 57 | * Contributors: Mitchell Wills, Russell Toris 58 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8.3) 2 | project(async_web_server_cpp) 3 | 4 | ## Find catkin macros and libraries 5 | ## if COMPONENTS list like find_package(catkin REQUIRED COMPONENTS xyz) 6 | ## is used, also find other catkin packages 7 | find_package(catkin REQUIRED) 8 | 9 | ## System dependencies are found with CMake's conventions 10 | find_package(Boost REQUIRED COMPONENTS thread system regex filesystem) 11 | find_package(OpenSSL) 12 | 13 | ################################################### 14 | ## Declare things to be passed to other projects ## 15 | ################################################### 16 | 17 | ## LIBRARIES: libraries you create in this project that dependent projects also need 18 | ## CATKIN_DEPENDS: catkin_packages dependent projects also need 19 | ## DEPENDS: system dependencies of this project that dependent projects also need 20 | catkin_package( 21 | INCLUDE_DIRS include 22 | LIBRARIES ${PROJECT_NAME} 23 | DEPENDS Boost OpenSSL 24 | ) 25 | 26 | ########### 27 | ## Build ## 28 | ########### 29 | 30 | ## Specify additional locations of header files 31 | include_directories( 32 | include 33 | ${Boost_INCLUDE_DIRS} 34 | ${OPENSSL_INCLUDE_DIRS} 35 | ) 36 | 37 | add_library(${PROJECT_NAME} src/http_server.cpp 38 | src/http_connection.cpp src/http_request_parser.cpp 39 | src/http_reply.cpp src/http_request_handler.cpp 40 | src/websocket_connection.cpp src/websocket_request_handler.cpp 41 | src/websocket_message.cpp src/http_request.cpp) 42 | 43 | target_link_libraries(${PROJECT_NAME} 44 | ${OPENSSL_LIBRARIES} 45 | ${Boost_LIBRARIES} 46 | ${catkin_LIBRARIES} 47 | ) 48 | 49 | ############# 50 | ## Install ## 51 | ############# 52 | 53 | ## Mark executables and/or libraries for installation 54 | install(TARGETS ${PROJECT_NAME} 55 | ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} 56 | LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} 57 | RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} 58 | ) 59 | 60 | ## Mark cpp header files for installation 61 | install(DIRECTORY include/${PROJECT_NAME}/ 62 | DESTINATION ${CATKIN_PACKAGE_INCLUDE_DESTINATION} 63 | FILES_MATCHING PATTERN "*.hpp" PATTERN "*.h" 64 | ) 65 | 66 | 67 | if(CATKIN_ENABLE_TESTING) 68 | find_package(catkin REQUIRED rospy roslib) 69 | find_package(rostest) 70 | add_subdirectory(test) 71 | endif() 72 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Software License Agreement (BSD License) 2 | 3 | Copyright (c) 2014, Worcester Polytechnic Institute 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions 8 | are met: 9 | 10 | * Redistributions of source code must retain the above copyright 11 | notice, this list of conditions and the following disclaimer. 12 | * Redistributions in binary form must reproduce the above 13 | copyright notice, this list of conditions and the following 14 | disclaimer in the documentation and/or other materials provided 15 | with the distribution. 16 | * Neither the name of Worcester Polytechnic Institute 17 | nor the names of its contributors may be used to 18 | endorse or promote products derived from this software without 19 | specific prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 23 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 24 | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 25 | COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 26 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 27 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 28 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 29 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 30 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 31 | ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 32 | POSSIBILITY OF SUCH DAMAGE. 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | async_web_server_cpp [![Build Status](https://api.travis-ci.org/GT-RAIL/async_web_server_cpp.png)](https://travis-ci.org/GT-RAIL/async_web_server_cpp) 2 | ==================== 3 | 4 | #### Asynchronous Web/WebSocket Server in C++ 5 | An implementation of an HTTP web server in C++ built on top of the [Boost.Asio Library](http://www.boost.org/doc/libs/1_57_0/doc/html/boost_asio.html). 6 | It also provides the ability to handle websocket upgrade requests. 7 | 8 | ### License 9 | async_web_server_cpp is released with a BSD license. For full terms and conditions, see the [LICENSE](LICENSE) file. 10 | 11 | ### Authors 12 | See the [AUTHORS](AUTHORS.md) file for a full list of contributors. 13 | -------------------------------------------------------------------------------- /include/async_web_server_cpp/http_connection.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CPP_WEB_SERVER_HTTP_CONNECTION_HPP 2 | #define CPP_WEB_SERVER_HTTP_CONNECTION_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "async_web_server_cpp/http_request_handler.hpp" 10 | #include "async_web_server_cpp/http_request.hpp" 11 | #include "async_web_server_cpp/http_request_parser.hpp" 12 | 13 | namespace async_web_server_cpp 14 | { 15 | 16 | class HttpConnection; 17 | typedef boost::shared_ptr HttpConnectionPtr; 18 | typedef boost::weak_ptr HttpConnectionWeakPtr; 19 | 20 | /** 21 | * Represents a connection to a client 22 | * The connection to the client is maintained as long as there is a shared 23 | * pointer to the connection object. If supplying the response is simple then 24 | * it can be done in the request handler callback from the server. However, 25 | * if the response will take time to generate or must be supplied over a long 26 | * period of time then a shared_ptr to the connection can be held and used 27 | * later. While reading and writing from multiple threads is supported, care 28 | * should be taken to ensure that proper external synchronization is done as 29 | * needed. For example, while write can be called from two threads, if two 30 | * calls to write need to be unseperated then calls to write should be locked 31 | * to prevent interleaving of different write calls. 32 | */ 33 | class HttpConnection : public boost::enable_shared_from_this, 34 | private boost::noncopyable 35 | { 36 | public: 37 | typedef boost::function ReadHandler; 38 | typedef boost::shared_ptr ResourcePtr; 39 | 40 | explicit HttpConnection(boost::asio::io_service &io_service, 41 | HttpServerRequestHandler request_handler); 42 | 43 | boost::asio::ip::tcp::socket &socket(); 44 | 45 | /** 46 | * Start async operation to read request (normally called by server) 47 | */ 48 | void start(); 49 | 50 | /** 51 | * Perform an async read 52 | */ 53 | void async_read(ReadHandler callback); 54 | 55 | /** 56 | * Write the given bytes to the socket and clear the vector 57 | */ 58 | void write_and_clear(std::vector &data); 59 | 60 | void write(const std::string &); 61 | 62 | void write(const boost::asio::const_buffer &buffer, 63 | ResourcePtr resource); 64 | 65 | void write(const std::vector &buffer, 66 | ResourcePtr resource); 67 | 68 | private: 69 | void handle_read(const char* begin, const char* end); 70 | void handle_read_raw(ReadHandler callback, 71 | const boost::system::error_code &e, 72 | std::size_t bytes_transferred); 73 | 74 | // Must be called while holding write lock 75 | void write_pending(); 76 | 77 | void handle_write(const boost::system::error_code &e, 78 | std::vector resources); 79 | 80 | boost::asio::io_service::strand strand_; 81 | boost::asio::ip::tcp::socket socket_; 82 | HttpServerRequestHandler request_handler_; 83 | boost::array buffer_; 84 | HttpRequest request_; 85 | HttpRequestParser request_parser_; 86 | 87 | boost::mutex write_mutex_; 88 | bool write_in_progress_; 89 | std::vector pending_write_buffers_; 90 | std::vector pending_write_resources_; 91 | boost::system::error_code last_error_; 92 | ReadHandler read_handler_; 93 | }; 94 | 95 | } 96 | 97 | #endif 98 | -------------------------------------------------------------------------------- /include/async_web_server_cpp/http_header.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CPP_WEB_SERVER_HTTP_HEADER_HPP 2 | #define CPP_WEB_SERVER_HTTP_HEADER_HPP 3 | 4 | #include 5 | 6 | namespace async_web_server_cpp 7 | { 8 | 9 | /** 10 | * Represents a HTTP header in a request or response 11 | */ 12 | struct HttpHeader 13 | { 14 | HttpHeader() 15 | { 16 | } 17 | 18 | HttpHeader(std::string name, std::string value) : name(name), value(value) 19 | { 20 | } 21 | 22 | std::string name; 23 | std::string value; 24 | }; 25 | 26 | } 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /include/async_web_server_cpp/http_reply.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CPP_WEB_SERVER_HTTP_REPLY_HPP 2 | #define CPP_WEB_SERVER_HTTP_REPLY_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include "async_web_server_cpp/http_header.hpp" 8 | #include "async_web_server_cpp/http_connection.hpp" 9 | #include "async_web_server_cpp/http_request_handler.hpp" 10 | #include 11 | 12 | namespace async_web_server_cpp 13 | { 14 | 15 | class ReplyBuilder; 16 | 17 | /** 18 | * Utility methods for constructing replys 19 | */ 20 | struct HttpReply 21 | { 22 | enum status_type 23 | { 24 | switching_protocols = 101, 25 | ok = 200, 26 | created = 201, 27 | accepted = 202, 28 | no_content = 204, 29 | multiple_choices = 300, 30 | moved_permanently = 301, 31 | moved_temporarily = 302, 32 | not_modified = 304, 33 | bad_request = 400, 34 | unauthorized = 401, 35 | forbidden = 403, 36 | not_found = 404, 37 | internal_server_error = 500, 38 | not_implemented = 501, 39 | bad_gateway = 502, 40 | service_unavailable = 503 41 | } status; 42 | 43 | static std::vector to_buffers(const std::vector &headers); 44 | 45 | /** 46 | * Create a request handler that sends a stock reply based on the stats code 47 | */ 48 | static HttpServerRequestHandler stock_reply(status_type status); 49 | 50 | /** 51 | * Create a request handler that sends the contents of a file 52 | */ 53 | static HttpServerRequestHandler from_file(HttpReply::status_type status, 54 | const std::string& content_type, 55 | const std::string& filename, 56 | const std::vector& additional_headers = std::vector()); 57 | 58 | /** 59 | * Create a request handler that reads files from the filesystem 60 | * No content type is served and it is left to the browser to determine the content type 61 | * @param path_root the prefix in the request path that should be ignored 62 | * @param filesystem_root the path to search for the requested file 63 | */ 64 | static HttpServerRequestHandler from_filesystem(HttpReply::status_type status, 65 | const std::string& path_root, 66 | const std::string& filesystem_root, 67 | bool list_directories, 68 | const std::vector& additional_headers = std::vector()); 69 | 70 | /** 71 | * Create a request handler that sends a static response 72 | */ 73 | static HttpServerRequestHandler static_reply(status_type status, 74 | const std::string& content_type, 75 | const std::string& content, 76 | const std::vector& additional_headers = std::vector()); 77 | 78 | /** 79 | * Create a builder to create and send reply headers 80 | */ 81 | static ReplyBuilder builder(status_type status); 82 | }; 83 | 84 | /** 85 | * Object to build and send reply headers 86 | */ 87 | class ReplyBuilder 88 | { 89 | public: 90 | ReplyBuilder(HttpReply::status_type status); 91 | 92 | /** 93 | * Add a header to the reply 94 | */ 95 | ReplyBuilder &header(const std::string &name, const std::string &value); 96 | 97 | /** 98 | * Add a header to the reply 99 | */ 100 | ReplyBuilder &header(const HttpHeader &header); 101 | 102 | /** 103 | * Add a group of headers to the reply 104 | */ 105 | ReplyBuilder &headers(const std::vector &headers); 106 | 107 | /** 108 | * Send the headers over the connection 109 | */ 110 | void write(HttpConnectionPtr connection); 111 | 112 | private: 113 | HttpReply::status_type status_; 114 | boost::shared_ptr > headers_; 115 | }; 116 | 117 | 118 | /** 119 | * Request Handler that serves a predefined response 120 | */ 121 | class StaticHttpRequestHandler 122 | { 123 | public: 124 | StaticHttpRequestHandler(HttpReply::status_type status, 125 | const std::vector &headers, 126 | const std::string &content); 127 | 128 | bool operator()(const HttpRequest &, boost::shared_ptr, const char* begin, const char* end); 129 | 130 | private: 131 | ReplyBuilder reply_builder_; 132 | const std::string content_string_; 133 | }; 134 | 135 | /** 136 | * Request Handler that serves a response from a file 137 | */ 138 | class FileHttpRequestHandler 139 | { 140 | public: 141 | FileHttpRequestHandler(HttpReply::status_type status, 142 | const std::string& filename, 143 | const std::vector& headers); 144 | 145 | bool operator()(const HttpRequest &, boost::shared_ptr, const char* begin, const char* end); 146 | 147 | private: 148 | HttpReply::status_type status_; 149 | std::vector headers_; 150 | std::string filename_; 151 | }; 152 | 153 | /** 154 | * Request Handler that serves a responses from the filesystem from a base path 155 | */ 156 | class FilesystemHttpRequestHandler 157 | { 158 | public: 159 | FilesystemHttpRequestHandler(HttpReply::status_type status, 160 | const std::string& path_root, 161 | const std::string& filesystem_root, 162 | bool list_directories, 163 | const std::vector& headers); 164 | 165 | bool operator()(const HttpRequest &, boost::shared_ptr, const char* begin, const char* end); 166 | 167 | private: 168 | HttpReply::status_type status_; 169 | std::vector headers_; 170 | std::string path_root_; 171 | boost::filesystem::path filesystem_root_; 172 | bool list_directories_; 173 | }; 174 | 175 | } 176 | 177 | #endif 178 | -------------------------------------------------------------------------------- /include/async_web_server_cpp/http_request.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CPP_WEB_SERVER_HTTP_REQUEST_HPP 2 | #define CPP_WEB_SERVER_HTTP_REQUEST_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "async_web_server_cpp/http_header.hpp" 9 | 10 | namespace async_web_server_cpp 11 | { 12 | 13 | /** 14 | * Represents a request from a browser 15 | */ 16 | struct HttpRequest 17 | { 18 | std::string method; 19 | std::string uri; 20 | int http_version_major; 21 | int http_version_minor; 22 | std::vector headers; 23 | 24 | std::string path; 25 | std::string query; 26 | std::map query_params; 27 | 28 | bool has_header(const std::string &name) const; 29 | 30 | std::string get_header_value_or_default(const std::string &name, 31 | const std::string &default_value) const; 32 | 33 | 34 | bool has_query_param(const std::string &name) const; 35 | 36 | std::string get_query_param_value_or_default(const std::string &name, 37 | const std::string &default_value) const; 38 | 39 | template 40 | T get_query_param_value_or_default(const std::string &name, 41 | const T &default_value) const 42 | { 43 | std::map::const_iterator itr = query_params.find(name); 44 | if (itr != query_params.end()) 45 | { 46 | try 47 | { 48 | return boost::lexical_cast(itr->second); 49 | } 50 | catch (const boost::bad_lexical_cast &) 51 | { 52 | return default_value; 53 | } 54 | } 55 | else 56 | { 57 | return default_value; 58 | } 59 | } 60 | 61 | bool parse_uri(); 62 | }; 63 | 64 | } 65 | 66 | #endif 67 | -------------------------------------------------------------------------------- /include/async_web_server_cpp/http_request_handler.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CPP_WEB_SERVER_HTTP_REQUEST_HANDLER_HPP 2 | #define CPP_WEB_SERVER_HTTP_REQUEST_HANDLER_HPP 3 | 4 | #include 5 | #include 6 | #include "async_web_server_cpp/http_request.hpp" 7 | 8 | namespace async_web_server_cpp 9 | { 10 | 11 | class HttpConnection; 12 | 13 | /** 14 | * A handler for requests 15 | * Should return true if the request was successfuly handled 16 | * Returning false will cause the next matching handler to be triggered 17 | * If false is returned then nothing should be written to the connection 18 | */ 19 | typedef boost::function, const char* begin, const char* end)> HttpServerRequestHandler; 20 | 21 | /** 22 | * A hander that can dispatch to a request to different handlers depending on a 23 | * predicate. If none of registered handlers satisfy the request then the 24 | * default request handler is used. 25 | */ 26 | class HttpRequestHandlerGroup 27 | { 28 | public: 29 | typedef boost::function HandlerPredicate; 30 | 31 | HttpRequestHandlerGroup(HttpServerRequestHandler default_handler); 32 | 33 | void addHandlerForPath(const std::string &path_regex, HttpServerRequestHandler handler); 34 | 35 | void addHandler(HandlerPredicate predicate, HttpServerRequestHandler handler); 36 | 37 | bool operator()(const HttpRequest &request, boost::shared_ptr connection, const char* begin, const char* end); 38 | 39 | private: 40 | HttpServerRequestHandler default_handler_; 41 | std::vector > handlers_; 42 | }; 43 | 44 | class HttpRequestBodyCollector 45 | { 46 | public: 47 | typedef boost::function, const std::string& body)> Handler; 48 | 49 | HttpRequestBodyCollector(Handler handler); 50 | 51 | bool operator()(const HttpRequest &request, boost::shared_ptr connection, const char* begin, const char* end); 52 | 53 | private: 54 | Handler handler_; 55 | }; 56 | 57 | } 58 | 59 | #endif 60 | -------------------------------------------------------------------------------- /include/async_web_server_cpp/http_request_parser.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // http_request_parser.hpp 3 | // ~~~~~~~~~~~~~~~~~~ 4 | // 5 | // Copyright (c) 2003-2011 Christopher M. Kohlhoff (chris at kohlhoff dot com) 6 | // 7 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 8 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 9 | // 10 | 11 | #ifndef CPP_WEB_SERVER_HTTP_REQUEST_PARSER_HPP 12 | #define CPP_WEB_SERVER_HTTP_REQUEST_PARSER_HPP 13 | 14 | #include 15 | #include 16 | #include "async_web_server_cpp/http_request.hpp" 17 | 18 | namespace async_web_server_cpp 19 | { 20 | 21 | class HttpRequestParser 22 | { 23 | public: 24 | /// Construct ready to parse the request method. 25 | HttpRequestParser(); 26 | 27 | /// Reset to initial parser state. 28 | void reset(); 29 | 30 | /// Parse some data. The tribool return value is true when a complete request 31 | /// has been parsed, false if the data is invalid, indeterminate when more 32 | /// data is required. The InputIterator return value indicates how much of the 33 | /// input has been consumed. 34 | template 35 | boost::tuple parse(HttpRequest &req, 36 | InputIterator begin, InputIterator end) 37 | { 38 | while (begin != end) 39 | { 40 | boost::tribool result = consume(req, *begin++); 41 | if (result || !result) 42 | return boost::make_tuple(result, begin); 43 | } 44 | boost::tribool result = boost::indeterminate; 45 | return boost::make_tuple(result, begin); 46 | } 47 | 48 | private: 49 | /// Handle the next character of input. 50 | boost::tribool consume(HttpRequest &req, char input); 51 | 52 | /// Check if a byte is an HTTP character. 53 | static bool is_char(int c); 54 | 55 | /// Check if a byte is an HTTP control character. 56 | static bool is_ctl(int c); 57 | 58 | /// Check if a byte is defined as an HTTP tspecial character. 59 | static bool is_tspecial(int c); 60 | 61 | /// Check if a byte is a digit. 62 | static bool is_digit(int c); 63 | 64 | /// The current state of the parser. 65 | enum state 66 | { 67 | method_start, 68 | method, 69 | uri, 70 | http_version_h, 71 | http_version_t_1, 72 | http_version_t_2, 73 | http_version_p, 74 | http_version_slash, 75 | http_version_major_start, 76 | http_version_major, 77 | http_version_minor_start, 78 | http_version_minor, 79 | expecting_newline_1, 80 | header_line_start, 81 | header_lws, 82 | header_name, 83 | space_before_header_value, 84 | header_value, 85 | expecting_newline_2, 86 | expecting_newline_3 87 | } state_; 88 | }; 89 | 90 | } 91 | 92 | #endif 93 | -------------------------------------------------------------------------------- /include/async_web_server_cpp/http_server.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CPP_WEB_SERVER_HTTP_SERVER_HPP 2 | #define CPP_WEB_SERVER_HTTP_SERVER_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "async_web_server_cpp/http_request_handler.hpp" 13 | #include "async_web_server_cpp/http_connection.hpp" 14 | 15 | namespace async_web_server_cpp 16 | { 17 | 18 | /** 19 | * @class HttpServer 20 | * The HttpServer is an implementation of a HTTP server that serves http request from a given port 21 | * The server maintains a pool of threads to use to serve requests. Each request is dispatched to 22 | * the given request handler to be handled. 23 | */ 24 | class HttpServer : private boost::noncopyable 25 | { 26 | public: 27 | HttpServer(const std::string &address, const std::string &port, 28 | HttpServerRequestHandler request_handler, std::size_t thread_pool_size); 29 | ~HttpServer(); 30 | 31 | void run(); 32 | 33 | void stop(); 34 | 35 | private: 36 | void start_accept(); 37 | 38 | void handle_accept(const boost::system::error_code &e); 39 | 40 | boost::asio::io_service io_service_; 41 | boost::asio::ip::tcp::acceptor acceptor_; 42 | std::size_t thread_pool_size_; 43 | std::vector > threads_; 44 | boost::shared_ptr new_connection_; 45 | HttpServerRequestHandler request_handler_; 46 | }; 47 | 48 | } 49 | 50 | #endif 51 | -------------------------------------------------------------------------------- /include/async_web_server_cpp/websocket_connection.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CPP_WEB_SERVER_WEBSOCKET_CONNECTION_HPP 2 | #define CPP_WEB_SERVER_WEBSOCKET_CONNECTION_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "async_web_server_cpp/http_connection.hpp" 10 | #include "async_web_server_cpp/websocket_message.hpp" 11 | 12 | namespace async_web_server_cpp 13 | { 14 | 15 | class WebsocketHttpRequestHandler; 16 | 17 | class WebsocketConnection; 18 | typedef boost::shared_ptr WebsocketConnectionPtr; 19 | typedef boost::weak_ptr WebsocketConnectionWeakPtr; 20 | 21 | /** 22 | * Represents a websocket connection. Similar to an HttpConnection, to keep the 23 | * connection alive keep a shared pointer to this object. 24 | */ 25 | class WebsocketConnection : public boost::enable_shared_from_this, 26 | private boost::noncopyable 27 | { 28 | public: 29 | explicit WebsocketConnection(HttpConnectionPtr connection); 30 | 31 | typedef boost::function MessageHandler; 32 | 33 | bool sendTextMessage(const std::string& content); 34 | bool sendPingMessage(const std::string& content = ""); 35 | 36 | bool sendMessage(const WebsocketMessage& message); 37 | bool sendFrame(WebsocketFrame& frame); 38 | 39 | private: 40 | static void static_handle_read(WebsocketConnectionWeakPtr weak_this, const char* begin, const char* end); 41 | void handle_read(const char* begin, const char* end); 42 | HttpConnectionPtr connection_; 43 | 44 | void set_message_handler(MessageHandler& handler); 45 | MessageHandler handler_; 46 | 47 | WebsocketFrame frame_; 48 | WebsocketMessage message_; 49 | WebsocketFrameParser frame_parser_; 50 | WebsocketFrameBuffer frame_buffer_; 51 | 52 | friend class WebsocketHttpRequestHandler; 53 | }; 54 | 55 | } 56 | 57 | #endif 58 | -------------------------------------------------------------------------------- /include/async_web_server_cpp/websocket_message.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CPP_WEB_SERVER_WEBSOCKET_MESSAGE_HPP 2 | #define CPP_WEB_SERVER_WEBSOCKET_MESSAGE_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace async_web_server_cpp 10 | { 11 | 12 | class WebsocketMessage; 13 | 14 | /** 15 | * An single websocket frame and associated header 16 | */ 17 | class WebsocketFrame 18 | { 19 | public: 20 | struct Header 21 | { 22 | enum opcode 23 | { 24 | opcode_continuation = 0, 25 | opcode_text = 1, 26 | opcode_binary = 2, 27 | opcode_close = 8, 28 | opcode_ping = 9, 29 | opcode_pong = 10, 30 | } opcode : 4; 31 | bool rsv3 : 1; 32 | bool rsv2 : 1; 33 | bool rsv1 : 1; 34 | bool fin : 1; 35 | 36 | unsigned int len : 7; 37 | bool mask : 1; 38 | } __attribute__((__packed__)); 39 | union 40 | { 41 | Header header; 42 | char header_bytes[2]; 43 | }; 44 | uint64_t length; 45 | unsigned char mask[4]; 46 | std::string content; 47 | 48 | bool fromMessage(const WebsocketMessage& message); 49 | bool serialize(std::vector& buffer); 50 | }; 51 | 52 | class WebsocketFrameParser 53 | { 54 | public: 55 | WebsocketFrameParser(); 56 | void reset(); 57 | boost::tribool consume(WebsocketFrame& frame, char input); 58 | template 59 | boost::tuple parse(WebsocketFrame& frame, 60 | InputIterator begin, InputIterator end) 61 | { 62 | while (begin != end) 63 | { 64 | boost::tribool result = consume(frame, *begin++); 65 | if (result || !result) 66 | return boost::make_tuple(result, begin); 67 | } 68 | boost::tribool result = boost::indeterminate; 69 | return boost::make_tuple(result, begin); 70 | } 71 | 72 | private: 73 | enum state 74 | { 75 | header_byte1, 76 | header_byte2, 77 | length_8bytes_left, 78 | length_7bytes_left, 79 | length_6bytes_left, 80 | length_5bytes_left, 81 | length_4bytes_left, 82 | length_3bytes_left, 83 | length_2bytes_left, 84 | length_1bytes_left, 85 | mask_byte1, 86 | mask_byte2, 87 | mask_byte3, 88 | mask_byte4, 89 | body 90 | } state_; 91 | 92 | }; 93 | 94 | /** 95 | * A websocket message that in potentially constructed from/destructed to 96 | * a WebsocketFrame. 97 | */ 98 | class WebsocketMessage 99 | { 100 | public: 101 | WebsocketMessage(); 102 | enum type 103 | { 104 | type_unknown, 105 | type_text, 106 | type_binary, 107 | type_close, 108 | type_ping, 109 | type_pong, 110 | } type; 111 | std::string content; 112 | }; 113 | 114 | class WebsocketFrameBuffer 115 | { 116 | public: 117 | boost::tribool consume(WebsocketMessage& message, WebsocketFrame& frame); 118 | }; 119 | 120 | 121 | } 122 | 123 | #endif 124 | -------------------------------------------------------------------------------- /include/async_web_server_cpp/websocket_request_handler.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CPP_WEB_SERVER_WEBSOCKET_REQUEST_HANDLER_HPP 2 | #define CPP_WEB_SERVER_WEBSOCKET_REQUEST_HANDLER_HPP 3 | 4 | #include 5 | #include 6 | #include "async_web_server_cpp/http_request_handler.hpp" 7 | #include "async_web_server_cpp/websocket_connection.hpp" 8 | 9 | namespace async_web_server_cpp 10 | { 11 | 12 | class WebsocketConnection; 13 | 14 | typedef boost::function)> WebsocketRequestHandler; 15 | 16 | /** 17 | * A HTTP request handler that upgrades a HttpConnection to a WebsocketConnection. 18 | */ 19 | class WebsocketHttpRequestHandler 20 | { 21 | public: 22 | WebsocketHttpRequestHandler(WebsocketRequestHandler handler); 23 | bool operator()(const HttpRequest &request, boost::shared_ptr connection, const char* begin, const char* end); 24 | 25 | static const std::string KEY_MAGIC_STRING; 26 | private: 27 | WebsocketRequestHandler handler_; 28 | }; 29 | 30 | } 31 | 32 | #endif 33 | -------------------------------------------------------------------------------- /package.xml: -------------------------------------------------------------------------------- 1 | 2 | async_web_server_cpp 3 | 0.0.3 4 | Asynchronous Web/WebSocket Server in C++ 5 | 6 | Russell Toris 7 | Mitchell Wills 8 | 9 | BSD 10 | 11 | http://ros.org/wiki/async_web_server_cpp 12 | https://github.com/GT-RAIL/async_web_server_cpp/issues 13 | https://github.com/GT-RAIL/async_web_server_cpp 14 | 15 | catkin 16 | 17 | libssl-dev 18 | boost 19 | 20 | libssl-dev 21 | boost 22 | 23 | rostest 24 | rospy 25 | roslib 26 | python-websocket 27 | 28 | -------------------------------------------------------------------------------- /src/http_connection.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "async_web_server_cpp/http_reply.hpp" 4 | 5 | namespace async_web_server_cpp 6 | { 7 | 8 | HttpConnection::HttpConnection(boost::asio::io_service &io_service, 9 | HttpServerRequestHandler handler) 10 | : strand_(io_service), 11 | socket_(io_service), 12 | request_handler_(handler), 13 | write_in_progress_(false) 14 | { 15 | } 16 | 17 | boost::asio::ip::tcp::socket &HttpConnection::socket() 18 | { 19 | return socket_; 20 | } 21 | 22 | void HttpConnection::start() 23 | { 24 | async_read(boost::bind(&HttpConnection::handle_read, shared_from_this(), _1, _2)); 25 | } 26 | 27 | void HttpConnection::handle_read(const char* begin, const char* end) 28 | { 29 | boost::tribool result; 30 | const char* parse_end; 31 | boost::tie(result, parse_end) = request_parser_.parse(request_, begin, end); 32 | 33 | if (result) 34 | { 35 | request_.parse_uri(); 36 | try 37 | { 38 | request_handler_(request_, shared_from_this(), parse_end, end); 39 | } 40 | catch (...) 41 | { 42 | // error constructing request 43 | // just kill the connection as the handler may have already started writing stuff out 44 | } 45 | } 46 | else if (!result) 47 | { 48 | HttpReply::stock_reply(HttpReply::bad_request)(request_, shared_from_this(), begin, end); 49 | } 50 | else 51 | { 52 | async_read(boost::bind(&HttpConnection::handle_read, shared_from_this(), _1, _2)); 53 | } 54 | } 55 | 56 | void HttpConnection::handle_read_raw(ReadHandler callback, 57 | const boost::system::error_code &e, 58 | std::size_t bytes_transferred) 59 | { 60 | if (!e) 61 | { 62 | callback(buffer_.data(), buffer_.data() + bytes_transferred); 63 | } 64 | else 65 | { 66 | last_error_ = e; 67 | } 68 | } 69 | void HttpConnection::async_read(ReadHandler callback) 70 | { 71 | if (last_error_) 72 | { 73 | boost::throw_exception(boost::system::system_error(last_error_)); 74 | } 75 | socket_.async_read_some(boost::asio::buffer(buffer_), 76 | strand_.wrap(boost::bind(&HttpConnection::handle_read_raw, shared_from_this(), 77 | callback, 78 | boost::asio::placeholders::error, 79 | boost::asio::placeholders::bytes_transferred))); 80 | } 81 | 82 | void HttpConnection::write_and_clear(std::vector &data) 83 | { 84 | boost::shared_ptr > buffer(new std::vector()); 85 | buffer->swap(data); 86 | write(boost::asio::buffer(*buffer), buffer); 87 | } 88 | 89 | void HttpConnection::write(const std::string &content) 90 | { 91 | boost::shared_ptr str(new std::string(content)); 92 | write(boost::asio::buffer(*str), str); 93 | } 94 | 95 | void HttpConnection::write(const boost::asio::const_buffer &buffer, 96 | ResourcePtr resource) 97 | { 98 | boost::mutex::scoped_lock lock(write_mutex_); 99 | pending_write_buffers_.push_back(buffer); 100 | if (resource) 101 | pending_write_resources_.push_back(resource); 102 | if (!write_in_progress_) 103 | write_pending(); 104 | } 105 | 106 | void HttpConnection::write(const std::vector &buffers, 107 | ResourcePtr resource) 108 | { 109 | boost::mutex::scoped_lock lock(write_mutex_); 110 | pending_write_buffers_.insert(pending_write_buffers_.end(), buffers.begin(), buffers.end()); 111 | if (resource) 112 | pending_write_resources_.push_back(resource); 113 | if (!write_in_progress_) 114 | write_pending(); 115 | } 116 | 117 | 118 | // Must be called while holding write lock 119 | void HttpConnection::write_pending() 120 | { 121 | if (last_error_) 122 | { 123 | boost::throw_exception(boost::system::system_error(last_error_)); 124 | } 125 | write_in_progress_ = true; 126 | boost::asio::async_write(socket_, pending_write_buffers_, 127 | boost::bind(&HttpConnection::handle_write, shared_from_this(), 128 | boost::asio::placeholders::error, 129 | pending_write_resources_)); 130 | pending_write_buffers_.clear(); 131 | pending_write_resources_.clear(); 132 | } 133 | 134 | void HttpConnection::handle_write(const boost::system::error_code &e, 135 | std::vector resources) 136 | { 137 | boost::mutex::scoped_lock lock(write_mutex_); 138 | write_in_progress_ = false; 139 | if (!e) 140 | { 141 | if (!pending_write_buffers_.empty()) 142 | { 143 | write_pending(); 144 | } 145 | } 146 | else 147 | { 148 | last_error_ = e; 149 | } 150 | } 151 | 152 | } 153 | -------------------------------------------------------------------------------- /src/http_reply.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // http_reply.hpp 3 | // ~~~~~~~~~~~~~~~~~~ 4 | // 5 | // Copyright (c) 2003-2011 Christopher M. Kohlhoff (chris at kohlhoff dot com) 6 | // 7 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 8 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 9 | // 10 | 11 | 12 | #include 13 | #include 14 | #include "async_web_server_cpp/http_reply.hpp" 15 | 16 | namespace async_web_server_cpp 17 | { 18 | 19 | namespace status_strings 20 | { 21 | 22 | const std::string switching_protocols = 23 | "HTTP/1.0 101 Switching Protocols\r\n"; 24 | const std::string ok = 25 | "HTTP/1.0 200 OK\r\n"; 26 | const std::string created = 27 | "HTTP/1.0 201 Created\r\n"; 28 | const std::string accepted = 29 | "HTTP/1.0 202 Accepted\r\n"; 30 | const std::string no_content = 31 | "HTTP/1.0 204 No Content\r\n"; 32 | const std::string multiple_choices = 33 | "HTTP/1.0 300 Multiple Choices\r\n"; 34 | const std::string moved_permanently = 35 | "HTTP/1.0 301 Moved Permanently\r\n"; 36 | const std::string moved_temporarily = 37 | "HTTP/1.0 302 Moved Temporarily\r\n"; 38 | const std::string not_modified = 39 | "HTTP/1.0 304 Not Modified\r\n"; 40 | const std::string bad_request = 41 | "HTTP/1.0 400 Bad Request\r\n"; 42 | const std::string unauthorized = 43 | "HTTP/1.0 401 Unauthorized\r\n"; 44 | const std::string forbidden = 45 | "HTTP/1.0 403 Forbidden\r\n"; 46 | const std::string not_found = 47 | "HTTP/1.0 404 Not Found\r\n"; 48 | const std::string internal_server_error = 49 | "HTTP/1.0 500 Internal Server Error\r\n"; 50 | const std::string not_implemented = 51 | "HTTP/1.0 501 Not Implemented\r\n"; 52 | const std::string bad_gateway = 53 | "HTTP/1.0 502 Bad Gateway\r\n"; 54 | const std::string service_unavailable = 55 | "HTTP/1.0 503 Service Unavailable\r\n"; 56 | 57 | boost::asio::const_buffer to_buffer(HttpReply::status_type status) 58 | { 59 | switch (status) 60 | { 61 | case HttpReply::switching_protocols: 62 | return boost::asio::buffer(switching_protocols); 63 | case HttpReply::ok: 64 | return boost::asio::buffer(ok); 65 | case HttpReply::created: 66 | return boost::asio::buffer(created); 67 | case HttpReply::accepted: 68 | return boost::asio::buffer(accepted); 69 | case HttpReply::no_content: 70 | return boost::asio::buffer(no_content); 71 | case HttpReply::multiple_choices: 72 | return boost::asio::buffer(multiple_choices); 73 | case HttpReply::moved_permanently: 74 | return boost::asio::buffer(moved_permanently); 75 | case HttpReply::moved_temporarily: 76 | return boost::asio::buffer(moved_temporarily); 77 | case HttpReply::not_modified: 78 | return boost::asio::buffer(not_modified); 79 | case HttpReply::bad_request: 80 | return boost::asio::buffer(bad_request); 81 | case HttpReply::unauthorized: 82 | return boost::asio::buffer(unauthorized); 83 | case HttpReply::forbidden: 84 | return boost::asio::buffer(forbidden); 85 | case HttpReply::not_found: 86 | return boost::asio::buffer(not_found); 87 | case HttpReply::internal_server_error: 88 | return boost::asio::buffer(internal_server_error); 89 | case HttpReply::not_implemented: 90 | return boost::asio::buffer(not_implemented); 91 | case HttpReply::bad_gateway: 92 | return boost::asio::buffer(bad_gateway); 93 | case HttpReply::service_unavailable: 94 | return boost::asio::buffer(service_unavailable); 95 | default: 96 | return boost::asio::buffer(internal_server_error); 97 | } 98 | } 99 | 100 | } // namespace status_strings 101 | 102 | namespace misc_strings 103 | { 104 | 105 | const char name_value_separator[] = {':', ' '}; 106 | const char crlf[] = {'\r', '\n'}; 107 | 108 | } // namespace misc_strings 109 | 110 | namespace stock_replies 111 | { 112 | 113 | const char ok[] = ""; 114 | const char created[] = 115 | "" 116 | "Created" 117 | "

201 Created

" 118 | ""; 119 | const char accepted[] = 120 | "" 121 | "Accepted" 122 | "

202 Accepted

" 123 | ""; 124 | const char no_content[] = 125 | "" 126 | "No Content" 127 | "

204 Content

" 128 | ""; 129 | const char multiple_choices[] = 130 | "" 131 | "Multiple Choices" 132 | "

300 Multiple Choices

" 133 | ""; 134 | const char moved_permanently[] = 135 | "" 136 | "Moved Permanently" 137 | "

301 Moved Permanently

" 138 | ""; 139 | const char moved_temporarily[] = 140 | "" 141 | "Moved Temporarily" 142 | "

302 Moved Temporarily

" 143 | ""; 144 | const char not_modified[] = 145 | "" 146 | "Not Modified" 147 | "

304 Not Modified

" 148 | ""; 149 | const char bad_request[] = 150 | "" 151 | "Bad Request" 152 | "

400 Bad Request

" 153 | ""; 154 | const char unauthorized[] = 155 | "" 156 | "Unauthorized" 157 | "

401 Unauthorized

" 158 | ""; 159 | const char forbidden[] = 160 | "" 161 | "Forbidden" 162 | "

403 Forbidden

" 163 | ""; 164 | const char not_found[] = 165 | "" 166 | "Not Found" 167 | "

404 Not Found

" 168 | ""; 169 | const char internal_server_error[] = 170 | "" 171 | "Internal Server Error" 172 | "

500 Internal Server Error

" 173 | ""; 174 | const char not_implemented[] = 175 | "" 176 | "Not Implemented" 177 | "

501 Not Implemented

" 178 | ""; 179 | const char bad_gateway[] = 180 | "" 181 | "Bad Gateway" 182 | "

502 Bad Gateway

" 183 | ""; 184 | const char service_unavailable[] = 185 | "" 186 | "Service Unavailable" 187 | "

503 Service Unavailable

" 188 | ""; 189 | 190 | std::string to_string(HttpReply::status_type status) 191 | { 192 | switch (status) 193 | { 194 | case HttpReply::ok: 195 | return ok; 196 | case HttpReply::created: 197 | return created; 198 | case HttpReply::accepted: 199 | return accepted; 200 | case HttpReply::no_content: 201 | return no_content; 202 | case HttpReply::multiple_choices: 203 | return multiple_choices; 204 | case HttpReply::moved_permanently: 205 | return moved_permanently; 206 | case HttpReply::moved_temporarily: 207 | return moved_temporarily; 208 | case HttpReply::not_modified: 209 | return not_modified; 210 | case HttpReply::bad_request: 211 | return bad_request; 212 | case HttpReply::unauthorized: 213 | return unauthorized; 214 | case HttpReply::forbidden: 215 | return forbidden; 216 | case HttpReply::not_found: 217 | return not_found; 218 | case HttpReply::internal_server_error: 219 | return internal_server_error; 220 | case HttpReply::not_implemented: 221 | return not_implemented; 222 | case HttpReply::bad_gateway: 223 | return bad_gateway; 224 | case HttpReply::service_unavailable: 225 | return service_unavailable; 226 | default: 227 | return internal_server_error; 228 | } 229 | } 230 | 231 | } // namespace stock_replies 232 | 233 | std::vector HttpReply::to_buffers(const std::vector &headers) 234 | { 235 | std::vector buffers; 236 | for (std::size_t i = 0; i < headers.size(); ++i) 237 | { 238 | const HttpHeader &h = headers[i]; 239 | buffers.push_back(boost::asio::buffer(h.name)); 240 | buffers.push_back(boost::asio::buffer(misc_strings::name_value_separator)); 241 | buffers.push_back(boost::asio::buffer(h.value)); 242 | buffers.push_back(boost::asio::buffer(misc_strings::crlf)); 243 | } 244 | buffers.push_back(boost::asio::buffer(misc_strings::crlf)); 245 | return buffers; 246 | } 247 | 248 | 249 | HttpServerRequestHandler HttpReply::from_file(HttpReply::status_type status, 250 | const std::string& content_type, 251 | const std::string& filename, 252 | const std::vector& additional_headers) 253 | { 254 | std::vector headers; 255 | headers.push_back(HttpHeader("Content-Type", content_type)); 256 | std::copy(additional_headers.begin(), additional_headers.end(), headers.begin()); 257 | 258 | return FileHttpRequestHandler(status, filename, headers); 259 | } 260 | FileHttpRequestHandler::FileHttpRequestHandler(HttpReply::status_type status, 261 | const std::string& filename, 262 | const std::vector& headers) 263 | : status_(status), headers_(headers), filename_(filename) 264 | { 265 | } 266 | 267 | static bool serveFromFile(HttpReply::status_type status, const std::string& filename, const std::vector& headers, boost::shared_ptr connection) { 268 | std::ifstream file_stream(filename.c_str()); 269 | std::stringstream file_buffer; 270 | file_buffer << file_stream.rdbuf(); 271 | std::string content = file_buffer.str(); 272 | 273 | ReplyBuilder reply_builder_(status); 274 | reply_builder_.headers(headers); 275 | reply_builder_.header("Content-Length", boost::lexical_cast(content.size())); 276 | reply_builder_.write(connection); 277 | connection->write(content); 278 | return true; 279 | } 280 | bool FileHttpRequestHandler::operator()(const HttpRequest &request, boost::shared_ptr connection, const char* begin, const char* end) 281 | { 282 | return serveFromFile(status_, filename_, headers_, connection); 283 | } 284 | 285 | 286 | HttpServerRequestHandler HttpReply::from_filesystem(HttpReply::status_type status, 287 | const std::string& path_root, 288 | const std::string& filesystem_root, 289 | bool list_directories, 290 | const std::vector& additional_headers) 291 | { 292 | return FilesystemHttpRequestHandler(status, path_root, filesystem_root, list_directories, additional_headers); 293 | } 294 | FilesystemHttpRequestHandler::FilesystemHttpRequestHandler(HttpReply::status_type status, 295 | const std::string& path_root, 296 | const std::string& filesystem_root, 297 | bool list_directories, 298 | const std::vector& headers) 299 | : status_(status), headers_(headers), path_root_(path_root), filesystem_root_(filesystem_root), list_directories_(list_directories) 300 | { 301 | } 302 | 303 | bool FilesystemHttpRequestHandler::operator()(const HttpRequest &request, boost::shared_ptr connection, const char* begin, const char* end) 304 | { 305 | if(request.path.find(path_root_) == 0) { // request.path startswith path_root 306 | std::string rel_path = request.path.substr(path_root_.length()); 307 | if(rel_path.find_first_of('/') == 0) { // remove leading slash to make path relative 308 | rel_path = rel_path.substr(1); 309 | } 310 | 311 | boost::filesystem::path requested_path = filesystem_root_ / rel_path; 312 | 313 | if(boost::filesystem::exists(requested_path)) { 314 | if(boost::filesystem::is_directory(requested_path)) { 315 | if(list_directories_) { 316 | std::stringstream content; 317 | content << ""; 318 | content << "

Directory Listing: " << request.path << "

"; 319 | boost::filesystem::directory_iterator end_itr; 320 | for (boost::filesystem::directory_iterator itr(requested_path); itr != end_itr; ++itr) { 321 | if(boost::filesystem::is_directory(itr->status())) { 322 | content << "path().leaf().generic_string() << "/\">"; 323 | content << itr->path().leaf().generic_string() << "/"; 324 | content << ""; 325 | } 326 | else if(boost::filesystem::is_regular_file(itr->status())) { 327 | content << "path().leaf().generic_string() << "\">"; 328 | content << itr->path().leaf().generic_string(); 329 | content << ""; 330 | } 331 | content << "
"; 332 | } 333 | content << ""; 334 | HttpReply::static_reply(HttpReply::ok, "text/html", content.str(), headers_)(request, connection, begin, end); 335 | } 336 | else { 337 | HttpReply::stock_reply(HttpReply::forbidden)(request, connection, begin, end); 338 | } 339 | return true; 340 | } 341 | else if(boost::filesystem::is_regular_file(requested_path)) { 342 | serveFromFile(status_, requested_path.generic_string(), headers_, connection); 343 | return true; 344 | } 345 | else { 346 | } 347 | } 348 | else { 349 | return false; 350 | } 351 | } 352 | else { 353 | return false; 354 | } 355 | } 356 | 357 | 358 | HttpServerRequestHandler HttpReply::stock_reply(HttpReply::status_type status) 359 | { 360 | return static_reply(status, "text/html", stock_replies::to_string(status)); 361 | } 362 | 363 | HttpServerRequestHandler HttpReply::static_reply(HttpReply::status_type status, 364 | const std::string& content_type, 365 | const std::string& content, 366 | const std::vector& additional_headers) 367 | { 368 | std::vector headers; 369 | headers.push_back(HttpHeader("Content-Length", boost::lexical_cast(content.size()))); 370 | headers.push_back(HttpHeader("Content-Type", content_type)); 371 | std::copy(additional_headers.begin(), additional_headers.end(), headers.begin()); 372 | return StaticHttpRequestHandler(status, headers, content); 373 | } 374 | 375 | 376 | StaticHttpRequestHandler::StaticHttpRequestHandler(HttpReply::status_type status, 377 | const std::vector &headers, 378 | const std::string &content) 379 | : reply_builder_(status), content_string_(content) 380 | { 381 | reply_builder_.headers(headers); 382 | } 383 | 384 | bool StaticHttpRequestHandler::operator()(const HttpRequest &request, boost::shared_ptr connection, const char* begin, const char* end) 385 | { 386 | reply_builder_.write(connection); 387 | connection->write(content_string_); 388 | return true; 389 | } 390 | 391 | 392 | ReplyBuilder HttpReply::builder(HttpReply::status_type status) 393 | { 394 | return ReplyBuilder(status); 395 | } 396 | 397 | ReplyBuilder::ReplyBuilder(HttpReply::status_type status) 398 | : status_(status), headers_(new std::vector()) 399 | { 400 | } 401 | 402 | ReplyBuilder &ReplyBuilder::header(const std::string &name, const std::string &value) 403 | { 404 | return header(HttpHeader(name, value)); 405 | } 406 | 407 | ReplyBuilder &ReplyBuilder::header(const HttpHeader &header) 408 | { 409 | headers_->push_back(header); 410 | return *this; 411 | } 412 | 413 | ReplyBuilder &ReplyBuilder::headers(const std::vector &headers) 414 | { 415 | headers_->insert(headers_->end(), headers.begin(), headers.end()); 416 | return *this; 417 | } 418 | 419 | void ReplyBuilder::write(HttpConnectionPtr connection) 420 | { 421 | connection->write(status_strings::to_buffer(status_), HttpConnection::ResourcePtr()); 422 | connection->write(HttpReply::to_buffers(*headers_), headers_); 423 | } 424 | 425 | } 426 | -------------------------------------------------------------------------------- /src/http_request.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "async_web_server_cpp/http_request.hpp" 5 | 6 | namespace async_web_server_cpp 7 | { 8 | 9 | static boost::regex uri_regex("(.*?)(?:\\?(.*?))?"); 10 | 11 | bool HttpRequest::parse_uri() 12 | { 13 | boost::smatch match; 14 | if (regex_match(uri, match, uri_regex)) 15 | { 16 | path.assign(match[1].first, match[1].second); 17 | if (match[2].matched) 18 | { 19 | query.assign(match[2].first, match[2].second); 20 | 21 | std::vector pair_strings; 22 | boost::split(pair_strings, query, boost::is_any_of("&")); 23 | BOOST_FOREACH(const std::string & pair_string, pair_strings) 24 | { 25 | std::vector pair_data; 26 | const int eq_index = pair_string.find_first_of('='); 27 | if (eq_index == std::string::npos) 28 | { 29 | if (pair_string.size() > 0) 30 | { 31 | query_params[pair_string] = ""; 32 | } 33 | } 34 | else 35 | { 36 | query_params[pair_string.substr(0, eq_index)] = pair_string.substr(eq_index + 1); 37 | } 38 | } 39 | } 40 | return true; 41 | } 42 | else 43 | { 44 | return false; 45 | } 46 | 47 | } 48 | 49 | bool HttpRequest::has_header(const std::string &name) const 50 | { 51 | typedef std::vector HeaderList; 52 | for (HeaderList::const_iterator itr = headers.begin(); itr != headers.end(); ++itr) 53 | { 54 | if (itr->name.compare(name) == 0) 55 | return false; 56 | } 57 | return true; 58 | } 59 | std::string HttpRequest::get_header_value_or_default(const std::string &name, 60 | const std::string &default_value) const 61 | { 62 | typedef std::vector HeaderList; 63 | for (HeaderList::const_iterator itr = headers.begin(); itr != headers.end(); ++itr) 64 | { 65 | if (itr->name.compare(name) == 0) 66 | return itr->value; 67 | } 68 | return default_value; 69 | } 70 | 71 | bool HttpRequest::has_query_param(const std::string &name) const 72 | { 73 | std::map::const_iterator itr = query_params.find(name); 74 | return itr != query_params.end(); 75 | } 76 | 77 | std::string HttpRequest::get_query_param_value_or_default(const std::string &name, 78 | const std::string &default_value) const 79 | { 80 | std::map::const_iterator itr = query_params.find(name); 81 | if (itr != query_params.end()) 82 | { 83 | return itr->second; 84 | } 85 | else 86 | { 87 | return default_value; 88 | } 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /src/http_request_handler.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "async_web_server_cpp/http_request_handler.hpp" 7 | #include "async_web_server_cpp/http_connection.hpp" 8 | #include "async_web_server_cpp/http_reply.hpp" 9 | 10 | namespace async_web_server_cpp 11 | { 12 | 13 | HttpRequestHandlerGroup::HttpRequestHandlerGroup(HttpServerRequestHandler default_handler) 14 | : default_handler_(default_handler) 15 | { 16 | } 17 | 18 | class PathMatcher 19 | { 20 | public: 21 | explicit PathMatcher(const std::string &path_regex_string) 22 | : path_regex_(boost::regex(path_regex_string)) 23 | { 24 | } 25 | 26 | bool operator()(const HttpRequest &request) 27 | { 28 | return regex_match(request.path, path_regex_); 29 | } 30 | 31 | private: 32 | const boost::regex path_regex_; 33 | }; 34 | 35 | void HttpRequestHandlerGroup::addHandlerForPath(const std::string &path_regex, HttpServerRequestHandler handler) 36 | { 37 | addHandler(PathMatcher(path_regex), handler); 38 | } 39 | 40 | void HttpRequestHandlerGroup::addHandler(HandlerPredicate predicate, HttpServerRequestHandler handler) 41 | { 42 | handlers_.push_back(std::make_pair(predicate, handler)); 43 | } 44 | 45 | 46 | bool HttpRequestHandlerGroup::operator()(const HttpRequest &request, boost::shared_ptr connection, const char* begin, const char* end) 47 | { 48 | for (int i = 0; i < handlers_.size(); ++i) 49 | { 50 | std::pair &handler = handlers_[i]; 51 | if (handler.first(request)) 52 | { 53 | if(handler.second(request, connection, begin, end)) 54 | return true; 55 | } 56 | } 57 | return default_handler_(request, connection, begin, end); 58 | } 59 | 60 | class BodyCollectingConnection; 61 | typedef boost::shared_ptr BodyCollectingConnectionPtr; 62 | typedef boost::weak_ptr BodyCollectingConnectionWeakPtr; 63 | class BodyCollectingConnection : public boost::enable_shared_from_this, 64 | private boost::noncopyable 65 | { 66 | public: 67 | BodyCollectingConnection(HttpRequestBodyCollector::Handler handler, const HttpRequest &request, boost::shared_ptr connection) 68 | : handler_(handler), request_(request), connection_(connection), received_length_(0) { 69 | std::string length_str = request_.get_header_value_or_default("Content-Length", ""); 70 | try { 71 | length_ = boost::lexical_cast(length_str); 72 | } 73 | catch(const boost::bad_lexical_cast &) { 74 | length_ = -1; //indicate error 75 | } 76 | } 77 | 78 | static void static_handle_read(BodyCollectingConnectionPtr _this, const char* begin, const char* end) { 79 | _this->handle_read(begin, end); 80 | } 81 | void handle_read(const char* begin, const char* end) { 82 | if(length_ < 0) { 83 | HttpReply::builder(HttpReply::bad_request).write(connection_); 84 | connection_->write("No Content-Length header"); 85 | return; 86 | } 87 | std::string chunk(begin, end-begin); 88 | body_stream_ << chunk; 89 | received_length_ += chunk.length(); 90 | if(received_length_ >= length_) { 91 | handler_(request_, connection_, body_stream_.str().substr(0, length_)); 92 | } 93 | else { 94 | connection_->async_read(boost::bind(&BodyCollectingConnection::static_handle_read, shared_from_this(), _1, _2)); 95 | } 96 | } 97 | 98 | private: 99 | HttpRequestBodyCollector::Handler handler_; 100 | const HttpRequest request_; 101 | boost::shared_ptr connection_; 102 | std::stringstream body_stream_; 103 | ssize_t length_; 104 | size_t received_length_; 105 | }; 106 | 107 | HttpRequestBodyCollector::HttpRequestBodyCollector(Handler handler) 108 | : handler_(handler) {} 109 | 110 | bool HttpRequestBodyCollector::operator()(const HttpRequest &request, boost::shared_ptr connection, const char* begin, const char* end) 111 | { 112 | BodyCollectingConnectionPtr collecting_connection(new BodyCollectingConnection(handler_, request, connection)); 113 | collecting_connection->handle_read(begin, end); 114 | return true; 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /src/http_request_parser.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // http_request_parser.cpp 3 | // ~~~~~~~~~~~~~~~~~~ 4 | // 5 | // Copyright (c) 2003-2011 Christopher M. Kohlhoff (chris at kohlhoff dot com) 6 | // 7 | // Distributed under the Boost Software License, Version 1.0. (See accompanying 8 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 9 | // 10 | 11 | #include "async_web_server_cpp/http_request_parser.hpp" 12 | 13 | namespace async_web_server_cpp 14 | { 15 | HttpRequestParser::HttpRequestParser() 16 | : state_(method_start) 17 | { 18 | } 19 | 20 | void HttpRequestParser::reset() 21 | { 22 | state_ = method_start; 23 | } 24 | 25 | boost::tribool HttpRequestParser::consume(HttpRequest &req, char input) 26 | { 27 | switch (state_) 28 | { 29 | case method_start: 30 | if (!is_char(input) || is_ctl(input) || is_tspecial(input)) 31 | { 32 | return false; 33 | } 34 | else 35 | { 36 | state_ = method; 37 | req.method.push_back(input); 38 | return boost::indeterminate; 39 | } 40 | case method: 41 | if (input == ' ') 42 | { 43 | state_ = uri; 44 | return boost::indeterminate; 45 | } 46 | else if (!is_char(input) || is_ctl(input) || is_tspecial(input)) 47 | { 48 | return false; 49 | } 50 | else 51 | { 52 | req.method.push_back(input); 53 | return boost::indeterminate; 54 | } 55 | case uri: 56 | if (input == ' ') 57 | { 58 | state_ = http_version_h; 59 | return boost::indeterminate; 60 | } 61 | else if (is_ctl(input)) 62 | { 63 | return false; 64 | } 65 | else 66 | { 67 | req.uri.push_back(input); 68 | return boost::indeterminate; 69 | } 70 | case http_version_h: 71 | if (input == 'H') 72 | { 73 | state_ = http_version_t_1; 74 | return boost::indeterminate; 75 | } 76 | else 77 | { 78 | return false; 79 | } 80 | case http_version_t_1: 81 | if (input == 'T') 82 | { 83 | state_ = http_version_t_2; 84 | return boost::indeterminate; 85 | } 86 | else 87 | { 88 | return false; 89 | } 90 | case http_version_t_2: 91 | if (input == 'T') 92 | { 93 | state_ = http_version_p; 94 | return boost::indeterminate; 95 | } 96 | else 97 | { 98 | return false; 99 | } 100 | case http_version_p: 101 | if (input == 'P') 102 | { 103 | state_ = http_version_slash; 104 | return boost::indeterminate; 105 | } 106 | else 107 | { 108 | return false; 109 | } 110 | case http_version_slash: 111 | if (input == '/') 112 | { 113 | req.http_version_major = 0; 114 | req.http_version_minor = 0; 115 | state_ = http_version_major_start; 116 | return boost::indeterminate; 117 | } 118 | else 119 | { 120 | return false; 121 | } 122 | case http_version_major_start: 123 | if (is_digit(input)) 124 | { 125 | req.http_version_major = req.http_version_major * 10 + input - '0'; 126 | state_ = http_version_major; 127 | return boost::indeterminate; 128 | } 129 | else 130 | { 131 | return false; 132 | } 133 | case http_version_major: 134 | if (input == '.') 135 | { 136 | state_ = http_version_minor_start; 137 | return boost::indeterminate; 138 | } 139 | else if (is_digit(input)) 140 | { 141 | req.http_version_major = req.http_version_major * 10 + input - '0'; 142 | return boost::indeterminate; 143 | } 144 | else 145 | { 146 | return false; 147 | } 148 | case http_version_minor_start: 149 | if (is_digit(input)) 150 | { 151 | req.http_version_minor = req.http_version_minor * 10 + input - '0'; 152 | state_ = http_version_minor; 153 | return boost::indeterminate; 154 | } 155 | else 156 | { 157 | return false; 158 | } 159 | case http_version_minor: 160 | if (input == '\r') 161 | { 162 | state_ = expecting_newline_1; 163 | return boost::indeterminate; 164 | } 165 | else if (is_digit(input)) 166 | { 167 | req.http_version_minor = req.http_version_minor * 10 + input - '0'; 168 | return boost::indeterminate; 169 | } 170 | else 171 | { 172 | return false; 173 | } 174 | case expecting_newline_1: 175 | if (input == '\n') 176 | { 177 | state_ = header_line_start; 178 | return boost::indeterminate; 179 | } 180 | else 181 | { 182 | return false; 183 | } 184 | case header_line_start: 185 | if (input == '\r') 186 | { 187 | state_ = expecting_newline_3; 188 | return boost::indeterminate; 189 | } 190 | else if (!req.headers.empty() && (input == ' ' || input == '\t')) 191 | { 192 | state_ = header_lws; 193 | return boost::indeterminate; 194 | } 195 | else if (!is_char(input) || is_ctl(input) || is_tspecial(input)) 196 | { 197 | return false; 198 | } 199 | else 200 | { 201 | req.headers.push_back(HttpHeader()); 202 | req.headers.back().name.push_back(input); 203 | state_ = header_name; 204 | return boost::indeterminate; 205 | } 206 | case header_lws: 207 | if (input == '\r') 208 | { 209 | state_ = expecting_newline_2; 210 | return boost::indeterminate; 211 | } 212 | else if (input == ' ' || input == '\t') 213 | { 214 | return boost::indeterminate; 215 | } 216 | else if (is_ctl(input)) 217 | { 218 | return false; 219 | } 220 | else 221 | { 222 | state_ = header_value; 223 | req.headers.back().value.push_back(input); 224 | return boost::indeterminate; 225 | } 226 | case header_name: 227 | if (input == ':') 228 | { 229 | state_ = space_before_header_value; 230 | return boost::indeterminate; 231 | } 232 | else if (!is_char(input) || is_ctl(input) || is_tspecial(input)) 233 | { 234 | return false; 235 | } 236 | else 237 | { 238 | req.headers.back().name.push_back(input); 239 | return boost::indeterminate; 240 | } 241 | case space_before_header_value: 242 | if (input == ' ') 243 | { 244 | state_ = header_value; 245 | return boost::indeterminate; 246 | } 247 | else 248 | { 249 | return false; 250 | } 251 | case header_value: 252 | if (input == '\r') 253 | { 254 | state_ = expecting_newline_2; 255 | return boost::indeterminate; 256 | } 257 | else if (is_ctl(input)) 258 | { 259 | return false; 260 | } 261 | else 262 | { 263 | req.headers.back().value.push_back(input); 264 | return boost::indeterminate; 265 | } 266 | case expecting_newline_2: 267 | if (input == '\n') 268 | { 269 | state_ = header_line_start; 270 | return boost::indeterminate; 271 | } 272 | else 273 | { 274 | return false; 275 | } 276 | case expecting_newline_3: 277 | return (input == '\n'); 278 | default: 279 | return false; 280 | } 281 | } 282 | 283 | bool HttpRequestParser::is_char(int c) 284 | { 285 | return c >= 0 && c <= 127; 286 | } 287 | 288 | bool HttpRequestParser::is_ctl(int c) 289 | { 290 | return (c >= 0 && c <= 31) || (c == 127); 291 | } 292 | 293 | bool HttpRequestParser::is_tspecial(int c) 294 | { 295 | switch (c) 296 | { 297 | case '(': 298 | case ')': 299 | case '<': 300 | case '>': 301 | case '@': 302 | case ',': 303 | case ';': 304 | case ':': 305 | case '\\': 306 | case '"': 307 | case '/': 308 | case '[': 309 | case ']': 310 | case '?': 311 | case '=': 312 | case '{': 313 | case '}': 314 | case ' ': 315 | case '\t': 316 | return true; 317 | default: 318 | return false; 319 | } 320 | } 321 | 322 | bool HttpRequestParser::is_digit(int c) 323 | { 324 | return c >= '0' && c <= '9'; 325 | } 326 | 327 | } 328 | -------------------------------------------------------------------------------- /src/http_server.cpp: -------------------------------------------------------------------------------- 1 | #include "async_web_server_cpp/http_server.hpp" 2 | #include "async_web_server_cpp/http_reply.hpp" 3 | 4 | namespace async_web_server_cpp 5 | { 6 | 7 | HttpServer::HttpServer(const std::string &address, const std::string &port, 8 | HttpServerRequestHandler request_handler, std::size_t thread_pool_size) 9 | : acceptor_(io_service_), thread_pool_size_(thread_pool_size), request_handler_(request_handler) 10 | { 11 | 12 | boost::asio::ip::tcp::resolver resolver(io_service_); 13 | boost::asio::ip::tcp::resolver::query query(address, port, boost::asio::ip::resolver_query_base::flags()); 14 | boost::asio::ip::tcp::endpoint endpoint = *resolver.resolve(query); 15 | acceptor_.open(endpoint.protocol()); 16 | acceptor_.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true)); 17 | acceptor_.bind(endpoint); 18 | acceptor_.listen(); 19 | } 20 | 21 | HttpServer::~HttpServer() { 22 | stop(); 23 | } 24 | 25 | void HttpServer::run() 26 | { 27 | start_accept(); 28 | for (std::size_t i = 0; i < thread_pool_size_; ++i) 29 | { 30 | boost::shared_ptr thread(new boost::thread( 31 | boost::bind(&boost::asio::io_service::run, &io_service_))); 32 | threads_.push_back(thread); 33 | } 34 | } 35 | 36 | void HttpServer::start_accept() 37 | { 38 | new_connection_.reset(new HttpConnection(io_service_, request_handler_)); 39 | acceptor_.async_accept(new_connection_->socket(), 40 | boost::bind(&HttpServer::handle_accept, this, 41 | boost::asio::placeholders::error)); 42 | } 43 | 44 | void HttpServer::handle_accept(const boost::system::error_code &e) 45 | { 46 | if (!e) 47 | { 48 | new_connection_->start(); 49 | } 50 | start_accept(); 51 | } 52 | 53 | void HttpServer::stop() 54 | { 55 | if(acceptor_.is_open()) { 56 | acceptor_.cancel(); 57 | acceptor_.close(); 58 | } 59 | io_service_.stop(); 60 | // Wait for all threads in the pool to exit. 61 | for (std::size_t i = 0; i < threads_.size(); ++i) 62 | threads_[i]->join(); 63 | threads_.clear(); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/websocket_connection.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "async_web_server_cpp/websocket_connection.hpp" 5 | 6 | namespace async_web_server_cpp 7 | { 8 | 9 | WebsocketConnection::WebsocketConnection(HttpConnectionPtr connection) 10 | : connection_(connection) {} 11 | 12 | void WebsocketConnection::set_message_handler(MessageHandler& handler) 13 | { 14 | handler_ = handler; 15 | } 16 | 17 | bool WebsocketConnection::sendTextMessage(const std::string& content) 18 | { 19 | async_web_server_cpp::WebsocketMessage m; 20 | m.type = async_web_server_cpp::WebsocketMessage::type_text; 21 | m.content = content; 22 | return sendMessage(m); 23 | } 24 | bool WebsocketConnection::sendPingMessage(const std::string& content) 25 | { 26 | async_web_server_cpp::WebsocketMessage m; 27 | m.type = async_web_server_cpp::WebsocketMessage::type_ping; 28 | m.content = content; 29 | return sendMessage(m); 30 | } 31 | 32 | 33 | bool WebsocketConnection::sendMessage(const WebsocketMessage& message) 34 | { 35 | WebsocketFrame frame; 36 | if (frame.fromMessage(message)) 37 | { 38 | return sendFrame(frame); 39 | } 40 | return false; 41 | } 42 | 43 | bool WebsocketConnection::sendFrame(WebsocketFrame& frame) 44 | { 45 | std::vector buffer; 46 | if (frame.serialize(buffer)) 47 | { 48 | connection_->write_and_clear(buffer); 49 | return true; 50 | } 51 | return false; 52 | } 53 | 54 | 55 | void WebsocketConnection::static_handle_read(WebsocketConnectionWeakPtr weak_this, const char* begin, const char* end) 56 | { 57 | WebsocketConnectionPtr _this = weak_this.lock(); 58 | if (_this) 59 | _this->handle_read(begin, end); 60 | } 61 | void WebsocketConnection::handle_read(const char* begin, const char* end) 62 | { 63 | boost::tribool frame_result; 64 | const char* parse_end = begin; 65 | while (parse_end < end) 66 | { 67 | boost::tie(frame_result, parse_end) = frame_parser_.parse(frame_, parse_end, end); 68 | if (frame_result) 69 | { 70 | frame_parser_.reset(); 71 | boost::tribool message_result = frame_buffer_.consume(message_, frame_); 72 | 73 | if (message_result) 74 | { 75 | if (handler_) 76 | handler_(message_); 77 | } 78 | } 79 | else if (!frame_result) 80 | { 81 | frame_parser_.reset(); 82 | message_.type = WebsocketMessage::type_unknown; 83 | } 84 | } 85 | WebsocketConnectionWeakPtr this_weak(shared_from_this()); 86 | connection_->async_read(boost::bind(&WebsocketConnection::static_handle_read, this_weak, _1, _2)); 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /src/websocket_message.cpp: -------------------------------------------------------------------------------- 1 | #include "async_web_server_cpp/websocket_message.hpp" 2 | 3 | namespace async_web_server_cpp 4 | { 5 | 6 | bool WebsocketFrame::fromMessage(const WebsocketMessage& message) 7 | { 8 | switch (message.type) 9 | { 10 | case WebsocketMessage::type_text: 11 | header.opcode = WebsocketFrame::Header::opcode_text; 12 | break; 13 | case WebsocketMessage::type_binary: 14 | header.opcode = WebsocketFrame::Header::opcode_binary; 15 | break; 16 | case WebsocketMessage::type_close: 17 | header.opcode = WebsocketFrame::Header::opcode_close; 18 | break; 19 | case WebsocketMessage::type_ping: 20 | header.opcode = WebsocketFrame::Header::opcode_ping; 21 | break; 22 | case WebsocketMessage::type_pong: 23 | header.opcode = WebsocketFrame::Header::opcode_pong; 24 | break; 25 | default: 26 | return false; 27 | } 28 | header.fin = true; 29 | header.rsv1 = false; 30 | header.rsv2 = false; 31 | header.rsv3 = false; 32 | content = message.content; 33 | length = message.content.size(); 34 | return true; 35 | } 36 | 37 | bool WebsocketFrame::serialize(std::vector& buffer) 38 | { 39 | int header_size = 2; // first two bytes of header 40 | if (length < 126) 41 | { 42 | header.len = length; 43 | } 44 | else if (length <= std::numeric_limits::max()) 45 | { 46 | header.len = 126; 47 | header_size += 2; 48 | } 49 | else if (length <= std::numeric_limits::max()) 50 | { 51 | header.len = 127; 52 | header_size += 8; 53 | } 54 | else 55 | return false; // Message too big 56 | header.mask = false; // sending from server 57 | 58 | buffer.resize(header_size + content.size()); 59 | buffer[0] = header_bytes[0]; 60 | buffer[1] = header_bytes[1]; 61 | 62 | if (length < 126) 63 | { 64 | // already copied in header bytes 65 | } 66 | else if (length <= std::numeric_limits::max()) 67 | { 68 | buffer[2] = (length >> 8) & 0xFF; 69 | buffer[3] = (length >> 0) & 0xFF; 70 | } 71 | else if (length <= std::numeric_limits::max()) 72 | { 73 | buffer[2] = (length >> 56) & 0xFF; 74 | buffer[3] = (length >> 48) & 0xFF; 75 | buffer[4] = (length >> 40) & 0xFF; 76 | buffer[5] = (length >> 32) & 0xFF; 77 | buffer[6] = (length >> 24) & 0xFF; 78 | buffer[7] = (length >> 16) & 0xFF; 79 | buffer[8] = (length >> 8) & 0xFF; 80 | buffer[9] = (length >> 0) & 0xFF; 81 | } 82 | content.copy((char*)&buffer[header_size], content.size()); 83 | return true; 84 | } 85 | 86 | 87 | WebsocketFrameParser::WebsocketFrameParser() 88 | { 89 | reset(); 90 | } 91 | 92 | void WebsocketFrameParser::reset() 93 | { 94 | state_ = header_byte1; 95 | } 96 | 97 | boost::tribool WebsocketFrameParser::consume(WebsocketFrame& frame, char input) 98 | { 99 | switch (state_) 100 | { 101 | case header_byte1: 102 | frame.header_bytes[0] = input; 103 | state_ = header_byte2; 104 | return boost::indeterminate; 105 | case header_byte2: 106 | frame.header_bytes[1] = input; 107 | if (frame.header.len < 126) 108 | { 109 | frame.length = frame.header.len; 110 | frame.content.reserve(frame.length); 111 | frame.content.resize(0); 112 | if (frame.header.mask) 113 | state_ = mask_byte1; 114 | else if (frame.length > 0) 115 | state_ = body; 116 | else 117 | return true; 118 | } 119 | else if (frame.header.len == 126) 120 | { 121 | frame.length = 0; 122 | state_ = length_2bytes_left; 123 | } 124 | else 125 | { 126 | frame.length = 0; 127 | state_ = length_8bytes_left; 128 | } 129 | return boost::indeterminate; 130 | 131 | case length_8bytes_left: 132 | frame.length |= ((uint64_t)(input & 0xFF) << 56); 133 | state_ = length_7bytes_left; 134 | return boost::indeterminate; 135 | case length_7bytes_left: 136 | frame.length |= ((uint64_t)(input & 0xFF) << 48); 137 | state_ = length_6bytes_left; 138 | return boost::indeterminate; 139 | case length_6bytes_left: 140 | frame.length |= ((uint64_t)(input & 0xFF) << 40); 141 | state_ = length_5bytes_left; 142 | return boost::indeterminate; 143 | case length_5bytes_left: 144 | frame.length |= ((uint64_t)(input & 0xFF) << 32); 145 | state_ = length_4bytes_left; 146 | return boost::indeterminate; 147 | case length_4bytes_left: 148 | frame.length |= ((uint64_t)(input & 0xFF) << 24); 149 | state_ = length_3bytes_left; 150 | return boost::indeterminate; 151 | case length_3bytes_left: 152 | frame.length |= ((uint64_t)(input & 0xFF) << 16); 153 | state_ = length_2bytes_left; 154 | return boost::indeterminate; 155 | case length_2bytes_left: 156 | frame.length |= ((uint64_t)(input & 0xFF) << 8); 157 | state_ = length_1bytes_left; 158 | return boost::indeterminate; 159 | case length_1bytes_left: 160 | frame.length |= ((uint64_t)(input & 0xFF) << 0); 161 | frame.content.reserve(frame.length); 162 | frame.content.resize(0); 163 | if (frame.header.mask) 164 | state_ = mask_byte1; 165 | else 166 | state_ = body; 167 | return boost::indeterminate; 168 | 169 | 170 | case mask_byte1: 171 | frame.mask[0] = input; 172 | state_ = mask_byte2; 173 | return boost::indeterminate; 174 | case mask_byte2: 175 | frame.mask[1] = input; 176 | state_ = mask_byte3; 177 | return boost::indeterminate; 178 | case mask_byte3: 179 | frame.mask[2] = input; 180 | state_ = mask_byte4; 181 | return boost::indeterminate; 182 | case mask_byte4: 183 | frame.mask[3] = input; 184 | if (frame.length > 0) 185 | state_ = body; 186 | else 187 | return true; 188 | return boost::indeterminate; 189 | 190 | case body: 191 | frame.content += input; 192 | if (frame.content.size() < frame.length) 193 | return boost::indeterminate; 194 | //unmask the frame 195 | if (frame.header.mask) 196 | { 197 | for (int i = 0; i < frame.length; ++i) 198 | { 199 | frame.content[i] = frame.content[i] ^ frame.mask[i % 4]; 200 | } 201 | } 202 | return true; 203 | default: 204 | return false; 205 | } 206 | } 207 | 208 | WebsocketMessage::WebsocketMessage() : type(type_unknown) {} 209 | 210 | boost::tribool WebsocketFrameBuffer::consume(WebsocketMessage& message, WebsocketFrame& frame) 211 | { 212 | if (frame.header.opcode == WebsocketFrame::Header::opcode_continuation) 213 | { 214 | if (message.type == WebsocketMessage::type_unknown) 215 | return false; 216 | else 217 | message.content.append(frame.content); 218 | } 219 | else 220 | { 221 | switch (frame.header.opcode) 222 | { 223 | case WebsocketFrame::Header::opcode_text: 224 | message.type = WebsocketMessage::type_text; 225 | break; 226 | case WebsocketFrame::Header::opcode_binary: 227 | message.type = WebsocketMessage::type_binary; 228 | break; 229 | case WebsocketFrame::Header::opcode_close: 230 | message.type = WebsocketMessage::type_close; 231 | break; 232 | case WebsocketFrame::Header::opcode_ping: 233 | message.type = WebsocketMessage::type_ping; 234 | break; 235 | case WebsocketFrame::Header::opcode_pong: 236 | message.type = WebsocketMessage::type_pong; 237 | break; 238 | default: 239 | message.type = WebsocketMessage::type_unknown; 240 | return false; 241 | } 242 | message.content = frame.content; 243 | } 244 | if (frame.header.fin) 245 | return true; 246 | else 247 | return boost::indeterminate; 248 | } 249 | 250 | 251 | } 252 | -------------------------------------------------------------------------------- /src/websocket_request_handler.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "async_web_server_cpp/websocket_request_handler.hpp" 8 | #include "async_web_server_cpp/websocket_connection.hpp" 9 | #include "async_web_server_cpp/http_reply.hpp" 10 | 11 | namespace async_web_server_cpp 12 | { 13 | 14 | const std::string WebsocketHttpRequestHandler::KEY_MAGIC_STRING = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; 15 | 16 | WebsocketHttpRequestHandler::WebsocketHttpRequestHandler(WebsocketRequestHandler handler) 17 | : handler_(handler) {} 18 | 19 | 20 | bool WebsocketHttpRequestHandler::operator()(const HttpRequest &request, boost::shared_ptr connection, const char* begin, const char* end) 21 | { 22 | std::string connection_header = request.get_header_value_or_default("Connection", ""); 23 | std::string upgrade_header = request.get_header_value_or_default("Upgrade", ""); 24 | std::string websocket_key = request.get_header_value_or_default("Sec-WebSocket-Key", ""); 25 | 26 | if (connection_header.find("Upgrade") != std::string::npos && upgrade_header.compare("websocket") == 0 27 | && websocket_key.size() > 0) 28 | { 29 | std::string concat_key = websocket_key + KEY_MAGIC_STRING; 30 | 31 | // compute the sha1 hash of the concatonated key 32 | unsigned char sha1_buf[20]; 33 | SHA1((const unsigned char*)concat_key.data(), concat_key.size(), sha1_buf); 34 | 35 | // base64 encode the hash 36 | BIO* b64 = BIO_new(BIO_f_base64()); 37 | BIO* bmem = BIO_new(BIO_s_mem()); 38 | b64 = BIO_push(b64, bmem); 39 | BIO_write(b64, sha1_buf, 20); 40 | BIO_flush(b64); 41 | BUF_MEM *bptr; 42 | BIO_get_mem_ptr(b64, &bptr); 43 | std::string base64_key(bptr->data, bptr->length - 1); 44 | BIO_free_all(b64); 45 | 46 | 47 | async_web_server_cpp::HttpReply::builder(async_web_server_cpp::HttpReply::switching_protocols) 48 | .header("Upgrade", "websocket") 49 | .header("Connection", "Upgrade") 50 | .header("Sec-WebSocket-Version", "13") 51 | .header("Sec-WebSocket-Accept", base64_key) 52 | .write(connection); 53 | 54 | WebsocketConnectionPtr websocket_connection(new WebsocketConnection(connection)); 55 | WebsocketConnection::MessageHandler message_handler = handler_(request, websocket_connection); 56 | websocket_connection->set_message_handler(message_handler); 57 | websocket_connection->handle_read(begin, end); 58 | } 59 | else 60 | { 61 | async_web_server_cpp::HttpReply::stock_reply(async_web_server_cpp::HttpReply::bad_request)(request, connection, begin, end); 62 | } 63 | return true; 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(test_web_server EXCLUDE_FROM_ALL test_web_server.cpp) 2 | target_link_libraries(test_web_server ${PROJECT_NAME} ${catkin_LIBRARIES}) 3 | 4 | if(TARGET tests) 5 | add_dependencies(tests 6 | test_web_server 7 | ) 8 | endif() 9 | 10 | add_rostest(tests.test) 11 | -------------------------------------------------------------------------------- /test/simple_http_requests_test.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | import httplib 4 | import rospy 5 | import unittest 6 | import time 7 | 8 | class TestSimpleHttpRequests(unittest.TestCase): 9 | def setUp(self): 10 | self.conn = httplib.HTTPConnection("localhost:9849") 11 | 12 | def test_ok(self): 13 | self.conn.request("GET", "/response/ok") 14 | response = self.conn.getresponse() 15 | self.assertEqual(200, response.status) 16 | 17 | def test_created(self): 18 | self.conn.request("GET", "/response/created") 19 | response = self.conn.getresponse() 20 | self.assertEqual(201, response.status) 21 | 22 | def test_accepted(self): 23 | self.conn.request("GET", "/response/accepted") 24 | response = self.conn.getresponse() 25 | self.assertEqual(202, response.status) 26 | 27 | def test_forbidden(self): 28 | self.conn.request("GET", "/response/forbidden") 29 | response = self.conn.getresponse() 30 | self.assertEqual(403, response.status) 31 | 32 | def test_not_found(self): 33 | self.conn.request("GET", "/response/not_found") 34 | response = self.conn.getresponse() 35 | self.assertEqual(404, response.status) 36 | 37 | def test_internal_server_error(self): 38 | self.conn.request("GET", "/response/internal_server_error") 39 | response = self.conn.getresponse() 40 | self.assertEqual(500, response.status) 41 | 42 | def test_default_action(self): 43 | self.conn.request("GET", "/some_random_url12345") 44 | response = self.conn.getresponse() 45 | self.assertEqual(404, response.status) 46 | 47 | def test_default_action(self): 48 | self.conn.request("GET", "/a_static_response") 49 | response = self.conn.getresponse() 50 | self.assertEqual(200, response.status) 51 | self.assertEqual("A RESPONSE", response.read()) 52 | 53 | def test_http_echo1(self): 54 | test_content = "hello HELLO"*1000 # make sure to exceed MTU 55 | self.conn.request("GET", "/http_body_echo", test_content) 56 | response = self.conn.getresponse() 57 | self.assertEqual(200, response.status) 58 | self.assertEqual(test_content, response.read()) 59 | 60 | def test_http_echo2(self): 61 | test_content = "THIS is A test"*1000 # make sure to exceed MTU 62 | self.conn.request("POST", "/http_body_echo", test_content) 63 | response = self.conn.getresponse() 64 | self.assertEqual(200, response.status) 65 | self.assertEqual(test_content, response.read()) 66 | 67 | def test_http_path_echo(self): 68 | self.conn.request("GET", "/http_path_echo/this_is_a_test") 69 | response = self.conn.getresponse() 70 | self.assertEqual(200, response.status) 71 | self.assertEqual("/http_path_echo/this_is_a_test", response.read()) 72 | 73 | def test_http_query_echo(self): 74 | self.conn.request("GET", "/http_query_echo?hello=1&b=test&c=10") 75 | response = self.conn.getresponse() 76 | self.assertEqual(200, response.status) 77 | self.assertEqual("b=test\nc=10\nhello=1\n", response.read()) 78 | 79 | def test_file(self): 80 | self.conn.request("GET", "/test_file") 81 | response = self.conn.getresponse() 82 | self.assertEqual(200, response.status) 83 | self.assertEqual("\n", response.read()) 84 | 85 | def test_file_from_filesystem(self): 86 | self.conn.request("GET", "/test_files/test_dir/test_file.txt") 87 | response = self.conn.getresponse() 88 | self.assertEqual(200, response.status) 89 | self.assertEqual("test\n", response.read()) 90 | 91 | def test_directory_listing_forbidden_from_filesystem1(self): 92 | self.conn.request("GET", "/test_files/test_dir/") 93 | response = self.conn.getresponse() 94 | self.assertEqual(403, response.status) 95 | 96 | def test_directory_listing_forbidden_from_filesystem2(self): 97 | self.conn.request("GET", "/test_files/test_dir") 98 | response = self.conn.getresponse() 99 | self.assertEqual(403, response.status) 100 | 101 | def test_directory_listing_from_filesystem(self): 102 | self.conn.request("GET", "/test_files_with_dir/test_dir/") 103 | response = self.conn.getresponse() 104 | self.assertEqual(200, response.status) 105 | 106 | if __name__ == '__main__': 107 | time.sleep(1) # ensure server is up 108 | 109 | import rostest 110 | rospy.init_node('simple_http_requests_test') 111 | rostest.rosrun('async_web_server_cpp', 'simple_http_requests', TestSimpleHttpRequests) 112 | -------------------------------------------------------------------------------- /test/test.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/test_dir/test_file.txt: -------------------------------------------------------------------------------- 1 | test 2 | -------------------------------------------------------------------------------- /test/test_web_server.cpp: -------------------------------------------------------------------------------- 1 | #include "async_web_server_cpp/http_server.hpp" 2 | #include "async_web_server_cpp/http_reply.hpp" 3 | #include "async_web_server_cpp/websocket_connection.hpp" 4 | #include "async_web_server_cpp/websocket_request_handler.hpp" 5 | #include 6 | #include 7 | 8 | using namespace async_web_server_cpp; 9 | 10 | volatile bool should_shutdown = false; 11 | void sig_handler(int signo) { 12 | should_shutdown = true; 13 | } 14 | 15 | static void http_body_echo(const async_web_server_cpp::HttpRequest &request, 16 | async_web_server_cpp::HttpConnectionPtr connection, const std::string& body) 17 | { 18 | HttpReply::builder(HttpReply::ok).write(connection); 19 | connection->write(body); 20 | } 21 | 22 | static bool http_path_echo(const async_web_server_cpp::HttpRequest &request, 23 | async_web_server_cpp::HttpConnectionPtr connection, const char* begin, const char* end) 24 | { 25 | HttpReply::builder(HttpReply::ok).write(connection); 26 | connection->write(request.path); 27 | return true; 28 | } 29 | 30 | static bool http_query_echo(const async_web_server_cpp::HttpRequest &request, 31 | async_web_server_cpp::HttpConnectionPtr connection, const char* begin, const char* end) 32 | { 33 | HttpReply::builder(HttpReply::ok).write(connection); 34 | for(std::map::const_iterator itr = request.query_params.begin(); 35 | itr != request.query_params.end(); ++ itr) { 36 | connection->write(itr->first + "=" + itr->second + "\n"); 37 | } 38 | return true; 39 | } 40 | 41 | class WebsocketEchoer { 42 | public: 43 | WebsocketEchoer(WebsocketConnectionPtr websocket) : websocket_(websocket) {} 44 | void operator()(const WebsocketMessage& message) { 45 | websocket_->sendMessage(message); 46 | } 47 | private: 48 | WebsocketConnectionPtr websocket_; 49 | }; 50 | WebsocketConnection::MessageHandler websocket_echo(const HttpRequest& request, WebsocketConnectionPtr websocket) 51 | { 52 | return WebsocketEchoer(websocket); 53 | } 54 | 55 | 56 | 57 | int main(int argc, char **argv) 58 | { 59 | if (signal(SIGINT, sig_handler) == SIG_ERR) { 60 | return 1; 61 | } 62 | 63 | HttpRequestHandlerGroup handler_group( 64 | HttpReply::stock_reply(HttpReply::not_found)); 65 | 66 | handler_group.addHandlerForPath("/response/ok", HttpReply::stock_reply(HttpReply::ok)); 67 | handler_group.addHandlerForPath("/response/created", HttpReply::stock_reply(HttpReply::created)); 68 | handler_group.addHandlerForPath("/response/accepted", HttpReply::stock_reply(HttpReply::accepted)); 69 | handler_group.addHandlerForPath("/response/forbidden", HttpReply::stock_reply(HttpReply::forbidden)); 70 | handler_group.addHandlerForPath("/response/not_found", HttpReply::stock_reply(HttpReply::not_found)); 71 | handler_group.addHandlerForPath("/response/internal_server_error", HttpReply::stock_reply(HttpReply::internal_server_error)); 72 | 73 | handler_group.addHandlerForPath("/a_static_response", HttpReply::static_reply(HttpReply::ok, 74 | "text/example", 75 | "A RESPONSE")); 76 | handler_group.addHandlerForPath("/http_body_echo", HttpRequestBodyCollector(http_body_echo)); 77 | handler_group.addHandlerForPath("/http_path_echo.*", http_path_echo); 78 | handler_group.addHandlerForPath("/http_query_echo", http_query_echo); 79 | 80 | handler_group.addHandlerForPath("/websocket_echo", WebsocketHttpRequestHandler(websocket_echo)); 81 | 82 | handler_group.addHandlerForPath("/test_files/.+", HttpReply::from_filesystem(HttpReply::ok, 83 | "/test_files/", ros::package::getPath("async_web_server_cpp") + "/test", 84 | false)); 85 | handler_group.addHandlerForPath("/test_files_with_dir/.+", HttpReply::from_filesystem(HttpReply::ok, 86 | "/test_files_with_dir/", ros::package::getPath("async_web_server_cpp") + "/test", 87 | true)); 88 | handler_group.addHandlerForPath("/test_file", HttpReply::from_file(HttpReply::ok, "text/html", 89 | ros::package::getPath("async_web_server_cpp") + "/test/test.html")); 90 | 91 | HttpServer server("0.0.0.0", "9849", handler_group, 1); 92 | 93 | server.run(); 94 | 95 | while(!should_shutdown) { 96 | sleep(1); 97 | } 98 | 99 | return (0); 100 | } 101 | -------------------------------------------------------------------------------- /test/tests.test: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /test/websocket_test.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | import websocket 4 | import rospy 5 | import unittest 6 | import time 7 | 8 | class TestWebsocket(unittest.TestCase): 9 | def setUp(self): 10 | self.ws = websocket.create_connection("ws://localhost:9849/websocket_echo") 11 | 12 | def tearDown(self): 13 | self.ws.close() 14 | 15 | def test_ok(self): 16 | self.ws.send("hello") 17 | self.assertEqual("hello", self.ws.recv()) 18 | self.ws.send("test") 19 | self.assertEqual("test", self.ws.recv()) 20 | self.ws.send("hi") 21 | self.assertEqual("hi", self.ws.recv()) 22 | 23 | self.ws.ping("test ping") 24 | ping_echo = self.ws.recv_frame() 25 | self.assertEqual(9, ping_echo.opcode) 26 | self.assertEqual("test ping", ping_echo.data) 27 | 28 | self.ws.pong("test pong") 29 | pong_echo = self.ws.recv_frame() 30 | self.assertEqual(10, pong_echo.opcode) 31 | self.assertEqual("test pong", pong_echo.data) 32 | 33 | if __name__ == '__main__': 34 | time.sleep(1) # ensure server is up 35 | 36 | import rostest 37 | rospy.init_node('websocket_test') 38 | rostest.rosrun('async_web_server_cpp', 'websocket', TestWebsocket) 39 | --------------------------------------------------------------------------------