Fixes and simplifications to HTTP example server:

The example HTTP server is updated to provide the correct MIME-type.
It no longer uses the now-deprecated http::stream class, since that
implementation does not provide flow control. A new example async_write
function is provided in the asynchronous server for managing the
lifetime of a message sent asynchronously.

The logging is thread-safe, and a bug causing connections to
malfunction is fixed.
This commit is contained in:
Vinnie Falco
2016-06-20 10:48:53 -04:00
parent 64206b5612
commit b42c928d5e
5 changed files with 315 additions and 98 deletions

View File

@@ -19,9 +19,8 @@ endif()
add_executable (http-server add_executable (http-server
${BEAST_INCLUDES} ${BEAST_INCLUDES}
file_body.hpp file_body.hpp
mime_type.hpp
http_async_server.hpp http_async_server.hpp
http_stream.hpp
http_stream.ipp
http_sync_server.hpp http_sync_server.hpp
http_server.cpp http_server.cpp
) )

View File

@@ -9,9 +9,11 @@
#define BEAST_EXAMPLE_HTTP_ASYNC_SERVER_H_INCLUDED #define BEAST_EXAMPLE_HTTP_ASYNC_SERVER_H_INCLUDED
#include "file_body.hpp" #include "file_body.hpp"
#include "http_stream.hpp" #include "mime_type.hpp"
#include <beast/http.hpp>
#include <beast/core/placeholders.hpp> #include <beast/core/placeholders.hpp>
#include <beast/core/streambuf.hpp>
#include <boost/asio.hpp> #include <boost/asio.hpp>
#include <cstdio> #include <cstdio>
#include <iostream> #include <iostream>
@@ -32,17 +34,19 @@ class http_async_server
using req_type = request_v1<string_body>; using req_type = request_v1<string_body>;
using resp_type = response_v1<file_body>; using resp_type = response_v1<file_body>;
std::mutex m_;
bool log_ = true;
boost::asio::io_service ios_; boost::asio::io_service ios_;
socket_type sock_;
boost::asio::ip::tcp::acceptor acceptor_; boost::asio::ip::tcp::acceptor acceptor_;
socket_type sock_;
std::string root_; std::string root_;
std::vector<std::thread> thread_; std::vector<std::thread> thread_;
public: public:
http_async_server(endpoint_type const& ep, http_async_server(endpoint_type const& ep,
int threads, std::string const& root) int threads, std::string const& root)
: sock_(ios_) : acceptor_(ios_)
, acceptor_(ios_) , sock_(ios_)
, root_(root) , root_(root)
{ {
acceptor_.open(ep.protocol()); acceptor_.open(ep.protocol());
@@ -67,13 +71,124 @@ public:
t.join(); t.join();
} }
template<class... Args>
void
log(Args const&... args)
{
if(log_)
{
std::lock_guard<std::mutex> lock(m_);
log_args(args...);
}
}
private: private:
template<class Stream, class Handler,
bool isRequest, class Body, class Headers>
class write_op
{
using alloc_type =
handler_alloc<char, Handler>;
struct data
{
Stream& s;
message_v1<isRequest, Body, Headers> m;
Handler h;
bool cont;
template<class DeducedHandler>
data(DeducedHandler&& h_, Stream& s_,
message_v1<isRequest, Body, Headers>&& m_)
: s(s_)
, m(std::move(m_))
, h(std::forward<DeducedHandler>(h_))
, cont(boost_asio_handler_cont_helpers::
is_continuation(h))
{
}
};
std::shared_ptr<data> d_;
public:
write_op(write_op&&) = default;
write_op(write_op const&) = default;
template<class DeducedHandler, class... Args>
write_op(DeducedHandler&& h, Stream& s, Args&&... args)
: d_(std::allocate_shared<data>(alloc_type{h},
std::forward<DeducedHandler>(h), s,
std::forward<Args>(args)...))
{
(*this)(error_code{}, false);
}
void
operator()(error_code ec, bool again = true)
{
auto& d = *d_;
d.cont = d.cont || again;
if(! again)
{
beast::http::async_write(d.s, d.m, std::move(*this));
return;
}
d.h(ec);
}
friend
void* asio_handler_allocate(
std::size_t size, write_op* op)
{
return boost_asio_handler_alloc_helpers::
allocate(size, op->d_->h);
}
friend
void asio_handler_deallocate(
void* p, std::size_t size, write_op* op)
{
return boost_asio_handler_alloc_helpers::
deallocate(p, size, op->d_->h);
}
friend
bool asio_handler_is_continuation(write_op* op)
{
return op->d_->cont;
}
template <class Function>
friend
void asio_handler_invoke(Function&& f, write_op* op)
{
return boost_asio_handler_invoke_helpers::
invoke(f, op->d_->h);
}
};
template<class Stream,
bool isRequest, class Body, class Headers,
class DeducedHandler>
static
void
async_write(Stream& stream, message_v1<
isRequest, Body, Headers>&& msg,
DeducedHandler&& handler)
{
write_op<Stream, typename std::decay<DeducedHandler>::type,
isRequest, Body, Headers>{std::forward<DeducedHandler>(
handler), stream, std::move(msg)};
}
class peer : public std::enable_shared_from_this<peer> class peer : public std::enable_shared_from_this<peer>
{ {
int id_; int id_;
stream<socket_type> stream_; streambuf sb_;
socket_type sock_;
http_async_server& server_;
boost::asio::io_service::strand strand_; boost::asio::io_service::strand strand_;
std::string root_;
req_type req_; req_type req_;
public: public:
@@ -82,16 +197,22 @@ private:
peer& operator=(peer&&) = delete; peer& operator=(peer&&) = delete;
peer& operator=(peer const&) = delete; peer& operator=(peer const&) = delete;
explicit peer(socket_type&& sock, http_async_server& server)
peer(socket_type&& sock, std::string const& root) : sock_(std::move(sock))
: stream_(std::move(sock)) , server_(server)
, strand_(stream_.get_io_service()) , strand_(sock_.get_io_service())
, root_(root)
{ {
static int n = 0; static int n = 0;
id_ = ++n; id_ = ++n;
} }
void
fail(error_code ec, std::string what)
{
if(ec != boost::asio::error::operation_aborted)
server_.log("#", id_, " ", what, ": ", ec.message(), "\n");
}
void run() void run()
{ {
do_read(); do_read();
@@ -99,43 +220,58 @@ private:
void do_read() void do_read()
{ {
stream_.async_read(req_, strand_.wrap( async_read(sock_, sb_, req_, strand_.wrap(
std::bind(&peer::on_read, shared_from_this(), std::bind(&peer::on_read, shared_from_this(),
asio::placeholders::error))); asio::placeholders::error)));
} }
void on_read(error_code ec) void on_read(error_code const& ec)
{ {
if(ec) if(ec)
return fail(ec, "read"); return fail(ec, "read");
do_read();
auto path = req_.url; auto path = req_.url;
if(path == "/") if(path == "/")
path = "/index.html"; path = "/index.html";
path = root_ + path; path = server_.root_ + path;
if(! boost::filesystem::exists(path)) if(! boost::filesystem::exists(path))
{ {
response_v1<string_body> resp; response_v1<string_body> res;
resp.status = 404; res.status = 404;
resp.reason = "Not Found"; res.reason = "Not Found";
resp.version = req_.version; res.version = req_.version;
resp.headers.replace("Server", "http_async_server"); res.headers.insert("Server", "http_async_server");
resp.body = "The file '" + path + "' was not found"; res.headers.insert("Content-Type", "text/html");
prepare(resp); res.body = "The file '" + path + "' was not found";
stream_.async_write(std::move(resp), prepare(res);
async_write(sock_, std::move(res),
std::bind(&peer::on_write, shared_from_this(), std::bind(&peer::on_write, shared_from_this(),
asio::placeholders::error)); asio::placeholders::error));
return; return;
} }
resp_type resp; resp_type res;
resp.status = 200; res.status = 200;
resp.reason = "OK"; res.reason = "OK";
resp.version = req_.version; res.version = req_.version;
resp.headers.replace("Server", "http_async_server"); res.headers.insert("Server", "http_async_server");
resp.headers.replace("Content-Type", "text/html"); res.headers.insert("Content-Type", mime_type(path));
resp.body = path; res.body = path;
prepare(resp); try
stream_.async_write(std::move(resp), {
prepare(res);
}
catch(std::exception const& e)
{
res = {};
res.status = 500;
res.reason = "Internal Error";
res.version = req_.version;
res.headers.insert("Server", "http_async_server");
res.headers.insert("Content-Type", "text/html");
res.body =
std::string{"An internal error occurred"} + e.what();
prepare(res);
}
async_write(sock_, std::move(res),
std::bind(&peer::on_write, shared_from_this(), std::bind(&peer::on_write, shared_from_this(),
asio::placeholders::error)); asio::placeholders::error));
} }
@@ -144,36 +280,27 @@ private:
{ {
if(ec) if(ec)
fail(ec, "write"); fail(ec, "write");
} do_read();
private:
void
fail(error_code ec, std::string what)
{
if(ec != boost::asio::error::operation_aborted)
{
std::cerr <<
"#" << std::to_string(id_) << " " <<
what << ": " << ec.message() << std::endl;
}
} }
}; };
void void
fail(error_code ec, std::string what) log_args()
{ {
std::cerr << }
what << ": " << ec.message() << std::endl;
template<class Arg, class... Args>
void
log_args(Arg const& arg, Args const&... args)
{
std::cerr << arg;
log_args(args...);
} }
void void
maybe_throw(error_code ec, std::string what) fail(error_code ec, std::string what)
{ {
if(ec) log(what, ": ", ec.message(), "\n");
{
fail(ec, what);
throw ec;
}
} }
void void
@@ -181,12 +308,13 @@ private:
{ {
if(! acceptor_.is_open()) if(! acceptor_.is_open())
return; return;
maybe_throw(ec, "accept"); if(ec)
return fail(ec, "accept");
socket_type sock(std::move(sock_)); socket_type sock(std::move(sock_));
acceptor_.async_accept(sock_, acceptor_.async_accept(sock_,
std::bind(&http_async_server::on_accept, this, std::bind(&http_async_server::on_accept, this,
asio::placeholders::error)); asio::placeholders::error));
std::make_shared<peer>(std::move(sock), root_)->run(); std::make_shared<peer>(std::move(sock), *this)->run();
} }
}; };

