forked from boostorg/beast
New server-framework, full featured server example:
A new server framework is introduced, allowing users to quickly get off the ground. Example servers are refactored to use the common framework.
This commit is contained in:
1 parent
e856c46864
commit
fd9a13b11f
60 files changed
+3701
-1376
No files matched your search
@@ -1,18 +0,0 @@
|
||||
# Part of Beast
|
||||
|
||||
GroupSources(extras/beast extras)
|
||||
GroupSources(include/beast beast)
|
||||
|
||||
GroupSources(example/http-server "/")
|
||||
|
||||
add_executable (http-server
|
||||
${BEAST_INCLUDES}
|
||||
${EXTRAS_INCLUDES}
|
||||
file_body.hpp
|
||||
mime_type.hpp
|
||||
http_async_server.hpp
|
||||
http_sync_server.hpp
|
||||
main.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(http-server Beast ${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} )
|
||||
@@ -1,10 +0,0 @@
|
||||
#
|
||||
# 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)
|
||||
#
|
||||
|
||||
exe http-server :
|
||||
main.cpp
|
||||
;
|
||||
@@ -1,411 +0,0 @@
|
||||
//
|
||||
// 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_FILE_BODY_H_INCLUDED
|
||||
#define BEAST_EXAMPLE_FILE_BODY_H_INCLUDED
|
||||
|
||||
#include <beast/core/error.hpp>
|
||||
#include <beast/http/message.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/assert.hpp>
|
||||
#include <boost/optional.hpp>
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <cstdint>
|
||||
#include <utility>
|
||||
|
||||
namespace beast {
|
||||
namespace http {
|
||||
|
||||
//[example_http_file_body_1
|
||||
|
||||
struct file_body
|
||||
{
|
||||
/** The type of the @ref message::body member.
|
||||
|
||||
Messages declared using `file_body` will have this
|
||||
type for the body member. We use a path indicating
|
||||
the location on the file system for which the data
|
||||
will be read or written.
|
||||
*/
|
||||
using value_type = boost::filesystem::path;
|
||||
|
||||
/** Returns the content length of the body in a message.
|
||||
|
||||
This optional static function returns the size of the
|
||||
body in bytes. It is called from @ref message::size to
|
||||
return the payload size, and from @ref message::prepare
|
||||
to automatically set the Content-Length field. If this
|
||||
function is omitted from a body type, calls to
|
||||
@ref message::prepare will set the chunked transfer
|
||||
encoding.
|
||||
|
||||
@param m The message containing a file body to check.
|
||||
|
||||
@return The size of the file in bytes.
|
||||
*/
|
||||
static
|
||||
std::uint64_t
|
||||
size(value_type const& v);
|
||||
|
||||
/** Algorithm for retrieving buffers when serializing.
|
||||
|
||||
Objects of this type are created during serialization
|
||||
to extract the buffers representing the body.
|
||||
*/
|
||||
class reader;
|
||||
|
||||
/** Algorithm for storing buffers when parsing.
|
||||
|
||||
Objects of this type are created during parsing
|
||||
to store incoming buffers representing the body.
|
||||
*/
|
||||
class writer;
|
||||
};
|
||||
|
||||
//]
|
||||
|
||||
//[example_http_file_body_2
|
||||
|
||||
std::uint64_t
|
||||
file_body::
|
||||
size(value_type const& v)
|
||||
{
|
||||
return boost::filesystem::file_size(v);
|
||||
}
|
||||
|
||||
//]
|
||||
|
||||
//[example_http_file_body_3
|
||||
|
||||
class file_body::reader
|
||||
{
|
||||
value_type const& path_; // Path of the file
|
||||
FILE* file_ = nullptr; // File handle
|
||||
std::uint64_t remain_ = 0; // The number of unread bytes
|
||||
char buf_[4096]; // Small buffer for reading
|
||||
|
||||
public:
|
||||
// This nested type informs the serializer that it should
|
||||
// wait until after sending the header to initialize the
|
||||
// reader. We set this to true, otherwise opening the file
|
||||
// during `init` could introduce latency which delays
|
||||
// the remote endpoint from receiving the header quickly.
|
||||
//
|
||||
using is_deferred = std::true_type;
|
||||
|
||||
// The type of buffer sequence returned by `get`.
|
||||
//
|
||||
using const_buffers_type =
|
||||
boost::asio::const_buffers_1;
|
||||
|
||||
// Constructor.
|
||||
//
|
||||
// This is called after the header is serialized, because
|
||||
// we declared `is_deferred` to be `std::true_type`.
|
||||
// `m` holds the message we are sending, which will
|
||||
// always have the `file_body` as the body type.
|
||||
//
|
||||
template<bool isRequest, class Fields>
|
||||
reader(message<isRequest, file_body, Fields> const& m);
|
||||
|
||||
// Destructor
|
||||
~reader();
|
||||
|
||||
// This function is called once before serialization
|
||||
// of the body is started.
|
||||
//
|
||||
void
|
||||
init(error_code& ec);
|
||||
|
||||
// This function is called zero or more times to
|
||||
// retrieve buffers. A return value of `boost::none`
|
||||
// means there are no more buffers. Otherwise,
|
||||
// the contained pair will have the next buffer
|
||||
// to serialize, and a `bool` indicating whether
|
||||
// or not there may be additional buffers.
|
||||
boost::optional<std::pair<const_buffers_type, bool>>
|
||||
get(error_code& ec);
|
||||
|
||||
// This function is called when reading is complete.
|
||||
// It is an opportunity to perform any final actions
|
||||
// which might fail, in order to return an error code.
|
||||
// Operations that might fail should not be attemped in
|
||||
// destructors, since an exception thrown from there
|
||||
// would terminate the program.
|
||||
void
|
||||
finish(error_code& ec);
|
||||
};
|
||||
|
||||
//]
|
||||
|
||||
//[example_http_file_body_4
|
||||
|
||||
// Here we just stash a reference to the path for later.
|
||||
// Rather than dealing with messy constructor exceptions,
|
||||
// we save the things that might fail for the call to `init`.
|
||||
//
|
||||
template<bool isRequest, class Fields>
|
||||
file_body::reader::
|
||||
reader(message<isRequest, file_body, Fields> const& m)
|
||||
: path_(m.body)
|
||||
{
|
||||
}
|
||||
|
||||
// This gets called right after construction, and provides
|
||||
// the opportunity to return an error code. The error code
|
||||
// will be propagated to the serializer and eventually
|
||||
// returned to the caller.
|
||||
//
|
||||
inline
|
||||
void
|
||||
file_body::reader::
|
||||
init(error_code& ec)
|
||||
{
|
||||
// Attempt to open the file for reading
|
||||
file_ = fopen(path_.string().c_str(), "rb");
|
||||
|
||||
if(! file_)
|
||||
{
|
||||
// Convert the old-school `errno` into
|
||||
// an error code using the system category.
|
||||
ec = error_code{errno, system_category()};
|
||||
return;
|
||||
}
|
||||
|
||||
// The file was opened successfully, get the size
|
||||
// of the file to know how much we need to read.
|
||||
ec = {};
|
||||
remain_ = boost::filesystem::file_size(path_);
|
||||
}
|
||||
|
||||
// This function is called repeatedly by the serializer to
|
||||
// retrieve the buffers representing the body. Our strategy
|
||||
// is to read into our buffer and return it until we have
|
||||
// read through the whole file.
|
||||
//
|
||||
inline
|
||||
auto
|
||||
file_body::reader::
|
||||
get(error_code& ec) ->
|
||||
boost::optional<std::pair<const_buffers_type, bool>>
|
||||
{
|
||||
// Calculate the smaller of our buffer size,
|
||||
// or the amount of unread data in the file.
|
||||
auto const amount = std::min<std::uint64_t>(remain_, sizeof(buf_));
|
||||
|
||||
// Check for an empty file
|
||||
if(amount == 0)
|
||||
{
|
||||
ec = {};
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
// Now read the next buffer
|
||||
auto const nread = fread(buf_, 1, amount, file_);
|
||||
|
||||
// Handle any errors
|
||||
if(ferror(file_))
|
||||
{
|
||||
// Convert old-school `errno` to error_code
|
||||
ec = error_code(errno, system_category());
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
// Make sure there is forward progress
|
||||
BOOST_ASSERT(nread != 0);
|
||||
BOOST_ASSERT(nread <= remain_);
|
||||
|
||||
// Update the amount remaining based on what we got
|
||||
remain_ -= nread;
|
||||
|
||||
// Return the buffer to the caller.
|
||||
//
|
||||
// The second element of the pair indicates whether or
|
||||
// not there is more data. As long as there is some
|
||||
// unread bytes, there will be more data. Otherwise,
|
||||
// we set this bool to `false` so we will not be called
|
||||
// again.
|
||||
//
|
||||
ec = {};
|
||||
return {{
|
||||
const_buffers_type{buf_, nread}, // buffer to return.
|
||||
remain_ > 0 // `true` if there are more buffers.
|
||||
}};
|
||||
}
|
||||
|
||||
// Called after reading is done when there's no error.
|
||||
inline
|
||||
void
|
||||
file_body::reader::
|
||||
finish(error_code& ec)
|
||||
{
|
||||
// Functions which pass back errors are
|
||||
// responsible for clearing the error code.
|
||||
ec = {};
|
||||
}
|
||||
|
||||
// The destructor is always invoked if construction succeeds.
|
||||
//
|
||||
inline
|
||||
file_body::reader::
|
||||
~reader()
|
||||
{
|
||||
// Just close the file if its open
|
||||
if(file_)
|
||||
fclose(file_);
|
||||
|
||||
// In theory fclose() can fail but how would we handle it?
|
||||
}
|
||||
|
||||
//]
|
||||
|
||||
//[example_http_file_body_5
|
||||
|
||||
class file_body::writer
|
||||
{
|
||||
value_type const& path_; // A path to the file
|
||||
FILE* file_ = nullptr; // The file handle
|
||||
|
||||
public:
|
||||
// Constructor.
|
||||
//
|
||||
// This is called after the header is parsed and
|
||||
// indicates that a non-zero sized body may be present.
|
||||
// `m` holds the message we are receiving, which will
|
||||
// always have the `file_body` as the body type.
|
||||
//
|
||||
template<bool isRequest, class Fields>
|
||||
explicit
|
||||
writer(message<isRequest, file_body, Fields>& m);
|
||||
|
||||
// This function is called once before parsing
|
||||
// of the body is started.
|
||||
//
|
||||
void
|
||||
init(boost::optional<std::uint64_t> const& content_length, error_code& ec);
|
||||
|
||||
// This function is called one or more times to store
|
||||
// buffer sequences corresponding to the incoming body.
|
||||
//
|
||||
template<class ConstBufferSequence>
|
||||
void
|
||||
put(ConstBufferSequence const& buffers, error_code& ec);
|
||||
|
||||
// This function is called when writing is complete.
|
||||
// It is an opportunity to perform any final actions
|
||||
// which might fail, in order to return an error code.
|
||||
// Operations that might fail should not be attemped in
|
||||
// destructors, since an exception thrown from there
|
||||
// would terminate the program.
|
||||
//
|
||||
void
|
||||
finish(error_code& ec);
|
||||
|
||||
// Destructor.
|
||||
//
|
||||
// Avoid calling anything that might fail here.
|
||||
//
|
||||
~writer();
|
||||
};
|
||||
|
||||
//]
|
||||
|
||||
//[example_http_file_body_6
|
||||
|
||||
// Just stash a reference to the path so we can open the file later.
|
||||
template<bool isRequest, class Fields>
|
||||
file_body::writer::
|
||||
writer(message<isRequest, file_body, Fields>& m)
|
||||
: path_(m.body)
|
||||
{
|
||||
}
|
||||
|
||||
// This gets called once when we know there's a body.
|
||||
// If the content_length is set, it lets us know the exact size
|
||||
// of the body. An implementation could use this to optimize its
|
||||
// storage strategy. For example by attempting to reserve space
|
||||
// ahead of time.
|
||||
//
|
||||
inline
|
||||
void
|
||||
file_body::writer::
|
||||
init(boost::optional<std::uint64_t> const& content_length, error_code& ec)
|
||||
{
|
||||
// Attempt to open the file for writing
|
||||
file_ = fopen(path_.string().c_str(), "wb");
|
||||
|
||||
if(! file_)
|
||||
{
|
||||
// Convert the old-school `errno` into
|
||||
// an error code using the system category.
|
||||
ec = error_code{errno, system_category()};
|
||||
return;
|
||||
}
|
||||
|
||||
// Indicate success
|
||||
ec = {};
|
||||
}
|
||||
|
||||
// This will get called one or more times with body buffers
|
||||
//
|
||||
template<class ConstBufferSequence>
|
||||
void
|
||||
file_body::writer::
|
||||
put(ConstBufferSequence const& buffers, error_code& ec)
|
||||
{
|
||||
// Loop over all the buffers in the sequence,
|
||||
// and write each one to the file.
|
||||
for(boost::asio::const_buffer buffer : buffers)
|
||||
{
|
||||
// Write this buffer to the file
|
||||
fwrite(
|
||||
boost::asio::buffer_cast<void const*>(buffer), 1,
|
||||
boost::asio::buffer_size(buffer),
|
||||
file_);
|
||||
|
||||
// Handle any errors
|
||||
if(ferror(file_))
|
||||
{
|
||||
// Convert old-school `errno` to error_code
|
||||
ec = error_code(errno, system_category());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Indicate success
|
||||
ec = {};
|
||||
}
|
||||
|
||||
// Called after writing is done when there's no error.
|
||||
inline
|
||||
void
|
||||
file_body::writer::
|
||||
finish(error_code& ec)
|
||||
{
|
||||
ec = {};
|
||||
}
|
||||
|
||||
// The destructor is always invoked if construction succeeds
|
||||
//
|
||||
inline
|
||||
file_body::writer::
|
||||
~writer()
|
||||
{
|
||||
// Just close the file if its open
|
||||
if(file_)
|
||||
fclose(file_);
|
||||
|
||||
// In theory fclose() can fail but how would we handle it?
|
||||
}
|
||||
|
||||
//]
|
||||
|
||||
} // http
|
||||
} // beast
|
||||
|
||||
#endif
|
||||
@@ -1,323 +0,0 @@
|
||||
//
|
||||
// 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_HTTP_ASYNC_SERVER_H_INCLUDED
|
||||
#define BEAST_EXAMPLE_HTTP_ASYNC_SERVER_H_INCLUDED
|
||||
|
||||
#include "file_body.hpp"
|
||||
#include "mime_type.hpp"
|
||||
|
||||
#include <beast/http.hpp>
|
||||
#include <beast/core/handler_ptr.hpp>
|
||||
#include <beast/core/multi_buffer.hpp>
|
||||
#include <boost/asio.hpp>
|
||||
#include <cstddef>
|
||||
#include <cstdio>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
|
||||
namespace beast {
|
||||
namespace http {
|
||||
|
||||
class http_async_server
|
||||
{
|
||||
using endpoint_type = boost::asio::ip::tcp::endpoint;
|
||||
using address_type = boost::asio::ip::address;
|
||||
using socket_type = boost::asio::ip::tcp::socket;
|
||||
|
||||
using req_type = request<string_body>;
|
||||
using resp_type = response<file_body>;
|
||||
|
||||
std::mutex m_;
|
||||
bool log_ = true;
|
||||
boost::asio::io_service ios_;
|
||||
boost::asio::ip::tcp::acceptor acceptor_;
|
||||
socket_type sock_;
|
||||
std::string root_;
|
||||
std::vector<std::thread> thread_;
|
||||
|
||||
public:
|
||||
http_async_server(endpoint_type const& ep,
|
||||
std::size_t threads, std::string const& root)
|
||||
: acceptor_(ios_)
|
||||
, sock_(ios_)
|
||||
, root_(root)
|
||||
{
|
||||
acceptor_.open(ep.protocol());
|
||||
acceptor_.bind(ep);
|
||||
acceptor_.listen(
|
||||
boost::asio::socket_base::max_connections);
|
||||
acceptor_.async_accept(sock_,
|
||||
std::bind(&http_async_server::on_accept, this,
|
||||
std::placeholders::_1));
|
||||
thread_.reserve(threads);
|
||||
for(std::size_t i = 0; i < threads; ++i)
|
||||
thread_.emplace_back(
|
||||
[&] { ios_.run(); });
|
||||
}
|
||||
|
||||
~http_async_server()
|
||||
{
|
||||
error_code ec;
|
||||
ios_.dispatch(
|
||||
[&]{ acceptor_.close(ec); });
|
||||
for(auto& t : thread_)
|
||||
t.join();
|
||||
}
|
||||
|
||||
template<class... Args>
|
||||
void
|
||||
log(Args const&... args)
|
||||
{
|
||||
if(log_)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_);
|
||||
log_args(args...);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
template<class Stream, class Handler,
|
||||
bool isRequest, class Body, class Fields>
|
||||
class write_op
|
||||
{
|
||||
struct data
|
||||
{
|
||||
bool cont;
|
||||
Stream& s;
|
||||
message<isRequest, Body, Fields> m;
|
||||
|
||||
data(Handler& handler, Stream& s_,
|
||||
message<isRequest, Body, Fields>&& m_)
|
||||
: s(s_)
|
||||
, m(std::move(m_))
|
||||
{
|
||||
using boost::asio::asio_handler_is_continuation;
|
||||
cont = asio_handler_is_continuation(std::addressof(handler));
|
||||
}
|
||||
};
|
||||
|
||||
handler_ptr<data, Handler> 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::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_.invoke(ec);
|
||||
}
|
||||
|
||||
friend
|
||||
void* asio_handler_allocate(
|
||||
std::size_t size, write_op* op)
|
||||
{
|
||||
using boost::asio::asio_handler_allocate;
|
||||
return asio_handler_allocate(
|
||||
size, std::addressof(op->d_.handler()));
|
||||
}
|
||||
|
||||
friend
|
||||
void asio_handler_deallocate(
|
||||
void* p, std::size_t size, write_op* op)
|
||||
{
|
||||
using boost::asio::asio_handler_deallocate;
|
||||
asio_handler_deallocate(
|
||||
p, size, std::addressof(op->d_.handler()));
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
using boost::asio::asio_handler_invoke;
|
||||
asio_handler_invoke(
|
||||
f, std::addressof(op->d_.handler()));
|
||||
}
|
||||
};
|
||||
|
||||
template<class Stream,
|
||||
bool isRequest, class Body, class Fields,
|
||||
class DeducedHandler>
|
||||
static
|
||||
void
|
||||
async_write(Stream& stream, message<
|
||||
isRequest, Body, Fields>&& msg,
|
||||
DeducedHandler&& handler)
|
||||
{
|
||||
write_op<Stream, typename std::decay<DeducedHandler>::type,
|
||||
isRequest, Body, Fields>{std::forward<DeducedHandler>(
|
||||
handler), stream, std::move(msg)};
|
||||
}
|
||||
|
||||
class peer : public std::enable_shared_from_this<peer>
|
||||
{
|
||||
int id_;
|
||||
multi_buffer sb_;
|
||||
socket_type sock_;
|
||||
http_async_server& server_;
|
||||
boost::asio::io_service::strand strand_;
|
||||
req_type req_;
|
||||
|
||||
public:
|
||||
peer(peer&&) = default;
|
||||
peer(peer const&) = default;
|
||||
peer& operator=(peer&&) = delete;
|
||||
peer& operator=(peer const&) = delete;
|
||||
|
||||
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 &&
|
||||
ec != error::end_of_stream)
|
||||
server_.log("#", id_, " ", what, ": ", ec.message(), "\n");
|
||||
}
|
||||
|
||||
void run()
|
||||
{
|
||||
do_read();
|
||||
}
|
||||
|
||||
void do_read()
|
||||
{
|
||||
async_read(sock_, sb_, req_, strand_.wrap(
|
||||
std::bind(&peer::on_read, shared_from_this(),
|
||||
std::placeholders::_1)));
|
||||
}
|
||||
|
||||
void on_read(error_code const& ec)
|
||||
{
|
||||
if(ec)
|
||||
return fail(ec, "read");
|
||||
auto path = req_.target().to_string();
|
||||
if(path == "/")
|
||||
path = "/index.html";
|
||||
path = server_.root_ + path;
|
||||
if(! boost::filesystem::exists(path))
|
||||
{
|
||||
response<string_body> res;
|
||||
res.result(status::not_found);
|
||||
res.version = req_.version;
|
||||
res.insert(field::server, "http_async_server");
|
||||
res.insert(field::content_type, "text/html");
|
||||
res.body = "The file '" + path + "' was not found";
|
||||
res.prepare();
|
||||
async_write(sock_, std::move(res),
|
||||
std::bind(&peer::on_write, shared_from_this(),
|
||||
std::placeholders::_1));
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
resp_type res;
|
||||
res.result(status::ok);
|
||||
res.version = req_.version;
|
||||
res.insert(field::server, "http_async_server");
|
||||
res.insert(field::content_type, mime_type(path));
|
||||
res.body = path;
|
||||
res.prepare();
|
||||
async_write(sock_, std::move(res),
|
||||
std::bind(&peer::on_write, shared_from_this(),
|
||||
std::placeholders::_1));
|
||||
}
|
||||
catch(std::exception const& e)
|
||||
{
|
||||
response<string_body> res;
|
||||
res.result(status::internal_server_error);
|
||||
res.version = req_.version;
|
||||
res.insert(field::server, "http_async_server");
|
||||
res.insert(field::content_type, "text/html");
|
||||
res.body =
|
||||
std::string{"An internal error occurred"} + e.what();
|
||||
res.prepare();
|
||||
async_write(sock_, std::move(res),
|
||||
std::bind(&peer::on_write, shared_from_this(),
|
||||
std::placeholders::_1));
|
||||
}
|
||||
}
|
||||
|
||||
void on_write(error_code ec)
|
||||
{
|
||||
if(ec)
|
||||
fail(ec, "write");
|
||||
do_read();
|
||||
}
|
||||
};
|
||||
|
||||
void
|
||||
log_args()
|
||||
{
|
||||
}
|
||||
|
||||
template<class Arg, class... Args>
|
||||
void
|
||||
log_args(Arg const& arg, Args const&... args)
|
||||
{
|
||||
std::cerr << arg;
|
||||
log_args(args...);
|
||||
}
|
||||
|
||||
void
|
||||
fail(error_code ec, std::string what)
|
||||
{
|
||||
log(what, ": ", ec.message(), "\n");
|
||||
}
|
||||
|
||||
void
|
||||
on_accept(error_code ec)
|
||||
{
|
||||
if(! acceptor_.is_open())
|
||||
return;
|
||||
if(ec)
|
||||
return fail(ec, "accept");
|
||||
socket_type sock(std::move(sock_));
|
||||
acceptor_.async_accept(sock_,
|
||||
std::bind(&http_async_server::on_accept, this,
|
||||
std::placeholders::_1));
|
||||
std::make_shared<peer>(std::move(sock), *this)->run();
|
||||
}
|
||||
};
|
||||
|
||||
} // http
|
||||
} // beast
|
||||
|
||||
#endif
|
||||
@@ -1,215 +0,0 @@
|
||||
//
|
||||
// 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_HTTP_SYNC_SERVER_H_INCLUDED
|
||||
#define BEAST_EXAMPLE_HTTP_SYNC_SERVER_H_INCLUDED
|
||||
|
||||
#include "file_body.hpp"
|
||||
#include "mime_type.hpp"
|
||||
|
||||
#include <beast/http.hpp>
|
||||
#include <beast/core/multi_buffer.hpp>
|
||||
#include <boost/asio.hpp>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace beast {
|
||||
namespace http {
|
||||
|
||||
class http_sync_server
|
||||
{
|
||||
using endpoint_type = boost::asio::ip::tcp::endpoint;
|
||||
using address_type = boost::asio::ip::address;
|
||||
using socket_type = boost::asio::ip::tcp::socket;
|
||||
|
||||
using req_type = request<string_body>;
|
||||
using resp_type = response<file_body>;
|
||||
|
||||
bool log_ = true;
|
||||
std::mutex m_;
|
||||
boost::asio::io_service ios_;
|
||||
socket_type sock_;
|
||||
boost::asio::ip::tcp::acceptor acceptor_;
|
||||
std::string root_;
|
||||
std::thread thread_;
|
||||
|
||||
public:
|
||||
http_sync_server(endpoint_type const& ep,
|
||||
std::string const& root)
|
||||
: sock_(ios_)
|
||||
, acceptor_(ios_)
|
||||
, root_(root)
|
||||
{
|
||||
acceptor_.open(ep.protocol());
|
||||
acceptor_.bind(ep);
|
||||
acceptor_.listen(
|
||||
boost::asio::socket_base::max_connections);
|
||||
acceptor_.async_accept(sock_,
|
||||
std::bind(&http_sync_server::on_accept, this,
|
||||
std::placeholders::_1));
|
||||
thread_ = std::thread{[&]{ ios_.run(); }};
|
||||
}
|
||||
|
||||
~http_sync_server()
|
||||
{
|
||||
error_code ec;
|
||||
ios_.dispatch(
|
||||
[&]{ acceptor_.close(ec); });
|
||||
thread_.join();
|
||||
}
|
||||
|
||||
template<class... Args>
|
||||
void
|
||||
log(Args const&... args)
|
||||
{
|
||||
if(log_)
|
||||
{
|
||||
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
|
||||
fail(error_code ec, std::string what)
|
||||
{
|
||||
log(what, ": ", ec.message(), "\n");
|
||||
}
|
||||
|
||||
void
|
||||
fail(int id, error_code const& ec)
|
||||
{
|
||||
if(ec != boost::asio::error::operation_aborted &&
|
||||
ec != error::end_of_stream)
|
||||
log("#", id, " ", ec.message(), "\n");
|
||||
}
|
||||
|
||||
struct lambda
|
||||
{
|
||||
int id;
|
||||
http_sync_server& self;
|
||||
socket_type sock;
|
||||
boost::asio::io_service::work work;
|
||||
|
||||
lambda(int id_, http_sync_server& self_,
|
||||
socket_type&& sock_)
|
||||
: id(id_)
|
||||
, self(self_)
|
||||
, sock(std::move(sock_))
|
||||
, work(sock.get_io_service())
|
||||
{
|
||||
}
|
||||
|
||||
void operator()()
|
||||
{
|
||||
self.do_peer(id, std::move(sock));
|
||||
}
|
||||
};
|
||||
|
||||
void
|
||||
on_accept(error_code ec)
|
||||
{
|
||||
if(! acceptor_.is_open())
|
||||
return;
|
||||
if(ec)
|
||||
return fail(ec, "accept");
|
||||
static int id_ = 0;
|
||||
std::thread{lambda{++id_, *this, std::move(sock_)}}.detach();
|
||||
acceptor_.async_accept(sock_,
|
||||
std::bind(&http_sync_server::on_accept, this,
|
||||
std::placeholders::_1));
|
||||
}
|
||||
|
||||
void
|
||||
do_peer(int id, socket_type&& sock0)
|
||||
{
|
||||
socket_type sock(std::move(sock0));
|
||||
multi_buffer b;
|
||||
error_code ec;
|
||||
for(;;)
|
||||
{
|
||||
req_type req;
|
||||
http::read(sock, b, req, ec);
|
||||
if(ec)
|
||||
break;
|
||||
auto path = req.target().to_string();
|
||||
if(path == "/")
|
||||
path = "/index.html";
|
||||
path = root_ + path;
|
||||
if(! boost::filesystem::exists(path))
|
||||
{
|
||||
response<string_body> res;
|
||||
res.result(status::not_found);
|
||||
res.version = req.version;
|
||||
res.insert(field::server, "http_sync_server");
|
||||
res.insert(field::content_type, "text/html");
|
||||
res.body = "The file '" + path + "' was not found";
|
||||
res.prepare();
|
||||
write(sock, res, ec);
|
||||
if(ec)
|
||||
break;
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
resp_type res;
|
||||
res.result(status::ok);
|
||||
res.reason("OK");
|
||||
res.version = req.version;
|
||||
res.insert(field::server, "http_sync_server");
|
||||
res.insert(field::content_type, mime_type(path));
|
||||
res.body = path;
|
||||
res.prepare();
|
||||
write(sock, res, ec);
|
||||
if(ec)
|
||||
break;
|
||||
}
|
||||
catch(std::exception const& e)
|
||||
{
|
||||
response<string_body> res;
|
||||
res.result(status::internal_server_error);
|
||||
res.reason("Internal Error");
|
||||
res.version = req.version;
|
||||
res.insert(field::server, "http_sync_server");
|
||||
res.insert(field::content_type, "text/html");
|
||||
res.body =
|
||||
std::string{"An internal error occurred: "} + e.what();
|
||||
res.prepare();
|
||||
write(sock, res, ec);
|
||||
if(ec)
|
||||
break;
|
||||
}
|
||||
}
|
||||
fail(id, ec);
|
||||
}
|
||||
};
|
||||
|
||||
} // http
|
||||
} // beast
|
||||
|
||||
#endif
|
||||
@@ -1,61 +0,0 @@
|
||||
//
|
||||
// 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)
|
||||
//
|
||||
|
||||
#include "http_async_server.hpp"
|
||||
#include "http_sync_server.hpp"
|
||||
|
||||
#include <beast/test/sig_wait.hpp>
|
||||
#include <boost/program_options.hpp>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
int main(int ac, char const* av[])
|
||||
{
|
||||
using namespace beast::http;
|
||||
namespace po = boost::program_options;
|
||||
po::options_description desc("Options");
|
||||
|
||||
desc.add_options()
|
||||
("root,r", po::value<std::string>()->default_value("."),
|
||||
"Set the root directory for serving files")
|
||||
("port,p", po::value<std::uint16_t>()->default_value(8080),
|
||||
"Set the port number for the server")
|
||||
("ip", po::value<std::string>()->default_value("0.0.0.0"),
|
||||
"Set the IP address to bind to, \"0.0.0.0\" for all")
|
||||
("threads,n", po::value<std::size_t>()->default_value(4),
|
||||
"Set the number of threads to use")
|
||||
("sync,s", "Launch a synchronous server")
|
||||
;
|
||||
po::variables_map vm;
|
||||
po::store(po::parse_command_line(ac, av, desc), vm);
|
||||
|
||||
std::string root = vm["root"].as<std::string>();
|
||||
|
||||
std::uint16_t port = vm["port"].as<std::uint16_t>();
|
||||
|
||||
std::string ip = vm["ip"].as<std::string>();
|
||||
|
||||
std::size_t threads = vm["threads"].as<std::size_t>();
|
||||
|
||||
bool sync = vm.count("sync") > 0;
|
||||
|
||||
using endpoint_type = boost::asio::ip::tcp::endpoint;
|
||||
using address_type = boost::asio::ip::address;
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
//
|
||||
// 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_HTTP_MIME_TYPE_H_INCLUDED
|
||||
#define BEAST_EXAMPLE_HTTP_MIME_TYPE_H_INCLUDED
|
||||
|
||||
#include <beast/core/string.hpp>
|
||||
#include <boost/filesystem/path.hpp>
|
||||
#include <string>
|
||||
|
||||
namespace beast {
|
||||
namespace http {
|
||||
|
||||
// Return the Mime-Type for a given file extension
|
||||
template<class = void>
|
||||
string_view
|
||||
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
|
||||
Reference in new issue
Block a user