From 8eb15148d7b9dbd4fee785a0f253100c43ee537f Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Thu, 28 Feb 2019 10:01:54 -0800 Subject: [PATCH] Advanced servers use HTTP parser interfaces for reading --- CHANGELOG.md | 1 + doc/qbk/02_examples/_examples.qbk | 3 + doc/qbk/release_notes.qbk | 2 + .../server-flex/advanced_server_flex.cpp | 29 +++--- example/advanced/server/advanced_server.cpp | 33 ++++--- .../server/chat-multi/http_session.cpp | 99 +++++++++++-------- .../server/chat-multi/http_session.hpp | 20 ++-- 7 files changed, 111 insertions(+), 76 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 78857bdc..9a406048 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ Version 224: * Remove extraneous error check in advanced-server-flex +* Advanced servers use HTTP parser interfaces for reading -------------------------------------------------------------------------------- diff --git a/doc/qbk/02_examples/_examples.qbk b/doc/qbk/02_examples/_examples.qbk index ebe3edfb..bda7c573 100644 --- a/doc/qbk/02_examples/_examples.qbk +++ b/doc/qbk/02_examples/_examples.qbk @@ -148,6 +148,7 @@ and illustrate the implementation of advanced features. [Timeouts] [Multi-threaded] [HTTP pipelining] + [Parser-oriented HTTP reading] [Dual protocols: HTTP and WebSocket] [Clean exit via SIGINT (CTRL+C) or SIGTERM (kill)] ]] @@ -158,6 +159,7 @@ and illustrate the implementation of advanced features. [Timeouts] [Multi-threaded] [HTTP pipelining] + [Parser-oriented HTTP reading] [Dual protocols: HTTP and WebSocket] [Flexible ports: plain and SSL on the same port] [Clean exit via SIGINT (CTRL+C) or SIGTERM (kill)] @@ -170,6 +172,7 @@ and illustrate the implementation of advanced features. [Broadcasting Messages] [Multi-user Chat Server] [JavaScript Browser Client] + [Parser-oriented HTTP reading] [Dual protocols: HTTP and WebSocket] [Clean exit via SIGINT (CTRL+C) or SIGTERM (kill)] ]] diff --git a/doc/qbk/release_notes.qbk b/doc/qbk/release_notes.qbk index ca3f1f65..864fc169 100644 --- a/doc/qbk/release_notes.qbk +++ b/doc/qbk/release_notes.qbk @@ -305,6 +305,8 @@ * ([issue 1401]) Examples use `flat_buffer` +* Advanced servers use HTTP parser interfaces for reading + * detect-ssl is rewritten * New example [path_link example/websocket/server/chat-multi example/websocket/server/chat-multi] diff --git a/example/advanced/server-flex/advanced_server_flex.cpp b/example/advanced/server-flex/advanced_server_flex.cpp index 38bb8491..3f379b23 100644 --- a/example/advanced/server-flex/advanced_server_flex.cpp +++ b/example/advanced/server-flex/advanced_server_flex.cpp @@ -25,7 +25,7 @@ #include #include #include -#include +#include #include #include #include @@ -561,9 +561,12 @@ class http_session }; std::shared_ptr doc_root_; - http::request req_; queue queue_; + // The parser is stored in an optional container so we can + // construct it from scratch it at the beginning of each new message. + boost::optional> parser_; + protected: beast::flat_buffer buffer_; @@ -581,19 +584,22 @@ public: void do_read() { - // Make the request empty before reading, - // otherwise the operation behavior is undefined. - req_ = {}; + // Construct a new parser for each message + parser_.emplace(); + + // Apply a reasonable limit to the allowed size + // of the body in bytes to prevent abuse. + parser_->body_limit(10000); // Set the timeout. beast::get_lowest_layer( derived().stream()).expires_after(std::chrono::seconds(30)); - // Read a request + // Read a request using the parser-oriented interface http::async_read( derived().stream(), buffer_, - req_, + *parser_, beast::bind_front_handler( &http_session::on_read, derived().shared_from_this())); @@ -612,20 +618,21 @@ public: return fail(ec, "read"); // See if it is a WebSocket Upgrade - if(websocket::is_upgrade(req_)) + if(websocket::is_upgrade(parser_->get())) { // Disable the timeout. // The websocket::stream uses its own timeout settings. beast::get_lowest_layer(derived().stream()).expires_never(); - // Transfer the stream to a new WebSocket session. + // Create a websocket session, transferring ownership + // of both the socket and the HTTP request. return make_websocket_session( derived().release_stream(), - std::move(req_)); + parser_->release()); } // Send the response - handle_request(*doc_root_, std::move(req_), queue_); + handle_request(*doc_root_, parser_->release(), queue_); // If we aren't at the queue limit, try to pipeline another request if(! queue_.is_full()) diff --git a/example/advanced/server/advanced_server.cpp b/example/advanced/server/advanced_server.cpp index aff62c30..11a4142e 100644 --- a/example/advanced/server/advanced_server.cpp +++ b/example/advanced/server/advanced_server.cpp @@ -21,7 +21,7 @@ #include #include #include -#include +#include #include #include #include @@ -320,6 +320,8 @@ private: } }; +//------------------------------------------------------------------------------ + // Handles an HTTP server connection class http_session : public std::enable_shared_from_this { @@ -416,9 +418,12 @@ class http_session : public std::enable_shared_from_this beast::tcp_stream stream_; beast::flat_buffer buffer_; std::shared_ptr doc_root_; - http::request req_; queue queue_; + // The parser is stored in an optional container so we can + // construct it from scratch it at the beginning of each new message. + boost::optional> parser_; + public: // Take ownership of the socket http_session( @@ -441,16 +446,21 @@ private: void do_read() { + // Construct a new parser for each message + parser_.emplace(); - // Make the request empty before reading, - // otherwise the operation behavior is undefined. - req_ = {}; + // Apply a reasonable limit to the allowed size + // of the body in bytes to prevent abuse. + parser_->body_limit(10000); // Set the timeout. stream_.expires_after(std::chrono::seconds(30)); - // Read a request - http::async_read(stream_, buffer_, req_, + // Read a request using the parser-oriented interface + http::async_read( + stream_, + buffer_, + *parser_, beast::bind_front_handler( &http_session::on_read, shared_from_this())); @@ -469,16 +479,17 @@ private: return fail(ec, "read"); // See if it is a WebSocket Upgrade - if(websocket::is_upgrade(req_)) + if(websocket::is_upgrade(parser_->get())) { - // Create a websocket session by transferring the socket + // Create a websocket session, transferring ownership + // of both the socket and the HTTP request. std::make_shared( - stream_.release_socket())->do_accept(std::move(req_)); + stream_.release_socket())->do_accept(parser_->release()); return; } // Send the response - handle_request(*doc_root_, std::move(req_), queue_); + handle_request(*doc_root_, std::move(parser_->release()), queue_); // If we aren't at the queue limit, try to pipeline another request if(! queue_.is_full()) diff --git a/example/websocket/server/chat-multi/http_session.cpp b/example/websocket/server/chat-multi/http_session.cpp index 60344f95..07921785 100644 --- a/example/websocket/server/chat-multi/http_session.cpp +++ b/example/websocket/server/chat-multi/http_session.cpp @@ -188,6 +188,40 @@ handle_request( //------------------------------------------------------------------------------ +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, @@ -201,14 +235,7 @@ void http_session:: run() { - // Set the timeout. - stream_.expires_after(std::chrono::seconds(30)); - - // Read a request - http::async_read(stream_, buffer_, req_, - beast::bind_front_handler( - &http_session::on_read, - shared_from_this())); + do_read(); } // Report a failure @@ -223,27 +250,28 @@ fail(beast::error_code ec, char const* what) std::cerr << what << ": " << ec.message() << "\n"; } -template void http_session:: -send_lambda:: -operator()(http::message&& msg) const +do_read() { - // 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)); + // Construct a new parser for each message + parser_.emplace(); - // 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()); - }); + // Apply a reasonable limit to the allowed size + // of the body in bytes to prevent abuse. + parser_->body_limit(10000); + + // Set the timeout. + stream_.expires_after(std::chrono::seconds(30)); + + // Read a request + http::async_read( + stream_, + buffer_, + parser_->get(), + beast::bind_front_handler( + &http_session::on_read, + shared_from_this())); } void @@ -262,12 +290,13 @@ on_read(beast::error_code ec, std::size_t) return fail(ec, "read"); // See if it is a WebSocket Upgrade - if(websocket::is_upgrade(req_)) + if(websocket::is_upgrade(parser_->get())) { - // Create a WebSocket session by transferring the socket + // Create a websocket session, transferring ownership + // of both the socket and the HTTP request. boost::make_shared( stream_.release_socket(), - state_)->run(std::move(req_)); + state_)->run(parser_->release()); return; } @@ -315,7 +344,7 @@ on_read(beast::error_code ec, std::size_t) // handle_request( state_->doc_root(), - std::move(req_), + parser_->release(), send_lambda(*this)); #endif @@ -337,16 +366,6 @@ on_write(beast::error_code ec, std::size_t, bool close) return; } - // Clear contents of the request message, - // otherwise the read behavior is undefined. - req_ = {}; - - // Set the timeout. - stream_.expires_after(std::chrono::seconds(30)); - // Read another request - http::async_read(stream_, buffer_, req_, - beast::bind_front_handler( - &http_session::on_read, - shared_from_this())); + do_read(); } diff --git a/example/websocket/server/chat-multi/http_session.hpp b/example/websocket/server/chat-multi/http_session.hpp index 442ab23c..637a5d89 100644 --- a/example/websocket/server/chat-multi/http_session.hpp +++ b/example/websocket/server/chat-multi/http_session.hpp @@ -13,6 +13,7 @@ #include "net.hpp" #include "beast.hpp" #include "shared_state.hpp" +#include #include #include #include @@ -24,24 +25,15 @@ class http_session : public boost::enable_shared_from_this beast::tcp_stream stream_; beast::flat_buffer buffer_; boost::shared_ptr state_; - http::request req_; - struct send_lambda - { - http_session& self_; + // The parser is stored in an optional container so we can + // construct it from scratch it at the beginning of each new message. + boost::optional> parser_; - explicit - send_lambda(http_session& self) - : self_(self) - { - } - - template - void - operator()(http::message&& msg) const; - }; + struct send_lambda; void fail(beast::error_code ec, char const* what); + void do_read(); void on_read(beast::error_code ec, std::size_t); void on_write(beast::error_code ec, std::size_t, bool close);