View File

@@ -57,8 +57,13 @@ int main(int ac, char const* av[])
endpoint_type ep{address_type::from_string(ip), port}; endpoint_type ep{address_type::from_string(ip), port};
if(sync) if(sync)
{
http_sync_server server(ep, root); http_sync_server server(ep, root);
beast::test::sig_wait();
}
else else
{
http_async_server server(ep, threads, root); http_async_server server(ep, threads, root);
beast::test::sig_wait(); beast::test::sig_wait();
} }
}

View File

@@ -9,8 +9,9 @@
#define BEAST_EXAMPLE_HTTP_SYNC_SERVER_H_INCLUDED #define BEAST_EXAMPLE_HTTP_SYNC_SERVER_H_INCLUDED
#include "file_body.hpp" #include "file_body.hpp"
#include "http_stream.hpp" #include "mime_type.hpp"
#include <beast/core/streambuf.hpp>
#include <boost/asio.hpp> #include <boost/asio.hpp>
#include <cstdint> #include <cstdint>
#include <cstdio> #include <cstdio>
@@ -34,6 +35,8 @@ class http_sync_server
using req_type = request_v1<string_body>; using req_type = request_v1<string_body>;
using resp_type = response_v1<file_body>; using resp_type = response_v1<file_body>;
bool log_ = true;
std::mutex m_;
boost::asio::io_service ios_; boost::asio::io_service ios_;
socket_type sock_; socket_type sock_;
boost::asio::ip::tcp::acceptor acceptor_; boost::asio::ip::tcp::acceptor acceptor_;
@@ -65,21 +68,43 @@ public:
thread_.join(); thread_.join();
} }
template<class... Args>
void void
fail(error_code ec, std::string what) log(Args const&... args)
{ {
std::cerr << if(log_)
what << ": " << ec.message() << std::endl; {
std::lock_guard<std::mutex> lock(m_);
log_args(args...);
}
}
private:
void
log_args()
{
}
template<class Arg, class... Args>
void
log_args(Arg const& arg, Args const&... args)
{
std::cerr << arg;
log_args(args...);
} }
void void
maybe_throw(error_code ec, std::string what) fail(error_code ec, std::string what)
{ {
if(ec) log(what, ": ", ec.message(), "\n");
{
fail(ec, what);
throw ec;
} }
void
fail(int id, error_code const& ec)
{
if(ec != boost::asio::error::operation_aborted &&
ec != boost::asio::error::eof)
log("#", id, " ", ec.message(), "\n");
} }
struct lambda struct lambda
@@ -109,7 +134,8 @@ public:
{ {
if(! acceptor_.is_open()) if(! acceptor_.is_open())
return; return;
maybe_throw(ec, "accept"); if(ec)
return fail(ec, "accept");
static int id_ = 0; static int id_ = 0;
std::thread{lambda{++id_, *this, std::move(sock_)}}.detach(); std::thread{lambda{++id_, *this, std::move(sock_)}}.detach();
acceptor_.async_accept(sock_, acceptor_.async_accept(sock_,
@@ -118,23 +144,15 @@ public:
} }
void void
fail(int id, error_code const& ec) do_peer(int id, socket_type&& sock0)
{ {
if(ec != boost::asio::error::operation_aborted && socket_type sock(std::move(sock0));
ec != boost::asio::error::eof) streambuf sb;
std::cerr <<
"#" << std::to_string(id) << " " << std::endl;
}
void
do_peer(int id, socket_type&& sock)
{
http::stream<socket_type> hs(std::move(sock));
error_code ec; error_code ec;
for(;;) for(;;)
{ {
req_type req; req_type req;
hs.read(req, ec); http::read(sock, sb, req, ec);
if(ec) if(ec)
break; break;
auto path = req.url; auto path = req.url;
@@ -143,26 +161,42 @@ public:
path = root_ + path; path = root_ + path;
if(! boost::filesystem::exists(path)) if(! boost::filesystem::exists(path))
{ {
response_v1<string_body> resp; response_v1<string_body> res;
resp.status = 404; res.status = 404;
resp.reason = "Not Found"; res.reason = "Not Found";
resp.version = req.version; res.version = req.version;
resp.headers.replace("Server", "http_sync_server"); res.headers.insert("Server", "http_sync_server");
resp.body = "The file '" + path + "' was not found"; res.headers.insert("Content-Type", "text/html");
prepare(resp); res.body = "The file '" + path + "' was not found";
hs.write(resp, ec); prepare(res);
write(sock, res, ec);
if(ec) if(ec)
break; break;
} }
resp_type resp; resp_type res;
resp.status = 200; res.status = 200;
resp.reason = "OK"; res.reason = "OK";
resp.version = req.version; res.version = req.version;
resp.headers.replace("Server", "http_sync_server"); res.headers.insert("Server", "http_sync_server");
resp.headers.replace("Content-Type", "text/html"); res.headers.insert("Content-Type", mime_type(path));
resp.body = path; res.body = path;
prepare(resp); try
hs.write(resp, ec); {
prepare(res);
}
catch(std::exception const& e)
{
res = {};
res.status = 500;
res.reason = "Internal Error";
res.version = req.version;
res.headers.insert("Server", "http_sync_server");
res.headers.insert("Content-Type", "text/html");
res.body =
std::string{"An internal error occurred"} + e.what();
prepare(res);
}
write(sock, res, ec);
if(ec) if(ec)
break; break;
} }

51
examples/mime_type.hpp Normal file
View File

@@ -0,0 +1,51 @@
//
// Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifndef BEAST_EXAMPLE_HTTP_MIME_TYPE_H_INCLUDED
#define BEAST_EXAMPLE_HTTP_MIME_TYPE_H_INCLUDED
#include <string>
#include <boost/filesystem/path.hpp>
namespace beast {
namespace http {
// Return the Mime-Type for a given file extension
template<class = void>
std::string
mime_type(std::string const& path)
{
auto const ext =
boost::filesystem::path{path}.extension().string();
if(ext == ".txt") return "text/plain";
if(ext == ".htm") return "text/html";
if(ext == ".html") return "text/html";
if(ext == ".php") return "text/html";
if(ext == ".css") return "text/css";
if(ext == ".js") return "application/javascript";
if(ext == ".json") return "application/json";
if(ext == ".xml") return "application/xml";
if(ext == ".swf") return "application/x-shockwave-flash";
if(ext == ".flv") return "video/x-flv";
if(ext == ".png") return "image/png";
if(ext == ".jpe") return "image/jpeg";
if(ext == ".jpeg") return "image/jpeg";
if(ext == ".jpg") return "image/jpeg";
if(ext == ".gif") return "image/gif";
if(ext == ".bmp") return "image/bmp";
if(ext == ".ico") return "image/vnd.microsoft.icon";
if(ext == ".tiff") return "image/tiff";
if(ext == ".tif") return "image/tiff";
if(ext == ".svg") return "image/svg+xml";
if(ext == ".svgz") return "image/svg+xml";
return "application/text";
}
} // http
} // beast
#endif