forked from boostorg/beast
469 lines
13 KiB
C++
469 lines
13 KiB
C++
//
|
|
// Copyright (c) 2013-2017 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_SERVER_HTTP_SYNC_PORT_HPP
|
|
#define BEAST_EXAMPLE_SERVER_HTTP_SYNC_PORT_HPP
|
|
|
|
#include "server.hpp"
|
|
|
|
#include "http_base.hpp"
|
|
#include "rfc7231.hpp"
|
|
#include "service_list.hpp"
|
|
#include "write_msg.hpp"
|
|
|
|
#include <beast/core/flat_buffer.hpp>
|
|
#include <beast/core/handler_ptr.hpp>
|
|
#include <beast/http/dynamic_body.hpp>
|
|
#include <beast/http/parser.hpp>
|
|
#include <beast/http/read.hpp>
|
|
#include <beast/http/string_body.hpp>
|
|
#include <beast/http/write.hpp>
|
|
#include <memory>
|
|
#include <utility>
|
|
#include <ostream>
|
|
#include <thread>
|
|
|
|
namespace framework {
|
|
|
|
/** A synchronous HTTP connection.
|
|
|
|
This base class implements an HTTP connection object using
|
|
synchronous calls.
|
|
|
|
It uses the Curiously Recurring Template pattern (CRTP) where
|
|
we refer to the derived class in order to access the stream object
|
|
to use for reading and writing. This lets the same class be used
|
|
for plain and SSL stream objects.
|
|
|
|
@tparam Services The list of services this connection will support.
|
|
*/
|
|
template<class Derived, class... Services>
|
|
class sync_http_con_base
|
|
: public http_base
|
|
{
|
|
// This function lets us access members of the derived class
|
|
Derived&
|
|
impl()
|
|
{
|
|
return static_cast<Derived&>(*this);
|
|
}
|
|
|
|
// The stream to use for logging
|
|
std::ostream& log_;
|
|
|
|
// The services configured for the port
|
|
service_list<Services...> const& services_;
|
|
|
|
// A small unique integer for logging
|
|
std::size_t id_;
|
|
|
|
// The remote endpoint. We cache it here because
|
|
// calls to remote_endpoint() can fail / throw.
|
|
//
|
|
endpoint_type ep_;
|
|
|
|
// The buffer for performing reads
|
|
beast::flat_buffer buffer_;
|
|
|
|
public:
|
|
/// Constructor
|
|
sync_http_con_base(
|
|
beast::string_view server_name,
|
|
std::ostream& log,
|
|
service_list<Services...> const& services,
|
|
std::size_t id,
|
|
endpoint_type const& ep)
|
|
: http_base(server_name)
|
|
, log_(log)
|
|
, services_(services)
|
|
, id_(id)
|
|
, ep_(ep)
|
|
|
|
// The buffer has a limit of 8192, otherwise
|
|
// the server is vulnerable to a buffer attack.
|
|
//
|
|
, buffer_(8192)
|
|
{
|
|
}
|
|
|
|
// This is called to start the connection after
|
|
// it is accepted.
|
|
//
|
|
void
|
|
run()
|
|
{
|
|
// Bind a shared pointer into the lambda for the
|
|
// thread, so the sync_http_con_base is destroyed after
|
|
// the thread function exits.
|
|
//
|
|
std::thread{
|
|
&sync_http_con_base::do_run,
|
|
impl().shared_from_this()
|
|
}.detach();
|
|
}
|
|
|
|
protected:
|
|
// Called when a failure occurs
|
|
//
|
|
void
|
|
fail(std::string what, error_code ec)
|
|
{
|
|
if(ec)
|
|
{
|
|
log_ <<
|
|
"[#" << id_ << " " << ep_ << "] " <<
|
|
what << ": " << ec.message() << std::endl;
|
|
}
|
|
}
|
|
|
|
private:
|
|
// This lambda is passed to the service list to handle
|
|
// the case of sending request objects of varying types.
|
|
// In C++14 this is more easily accomplished using a generic
|
|
// lambda, but we want C+11 compatibility so we manually
|
|
// write the lambda out.
|
|
//
|
|
struct send_lambda
|
|
{
|
|
// holds "this"
|
|
sync_http_con_base& self_;
|
|
|
|
// holds the captured error code
|
|
error_code& ec_;
|
|
|
|
public:
|
|
// Constructor
|
|
//
|
|
// Capture "this" and "ec"
|
|
//
|
|
send_lambda(sync_http_con_base& self, error_code& ec)
|
|
: self_(self)
|
|
, ec_(ec)
|
|
{
|
|
}
|
|
|
|
// Sends a message
|
|
//
|
|
// Since this is a synchronous implementation we
|
|
// just call the write function and block.
|
|
//
|
|
template<class Body, class Fields>
|
|
void
|
|
operator()(
|
|
beast::http::response<Body, Fields>&& res) const
|
|
{
|
|
beast::http::write(self_.impl().stream(), res, ec_);
|
|
}
|
|
};
|
|
|
|
void
|
|
do_run()
|
|
{
|
|
error_code ec;
|
|
|
|
// Give the derived class a chance to do stuff before we
|
|
// enter the main loop. This is for SSL connections really.
|
|
//
|
|
impl().do_handshake(ec);
|
|
|
|
if(ec)
|
|
return fail("handshake", ec);
|
|
|
|
// The main connection loop, we alternate between
|
|
// reading a request and sending a response. On
|
|
// error we log and return, which destroys the thread
|
|
// and the stream (thus closing the connection)
|
|
//
|
|
for(;;)
|
|
{
|
|
// Arguments passed to the parser constructor are
|
|
// forwarded to the message object. A single argument
|
|
// is forwarded to the body constructor.
|
|
//
|
|
// We construct the dynamic body with a 1MB limit
|
|
// to prevent vulnerability to buffer attacks.
|
|
//
|
|
beast::http::request_parser<beast::http::dynamic_body> parser(1024* 1024);
|
|
|
|
// Read the header first
|
|
beast::http::read_header(impl().stream(), buffer_, parser, ec);
|
|
|
|
// This happens when the other end closes gracefully
|
|
//
|
|
if(ec == beast::http::error::end_of_stream)
|
|
{
|
|
// Give the derived class a chance to do stuff
|
|
impl().do_shutdown(ec);
|
|
if(ec)
|
|
return fail("shutdown", ec);
|
|
return;
|
|
}
|
|
|
|
// Any other error and we fail the connection
|
|
if(ec)
|
|
return fail("read_header", ec);
|
|
|
|
send_lambda send{*this, ec};
|
|
|
|
auto& req = parser.get();
|
|
|
|
// See if they are specifying Expect: 100-continue
|
|
//
|
|
if(rfc7231::is_expect_100_continue(req))
|
|
{
|
|
// They want to know if they should continue,
|
|
// so send the appropriate response synchronously.
|
|
//
|
|
send(this->continue_100(req));
|
|
|
|
// This happens when we send an HTTP message
|
|
// whose semantics indicate that the connection
|
|
// should be closed afterwards. For example if
|
|
// we send a Connection: close.
|
|
//
|
|
if(ec == beast::http::error::end_of_stream)
|
|
{
|
|
// Give the derived class a chance to do stuff
|
|
impl().do_shutdown(ec);
|
|
if(ec)
|
|
return fail("shutdown", ec);
|
|
return;
|
|
}
|
|
|
|
// Have to check the error every time we call the lambda
|
|
//
|
|
if(ec)
|
|
return fail("write", ec);
|
|
}
|
|
|
|
// Read the rest of the message, if any.
|
|
//
|
|
beast::http::read(impl().stream(), buffer_, parser, ec);
|
|
|
|
// Shouldn't be getting end_of_stream here;
|
|
// that would mean that we got an incomplete
|
|
// message, counting as an error.
|
|
//
|
|
if(ec)
|
|
return fail("read", ec);
|
|
|
|
// Give each service a chance to handle the request
|
|
//
|
|
if(! services_.respond(
|
|
std::move(impl().stream()),
|
|
ep_,
|
|
std::move(req),
|
|
send))
|
|
{
|
|
// No service handled the request,
|
|
// send a Bad Request result to the client.
|
|
//
|
|
send(this->bad_request(req));
|
|
|
|
// This happens when we send an HTTP message
|
|
// whose semantics indicate that the connection
|
|
// should be closed afterwards. For example if
|
|
// we send a Connection: close.
|
|
//
|
|
if(ec == beast::http::error::end_of_stream)
|
|
{
|
|
// Give the derived class a chance to do stuff
|
|
impl().do_shutdown(ec);
|
|
if(ec)
|
|
return fail("shutdown", ec);
|
|
return;
|
|
}
|
|
|
|
// Have to check the error every time we call the lambda
|
|
//
|
|
if(ec)
|
|
return fail("write", ec);
|
|
}
|
|
else
|
|
{
|
|
// This happens when we send an HTTP message
|
|
// whose semantics indicate that the connection
|
|
// should be closed afterwards. For example if
|
|
// we send a Connection: close.
|
|
//
|
|
if(ec == beast::http::error::end_of_stream)
|
|
{
|
|
// Give the derived class a chance to do stuff
|
|
impl().do_shutdown(ec);
|
|
if(ec)
|
|
return fail("shutdown", ec);
|
|
return;
|
|
}
|
|
|
|
// Have to check the error every time we call the lambda
|
|
//
|
|
if(ec)
|
|
return fail("write", ec);
|
|
|
|
// See if the service that handled the
|
|
// response took ownership of the stream.
|
|
if(! impl().stream().lowest_layer().is_open())
|
|
{
|
|
// They took ownership so just return and
|
|
// let this sync_http_con_base object get destroyed.
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Theres no pipelining possible in a synchronous server
|
|
// because we can't do reads and writes at the same time.
|
|
}
|
|
}
|
|
};
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
// This class represents a synchronous HTTP connection which
|
|
// uses a plain TCP/IP socket (no encryption) as the stream.
|
|
//
|
|
template<class... Services>
|
|
class sync_http_con
|
|
|
|
// Note that we give this object the `enable_shared_from_this`, and have
|
|
// the base class call `impl().shared_from_this()` when needed.
|
|
//
|
|
: public std::enable_shared_from_this<sync_http_con<Services...>>
|
|
|
|
// We want the socket to be created before the base class so we use
|
|
// the "base from member" idiom which Boost provides as a class.
|
|
//
|
|
, public base_from_member<socket_type>
|
|
|
|
// Declare this base last now that everything else got set up first.
|
|
//
|
|
, public sync_http_con_base<sync_http_con<Services...>, Services...>
|
|
{
|
|
public:
|
|
// Construct the plain connection.
|
|
//
|
|
template<class... Args>
|
|
sync_http_con(
|
|
socket_type&& sock,
|
|
Args&&... args)
|
|
: base_from_member<socket_type>(std::move(sock))
|
|
, sync_http_con_base<sync_http_con<Services...>, Services...>(
|
|
std::forward<Args>(args)...)
|
|
{
|
|
}
|
|
|
|
// Returns the stream.
|
|
//
|
|
// The base class calls this to obtain the object to
|
|
// use for reading and writing HTTP messages.
|
|
//
|
|
socket_type&
|
|
stream()
|
|
{
|
|
return this->member;
|
|
}
|
|
|
|
private:
|
|
// Base class needs to be a friend to call our private members
|
|
friend class sync_http_con_base<sync_http_con<Services...>, Services...>;
|
|
|
|
// This is called by the base before running the main loop.
|
|
// There's nothing to do for a plain connection.
|
|
//
|
|
void
|
|
do_handshake(error_code& ec)
|
|
{
|
|
// This is required by the specifications for error_code
|
|
//
|
|
ec = {};
|
|
}
|
|
|
|
// This is called when the other end closes the connection gracefully.
|
|
//
|
|
void
|
|
do_shutdown(error_code& ec)
|
|
{
|
|
stream().shutdown(socket_type::shutdown_both, ec);
|
|
}
|
|
};
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
/* A synchronous HTTP port handler
|
|
|
|
This type meets the requirements of @b PortHandler. It supports
|
|
variable list of HTTP services in its template parameter list,
|
|
and provides a synchronous connection implementation to service
|
|
*/
|
|
template<class... Services>
|
|
class http_sync_port
|
|
{
|
|
server& instance_;
|
|
std::ostream& log_;
|
|
service_list<Services...> services_;
|
|
|
|
public:
|
|
/** Constructor
|
|
|
|
@param instance The server instance which owns this port
|
|
|
|
@param log The stream to use for logging
|
|
*/
|
|
http_sync_port(
|
|
server& instance,
|
|
std::ostream& log)
|
|
: instance_(instance)
|
|
, log_(log)
|
|
{
|
|
}
|
|
|
|
/** Initialize a service
|
|
|
|
Every service in the list must be initialized exactly once.
|
|
|
|
@param ec Set to the error, if any occurred
|
|
|
|
@param args Optional arguments forwarded to the service
|
|
constructor.
|
|
|
|
@tparam Index The 0-based index of the service to initialize.
|
|
*/
|
|
template<std::size_t Index, class... Args>
|
|
void
|
|
init(error_code& ec, Args&&... args)
|
|
{
|
|
services_.template init<Index>(
|
|
ec,
|
|
std::forward<Args>(args)...);
|
|
}
|
|
|
|
/** Called by the server to provide ownership of the socket for a new connection
|
|
|
|
@param sock The socket whose ownership is to be transferred
|
|
|
|
@ep The remote endpoint
|
|
*/
|
|
void
|
|
on_accept(socket_type&& sock, endpoint_type ep)
|
|
{
|
|
// Create a plain http connection object
|
|
// and transfer ownership of the socket.
|
|
//
|
|
std::make_shared<sync_http_con<Services...>>(
|
|
std::move(sock),
|
|
"http_sync_port",
|
|
log_,
|
|
services_,
|
|
instance_.next_id(),
|
|
ep
|
|
)->run();
|
|
}
|
|
};
|
|
|
|
} // framework
|
|
|
|
#endif
|