diff --git a/example/advanced/server-flex/advanced_server_flex.cpp b/example/advanced/server-flex/advanced_server_flex.cpp index 70f2cb5a..67b1f0e6 100644 --- a/example/advanced/server-flex/advanced_server_flex.cpp +++ b/example/advanced/server-flex/advanced_server_flex.cpp @@ -23,7 +23,6 @@ #include #include #include -#include #include #include #include @@ -106,18 +105,15 @@ path_cat( return result; } -// This function produces an HTTP response for the given -// request. The type of the response object depends on the -// contents of the request, so the interface requires the -// caller to pass a generic lambda for receiving the response. -template< - class Body, class Allocator, - class Send> -void +// Return a response for the given request. +// +// The concrete type of the response message (which depends on the +// request), is type-erased in message_generator. +template +http::message_generator handle_request( beast::string_view doc_root, - http::request>&& req, - Send&& send) + http::request>&& req) { // Returns a bad request response auto const bad_request = @@ -161,13 +157,13 @@ handle_request( // Make sure we can handle the method if( req.method() != http::verb::get && req.method() != http::verb::head) - return send(bad_request("Unknown HTTP-method")); + return bad_request("Unknown HTTP-method"); // Request path must be absolute and not contain "..". if( req.target().empty() || req.target()[0] != '/' || req.target().find("..") != beast::string_view::npos) - return send(bad_request("Illegal request-target")); + return bad_request("Illegal request-target"); // Build the path to the requested file std::string path = path_cat(doc_root, req.target()); @@ -181,11 +177,11 @@ handle_request( // Handle the case where the file doesn't exist if(ec == beast::errc::no_such_file_or_directory) - return send(not_found(req.target())); + return not_found(req.target()); // Handle an unknown error if(ec) - return send(server_error(ec.message())); + return server_error(ec.message()); // Cache the size since we need it after the move auto const size = body.size(); @@ -198,7 +194,7 @@ handle_request( res.set(http::field::content_type, mime_type(path)); res.content_length(size); res.keep_alive(req.keep_alive()); - return send(std::move(res)); + return res; } // Respond to GET request @@ -210,7 +206,7 @@ handle_request( res.set(http::field::content_type, mime_type(path)); res.content_length(size); res.keep_alive(req.keep_alive()); - return send(std::move(res)); + return res; } //------------------------------------------------------------------------------ @@ -288,6 +284,7 @@ class websocket_session derived().shared_from_this())); } +private: void on_accept(beast::error_code ec) { @@ -444,6 +441,8 @@ make_websocket_session( template class http_session { + std::shared_ptr doc_root_; + // Access the derived class, this is part of // the Curiously Recurring Template Pattern idiom. Derived& @@ -452,98 +451,8 @@ class http_session return static_cast(*this); } - // This queue is used for HTTP pipelining. - class queue - { - enum - { - // Maximum number of responses we will queue - limit = 8 - }; - - // The type-erased, saved work item - struct work - { - virtual ~work() = default; - virtual void operator()() = 0; - }; - - http_session& self_; - std::vector> items_; - - public: - explicit - queue(http_session& self) - : self_(self) - { - static_assert(limit > 0, "queue limit must be positive"); - items_.reserve(limit); - } - - // Returns `true` if we have reached the queue limit - bool - is_full() const - { - return items_.size() >= limit; - } - - // Called when a message finishes sending - // Returns `true` if the caller should initiate a read - bool - on_write() - { - BOOST_ASSERT(! items_.empty()); - auto const was_full = is_full(); - items_.erase(items_.begin()); - if(! items_.empty()) - (*items_.front())(); - return was_full; - } - - // Called by the HTTP handler to send a response. - template - void - operator()(http::message&& msg) - { - // This holds a work item - struct work_impl : work - { - http_session& self_; - http::message msg_; - - work_impl( - http_session& self, - http::message&& msg) - : self_(self) - , msg_(std::move(msg)) - { - } - - void - operator()() - { - http::async_write( - self_.derived().stream(), - msg_, - beast::bind_front_handler( - &http_session::on_write, - self_.derived().shared_from_this(), - msg_.need_eof())); - } - }; - - // Allocate and store the work - items_.push_back( - boost::make_unique(self_, std::move(msg))); - - // If there was no previous work, start this one - if(items_.size() == 1) - (*items_.front())(); - } - }; - - std::shared_ptr doc_root_; - queue queue_; + static constexpr std::size_t queue_limit = 8; // max responses + std::vector response_queue_; // The parser is stored in an optional container so we can // construct it from scratch it at the beginning of each new message. @@ -558,7 +467,6 @@ public: beast::flat_buffer buffer, std::shared_ptr const& doc_root) : doc_root_(doc_root) - , queue_(*this) , buffer_(std::move(buffer)) { } @@ -614,22 +522,67 @@ public: } // Send the response - handle_request(*doc_root_, parser_->release(), queue_); + queue_write(handle_request(*doc_root_, parser_->release())); // If we aren't at the queue limit, try to pipeline another request - if(! queue_.is_full()) + if (response_queue_.size() < queue_limit) do_read(); } void - on_write(bool close, beast::error_code ec, std::size_t bytes_transferred) + queue_write(http::message_generator response) + { + // Allocate and store the work + response_queue_.push_back(std::move(response)); + + // If there was no previous work, start the write + // loop + if (response_queue_.size() == 1) + do_write(); + } + + // Called to start/continue the write-loop. Should not be called when + // write_loop is already active. + // + // Returns `true` if the caller may initiate a new read + bool + do_write() + { + bool const was_full = + response_queue_.size() == queue_limit; + + if(! response_queue_.empty()) + { + http::message_generator msg = + std::move(response_queue_.front()); + response_queue_.erase(response_queue_.begin()); + + bool keep_alive = msg.keep_alive(); + + beast::async_write( + derived().stream(), + std::move(msg), + beast::bind_front_handler( + &http_session::on_write, + derived().shared_from_this(), + keep_alive)); + } + + return was_full; + } + + void + on_write( + bool keep_alive, + beast::error_code ec, + std::size_t bytes_transferred) { boost::ignore_unused(bytes_transferred); if(ec) return fail(ec, "write"); - if(close) + if(! keep_alive) { // This means we should close the connection, usually because // the response indicated the "Connection: close" semantic. @@ -637,7 +590,7 @@ public: } // Inform the queue that a write completed - if(queue_.on_write()) + if(do_write()) { // Read another request do_read(); diff --git a/example/advanced/server/advanced_server.cpp b/example/advanced/server/advanced_server.cpp index 715f8b8c..ede5edc2 100644 --- a/example/advanced/server/advanced_server.cpp +++ b/example/advanced/server/advanced_server.cpp @@ -101,18 +101,15 @@ path_cat( return result; } -// This function produces an HTTP response for the given -// request. The type of the response object depends on the -// contents of the request, so the interface requires the -// caller to pass a generic lambda for receiving the response. -template< - class Body, class Allocator, - class Send> -void +// Return a response for the given request. +// +// The concrete type of the response message (which depends on the +// request), is type-erased in message_generator. +template +http::message_generator handle_request( beast::string_view doc_root, - http::request>&& req, - Send&& send) + http::request>&& req) { // Returns a bad request response auto const bad_request = @@ -156,13 +153,13 @@ handle_request( // Make sure we can handle the method if( req.method() != http::verb::get && req.method() != http::verb::head) - return send(bad_request("Unknown HTTP-method")); + return bad_request("Unknown HTTP-method"); // Request path must be absolute and not contain "..". if( req.target().empty() || req.target()[0] != '/' || req.target().find("..") != beast::string_view::npos) - return send(bad_request("Illegal request-target")); + return bad_request("Illegal request-target"); // Build the path to the requested file std::string path = path_cat(doc_root, req.target()); @@ -176,11 +173,11 @@ handle_request( // Handle the case where the file doesn't exist if(ec == beast::errc::no_such_file_or_directory) - return send(not_found(req.target())); + return not_found(req.target()); // Handle an unknown error if(ec) - return send(server_error(ec.message())); + return server_error(ec.message()); // Cache the size since we need it after the move auto const size = body.size(); @@ -193,7 +190,7 @@ handle_request( res.set(http::field::content_type, mime_type(path)); res.content_length(size); res.keep_alive(req.keep_alive()); - return send(std::move(res)); + return res; } // Respond to GET request @@ -205,7 +202,7 @@ handle_request( res.set(http::field::content_type, mime_type(path)); res.content_length(size); res.keep_alive(req.keep_alive()); - return send(std::move(res)); + return res; } //------------------------------------------------------------------------------ @@ -326,100 +323,12 @@ private: // Handles an HTTP server connection class http_session : public std::enable_shared_from_this { - // This queue is used for HTTP pipelining. - class queue - { - enum - { - // Maximum number of responses we will queue - limit = 8 - }; - - // The type-erased, saved work item - struct work - { - virtual ~work() = default; - virtual void operator()() = 0; - }; - - http_session& self_; - std::vector> items_; - - public: - explicit - queue(http_session& self) - : self_(self) - { - static_assert(limit > 0, "queue limit must be positive"); - items_.reserve(limit); - } - - // Returns `true` if we have reached the queue limit - bool - is_full() const - { - return items_.size() >= limit; - } - - // Called when a message finishes sending - // Returns `true` if the caller should initiate a read - bool - on_write() - { - BOOST_ASSERT(! items_.empty()); - auto const was_full = is_full(); - items_.erase(items_.begin()); - if(! items_.empty()) - (*items_.front())(); - return was_full; - } - - // Called by the HTTP handler to send a response. - template - void - operator()(http::message&& msg) - { - // This holds a work item - struct work_impl : work - { - http_session& self_; - http::message msg_; - - work_impl( - http_session& self, - http::message&& msg) - : self_(self) - , msg_(std::move(msg)) - { - } - - void - operator()() - { - http::async_write( - self_.stream_, - msg_, - beast::bind_front_handler( - &http_session::on_write, - self_.shared_from_this(), - msg_.need_eof())); - } - }; - - // Allocate and store the work - items_.push_back( - boost::make_unique(self_, std::move(msg))); - - // If there was no previous work, start this one - if(items_.size() == 1) - (*items_.front())(); - } - }; - beast::tcp_stream stream_; beast::flat_buffer buffer_; std::shared_ptr doc_root_; - queue queue_; + + static constexpr std::size_t queue_limit = 8; // max responses + std::vector response_queue_; // The parser is stored in an optional container so we can // construct it from scratch it at the beginning of each new message. @@ -432,8 +341,10 @@ public: std::shared_ptr const& doc_root) : stream_(std::move(socket)) , doc_root_(doc_root) - , queue_(*this) { + static_assert(queue_limit > 0, + "queue limit must be positive"); + response_queue_.reserve(queue_limit); } // Start the session @@ -451,7 +362,6 @@ public: this->shared_from_this())); } - private: void do_read() @@ -499,22 +409,67 @@ private: } // Send the response - handle_request(*doc_root_, parser_->release(), queue_); + queue_write(handle_request(*doc_root_, parser_->release())); // If we aren't at the queue limit, try to pipeline another request - if(! queue_.is_full()) + if (response_queue_.size() < queue_limit) do_read(); } void - on_write(bool close, beast::error_code ec, std::size_t bytes_transferred) + queue_write(http::message_generator response) + { + // Allocate and store the work + response_queue_.push_back(std::move(response)); + + // If there was no previous work, start the write + // loop + if (response_queue_.size() == 1) + do_write(); + } + + // Called to start/continue the write-loop. Should not be called when + // write_loop is already active. + // + // Returns `true` if the caller may initiate a new read + bool + do_write() + { + bool const was_full = + response_queue_.size() == queue_limit; + + if(! response_queue_.empty()) + { + http::message_generator msg = + std::move(response_queue_.front()); + response_queue_.erase(response_queue_.begin()); + + bool keep_alive = msg.keep_alive(); + + beast::async_write( + stream_, + std::move(msg), + beast::bind_front_handler( + &http_session::on_write, + shared_from_this(), + keep_alive)); + } + + return was_full; + } + + void + on_write( + bool keep_alive, + beast::error_code ec, + std::size_t bytes_transferred) { boost::ignore_unused(bytes_transferred); if(ec) return fail(ec, "write"); - if(close) + if(! keep_alive) { // This means we should close the connection, usually because // the response indicated the "Connection: close" semantic. @@ -522,7 +477,7 @@ private: } // Inform the queue that a write completed - if(queue_.on_write()) + if(do_write()) { // Read another request do_read(); diff --git a/example/http/server/async-ssl/http_server_async_ssl.cpp b/example/http/server/async-ssl/http_server_async_ssl.cpp index 36cfc10c..01d7e8dc 100644 --- a/example/http/server/async-ssl/http_server_async_ssl.cpp +++ b/example/http/server/async-ssl/http_server_async_ssl.cpp @@ -100,18 +100,15 @@ path_cat( return result; } -// This function produces an HTTP response for the given -// request. The type of the response object depends on the -// contents of the request, so the interface requires the -// caller to pass a generic lambda for receiving the response. -template< - class Body, class Allocator, - class Send> -void +// Return a response for the given request. +// +// The concrete type of the response message (which depends on the +// request), is type-erased in message_generator. +template +http::message_generator handle_request( beast::string_view doc_root, - http::request>&& req, - Send&& send) + http::request>&& req) { // Returns a bad request response auto const bad_request = @@ -155,13 +152,13 @@ handle_request( // Make sure we can handle the method if( req.method() != http::verb::get && req.method() != http::verb::head) - return send(bad_request("Unknown HTTP-method")); + return bad_request("Unknown HTTP-method"); // Request path must be absolute and not contain "..". if( req.target().empty() || req.target()[0] != '/' || req.target().find("..") != beast::string_view::npos) - return send(bad_request("Illegal request-target")); + return bad_request("Illegal request-target"); // Build the path to the requested file std::string path = path_cat(doc_root, req.target()); @@ -175,11 +172,11 @@ handle_request( // Handle the case where the file doesn't exist if(ec == beast::errc::no_such_file_or_directory) - return send(not_found(req.target())); + return not_found(req.target()); // Handle an unknown error if(ec) - return send(server_error(ec.message())); + return server_error(ec.message()); // Cache the size since we need it after the move auto const size = body.size(); @@ -192,7 +189,7 @@ handle_request( res.set(http::field::content_type, mime_type(path)); res.content_length(size); res.keep_alive(req.keep_alive()); - return send(std::move(res)); + return res; } // Respond to GET request @@ -204,7 +201,7 @@ handle_request( res.set(http::field::content_type, mime_type(path)); res.content_length(size); res.keep_alive(req.keep_alive()); - return send(std::move(res)); + return res; } //------------------------------------------------------------------------------ @@ -239,49 +236,10 @@ fail(beast::error_code ec, char const* what) // Handles an HTTP server connection class session : public std::enable_shared_from_this { - // This is the C++11 equivalent of a generic lambda. - // The function object is used to send an HTTP message. - struct send_lambda - { - session& self_; - - explicit - send_lambda(session& self) - : self_(self) - { - } - - template - void - operator()(http::message&& msg) const - { - // The lifetime of the message has to extend - // for the duration of the async operation so - // we use a shared_ptr to manage it. - auto sp = std::make_shared< - http::message>(std::move(msg)); - - // Store a type-erased version of the shared - // pointer in the class to keep it alive. - self_.res_ = sp; - - // Write the response - http::async_write( - self_.stream_, - *sp, - beast::bind_front_handler( - &session::on_write, - self_.shared_from_this(), - sp->need_eof())); - } - }; - beast::ssl_stream stream_; beast::flat_buffer buffer_; std::shared_ptr doc_root_; http::request req_; - std::shared_ptr res_; - send_lambda lambda_; public: // Take ownership of the socket @@ -292,7 +250,6 @@ public: std::shared_ptr const& doc_root) : stream_(std::move(socket), ctx) , doc_root_(doc_root) - , lambda_(*this) { } @@ -367,12 +324,28 @@ public: return fail(ec, "read"); // Send the response - handle_request(*doc_root_, std::move(req_), lambda_); + send_response( + handle_request(*doc_root_, std::move(req_))); + } + + void + send_response(http::message_generator&& msg) + { + bool keep_alive = msg.keep_alive(); + + // Write the response + beast::async_write( + stream_, + std::move(msg), + beast::bind_front_handler( + &session::on_write, + this->shared_from_this(), + keep_alive)); } void on_write( - bool close, + bool keep_alive, beast::error_code ec, std::size_t bytes_transferred) { @@ -381,16 +354,13 @@ public: if(ec) return fail(ec, "write"); - if(close) + if(! keep_alive) { // This means we should close the connection, usually because // the response indicated the "Connection: close" semantic. return do_close(); } - // We're done with the response so delete it - res_ = nullptr; - // Read another request do_read(); } diff --git a/example/http/server/async/http_server_async.cpp b/example/http/server/async/http_server_async.cpp index bc232c74..617701d2 100644 --- a/example/http/server/async/http_server_async.cpp +++ b/example/http/server/async/http_server_async.cpp @@ -96,18 +96,15 @@ path_cat( return result; } -// This function produces an HTTP response for the given -// request. The type of the response object depends on the -// contents of the request, so the interface requires the -// caller to pass a generic lambda for receiving the response. -template< - class Body, class Allocator, - class Send> -void +// Return a response for the given request. +// +// The concrete type of the response message (which depends on the +// request), is type-erased in message_generator. +template +http::message_generator handle_request( beast::string_view doc_root, - http::request>&& req, - Send&& send) + http::request>&& req) { // Returns a bad request response auto const bad_request = @@ -151,13 +148,13 @@ handle_request( // Make sure we can handle the method if( req.method() != http::verb::get && req.method() != http::verb::head) - return send(bad_request("Unknown HTTP-method")); + return bad_request("Unknown HTTP-method"); // Request path must be absolute and not contain "..". if( req.target().empty() || req.target()[0] != '/' || req.target().find("..") != beast::string_view::npos) - return send(bad_request("Illegal request-target")); + return bad_request("Illegal request-target"); // Build the path to the requested file std::string path = path_cat(doc_root, req.target()); @@ -171,11 +168,11 @@ handle_request( // Handle the case where the file doesn't exist if(ec == beast::errc::no_such_file_or_directory) - return send(not_found(req.target())); + return not_found(req.target()); // Handle an unknown error if(ec) - return send(server_error(ec.message())); + return server_error(ec.message()); // Cache the size since we need it after the move auto const size = body.size(); @@ -188,7 +185,7 @@ handle_request( res.set(http::field::content_type, mime_type(path)); res.content_length(size); res.keep_alive(req.keep_alive()); - return send(std::move(res)); + return res; } // Respond to GET request @@ -200,7 +197,7 @@ handle_request( res.set(http::field::content_type, mime_type(path)); res.content_length(size); res.keep_alive(req.keep_alive()); - return send(std::move(res)); + return res; } //------------------------------------------------------------------------------ @@ -215,49 +212,10 @@ fail(beast::error_code ec, char const* what) // Handles an HTTP server connection class session : public std::enable_shared_from_this { - // This is the C++11 equivalent of a generic lambda. - // The function object is used to send an HTTP message. - struct send_lambda - { - session& self_; - - explicit - send_lambda(session& self) - : self_(self) - { - } - - template - void - operator()(http::message&& msg) const - { - // The lifetime of the message has to extend - // for the duration of the async operation so - // we use a shared_ptr to manage it. - auto sp = std::make_shared< - http::message>(std::move(msg)); - - // Store a type-erased version of the shared - // pointer in the class to keep it alive. - self_.res_ = sp; - - // Write the response - http::async_write( - self_.stream_, - *sp, - beast::bind_front_handler( - &session::on_write, - self_.shared_from_this(), - sp->need_eof())); - } - }; - beast::tcp_stream stream_; beast::flat_buffer buffer_; std::shared_ptr doc_root_; http::request req_; - std::shared_ptr res_; - send_lambda lambda_; public: // Take ownership of the stream @@ -266,7 +224,6 @@ public: std::shared_ptr const& doc_root) : stream_(std::move(socket)) , doc_root_(doc_root) - , lambda_(*this) { } @@ -316,12 +273,26 @@ public: return fail(ec, "read"); // Send the response - handle_request(*doc_root_, std::move(req_), lambda_); + send_response( + handle_request(*doc_root_, std::move(req_))); + } + + void + send_response(http::message_generator&& msg) + { + bool keep_alive = msg.keep_alive(); + + // Write the response + beast::async_write( + stream_, + std::move(msg), + beast::bind_front_handler( + &session::on_write, shared_from_this(), keep_alive)); } void on_write( - bool close, + bool keep_alive, beast::error_code ec, std::size_t bytes_transferred) { @@ -330,16 +301,13 @@ public: if(ec) return fail(ec, "write"); - if(close) + if(! keep_alive) { // This means we should close the connection, usually because // the response indicated the "Connection: close" semantic. return do_close(); } - // We're done with the response so delete it - res_ = nullptr; - // Read another request do_read(); } diff --git a/example/http/server/coro-ssl/http_server_coro_ssl.cpp b/example/http/server/coro-ssl/http_server_coro_ssl.cpp index a733c624..9d9ff2e4 100644 --- a/example/http/server/coro-ssl/http_server_coro_ssl.cpp +++ b/example/http/server/coro-ssl/http_server_coro_ssl.cpp @@ -98,18 +98,15 @@ path_cat( return result; } -// This function produces an HTTP response for the given -// request. The type of the response object depends on the -// contents of the request, so the interface requires the -// caller to pass a generic lambda for receiving the response. -template< - class Body, class Allocator, - class Send> -void +// Return a response for the given request. +// +// The concrete type of the response message (which depends on the +// request), is type-erased in message_generator. +template +http::message_generator handle_request( beast::string_view doc_root, - http::request>&& req, - Send&& send) + http::request>&& req) { // Returns a bad request response auto const bad_request = @@ -153,13 +150,13 @@ handle_request( // Make sure we can handle the method if( req.method() != http::verb::get && req.method() != http::verb::head) - return send(bad_request("Unknown HTTP-method")); + return bad_request("Unknown HTTP-method"); // Request path must be absolute and not contain "..". if( req.target().empty() || req.target()[0] != '/' || req.target().find("..") != beast::string_view::npos) - return send(bad_request("Illegal request-target")); + return bad_request("Illegal request-target"); // Build the path to the requested file std::string path = path_cat(doc_root, req.target()); @@ -173,11 +170,11 @@ handle_request( // Handle the case where the file doesn't exist if(ec == beast::errc::no_such_file_or_directory) - return send(not_found(req.target())); + return not_found(req.target()); // Handle an unknown error if(ec) - return send(server_error(ec.message())); + return server_error(ec.message()); // Cache the size since we need it after the move auto const size = body.size(); @@ -190,7 +187,7 @@ handle_request( res.set(http::field::content_type, mime_type(path)); res.content_length(size); res.keep_alive(req.keep_alive()); - return send(std::move(res)); + return res; } // Respond to GET request @@ -202,7 +199,7 @@ handle_request( res.set(http::field::content_type, mime_type(path)); res.content_length(size); res.keep_alive(req.keep_alive()); - return send(std::move(res)); + return res; } //------------------------------------------------------------------------------ @@ -234,42 +231,6 @@ fail(beast::error_code ec, char const* what) std::cerr << what << ": " << ec.message() << "\n"; } -// This is the C++11 equivalent of a generic lambda. -// The function object is used to send an HTTP message. -struct send_lambda -{ - beast::ssl_stream& stream_; - bool& close_; - beast::error_code& ec_; - net::yield_context yield_; - - send_lambda( - beast::ssl_stream& stream, - bool& close, - beast::error_code& ec, - net::yield_context yield) - : stream_(stream) - , close_(close) - , ec_(ec) - , yield_(yield) - { - } - - template - void - operator()(http::message&& msg) const - { - // Determine if we should close the connection after - close_ = msg.need_eof(); - - // We need the serializer here because the serializer requires - // a non-const file_body, and the message oriented version of - // http::write only works with const messages. - http::serializer sr{msg}; - http::async_write(stream_, sr, yield_[ec_]); - } -}; - // Handles an HTTP server connection void do_session( @@ -277,7 +238,7 @@ do_session( std::shared_ptr const& doc_root, net::yield_context yield) { - bool close = false; + bool keep_alive = true; beast::error_code ec; // Set the timeout. @@ -291,9 +252,6 @@ do_session( // This buffer is required to persist across reads beast::flat_buffer buffer; - // This lambda is used to send messages - send_lambda lambda{stream, close, ec, yield}; - for(;;) { // Set the timeout. @@ -307,11 +265,19 @@ do_session( if(ec) return fail(ec, "read"); + // Handle the request + http::message_generator res = + handle_request(*doc_root, std::move(req)); + + // Determine if we should close the connection + keep_alive = res.keep_alive(); + // Send the response - handle_request(*doc_root, std::move(req), lambda); + beast::async_write(stream, std::move(res), yield[ec]); + if(ec) return fail(ec, "write"); - if(close) + if(! keep_alive) { // This means we should close the connection, usually because // the response indicated the "Connection: close" semantic. diff --git a/example/http/server/coro/http_server_coro.cpp b/example/http/server/coro/http_server_coro.cpp index a8baae64..f134feb7 100644 --- a/example/http/server/coro/http_server_coro.cpp +++ b/example/http/server/coro/http_server_coro.cpp @@ -95,18 +95,15 @@ path_cat( return result; } -// This function produces an HTTP response for the given -// request. The type of the response object depends on the -// contents of the request, so the interface requires the -// caller to pass a generic lambda for receiving the response. -template< - class Body, class Allocator, - class Send> -void +// Return a response for the given request. +// +// The concrete type of the response message (which depends on the +// request), is type-erased in message_generator. +template +http::message_generator handle_request( beast::string_view doc_root, - http::request>&& req, - Send&& send) + http::request>&& req) { // Returns a bad request response auto const bad_request = @@ -150,13 +147,13 @@ handle_request( // Make sure we can handle the method if( req.method() != http::verb::get && req.method() != http::verb::head) - return send(bad_request("Unknown HTTP-method")); + return bad_request("Unknown HTTP-method"); // Request path must be absolute and not contain "..". if( req.target().empty() || req.target()[0] != '/' || req.target().find("..") != beast::string_view::npos) - return send(bad_request("Illegal request-target")); + return bad_request("Illegal request-target"); // Build the path to the requested file std::string path = path_cat(doc_root, req.target()); @@ -170,11 +167,11 @@ handle_request( // Handle the case where the file doesn't exist if(ec == beast::errc::no_such_file_or_directory) - return send(not_found(req.target())); + return not_found(req.target()); // Handle an unknown error if(ec) - return send(server_error(ec.message())); + return server_error(ec.message()); // Cache the size since we need it after the move auto const size = body.size(); @@ -187,7 +184,7 @@ handle_request( res.set(http::field::content_type, mime_type(path)); res.content_length(size); res.keep_alive(req.keep_alive()); - return send(std::move(res)); + return res; } // Respond to GET request @@ -199,7 +196,7 @@ handle_request( res.set(http::field::content_type, mime_type(path)); res.content_length(size); res.keep_alive(req.keep_alive()); - return send(std::move(res)); + return res; } //------------------------------------------------------------------------------ @@ -211,41 +208,6 @@ fail(beast::error_code ec, char const* what) std::cerr << what << ": " << ec.message() << "\n"; } -// This is the C++11 equivalent of a generic lambda. -// The function object is used to send an HTTP message. -struct send_lambda -{ - beast::tcp_stream& stream_; - bool& close_; - beast::error_code& ec_; - net::yield_context yield_; - - send_lambda( - beast::tcp_stream& stream, - bool& close, - beast::error_code& ec, - net::yield_context yield) - : stream_(stream) - , close_(close) - , ec_(ec) - , yield_(yield) - { - } - - template - void - operator()(http::message&& msg) const - { - // Determine if we should close the connection after - close_ = msg.need_eof(); - - // We need the serializer here because the serializer requires - // a non-const file_body, and the message oriented version of - // http::write only works with const messages. - http::serializer sr{msg}; - http::async_write(stream_, sr, yield_[ec_]); - } -}; // Handles an HTTP server connection void @@ -254,15 +216,12 @@ do_session( std::shared_ptr const& doc_root, net::yield_context yield) { - bool close = false; beast::error_code ec; // This buffer is required to persist across reads beast::flat_buffer buffer; // This lambda is used to send messages - send_lambda lambda{stream, close, ec, yield}; - for(;;) { // Set the timeout. @@ -276,11 +235,20 @@ do_session( if(ec) return fail(ec, "read"); + // Handle the request + http::message_generator msg = + handle_request(*doc_root, std::move(req)); + + // Determine if we should close the connection + bool keep_alive = msg.keep_alive(); + // Send the response - handle_request(*doc_root, std::move(req), lambda); + beast::async_write(stream, std::move(msg), yield[ec]); + if(ec) return fail(ec, "write"); - if(close) + + if(! keep_alive) { // This means we should close the connection, usually because // the response indicated the "Connection: close" semantic. diff --git a/example/http/server/flex/http_server_flex.cpp b/example/http/server/flex/http_server_flex.cpp index dd4b4ccd..c3642d8b 100644 --- a/example/http/server/flex/http_server_flex.cpp +++ b/example/http/server/flex/http_server_flex.cpp @@ -99,18 +99,15 @@ path_cat( return result; } -// This function produces an HTTP response for the given -// request. The type of the response object depends on the -// contents of the request, so the interface requires the -// caller to pass a generic lambda for receiving the response. -template< - class Body, class Allocator, - class Send> -void +// Return a response for the given request. +// +// The concrete type of the response message (which depends on the +// request), is type-erased in message_generator. +template +http::message_generator handle_request( beast::string_view doc_root, - http::request>&& req, - Send&& send) + http::request>&& req) { // Returns a bad request response auto const bad_request = @@ -154,13 +151,13 @@ handle_request( // Make sure we can handle the method if( req.method() != http::verb::get && req.method() != http::verb::head) - return send(bad_request("Unknown HTTP-method")); + return bad_request("Unknown HTTP-method"); // Request path must be absolute and not contain "..". if( req.target().empty() || req.target()[0] != '/' || req.target().find("..") != beast::string_view::npos) - return send(bad_request("Illegal request-target")); + return bad_request("Illegal request-target"); // Build the path to the requested file std::string path = path_cat(doc_root, req.target()); @@ -174,11 +171,11 @@ handle_request( // Handle the case where the file doesn't exist if(ec == beast::errc::no_such_file_or_directory) - return send(not_found(req.target())); + return not_found(req.target()); // Handle an unknown error if(ec) - return send(server_error(ec.message())); + return server_error(ec.message()); // Cache the size since we need it after the move auto const size = body.size(); @@ -191,7 +188,7 @@ handle_request( res.set(http::field::content_type, mime_type(path)); res.content_length(size); res.keep_alive(req.keep_alive()); - return send(std::move(res)); + return res; } // Respond to GET request @@ -203,7 +200,7 @@ handle_request( res.set(http::field::content_type, mime_type(path)); res.content_length(size); res.keep_alive(req.keep_alive()); - return send(std::move(res)); + return res; } //------------------------------------------------------------------------------ @@ -249,47 +246,8 @@ class session return static_cast(*this); } - // This is the C++11 equivalent of a generic lambda. - // The function object is used to send an HTTP message. - struct send_lambda - { - session& self_; - - explicit - send_lambda(session& self) - : self_(self) - { - } - - template - void - operator()(http::message&& msg) const - { - // The lifetime of the message has to extend - // for the duration of the async operation so - // we use a shared_ptr to manage it. - auto sp = std::make_shared< - http::message>(std::move(msg)); - - // Store a type-erased version of the shared - // pointer in the class to keep it alive. - self_.res_ = sp; - - // Write the response - http::async_write( - self_.derived().stream(), - *sp, - beast::bind_front_handler( - &session::on_write, - self_.derived().shared_from_this(), - sp->need_eof())); - } - }; - std::shared_ptr doc_root_; http::request req_; - std::shared_ptr res_; - send_lambda lambda_; protected: beast::flat_buffer buffer_; @@ -300,7 +258,6 @@ public: beast::flat_buffer buffer, std::shared_ptr const& doc_root) : doc_root_(doc_root) - , lambda_(*this) , buffer_(std::move(buffer)) { } @@ -337,12 +294,28 @@ public: return fail(ec, "read"); // Send the response - handle_request(*doc_root_, std::move(req_), lambda_); + send_response( + handle_request(*doc_root_, std::move(req_))); + } + + void + send_response(http::message_generator&& msg) + { + bool keep_alive = msg.keep_alive(); + + // Write the response + beast::async_write( + derived().stream(), + std::move(msg), + beast::bind_front_handler( + &session::on_write, + derived().shared_from_this(), + keep_alive)); } void on_write( - bool close, + bool keep_alive, beast::error_code ec, std::size_t bytes_transferred) { @@ -351,16 +324,13 @@ public: if(ec) return fail(ec, "write"); - if(close) + if(! keep_alive) { // This means we should close the connection, usually because // the response indicated the "Connection: close" semantic. return derived().do_eof(); } - // We're done with the response so delete it - res_ = nullptr; - // Read another request do_read(); } diff --git a/example/http/server/stackless-ssl/http_server_stackless_ssl.cpp b/example/http/server/stackless-ssl/http_server_stackless_ssl.cpp index 7786cbb4..f9ebda3f 100644 --- a/example/http/server/stackless-ssl/http_server_stackless_ssl.cpp +++ b/example/http/server/stackless-ssl/http_server_stackless_ssl.cpp @@ -101,18 +101,15 @@ path_cat( return result; } -// This function produces an HTTP response for the given -// request. The type of the response object depends on the -// contents of the request, so the interface requires the -// caller to pass a generic lambda for receiving the response. -template< - class Body, class Allocator, - class Send> -void +// Return a response for the given request. +// +// The concrete type of the response message (which depends on the +// request), is type-erased in message_generator. +template +http::message_generator handle_request( beast::string_view doc_root, - http::request>&& req, - Send&& send) + http::request>&& req) { // Returns a bad request response auto const bad_request = @@ -156,13 +153,13 @@ handle_request( // Make sure we can handle the method if( req.method() != http::verb::get && req.method() != http::verb::head) - return send(bad_request("Unknown HTTP-method")); + return bad_request("Unknown HTTP-method"); // Request path must be absolute and not contain "..". if( req.target().empty() || req.target()[0] != '/' || req.target().find("..") != beast::string_view::npos) - return send(bad_request("Illegal request-target")); + return bad_request("Illegal request-target"); // Build the path to the requested file std::string path = path_cat(doc_root, req.target()); @@ -176,11 +173,11 @@ handle_request( // Handle the case where the file doesn't exist if(ec == beast::errc::no_such_file_or_directory) - return send(not_found(req.target())); + return not_found(req.target()); // Handle an unknown error if(ec) - return send(server_error(ec.message())); + return server_error(ec.message()); // Cache the size since we need it after the move auto const size = body.size(); @@ -193,7 +190,7 @@ handle_request( res.set(http::field::content_type, mime_type(path)); res.content_length(size); res.keep_alive(req.keep_alive()); - return send(std::move(res)); + return res; } // Respond to GET request @@ -205,7 +202,7 @@ handle_request( res.set(http::field::content_type, mime_type(path)); res.content_length(size); res.keep_alive(req.keep_alive()); - return send(std::move(res)); + return res; } //------------------------------------------------------------------------------ @@ -242,51 +239,11 @@ class session : public boost::asio::coroutine , public std::enable_shared_from_this { - // This is the C++11 equivalent of a generic lambda. - // The function object is used to send an HTTP message. - struct send_lambda - { - session& self_; - - explicit - send_lambda(session& self) - : self_(self) - { - } - - template - void - operator()(http::message&& msg) const - { - // The lifetime of the message has to extend - // for the duration of the async operation so - // we use a shared_ptr to manage it. - auto sp = std::make_shared< - http::message>(std::move(msg)); - - // Store a type-erased version of the shared - // pointer in the class to keep it alive. - self_.res_ = sp; - - // Write the response - http::async_write( - self_.stream_, - *sp, - std::bind( - &session::loop, - self_.shared_from_this(), - std::placeholders::_1, - std::placeholders::_2, - sp->need_eof())); - } - }; - beast::ssl_stream stream_; beast::flat_buffer buffer_; std::shared_ptr doc_root_; http::request req_; - std::shared_ptr res_; - send_lambda lambda_; + bool keep_alive_ = true; public: // Take ownership of the socket @@ -297,7 +254,6 @@ public: std::shared_ptr const& doc_root) : stream_(std::move(socket), ctx) , doc_root_(doc_root) - , lambda_(*this) { } @@ -313,17 +269,13 @@ public: beast::bind_front_handler(&session::loop, shared_from_this(), beast::error_code{}, - 0, - false)); + 0)); } #include void - loop( - beast::error_code ec, - std::size_t bytes_transferred, - bool close) + loop(beast::error_code ec, std::size_t bytes_transferred) { boost::ignore_unused(bytes_transferred); reenter(*this) @@ -335,11 +287,8 @@ public: yield stream_.async_handshake( ssl::stream_base::server, std::bind( - &session::loop, - shared_from_this(), - std::placeholders::_1, - 0, - false)); + &session::loop, shared_from_this(), + std::placeholders::_1, 0)); if(ec) return fail(ec, "handshake"); @@ -353,34 +302,41 @@ public: req_ = {}; // Read a request - yield http::async_read(stream_, buffer_, req_, - std::bind( - &session::loop, - shared_from_this(), - std::placeholders::_1, - std::placeholders::_2, - false)); - if(ec == http::error::end_of_stream) - { + yield http::async_read( + stream_, buffer_, req_, + beast::bind_front_handler( + &session::loop, shared_from_this())); + + if (ec == http::error::end_of_stream) { // The remote host closed the connection break; } if(ec) return fail(ec, "read"); - // Send the response - yield handle_request(*doc_root_, std::move(req_), lambda_); + yield { + // Handle request + http::message_generator msg = + handle_request(*doc_root_, std::move(req_)); + + // Determine if we should close the connection + keep_alive_ = msg.keep_alive(); + + // Send the response + beast::async_write( + stream_, std::move(msg), + beast::bind_front_handler( + &session::loop, shared_from_this())); + } + if(ec) return fail(ec, "write"); - if(close) + if(! keep_alive_) { // This means we should close the connection, usually because // the response indicated the "Connection: close" semantic. break; } - - // We're done with the response so delete it - res_ = nullptr; } // Set the timeout. @@ -392,8 +348,7 @@ public: &session::loop, shared_from_this(), std::placeholders::_1, - 0, - false)); + 0)); if(ec) return fail(ec, "shutdown"); diff --git a/example/http/server/stackless/http_server_stackless.cpp b/example/http/server/stackless/http_server_stackless.cpp index 2439c689..87d0fccc 100644 --- a/example/http/server/stackless/http_server_stackless.cpp +++ b/example/http/server/stackless/http_server_stackless.cpp @@ -97,18 +97,15 @@ path_cat( return result; } -// This function produces an HTTP response for the given -// request. The type of the response object depends on the -// contents of the request, so the interface requires the -// caller to pass a generic lambda for receiving the response. -template< - class Body, class Allocator, - class Send> -void +// Return a response for the given request. +// +// The concrete type of the response message (which depends on the +// request), is type-erased in message_generator. +template +http::message_generator handle_request( beast::string_view doc_root, - http::request>&& req, - Send&& send) + http::request>&& req) { // Returns a bad request response auto const bad_request = @@ -152,13 +149,13 @@ handle_request( // Make sure we can handle the method if( req.method() != http::verb::get && req.method() != http::verb::head) - return send(bad_request("Unknown HTTP-method")); + return bad_request("Unknown HTTP-method"); // Request path must be absolute and not contain "..". if( req.target().empty() || req.target()[0] != '/' || req.target().find("..") != beast::string_view::npos) - return send(bad_request("Illegal request-target")); + return bad_request("Illegal request-target"); // Build the path to the requested file std::string path = path_cat(doc_root, req.target()); @@ -172,11 +169,11 @@ handle_request( // Handle the case where the file doesn't exist if(ec == beast::errc::no_such_file_or_directory) - return send(not_found(req.target())); + return not_found(req.target()); // Handle an unknown error if(ec) - return send(server_error(ec.message())); + return server_error(ec.message()); // Cache the size since we need it after the move auto const size = body.size(); @@ -189,7 +186,7 @@ handle_request( res.set(http::field::content_type, mime_type(path)); res.content_length(size); res.keep_alive(req.keep_alive()); - return send(std::move(res)); + return res; } // Respond to GET request @@ -201,7 +198,7 @@ handle_request( res.set(http::field::content_type, mime_type(path)); res.content_length(size); res.keep_alive(req.keep_alive()); - return send(std::move(res)); + return res; } //------------------------------------------------------------------------------ @@ -218,50 +215,11 @@ class session : public boost::asio::coroutine , public std::enable_shared_from_this { - // This is the C++11 equivalent of a generic lambda. - // The function object is used to send an HTTP message. - struct send_lambda - { - session& self_; - std::shared_ptr res_; - - explicit - send_lambda(session& self) - : self_(self) - { - } - - template - void - operator()(http::message&& msg) const - { - // The lifetime of the message has to extend - // for the duration of the async operation so - // we use a shared_ptr to manage it. - auto sp = std::make_shared< - http::message>(std::move(msg)); - - // Store a type-erased version of the shared - // pointer in the class to keep it alive. - self_.res_ = sp; - - // Write the response - http::async_write( - self_.stream_, - *sp, - beast::bind_front_handler( - &session::loop, - self_.shared_from_this(), - sp->need_eof())); - } - }; - beast::tcp_stream stream_; beast::flat_buffer buffer_; std::shared_ptr doc_root_; http::request req_; - std::shared_ptr res_; - send_lambda lambda_; + bool keep_alive_ = true; public: // Take ownership of the socket @@ -271,7 +229,6 @@ public: std::shared_ptr const& doc_root) : stream_(std::move(socket)) , doc_root_(doc_root) - , lambda_(*this) { } @@ -286,7 +243,6 @@ public: net::dispatch(stream_.get_executor(), beast::bind_front_handler(&session::loop, shared_from_this(), - false, beast::error_code{}, 0)); } @@ -294,10 +250,7 @@ public: #include void - loop( - bool close, - beast::error_code ec, - std::size_t bytes_transferred) + loop(beast::error_code ec, std::size_t bytes_transferred) { boost::ignore_unused(bytes_transferred); reenter(*this) @@ -315,8 +268,8 @@ public: yield http::async_read(stream_, buffer_, req_, beast::bind_front_handler( &session::loop, - shared_from_this(), - false)); + shared_from_this())); + if(ec == http::error::end_of_stream) { // The remote host closed the connection @@ -325,19 +278,30 @@ public: if(ec) return fail(ec, "read"); - // Send the response - yield handle_request(*doc_root_, std::move(req_), lambda_); + yield { + // Handle request + http::message_generator msg = + handle_request(*doc_root_, std::move(req_)); + + // Determine if we should close the connection + keep_alive_ = msg.keep_alive(); + + // Send the response + beast::async_write( + stream_, + std::move(msg), + beast::bind_front_handler( + &session::loop, shared_from_this())); + } + if(ec) return fail(ec, "write"); - if(close) + if(! keep_alive_) { // This means we should close the connection, usually because // the response indicated the "Connection: close" semantic. break; } - - // We're done with the response so delete it - res_ = nullptr; } // Send a TCP shutdown diff --git a/example/http/server/sync-ssl/http_server_sync_ssl.cpp b/example/http/server/sync-ssl/http_server_sync_ssl.cpp index 34b27753..57bf76b7 100644 --- a/example/http/server/sync-ssl/http_server_sync_ssl.cpp +++ b/example/http/server/sync-ssl/http_server_sync_ssl.cpp @@ -97,18 +97,15 @@ path_cat( return result; } -// This function produces an HTTP response for the given -// request. The type of the response object depends on the -// contents of the request, so the interface requires the -// caller to pass a generic lambda for receiving the response. -template< - class Body, class Allocator, - class Send> -void +// Return a response for the given request. +// +// The concrete type of the response message (which depends on the +// request), is type-erased in message_generator. +template +http::message_generator handle_request( beast::string_view doc_root, - http::request>&& req, - Send&& send) + http::request>&& req) { // Returns a bad request response auto const bad_request = @@ -152,13 +149,13 @@ handle_request( // Make sure we can handle the method if( req.method() != http::verb::get && req.method() != http::verb::head) - return send(bad_request("Unknown HTTP-method")); + return bad_request("Unknown HTTP-method"); // Request path must be absolute and not contain "..". if( req.target().empty() || req.target()[0] != '/' || req.target().find("..") != beast::string_view::npos) - return send(bad_request("Illegal request-target")); + return bad_request("Illegal request-target"); // Build the path to the requested file std::string path = path_cat(doc_root, req.target()); @@ -172,11 +169,11 @@ handle_request( // Handle the case where the file doesn't exist if(ec == beast::errc::no_such_file_or_directory) - return send(not_found(req.target())); + return not_found(req.target()); // Handle an unknown error if(ec) - return send(server_error(ec.message())); + return server_error(ec.message()); // Cache the size since we need it after the move auto const size = body.size(); @@ -189,7 +186,7 @@ handle_request( res.set(http::field::content_type, mime_type(path)); res.content_length(size); res.keep_alive(req.keep_alive()); - return send(std::move(res)); + return res; } // Respond to GET request @@ -201,7 +198,7 @@ handle_request( res.set(http::field::content_type, mime_type(path)); res.content_length(size); res.keep_alive(req.keep_alive()); - return send(std::move(res)); + return res; } //------------------------------------------------------------------------------ @@ -213,41 +210,6 @@ fail(beast::error_code ec, char const* what) std::cerr << what << ": " << ec.message() << "\n"; } -// This is the C++11 equivalent of a generic lambda. -// The function object is used to send an HTTP message. -template -struct send_lambda -{ - Stream& stream_; - bool& close_; - beast::error_code& ec_; - - explicit - send_lambda( - Stream& stream, - bool& close, - beast::error_code& ec) - : stream_(stream) - , close_(close) - , ec_(ec) - { - } - - template - void - operator()(http::message&& msg) const - { - // Determine if we should close the connection after - close_ = msg.need_eof(); - - // We need the serializer here because the serializer requires - // a non-const file_body, and the message oriented version of - // http::write only works with const messages. - http::serializer sr{msg}; - http::write(stream_, sr, ec_); - } -}; - // Handles an HTTP server connection void do_session( @@ -255,7 +217,6 @@ do_session( ssl::context& ctx, std::shared_ptr const& doc_root) { - bool close = false; beast::error_code ec; // Construct the stream around the socket @@ -269,9 +230,6 @@ do_session( // This buffer is required to persist across reads beast::flat_buffer buffer; - // This lambda is used to send messages - send_lambda> lambda{stream, close, ec}; - for(;;) { // Read a request @@ -282,11 +240,20 @@ do_session( if(ec) return fail(ec, "read"); + // Handle request + http::message_generator msg = + handle_request(*doc_root, std::move(req)); + + // Determine if we should close the connection + bool keep_alive = msg.keep_alive(); + // Send the response - handle_request(*doc_root, std::move(req), lambda); + beast::write(stream, std::move(msg), ec); + if(ec) return fail(ec, "write"); - if(close) + + if(! keep_alive) { // This means we should close the connection, usually because // the response indicated the "Connection: close" semantic. diff --git a/example/http/server/sync/http_server_sync.cpp b/example/http/server/sync/http_server_sync.cpp index 2c5ca60f..09a3af31 100644 --- a/example/http/server/sync/http_server_sync.cpp +++ b/example/http/server/sync/http_server_sync.cpp @@ -94,18 +94,15 @@ path_cat( return result; } -// This function produces an HTTP response for the given -// request. The type of the response object depends on the -// contents of the request, so the interface requires the -// caller to pass a generic lambda for receiving the response. -template< - class Body, class Allocator, - class Send> -void +// Return a response for the given request. +// +// The concrete type of the response message (which depends on the +// request), is type-erased in message_generator. +template +http::message_generator handle_request( beast::string_view doc_root, - http::request>&& req, - Send&& send) + http::request>&& req) { // Returns a bad request response auto const bad_request = @@ -149,13 +146,13 @@ handle_request( // Make sure we can handle the method if( req.method() != http::verb::get && req.method() != http::verb::head) - return send(bad_request("Unknown HTTP-method")); + return bad_request("Unknown HTTP-method"); // Request path must be absolute and not contain "..". if( req.target().empty() || req.target()[0] != '/' || req.target().find("..") != beast::string_view::npos) - return send(bad_request("Illegal request-target")); + return bad_request("Illegal request-target"); // Build the path to the requested file std::string path = path_cat(doc_root, req.target()); @@ -169,11 +166,11 @@ handle_request( // Handle the case where the file doesn't exist if(ec == beast::errc::no_such_file_or_directory) - return send(not_found(req.target())); + return not_found(req.target()); // Handle an unknown error if(ec) - return send(server_error(ec.message())); + return server_error(ec.message()); // Cache the size since we need it after the move auto const size = body.size(); @@ -186,7 +183,7 @@ handle_request( res.set(http::field::content_type, mime_type(path)); res.content_length(size); res.keep_alive(req.keep_alive()); - return send(std::move(res)); + return res; } // Respond to GET request @@ -198,7 +195,7 @@ handle_request( res.set(http::field::content_type, mime_type(path)); res.content_length(size); res.keep_alive(req.keep_alive()); - return send(std::move(res)); + return res; } //------------------------------------------------------------------------------ @@ -210,56 +207,17 @@ fail(beast::error_code ec, char const* what) std::cerr << what << ": " << ec.message() << "\n"; } -// This is the C++11 equivalent of a generic lambda. -// The function object is used to send an HTTP message. -template -struct send_lambda -{ - Stream& stream_; - bool& close_; - beast::error_code& ec_; - - explicit - send_lambda( - Stream& stream, - bool& close, - beast::error_code& ec) - : stream_(stream) - , close_(close) - , ec_(ec) - { - } - - template - void - operator()(http::message&& msg) const - { - // Determine if we should close the connection after - close_ = msg.need_eof(); - - // We need the serializer here because the serializer requires - // a non-const file_body, and the message oriented version of - // http::write only works with const messages. - http::serializer sr{msg}; - http::write(stream_, sr, ec_); - } -}; - // Handles an HTTP server connection void do_session( tcp::socket& socket, std::shared_ptr const& doc_root) { - bool close = false; beast::error_code ec; // This buffer is required to persist across reads beast::flat_buffer buffer; - // This lambda is used to send messages - send_lambda lambda{socket, close, ec}; - for(;;) { // Read a request @@ -270,11 +228,19 @@ do_session( if(ec) return fail(ec, "read"); + // Handle request + http::message_generator msg = + handle_request(*doc_root, std::move(req)); + + // Determine if we should close the connection + bool keep_alive = msg.keep_alive(); + // Send the response - handle_request(*doc_root, std::move(req), lambda); + beast::write(socket, std::move(msg), ec); + if(ec) return fail(ec, "write"); - if(close) + if(! keep_alive) { // This means we should close the connection, usually because // the response indicated the "Connection: close" semantic. diff --git a/example/websocket/server/chat-multi/http_session.cpp b/example/websocket/server/chat-multi/http_session.cpp index 9d4dee87..ee6cb0c3 100644 --- a/example/websocket/server/chat-multi/http_session.cpp +++ b/example/websocket/server/chat-multi/http_session.cpp @@ -12,8 +12,6 @@ #include #include -#define BOOST_NO_CXX14_GENERIC_LAMBDAS - //------------------------------------------------------------------------------ // Return a reasonable mime type based on the extension of a file. @@ -79,18 +77,15 @@ path_cat( return result; } -// This function produces an HTTP response for the given -// request. The type of the response object depends on the -// contents of the request, so the interface requires the -// caller to pass a generic lambda for receiving the response. -template< - class Body, class Allocator, - class Send> -void +// Return a response for the given request. +// +// The concrete type of the response message (which depends on the +// request), is type-erased in message_generator. +template +http::message_generator handle_request( beast::string_view doc_root, - http::request>&& req, - Send&& send) + http::request>&& req) { // Returns a bad request response auto const bad_request = @@ -134,13 +129,13 @@ handle_request( // Make sure we can handle the method if( req.method() != http::verb::get && req.method() != http::verb::head) - return send(bad_request("Unknown HTTP-method")); + return bad_request("Unknown HTTP-method"); // Request path must be absolute and not contain "..". if( req.target().empty() || req.target()[0] != '/' || req.target().find("..") != beast::string_view::npos) - return send(bad_request("Illegal request-target")); + return bad_request("Illegal request-target"); // Build the path to the requested file std::string path = path_cat(doc_root, req.target()); @@ -154,11 +149,11 @@ handle_request( // Handle the case where the file doesn't exist if(ec == boost::system::errc::no_such_file_or_directory) - return send(not_found(req.target())); + return not_found(req.target()); // Handle an unknown error if(ec) - return send(server_error(ec.message())); + return server_error(ec.message()); // Cache the size since we need it after the move auto const size = body.size(); @@ -171,7 +166,7 @@ handle_request( res.set(http::field::content_type, mime_type(path)); res.content_length(size); res.keep_alive(req.keep_alive()); - return send(std::move(res)); + return res; } // Respond to GET request @@ -183,45 +178,11 @@ handle_request( res.set(http::field::content_type, mime_type(path)); res.content_length(size); res.keep_alive(req.keep_alive()); - return send(std::move(res)); + return res; } //------------------------------------------------------------------------------ -struct http_session::send_lambda -{ - http_session& self_; - - explicit - send_lambda(http_session& self) - : self_(self) - { - } - - template - void - operator()(http::message&& msg) const - { - // The lifetime of the message has to extend - // for the duration of the async operation so - // we use a shared_ptr to manage it. - auto sp = boost::make_shared< - http::message>(std::move(msg)); - - // Write the response - auto self = self_.shared_from_this(); - http::async_write( - self_.stream_, - *sp, - [self, sp](beast::error_code ec, std::size_t bytes) - { - self->on_write(ec, bytes, sp->need_eof()); - }); - } -}; - -//------------------------------------------------------------------------------ - http_session:: http_session( tcp::socket&& socket, @@ -300,65 +261,33 @@ on_read(beast::error_code ec, std::size_t) return; } + // Handle request + http::message_generator msg = + handle_request(state_->doc_root(), parser_->release()); + + // Determine if we should close the connection + bool keep_alive = msg.keep_alive(); + + auto self = shared_from_this(); + // Send the response -#ifndef BOOST_NO_CXX14_GENERIC_LAMBDAS - // - // The following code requires generic - // lambdas, available in C++14 and later. - // - handle_request( - state_->doc_root(), - std::move(req_), - [this](auto&& response) + beast::async_write( + stream_, std::move(msg), + [self, keep_alive](beast::error_code ec, std::size_t bytes) { - // The lifetime of the message has to extend - // for the duration of the async operation so - // we use a shared_ptr to manage it. - using response_type = typename std::decay::type; - auto sp = boost::make_shared(std::forward(response)); - - #if 0 - // NOTE This causes an ICE in gcc 7.3 - // Write the response - http::async_write(this->stream_, *sp, - [self = shared_from_this(), sp]( - beast::error_code ec, std::size_t bytes) - { - self->on_write(ec, bytes, sp->need_eof()); - }); - #else - // Write the response - auto self = shared_from_this(); - http::async_write(stream_, *sp, - [self, sp]( - beast::error_code ec, std::size_t bytes) - { - self->on_write(ec, bytes, sp->need_eof()); - }); - #endif + self->on_write(ec, bytes, keep_alive); }); -#else - // - // This code uses the function object type send_lambda in - // place of a generic lambda which is not available in C++11 - // - handle_request( - state_->doc_root(), - parser_->release(), - send_lambda(*this)); - -#endif } void http_session:: -on_write(beast::error_code ec, std::size_t, bool close) +on_write(beast::error_code ec, std::size_t, bool keep_alive) { // Handle the error, if any if(ec) return fail(ec, "write"); - if(close) + if(! keep_alive) { // This means we should close the connection, usually because // the response indicated the "Connection: close" semantic.