diff --git a/.travis.yml b/.travis.yml index 380cb741c9..57c16c5dde 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,7 @@ language: cpp env: global: + - LLVM_VERSION=3.8.0 # Maintenance note: to move to a new version # of boost, update both BOOST_ROOT and BOOST_URL. # Note that for simplicity, BOOST_ROOT's final @@ -27,57 +28,50 @@ packages: &gcc5_pkgs - autotools-dev - libc6-dbg -packages: &clang38_pkgs - - clang-3.8 - - g++-5 - - python-software-properties - - libssl-dev - - libffi-dev - - libstdc++6 - - binutils-gold - # Provides a backtrace if the unittests crash - - gdb - # Needed for installing valgrind - - subversion - - automake - - autotools-dev - - libc6-dbg - matrix: include: - # GCC/Coverage + # GCC/Coverage/Autobahn - compiler: gcc - env: GCC_VER=5 VARIANT=coverage ADDRESS_MODEL=64 + env: + - GCC_VER=5 + - VARIANT=coverage + - ADDRESS_MODEL=64 + - BUILD_SYSTEM=cmake + - PATH=$PWD/cmake/bin:$PATH addons: &ao_gcc5 apt: sources: ['ubuntu-toolchain-r-test'] packages: *gcc5_pkgs - # # GCC/Debug - # - compiler: gcc - # env: GCC_VER=5 VARIANT=debug ADDRESS_MODEL=64 - # addons: *ao_gcc5 - # branches: # NOTE: this does NOT work, though it SHOULD - # - master - # - develop - # Clang/UndefinedBehaviourSanitizer - compiler: clang - env: GCC_VER=5 VARIANT=usan CLANG_VER=3.8 ADDRESS_MODEL=64 UBSAN_OPTIONS='print_stacktrace=1' - addons: &ao_clang38 - apt: - sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-precise-3.8'] - packages: *clang38_pkgs + env: + - GCC_VER=5 + - VARIANT=usan + - CLANG_VER=3.8 + - ADDRESS_MODEL=64 + - UBSAN_OPTIONS='print_stacktrace=1' + - BUILD_SYSTEM=cmake + - PATH=$PWD/cmake/bin:$PATH + - PATH=$PWD/llvm-$LLVM_VERSION/bin:$PATH + addons: *ao_gcc5 # Clang/AddressSanitizer - compiler: clang - env: GCC_VER=5 VARIANT=asan CLANG_VER=3.8 ADDRESS_MODEL=64 - addons: *ao_clang38 + env: + - GCC_VER=5 + - VARIANT=asan + - CLANG_VER=3.8 + - ADDRESS_MODEL=64 + - PATH=$PWD/llvm-$LLVM_VERSION/bin:$PATH + addons: *ao_gcc5 cache: directories: - $BOOST_ROOT - $VALGRIND_ROOT + - llvm-$LLVM_VERSION + - cmake before_install: - scripts/install-dependencies.sh diff --git a/CHANGELOG b/CHANGELOG index 0eacc6feff..17cddb67e0 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,18 @@ +1.0.0-b7 + +* Fix prepare by calling init. prepare() can throw depending on the + implementation of Writer. Publicly provided beast::http writers never throw. +* Fixes to example HTTP server +* Fully qualify ambiguous calls to read and p +* Remove deprecated http::stream wrapper +* Example HTTP server now calculates the MIME-type +* Fixes and documentation for teardown and use with SSL: +* Add example code to rfc7230 javadocs +* Remove extraneous header file +* Add skip_body parser option + +-------------------------------------------------------------------------------- + 1.0.0-b6 * Use SFINAE on return values diff --git a/CMakeLists.txt b/CMakeLists.txt index 956f713311..97fb75ea98 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,7 +20,28 @@ else() find_package(Threads) set(CMAKE_CXX_FLAGS - "${CMAKE_CXX_FLAGS} -g -std=c++11 -Wall -Wpedantic") + "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wpedantic") +endif() + +if (${VARIANT} STREQUAL "coverage") + set(CMAKE_CXX_FLAGS + "${CMAKE_CXX_FLAGS} -fprofile-arcs -ftest-coverage") + set(CMAKE_BUILD_TYPE RELWITHDEBINFO) + set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lgcov") +elseif (${VARIANT} STREQUAL "asan") + set(CMAKE_CXX_FLAGS + "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer") + set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address") + set(CMAKE_BUILD_TYPE RELWITHDEBINFO) +elseif (${VARIANT} STREQUAL "usan") + set(CMAKE_CXX_FLAGS + "${CMAKE_CXX_FLAGS} -fsanitize=undefined -fno-omit-frame-pointer") + set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=undefined") + set(CMAKE_BUILD_TYPE RELWITHDEBINFO) +elseif (${VARIANT} STREQUAL "debug") + set(CMAKE_BUILD_TYPE DEBUG) +elseif (${VARIANT} STREQUAL "release") + set(CMAKE_BUILD_TYPE RELEASE) endif() message ("cxx Flags: " ${CMAKE_CXX_FLAGS}) diff --git a/doc/quickref.xml b/doc/quickref.xml index d025b35676..729a0ad5fb 100644 --- a/doc/quickref.xml +++ b/doc/quickref.xml @@ -43,6 +43,7 @@ body_max_size headers_max_size + skip_body Type Traits @@ -85,6 +86,7 @@ ping_data stream reason_string + teardown_tag Options diff --git a/doc/websocket.qbk b/doc/websocket.qbk index da2808cc0b..ff9c235e83 100644 --- a/doc/websocket.qbk +++ b/doc/websocket.qbk @@ -66,7 +66,7 @@ both Boost.Asio and the WebSocket protocol specification described in -[section:creating Creating the socket] +[section:creation Creation] The interface to Beast's WebSocket implementation is a single template class [link beast.ref.websocket__stream `beast::websocket::stream`] which @@ -75,24 +75,40 @@ of [link beast.types.streams.SyncStream [*`SyncReadStream`]] if synchronous operations are performed, or [link beast.types.streams.AsyncStream [*`AsyncStream`]] if asynchronous operations are performed, or both. Arguments supplied during construction are -passed to next layer's constructor. Here we declare two websockets which have -ownership of the next layer: +passed to next layer's constructor. Here we declare a websocket stream over +a TCP/IP socket with ownership of the socket: ``` boost::asio::io_service ios; beast::websocket::stream ws(ios); +``` + +[heading Using SSL] + +To use WebSockets over SSL, choose an SSL stream for the next layer template +argument when constructing the stream. +``` +#include +#include +#include +boost::asio::io_service ios; boost::asio::ssl::context ctx(boost::asio::ssl::context::sslv23); -beast::websocket::stream< - boost::asio::ssl::stream> wss(ios, ctx); +beast::websocket::stream ws(ios, ctx); ``` +[note + When creating websocket stream objects using SSL, it is necessary + to include the file ``. +] + +[heading Non-owning references] + For servers that can handshake in multiple protocols, it may be desired to wrap an object that already exists. This socket can be moved in: ``` boost::asio::ip::tcp::socket&& sock; ... - beast::websocket::stream< - boost::asio::ip::tcp::socket> ws(std::move(sock)); + beast::websocket::stream ws(std::move(sock)); ``` Or, the wrapper can be constructed with a non-owning reference. In @@ -108,8 +124,7 @@ The layer being wrapped can be accessed through the websocket's "next layer", permitting callers to interact directly with its interface. ``` boost::asio::ssl::context ctx(boost::asio::ssl::context::sslv23); - beast::websocket::stream< - boost::asio::ssl::stream> ws(ios, ctx); + beast::websocket::stream> ws(ios, ctx); ... ws.next_layer().shutdown(); // ssl::stream shutdown ``` diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 5b3d09721e..a67c0d8b0c 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -13,21 +13,20 @@ add_executable (http-crawl ) if (NOT WIN32) - target_link_libraries(http-crawl ${Boost_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) + target_link_libraries(http-crawl ${Boost_LIBRARIES} Threads::Threads) endif() add_executable (http-server ${BEAST_INCLUDES} file_body.hpp + mime_type.hpp http_async_server.hpp - http_stream.hpp - http_stream.ipp http_sync_server.hpp http_server.cpp ) if (NOT WIN32) - target_link_libraries(http-server ${Boost_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) + target_link_libraries(http-server ${Boost_LIBRARIES} Threads::Threads) endif() add_executable (http-example @@ -36,7 +35,7 @@ add_executable (http-example ) if (NOT WIN32) - target_link_libraries(http-example ${Boost_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) + target_link_libraries(http-example ${Boost_LIBRARIES} Threads::Threads) endif() add_executable (websocket-example @@ -45,5 +44,5 @@ add_executable (websocket-example ) if (NOT WIN32) - target_link_libraries(websocket-example ${Boost_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) + target_link_libraries(websocket-example ${Boost_LIBRARIES} Threads::Threads) endif() diff --git a/examples/http_async_server.hpp b/examples/http_async_server.hpp index bcb088d160..501aa8a586 100644 --- a/examples/http_async_server.hpp +++ b/examples/http_async_server.hpp @@ -9,9 +9,11 @@ #define BEAST_EXAMPLE_HTTP_ASYNC_SERVER_H_INCLUDED #include "file_body.hpp" -#include "http_stream.hpp" +#include "mime_type.hpp" +#include #include +#include #include #include #include @@ -32,17 +34,19 @@ class http_async_server using req_type = request_v1; using resp_type = response_v1; + std::mutex m_; + bool log_ = true; boost::asio::io_service ios_; - socket_type sock_; boost::asio::ip::tcp::acceptor acceptor_; + socket_type sock_; std::string root_; std::vector thread_; public: http_async_server(endpoint_type const& ep, int threads, std::string const& root) - : sock_(ios_) - , acceptor_(ios_) + : acceptor_(ios_) + , sock_(ios_) , root_(root) { acceptor_.open(ep.protocol()); @@ -67,13 +71,124 @@ class http_async_server t.join(); } + template + void + log(Args const&... args) + { + if(log_) + { + std::lock_guard lock(m_); + log_args(args...); + } + } + private: + template + class write_op + { + using alloc_type = + handler_alloc; + + struct data + { + Stream& s; + message_v1 m; + Handler h; + bool cont; + + template + data(DeducedHandler&& h_, Stream& s_, + message_v1&& m_) + : s(s_) + , m(std::move(m_)) + , h(std::forward(h_)) + , cont(boost_asio_handler_cont_helpers:: + is_continuation(h)) + { + } + }; + + std::shared_ptr d_; + + public: + write_op(write_op&&) = default; + write_op(write_op const&) = default; + + template + write_op(DeducedHandler&& h, Stream& s, Args&&... args) + : d_(std::allocate_shared(alloc_type{h}, + std::forward(h), s, + std::forward(args)...)) + { + (*this)(error_code{}, false); + } + + void + operator()(error_code ec, bool again = true) + { + auto& d = *d_; + d.cont = d.cont || again; + if(! again) + { + beast::http::async_write(d.s, d.m, std::move(*this)); + return; + } + d.h(ec); + } + + friend + void* asio_handler_allocate( + std::size_t size, write_op* op) + { + return boost_asio_handler_alloc_helpers:: + allocate(size, op->d_->h); + } + + friend + void asio_handler_deallocate( + void* p, std::size_t size, write_op* op) + { + return boost_asio_handler_alloc_helpers:: + deallocate(p, size, op->d_->h); + } + + friend + bool asio_handler_is_continuation(write_op* op) + { + return op->d_->cont; + } + + template + friend + void asio_handler_invoke(Function&& f, write_op* op) + { + return boost_asio_handler_invoke_helpers:: + invoke(f, op->d_->h); + } + }; + + template + static + void + async_write(Stream& stream, message_v1< + isRequest, Body, Headers>&& msg, + DeducedHandler&& handler) + { + write_op::type, + isRequest, Body, Headers>{std::forward( + handler), stream, std::move(msg)}; + } + class peer : public std::enable_shared_from_this { int id_; - stream stream_; + streambuf sb_; + socket_type sock_; + http_async_server& server_; boost::asio::io_service::strand strand_; - std::string root_; req_type req_; public: @@ -82,16 +197,22 @@ class http_async_server peer& operator=(peer&&) = delete; peer& operator=(peer const&) = delete; - explicit - peer(socket_type&& sock, std::string const& root) - : stream_(std::move(sock)) - , strand_(stream_.get_io_service()) - , root_(root) + peer(socket_type&& sock, http_async_server& server) + : sock_(std::move(sock)) + , server_(server) + , strand_(sock_.get_io_service()) { static int n = 0; id_ = ++n; } + void + fail(error_code ec, std::string what) + { + if(ec != boost::asio::error::operation_aborted) + server_.log("#", id_, " ", what, ": ", ec.message(), "\n"); + } + void run() { do_read(); @@ -99,43 +220,58 @@ class http_async_server void do_read() { - stream_.async_read(req_, strand_.wrap( + async_read(sock_, sb_, req_, strand_.wrap( std::bind(&peer::on_read, shared_from_this(), asio::placeholders::error))); } - void on_read(error_code ec) + void on_read(error_code const& ec) { if(ec) return fail(ec, "read"); - do_read(); auto path = req_.url; if(path == "/") path = "/index.html"; - path = root_ + path; + path = server_.root_ + path; if(! boost::filesystem::exists(path)) { - response_v1 resp; - resp.status = 404; - resp.reason = "Not Found"; - resp.version = req_.version; - resp.headers.replace("Server", "http_async_server"); - resp.body = "The file '" + path + "' was not found"; - prepare(resp); - stream_.async_write(std::move(resp), + response_v1 res; + res.status = 404; + res.reason = "Not Found"; + res.version = req_.version; + res.headers.insert("Server", "http_async_server"); + res.headers.insert("Content-Type", "text/html"); + res.body = "The file '" + path + "' was not found"; + prepare(res); + async_write(sock_, std::move(res), std::bind(&peer::on_write, shared_from_this(), asio::placeholders::error)); return; } - resp_type resp; - resp.status = 200; - resp.reason = "OK"; - resp.version = req_.version; - resp.headers.replace("Server", "http_async_server"); - resp.headers.replace("Content-Type", "text/html"); - resp.body = path; - prepare(resp); - stream_.async_write(std::move(resp), + resp_type res; + res.status = 200; + res.reason = "OK"; + res.version = req_.version; + res.headers.insert("Server", "http_async_server"); + res.headers.insert("Content-Type", mime_type(path)); + res.body = path; + try + { + prepare(res); + } + catch(std::exception const& e) + { + res = {}; + res.status = 500; + res.reason = "Internal Error"; + res.version = req_.version; + res.headers.insert("Server", "http_async_server"); + res.headers.insert("Content-Type", "text/html"); + res.body = + std::string{"An internal error occurred"} + e.what(); + prepare(res); + } + async_write(sock_, std::move(res), std::bind(&peer::on_write, shared_from_this(), asio::placeholders::error)); } @@ -144,36 +280,27 @@ class http_async_server { if(ec) fail(ec, "write"); - } - - private: - void - fail(error_code ec, std::string what) - { - if(ec != boost::asio::error::operation_aborted) - { - std::cerr << - "#" << std::to_string(id_) << " " << - what << ": " << ec.message() << std::endl; - } + do_read(); } }; void - fail(error_code ec, std::string what) + log_args() { - std::cerr << - what << ": " << ec.message() << std::endl; } + template void - maybe_throw(error_code ec, std::string what) + log_args(Arg const& arg, Args const&... args) { - if(ec) - { - fail(ec, what); - throw ec; - } + std::cerr << arg; + log_args(args...); + } + + void + fail(error_code ec, std::string what) + { + log(what, ": ", ec.message(), "\n"); } void @@ -181,12 +308,13 @@ class http_async_server { if(! acceptor_.is_open()) return; - maybe_throw(ec, "accept"); + if(ec) + return fail(ec, "accept"); socket_type sock(std::move(sock_)); acceptor_.async_accept(sock_, std::bind(&http_async_server::on_accept, this, asio::placeholders::error)); - std::make_shared(std::move(sock), root_)->run(); + std::make_shared(std::move(sock), *this)->run(); } }; diff --git a/examples/http_crawl.cpp b/examples/http_crawl.cpp index b995c2011b..9948851fbd 100644 --- a/examples/http_crawl.cpp +++ b/examples/http_crawl.cpp @@ -5,9 +5,10 @@ // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // -#include "http_stream.hpp" #include "urls_large_data.hpp" +#include +#include #include #include @@ -31,9 +32,9 @@ int main(int, char const*[]) ip::tcp::resolver r(ios); auto it = r.resolve( ip::tcp::resolver::query{host, "http"}); - stream hs(ios); - connect(hs.lowest_layer(), it); - auto ep = hs.lowest_layer().remote_endpoint(); + ip::tcp::socket sock(ios); + connect(sock, it); + auto ep = sock.remote_endpoint(); request_v1 req; req.method = "GET"; req.url = "/"; @@ -42,10 +43,11 @@ int main(int, char const*[]) std::string(":") + std::to_string(ep.port())); req.headers.insert("User-Agent", "beast/http"); prepare(req); - hs.write(req); - response_v1 resp; - hs.read(resp); - std::cout << resp; + write(sock, req); + response_v1 res; + streambuf sb; + beast::http::read(sock, sb, res); + std::cout << res; } catch(boost::system::system_error const& ec) { diff --git a/examples/http_server.cpp b/examples/http_server.cpp index 32d1b13b7e..246508a8e0 100644 --- a/examples/http_server.cpp +++ b/examples/http_server.cpp @@ -57,8 +57,13 @@ int main(int ac, char const* av[]) endpoint_type ep{address_type::from_string(ip), port}; if(sync) + { http_sync_server server(ep, root); + beast::test::sig_wait(); + } else + { http_async_server server(ep, threads, root); - beast::test::sig_wait(); + beast::test::sig_wait(); + } } diff --git a/examples/http_stream.hpp b/examples/http_stream.hpp deleted file mode 100644 index eb07f63786..0000000000 --- a/examples/http_stream.hpp +++ /dev/null @@ -1,480 +0,0 @@ -// -// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail 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 BEAST_HTTP_STREAM_H_INCLUDED -#define BEAST_HTTP_STREAM_H_INCLUDED - -#include -#include -#include -#include -#include -#include - -namespace beast { -namespace http { - -namespace detail { - -class stream_base -{ -protected: - struct op - : boost::intrusive::list_base_hook< - boost::intrusive::link_mode< - boost::intrusive::normal_link>> - { - virtual ~op() = default; - virtual void operator()() = 0; - virtual void cancel() = 0; - }; - - using op_list = typename boost::intrusive::make_list< - op, boost::intrusive::constant_time_size>::type; - - op_list wr_q_; - bool wr_active_ = false; -}; - -} // detail - -/** Provides message-oriented functionality using HTTP. - - The stream class template provides asynchronous and blocking - message-oriented functionality necessary for clients and servers - to utilize the HTTP protocol. - - @par Thread Safety - @e Distinct @e objects: Safe.@n - @e Shared @e objects: Unsafe. The application must ensure that - all asynchronous operations are performed within the same - implicit or explicit strand. - - @par Example - - To use the class template with an `ip::tcp::socket`, you would write: - - @code - http::stream hs(io_service); - @endcode - Alternatively, you can write: - @code - ip::tcp::socket sock(io_service); - http::stream hs(sock); - @endcode - - @note A stream object must not be destroyed while there are - pending asynchronous operations associated with it. - - @par Concepts - AsyncReadStream, AsyncWriteStream, Stream, SyncReadStream, SyncWriteStream. - */ -template> -class stream : public detail::stream_base -{ - NextLayer next_layer_; - basic_streambuf rd_buf_; - -public: - /// The type of the next layer. - using next_layer_type = - typename std::remove_reference::type; - - /// The type of the lowest layer. - using lowest_layer_type = - typename next_layer_type::lowest_layer_type; - - /// The type of endpoint of the lowest layer. - using endpoint_type = - typename lowest_layer_type::endpoint_type; - - /// The protocol of the next layer. - using protocol_type = - typename lowest_layer_type::protocol_type; - - /// The type of resolver of the next layer. - using resolver_type = - typename protocol_type::resolver; - - /** Destructor. - - @note A stream object must not be destroyed while there - are pending asynchronous operations associated with it. - */ - ~stream(); - - /** Move constructor. - - Undefined behavior if operations are active or pending. - */ - stream(stream&&) = default; - - /** Move assignment. - - Undefined behavior if operations are active or pending. - */ - stream& operator=(stream&&) = default; - - /** Construct a HTTP stream. - - This constructor creates a HTTP stream and initialises - the next layer. - - @throws Any exceptions thrown by the Stream constructor. - - @param args The arguments to be passed to initialise the - next layer. The arguments are forwarded to the next layer's - constructor. - */ - template - explicit - stream(Args&&... args); - - /** Get the io_service associated with the stream. - - This function may be used to obtain the io_service object - that the stream uses to dispatch handlers for asynchronous - operations. - - @return A reference to the io_service object that the stream - will use to dispatch handlers. Ownership is not transferred - to the caller. - */ - boost::asio::io_service& - get_io_service() - { - return next_layer_.lowest_layer().get_io_service(); - } - - /** Get a reference to the next layer. - - This function returns a reference to the next layer - in a stack of stream layers. - - @return A reference to the next layer in the stack of - stream layers. Ownership is not transferred to the caller. - */ - next_layer_type& - next_layer() - { - return next_layer_; - } - - /** Get a reference to the next layer. - - This function returns a reference to the next layer in a - stack of stream layers. - - @return A reference to the next layer in the stack of - stream layers. Ownership is not transferred to the caller. - */ - next_layer_type const& - next_layer() const - { - return next_layer_; - } - - /** Get a reference to the lowest layer. - - This function returns a reference to the lowest layer - in a stack of stream layers. - - @return A reference to the lowest layer in the stack of - stream layers. Ownership is not transferred to the caller. - */ - lowest_layer_type& - lowest_layer() - { - return next_layer_.lowest_layer(); - } - - /** Get a reference to the lowest layer. - - This function returns a reference to the lowest layer - in a stack of stream layers. - - @return A reference to the lowest layer in the stack of - stream layers. Ownership is not transferred to the caller. - */ - lowest_layer_type const& - lowest_layer() const - { - return next_layer_.lowest_layer(); - } - - /** Cancel pending operations. - - This will cancel all of the asynchronous operations pending, - including pipelined writes that have not been started. Handlers for - canceled writes will be called with - `boost::asio::error::operation_aborted`. - - @throws boost::system::system_error Thrown on failure. - */ - void - cancel() - { - error_code ec; - cancel(ec); - if(ec) - throw system_error{ec}; - } - - /** Cancel pending operations. - - This will cancel all of the asynchronous operations pending, - including pipelined writes that have not been started. Handlers for - canceled writes will be called with - `boost::asio::error::operation_aborted`. - - @param ec Set to indicate what error occurred, if any. - */ - void - cancel(error_code& ec); - - /** Read a HTTP message from the stream. - - This function is used to read a single HTTP message from the stream. - The call will block until one of the followign conditions is true: - - @li A message has been read. - - @li An error occurred. - - The operation is implemented in terms of zero or more calls to the - next layer's `read_some` function. - - @param msg An object used to store the message. The previous - contents of the object will be overwritten. - - @throws boost::system::system_error Thrown on failure. - */ - template - void - read(message_v1& msg) - { - error_code ec; - read(msg, ec); - if(ec) - throw system_error{ec}; - } - - /** Read a HTTP message from the stream. - - This function is used to read a single HTTP message from the stream. - The call will block until one of the followign conditions is true: - - @li A message has been read. - - @li An error occurred. - - The operation is implemented in terms of zero or more calls to the - next layer's `read_some` function. - - @param msg An object used to store the message. The previous - contents of the object will be overwritten. - - @param ec Set to indicate what error occurred, if any. - */ - template - void - read(message_v1& msg, - error_code& ec); - - /** Start reading a HTTP message from the stream asynchronously. - - This function is used to asynchronously read a single HTTP message - from the stream. The function call always returns immediately. The - asynchronous operation will continue until one of the following - conditions is true: - - @li The message has been written. - - @li An error occurred. - - This operation is implemented in terms of zero or more calls to the - next layer's async_read_some function, and is known as a composed - operation. The program must ensure that the stream performs no other - read operations or any other composed operations that perform reads - until this operation completes. - - @param msg An object used to store the message. The previous - contents of the object will be overwritten. Ownership of the message - is not transferred; the caller must guarantee that the object remains - valid until the handler is called. - - @param handler The handler to be called when the request completes. - Copies will be made of the handler as required. The equivalent - function signature of the handler must be: - @code void handler( - error_code const& error // result of operation - ); @endcode - Regardless of whether the asynchronous operation completes - immediately or not, the handler will not be invoked from within - this function. Invocation of the handler will be performed in a - manner equivalent to using boost::asio::io_service::post(). - */ - template -#if GENERATING_DOCS - void_or_deduced -#else - typename async_completion< - ReadHandler, void(error_code)>::result_type -#endif - async_read(message_v1& msg, - ReadHandler&& handler); - - /** Write a HTTP message to the stream. - - This function is used to write a single HTTP message to the - stream. The call will block until one of the following conditions - is true: - - @li The entire message is sent. - - @li An error occurred. - - If the semantics of the message require that the connection is - closed to indicate the end of the content body, - `boost::asio::error::eof` is thrown after the message is sent. - successfuly. The caller is responsible for actually closing the - connection. For regular TCP/IP streams this means shutting down the - send side, while SSL streams may call the SSL shutdown function. - - @param msg The message to send. - - @throws boost::system::system_error Thrown on failure. - */ - template - void - write(message_v1 const& msg) - { - error_code ec; - write(msg, ec); - if(ec) - throw system_error{ec}; - } - - /** Write a HTTP message to the stream. - - This function is used to write a single HTTP message to the - stream. The call will block until one of the following conditions - is true: - - @li The entire message is sent. - - @li An error occurred. - - If the semantics of the message require that the connection is - closed to indicate the end of the content body, - `boost::asio::error::eof` is returned after the message is sent. - successfuly. The caller is responsible for actually closing the - connection. For regular TCP/IP streams this means shutting down the - send side, while SSL streams may call the SSL shutdown function. - - @param msg The message to send. - - @param ec Set to the error, if any occurred. - */ - template - void - write(message_v1 const& msg, - error_code& ec); - - /** Start pipelining a HTTP message to the stream asynchronously. - - This function is used to queue a message to be sent on the stream. - Unlike the free function, this version will place the message on an - outgoing message queue if there is already a write pending. - - If the semantics of the message require that the connection is - closed to indicate the end of the content body, the handler - is called with the error `boost::asio::error::eof` after the message - has been sent successfully. The caller is responsible for actually - closing the connection. For regular TCP/IP streams this means - shutting down the send side, while SSL streams may call the SSL - `async_shutdown` function. - - @param msg The message to send. A copy of the message will be made. - - @param handler The handler to be called when the request completes. - Copies will be made of the handler as required. The equivalent - function signature of the handler must be: - @code void handler( - error_code const& error // result of operation - ); @endcode - Regardless of whether the asynchronous operation completes - immediately or not, the handler will not be invoked from within - this function. Invocation of the handler will be performed in a - manner equivalent to using boost::asio::io_service::post(). - */ - template -#if GENERATING_DOCS - void_or_deduced -#else - typename async_completion< - WriteHandler, void(error_code)>::result_type -#endif - async_write(message_v1 const& msg, - WriteHandler&& handler); - - /** Start pipelining a HTTP message to the stream asynchronously. - - This function is used to queue a message to be sent on the stream. - Unlike the free function, this version will place the message on an - outgoing message queue if there is already a write pending. - - If the semantics of the message require that the connection is - closed to indicate the end of the content body, the handler - is called with the error boost::asio::error::eof. The caller is - responsible for actually closing the connection. For regular - TCP/IP streams this means shutting down the send side, while SSL - streams may call the SSL async_shutdown function. - - @param msg The message to send. Ownership of the message, which - must be movable, is transferred to the implementation. The message - will not be destroyed until the asynchronous operation completes. - - @param handler The handler to be called when the request completes. - Copies will be made of the handler as required. The equivalent - function signature of the handler must be: - @code void handler( - error_code const& error // result of operation - ); @endcode - Regardless of whether the asynchronous operation completes - immediately or not, the handler will not be invoked from within - this function. Invocation of the handler will be performed in a - manner equivalent to using boost::asio::io_service::post(). - */ - template - #if GENERATING_DOCS - void_or_deduced - #else - typename async_completion< - WriteHandler, void(error_code)>::result_type - #endif - async_write(message_v1&& msg, - WriteHandler&& handler); - -private: - template class read_op; - template class write_op; - - void - cancel_all(); -}; - -} // http -} // beast - -#include "http_stream.ipp" - -#endif diff --git a/examples/http_stream.ipp b/examples/http_stream.ipp deleted file mode 100644 index 4e9df6e77d..0000000000 --- a/examples/http_stream.ipp +++ /dev/null @@ -1,412 +0,0 @@ -// -// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail 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 BEAST_HTTP_STREAM_IPP_INCLUDED -#define BEAST_HTTP_STREAM_IPP_INCLUDED - -#include -#include -#include -#include -#include -#include - -namespace beast { -namespace http { - -template -template -class stream::read_op -{ - using alloc_type = - handler_alloc; - - struct data - { - stream& s; - message_v1& m; - Handler h; - bool cont; - int state = 0; - - template - data(DeducedHandler&& h_, stream& s_, - message_v1& m_) - : s(s_) - , m(m_) - , h(std::forward(h_)) - , cont(boost_asio_handler_cont_helpers:: - is_continuation(h)) - { - } - }; - - std::shared_ptr d_; - -public: - read_op(read_op&&) = default; - read_op(read_op const&) = default; - - template - read_op(DeducedHandler&& h, - stream& s, Args&&... args) - : d_(std::allocate_shared(alloc_type{h}, - std::forward(h), s, - std::forward(args)...)) - { - (*this)(error_code{}, false); - } - - void operator()(error_code const& ec, bool again = true); - - friend - void* asio_handler_allocate( - std::size_t size, read_op* op) - { - return boost_asio_handler_alloc_helpers:: - allocate(size, op->d_->h); - } - - friend - void asio_handler_deallocate( - void* p, std::size_t size, read_op* op) - { - return boost_asio_handler_alloc_helpers:: - deallocate(p, size, op->d_->h); - } - - friend - bool asio_handler_is_continuation(read_op* op) - { - return op->d_->cont; - } - - template - friend - void asio_handler_invoke(Function&& f, read_op* op) - { - return boost_asio_handler_invoke_helpers:: - invoke(f, op->d_->h); - } -}; - -template -template -void -stream:: -read_op:: -operator()(error_code const& ec, bool again) -{ - auto& d = *d_; - d.cont = d.cont || again; - while(! ec && d.state != 99) - { - switch(d.state) - { - case 0: - d.state = 99; - beast::http::async_read(d.s.next_layer_, - d.s.rd_buf_, d.m, std::move(*this)); - return; - } - } - d.h(ec); -} - -//------------------------------------------------------------------------------ - -template -template -class stream::write_op : public op -{ - using alloc_type = - handler_alloc; - - struct data - { - stream& s; - message_v1 m; - Handler h; - bool cont; - int state = 0; - - template - data(DeducedHandler&& h_, stream& s_, - message_v1 const& m_, - bool cont_) - : s(s_) - , m(m_) - , h(std::forward(h_)) - , cont(cont_) - { - } - - template - data(DeducedHandler&& h_, stream& s_, - message_v1&& m_, - bool cont_) - : s(s_) - , m(std::move(m_)) - , h(std::forward(h_)) - , cont(cont_) - { - } - }; - - std::shared_ptr d_; - -public: - write_op(write_op&&) = default; - write_op(write_op const&) = default; - - template - write_op(DeducedHandler&& h, - stream& s, Args&&... args) - : d_(std::allocate_shared(alloc_type{h}, - std::forward(h), s, - std::forward(args)...)) - { - } - - void - operator()() override - { - (*this)(error_code{}, false); - } - - void cancel() override; - - void operator()(error_code const& ec, bool again = true); - - friend - void* asio_handler_allocate( - std::size_t size, write_op* op) - { - return boost_asio_handler_alloc_helpers:: - allocate(size, op->d_->h); - } - - friend - void asio_handler_deallocate( - void* p, std::size_t size, write_op* op) - { - return boost_asio_handler_alloc_helpers:: - deallocate(p, size, op->d_->h); - } - - friend - bool asio_handler_is_continuation(write_op* op) - { - return op->d_->cont; - } - - template - friend - void asio_handler_invoke(Function&& f, write_op* op) - { - return boost_asio_handler_invoke_helpers:: - invoke(f, op->d_->h); - } -}; - -template -template -void -stream:: -write_op:: -cancel() -{ - auto& d = *d_; - d.s.get_io_service().post( - bind_handler(std::move(*this), - boost::asio::error::operation_aborted)); -} - -template -template -void -stream:: -write_op:: -operator()(error_code const& ec, bool again) -{ - auto& d = *d_; - d.cont = d.cont || again; - while(! ec && d.state != 99) - { - switch(d.state) - { - case 0: - d.state = 99; - beast::http::async_write(d.s.next_layer_, - d.m, std::move(*this)); - return; - } - } - d.h(ec); - if(! d.s.wr_q_.empty()) - { - auto& op = d.s.wr_q_.front(); - op(); - // VFALCO Use allocator - delete &op; - d.s.wr_q_.pop_front(); - } - else - { - d.s.wr_active_ = false; - } -} - -//------------------------------------------------------------------------------ - -template -stream:: -~stream() -{ - // Can't destroy with pending operations! - assert(wr_q_.empty()); -} - -template -template -stream:: -stream(Args&&... args) - : next_layer_(std::forward(args)...) -{ -} - -template -void -stream:: -cancel(error_code& ec) -{ - cancel_all(); - lowest_layer().cancel(ec); -} - -template -template -void -stream:: -read(message_v1& msg, - error_code& ec) -{ - beast::http::read(next_layer_, rd_buf_, msg, ec); -} - -template -template -auto -stream:: -async_read(message_v1& msg, - ReadHandler&& handler) -> - typename async_completion< - ReadHandler, void(error_code)>::result_type -{ - async_completion< - ReadHandler, void(error_code) - > completion(handler); - read_op{ - completion.handler, *this, msg}; - return completion.result.get(); -} - -template -template -void -stream:: -write(message_v1 const& msg, - error_code& ec) -{ - beast::http::write(next_layer_, msg, ec); -} - -template -template -auto -stream:: -async_write(message_v1 const& msg, - WriteHandler&& handler) -> - typename async_completion< - WriteHandler, void(error_code)>::result_type -{ - async_completion< - WriteHandler, void(error_code)> completion(handler); - auto const cont = wr_active_ || - boost_asio_handler_cont_helpers::is_continuation(handler); - if(! wr_active_) - { - wr_active_ = true; - write_op{ - completion.handler, *this, msg, cont }(); - } - else - { - // VFALCO Use allocator - wr_q_.push_back(*new write_op( - completion.handler, *this, msg, cont)); - } - return completion.result.get(); -} - -template -template -auto -stream:: -async_write(message_v1&& msg, - WriteHandler&& handler) -> - typename async_completion< - WriteHandler, void(error_code)>::result_type -{ - async_completion< - WriteHandler, void(error_code)> completion(handler); - auto const cont = wr_active_ || - boost_asio_handler_cont_helpers::is_continuation(handler); - if(! wr_active_) - { - wr_active_ = true; - write_op{completion.handler, - *this, std::move(msg), cont}(); - } - else - { - // VFALCO Use allocator - wr_q_.push_back(*new write_op(completion.handler, - *this, std::move(msg), cont)); - } - return completion.result.get(); -} - -template -void -stream:: -cancel_all() -{ - for(auto it = wr_q_.begin(); it != wr_q_.end();) - { - auto& op = *it++; - op.cancel(); - // VFALCO Use allocator - delete &op; - } - wr_q_.clear(); -} - -} // http -} // beast - -#endif diff --git a/examples/http_sync_server.hpp b/examples/http_sync_server.hpp index af3f1d196e..8370684723 100644 --- a/examples/http_sync_server.hpp +++ b/examples/http_sync_server.hpp @@ -9,8 +9,9 @@ #define BEAST_EXAMPLE_HTTP_SYNC_SERVER_H_INCLUDED #include "file_body.hpp" -#include "http_stream.hpp" +#include "mime_type.hpp" +#include #include #include #include @@ -34,6 +35,8 @@ class http_sync_server using req_type = request_v1; using resp_type = response_v1; + bool log_ = true; + std::mutex m_; boost::asio::io_service ios_; socket_type sock_; boost::asio::ip::tcp::acceptor acceptor_; @@ -65,21 +68,43 @@ class http_sync_server thread_.join(); } + template + void + log(Args const&... args) + { + if(log_) + { + std::lock_guard lock(m_); + log_args(args...); + } + } + +private: + void + log_args() + { + } + + template + void + log_args(Arg const& arg, Args const&... args) + { + std::cerr << arg; + log_args(args...); + } + void fail(error_code ec, std::string what) { - std::cerr << - what << ": " << ec.message() << std::endl; + log(what, ": ", ec.message(), "\n"); } void - maybe_throw(error_code ec, std::string what) + fail(int id, error_code const& ec) { - if(ec) - { - fail(ec, what); - throw ec; - } + if(ec != boost::asio::error::operation_aborted && + ec != boost::asio::error::eof) + log("#", id, " ", ec.message(), "\n"); } struct lambda @@ -109,7 +134,8 @@ class http_sync_server { if(! acceptor_.is_open()) return; - maybe_throw(ec, "accept"); + if(ec) + return fail(ec, "accept"); static int id_ = 0; std::thread{lambda{++id_, *this, std::move(sock_)}}.detach(); acceptor_.async_accept(sock_, @@ -118,23 +144,15 @@ class http_sync_server } void - fail(int id, error_code const& ec) - { - if(ec != boost::asio::error::operation_aborted && - ec != boost::asio::error::eof) - std::cerr << - "#" << std::to_string(id) << " " << std::endl; - } - - void - do_peer(int id, socket_type&& sock) + do_peer(int id, socket_type&& sock0) { - http::stream hs(std::move(sock)); + socket_type sock(std::move(sock0)); + streambuf sb; error_code ec; for(;;) { req_type req; - hs.read(req, ec); + http::read(sock, sb, req, ec); if(ec) break; auto path = req.url; @@ -143,26 +161,42 @@ class http_sync_server path = root_ + path; if(! boost::filesystem::exists(path)) { - response_v1 resp; - resp.status = 404; - resp.reason = "Not Found"; - resp.version = req.version; - resp.headers.replace("Server", "http_sync_server"); - resp.body = "The file '" + path + "' was not found"; - prepare(resp); - hs.write(resp, ec); + response_v1 res; + res.status = 404; + res.reason = "Not Found"; + res.version = req.version; + res.headers.insert("Server", "http_sync_server"); + res.headers.insert("Content-Type", "text/html"); + res.body = "The file '" + path + "' was not found"; + prepare(res); + write(sock, res, ec); if(ec) break; } - resp_type resp; - resp.status = 200; - resp.reason = "OK"; - resp.version = req.version; - resp.headers.replace("Server", "http_sync_server"); - resp.headers.replace("Content-Type", "text/html"); - resp.body = path; - prepare(resp); - hs.write(resp, ec); + resp_type res; + res.status = 200; + res.reason = "OK"; + res.version = req.version; + res.headers.insert("Server", "http_sync_server"); + res.headers.insert("Content-Type", mime_type(path)); + res.body = path; + try + { + prepare(res); + } + catch(std::exception const& e) + { + res = {}; + res.status = 500; + res.reason = "Internal Error"; + res.version = req.version; + res.headers.insert("Server", "http_sync_server"); + res.headers.insert("Content-Type", "text/html"); + res.body = + std::string{"An internal error occurred"} + e.what(); + prepare(res); + } + write(sock, res, ec); if(ec) break; } diff --git a/examples/mime_type.hpp b/examples/mime_type.hpp new file mode 100644 index 0000000000..d20aa605a4 --- /dev/null +++ b/examples/mime_type.hpp @@ -0,0 +1,51 @@ +// +// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail 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 BEAST_EXAMPLE_HTTP_MIME_TYPE_H_INCLUDED +#define BEAST_EXAMPLE_HTTP_MIME_TYPE_H_INCLUDED + +#include +#include + +namespace beast { +namespace http { + +// Return the Mime-Type for a given file extension +template +std::string +mime_type(std::string const& path) +{ + auto const ext = + boost::filesystem::path{path}.extension().string(); + if(ext == ".txt") return "text/plain"; + if(ext == ".htm") return "text/html"; + if(ext == ".html") return "text/html"; + if(ext == ".php") return "text/html"; + if(ext == ".css") return "text/css"; + if(ext == ".js") return "application/javascript"; + if(ext == ".json") return "application/json"; + if(ext == ".xml") return "application/xml"; + if(ext == ".swf") return "application/x-shockwave-flash"; + if(ext == ".flv") return "video/x-flv"; + if(ext == ".png") return "image/png"; + if(ext == ".jpe") return "image/jpeg"; + if(ext == ".jpeg") return "image/jpeg"; + if(ext == ".jpg") return "image/jpeg"; + if(ext == ".gif") return "image/gif"; + if(ext == ".bmp") return "image/bmp"; + if(ext == ".ico") return "image/vnd.microsoft.icon"; + if(ext == ".tiff") return "image/tiff"; + if(ext == ".tif") return "image/tiff"; + if(ext == ".svg") return "image/svg+xml"; + if(ext == ".svgz") return "image/svg+xml"; + return "application/text"; +} + +} // http +} // beast + +#endif diff --git a/extras/beast/test/fail_stream.hpp b/extras/beast/test/fail_stream.hpp index 1c3a34e267..47742d06cc 100644 --- a/extras/beast/test/fail_stream.hpp +++ b/extras/beast/test/fail_stream.hpp @@ -161,19 +161,21 @@ class fail_stream friend void - teardown(fail_stream& stream, - boost::system::error_code& ec) + teardown(websocket::teardown_tag, + fail_stream& stream, + boost::system::error_code& ec) { if(stream.pfc_->fail(ec)) return; - websocket_helpers::call_teardown(stream.next_layer(), ec); + beast::websocket_helpers::call_teardown(stream.next_layer(), ec); } template friend void - async_teardown(fail_stream& stream, - TeardownHandler&& handler) + async_teardown(websocket::teardown_tag, + fail_stream& stream, + TeardownHandler&& handler) { error_code ec; if(stream.pfc_->fail(ec)) @@ -182,7 +184,7 @@ class fail_stream bind_handler(std::move(handler), ec)); return; } - websocket_helpers::call_async_teardown( + beast::websocket_helpers::call_async_teardown( stream.next_layer(), std::forward(handler)); } }; diff --git a/include/beast/http/impl/message_v1.ipp b/include/beast/http/impl/message_v1.ipp index d0b69e4ef6..e2a2568d88 100644 --- a/include/beast/http/impl/message_v1.ipp +++ b/include/beast/http/impl/message_v1.ipp @@ -8,6 +8,7 @@ #ifndef BEAST_HTTP_IMPL_MESSAGE_V1_IPP #define BEAST_HTTP_IMPL_MESSAGE_V1_IPP +#include #include #include #include @@ -87,7 +88,11 @@ prepare_content_length(prepare_info& pi, std::true_type) { typename Body::writer w(msg); - //w.init(ec); // VFALCO This is a design problem! + // VFALCO This is a design problem! + error_code ec; + w.init(ec); + if(ec) + throw system_error{ec}; pi.content_length = w.content_length(); } diff --git a/include/beast/http/impl/read.ipp b/include/beast/http/impl/read.ipp index 38c09eb568..24b484e335 100644 --- a/include/beast/http/impl/read.ipp +++ b/include/beast/http/impl/read.ipp @@ -412,7 +412,7 @@ read(SyncReadStream& stream, DynamicBuffer& dynabuf, static_assert(is_ReadableBody::value, "ReadableBody requirements not met"); error_code ec; - read(stream, dynabuf, msg, ec); + beast::http::read(stream, dynabuf, msg, ec); if(ec) throw system_error{ec}; } @@ -431,7 +431,7 @@ read(SyncReadStream& stream, DynamicBuffer& dynabuf, static_assert(is_ReadableBody::value, "ReadableBody requirements not met"); parser_v1 p; - parse(stream, dynabuf, p, ec); + beast::http::parse(stream, dynabuf, p, ec); if(ec) return; assert(p.complete()); diff --git a/include/beast/http/parser_v1.hpp b/include/beast/http/parser_v1.hpp index 0909c298c9..aada5243b5 100644 --- a/include/beast/http/parser_v1.hpp +++ b/include/beast/http/parser_v1.hpp @@ -35,6 +35,37 @@ struct parser_response } // detail +/** Skip body option. + + The options controls whether or not the parser expects to see a + HTTP body, regardless of the presence or absence of certain fields + such as Content-Length. + + Depending on the request, some responses do not carry a body. + For example, a 200 response to a CONNECT request from a tunneling + proxy. In these cases, callers use the @ref skip_body option to + inform the parser that no body is expected. The parser will consider + the message complete after the all headers have been received. + + Example: + @code + parser_v1 p; + p.set_option(skip_body{true}); + @endcode + + @note Objects of this type are passed to @ref basic_parser_v1::set_option. +*/ +struct skip_body +{ + bool value; + + explicit + skip_body(bool v) + : value(v) + { + } +}; + /** A parser for producing HTTP/1 messages. This class uses the basic HTTP/1 wire format parser to convert @@ -62,6 +93,7 @@ class parser_v1 std::string value_; message_type m_; typename message_type::body_type::reader r_; + std::uint8_t skip_body_ = 0; public: parser_v1(parser_v1&&) = default; @@ -81,6 +113,13 @@ class parser_v1 { } + /// Set the expect body option. + void + set_option(skip_body const& o) + { + skip_body_ = o.value ? 1 : 0; + } + /** Returns the parsed message. Only valid if `complete()` would return `true`. @@ -176,7 +215,7 @@ class parser_v1 { flush(); m_.version = 10 * this->http_major() + this->http_minor(); - return 0; + return skip_body_; } void on_request(error_code& ec) diff --git a/include/beast/http/rfc7230.hpp b/include/beast/http/rfc7230.hpp index e429efba2b..3b6f52c4b0 100644 --- a/include/beast/http/rfc7230.hpp +++ b/include/beast/http/rfc7230.hpp @@ -30,6 +30,16 @@ namespace http { the behavior of the container will be as if a string containing only characters up to but excluding the first invalid character was used to construct the list. + + @code + for(auto const& param : param_list{";level=9;no_context_takeover;bits=15"}) + { + std::cout << ";" << param.first; + if(! param.second.empty()) + std::cout << "=" << param.second; + std::cout << "\n"; + } + @endcode */ class param_list { @@ -98,6 +108,24 @@ class param_list the behavior of the container will be as if a string containing only characters up to but excluding the first invalid character was used to construct the list. + + To use this class, construct with the string to be parsed and + then use @ref begin and @end, or range-for to iterate each + item: + + @code + for(auto const& ext : ext_list{"none, 7z;level=9, zip;no_context_takeover;bits=15"}) + { + std::cout << ext.first << "\n"; + for(auto const& param : ext.second) + { + std::cout << ";" << param.first; + if(! param.second.empty()) + std::cout << "=" << param.second; + std::cout << "\n"; + } + } + @endcode */ class ext_list { @@ -181,6 +209,15 @@ class ext_list the behavior of the container will be as if a string containing only characters up to but excluding the first invalid character was used to construct the list. + + To use this class, construct with the string to be parsed and + then use @ref begin and @end, or range-for to iterate each + item: + + @code + for(auto const& token : token_list{"apple, pear, banana"}) + std::cout << token << "\n"; + @endcode */ class token_list { diff --git a/include/beast/http/status.hpp b/include/beast/http/status.hpp deleted file mode 100644 index ee7a9c9c80..0000000000 --- a/include/beast/http/status.hpp +++ /dev/null @@ -1,71 +0,0 @@ -// -// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail 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 BEAST_HTTP_STATUS_HPP -#define BEAST_HTTP_STATUS_HPP - -namespace beast { -namespace http { - -/** Returns the string corresponding to the numeric HTTP status code. */ -template -char const* -status_text(int status) -{ - switch(status) - { - case 100: return "Continue"; - case 101: return "Switching Protocols"; - case 200: return "OK"; - case 201: return "Created"; - case 202: return "Accepted"; - case 203: return "Non-Authoritative Information"; - case 204: return "No Content"; - case 205: return "Reset Content"; - case 206: return "Partial Content"; - case 300: return "Multiple Choices"; - case 301: return "Moved Permanently"; - case 302: return "Found"; - case 303: return "See Other"; - case 304: return "Not Modified"; - case 305: return "Use Proxy"; -// case 306: return ""; - case 307: return "Temporary Redirect"; - case 400: return "Bad Request"; - case 401: return "Unauthorized"; - case 402: return "Payment Required"; - case 403: return "Forbidden"; - case 404: return "Not Found"; - case 405: return "Method Not Allowed"; - case 406: return "Not Acceptable"; - case 407: return "Proxy Authentication Required"; - case 408: return "Request Timeout"; - case 409: return "Conflict"; - case 410: return "Gone"; - case 411: return "Length Required"; - case 412: return "Precondition Failed"; - case 413: return "Request Entity Too Large"; - case 414: return "Request-URI Too Long"; - case 415: return "Unsupported Media Type"; - case 416: return "Requested Range Not Satisfiable"; - case 417: return "Expectation Failed"; - case 500: return "Internal Server Error"; - case 501: return "Not Implemented"; - case 502: return "Bad Gateway"; - case 503: return "Service Unavailable"; - case 504: return "Gateway Timeout"; - case 505: return "HTTP Version Not Supported"; - default: - break; - } - return "Unknown HTTP status"; -} - -} // http -} // beast - -#endif diff --git a/include/beast/version.hpp b/include/beast/version.hpp index e85e4107d1..75334f588d 100644 --- a/include/beast/version.hpp +++ b/include/beast/version.hpp @@ -14,8 +14,8 @@ // BEAST_VERSION / 100 % 1000 is the minor version // BEAST_VERSION / 100000 is the major version // -#define BEAST_VERSION 100006 +#define BEAST_VERSION 100000 -#define BEAST_VERSION_STRING "1.0.0-b6" +#define BEAST_VERSION_STRING "1.0.0-b7" #endif diff --git a/include/beast/websocket/impl/ssl.ipp b/include/beast/websocket/impl/ssl.ipp index 681ecd341d..a7cd325941 100644 --- a/include/beast/websocket/impl/ssl.ipp +++ b/include/beast/websocket/impl/ssl.ipp @@ -129,7 +129,7 @@ operator()(error_code ec, bool again) template void -teardown( +teardown(teardown_tag, boost::asio::ssl::stream& stream, error_code& ec) { @@ -138,7 +138,7 @@ teardown( template void -async_teardown( +async_teardown(teardown_tag, boost::asio::ssl::stream& stream, TeardownHandler&& handler) { diff --git a/include/beast/websocket/impl/teardown.ipp b/include/beast/websocket/impl/teardown.ipp index fa7133b282..1d0389b012 100644 --- a/include/beast/websocket/impl/teardown.ipp +++ b/include/beast/websocket/impl/teardown.ipp @@ -128,7 +128,7 @@ operator()(error_code ec, std::size_t, bool again) inline void -teardown( +teardown(teardown_tag, boost::asio::ip::tcp::socket& socket, error_code& ec) { @@ -151,7 +151,7 @@ teardown( template inline void -async_teardown( +async_teardown(teardown_tag, boost::asio::ip::tcp::socket& socket, TeardownHandler&& handler) { diff --git a/include/beast/websocket/ssl.hpp b/include/beast/websocket/ssl.hpp index 5ed57222c2..ef94ed9a7b 100644 --- a/include/beast/websocket/ssl.hpp +++ b/include/beast/websocket/ssl.hpp @@ -31,7 +31,7 @@ namespace websocket { */ template void -teardown( +teardown(teardown_tag, boost::asio::ssl::stream& stream, error_code& ec); @@ -62,7 +62,7 @@ teardown( template inline void -async_teardown( +async_teardown(teardown_tag, boost::asio::ssl::stream& stream, TeardownHandler&& handler); diff --git a/include/beast/websocket/teardown.hpp b/include/beast/websocket/teardown.hpp index ecb6dba310..95a54f5ef6 100644 --- a/include/beast/websocket/teardown.hpp +++ b/include/beast/websocket/teardown.hpp @@ -13,8 +13,17 @@ #include namespace beast { + namespace websocket { +/** Tag type used to find teardown and async_teardown overloads + + Overloads of @ref teardown and @async_teardown for user defined + types must take a value of type @ref teardown_tag in the first + argument in order to be found by the implementation. +*/ +struct teardown_tag {}; + /** Tear down a connection. This tears down a connection. The implementation will call @@ -30,7 +39,19 @@ namespace websocket { */ template void -teardown(Socket& socket, error_code& ec) = delete; +teardown(teardown_tag, Socket& socket, error_code& ec) +{ +/* + If you are trying to use OpenSSL and this goes off, you need to + add an include for . + + If you are creating an instance of beast::websocket::stream with your + own user defined type, you must provide an overload of teardown with + the corresponding signature (including the teardown_tag). +*/ + static_assert(sizeof(Socket)==-1, + "Unknown Socket type in teardown."); +}; /** Start tearing down a connection. @@ -49,7 +70,8 @@ teardown(Socket& socket, error_code& ec) = delete; function signature of the handler must be: @code void handler( error_code const& error // result of operation - ); @endcode + ); + @endcode Regardless of whether the asynchronous operation completes immediately or not, the handler will not be invoked from within this function. Invocation of the handler will be performed in a @@ -58,10 +80,56 @@ teardown(Socket& socket, error_code& ec) = delete; */ template void -async_teardown(Socket& socket, TeardownHandler&& handler) = delete; +async_teardown(teardown_tag, Socket& socket, TeardownHandler&& handler) +{ +/* + If you are trying to use OpenSSL and this goes off, you need to + add an include for . + + If you are creating an instance of beast::websocket::stream with your + own user defined type, you must provide an overload of teardown with + the corresponding signature (including the teardown_tag). +*/ + static_assert(sizeof(Socket)==-1, + "Unknown Socket type in async_teardown."); +} + +} // websocket + +//------------------------------------------------------------------------------ + +namespace websocket_helpers { + +// Calls to teardown and async_teardown must be made from +// a namespace that does not contain any overloads of these +// functions. The websocket_helpers namespace is defined here +// for that purpose. + +template +inline +void +call_teardown(Socket& socket, error_code& ec) +{ + using websocket::teardown; + teardown(websocket::teardown_tag{}, socket, ec); +} + +template +inline +void +call_async_teardown(Socket& socket, TeardownHandler&& handler) +{ + using websocket::async_teardown; + async_teardown(websocket::teardown_tag{}, socket, + std::forward(handler)); +} + +} // websocket_helpers //------------------------------------------------------------------------------ +namespace websocket { + /** Tear down a `boost::asio::ip::tcp::socket`. This tears down a connection. The implementation will call @@ -76,9 +144,8 @@ async_teardown(Socket& socket, TeardownHandler&& handler) = delete; @param ec Set to the error if any occurred. */ void -teardown( - boost::asio::ip::tcp::socket& socket, - error_code& ec); +teardown(teardown_tag, + boost::asio::ip::tcp::socket& socket, error_code& ec); /** Start tearing down a `boost::asio::ip::tcp::socket`. @@ -97,7 +164,8 @@ teardown( function signature of the handler must be: @code void handler( error_code const& error // result of operation - ); @endcode + ); + @endcode Regardless of whether the asynchronous operation completes immediately or not, the handler will not be invoked from within this function. Invocation of the handler will be performed in a @@ -106,42 +174,11 @@ teardown( */ template void -async_teardown( - boost::asio::ip::tcp::socket& socket, - TeardownHandler&& handler); +async_teardown(teardown_tag, + boost::asio::ip::tcp::socket& socket, TeardownHandler&& handler); } // websocket -//------------------------------------------------------------------------------ - -namespace websocket_helpers { - -// Calls to teardown and async_teardown must be made from -// a namespace that does not contain any overloads of these -// functions. The websocket_helpers namespace is defined here -// for that purpose. - -template -inline -void -call_teardown(Socket& socket, error_code& ec) -{ - using websocket::teardown; - teardown(socket, ec); -} - -template -inline -void -call_async_teardown(Socket& socket, TeardownHandler&& handler) -{ - using websocket::async_teardown; - async_teardown(socket, - std::forward(handler)); -} - -} // websocket_helpers - } // beast #include diff --git a/scripts/build-and-test.sh b/scripts/build-and-test.sh index 0aa4104067..4b66013e13 100755 --- a/scripts/build-and-test.sh +++ b/scripts/build-and-test.sh @@ -20,15 +20,6 @@ else export PATH=$VALGRIND_ROOT/bin:$LCOV_ROOT/usr/bin:$PATH fi -MAIN_BRANCH="0" -# For builds not triggered by a pull request TRAVIS_BRANCH is the name of the -# branch currently being built; whereas for builds triggered by a pull request -# it is the name of the branch targeted by the pull request (in many cases this -# will be master). -if [[ $TRAVIS_BRANCH == "master" || $TRAVIS_BRANCH == "develop" ]]; then - MAIN_BRANCH="1" -fi - num_jobs="1" if [[ $(uname) == "Darwin" ]]; then num_jobs=$(sysctl -n hw.physicalcpu) @@ -37,8 +28,8 @@ elif [[ $(uname -s) == "Linux" ]]; then num_proc_units=$(nproc) # Physical cores num_jobs=$(lscpu -p | grep -v '^#' | sort -u -t, -k 2,4 | wc -l) - if (("$num_proc_units" < "$num_jobs")); then - num_jobs=$num_proc_units + if ((${num_proc_units} < ${num_jobs})); then + num_jobs=$num_proc_units fi fi @@ -46,7 +37,6 @@ echo "using toolset: $CC" echo "using variant: $VARIANT" echo "using address-model: $ADDRESS_MODEL" echo "using PATH: $PATH" -echo "using MAIN_BRANCH: $MAIN_BRANCH" echo "using BOOST_ROOT: $BOOST_ROOT" #################################### HELPERS ################################### @@ -82,6 +72,16 @@ function build_beast { -j${num_jobs} } +function build_beast_cmake { + mkdir -p build + pushd build > /dev/null + cmake -DVARIANT=${VARIANT} .. + make -j${num_jobs} + mkdir -p ../bin/$VARIANT + find . -executable -type f -exec cp {} ../bin/$VARIANT/. \; + popd > /dev/null +} + function run_autobahn_test_suite { # Run autobahn tests wsecho=$(find bin -name "websocket-echo" | grep /$VARIANT/) @@ -108,7 +108,11 @@ function run_autobahn_test_suite { ##################################### BUILD #################################### -build_beast +if [[ ${BUILD_SYSTEM:-} == cmake ]]; then + build_beast_cmake +else + build_beast +fi ##################################### TESTS #################################### @@ -119,11 +123,11 @@ if [[ $VARIANT == "coverage" ]]; then lcov --no-external -c -i -d . -o baseline.info > /dev/null # Perform test - if [[ $MAIN_BRANCH == "1" ]]; then + if [[ $TRAVIS_PULL_REQUEST == "false" ]]; then + echo "skipping autobahn tests for non-pull request build" + else run_tests_with_valgrind run_autobahn_test_suite - else - run_tests fi # Create test coverage data file diff --git a/scripts/install-dependencies.sh b/scripts/install-dependencies.sh index 4130e2b473..4f0b7cb722 100755 --- a/scripts/install-dependencies.sh +++ b/scripts/install-dependencies.sh @@ -12,11 +12,32 @@ do test -x $( type -p ${g}-$GCC_VER ) ln -sv $(type -p ${g}-$GCC_VER) $HOME/bin/${g} done -for c in clang clang++ llvm-symbolizer -do - test -x $( type -p ${c}-$CLANG_VER ) - ln -sv $(type -p ${c}-$CLANG_VER) $HOME/bin/${c} -done + +if [[ -n ${CLANG_VER:-} ]]; then + # There are cases where the directory exists, but the exe is not available. + # Use this workaround for now. + if [[ ! -x llvm-${LLVM_VERSION}/bin/llvm-config ]] && [[ -d llvm-${LLVM_VERSION} ]]; then + rm -fr llvm-${LLVM_VERSION} + fi + if [[ ! -d llvm-${LLVM_VERSION} ]]; then + mkdir llvm-${LLVM_VERSION} + LLVM_URL="http://llvm.org/releases/${LLVM_VERSION}/clang+llvm-${LLVM_VERSION}-x86_64-linux-gnu-ubuntu-14.04.tar.xz" + wget -O - ${LLVM_URL} | tar -Jxvf - --strip 1 -C llvm-${LLVM_VERSION} + fi + llvm-${LLVM_VERSION}/bin/llvm-config --version; + export LLVM_CONFIG="llvm-${LLVM_VERSION}/bin/llvm-config"; +fi + +# There are cases where the directory exists, but the exe is not available. +# Use this workaround for now. +if [[ ! -x cmake/bin/cmake && -d cmake ]]; then + rm -fr cmake +fi +if [[ ! -d cmake && ${BUILD_SYSTEM:-} == cmake ]]; then + CMAKE_URL="http://www.cmake.org/files/v3.5/cmake-3.5.2-Linux-x86_64.tar.gz" + mkdir cmake && wget --no-check-certificate -O - ${CMAKE_URL} | tar --strip-components=1 -xz -C cmake +fi + # NOTE, changed from PWD -> HOME export PATH=$HOME/bin:$PATH diff --git a/test/Jamfile b/test/Jamfile index 508b6e06e7..f3a82b04fe 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -56,7 +56,6 @@ unit-test http-tests : http/reason.cpp http/resume_context.cpp http/rfc7230.cpp - http/status.cpp http/streambuf_body.cpp http/string_body.cpp http/write.cpp diff --git a/test/core/CMakeLists.txt b/test/core/CMakeLists.txt index 3cc71b05df..b7ce10c9b9 100644 --- a/test/core/CMakeLists.txt +++ b/test/core/CMakeLists.txt @@ -34,5 +34,5 @@ add_executable (core-tests ) if (NOT WIN32) - target_link_libraries(core-tests ${Boost_LIBRARIES}) + target_link_libraries(core-tests ${Boost_LIBRARIES} Threads::Threads) endif() diff --git a/test/http/CMakeLists.txt b/test/http/CMakeLists.txt index 0d9f9da2eb..233ea65edb 100644 --- a/test/http/CMakeLists.txt +++ b/test/http/CMakeLists.txt @@ -24,7 +24,6 @@ add_executable (http-tests reason.cpp resume_context.cpp rfc7230.cpp - status.cpp streambuf_body.cpp string_body.cpp write.cpp @@ -32,7 +31,7 @@ add_executable (http-tests ) if (NOT WIN32) - target_link_libraries(http-tests ${Boost_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) + target_link_libraries(http-tests ${Boost_LIBRARIES} Threads::Threads) endif() add_executable (bench-tests diff --git a/test/http/parser_v1.cpp b/test/http/parser_v1.cpp index 238e13052b..b1ee8cab6d 100644 --- a/test/http/parser_v1.cpp +++ b/test/http/parser_v1.cpp @@ -61,6 +61,19 @@ class parser_v1_test : public beast::unit_test::suite expect(m.headers["Server"] == "test"); expect(m.body == "*"); } + // skip body + { + error_code ec; + parser_v1 p; + std::string const s = + "HTTP/1.1 200 Connection Established\r\n" + "Proxy-Agent: Zscaler/5.1\r\n" + "\r\n"; + p.set_option(skip_body{true}); + p.write(buffer(s), ec); + expect(! ec); + expect(p.complete()); + } } }; diff --git a/test/http/status.cpp b/test/http/status.cpp deleted file mode 100644 index df270195d7..0000000000 --- a/test/http/status.cpp +++ /dev/null @@ -1,9 +0,0 @@ -// -// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail 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) -// - -// Test that header file is self-contained. -#include diff --git a/test/websocket/CMakeLists.txt b/test/websocket/CMakeLists.txt index 7a52207b60..b9cc469a6e 100644 --- a/test/websocket/CMakeLists.txt +++ b/test/websocket/CMakeLists.txt @@ -21,7 +21,7 @@ add_executable (websocket-tests ) if (NOT WIN32) - target_link_libraries(websocket-tests ${Boost_LIBRARIES}) + target_link_libraries(websocket-tests ${Boost_LIBRARIES} Threads::Threads) endif() add_executable (websocket-echo @@ -32,5 +32,5 @@ add_executable (websocket-echo ) if (NOT WIN32) - target_link_libraries(websocket-echo ${Boost_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) + target_link_libraries(websocket-echo ${Boost_LIBRARIES} Threads::Threads) endif()