From b42c928d5e68b68b72ef7cd99eaba883f00ba4e0 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Mon, 20 Jun 2016 10:48:53 -0400 Subject: [PATCH] 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. --- examples/CMakeLists.txt | 3 +- examples/http_async_server.hpp | 238 +++++++++++++++++++++++++-------- examples/http_server.cpp | 7 +- examples/http_sync_server.hpp | 114 ++++++++++------ examples/mime_type.hpp | 51 +++++++ 5 files changed, 315 insertions(+), 98 deletions(-) create mode 100644 examples/mime_type.hpp diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 5b3d0972..fea89e64 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -19,9 +19,8 @@ endif() add_executable (http-server ${BEAST_INCLUDES} file_body.hpp + mime_type.hpp http_async_server.hpp - http_stream.hpp - http_stream.ipp http_sync_server.hpp http_server.cpp ) diff --git a/examples/http_async_server.hpp b/examples/http_async_server.hpp index bcb088d1..501aa8a5 100644 --- a/examples/http_async_server.hpp +++ b/examples/http_async_server.hpp @@ -9,9 +9,11 @@ #define BEAST_EXAMPLE_HTTP_ASYNC_SERVER_H_INCLUDED #include "file_body.hpp" -#include "http_stream.hpp" +#include "mime_type.hpp" +#include #include +#include #include #include #include @@ -32,17 +34,19 @@ class http_async_server using req_type = request_v1; using resp_type = response_v1; + std::mutex m_; + bool log_ = true; boost::asio::io_service ios_; - socket_type sock_; boost::asio::ip::tcp::acceptor acceptor_; + socket_type sock_; std::string root_; std::vector thread_; public: http_async_server(endpoint_type const& ep, int threads, std::string const& root) - : sock_(ios_) - , acceptor_(ios_) + : acceptor_(ios_) + , sock_(ios_) , root_(root) { acceptor_.open(ep.protocol()); @@ -67,13 +71,124 @@ public: t.join(); } + template + void + log(Args const&... args) + { + if(log_) + { + std::lock_guard lock(m_); + log_args(args...); + } + } + private: + template + class write_op + { + using alloc_type = + handler_alloc; + + struct data + { + Stream& s; + message_v1 m; + Handler h; + bool cont; + + template + data(DeducedHandler&& h_, Stream& s_, + message_v1&& m_) + : s(s_) + , m(std::move(m_)) + , h(std::forward(h_)) + , cont(boost_asio_handler_cont_helpers:: + is_continuation(h)) + { + } + }; + + std::shared_ptr d_; + + public: + write_op(write_op&&) = default; + write_op(write_op const&) = default; + + template + write_op(DeducedHandler&& h, Stream& s, Args&&... args) + : d_(std::allocate_shared(alloc_type{h}, + std::forward(h), s, + std::forward(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 + friend + void asio_handler_invoke(Function&& f, write_op* op) + { + return boost_asio_handler_invoke_helpers:: + invoke(f, op->d_->h); + } + }; + + template + static + void + async_write(Stream& stream, message_v1< + isRequest, Body, Headers>&& msg, + DeducedHandler&& handler) + { + write_op::type, + isRequest, Body, Headers>{std::forward( + handler), stream, std::move(msg)}; + } + class peer : public std::enable_shared_from_this { int id_; - stream stream_; + streambuf sb_; + socket_type sock_; + http_async_server& server_; boost::asio::io_service::strand strand_; - std::string root_; req_type req_; public: @@ -82,16 +197,22 @@ private: peer& operator=(peer&&) = delete; peer& operator=(peer const&) = delete; - explicit - peer(socket_type&& sock, std::string const& root) - : stream_(std::move(sock)) - , strand_(stream_.get_io_service()) - , root_(root) + peer(socket_type&& sock, http_async_server& server) + : sock_(std::move(sock)) + , server_(server) + , strand_(sock_.get_io_service()) { static int n = 0; 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() { do_read(); @@ -99,43 +220,58 @@ private: void do_read() { - stream_.async_read(req_, strand_.wrap( + async_read(sock_, sb_, req_, strand_.wrap( std::bind(&peer::on_read, shared_from_this(), asio::placeholders::error))); } - void on_read(error_code ec) + void on_read(error_code const& ec) { if(ec) return fail(ec, "read"); - do_read(); auto path = req_.url; if(path == "/") path = "/index.html"; - path = root_ + path; + path = server_.root_ + path; if(! boost::filesystem::exists(path)) { - response_v1 resp; - resp.status = 404; - resp.reason = "Not Found"; - resp.version = req_.version; - resp.headers.replace("Server", "http_async_server"); - resp.body = "The file '" + path + "' was not found"; - prepare(resp); - stream_.async_write(std::move(resp), + response_v1 res; + res.status = 404; + res.reason = "Not Found"; + res.version = req_.version; + res.headers.insert("Server", "http_async_server"); + res.headers.insert("Content-Type", "text/html"); + res.body = "The file '" + path + "' was not found"; + prepare(res); + async_write(sock_, std::move(res), std::bind(&peer::on_write, shared_from_this(), asio::placeholders::error)); return; } - resp_type resp; - resp.status = 200; - resp.reason = "OK"; - resp.version = req_.version; - resp.headers.replace("Server", "http_async_server"); - resp.headers.replace("Content-Type", "text/html"); - resp.body = path; - prepare(resp); - stream_.async_write(std::move(resp), + resp_type res; + res.status = 200; + res.reason = "OK"; + res.version = req_.version; + res.headers.insert("Server", "http_async_server"); + res.headers.insert("Content-Type", mime_type(path)); + res.body = path; + try + { + 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(), asio::placeholders::error)); } @@ -144,36 +280,27 @@ private: { if(ec) fail(ec, "write"); - } - - 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; - } + do_read(); } }; void - fail(error_code ec, std::string what) + log_args() { - std::cerr << - what << ": " << ec.message() << std::endl; + } + + template + void + log_args(Arg const& arg, Args const&... args) + { + std::cerr << arg; + log_args(args...); } void - maybe_throw(error_code ec, std::string what) + fail(error_code ec, std::string what) { - if(ec) - { - fail(ec, what); - throw ec; - } + log(what, ": ", ec.message(), "\n"); } void @@ -181,12 +308,13 @@ private: { if(! acceptor_.is_open()) return; - maybe_throw(ec, "accept"); + if(ec) + return fail(ec, "accept"); socket_type sock(std::move(sock_)); acceptor_.async_accept(sock_, std::bind(&http_async_server::on_accept, this, asio::placeholders::error)); - std::make_shared(std::move(sock), root_)->run(); + std::make_shared(std::move(sock), *this)->run(); } }; diff --git a/examples/http_server.cpp b/examples/http_server.cpp index 32d1b13b..246508a8 100644 --- a/examples/http_server.cpp +++ b/examples/http_server.cpp @@ -57,8 +57,13 @@ int main(int ac, char const* av[]) endpoint_type ep{address_type::from_string(ip), port}; if(sync) + { http_sync_server server(ep, root); + beast::test::sig_wait(); + } else + { http_async_server server(ep, threads, root); - beast::test::sig_wait(); + beast::test::sig_wait(); + } } diff --git a/examples/http_sync_server.hpp b/examples/http_sync_server.hpp index af3f1d19..83706847 100644 --- a/examples/http_sync_server.hpp +++ b/examples/http_sync_server.hpp @@ -9,8 +9,9 @@ #define BEAST_EXAMPLE_HTTP_SYNC_SERVER_H_INCLUDED #include "file_body.hpp" -#include "http_stream.hpp" +#include "mime_type.hpp" +#include #include #include #include @@ -34,6 +35,8 @@ class http_sync_server using req_type = request_v1; using resp_type = response_v1; + bool log_ = true; + std::mutex m_; boost::asio::io_service ios_; socket_type sock_; boost::asio::ip::tcp::acceptor acceptor_; @@ -65,21 +68,43 @@ public: thread_.join(); } + template void - fail(error_code ec, std::string what) + log(Args const&... args) { - std::cerr << - what << ": " << ec.message() << std::endl; + if(log_) + { + std::lock_guard lock(m_); + log_args(args...); + } + } + +private: + void + log_args() + { + } + + template + void + log_args(Arg const& arg, Args const&... args) + { + std::cerr << arg; + log_args(args...); } void - maybe_throw(error_code ec, std::string what) + fail(error_code ec, std::string what) { - if(ec) - { - fail(ec, what); - throw ec; - } + log(what, ": ", ec.message(), "\n"); + } + + 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 @@ -109,7 +134,8 @@ public: { if(! acceptor_.is_open()) return; - maybe_throw(ec, "accept"); + if(ec) + return fail(ec, "accept"); static int id_ = 0; std::thread{lambda{++id_, *this, std::move(sock_)}}.detach(); acceptor_.async_accept(sock_, @@ -118,23 +144,15 @@ public: } void - fail(int id, error_code const& ec) + do_peer(int id, socket_type&& sock0) { - if(ec != boost::asio::error::operation_aborted && - ec != boost::asio::error::eof) - std::cerr << - "#" << std::to_string(id) << " " << std::endl; - } - - void - do_peer(int id, socket_type&& sock) - { - http::stream hs(std::move(sock)); + socket_type sock(std::move(sock0)); + streambuf sb; error_code ec; for(;;) { req_type req; - hs.read(req, ec); + http::read(sock, sb, req, ec); if(ec) break; auto path = req.url; @@ -143,26 +161,42 @@ public: path = root_ + path; if(! boost::filesystem::exists(path)) { - response_v1 resp; - resp.status = 404; - resp.reason = "Not Found"; - resp.version = req.version; - resp.headers.replace("Server", "http_sync_server"); - resp.body = "The file '" + path + "' was not found"; - prepare(resp); - hs.write(resp, ec); + response_v1 res; + res.status = 404; + res.reason = "Not Found"; + res.version = req.version; + res.headers.insert("Server", "http_sync_server"); + res.headers.insert("Content-Type", "text/html"); + res.body = "The file '" + path + "' was not found"; + prepare(res); + write(sock, res, ec); if(ec) break; } - resp_type resp; - resp.status = 200; - resp.reason = "OK"; - resp.version = req.version; - resp.headers.replace("Server", "http_sync_server"); - resp.headers.replace("Content-Type", "text/html"); - resp.body = path; - prepare(resp); - hs.write(resp, ec); + resp_type res; + res.status = 200; + res.reason = "OK"; + res.version = req.version; + res.headers.insert("Server", "http_sync_server"); + res.headers.insert("Content-Type", mime_type(path)); + res.body = path; + try + { + 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) break; } diff --git a/examples/mime_type.hpp b/examples/mime_type.hpp new file mode 100644 index 00000000..d20aa605 --- /dev/null +++ b/examples/mime_type.hpp @@ -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 +#include + +namespace beast { +namespace http { + +// Return the Mime-Type for a given file extension +template +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