Advanced servers use HTTP parser interfaces for reading

This commit is contained in:
Vinnie Falco
2019-02-28 10:01:54 -08:00
parent 16280f9991
commit 8eb15148d7
7 changed files with 111 additions and 76 deletions

View File

@ -1,6 +1,7 @@
Version 224:
* Remove extraneous error check in advanced-server-flex
* Advanced servers use HTTP parser interfaces for reading
--------------------------------------------------------------------------------

View File

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

View File

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

View File

@ -25,7 +25,7 @@
#include <boost/asio/steady_timer.hpp>
#include <boost/asio/strand.hpp>
#include <boost/make_unique.hpp>
#include <boost/config.hpp>
#include <boost/optional.hpp>
#include <algorithm>
#include <cstdlib>
#include <functional>
@ -561,9 +561,12 @@ class http_session
};
std::shared_ptr<std::string const> doc_root_;
http::request<http::string_body> 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<http::request_parser<http::string_body>> 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())

View File

@ -21,7 +21,7 @@
#include <boost/asio/signal_set.hpp>
#include <boost/asio/strand.hpp>
#include <boost/make_unique.hpp>
#include <boost/config.hpp>
#include <boost/optional.hpp>
#include <algorithm>
#include <cstdlib>
#include <functional>
@ -320,6 +320,8 @@ private:
}
};
//------------------------------------------------------------------------------
// Handles an HTTP server connection
class http_session : public std::enable_shared_from_this<http_session>
{
@ -416,9 +418,12 @@ class http_session : public std::enable_shared_from_this<http_session>
beast::tcp_stream stream_;
beast::flat_buffer buffer_;
std::shared_ptr<std::string const> doc_root_;
http::request<http::string_body> 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<http::request_parser<http::string_body>> 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<websocket_session>(
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())

View File

@ -188,6 +188,40 @@ handle_request(
//------------------------------------------------------------------------------
struct http_session::send_lambda
{
http_session& self_;
explicit
send_lambda(http_session& self)
: self_(self)
{
}
template<bool isRequest, class Body, class Fields>
void
operator()(http::message<isRequest, Body, Fields>&& 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<isRequest, Body, Fields>>(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<bool isRequest, class Body, class Fields>
void
http_session::
send_lambda::
operator()(http::message<isRequest, Body, Fields>&& 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<isRequest, Body, Fields>>(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<websocket_session>(
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();
}

View File

@ -13,6 +13,7 @@
#include "net.hpp"
#include "beast.hpp"
#include "shared_state.hpp"
#include <boost/optional.hpp>
#include <boost/smart_ptr.hpp>
#include <cstdlib>
#include <memory>
@ -24,24 +25,15 @@ class http_session : public boost::enable_shared_from_this<http_session>
beast::tcp_stream stream_;
beast::flat_buffer buffer_;
boost::shared_ptr<shared_state> state_;
http::request<http::string_body> 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<http::request_parser<http::string_body>> parser_;
explicit
send_lambda(http_session& self)
: self_(self)
{
}
template<bool isRequest, class Body, class Fields>
void
operator()(http::message<isRequest, Body, Fields>&& 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);