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:
@@ -2,6 +2,7 @@ Version 60:
|
||||
|
||||
* String comparisons are public interfaces
|
||||
* Fix response message type in async websocket accept
|
||||
* New server-framework, full featured server example
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
|
@@ -174,11 +174,11 @@ file(GLOB_RECURSE EXTRAS_INCLUDES
|
||||
${PROJECT_SOURCE_DIR}/extras/beast/*.ipp
|
||||
)
|
||||
|
||||
file(GLOB_RECURSE SERVER_INCLUDES
|
||||
${PROJECT_SOURCE_DIR}/example/server-framework/*.hpp
|
||||
)
|
||||
|
||||
add_subdirectory (test)
|
||||
add_subdirectory (test/core)
|
||||
add_subdirectory (test/http)
|
||||
add_subdirectory (test/websocket)
|
||||
add_subdirectory (test/zlib)
|
||||
|
||||
add_subdirectory (example)
|
||||
|
||||
|
@@ -79,7 +79,7 @@
|
||||
[import ../example/doc/http_examples.hpp]
|
||||
[import ../example/echo-op/echo_op.cpp]
|
||||
[import ../example/http-client/http_client.cpp]
|
||||
[import ../example/http-server/file_body.hpp]
|
||||
[import ../example/server-framework/file_body.hpp]
|
||||
[import ../example/websocket-client/websocket_client.cpp]
|
||||
|
||||
[import ../test/core/doc_snippets.cpp]
|
||||
|
@@ -42,9 +42,11 @@ standardized implementation of these protocols.
|
||||
|
||||
[heading Requirements]
|
||||
|
||||
[important
|
||||
This library is for programmers familiar with __Asio__. Users who
|
||||
wish to use asynchronous interfaces should already know how to
|
||||
create concurrent network programs using callbacks or coroutines.
|
||||
]
|
||||
|
||||
Beast requires:
|
||||
|
||||
|
@@ -5,7 +5,8 @@
|
||||
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
]
|
||||
|
||||
[section:example Example Programs]
|
||||
[section:example Examples]
|
||||
[block'''<?dbhtml stop-chunking?>''']
|
||||
|
||||
These complete programs are intended to quickly impress upon readers the
|
||||
flavor of the library. Source code and build scripts for them are located
|
||||
@@ -13,7 +14,7 @@ in the examples directory.
|
||||
|
||||
|
||||
|
||||
[heading HTTP GET]
|
||||
[section HTTP Client]
|
||||
|
||||
Use HTTP to make a GET request to a website and print the response:
|
||||
|
||||
@@ -21,9 +22,33 @@ File: [repo_file example/http-client/http_client.cpp]
|
||||
|
||||
[example_http_client]
|
||||
|
||||
[endsect]
|
||||
|
||||
|
||||
[heading WebSocket]
|
||||
|
||||
[section HTTP Client (with SSL)]
|
||||
|
||||
This example demonstrates sending and receiving HTTP messages
|
||||
over a TLS connection. Requires OpenSSL to build.
|
||||
|
||||
* [repo_file example/ssl/http_ssl_example.cpp]
|
||||
|
||||
[endsect]
|
||||
|
||||
|
||||
|
||||
[section HTTP Crawl]
|
||||
|
||||
This example retrieves the page at each of the most popular domains
|
||||
as measured by Alexa.
|
||||
|
||||
* [repo_file example/http-crawl/http_crawl.cpp]
|
||||
|
||||
[endsect]
|
||||
|
||||
|
||||
|
||||
[section WebSocket Client]
|
||||
|
||||
Establish a WebSocket connection, send a message and receive the reply:
|
||||
|
||||
@@ -31,60 +56,47 @@ File: [repo_file example/websocket-client/websocket_client.cpp]
|
||||
|
||||
[example_websocket_client]
|
||||
|
||||
|
||||
|
||||
[heading WebSocket Echo Server]
|
||||
|
||||
This example demonstrates both synchronous and asynchronous
|
||||
WebSocket server implementations.
|
||||
|
||||
* [repo_file example/websocket-server/main.cpp]
|
||||
* [repo_file example/websocket-server/websocket_async_echo_server.hpp]
|
||||
* [repo_file example/websocket-server/websocket_sync_echo_server.hpp]
|
||||
[endsect]
|
||||
|
||||
|
||||
|
||||
[heading Secure WebSocket]
|
||||
[section WebSocket Client (with SSL)]
|
||||
|
||||
Establish a WebSocket connection over an encrypted TLS connection,
|
||||
send a message and receive the reply. Requires OpenSSL to build.
|
||||
|
||||
* [repo_file example/ssl/websocket_ssl_example.cpp]
|
||||
|
||||
|
||||
|
||||
[heading HTTPS GET]
|
||||
|
||||
This example demonstrates sending and receiving HTTP messages
|
||||
over a TLS connection. Requires OpenSSL to build.
|
||||
|
||||
* [repo_file example/ssl/http_ssl_example.cpp]
|
||||
[endsect]
|
||||
|
||||
|
||||
|
||||
[heading HTTP Crawl]
|
||||
[section Server Framework]
|
||||
|
||||
This example retrieves the page at each of the most popular domains
|
||||
as measured by Alexa.
|
||||
This is a complete program and framework of classes implementing
|
||||
a general purpose server that users may copy to use as the basis
|
||||
for writing their own servers. It serves both HTTP and WebSocket.
|
||||
|
||||
* [repo_file example/http-crawl/http_crawl.cpp]
|
||||
* [repo_file example/server-framework/file_body.hpp]
|
||||
* [repo_file example/server-framework/file_service.hpp]
|
||||
* [repo_file example/server-framework/framework.hpp]
|
||||
* [repo_file example/server-framework/http_async_port.hpp]
|
||||
* [repo_file example/server-framework/http_base.hpp]
|
||||
* [repo_file example/server-framework/http_sync_port.hpp]
|
||||
* [repo_file example/server-framework/main.cpp]
|
||||
* [repo_file example/server-framework/rfc7231.hpp]
|
||||
* [repo_file example/server-framework/server.hpp]
|
||||
* [repo_file example/server-framework/service_list.hpp]
|
||||
* [repo_file example/server-framework/write_msg.hpp]
|
||||
* [repo_file example/server-framework/ws_async_port.hpp]
|
||||
* [repo_file example/server-framework/ws_sync_port.hpp]
|
||||
* [repo_file example/server-framework/ws_upgrade_service.hpp]
|
||||
|
||||
[endsect]
|
||||
|
||||
|
||||
|
||||
[heading HTTP Server]
|
||||
|
||||
This example demonstrates both synchronous and asynchronous server
|
||||
implementations. It also provides an example of implementing a [*Body]
|
||||
type, in `file_body`.
|
||||
|
||||
* [repo_file example/http-server/file_body.hpp]
|
||||
* [repo_file example/http-server/http_async_server.hpp]
|
||||
* [repo_file example/http-server/http_sync_server.hpp]
|
||||
* [repo_file example/http-server/main.cpp]
|
||||
|
||||
|
||||
|
||||
[heading Composed Operations]
|
||||
[section Composed Operations]
|
||||
|
||||
This program shows how to use Beast's network foundations to build a
|
||||
composable asynchronous initiation function with associated composed
|
||||
@@ -93,9 +105,11 @@ the example described in the Core Foundations document section.
|
||||
|
||||
* [repo_file example/echo-op/echo_op.cpp]
|
||||
|
||||
[endsect]
|
||||
|
||||
|
||||
[heading Documentation Samples]
|
||||
|
||||
[section Documentation Samples]
|
||||
|
||||
Here are all of the example functions and classes presented
|
||||
throughout the documentation, they can be included and used
|
||||
@@ -105,6 +119,8 @@ in your program without modification
|
||||
|
||||
* [repo_file example/doc/http_examples.hpp]
|
||||
|
||||
[endsect]
|
||||
|
||||
|
||||
|
||||
[endsect]
|
||||
|
@@ -15,6 +15,13 @@
|
||||
left to the interfaces already existing on the underlying streams.
|
||||
]
|
||||
|
||||
Library stream algorithms require a __socket__, __ssl_stream__, or other
|
||||
__Stream__ object that has already established communication with an
|
||||
endpoint. This example is provided as a reminder of how to work with
|
||||
sockets:
|
||||
|
||||
[snippet_core_2]
|
||||
|
||||
Throughout this documentation identifiers with the following names have
|
||||
special meaning:
|
||||
|
||||
@@ -52,11 +59,4 @@ special meaning:
|
||||
]]
|
||||
]
|
||||
|
||||
Library stream algorithms require a __socket__, __ssl_stream__, or other
|
||||
__Stream__ object that has already established communication with an
|
||||
endpoint. This example is provided as a reminder of how to work with
|
||||
sockets:
|
||||
|
||||
[snippet_core_2]
|
||||
|
||||
[endsect]
|
||||
|
@@ -85,7 +85,7 @@ format using __Asio__. Specifically, the library provides:
|
||||
[include 5_05_parser_streams.qbk]
|
||||
[include 5_06_serializer_buffers.qbk]
|
||||
[include 5_07_parser_buffers.qbk]
|
||||
[include 5_08_custom_parsers.qbk]
|
||||
[include 5_09_custom_body.qbk]
|
||||
[include 5_08_custom_body.qbk]
|
||||
[include 5_09_custom_parsers.qbk]
|
||||
|
||||
[endsect]
|
||||
|
BIN
doc/images/server.png
Normal file
BIN
doc/images/server.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
@@ -3,6 +3,5 @@
|
||||
add_subdirectory (echo-op)
|
||||
add_subdirectory (http-client)
|
||||
add_subdirectory (http-crawl)
|
||||
add_subdirectory (http-server)
|
||||
add_subdirectory (server-framework)
|
||||
add_subdirectory (websocket-client)
|
||||
add_subdirectory (websocket-server)
|
||||
|
@@ -8,6 +8,5 @@
|
||||
build-project echo-op ;
|
||||
build-project http-client ;
|
||||
build-project http-crawl ;
|
||||
build-project http-server ;
|
||||
build-project server-framework ;
|
||||
build-project websocket-client ;
|
||||
build-project websocket-server ;
|
||||
|
@@ -5,8 +5,8 @@
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#ifndef URLS_LARGE_DATA_H_INCLUDED
|
||||
#define URLS_LARGE_DATA_H_INCLUDED
|
||||
#ifndef BEAST_EXAMPLE_HTTP_CRAWL_URLS_LARGE_DATA_HPP
|
||||
#define BEAST_EXAMPLE_HTTP_CRAWL_URLS_LARGE_DATA_HPP
|
||||
|
||||
#include <vector>
|
||||
|
||||
|
@@ -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,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
|
17
example/server-framework/CMakeLists.txt
Normal file
17
example/server-framework/CMakeLists.txt
Normal file
@@ -0,0 +1,17 @@
|
||||
# Part of Beast
|
||||
|
||||
GroupSources(include/beast beast)
|
||||
|
||||
GroupSources(example/server-framework "/")
|
||||
|
||||
add_executable (server-framework
|
||||
${BEAST_INCLUDES}
|
||||
${SERVER_INCLUDES}
|
||||
main.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(
|
||||
server-framework
|
||||
Beast
|
||||
${Boost_PROGRAM_OPTIONS_LIBRARY}
|
||||
${Boost_FILESYSTEM_LIBRARY})
|
@@ -5,6 +5,6 @@
|
||||
# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
#
|
||||
|
||||
exe http-server :
|
||||
exe server-framework :
|
||||
main.cpp
|
||||
;
|
68
example/server-framework/README.md
Normal file
68
example/server-framework/README.md
Normal file
@@ -0,0 +1,68 @@
|
||||
<img width="880" height = "80" alt = "Beast"
|
||||
src="https://raw.githubusercontent.com/vinniefalco/Beast/master/doc/images/readme.png">
|
||||
|
||||
# HTTP and WebSocket built on Boost.Asio in C++11
|
||||
|
||||
## Server-Framework
|
||||
|
||||
This example is a complete, multi-threaded server built with Beast.
|
||||
It contains the following components
|
||||
|
||||
* WebSocket ports (synchronous and asynchronous)
|
||||
- Echoes back any message received
|
||||
|
||||
* HTTP ports (synchronous and asynchronous)
|
||||
- Serves files from a configurable directory on GET request
|
||||
- Responds to HEAD requests with the appropriate result
|
||||
- Routes WebSocket Upgrade requests to a WebSocket port
|
||||
- Handles Expect: 100-continue
|
||||
- Supports pipelined requests
|
||||
|
||||
The server is designed to use modular components that users may simply copy
|
||||
into their own project to get started quickly. Two concepts are introduced:
|
||||
|
||||
## PortHandler
|
||||
|
||||
The **PortHandler** concept defines an algorithm for handling incoming
|
||||
connections received on a listening socket. The example comes with four
|
||||
port handlers:
|
||||
|
||||
* `http_sync_port` Serves HTTP content using synchronous Beast calls
|
||||
|
||||
* `http_async_port` Serves HTTP content using asynchronous Beast calls
|
||||
|
||||
* `ws_sync_port` A synchronous WebSocket echo server using synchronous Beast calls
|
||||
|
||||
* `ws_async_port` An asynchronous WebSocket echo server using synchronous Beast calls
|
||||
|
||||
A port handler takes the stream object resulting form an incoming connection
|
||||
request and constructs a handler-specific connection object which provides
|
||||
the desired behavior.
|
||||
|
||||
## Service
|
||||
|
||||
The HTTP ports which come with the example have a system built in which allows
|
||||
installation of framework and user-defined "HTTP services". These services
|
||||
inform connections using the port on how to handle specific requests. This is
|
||||
similar in concept to an "HTTP router" which is an element of most modern
|
||||
servers.
|
||||
|
||||
These HTTP services are represented by the **Service** concept, and managed
|
||||
in a container holding a type-list, called a `service_list`. Each HTTP port
|
||||
allows the sevice list to be defined at compile-time and initialized at run
|
||||
time. The framework provides these services:
|
||||
|
||||
* `file_service` Produces HTTP responses delivering files from a system path
|
||||
|
||||
* `ws_upgrade_service` Transports a connection requesting a WebSocket Upgrade
|
||||
to a websocket port handler.
|
||||
|
||||
## Relationship
|
||||
|
||||
This diagram shows the relationship of the server object, to the four
|
||||
ports created in the example program, and the HTTP services contained by
|
||||
the HTTP ports:
|
||||
|
||||
<img width="880" height = "344" alt = "ServerFramework"
|
||||
src="https://raw.githubusercontent.com/vinniefalco/Beast/server/doc/images/server.png">
|
||||
|
@@ -5,8 +5,8 @@
|
||||
// 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
|
||||
#ifndef BEAST_EXAMPLE_HTTP_SERVER_FILE_BODY_HPP
|
||||
#define BEAST_EXAMPLE_HTTP_SERVER_FILE_BODY_HPP
|
||||
|
||||
#include <beast/core/error.hpp>
|
||||
#include <beast/http/message.hpp>
|
||||
@@ -18,11 +18,18 @@
|
||||
#include <cstdint>
|
||||
#include <utility>
|
||||
|
||||
namespace beast {
|
||||
namespace http {
|
||||
|
||||
//[example_http_file_body_1
|
||||
|
||||
/** A message body represented by a file on the filesystem.
|
||||
|
||||
Messages with this type have bodies represented by a
|
||||
file on the file system. When parsing a message using
|
||||
this body type, the data is stored in the file pointed
|
||||
to by the path, which must be writable. When serializing,
|
||||
the implementation will read the file and present those
|
||||
octets as the body content. This may be used to serve
|
||||
content from a directory as part of a web service.
|
||||
*/
|
||||
struct file_body
|
||||
{
|
||||
/** The type of the @ref message::body member.
|
||||
@@ -71,6 +78,7 @@ struct file_body
|
||||
|
||||
//[example_http_file_body_2
|
||||
|
||||
inline
|
||||
std::uint64_t
|
||||
file_body::
|
||||
size(value_type const& v)
|
||||
@@ -111,7 +119,7 @@ public:
|
||||
// always have the `file_body` as the body type.
|
||||
//
|
||||
template<bool isRequest, class Fields>
|
||||
reader(message<isRequest, file_body, Fields> const& m);
|
||||
reader(beast::http::message<isRequest, file_body, Fields> const& m);
|
||||
|
||||
// Destructor
|
||||
~reader();
|
||||
@@ -120,7 +128,7 @@ public:
|
||||
// of the body is started.
|
||||
//
|
||||
void
|
||||
init(error_code& ec);
|
||||
init(beast::error_code& ec);
|
||||
|
||||
// This function is called zero or more times to
|
||||
// retrieve buffers. A return value of `boost::none`
|
||||
@@ -129,7 +137,7 @@ public:
|
||||
// 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);
|
||||
get(beast::error_code& ec);
|
||||
|
||||
// This function is called when reading is complete.
|
||||
// It is an opportunity to perform any final actions
|
||||
@@ -138,7 +146,7 @@ public:
|
||||
// destructors, since an exception thrown from there
|
||||
// would terminate the program.
|
||||
void
|
||||
finish(error_code& ec);
|
||||
finish(beast::error_code& ec);
|
||||
};
|
||||
|
||||
//]
|
||||
@@ -151,7 +159,7 @@ public:
|
||||
//
|
||||
template<bool isRequest, class Fields>
|
||||
file_body::reader::
|
||||
reader(message<isRequest, file_body, Fields> const& m)
|
||||
reader(beast::http::message<isRequest, file_body, Fields> const& m)
|
||||
: path_(m.body)
|
||||
{
|
||||
}
|
||||
@@ -164,7 +172,7 @@ reader(message<isRequest, file_body, Fields> const& m)
|
||||
inline
|
||||
void
|
||||
file_body::reader::
|
||||
init(error_code& ec)
|
||||
init(beast::error_code& ec)
|
||||
{
|
||||
// Attempt to open the file for reading
|
||||
file_ = fopen(path_.string().c_str(), "rb");
|
||||
@@ -173,7 +181,7 @@ init(error_code& ec)
|
||||
{
|
||||
// Convert the old-school `errno` into
|
||||
// an error code using the system category.
|
||||
ec = error_code{errno, system_category()};
|
||||
ec = beast::error_code{errno, beast::system_category()};
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -191,7 +199,7 @@ init(error_code& ec)
|
||||
inline
|
||||
auto
|
||||
file_body::reader::
|
||||
get(error_code& ec) ->
|
||||
get(beast::error_code& ec) ->
|
||||
boost::optional<std::pair<const_buffers_type, bool>>
|
||||
{
|
||||
// Calculate the smaller of our buffer size,
|
||||
@@ -212,7 +220,7 @@ get(error_code& ec) ->
|
||||
if(ferror(file_))
|
||||
{
|
||||
// Convert old-school `errno` to error_code
|
||||
ec = error_code(errno, system_category());
|
||||
ec = beast::error_code(errno, beast::system_category());
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
@@ -242,7 +250,7 @@ get(error_code& ec) ->
|
||||
inline
|
||||
void
|
||||
file_body::reader::
|
||||
finish(error_code& ec)
|
||||
finish(beast::error_code& ec)
|
||||
{
|
||||
// Functions which pass back errors are
|
||||
// responsible for clearing the error code.
|
||||
@@ -281,20 +289,20 @@ public:
|
||||
//
|
||||
template<bool isRequest, class Fields>
|
||||
explicit
|
||||
writer(message<isRequest, file_body, Fields>& m);
|
||||
writer(beast::http::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);
|
||||
init(boost::optional<std::uint64_t> const& content_length, beast::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);
|
||||
put(ConstBufferSequence const& buffers, beast::error_code& ec);
|
||||
|
||||
// This function is called when writing is complete.
|
||||
// It is an opportunity to perform any final actions
|
||||
@@ -304,7 +312,7 @@ public:
|
||||
// would terminate the program.
|
||||
//
|
||||
void
|
||||
finish(error_code& ec);
|
||||
finish(beast::error_code& ec);
|
||||
|
||||
// Destructor.
|
||||
//
|
||||
@@ -320,7 +328,7 @@ public:
|
||||
// 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)
|
||||
writer(beast::http::message<isRequest, file_body, Fields>& m)
|
||||
: path_(m.body)
|
||||
{
|
||||
}
|
||||
@@ -334,7 +342,7 @@ writer(message<isRequest, file_body, Fields>& m)
|
||||
inline
|
||||
void
|
||||
file_body::writer::
|
||||
init(boost::optional<std::uint64_t> const& content_length, error_code& ec)
|
||||
init(boost::optional<std::uint64_t> const& content_length, beast::error_code& ec)
|
||||
{
|
||||
// Attempt to open the file for writing
|
||||
file_ = fopen(path_.string().c_str(), "wb");
|
||||
@@ -343,7 +351,7 @@ init(boost::optional<std::uint64_t> const& content_length, error_code& ec)
|
||||
{
|
||||
// Convert the old-school `errno` into
|
||||
// an error code using the system category.
|
||||
ec = error_code{errno, system_category()};
|
||||
ec = beast::error_code{errno, beast::system_category()};
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -356,7 +364,7 @@ init(boost::optional<std::uint64_t> const& content_length, error_code& ec)
|
||||
template<class ConstBufferSequence>
|
||||
void
|
||||
file_body::writer::
|
||||
put(ConstBufferSequence const& buffers, error_code& ec)
|
||||
put(ConstBufferSequence const& buffers, beast::error_code& ec)
|
||||
{
|
||||
// Loop over all the buffers in the sequence,
|
||||
// and write each one to the file.
|
||||
@@ -372,7 +380,7 @@ put(ConstBufferSequence const& buffers, error_code& ec)
|
||||
if(ferror(file_))
|
||||
{
|
||||
// Convert old-school `errno` to error_code
|
||||
ec = error_code(errno, system_category());
|
||||
ec = beast::error_code(errno, beast::system_category());
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -385,8 +393,10 @@ put(ConstBufferSequence const& buffers, error_code& ec)
|
||||
inline
|
||||
void
|
||||
file_body::writer::
|
||||
finish(error_code& ec)
|
||||
finish(beast::error_code& ec)
|
||||
{
|
||||
// This has to be cleared before returning, to
|
||||
// indicate no error. The specification requires it.
|
||||
ec = {};
|
||||
}
|
||||
|
||||
@@ -405,7 +415,4 @@ file_body::writer::
|
||||
|
||||
//]
|
||||
|
||||
} // http
|
||||
} // beast
|
||||
|
||||
#endif
|
248
example/server-framework/file_service.hpp
Normal file
248
example/server-framework/file_service.hpp
Normal file
@@ -0,0 +1,248 @@
|
||||
//
|
||||
// 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_FILE_SERVICE_HPP
|
||||
#define BEAST_EXAMPLE_SERVER_FILE_SERVICE_HPP
|
||||
|
||||
#include "file_body.hpp"
|
||||
#include "framework.hpp"
|
||||
|
||||
#include <beast/core/string.hpp>
|
||||
#include <beast/http/empty_body.hpp>
|
||||
#include <beast/http/message.hpp>
|
||||
#include <beast/http/string_body.hpp>
|
||||
#include <boost/filesystem/path.hpp>
|
||||
#include <string>
|
||||
|
||||
namespace framework {
|
||||
|
||||
/** An HTTP service which delivers files from a root directory.
|
||||
|
||||
This service will accept GET and HEAD requests for files,
|
||||
and deliver them as responses. The service constructs with
|
||||
the location on the file system to act as the root for the
|
||||
tree of files to serve.
|
||||
|
||||
Meets the requirements of @b Service
|
||||
*/
|
||||
class file_service
|
||||
{
|
||||
// The path to serve files from
|
||||
boost::filesystem::path root_;
|
||||
|
||||
// The name to use in the Server HTTP field
|
||||
std::string server_;
|
||||
|
||||
public:
|
||||
/** Constructor
|
||||
|
||||
@param root A path with files to serve. A GET request
|
||||
for "/" will try to deliver the file "/index.html".
|
||||
|
||||
@param The string to use in the Server HTTP field.
|
||||
*/
|
||||
explicit
|
||||
file_service(
|
||||
boost::filesystem::path const& root,
|
||||
beast::string_view server)
|
||||
: root_(root)
|
||||
, server_(server)
|
||||
{
|
||||
}
|
||||
|
||||
/** Initialize the service.
|
||||
|
||||
This provides an opportunity for the service to perform
|
||||
initialization which may fail, while reporting an error
|
||||
code instead of throwing an exception from the constructor.
|
||||
|
||||
@note This is needed for to meet the requirements for @b Service
|
||||
*/
|
||||
void
|
||||
init(error_code& ec)
|
||||
{
|
||||
// This is required by the error_code specification
|
||||
//
|
||||
ec = {};
|
||||
}
|
||||
|
||||
/** Process a request.
|
||||
|
||||
|
||||
@note This is needed for to meet the requirements for @b Service
|
||||
*/
|
||||
template<
|
||||
class Stream,
|
||||
class Body, class Fields,
|
||||
class Send>
|
||||
bool
|
||||
respond(
|
||||
Stream&&,
|
||||
endpoint_type const& ep,
|
||||
beast::http::request<Body, Fields>&& req,
|
||||
Send const& send) const
|
||||
{
|
||||
// Check the method and take action
|
||||
switch(req.method())
|
||||
{
|
||||
case beast::http::verb::get:
|
||||
{
|
||||
// For GET requests we deliver the actual file
|
||||
boost::filesystem::path rel_path(req.target().to_string());
|
||||
|
||||
// Give them the root web page if the target is "/"
|
||||
if(rel_path == "/")
|
||||
rel_path = "/index.html";
|
||||
|
||||
// Calculate full path from root
|
||||
boost::filesystem::path full_path = root_ / rel_path;
|
||||
|
||||
// Make sure the file is there
|
||||
if(boost::filesystem::exists(full_path))
|
||||
{
|
||||
// Send the file
|
||||
send(get(req, full_path));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Send a Not Found result
|
||||
send(not_found(req, rel_path));
|
||||
}
|
||||
|
||||
// Indicate that we handled the request
|
||||
return true;
|
||||
}
|
||||
|
||||
case beast::http::verb::head:
|
||||
{
|
||||
// We are just going to give them the headers they
|
||||
// would otherwise get, but without the body.
|
||||
boost::filesystem::path rel_path(req.target().to_string());
|
||||
if(rel_path == "/")
|
||||
rel_path = "/index.html";
|
||||
|
||||
// Calculate full path from root
|
||||
boost::filesystem::path full_path = root_ / rel_path;
|
||||
|
||||
// Make sure the file is there
|
||||
if(! boost::filesystem::exists(full_path))
|
||||
{
|
||||
// Send a HEAD response
|
||||
send(head(req, full_path));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Send a Not Found result
|
||||
send(not_found(req, rel_path));
|
||||
}
|
||||
|
||||
// Indicate that we handled the request
|
||||
return true;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// We didn't handle this request, so return false to
|
||||
// inform the service list to try the next service.
|
||||
//
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
// Return a reasonable mime type based on the extension of a file.
|
||||
//
|
||||
beast::string_view
|
||||
mime_type(boost::filesystem::path const& path) const
|
||||
{
|
||||
using beast::iequals;
|
||||
auto const ext = path.extension().string();
|
||||
if(iequals(ext, ".txt")) return "text/plain";
|
||||
if(iequals(ext, ".htm")) return "text/html";
|
||||
if(iequals(ext, ".html")) return "text/html";
|
||||
if(iequals(ext, ".php")) return "text/html";
|
||||
if(iequals(ext, ".css")) return "text/css";
|
||||
if(iequals(ext, ".js")) return "application/javascript";
|
||||
if(iequals(ext, ".json")) return "application/json";
|
||||
if(iequals(ext, ".xml")) return "application/xml";
|
||||
if(iequals(ext, ".swf")) return "application/x-shockwave-flash";
|
||||
if(iequals(ext, ".flv")) return "video/x-flv";
|
||||
if(iequals(ext, ".png")) return "image/png";
|
||||
if(iequals(ext, ".jpe")) return "image/jpeg";
|
||||
if(iequals(ext, ".jpeg")) return "image/jpeg";
|
||||
if(iequals(ext, ".jpg")) return "image/jpeg";
|
||||
if(iequals(ext, ".gif")) return "image/gif";
|
||||
if(iequals(ext, ".bmp")) return "image/bmp";
|
||||
if(iequals(ext, ".ico")) return "image/vnd.microsoft.icon";
|
||||
if(iequals(ext, ".tiff")) return "image/tiff";
|
||||
if(iequals(ext, ".tif")) return "image/tiff";
|
||||
if(iequals(ext, ".svg")) return "image/svg+xml";
|
||||
if(iequals(ext, ".svgz")) return "image/svg+xml";
|
||||
return "application/text";
|
||||
}
|
||||
|
||||
// Return an HTTP Not Found response
|
||||
//
|
||||
template<class Body, class Fields>
|
||||
beast::http::response<beast::http::string_body>
|
||||
not_found(beast::http::request<Body, Fields> const& req,
|
||||
boost::filesystem::path const& rel_path) const
|
||||
{
|
||||
beast::http::response<beast::http::string_body> res;
|
||||
res.version = req.version;
|
||||
res.result(beast::http::status::not_found);
|
||||
res.set(beast::http::field::server, server_);
|
||||
res.set(beast::http::field::content_type, "text/html");
|
||||
res.body = "The file was not found"; // VFALCO append rel_path
|
||||
res.prepare();
|
||||
return res;
|
||||
}
|
||||
|
||||
// Return a file response to an HTTP GET request
|
||||
//
|
||||
template<class Body, class Fields>
|
||||
beast::http::response<file_body>
|
||||
get(beast::http::request<Body, Fields> const& req,
|
||||
boost::filesystem::path const& full_path) const
|
||||
{
|
||||
beast::http::response<file_body> res;
|
||||
res.version = req.version;
|
||||
res.result(beast::http::status::ok);
|
||||
res.set(beast::http::field::server, server_);
|
||||
res.set(beast::http::field::content_type, mime_type(full_path));
|
||||
res.body = full_path;
|
||||
res.prepare();
|
||||
return res;
|
||||
}
|
||||
|
||||
// Return a response to an HTTP HEAD request
|
||||
//
|
||||
template<class Body, class Fields>
|
||||
beast::http::response<beast::http::empty_body>
|
||||
head(beast::http::request<Body, Fields> const& req,
|
||||
boost::filesystem::path const& full_path) const
|
||||
{
|
||||
beast::http::response<beast::http::empty_body> res;
|
||||
res.version = req.version;
|
||||
res.result(beast::http::status::ok);
|
||||
res.set(beast::http::field::server, server_);
|
||||
res.set(beast::http::field::content_type, mime_type(full_path));
|
||||
|
||||
// Set the Content-Length field manually. We don't have a body,
|
||||
// but this is a response to a HEAD request so we include the
|
||||
// content length anyway.
|
||||
//
|
||||
res.set(beast::http::field::content_length, file_body::size(full_path));
|
||||
|
||||
return res;
|
||||
}
|
||||
};
|
||||
|
||||
} // framework
|
||||
|
||||
#endif
|
53
example/server-framework/framework.hpp
Normal file
53
example/server-framework/framework.hpp
Normal file
@@ -0,0 +1,53 @@
|
||||
//
|
||||
// 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_FRAMEWORK_HPP
|
||||
#define BEAST_EXAMPLE_SERVER_FRAMEWORK_HPP
|
||||
|
||||
#include <boost/asio/io_service.hpp>
|
||||
#include <boost/asio/strand.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/system/error_code.hpp>
|
||||
#include <utility>
|
||||
|
||||
/** The framework namespace
|
||||
|
||||
This namespace contains all of the identifiers in the
|
||||
server-framework system. Here we import some commonly
|
||||
used types for brevity.
|
||||
*/
|
||||
namespace framework {
|
||||
|
||||
// This is our own base from member idiom written for C++11
|
||||
// which is simple and works around a glitch in boost's version.
|
||||
//
|
||||
template<class T>
|
||||
class base_from_member
|
||||
{
|
||||
public:
|
||||
template<class... Args>
|
||||
explicit
|
||||
base_from_member(Args&&... args)
|
||||
: member(std::forward<Args>(args)...)
|
||||
{
|
||||
}
|
||||
|
||||
protected:
|
||||
T member;
|
||||
};
|
||||
|
||||
using error_code = boost::system::error_code;
|
||||
using socket_type = boost::asio::ip::tcp::socket;
|
||||
using strand_type = boost::asio::io_service::strand;
|
||||
using address_type = boost::asio::ip::address_v4;
|
||||
using endpoint_type = boost::asio::ip::tcp::endpoint;
|
||||
using acceptor_type = boost::asio::ip::tcp::acceptor;
|
||||
using io_service_type = boost::asio::io_service;
|
||||
|
||||
} // framework
|
||||
|
||||
#endif
|
550
example/server-framework/http_async_port.hpp
Normal file
550
example/server-framework/http_async_port.hpp
Normal file
@@ -0,0 +1,550 @@
|
||||
//
|
||||
// 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_ASYNC_PORT_HPP
|
||||
#define BEAST_EXAMPLE_SERVER_HTTP_ASYNC_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/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>
|
||||
|
||||
namespace framework {
|
||||
|
||||
// Base class for a type-erased, queued asynchronous HTTP write
|
||||
//
|
||||
struct queued_http_write
|
||||
{
|
||||
virtual ~queued_http_write() = default;
|
||||
|
||||
// When invoked, performs the write operation.
|
||||
virtual void invoke() = 0;
|
||||
};
|
||||
|
||||
/* This implements an object which, when invoked, writes an HTTP
|
||||
message asynchronously to the stream. These objects are used
|
||||
to form a queue of outgoing messages for pipelining. The base
|
||||
class type-erases the message so the queue can hold messsages
|
||||
of different types.
|
||||
*/
|
||||
template<
|
||||
class Stream,
|
||||
bool isRequest, class Body, class Fields,
|
||||
class Handler>
|
||||
class queued_http_write_impl : public queued_http_write
|
||||
{
|
||||
Stream& stream_;
|
||||
beast::http::message<isRequest, Body, Fields> msg_;
|
||||
Handler handler_;
|
||||
|
||||
public:
|
||||
// Constructor.
|
||||
//
|
||||
// Ownership of the message is transferred into the object
|
||||
//
|
||||
template<class DeducedHandler>
|
||||
queued_http_write_impl(
|
||||
Stream& stream,
|
||||
beast::http::message<isRequest, Body, Fields>&& msg,
|
||||
DeducedHandler&& handler)
|
||||
: stream_(stream)
|
||||
, msg_(std::move(msg))
|
||||
, handler_(std::forward<DeducedHandler>(handler))
|
||||
{
|
||||
}
|
||||
|
||||
// Writes the stored message
|
||||
void
|
||||
invoke() override
|
||||
{
|
||||
async_write_msg(
|
||||
stream_,
|
||||
std::move(msg_),
|
||||
std::move(handler_));
|
||||
}
|
||||
};
|
||||
|
||||
// This helper function creates a queued_http_write
|
||||
// object and returns it inside a unique_ptr.
|
||||
//
|
||||
template<
|
||||
class Stream,
|
||||
bool isRequest, class Body, class Fields,
|
||||
class Handler>
|
||||
std::unique_ptr<queued_http_write>
|
||||
make_queued_http_write(
|
||||
Stream& stream,
|
||||
beast::http::message<isRequest, Body, Fields>&& msg,
|
||||
Handler&& handler)
|
||||
{
|
||||
return std::unique_ptr<queued_http_write>{
|
||||
new queued_http_write_impl<
|
||||
Stream,
|
||||
isRequest, Body, Fields,
|
||||
typename std::decay<Handler>::type>{
|
||||
stream,
|
||||
std::move(msg),
|
||||
std::forward<Handler>(handler)}};
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** An asynchronous HTTP connection.
|
||||
|
||||
This base class implements an HTTP connection object using
|
||||
asynchronous calls.
|
||||
|
||||
It uses the Curiously Recurring Template pattern (CRTP) where
|
||||
we refer to the derivd 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 async_http_con : 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_;
|
||||
|
||||
// The parser for reading the requests
|
||||
boost::optional<beast::http::request_parser<beast::http::dynamic_body>> parser_;
|
||||
|
||||
// This is the queue of outgoing messages
|
||||
std::vector<std::unique_ptr<queued_http_write>> queue_;
|
||||
|
||||
// Indicates if we have a write active.
|
||||
bool writing_ = false;
|
||||
|
||||
protected:
|
||||
// The strand makes sure that our data is
|
||||
// accessed from only one thread at a time.
|
||||
//
|
||||
strand_type strand_;
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
async_http_con(
|
||||
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)
|
||||
, strand_(impl().stream().get_io_service())
|
||||
{
|
||||
}
|
||||
|
||||
// Called by the port after creating the object
|
||||
void
|
||||
run()
|
||||
{
|
||||
// Start reading the header for the first request.
|
||||
//
|
||||
do_read_header();
|
||||
}
|
||||
|
||||
private:
|
||||
// Perform an asynchronous read for the next request header
|
||||
//
|
||||
void
|
||||
do_read_header()
|
||||
{
|
||||
// On each read the parser needs to be destroyed and
|
||||
// recreated. We store it in a boost::optional to
|
||||
// achieve that.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
parser_.emplace(1024 * 1024);
|
||||
|
||||
// Read just the header
|
||||
beast::http::async_read_header(
|
||||
impl().stream(),
|
||||
buffer_,
|
||||
*parser_,
|
||||
strand_.wrap(std::bind(
|
||||
&async_http_con::on_read_header,
|
||||
impl().shared_from_this(),
|
||||
std::placeholders::_1)));
|
||||
}
|
||||
|
||||
// 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"
|
||||
async_http_con& self_;
|
||||
|
||||
public:
|
||||
// capture "this"
|
||||
explicit
|
||||
send_lambda(async_http_con& self)
|
||||
: self_(self)
|
||||
{
|
||||
}
|
||||
|
||||
// sends a message
|
||||
template<class Body, class Fields>
|
||||
void
|
||||
operator()(beast::http::response<Body, Fields>&& res) const
|
||||
{
|
||||
self_.do_write(std::move(res));
|
||||
}
|
||||
};
|
||||
|
||||
// Called when the header has been read in
|
||||
void
|
||||
on_read_header(error_code ec)
|
||||
{
|
||||
// On failure we just return, the shared_ptr that is bound
|
||||
// into the completion will go out of scope and eventually
|
||||
// we will get destroyed.
|
||||
//
|
||||
if(ec)
|
||||
return fail("on_read", ec);
|
||||
|
||||
// The parser holds the request object,
|
||||
// at this point it only has the header in it.
|
||||
auto& req = parser_->get();
|
||||
|
||||
send_lambda send{*this};
|
||||
|
||||
// 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.
|
||||
//
|
||||
send(this->continue_100(req));
|
||||
}
|
||||
|
||||
// Read the rest of the message, if any.
|
||||
//
|
||||
beast::http::async_read(
|
||||
impl().stream(),
|
||||
buffer_,
|
||||
*parser_,
|
||||
strand_.wrap(std::bind(
|
||||
&async_http_con::on_read,
|
||||
impl().shared_from_this(),
|
||||
std::placeholders::_1)));
|
||||
}
|
||||
|
||||
// Called when the message is complete
|
||||
void
|
||||
on_read(error_code ec)
|
||||
{
|
||||
// Grab a reference to the request again
|
||||
auto& req = parser_->get();
|
||||
|
||||
// Create a variable for our send
|
||||
// lambda since we use it more than once.
|
||||
//
|
||||
send_lambda send{*this};
|
||||
|
||||
// Give each services a chance to handle the request
|
||||
//
|
||||
if(! services_.respond(
|
||||
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));
|
||||
}
|
||||
else
|
||||
{
|
||||
// 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 async_http_con object get destroyed.
|
||||
//
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// VFALCO Right now we do unlimited pipelining which
|
||||
// can lead to unbounded resource consumption.
|
||||
// A more sophisticated server might only issue
|
||||
// this read when the queue is below some limit.
|
||||
//
|
||||
|
||||
// Start reading another header
|
||||
do_read_header();
|
||||
}
|
||||
|
||||
// This function either queues a message or
|
||||
// starts writing it if no other writes are taking place.
|
||||
//
|
||||
template<class Body, class Fields>
|
||||
void
|
||||
do_write(beast::http::response<Body, Fields>&& res)
|
||||
{
|
||||
// See if a write is in progress
|
||||
if(! writing_)
|
||||
{
|
||||
// An assert or two to keep things sane when
|
||||
// writing asynchronous code can be very helpful.
|
||||
BOOST_ASSERT(queue_.empty());
|
||||
|
||||
// We're going to be writing so set the flag
|
||||
writing_ = true;
|
||||
|
||||
// And now perform the write
|
||||
return async_write_msg(
|
||||
impl().stream(),
|
||||
std::move(res),
|
||||
strand_.wrap(std::bind(
|
||||
&async_http_con::on_write,
|
||||
impl().shared_from_this(),
|
||||
std::placeholders::_1)));
|
||||
}
|
||||
|
||||
// Queue is not empty, so append this message to the queue.
|
||||
// It will be sent late when the queue empties.
|
||||
//
|
||||
queue_.emplace_back(make_queued_http_write(
|
||||
impl().stream(),
|
||||
std::move(res),
|
||||
strand_.wrap(std::bind(
|
||||
&async_http_con::on_write,
|
||||
impl().shared_from_this(),
|
||||
std::placeholders::_1))));
|
||||
}
|
||||
|
||||
// Called when a message finishes writing
|
||||
void
|
||||
on_write(error_code ec)
|
||||
{
|
||||
// Make sure our state is what we think it is
|
||||
BOOST_ASSERT(writing_);
|
||||
|
||||
// On failure just log and return
|
||||
if(ec)
|
||||
return fail("on_write", ec);
|
||||
|
||||
// See if the queue is empty
|
||||
if(queue_.empty())
|
||||
{
|
||||
// Queue was empty so clear the flag...
|
||||
writing_ = false;
|
||||
|
||||
// ...and return
|
||||
return;
|
||||
}
|
||||
|
||||
// Queue was not empty, so invoke the object
|
||||
// at the head of the queue. This will start
|
||||
// another wrte.
|
||||
queue_.front()->invoke();
|
||||
|
||||
// Delete the item since we used it
|
||||
queue_.erase(queue_.begin());
|
||||
}
|
||||
|
||||
// Called when a failure occurs
|
||||
//
|
||||
void
|
||||
fail(std::string what, error_code ec)
|
||||
{
|
||||
// Don't log end of stream or operation aborted
|
||||
// since those happen under normal circumstances.
|
||||
//
|
||||
if( ec != beast::http::error::end_of_stream &&
|
||||
ec != boost::asio::error::operation_aborted)
|
||||
{
|
||||
log_ <<
|
||||
"[#" << id_ << " " << ep_ << "] " <<
|
||||
what << ": " << ec.message() << std::endl;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// This class represents an asynchronous HTTP connection which
|
||||
// uses a plain TCP/IP socket (no encryption) as the stream.
|
||||
//
|
||||
template<class... Services>
|
||||
class async_http_con_plain
|
||||
|
||||
// Note that we give this object the enable_shared_from_this, and have
|
||||
// the base class call impl().shared_from_this(). The reason we do that
|
||||
// is so that the shared_ptr has the correct type. This lets the
|
||||
// derived class (this class) use its members in calls to std::bind,
|
||||
// without an ugly call to `dynamic_downcast` or other nonsense.
|
||||
//
|
||||
: public std::enable_shared_from_this<async_http_con_plain<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 async_http_con<async_http_con_plain<Services...>, Services...>
|
||||
{
|
||||
public:
|
||||
// Construct the plain connection.
|
||||
//
|
||||
template<class... Args>
|
||||
async_http_con_plain(
|
||||
socket_type&& sock,
|
||||
Args&&... args)
|
||||
: base_from_member<socket_type>(std::move(sock))
|
||||
, async_http_con<async_http_con_plain<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;
|
||||
}
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/* An asynchronous 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_async_port
|
||||
{
|
||||
// Reference to the server instance that made us
|
||||
server& instance_;
|
||||
|
||||
// The stream to log to
|
||||
std::ostream& log_;
|
||||
|
||||
// The list of services connections created from this port will support
|
||||
service_list<Services...> services_;
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
http_async_port(
|
||||
server& instance,
|
||||
std::ostream& log)
|
||||
: instance_(instance)
|
||||
, log_(log)
|
||||
{
|
||||
}
|
||||
|
||||
/** Initialize a service
|
||||
|
||||
Every service in the list must be initialized exactly once.
|
||||
|
||||
@param args Optional arguments forwarded to the service
|
||||
constructor.
|
||||
|
||||
@tparam Index The 0-based index of the service to initialize.
|
||||
|
||||
@return A reference to the service list. This permits
|
||||
calls to be chained in a single expression.
|
||||
*/
|
||||
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<async_http_con_plain<Services...>>(
|
||||
std::move(sock),
|
||||
"http_async_port",
|
||||
log_,
|
||||
services_,
|
||||
instance_.next_id(),
|
||||
ep
|
||||
)->run();
|
||||
}
|
||||
};
|
||||
|
||||
} // framework
|
||||
|
||||
#endif
|
77
example/server-framework/http_base.hpp
Normal file
77
example/server-framework/http_base.hpp
Normal file
@@ -0,0 +1,77 @@
|
||||
//
|
||||
// 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_BASE_HPP
|
||||
#define BEAST_EXAMPLE_SERVER_HTTP_BASE_HPP
|
||||
|
||||
#include <beast/core/string.hpp>
|
||||
#include <beast/http/empty_body.hpp>
|
||||
#include <beast/http/message.hpp>
|
||||
#include <beast/http/string_body.hpp>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <ostream>
|
||||
|
||||
namespace framework {
|
||||
|
||||
/* Base class for HTTP PortHandlers
|
||||
|
||||
This holds the server name and has some shared
|
||||
routines for building typical HTTP responses.
|
||||
*/
|
||||
class http_base
|
||||
{
|
||||
beast::string_view server_name_;
|
||||
|
||||
public:
|
||||
explicit
|
||||
http_base(beast::string_view server_name)
|
||||
: server_name_(server_name)
|
||||
{
|
||||
}
|
||||
|
||||
protected:
|
||||
// Returns a bad request result response
|
||||
//
|
||||
template<class Body, class Fields>
|
||||
beast::http::response<beast::http::string_body>
|
||||
bad_request(beast::http::request<Body, Fields> const& req) const
|
||||
{
|
||||
beast::http::response<beast::http::string_body> res;
|
||||
|
||||
// Match the version to the request
|
||||
res.version = req.version;
|
||||
|
||||
res.result(beast::http::status::bad_request);
|
||||
res.set(beast::http::field::server, server_name_);
|
||||
res.set(beast::http::field::content_type, "text/html");
|
||||
res.body = "Bad request";
|
||||
res.prepare();
|
||||
return res;
|
||||
}
|
||||
|
||||
// Returns a 100 Continue result response
|
||||
//
|
||||
template<class Body, class Fields>
|
||||
beast::http::response<beast::http::empty_body>
|
||||
continue_100(beast::http::request<Body, Fields> const& req) const
|
||||
{
|
||||
beast::http::response<beast::http::empty_body> res;
|
||||
|
||||
// Match the version to the request
|
||||
res.version = req.version;
|
||||
|
||||
res.result(beast::http::status::continue_);
|
||||
res.set(beast::http::field::server, server_name_);
|
||||
|
||||
return res;
|
||||
}
|
||||
};
|
||||
|
||||
} // framework
|
||||
|
||||
#endif
|
354
example/server-framework/http_sync_port.hpp
Normal file
354
example/server-framework/http_sync_port.hpp
Normal file
@@ -0,0 +1,354 @@
|
||||
//
|
||||
// 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 derivd 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
|
||||
: public http_base
|
||||
{
|
||||
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(
|
||||
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)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
run()
|
||||
{
|
||||
// Bind a shared pointer into the lambda for the
|
||||
// thread, so the sync_http_con is destroyed after
|
||||
// the thread function exits.
|
||||
//
|
||||
std::thread{
|
||||
&sync_http_con::do_run,
|
||||
impl().shared_from_this()
|
||||
}.detach();
|
||||
}
|
||||
|
||||
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& self_;
|
||||
|
||||
// holds the captured error code
|
||||
error_code& ec_;
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
//
|
||||
// Capture "this" and "ec"
|
||||
//
|
||||
send_lambda(sync_http_con& 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()
|
||||
{
|
||||
// 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(;;)
|
||||
{
|
||||
error_code ec;
|
||||
|
||||
// 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);
|
||||
|
||||
if(ec)
|
||||
return fail("on_read", 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));
|
||||
}
|
||||
|
||||
// Read the rest of the message, if any.
|
||||
//
|
||||
beast::http::read(impl().stream(), buffer_, parser, ec);
|
||||
|
||||
// Give each services a chance to handle the request
|
||||
//
|
||||
if(! services_.respond(
|
||||
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));
|
||||
}
|
||||
else
|
||||
{
|
||||
// See if the service that handled the
|
||||
// response took ownership of the stream.
|
||||
if(! impl().stream().is_open())
|
||||
{
|
||||
// They took ownership so just return and
|
||||
// let this sync_http_con object get destroyed.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(ec)
|
||||
return fail("on_write", ec);
|
||||
|
||||
// Theres no pipelining possible in a synchronous server
|
||||
// because we can't do reads and writes at the same time.
|
||||
}
|
||||
}
|
||||
|
||||
// Called when a failure occurs
|
||||
//
|
||||
void
|
||||
fail(std::string what, error_code ec)
|
||||
{
|
||||
if( ec != beast::http::error::end_of_stream &&
|
||||
ec != boost::asio::error::operation_aborted)
|
||||
{
|
||||
log_ <<
|
||||
"[#" << id_ << " " << ep_ << "] " <<
|
||||
what << ": " << ec.message() << std::endl;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// 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_plain
|
||||
|
||||
// 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_plain<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<sync_http_con_plain<Services...>, Services...>
|
||||
{
|
||||
public:
|
||||
// Construct the plain connection.
|
||||
//
|
||||
template<class... Args>
|
||||
sync_http_con_plain(
|
||||
socket_type&& sock,
|
||||
Args&&... args)
|
||||
: base_from_member<socket_type>(std::move(sock))
|
||||
, sync_http_con<sync_http_con_plain<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;
|
||||
}
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/* 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
|
||||
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_plain<Services...>>(
|
||||
std::move(sock),
|
||||
"http_sync_port",
|
||||
log_,
|
||||
services_,
|
||||
instance_.next_id(),
|
||||
ep
|
||||
)->run();
|
||||
}
|
||||
};
|
||||
|
||||
} // framework
|
||||
|
||||
#endif
|
242
example/server-framework/main.cpp
Normal file
242
example/server-framework/main.cpp
Normal file
@@ -0,0 +1,242 @@
|
||||
//
|
||||
// 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 "server.hpp"
|
||||
|
||||
#include "http_async_port.hpp"
|
||||
#include "http_sync_port.hpp"
|
||||
#include "ws_async_port.hpp"
|
||||
#include "ws_sync_port.hpp"
|
||||
|
||||
#include "file_service.hpp"
|
||||
#include "ws_upgrade_service.hpp"
|
||||
|
||||
#include <boost/program_options.hpp>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
/// Block until SIGINT or SIGTERM is received.
|
||||
void
|
||||
sig_wait()
|
||||
{
|
||||
// Create our own io_service for this
|
||||
boost::asio::io_service ios;
|
||||
|
||||
// Get notified on the signals we want
|
||||
boost::asio::signal_set signals(
|
||||
ios, SIGINT, SIGTERM);
|
||||
|
||||
// Now perform the asynchronous call
|
||||
signals.async_wait(
|
||||
[&](boost::system::error_code const&, int)
|
||||
{
|
||||
});
|
||||
|
||||
// Block the current thread on run(), when the
|
||||
// signal is received then this call will return.
|
||||
ios.run();
|
||||
}
|
||||
|
||||
/** Set the options on a WebSocket stream.
|
||||
|
||||
This is used by the websocket server port handlers.
|
||||
It is called every time a new websocket stream is
|
||||
created, to provide the opportunity to set settings
|
||||
for the connection.
|
||||
*/
|
||||
class set_ws_options
|
||||
{
|
||||
beast::websocket::permessage_deflate pmd_;
|
||||
|
||||
public:
|
||||
set_ws_options(beast::websocket::permessage_deflate const& pmd)
|
||||
: pmd_(pmd)
|
||||
{
|
||||
}
|
||||
|
||||
template<class NextLayer>
|
||||
void
|
||||
operator()(beast::websocket::stream<NextLayer>& ws) const
|
||||
{
|
||||
ws.auto_fragment(false);
|
||||
ws.set_option(pmd_);
|
||||
ws.read_message_max(64 * 1024 * 1024);
|
||||
}
|
||||
};
|
||||
|
||||
int
|
||||
main(
|
||||
int ac,
|
||||
char const* av[])
|
||||
{
|
||||
// Helper for reporting failures
|
||||
//
|
||||
using namespace framework;
|
||||
using namespace beast::http;
|
||||
|
||||
auto const fail =
|
||||
[&](
|
||||
std::string const& what,
|
||||
error_code const& ec)
|
||||
{
|
||||
std::cerr <<
|
||||
av[0] << ": " <<
|
||||
what << " failed, " <<
|
||||
ec.message() <<
|
||||
std::endl;
|
||||
return EXIT_FAILURE;
|
||||
};
|
||||
|
||||
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(1000),
|
||||
"Set the base 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")
|
||||
;
|
||||
po::variables_map vm;
|
||||
po::store(po::parse_command_line(ac, av, desc), vm);
|
||||
|
||||
// Get the IP address from the options
|
||||
std::string const ip = vm["ip"].as<std::string>();
|
||||
|
||||
// Get the port number from the options
|
||||
std::uint16_t const port = vm["port"].as<std::uint16_t>();
|
||||
|
||||
// Build an endpoint from the address and port
|
||||
address_type const addr{address_type::from_string(ip)};
|
||||
|
||||
// Get the number of threads from the command line
|
||||
std::size_t const threads = vm["threads"].as<std::size_t>();
|
||||
|
||||
// Get the root path from the command line
|
||||
boost::filesystem::path const root = vm["root"].as<std::string>();
|
||||
|
||||
// These settings will be applied to all new websocket connections
|
||||
beast::websocket::permessage_deflate pmd;
|
||||
pmd.client_enable = true;
|
||||
pmd.server_enable = true;
|
||||
pmd.compLevel = 3;
|
||||
|
||||
error_code ec;
|
||||
|
||||
// Create our server instance with the specified number of threads
|
||||
server instance{threads};
|
||||
|
||||
{
|
||||
// Install an asynchronous WebSocket echo port handler
|
||||
//
|
||||
auto wsp = instance.make_port<ws_async_port>(
|
||||
ec,
|
||||
endpoint_type{addr, port},
|
||||
instance,
|
||||
std::cout,
|
||||
set_ws_options{pmd}
|
||||
);
|
||||
|
||||
if(ec)
|
||||
return fail("ws_async_port", ec);
|
||||
|
||||
// Install an asynchronous HTTP port handler
|
||||
//
|
||||
auto sp = instance.make_port<http_async_port<
|
||||
ws_upgrade_service<ws_async_port>,
|
||||
file_service
|
||||
>>(
|
||||
ec,
|
||||
endpoint_type{addr,
|
||||
static_cast<unsigned short>(port + 1)},
|
||||
instance,
|
||||
std::cout);
|
||||
|
||||
if(ec)
|
||||
return fail("http_async_port", ec);
|
||||
|
||||
// Set up the ws_upgrade_service. We will route upgrade
|
||||
// requests to the websocket port handler created earlier.
|
||||
//
|
||||
sp->template init<0>(
|
||||
ec,
|
||||
wsp // The websocket port handler
|
||||
);
|
||||
|
||||
if(ec)
|
||||
return fail("http_async_port/ws_upgrade_service", ec);
|
||||
|
||||
// Set up the file_service to point to the root path.
|
||||
//
|
||||
sp->template init<1>(
|
||||
ec,
|
||||
root, // The root path
|
||||
"http_async_port" // The value for the Server field
|
||||
);
|
||||
|
||||
if(ec)
|
||||
return fail("http_async_port/file_service", ec);
|
||||
}
|
||||
|
||||
{
|
||||
// Install a synchronous WebSocket echo port handler
|
||||
//
|
||||
auto wsp = instance.make_port<ws_sync_port>(
|
||||
ec,
|
||||
endpoint_type{addr,
|
||||
static_cast<unsigned short>(port + 2)},
|
||||
instance,
|
||||
std::cout,
|
||||
set_ws_options{pmd});
|
||||
|
||||
if(ec)
|
||||
return fail("ws_sync_port", ec);
|
||||
|
||||
|
||||
// Install a synchronous HTTP port handler
|
||||
//
|
||||
auto sp = instance.make_port<http_sync_port<
|
||||
ws_upgrade_service<ws_sync_port>,
|
||||
file_service
|
||||
>>(
|
||||
ec,
|
||||
endpoint_type{addr,
|
||||
static_cast<unsigned short>(port + 3)},
|
||||
instance,
|
||||
std::cout);
|
||||
|
||||
if(ec)
|
||||
return fail("http_sync_port", ec);
|
||||
|
||||
// Set up the ws_upgrade_service. We will route upgrade
|
||||
// requests to the websocket port handler created earlier.
|
||||
//
|
||||
sp->template init<0>(
|
||||
ec,
|
||||
wsp
|
||||
);
|
||||
|
||||
if(ec)
|
||||
return fail("http_sync_port/ws_upgrade_service", ec);
|
||||
|
||||
// Set up the file_service to point to the root path.
|
||||
//
|
||||
sp->template init<1>(
|
||||
ec,
|
||||
root,
|
||||
"http_sync_port"
|
||||
);
|
||||
|
||||
if(ec)
|
||||
return fail("http_sync_port/file_service", ec);
|
||||
}
|
||||
|
||||
sig_wait();
|
||||
}
|
40
example/server-framework/rfc7231.hpp
Normal file
40
example/server-framework/rfc7231.hpp
Normal file
@@ -0,0 +1,40 @@
|
||||
//
|
||||
// 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_RFC7231_HPP
|
||||
#define BEAST_EXAMPLE_SERVER_RFC7231_HPP
|
||||
|
||||
#include <beast/core/string.hpp>
|
||||
#include <beast/http/message.hpp>
|
||||
|
||||
namespace framework {
|
||||
namespace rfc7231 {
|
||||
|
||||
// This aggregates a collection of algorithms
|
||||
// corresponding to specifications in rfc7231:
|
||||
//
|
||||
// https://tools.ietf.org/html/rfc7231
|
||||
//
|
||||
|
||||
/** Returns `true` if the message specifies Expect: 100-continue
|
||||
|
||||
@param req The request to check
|
||||
|
||||
@see https://tools.ietf.org/html/rfc7231#section-5.1.1
|
||||
*/
|
||||
template<class Body, class Fields>
|
||||
bool
|
||||
is_expect_100_continue(beast::http::request<Body, Fields> const& req)
|
||||
{
|
||||
return beast::iequals(
|
||||
req[beast::http::field::expect], "100-continue");
|
||||
}
|
||||
|
||||
} // rfc7231
|
||||
} // framework
|
||||
|
||||
#endif
|
258
example/server-framework/server.hpp
Normal file
258
example/server-framework/server.hpp
Normal file
@@ -0,0 +1,258 @@
|
||||
//
|
||||
// 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_FRAMEWORK_SERVER_HPP
|
||||
#define BEAST_EXAMPLE_FRAMEWORK_SERVER_HPP
|
||||
|
||||
#include "framework.hpp"
|
||||
|
||||
#include <boost/optional.hpp>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <stdexcept>
|
||||
#include <thread>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
namespace framework {
|
||||
|
||||
/** A server instance that accepts TCP/IP connections.
|
||||
|
||||
This is a general purpose TCP/IP server which contains
|
||||
zero or more user defined "ports". Each port represents
|
||||
a listening socket whose behavior is defined by an
|
||||
instance of the @b PortHandler concept.
|
||||
|
||||
To use the server, construct the class and then add the
|
||||
ports that you want using @ref make_port.
|
||||
|
||||
@par Example
|
||||
|
||||
@code
|
||||
|
||||
// Create a server with 4 threads
|
||||
//
|
||||
framework::server si(4);
|
||||
|
||||
// Create a port that echoes everything back.
|
||||
// Bind all available interfaces on port 1000.
|
||||
//
|
||||
framework::error_code ec;
|
||||
si.make_port<echo_port>(
|
||||
ec,
|
||||
server::endpoint_type{
|
||||
server::address_type::from_string("0.0.0.0"), 1000}
|
||||
);
|
||||
|
||||
...
|
||||
|
||||
// Close all connections, shut down the server
|
||||
si.stop();
|
||||
|
||||
@endcode
|
||||
*/
|
||||
class server
|
||||
{
|
||||
io_service_type ios_;
|
||||
std::vector<std::thread> tv_;
|
||||
boost::optional<boost::asio::io_service::work> work_;
|
||||
|
||||
public:
|
||||
server(server const&) = delete;
|
||||
server& operator=(server const&) = delete;
|
||||
|
||||
/** Constructor
|
||||
|
||||
@param n The number of threads to run on the `io_service`,
|
||||
which must be greater than zero.
|
||||
*/
|
||||
explicit
|
||||
server(std::size_t n = 1)
|
||||
: work_(ios_)
|
||||
{
|
||||
if(n < 1)
|
||||
throw std::invalid_argument{"threads < 1"};
|
||||
tv_.reserve(n);
|
||||
while(n--)
|
||||
tv_.emplace_back(
|
||||
[&]
|
||||
{
|
||||
ios_.run();
|
||||
});
|
||||
}
|
||||
|
||||
/** Destructor
|
||||
|
||||
Upon destruction, the `io_service` will be stopped
|
||||
and all pending completion handlers destroyed.
|
||||
*/
|
||||
~server()
|
||||
{
|
||||
work_ = boost::none;
|
||||
ios_.stop();
|
||||
for(auto& t : tv_)
|
||||
t.join();
|
||||
}
|
||||
|
||||
/// Return the `io_service` associated with the server
|
||||
boost::asio::io_service&
|
||||
get_io_service()
|
||||
{
|
||||
return ios_;
|
||||
}
|
||||
|
||||
/** Return a new, small integer unique id
|
||||
|
||||
These ids are used to uniquely identify connections
|
||||
in log output.
|
||||
*/
|
||||
std::size_t
|
||||
next_id()
|
||||
{
|
||||
static std::atomic<std::size_t> id_{0};
|
||||
return ++id_;
|
||||
}
|
||||
|
||||
/** Create a listening port.
|
||||
|
||||
@param ec Set to the error, if any occurred.
|
||||
|
||||
@param ep The address and port to bind to.
|
||||
|
||||
@param args Optional arguments, forwarded to the
|
||||
port handler's constructor.
|
||||
|
||||
@tparam PortHandler The port handler to use for handling
|
||||
incoming connections on this port. This handler must meet
|
||||
the requirements of @b PortHandler. A model of PortHandler
|
||||
is as follows:
|
||||
|
||||
@code
|
||||
|
||||
struct PortHandler
|
||||
{
|
||||
void
|
||||
on_accept(
|
||||
endpoint_type ep, // address of the remote endpoint
|
||||
socket_type&& sock, // the connected socket
|
||||
);
|
||||
};
|
||||
|
||||
@endcode
|
||||
*/
|
||||
template<class PortHandler, class... Args>
|
||||
std::shared_ptr<PortHandler>
|
||||
make_port(
|
||||
error_code& ec,
|
||||
endpoint_type const& ep,
|
||||
Args&&... args);
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
template<class PortHandler>
|
||||
class port
|
||||
: public std::enable_shared_from_this<
|
||||
port<PortHandler>>
|
||||
{
|
||||
server& instance_;
|
||||
PortHandler handler_;
|
||||
endpoint_type ep_;
|
||||
strand_type strand_;
|
||||
acceptor_type acceptor_;
|
||||
socket_type sock_;
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
//
|
||||
// args are forwarded to the PortHandler
|
||||
//
|
||||
template<class... Args>
|
||||
explicit
|
||||
port(server& instance, Args&&... args)
|
||||
: instance_(instance)
|
||||
, handler_(std::forward<Args>(args)...)
|
||||
, strand_(instance.get_io_service())
|
||||
, acceptor_(instance.get_io_service())
|
||||
, sock_(instance.get_io_service())
|
||||
{
|
||||
}
|
||||
|
||||
// Return the PortHandler wrapped in a shared_ptr
|
||||
//
|
||||
std::shared_ptr<PortHandler>
|
||||
handler()
|
||||
{
|
||||
// This uses a feature of std::shared_ptr invented by
|
||||
// Peter Dimov where the managed object piggy backs off
|
||||
// the reference count of another object containing it.
|
||||
//
|
||||
return std::shared_ptr<PortHandler>(
|
||||
this->shared_from_this(), &handler_);
|
||||
}
|
||||
|
||||
// Open the listening socket
|
||||
//
|
||||
void
|
||||
open(endpoint_type const& ep, error_code& ec)
|
||||
{
|
||||
acceptor_.open(ep.protocol(), ec);
|
||||
if(ec)
|
||||
return;
|
||||
acceptor_.set_option(
|
||||
boost::asio::socket_base::reuse_address{true});
|
||||
acceptor_.bind(ep, ec);
|
||||
if(ec)
|
||||
return;
|
||||
acceptor_.listen(
|
||||
boost::asio::socket_base::max_connections, ec);
|
||||
if(ec)
|
||||
return;
|
||||
acceptor_.async_accept(sock_, ep_,
|
||||
std::bind(&port::on_accept, this->shared_from_this(),
|
||||
std::placeholders::_1));
|
||||
}
|
||||
|
||||
private:
|
||||
// Called when an incoming connection is accepted
|
||||
//
|
||||
void
|
||||
on_accept(error_code ec)
|
||||
{
|
||||
if(! acceptor_.is_open())
|
||||
return;
|
||||
if(ec == boost::asio::error::operation_aborted)
|
||||
return;
|
||||
if(! ec)
|
||||
handler_.on_accept(std::move(sock_), ep_);
|
||||
acceptor_.async_accept(sock_, ep_,
|
||||
std::bind(&port::on_accept, this->shared_from_this(),
|
||||
std::placeholders::_1));
|
||||
}
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
template<class PortHandler, class... Args>
|
||||
std::shared_ptr<PortHandler>
|
||||
server::
|
||||
make_port(
|
||||
error_code& ec,
|
||||
endpoint_type const& ep,
|
||||
Args&&... args)
|
||||
{
|
||||
auto sp = std::make_shared<port<PortHandler>>(
|
||||
*this, std::forward<Args>(args)...);
|
||||
sp->open(ep, ec);
|
||||
if(ec)
|
||||
return nullptr;
|
||||
return sp->handler();
|
||||
}
|
||||
|
||||
} // framework
|
||||
|
||||
#endif
|
224
example/server-framework/service_list.hpp
Normal file
224
example/server-framework/service_list.hpp
Normal file
@@ -0,0 +1,224 @@
|
||||
//
|
||||
// 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_SERVICE_LIST_HPP
|
||||
#define BEAST_EXAMPLE_SERVER_SERVICE_LIST_HPP
|
||||
|
||||
#include "framework.hpp"
|
||||
|
||||
#include <beast/http/message.hpp>
|
||||
#include <boost/optional.hpp>
|
||||
#include <utility>
|
||||
|
||||
namespace framework {
|
||||
|
||||
/** A list of HTTP services which may process requests.
|
||||
|
||||
When a service is invoked, it is provided with the stream and
|
||||
endpoint metadata in addtion to an HTTP request. The service
|
||||
decides whether or not the process the request, returning
|
||||
`true` if the request is processed or `false` if it does not
|
||||
process the request.
|
||||
|
||||
@b Service requirements
|
||||
|
||||
@code
|
||||
|
||||
struct Service
|
||||
{
|
||||
// Initialize the service
|
||||
//
|
||||
void
|
||||
init(error_code& ec);
|
||||
|
||||
// Maybe respond to an HTTP request
|
||||
//
|
||||
// Returns `true` if it handled the response.
|
||||
//
|
||||
// Upon handling the response, the service may optionally
|
||||
// take ownership of either the stream, the request, or both.
|
||||
//
|
||||
template<
|
||||
class Stream,
|
||||
class Body, class Fields,
|
||||
class Send>
|
||||
bool
|
||||
respond(
|
||||
Stream&&,
|
||||
endpoint_type const& ep,
|
||||
beast::http::request<Body, Fields>&& req,
|
||||
Send const& send) const
|
||||
};
|
||||
|
||||
@endcode
|
||||
|
||||
@see file_service, ws_upgrade_service
|
||||
*/
|
||||
template<class... Services>
|
||||
class service_list
|
||||
{
|
||||
// This helper is for tag-dispatching tuple index
|
||||
template<std::size_t I>
|
||||
using C = std::integral_constant<std::size_t, I>;
|
||||
|
||||
// Each service is wrapped in a boost::optional so we
|
||||
// can construct them one by one later, instead of
|
||||
// having to construct them all at once.
|
||||
//
|
||||
std::tuple<boost::optional<Services>...> list_;
|
||||
|
||||
public:
|
||||
/// Constructor
|
||||
service_list() = default;
|
||||
|
||||
/// Constructor
|
||||
service_list(service_list&&) = default;
|
||||
|
||||
/// Constructor
|
||||
service_list(service_list const&) = default;
|
||||
|
||||
/** Initialize a service.
|
||||
|
||||
Every service in the list must be initialized exactly once
|
||||
before the service list is invoked.
|
||||
|
||||
@param args Optional arguments forwarded to the service
|
||||
constructor.
|
||||
|
||||
@tparam Index The 0-based index of the service to initialize.
|
||||
|
||||
@return A reference to the service list. This permits
|
||||
calls to be chained in a single expression.
|
||||
*/
|
||||
template<std::size_t Index, class... Args>
|
||||
void
|
||||
init(error_code& ec, Args&&... args)
|
||||
{
|
||||
// First, construct the service inside the optional
|
||||
std::get<Index>(list_).emplace(std::forward<Args>(args)...);
|
||||
|
||||
// Now allow the service to finish the initialization
|
||||
std::get<Index>(list_)->init(ec);
|
||||
}
|
||||
|
||||
/** Handle a request.
|
||||
|
||||
This function attempts to process the given HTTP request by
|
||||
invoking each service one at a time starting with the first
|
||||
service in the list. When a service indicates that it handles
|
||||
the request, by returning `true`, the function stops and
|
||||
returns the value `true`. Otherwise, if no service handles
|
||||
the request then the function returns the value `false`.
|
||||
|
||||
@param stream The stream belonging to the connection. A service
|
||||
which handles the request may optionally take ownership of the
|
||||
stream.
|
||||
|
||||
@param ep The remote endpoint of the connection corresponding
|
||||
to the stream.
|
||||
|
||||
@param req The request message to attempt handling. A service
|
||||
which handles the request may optionally take ownershop of the
|
||||
message.
|
||||
|
||||
@param send The function to invoke with the response. The function
|
||||
should have this equivalent signature:
|
||||
|
||||
@code
|
||||
|
||||
template<class Body, class Fields>
|
||||
void
|
||||
send(response<Body, Fields>&&);
|
||||
|
||||
@endcode
|
||||
|
||||
In C++14 this can be expressed using a generic lambda. In
|
||||
C++11 it will require a template member function of an invocable
|
||||
object.
|
||||
|
||||
@return `true` if the request was handled by a service.
|
||||
*/
|
||||
template<
|
||||
class Stream,
|
||||
class Body, class Fields,
|
||||
class Send>
|
||||
bool
|
||||
respond(
|
||||
Stream&& stream,
|
||||
endpoint_type const& ep,
|
||||
beast::http::request<Body, Fields>&& req,
|
||||
Send const& send) const
|
||||
{
|
||||
return try_respond(
|
||||
std::move(stream),
|
||||
ep,
|
||||
std::move(req),
|
||||
send, C<0>{});
|
||||
}
|
||||
|
||||
private:
|
||||
/* The implementation of `try_respond` is implemented using
|
||||
tail recursion which can usually be optimized away to
|
||||
something resembling a switch statement.
|
||||
*/
|
||||
template<
|
||||
class Stream,
|
||||
class Body, class Fields,
|
||||
class Send>
|
||||
bool
|
||||
try_respond(
|
||||
Stream&&,
|
||||
endpoint_type const&,
|
||||
beast::http::request<Body, Fields>&&,
|
||||
Send const&,
|
||||
C<sizeof...(Services)> const&) const
|
||||
{
|
||||
// This function breaks the recursion for the case where
|
||||
// where the Index is one past the last type in the list.
|
||||
//
|
||||
return false;
|
||||
}
|
||||
|
||||
// Invoke the I-th type in the type list
|
||||
//
|
||||
template<
|
||||
class Stream,
|
||||
class Body, class Fields,
|
||||
class Send,
|
||||
std::size_t I>
|
||||
bool
|
||||
try_respond(
|
||||
Stream&& stream,
|
||||
endpoint_type const& ep,
|
||||
beast::http::request<Body, Fields>&& req,
|
||||
Send const& send,
|
||||
C<I> const&) const
|
||||
{
|
||||
// If the I-th service handles the request then return
|
||||
//
|
||||
if(std::get<I>(list_)->respond(
|
||||
std::move(stream),
|
||||
ep,
|
||||
std::move(req),
|
||||
send))
|
||||
return true;
|
||||
|
||||
// Try the I+1th service. If I==sizeof...(Services)
|
||||
// then we call the other overload and return false.
|
||||
//
|
||||
return try_respond(
|
||||
std::move(stream),
|
||||
ep,
|
||||
std::move(req),
|
||||
send,
|
||||
C<I+1>{});
|
||||
}
|
||||
};
|
||||
|
||||
} // framework
|
||||
|
||||
#endif
|
264
example/server-framework/ssl/ssl_stream.hpp
Normal file
264
example/server-framework/ssl/ssl_stream.hpp
Normal file
@@ -0,0 +1,264 @@
|
||||
//
|
||||
// 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_SSL_STREAM_HPP
|
||||
#define BEAST_EXAMPLE_SERVER_SSL_STREAM_HPP
|
||||
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/asio/ssl/stream.hpp>
|
||||
#include <memory>
|
||||
|
||||
namespace beast {
|
||||
|
||||
/** Movable SSL socket wrapper
|
||||
|
||||
This wrapper provides an interface identical to `boost::asio::ssl::stream`,
|
||||
which is additionally move constructible and move assignable.
|
||||
*/
|
||||
template<class NextLayer>
|
||||
class ssl_stream
|
||||
: public boost::asio::ssl::stream_base
|
||||
{
|
||||
using stream_type = boost::asio::ssl::stream<NextLayer>;
|
||||
|
||||
std::unique_ptr<stream_type> p_;
|
||||
|
||||
public:
|
||||
/// The native handle type of the SSL stream.
|
||||
using native_handle_type = typename stream_type::native_handle_type;
|
||||
|
||||
/// Structure for use with deprecated impl_type.
|
||||
using impl_struct = typename stream_type::impl_struct;
|
||||
|
||||
/// (Deprecated: Use native_handle_type.) The underlying implementation type.
|
||||
using impl_type = typename stream_type::impl_type;
|
||||
|
||||
/// The type of the next layer.
|
||||
using next_layer_type = typename stream_type::next_layer_type;
|
||||
|
||||
/// The type of the lowest layer.
|
||||
using lowest_layer_type = typename stream_type::lowest_layer_type;
|
||||
|
||||
ssl_stream(ssl_stream&&) = default;
|
||||
ssl_stream(ssl_stream const&) = delete;
|
||||
ssl_stream& operator=(ssl_stream&&) = default;
|
||||
ssl_stream& operator=(ssl_stream const&) = delete;
|
||||
|
||||
template<class... Args>
|
||||
ssl_stream(Args&&... args)
|
||||
: p_(new stream_type{std::forward<Args>(args)...)
|
||||
{
|
||||
}
|
||||
|
||||
boost::asio::io_service&
|
||||
get_io_service()
|
||||
{
|
||||
return p_->get_io_service();
|
||||
}
|
||||
|
||||
native_handle_type
|
||||
native_handle()
|
||||
{
|
||||
return p_->native_handle();
|
||||
}
|
||||
|
||||
impl_type
|
||||
impl()
|
||||
{
|
||||
return p_->impl();
|
||||
}
|
||||
|
||||
next_layer_type const&
|
||||
next_layer() const
|
||||
{
|
||||
return p_->next_layer();
|
||||
}
|
||||
|
||||
next_layer_type&
|
||||
next_layer()
|
||||
{
|
||||
return p_->next_layer();
|
||||
}
|
||||
|
||||
lowest_layer_type&
|
||||
lowest_layer()
|
||||
{
|
||||
return p_->lowest_layer();
|
||||
}
|
||||
|
||||
lowest_layer_type const&
|
||||
lowest_layer() const
|
||||
{
|
||||
return p_->lowest_layer();
|
||||
}
|
||||
|
||||
void
|
||||
set_verify_mode(boost::asio::ssl::verify_mode v)
|
||||
{
|
||||
p_->set_verify_mode(v);
|
||||
}
|
||||
|
||||
boost::system::error_code
|
||||
set_verify_mode(boost::asio::ssl::verify_mode v,
|
||||
boost::system::error_code& ec)
|
||||
{
|
||||
return p_->set_verify_mode(v, ec);
|
||||
}
|
||||
|
||||
void
|
||||
set_verify_depth(int depth)
|
||||
{
|
||||
p_->set_verify_depth(depth);
|
||||
}
|
||||
|
||||
boost::system::error_code
|
||||
set_verify_depth(
|
||||
int depth, boost::system::error_code& ec)
|
||||
{
|
||||
return p_->set_verify_depth(depth, ec);
|
||||
}
|
||||
|
||||
template<class VerifyCallback>
|
||||
void
|
||||
set_verify_callback(VerifyCallback callback)
|
||||
{
|
||||
p_->set_verify_callback(callback);
|
||||
}
|
||||
|
||||
template<class VerifyCallback>
|
||||
boost::system::error_code
|
||||
set_verify_callback(VerifyCallback callback,
|
||||
boost::system::error_code& ec)
|
||||
{
|
||||
return p_->set_verify_callback(callback, ec);
|
||||
}
|
||||
|
||||
void
|
||||
handshake(handshake_type type)
|
||||
{
|
||||
p_->handshake(type);
|
||||
}
|
||||
|
||||
boost::system::error_code
|
||||
handshake(handshake_type type,
|
||||
boost::system::error_code& ec)
|
||||
{
|
||||
return p_->handshake(type, ec);
|
||||
}
|
||||
|
||||
template<class ConstBufferSequence>
|
||||
void
|
||||
handshake(
|
||||
handshake_type type, ConstBufferSequence const& buffers)
|
||||
{
|
||||
p_->handshake(type, buffers);
|
||||
}
|
||||
|
||||
template<class ConstBufferSequence>
|
||||
boost::system::error_code
|
||||
handshake(handshake_type type,
|
||||
ConstBufferSequence const& buffers,
|
||||
boost::system::error_code& ec)
|
||||
{
|
||||
return p_->handshake(type, buffers, ec);
|
||||
}
|
||||
|
||||
template<class HandshakeHandler>
|
||||
BOOST_ASIO_INITFN_RESULT_TYPE(HandshakeHandler,
|
||||
void(boost::system::error_code))
|
||||
async_handshake(handshake_type type,
|
||||
BOOST_ASIO_MOVE_ARG(HandshakeHandler) handler)
|
||||
{
|
||||
return p_->async_handshake(type,
|
||||
BOOST_ASIO_MOVE_CAST(HandshakeHandler)(handler));
|
||||
}
|
||||
|
||||
template<class ConstBufferSequence, class BufferedHandshakeHandler>
|
||||
BOOST_ASIO_INITFN_RESULT_TYPE(BufferedHandshakeHandler,
|
||||
void (boost::system::error_code, std::size_t))
|
||||
async_handshake(handshake_type type, ConstBufferSequence const& buffers,
|
||||
BOOST_ASIO_MOVE_ARG(BufferedHandshakeHandler) handler)
|
||||
{
|
||||
return p_->async_handshake(type, buffers,
|
||||
BOOST_ASIO__MOVE_CAST(BufferedHandshakeHandler)(handler));
|
||||
}
|
||||
|
||||
void
|
||||
shutdown()
|
||||
{
|
||||
p_->shutdown();
|
||||
}
|
||||
|
||||
boost::system::error_code
|
||||
shutdown(boost::system::error_code& ec)
|
||||
{
|
||||
return p_->shutdown(ec);
|
||||
}
|
||||
|
||||
template<class ShutdownHandler>
|
||||
BOOST_ASIO_INITFN_RESULT_TYPE(ShutdownHandler,
|
||||
void (boost::system::error_code))
|
||||
async_shutdown(BOOST_ASIO_MOVE_ARG(ShutdownHandler) handler)
|
||||
{
|
||||
return p_->async_shutdown(
|
||||
BOOST_ASIO_MOVE_CAST(ShutdownHandler)(handler));
|
||||
}
|
||||
|
||||
template<class ConstBufferSequence>
|
||||
std::size_t
|
||||
write_some(ConstBufferSequence const& buffers)
|
||||
{
|
||||
return p_->write_some(buffers);
|
||||
}
|
||||
|
||||
template<class ConstBufferSequence>
|
||||
std::size_t
|
||||
write_some(ConstBufferSequence const& buffers,
|
||||
boost::system::error_code& ec)
|
||||
{
|
||||
return p_->write_some(buffers, ec);
|
||||
}
|
||||
|
||||
template<class ConstBufferSequence, class WriteHandler>
|
||||
BOOST_ASIO_INITFN_RESULT_TYPE(WriteHandler,
|
||||
void (boost::system::error_code, std::size_t))
|
||||
async_write_some(ConstBufferSequence const& buffers,
|
||||
BOOST_ASIO_MOVE_ARG(WriteHandler) handler)
|
||||
{
|
||||
return p_->async_write_some(buffers,
|
||||
BOOST_ASIO_MOVE_CAST(WriteHandler)(handler));
|
||||
}
|
||||
|
||||
template<class MutableBufferSequence>
|
||||
std::size_t
|
||||
read_some(MutableBufferSequence const& buffers)
|
||||
{
|
||||
return p_->read_some(buffers);
|
||||
}
|
||||
|
||||
template<class MutableBufferSequence>
|
||||
std::size_t
|
||||
read_some(MutableBufferSequence const& buffers,
|
||||
boost::system::error_code& ec)
|
||||
{
|
||||
return p_->read_some(buffers, ec);
|
||||
}
|
||||
|
||||
template<class MutableBufferSequence, class ReadHandler>
|
||||
BOOST_ASIO_INITFN_RESULT_TYPE(ReadHandler,
|
||||
void(boost::system::error_code, std::size_t))
|
||||
async_read_some(MutableBufferSequence& buffers,
|
||||
BOOST_ASIO_MOVE_ARG(ReadHandler) handler)
|
||||
{
|
||||
return p_->async_read_some(buffers,
|
||||
BOOST_ASIO_MOVE_CAST(ReadHandler)(handler))
|
||||
}
|
||||
};
|
||||
|
||||
} // beast
|
||||
|
||||
#endif
|
188
example/server-framework/write_msg.hpp
Normal file
188
example/server-framework/write_msg.hpp
Normal file
@@ -0,0 +1,188 @@
|
||||
//
|
||||
// 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_WRITE_MSG_HPP
|
||||
#define BEAST_EXAMPLE_SERVER_WRITE_MSG_HPP
|
||||
|
||||
#include "server.hpp"
|
||||
|
||||
#include <beast/core/async_result.hpp>
|
||||
#include <beast/core/handler_ptr.hpp>
|
||||
#include <beast/core/type_traits.hpp>
|
||||
#include <beast/http/message.hpp>
|
||||
#include <beast/http/write.hpp>
|
||||
#include <beast/http/type_traits.hpp>
|
||||
#include <boost/asio/handler_alloc_hook.hpp>
|
||||
#include <boost/asio/handler_continuation_hook.hpp>
|
||||
#include <boost/asio/handler_invoke_hook.hpp>
|
||||
|
||||
namespace framework {
|
||||
|
||||
namespace detail {
|
||||
|
||||
/** Composed operation to send an HTTP message
|
||||
|
||||
This implements the composed operation needed for the
|
||||
@ref async_write_msg function.
|
||||
*/
|
||||
template<
|
||||
class AsyncWriteStream,
|
||||
class Handler,
|
||||
bool isRequest, class Body, class Fields>
|
||||
class write_msg_op
|
||||
{
|
||||
struct data
|
||||
{
|
||||
AsyncWriteStream& stream;
|
||||
beast::http::message<isRequest, Body, Fields> msg;
|
||||
|
||||
data(
|
||||
Handler& handler,
|
||||
AsyncWriteStream& stream_,
|
||||
beast::http::message<isRequest, Body, Fields>&& msg_)
|
||||
: stream(stream_)
|
||||
, msg(std::move(msg_))
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
beast::handler_ptr<data, Handler> d_;
|
||||
|
||||
public:
|
||||
write_msg_op(write_msg_op&&) = default;
|
||||
write_msg_op(write_msg_op const&) = default;
|
||||
|
||||
template<class DeducedHandler, class... Args>
|
||||
write_msg_op(DeducedHandler&& h, AsyncWriteStream& s, Args&&... args)
|
||||
: d_(std::forward<DeducedHandler>(h),
|
||||
s, std::forward<Args>(args)...)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
operator()()
|
||||
{
|
||||
auto& d = *d_;
|
||||
beast::http::async_write(
|
||||
d.stream, d.msg, std::move(*this));
|
||||
}
|
||||
|
||||
void
|
||||
operator()(error_code ec)
|
||||
{
|
||||
d_.invoke(ec);
|
||||
}
|
||||
|
||||
friend
|
||||
void* asio_handler_allocate(
|
||||
std::size_t size, write_msg_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_msg_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_msg_op* op)
|
||||
{
|
||||
using boost::asio::asio_handler_is_continuation;
|
||||
return asio_handler_is_continuation(std::addressof(op->d_.handler()));
|
||||
}
|
||||
|
||||
template<class Function>
|
||||
friend
|
||||
void asio_handler_invoke(Function&& f, write_msg_op* op)
|
||||
{
|
||||
using boost::asio::asio_handler_invoke;
|
||||
asio_handler_invoke(
|
||||
f, std::addressof(op->d_.handler()));
|
||||
}
|
||||
};
|
||||
|
||||
} // detail
|
||||
|
||||
/** Write an HTTP message to a stream asynchronously
|
||||
|
||||
This function is used to write a complete message to a stream asynchronously
|
||||
using HTTP/1. The function call always returns immediately. The asynchronous
|
||||
operation will continue until one of the following conditions is true:
|
||||
|
||||
@li The entire message is written.
|
||||
|
||||
@li An error occurs.
|
||||
|
||||
This operation is implemented in terms of zero or more calls to the stream's
|
||||
`async_write_some` function, and is known as a <em>composed operation</em>.
|
||||
The program must ensure that the stream performs no other write operations
|
||||
until this operation completes. The algorithm will use a temporary
|
||||
@ref serializer with an empty chunk decorator to produce buffers. If
|
||||
the semantics of the message indicate that the connection should be
|
||||
closed after the message is sent, the error delivered by this function
|
||||
will be @ref error::end_of_stream
|
||||
|
||||
@param stream The stream to which the data is to be written.
|
||||
The type must support the @b AsyncWriteStream concept.
|
||||
|
||||
@param msg The message to write. The function will take ownership
|
||||
of the object as if by move constrction.
|
||||
|
||||
@param handler The handler to be called when the operation
|
||||
completes. Copies will be made of the handler as required.
|
||||
The equivalent function signature of the handler must be:
|
||||
@code void handler(
|
||||
error_code const& error // result of operation
|
||||
); @endcode
|
||||
Regardless of whether the asynchronous operation completes
|
||||
immediately or not, the handler will not be invoked from within
|
||||
this function. Invocation of the handler will be performed in a
|
||||
manner equivalent to using `boost::asio::io_service::post`.
|
||||
*/
|
||||
template<
|
||||
class AsyncWriteStream,
|
||||
bool isRequest, class Body, class Fields,
|
||||
class WriteHandler>
|
||||
beast::async_return_type<WriteHandler, void(error_code)>
|
||||
async_write_msg(
|
||||
AsyncWriteStream& stream,
|
||||
beast::http::message<isRequest, Body, Fields>&& msg,
|
||||
WriteHandler&& handler)
|
||||
{
|
||||
static_assert(
|
||||
beast::is_async_write_stream<AsyncWriteStream>::value,
|
||||
"AsyncWriteStream requirements not met");
|
||||
|
||||
static_assert(beast::http::is_body<Body>::value,
|
||||
"Body requirements not met");
|
||||
|
||||
static_assert(beast::http::is_body_reader<Body>::value,
|
||||
"BodyReader requirements not met");
|
||||
|
||||
beast::async_completion<WriteHandler, void(error_code)> init{handler};
|
||||
|
||||
detail::write_msg_op<
|
||||
AsyncWriteStream,
|
||||
beast::handler_type<WriteHandler, void(error_code)>,
|
||||
isRequest, Body, Fields>{
|
||||
init.completion_handler,
|
||||
stream,
|
||||
std::move(msg)}();
|
||||
|
||||
return init.result.get();
|
||||
}
|
||||
|
||||
} // framework
|
||||
|
||||
#endif
|
330
example/server-framework/ws_async_port.hpp
Normal file
330
example/server-framework/ws_async_port.hpp
Normal file
@@ -0,0 +1,330 @@
|
||||
//
|
||||
// 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_WS_ASYNC_PORT_HPP
|
||||
#define BEAST_EXAMPLE_SERVER_WS_ASYNC_PORT_HPP
|
||||
|
||||
#include "server.hpp"
|
||||
|
||||
#include <beast/core/multi_buffer.hpp>
|
||||
#include <beast/websocket/stream.hpp>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <ostream>
|
||||
|
||||
namespace framework {
|
||||
|
||||
// This object holds the state of the connection
|
||||
// including, most importantly, the socket or stream.
|
||||
//
|
||||
// `Stream` is the type of socket or stream used as the
|
||||
// transport. Examples include boost::asio::ip::tcp::socket
|
||||
// or `ssl_stream`.
|
||||
//
|
||||
template<class Derived>
|
||||
class async_ws_con
|
||||
{
|
||||
Derived&
|
||||
impl()
|
||||
{
|
||||
return static_cast<Derived&>(*this);
|
||||
}
|
||||
|
||||
// The string used to set the Server http field
|
||||
std::string server_name_;
|
||||
|
||||
// The stream to use for logging
|
||||
std::ostream& log_;
|
||||
|
||||
// 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_;
|
||||
|
||||
// This is used to hold the message data
|
||||
beast::multi_buffer buffer_;
|
||||
|
||||
protected:
|
||||
// The strand makes sure that our data is
|
||||
// accessed from only one thread at a time.
|
||||
//
|
||||
strand_type strand_;
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
template<class Callback>
|
||||
async_ws_con(
|
||||
beast::string_view server_name,
|
||||
std::ostream& log,
|
||||
std::size_t id,
|
||||
endpoint_type const& ep,
|
||||
Callback const& cb)
|
||||
: server_name_(server_name)
|
||||
, log_(log)
|
||||
, id_(id)
|
||||
, ep_(ep)
|
||||
|
||||
// Limit of 1MB on messages
|
||||
, buffer_(1024 * 1024)
|
||||
|
||||
, strand_(impl().ws().get_io_service())
|
||||
{
|
||||
cb(impl().ws());
|
||||
}
|
||||
|
||||
// Run the connection
|
||||
//
|
||||
void
|
||||
run()
|
||||
{
|
||||
// Read the WebSocket upgrade request and attempt
|
||||
// to send back the response.
|
||||
//
|
||||
impl().ws().async_accept_ex(
|
||||
[&](beast::websocket::response_type& res)
|
||||
{
|
||||
res.set(beast::http::field::server, server_name_);
|
||||
},
|
||||
strand_.wrap(std::bind(
|
||||
&async_ws_con::on_accept,
|
||||
impl().shared_from_this(),
|
||||
std::placeholders::_1)));
|
||||
}
|
||||
|
||||
// Run the connection.
|
||||
//
|
||||
// This overload handles the case where we
|
||||
// already have the WebSocket Upgrade request.
|
||||
//
|
||||
template<class Body, class Fields>
|
||||
void
|
||||
run(beast::http::request<Body, Fields> const& req)
|
||||
{
|
||||
// Call the overload of accept() which takes
|
||||
// the request by parameter, instead of reading
|
||||
// it from the network.
|
||||
//
|
||||
impl().ws().async_accept_ex(req,
|
||||
[&](beast::websocket::response_type& res)
|
||||
{
|
||||
res.set(beast::http::field::server, server_name_);
|
||||
},
|
||||
strand_.wrap(std::bind(
|
||||
&async_ws_con::on_accept,
|
||||
impl().shared_from_this(),
|
||||
std::placeholders::_1)));
|
||||
}
|
||||
|
||||
private:
|
||||
// Called when accept_ex completes
|
||||
//
|
||||
void
|
||||
on_accept(error_code ec)
|
||||
{
|
||||
if(ec)
|
||||
return fail("async_accept", ec);
|
||||
do_read();
|
||||
}
|
||||
|
||||
// Read the next WebSocket message
|
||||
//
|
||||
void
|
||||
do_read()
|
||||
{
|
||||
impl().ws().async_read(
|
||||
buffer_,
|
||||
strand_.wrap(std::bind(
|
||||
&async_ws_con::on_read,
|
||||
impl().shared_from_this(),
|
||||
std::placeholders::_1)));
|
||||
}
|
||||
|
||||
// Called when the message read completes
|
||||
//
|
||||
void
|
||||
on_read(error_code ec)
|
||||
{
|
||||
if(ec)
|
||||
return fail("on_read", ec);
|
||||
|
||||
// Set the outgoing message type. We will use
|
||||
// the same setting as the message we just read.
|
||||
//
|
||||
impl().ws().binary(impl().ws().got_binary());
|
||||
|
||||
// Now echo back the message
|
||||
//
|
||||
impl().ws().async_write(
|
||||
buffer_.data(),
|
||||
strand_.wrap(std::bind(
|
||||
&async_ws_con::on_write,
|
||||
impl().shared_from_this(),
|
||||
std::placeholders::_1)));
|
||||
}
|
||||
|
||||
// Called when the message write completes
|
||||
//
|
||||
void
|
||||
on_write(error_code ec)
|
||||
{
|
||||
if(ec)
|
||||
return fail("on_write", ec);
|
||||
|
||||
// Empty out the contents of the message buffer
|
||||
// to prepare it for the next call to read.
|
||||
//
|
||||
buffer_.consume(buffer_.size());
|
||||
|
||||
// Now read another message
|
||||
//
|
||||
do_read();
|
||||
}
|
||||
|
||||
// This helper reports failures
|
||||
//
|
||||
void
|
||||
fail(std::string what, error_code ec)
|
||||
{
|
||||
if(ec != beast::websocket::error::closed)
|
||||
log_ <<
|
||||
"[#" << id_ << " " << ep_ << "] " <<
|
||||
what << ": " << ec.message() << std::endl;
|
||||
}
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
class async_ws_con_plain
|
||||
: public std::enable_shared_from_this<async_ws_con_plain>
|
||||
, public base_from_member<beast::websocket::stream<socket_type>>
|
||||
, public async_ws_con<async_ws_con_plain>
|
||||
{
|
||||
public:
|
||||
template<class... Args>
|
||||
explicit
|
||||
async_ws_con_plain(
|
||||
socket_type&& sock,
|
||||
Args&&... args)
|
||||
: base_from_member<beast::websocket::stream<socket_type>>(std::move(sock))
|
||||
, async_ws_con<async_ws_con_plain>(std::forward<Args>(args)...)
|
||||
{
|
||||
}
|
||||
|
||||
beast::websocket::stream<socket_type>&
|
||||
ws()
|
||||
{
|
||||
return this->member;
|
||||
}
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** An synchronous WebSocket @b PortHandler which implements echo.
|
||||
|
||||
This is a port handler which accepts WebSocket upgrade HTTP
|
||||
requests and implements the echo protocol. All received
|
||||
WebSocket messages will be echoed back to the remote host.
|
||||
*/
|
||||
class ws_async_port
|
||||
{
|
||||
// The type of the on_stream callback
|
||||
using on_new_stream_cb = std::function<
|
||||
void(beast::websocket::stream<socket_type>&)>;
|
||||
|
||||
server& instance_;
|
||||
std::ostream& log_;
|
||||
on_new_stream_cb cb_;
|
||||
|
||||
public:
|
||||
/** Constructor
|
||||
|
||||
@param instance The server instance which owns this port
|
||||
|
||||
@param log The stream to use for logging
|
||||
|
||||
@param cb A callback which will be invoked for every new
|
||||
WebSocket connection. This provides an opportunity to change
|
||||
the settings on the stream before it is used. The callback
|
||||
should have this equivalent signature:
|
||||
@code
|
||||
template<class NextLayer>
|
||||
void callback(beast::websocket::stream<NextLayer>&);
|
||||
@endcode
|
||||
In C++14 this can be accomplished with a generic lambda. In
|
||||
C++11 it will be necessary to write out a lambda manually,
|
||||
with a templated operator().
|
||||
*/
|
||||
template<class Callback>
|
||||
ws_async_port(
|
||||
server& instance,
|
||||
std::ostream& log,
|
||||
Callback const& cb)
|
||||
: instance_(instance)
|
||||
, log_(log)
|
||||
, cb_(cb)
|
||||
{
|
||||
}
|
||||
|
||||
/** Accept a TCP/IP async_ws_con.
|
||||
|
||||
This function is called when the server has accepted an
|
||||
incoming async_ws_con.
|
||||
|
||||
@param sock The connected socket.
|
||||
|
||||
@param ep The endpoint of the remote host.
|
||||
*/
|
||||
void
|
||||
on_accept(
|
||||
socket_type&& sock,
|
||||
endpoint_type ep)
|
||||
{
|
||||
std::make_shared<async_ws_con_plain>(
|
||||
std::move(sock),
|
||||
"ws_async_port",
|
||||
log_,
|
||||
instance_.next_id(),
|
||||
ep,
|
||||
cb_
|
||||
)->run();
|
||||
}
|
||||
|
||||
/** Accept a WebSocket upgrade request.
|
||||
|
||||
This is used to accept a async_ws_con that has already
|
||||
delivered the handshake.
|
||||
|
||||
@param stream The stream corresponding to the async_ws_con.
|
||||
|
||||
@param ep The remote endpoint.
|
||||
|
||||
@param req The upgrade request.
|
||||
*/
|
||||
template<class Body, class Fields>
|
||||
void
|
||||
accept(
|
||||
socket_type&& stream,
|
||||
endpoint_type ep,
|
||||
beast::http::request<Body, Fields>&& req)
|
||||
{
|
||||
std::make_shared<async_ws_con_plain>(
|
||||
std::move(stream),
|
||||
"ws_async_port",
|
||||
log_,
|
||||
instance_.next_id(),
|
||||
ep,
|
||||
cb_
|
||||
)->run(std::move(req));
|
||||
}
|
||||
};
|
||||
|
||||
} // framework
|
||||
|
||||
#endif
|
369
example/server-framework/ws_sync_port.hpp
Normal file
369
example/server-framework/ws_sync_port.hpp
Normal file
@@ -0,0 +1,369 @@
|
||||
//
|
||||
// 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_WS_SYNC_PORT_HPP
|
||||
#define BEAST_EXAMPLE_SERVER_WS_SYNC_PORT_HPP
|
||||
|
||||
#include "server.hpp"
|
||||
|
||||
#include <beast/core/multi_buffer.hpp>
|
||||
#include <beast/websocket.hpp>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <ostream>
|
||||
#include <thread>
|
||||
|
||||
namespace framework {
|
||||
|
||||
// The connection object holds the state of the connection
|
||||
// including, most importantly, the socket or stream.
|
||||
//
|
||||
// `Stream` is the type of socket or stream used as the
|
||||
// transport. Examples include boost::asio::ip::tcp::socket
|
||||
// or `ssl_stream`.
|
||||
//
|
||||
template<class Derived>
|
||||
class sync_ws_con
|
||||
{
|
||||
Derived&
|
||||
impl()
|
||||
{
|
||||
return static_cast<Derived&>(*this);
|
||||
}
|
||||
|
||||
// The string used to set the Server http field
|
||||
std::string server_name_;
|
||||
|
||||
// The stream to use for logging
|
||||
std::ostream& log_;
|
||||
|
||||
// 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_;
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
template<class Callback>
|
||||
sync_ws_con(
|
||||
beast::string_view server_name,
|
||||
std::ostream& log,
|
||||
std::size_t id,
|
||||
endpoint_type const& ep,
|
||||
Callback const& cb)
|
||||
: server_name_(server_name)
|
||||
, log_(log)
|
||||
, id_(id)
|
||||
, ep_(ep)
|
||||
{
|
||||
cb(impl().ws());
|
||||
}
|
||||
|
||||
// Run the connection.
|
||||
//
|
||||
void
|
||||
run()
|
||||
{
|
||||
// We run the do_accept function in its own thread,
|
||||
// and bind a shared pointer to the connection object
|
||||
// into the function. The last reference to the shared
|
||||
// pointer will go away when the thread exits, thus
|
||||
// destroying the connection object.
|
||||
//
|
||||
std::thread{
|
||||
&sync_ws_con::do_accept,
|
||||
impl().shared_from_this()
|
||||
}.detach();
|
||||
}
|
||||
|
||||
// Run the connection from an already-received Upgrade request.
|
||||
//
|
||||
template<class Body, class Fields>
|
||||
void
|
||||
run(beast::http::request<Body, Fields>&& req)
|
||||
{
|
||||
BOOST_ASSERT(beast::websocket::is_upgrade(req));
|
||||
|
||||
// We need to transfer ownership of the request object into
|
||||
// the lambda, but there's no C++14 lambda capture
|
||||
// so we have to write it out by manually specifying the lambda.
|
||||
//
|
||||
std::thread{
|
||||
lambda<Body, Fields>{
|
||||
impl().shared_from_this(),
|
||||
std::move(req)
|
||||
}}.detach();
|
||||
}
|
||||
|
||||
private:
|
||||
// This is the lambda used when launching a connection from
|
||||
// an already-received request. In C++14 we could simply use
|
||||
// a lambda capture but this example requires only C++11 so
|
||||
// we write out the lambda ourselves. This is similar to what
|
||||
// the compiler would generate anyway.
|
||||
//
|
||||
template<class Body, class Fields>
|
||||
class lambda
|
||||
{
|
||||
std::shared_ptr<sync_ws_con> self_;
|
||||
beast::http::request<Body, Fields> req_;
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
//
|
||||
// This is the equivalent of the capture section of the lambda.
|
||||
//
|
||||
lambda(
|
||||
std::shared_ptr<sync_ws_con> self,
|
||||
beast::http::request<Body, Fields>&& req)
|
||||
: self_(std::move(self))
|
||||
, req_(std::move(req))
|
||||
{
|
||||
BOOST_ASSERT(beast::websocket::is_upgrade(req_));
|
||||
}
|
||||
|
||||
// Invoke the lambda
|
||||
//
|
||||
void
|
||||
operator()()
|
||||
{
|
||||
BOOST_ASSERT(beast::websocket::is_upgrade(req_));
|
||||
error_code ec;
|
||||
{
|
||||
// Move the message to the stack so we can get
|
||||
// rid of resources, otherwise it will linger
|
||||
// for the lifetime of the connection.
|
||||
//
|
||||
auto req = std::move(req_);
|
||||
|
||||
// Call the overload of accept() which takes
|
||||
// the request by parameter, instead of reading
|
||||
// it from the network.
|
||||
//
|
||||
self_->impl().ws().accept_ex(req,
|
||||
[&](beast::websocket::response_type& res)
|
||||
{
|
||||
res.insert(beast::http::field::server, self_->server_name_);
|
||||
},
|
||||
ec);
|
||||
}
|
||||
|
||||
// Run the connection
|
||||
//
|
||||
self_->do_run(ec);
|
||||
}
|
||||
};
|
||||
|
||||
void
|
||||
do_accept()
|
||||
{
|
||||
error_code ec;
|
||||
|
||||
// Read the WebSocket upgrade request and attempt
|
||||
// to send back the response.
|
||||
//
|
||||
impl().ws().accept_ex(
|
||||
[&](beast::websocket::response_type& res)
|
||||
{
|
||||
res.insert(beast::http::field::server, server_name_);
|
||||
},
|
||||
ec);
|
||||
|
||||
// Run the connection
|
||||
//
|
||||
do_run(ec);
|
||||
}
|
||||
|
||||
void
|
||||
do_run(error_code ec)
|
||||
{
|
||||
// Helper lambda to report a failure
|
||||
//
|
||||
auto const fail =
|
||||
[&](std::string const& what, error_code ev)
|
||||
{
|
||||
if(ev != beast::websocket::error::closed)
|
||||
log_ <<
|
||||
"[#" << id_ << " " << ep_ << "] " <<
|
||||
what << ": " << ev.message() << std::endl;
|
||||
};
|
||||
|
||||
// Check for an error upon entry. This will
|
||||
// come from one of the two calls to accept()
|
||||
//
|
||||
if(ec)
|
||||
{
|
||||
fail("accept", ec);
|
||||
return;
|
||||
}
|
||||
|
||||
// Loop, reading messages and echoing them back.
|
||||
//
|
||||
for(;;)
|
||||
{
|
||||
// This buffer holds the message. We place a one
|
||||
// megabyte limit on the size to prevent abuse.
|
||||
//
|
||||
beast::multi_buffer buffer{1024*1024};
|
||||
|
||||
// Read the message
|
||||
//
|
||||
impl().ws().read(buffer, ec);
|
||||
|
||||
if(ec)
|
||||
return fail("read", ec);
|
||||
|
||||
// Set the outgoing message type. We will use
|
||||
// the same setting as the message we just read.
|
||||
//
|
||||
impl().ws().binary(impl().ws().got_binary());
|
||||
|
||||
// Now echo back the message
|
||||
//
|
||||
impl().ws().write(buffer.data(), ec);
|
||||
|
||||
if(ec)
|
||||
return fail("write", ec);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
class sync_ws_con_plain
|
||||
: public std::enable_shared_from_this<sync_ws_con_plain>
|
||||
, public base_from_member<beast::websocket::stream<socket_type>>
|
||||
, public sync_ws_con<sync_ws_con_plain>
|
||||
{
|
||||
public:
|
||||
template<class... Args>
|
||||
explicit
|
||||
sync_ws_con_plain(
|
||||
socket_type&& sock,
|
||||
Args&&... args)
|
||||
: base_from_member<beast::websocket::stream<socket_type>>(std::move(sock))
|
||||
, sync_ws_con<sync_ws_con_plain>(std::forward<Args>(args)...)
|
||||
{
|
||||
}
|
||||
|
||||
beast::websocket::stream<socket_type>&
|
||||
ws()
|
||||
{
|
||||
return this->member;
|
||||
}
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** A synchronous WebSocket @b PortHandler which implements echo.
|
||||
|
||||
This is a port handler which accepts WebSocket upgrade HTTP
|
||||
requests and implements the echo protocol. All received
|
||||
WebSocket messages will be echoed back to the remote host.
|
||||
*/
|
||||
class ws_sync_port
|
||||
{
|
||||
// The type of the on_stream callback
|
||||
using on_new_stream_cb = std::function<
|
||||
void(beast::websocket::stream<socket_type>&)>;
|
||||
|
||||
server& instance_;
|
||||
std::ostream& log_;
|
||||
on_new_stream_cb cb_;
|
||||
|
||||
public:
|
||||
/** Constructor
|
||||
|
||||
@param instance The server instance which owns this port
|
||||
|
||||
@param log The stream to use for logging
|
||||
|
||||
@param cb A callback which will be invoked for every new
|
||||
WebSocket connection. This provides an opportunity to change
|
||||
the settings on the stream before it is used. The callback
|
||||
should have this equivalent signature:
|
||||
@code
|
||||
template<class NextLayer>
|
||||
void callback(beast::websocket::stream<NextLayer>&);
|
||||
@endcode
|
||||
In C++14 this can be accomplished with a generic lambda. In
|
||||
C++11 it will be necessary to write out a lambda manually,
|
||||
with a templated operator().
|
||||
*/
|
||||
template<class Callback>
|
||||
ws_sync_port(
|
||||
server& instance,
|
||||
std::ostream& log,
|
||||
Callback const& cb)
|
||||
: instance_(instance)
|
||||
, log_(log)
|
||||
, cb_(cb)
|
||||
{
|
||||
}
|
||||
|
||||
/** Accept a TCP/IP connection.
|
||||
|
||||
This function is called when the server has accepted an
|
||||
incoming connection.
|
||||
|
||||
@param sock The connected socket.
|
||||
|
||||
@param ep The endpoint of the remote host.
|
||||
*/
|
||||
void
|
||||
on_accept(socket_type&& sock, endpoint_type ep)
|
||||
{
|
||||
// Create our connection object and run it
|
||||
//
|
||||
std::make_shared<sync_ws_con_plain>(
|
||||
std::move(sock),
|
||||
"ws_sync_port",
|
||||
log_,
|
||||
instance_.next_id(),
|
||||
ep,
|
||||
cb_
|
||||
)->run();
|
||||
}
|
||||
|
||||
/** Accept a WebSocket upgrade request.
|
||||
|
||||
This is used to accept a connection that has already
|
||||
delivered the handshake.
|
||||
|
||||
@param stream The stream corresponding to the connection.
|
||||
|
||||
@param ep The remote endpoint.
|
||||
|
||||
@param req The upgrade request.
|
||||
*/
|
||||
template<class Body, class Fields>
|
||||
void
|
||||
accept(
|
||||
socket_type&& sock,
|
||||
endpoint_type ep,
|
||||
beast::http::request<Body, Fields>&& req)
|
||||
{
|
||||
// Create the connection object and run it,
|
||||
// transferring ownershop of the ugprade request.
|
||||
//
|
||||
std::make_shared<sync_ws_con_plain>(
|
||||
std::move(sock),
|
||||
"ws_sync_port",
|
||||
log_,
|
||||
instance_.next_id(),
|
||||
ep,
|
||||
cb_
|
||||
)->run(std::move(req));
|
||||
}
|
||||
};
|
||||
|
||||
} // framework
|
||||
|
||||
#endif
|
102
example/server-framework/ws_upgrade_service.hpp
Normal file
102
example/server-framework/ws_upgrade_service.hpp
Normal file
@@ -0,0 +1,102 @@
|
||||
//
|
||||
// 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_WS_UPGRADE_SERVICE_HPP
|
||||
#define BEAST_EXAMPLE_SERVER_WS_UPGRADE_SERVICE_HPP
|
||||
|
||||
#include "framework.hpp"
|
||||
|
||||
#include <beast/http/message.hpp>
|
||||
#include <beast/websocket/rfc6455.hpp>
|
||||
#include <memory>
|
||||
|
||||
namespace framework {
|
||||
|
||||
/** An HTTP service which transfers WebSocket upgrade request to another port handler.
|
||||
|
||||
@tparam PortHandler The type of port handler. The service will
|
||||
handle WebSocket Upgrade requests by transferring ownership
|
||||
of the stream and request to a port handler of this type.
|
||||
*/
|
||||
template<class PortHandler>
|
||||
class ws_upgrade_service
|
||||
{
|
||||
std::shared_ptr<PortHandler> handler_;
|
||||
|
||||
public:
|
||||
/** Constructor
|
||||
|
||||
@param handler A shared pointer to the @b PortHandler to
|
||||
handle WebSocket upgrade requests.
|
||||
*/
|
||||
explicit
|
||||
ws_upgrade_service(
|
||||
std::shared_ptr<PortHandler> handler)
|
||||
: handler_(std::move(handler))
|
||||
{
|
||||
}
|
||||
|
||||
/** Initialize the service.
|
||||
|
||||
This provides an opportunity for the service to perform
|
||||
initialization which may fail, while reporting an error
|
||||
code instead of throwing an exception from the constructor.
|
||||
*/
|
||||
void
|
||||
init(error_code& ec)
|
||||
{
|
||||
// This is required by the error_code specification
|
||||
//
|
||||
ec = {};
|
||||
}
|
||||
|
||||
/** Handle a WebSocket Upgrade request.
|
||||
|
||||
If the request is an upgrade request, ownership of the
|
||||
stream and request will be transferred to the corresponding
|
||||
WebSocket port handler.
|
||||
|
||||
@param stream The stream corresponding to the connection.
|
||||
|
||||
@param ep The remote endpoint associated with the stream.
|
||||
|
||||
@req The request to check.
|
||||
*/
|
||||
template<
|
||||
class Stream,
|
||||
class Body, class Fields,
|
||||
class Send>
|
||||
bool
|
||||
respond(
|
||||
Stream&& stream,
|
||||
endpoint_type const& ep,
|
||||
beast::http::request<Body, Fields>&& req,
|
||||
Send const&) const
|
||||
{
|
||||
// If its not an upgrade request, return `false`
|
||||
// to indicate that we are not handling it.
|
||||
//
|
||||
if(! beast::websocket::is_upgrade(req))
|
||||
return false;
|
||||
|
||||
// Its an ugprade request, so transfer ownership
|
||||
// of the stream and request to the port handler.
|
||||
//
|
||||
handler_->accept(
|
||||
std::move(stream),
|
||||
ep,
|
||||
std::move(req));
|
||||
|
||||
// Tell the service list that we handled the request.
|
||||
//
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
} // framework
|
||||
|
||||
#endif
|
@@ -1,15 +0,0 @@
|
||||
# Part of Beast
|
||||
|
||||
GroupSources(extras/beast extras)
|
||||
GroupSources(include/beast beast)
|
||||
|
||||
GroupSources(example/websocket-server "/")
|
||||
|
||||
add_executable (websocket-server
|
||||
${BEAST_INCLUDES}
|
||||
main.cpp
|
||||
websocket_async_echo_server.hpp
|
||||
websocket_sync_echo_server.hpp
|
||||
)
|
||||
|
||||
target_link_libraries(websocket-server Beast)
|
@@ -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 websocket-server :
|
||||
main.cpp
|
||||
;
|
@@ -1,75 +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 "websocket_async_echo_server.hpp"
|
||||
#include "websocket_sync_echo_server.hpp"
|
||||
#include <boost/asio/io_service.hpp>
|
||||
#include <boost/asio/signal_set.hpp>
|
||||
#include <iostream>
|
||||
|
||||
/// Block until SIGINT or SIGTERM is received.
|
||||
void
|
||||
sig_wait()
|
||||
{
|
||||
boost::asio::io_service ios;
|
||||
boost::asio::signal_set signals(
|
||||
ios, SIGINT, SIGTERM);
|
||||
signals.async_wait(
|
||||
[&](boost::system::error_code const&, int)
|
||||
{
|
||||
});
|
||||
ios.run();
|
||||
}
|
||||
|
||||
class set_stream_options
|
||||
{
|
||||
beast::websocket::permessage_deflate pmd_;
|
||||
|
||||
public:
|
||||
set_stream_options(set_stream_options const&) = default;
|
||||
|
||||
set_stream_options(
|
||||
beast::websocket::permessage_deflate const& pmd)
|
||||
: pmd_(pmd)
|
||||
{
|
||||
}
|
||||
|
||||
template<class NextLayer>
|
||||
void
|
||||
operator()(beast::websocket::stream<NextLayer>& ws) const
|
||||
{
|
||||
ws.auto_fragment(false);
|
||||
ws.set_option(pmd_);
|
||||
ws.read_message_max(64 * 1024 * 1024);
|
||||
}
|
||||
};
|
||||
|
||||
int main()
|
||||
{
|
||||
using namespace beast::websocket;
|
||||
using endpoint_type = boost::asio::ip::tcp::endpoint;
|
||||
using address_type = boost::asio::ip::address;
|
||||
|
||||
beast::error_code ec;
|
||||
|
||||
permessage_deflate pmd;
|
||||
pmd.client_enable = true;
|
||||
pmd.server_enable = true;
|
||||
pmd.compLevel = 3;
|
||||
|
||||
websocket::async_echo_server s1{&std::cout, 1};
|
||||
s1.on_new_stream(set_stream_options{pmd});
|
||||
s1.open(endpoint_type{
|
||||
address_type::from_string("127.0.0.1"), 6000 }, ec);
|
||||
|
||||
websocket::sync_echo_server s2{&std::cout};
|
||||
s2.on_new_stream(set_stream_options{pmd});
|
||||
s2.open(endpoint_type{
|
||||
address_type::from_string("127.0.0.1"), 6001 }, ec);
|
||||
|
||||
sig_wait();
|
||||
}
|
@@ -1,282 +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 WEBSOCKET_ASYNC_ECHO_SERVER_HPP
|
||||
#define WEBSOCKET_ASYNC_ECHO_SERVER_HPP
|
||||
|
||||
#include <beast/core/multi_buffer.hpp>
|
||||
#include <beast/websocket/stream.hpp>
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include <boost/optional.hpp>
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
namespace websocket {
|
||||
|
||||
/** Asynchronous WebSocket echo client/server
|
||||
*/
|
||||
class async_echo_server
|
||||
{
|
||||
public:
|
||||
using error_code = beast::error_code;
|
||||
using address_type = boost::asio::ip::address;
|
||||
using socket_type = boost::asio::ip::tcp::socket;
|
||||
using endpoint_type = boost::asio::ip::tcp::endpoint;
|
||||
|
||||
private:
|
||||
std::ostream* log_;
|
||||
boost::asio::io_service ios_;
|
||||
socket_type sock_;
|
||||
endpoint_type ep_;
|
||||
boost::asio::ip::tcp::acceptor acceptor_;
|
||||
std::vector<std::thread> thread_;
|
||||
boost::optional<boost::asio::io_service::work> work_;
|
||||
std::function<void(beast::websocket::stream<socket_type>&)> mod_;
|
||||
|
||||
public:
|
||||
async_echo_server(async_echo_server const&) = delete;
|
||||
async_echo_server& operator=(async_echo_server const&) = delete;
|
||||
|
||||
/** Constructor.
|
||||
|
||||
@param log A pointer to a stream to log to, or `nullptr`
|
||||
to disable logging.
|
||||
|
||||
@param threads The number of threads in the io_service.
|
||||
*/
|
||||
async_echo_server(std::ostream* log,
|
||||
std::size_t threads)
|
||||
: log_(log)
|
||||
, sock_(ios_)
|
||||
, acceptor_(ios_)
|
||||
, work_(ios_)
|
||||
{
|
||||
thread_.reserve(threads);
|
||||
for(std::size_t i = 0; i < threads; ++i)
|
||||
thread_.emplace_back(
|
||||
[&]{ ios_.run(); });
|
||||
}
|
||||
|
||||
/** Destructor.
|
||||
*/
|
||||
~async_echo_server()
|
||||
{
|
||||
work_ = boost::none;
|
||||
ios_.dispatch(
|
||||
[&]
|
||||
{
|
||||
error_code ec;
|
||||
acceptor_.close(ec);
|
||||
});
|
||||
for(auto& t : thread_)
|
||||
t.join();
|
||||
}
|
||||
|
||||
/** Return the listening endpoint.
|
||||
*/
|
||||
endpoint_type
|
||||
local_endpoint() const
|
||||
{
|
||||
return acceptor_.local_endpoint();
|
||||
}
|
||||
|
||||
/** Set a handler called for new streams.
|
||||
|
||||
This function is called for each new stream.
|
||||
It is used to set options for every connection.
|
||||
*/
|
||||
template<class F>
|
||||
void
|
||||
on_new_stream(F const& f)
|
||||
{
|
||||
mod_ = f;
|
||||
}
|
||||
|
||||
/** Open a listening port.
|
||||
|
||||
@param ep The address and port to bind to.
|
||||
|
||||
@param ec Set to the error, if any occurred.
|
||||
*/
|
||||
void
|
||||
open(endpoint_type const& ep, error_code& ec)
|
||||
{
|
||||
acceptor_.open(ep.protocol(), ec);
|
||||
if(ec)
|
||||
return fail("open", ec);
|
||||
acceptor_.set_option(
|
||||
boost::asio::socket_base::reuse_address{true});
|
||||
acceptor_.bind(ep, ec);
|
||||
if(ec)
|
||||
return fail("bind", ec);
|
||||
acceptor_.listen(
|
||||
boost::asio::socket_base::max_connections, ec);
|
||||
if(ec)
|
||||
return fail("listen", ec);
|
||||
acceptor_.async_accept(sock_, ep_,
|
||||
std::bind(&async_echo_server::on_accept, this,
|
||||
std::placeholders::_1));
|
||||
}
|
||||
|
||||
private:
|
||||
class peer
|
||||
{
|
||||
struct data
|
||||
{
|
||||
async_echo_server& server;
|
||||
endpoint_type ep;
|
||||
int state = 0;
|
||||
beast::websocket::stream<socket_type> ws;
|
||||
boost::asio::io_service::strand strand;
|
||||
beast::multi_buffer db;
|
||||
std::size_t id;
|
||||
|
||||
data(async_echo_server& server_,
|
||||
endpoint_type const& ep_,
|
||||
socket_type&& sock_)
|
||||
: server(server_)
|
||||
, ep(ep_)
|
||||
, ws(std::move(sock_))
|
||||
, strand(ws.get_io_service())
|
||||
, id([]
|
||||
{
|
||||
static std::atomic<std::size_t> n{0};
|
||||
return ++n;
|
||||
}())
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
// VFALCO This could be unique_ptr in [Net.TS]
|
||||
std::shared_ptr<data> d_;
|
||||
|
||||
public:
|
||||
peer(peer&&) = default;
|
||||
peer(peer const&) = default;
|
||||
peer& operator=(peer&&) = delete;
|
||||
peer& operator=(peer const&) = delete;
|
||||
|
||||
template<class... Args>
|
||||
explicit
|
||||
peer(async_echo_server& server,
|
||||
endpoint_type const& ep, socket_type&& sock,
|
||||
Args&&... args)
|
||||
: d_(std::make_shared<data>(server, ep,
|
||||
std::forward<socket_type>(sock),
|
||||
std::forward<Args>(args)...))
|
||||
{
|
||||
auto& d = *d_;
|
||||
d.server.mod_(d.ws);
|
||||
run();
|
||||
}
|
||||
|
||||
void run()
|
||||
{
|
||||
auto& d = *d_;
|
||||
d.ws.async_accept_ex(
|
||||
[](beast::websocket::response_type& res)
|
||||
{
|
||||
res.insert(
|
||||
"Server", "async_echo_server");
|
||||
},
|
||||
std::move(*this));
|
||||
}
|
||||
|
||||
void operator()(error_code ec, std::size_t)
|
||||
{
|
||||
(*this)(ec);
|
||||
}
|
||||
|
||||
void operator()(error_code ec)
|
||||
{
|
||||
using boost::asio::buffer;
|
||||
using boost::asio::buffer_copy;
|
||||
auto& d = *d_;
|
||||
switch(d.state)
|
||||
{
|
||||
// did accept
|
||||
case 0:
|
||||
if(ec)
|
||||
return fail("async_accept", ec);
|
||||
|
||||
// start
|
||||
case 1:
|
||||
if(ec)
|
||||
return fail("async_handshake", ec);
|
||||
d.db.consume(d.db.size());
|
||||
// read message
|
||||
d.state = 2;
|
||||
d.ws.async_read(d.db,
|
||||
d.strand.wrap(std::move(*this)));
|
||||
return;
|
||||
|
||||
// got message
|
||||
case 2:
|
||||
if(ec == beast::websocket::error::closed)
|
||||
return;
|
||||
if(ec)
|
||||
return fail("async_read", ec);
|
||||
// write message
|
||||
d.state = 1;
|
||||
d.ws.binary(d.ws.got_binary());
|
||||
d.ws.async_write(d.db.data(),
|
||||
d.strand.wrap(std::move(*this)));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void
|
||||
fail(std::string what, error_code ec)
|
||||
{
|
||||
auto& d = *d_;
|
||||
if(d.server.log_)
|
||||
if(ec != beast::websocket::error::closed)
|
||||
d.server.fail("[#" + std::to_string(d.id) +
|
||||
" " + boost::lexical_cast<std::string>(d.ep) +
|
||||
"] " + what, ec);
|
||||
}
|
||||
};
|
||||
|
||||
void
|
||||
fail(std::string what, error_code ec)
|
||||
{
|
||||
if(log_)
|
||||
{
|
||||
static std::mutex m;
|
||||
std::lock_guard<std::mutex> lock{m};
|
||||
(*log_) << what << ": " <<
|
||||
ec.message() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
on_accept(error_code ec)
|
||||
{
|
||||
if(! acceptor_.is_open())
|
||||
return;
|
||||
if(ec == boost::asio::error::operation_aborted)
|
||||
return;
|
||||
if(ec)
|
||||
fail("accept", ec);
|
||||
peer{*this, ep_, std::move(sock_)};
|
||||
acceptor_.async_accept(sock_, ep_,
|
||||
std::bind(&async_echo_server::on_accept, this,
|
||||
std::placeholders::_1));
|
||||
}
|
||||
};
|
||||
|
||||
} // websocket
|
||||
|
||||
#endif
|
@@ -1,231 +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 WEBSOCKET_SYNC_ECHO_SERVER_HPP
|
||||
#define WEBSOCKET_SYNC_ECHO_SERVER_HPP
|
||||
|
||||
#include <beast/core/multi_buffer.hpp>
|
||||
#include <beast/websocket.hpp>
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include <boost/optional.hpp>
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
namespace websocket {
|
||||
|
||||
/** Synchronous WebSocket echo client/server
|
||||
*/
|
||||
class sync_echo_server
|
||||
{
|
||||
public:
|
||||
using error_code = beast::error_code;
|
||||
using endpoint_type = boost::asio::ip::tcp::endpoint;
|
||||
using address_type = boost::asio::ip::address;
|
||||
using socket_type = boost::asio::ip::tcp::socket;
|
||||
|
||||
private:
|
||||
std::ostream* log_;
|
||||
boost::asio::io_service ios_;
|
||||
socket_type sock_;
|
||||
endpoint_type ep_;
|
||||
boost::asio::ip::tcp::acceptor acceptor_;
|
||||
std::thread thread_;
|
||||
std::function<void(beast::websocket::stream<socket_type>&)> mod_;
|
||||
|
||||
public:
|
||||
/** Constructor.
|
||||
|
||||
@param log A pointer to a stream to log to, or `nullptr`
|
||||
to disable logging.
|
||||
*/
|
||||
sync_echo_server(std::ostream* log)
|
||||
: log_(log)
|
||||
, sock_(ios_)
|
||||
, acceptor_(ios_)
|
||||
{
|
||||
}
|
||||
|
||||
/** Destructor.
|
||||
*/
|
||||
~sync_echo_server()
|
||||
{
|
||||
if(thread_.joinable())
|
||||
{
|
||||
error_code ec;
|
||||
ios_.dispatch(
|
||||
[&]{ acceptor_.close(ec); });
|
||||
thread_.join();
|
||||
}
|
||||
}
|
||||
|
||||
/** Return the listening endpoint.
|
||||
*/
|
||||
endpoint_type
|
||||
local_endpoint() const
|
||||
{
|
||||
return acceptor_.local_endpoint();
|
||||
}
|
||||
|
||||
/** Set a handler called for new streams.
|
||||
|
||||
This function is called for each new stream.
|
||||
It is used to set options for every connection.
|
||||
*/
|
||||
template<class F>
|
||||
void
|
||||
on_new_stream(F const& f)
|
||||
{
|
||||
mod_ = f;
|
||||
}
|
||||
|
||||
/** Open a listening port.
|
||||
|
||||
@param ep The address and port to bind to.
|
||||
|
||||
@param ec Set to the error, if any occurred.
|
||||
*/
|
||||
void
|
||||
open(endpoint_type const& ep, error_code& ec)
|
||||
{
|
||||
acceptor_.open(ep.protocol(), ec);
|
||||
if(ec)
|
||||
return fail("open", ec);
|
||||
acceptor_.set_option(
|
||||
boost::asio::socket_base::reuse_address{true});
|
||||
acceptor_.bind(ep, ec);
|
||||
if(ec)
|
||||
return fail("bind", ec);
|
||||
acceptor_.listen(
|
||||
boost::asio::socket_base::max_connections, ec);
|
||||
if(ec)
|
||||
return fail("listen", ec);
|
||||
acceptor_.async_accept(sock_, ep_,
|
||||
std::bind(&sync_echo_server::on_accept, this,
|
||||
std::placeholders::_1));
|
||||
thread_ = std::thread{[&]{ ios_.run(); }};
|
||||
}
|
||||
|
||||
private:
|
||||
void
|
||||
fail(std::string what, error_code ec)
|
||||
{
|
||||
if(log_)
|
||||
{
|
||||
static std::mutex m;
|
||||
std::lock_guard<std::mutex> lock{m};
|
||||
(*log_) << what << ": " <<
|
||||
ec.message() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
fail(std::string what, error_code ec,
|
||||
int id, endpoint_type const& ep)
|
||||
{
|
||||
if(log_)
|
||||
if(ec != beast::websocket::error::closed)
|
||||
fail("[#" + std::to_string(id) + " " +
|
||||
boost::lexical_cast<std::string>(ep) +
|
||||
"] " + what, ec);
|
||||
}
|
||||
|
||||
void
|
||||
on_accept(error_code ec)
|
||||
{
|
||||
if(ec == boost::asio::error::operation_aborted)
|
||||
return;
|
||||
if(ec)
|
||||
return fail("accept", ec);
|
||||
struct lambda
|
||||
{
|
||||
std::size_t id;
|
||||
endpoint_type ep;
|
||||
sync_echo_server& self;
|
||||
boost::asio::io_service::work work;
|
||||
// Must be destroyed before work otherwise the
|
||||
// io_service could be destroyed before the socket.
|
||||
socket_type sock;
|
||||
|
||||
lambda(sync_echo_server& self_,
|
||||
endpoint_type const& ep_,
|
||||
socket_type&& sock_)
|
||||
: id([]
|
||||
{
|
||||
static std::atomic<std::size_t> n{0};
|
||||
return ++n;
|
||||
}())
|
||||
, ep(ep_)
|
||||
, self(self_)
|
||||
, work(sock_.get_io_service())
|
||||
, sock(std::move(sock_))
|
||||
{
|
||||
}
|
||||
|
||||
void operator()()
|
||||
{
|
||||
self.do_peer(id, ep, std::move(sock));
|
||||
}
|
||||
};
|
||||
std::thread{lambda{*this, ep_, std::move(sock_)}}.detach();
|
||||
acceptor_.async_accept(sock_, ep_,
|
||||
std::bind(&sync_echo_server::on_accept, this,
|
||||
std::placeholders::_1));
|
||||
}
|
||||
|
||||
void
|
||||
do_peer(std::size_t id,
|
||||
endpoint_type const& ep, socket_type&& sock)
|
||||
{
|
||||
using boost::asio::buffer;
|
||||
using boost::asio::buffer_copy;
|
||||
beast::websocket::stream<
|
||||
socket_type> ws{std::move(sock)};
|
||||
mod_(ws);
|
||||
error_code ec;
|
||||
ws.accept_ex(
|
||||
[](beast::websocket::response_type& res)
|
||||
{
|
||||
res.insert(
|
||||
"Server", "sync_echo_server");
|
||||
},
|
||||
ec);
|
||||
if(ec)
|
||||
{
|
||||
fail("accept", ec, id, ep);
|
||||
return;
|
||||
}
|
||||
for(;;)
|
||||
{
|
||||
beast::multi_buffer b;
|
||||
ws.read(b, ec);
|
||||
if(ec)
|
||||
{
|
||||
auto const s = ec.message();
|
||||
break;
|
||||
}
|
||||
ws.binary(ws.got_binary());
|
||||
ws.write(b.data(), ec);
|
||||
if(ec)
|
||||
break;
|
||||
}
|
||||
if(ec && ec != beast::websocket::error::closed)
|
||||
{
|
||||
fail("read", ec, id, ep);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // websocket
|
||||
|
||||
#endif
|
@@ -1,5 +1,11 @@
|
||||
# Part of Beast
|
||||
|
||||
add_subdirectory (core)
|
||||
add_subdirectory (http)
|
||||
add_subdirectory (server)
|
||||
add_subdirectory (websocket)
|
||||
add_subdirectory (zlib)
|
||||
|
||||
GroupSources(extras/beast extras)
|
||||
GroupSources(include/beast beast)
|
||||
GroupSources(test "/")
|
||||
|
@@ -14,6 +14,8 @@ compile version.cpp : : ;
|
||||
compile websocket.cpp : : ;
|
||||
compile zlib.cpp : : ;
|
||||
|
||||
build-project server ;
|
||||
|
||||
unit-test core-tests :
|
||||
../extras/beast/unit_test/main.cpp
|
||||
core/async_result.cpp
|
||||
|
@@ -6,7 +6,7 @@
|
||||
//
|
||||
|
||||
#include "example/doc/http_examples.hpp"
|
||||
#include "example/http-server/file_body.hpp"
|
||||
#include "example/server-framework/file_body.hpp"
|
||||
|
||||
#include <beast/core/read_size.hpp>
|
||||
#include <beast/core/detail/clamp.hpp>
|
||||
|
28
test/server/CMakeLists.txt
Normal file
28
test/server/CMakeLists.txt
Normal file
@@ -0,0 +1,28 @@
|
||||
# Part of Beast
|
||||
|
||||
GroupSources(example/server-framework framework)
|
||||
GroupSources(include/beast beast)
|
||||
|
||||
GroupSources(test/server "/")
|
||||
|
||||
add_executable (server-test
|
||||
${BEAST_INCLUDES}
|
||||
${SERVER_INCLUDES}
|
||||
file_body.cpp
|
||||
file_service.cpp
|
||||
framework.cpp
|
||||
http_async_port.cpp
|
||||
http_base.cpp
|
||||
http_sync_port.cpp
|
||||
main.cpp
|
||||
rfc7231.cpp
|
||||
server.cpp
|
||||
service_list.cpp
|
||||
write_msg.cpp
|
||||
ws_async_port.cpp
|
||||
ws_sync_port.cpp
|
||||
ws_upgrade_service.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(server-test Beast)
|
||||
|
23
test/server/Jamfile
Normal file
23
test/server/Jamfile
Normal file
@@ -0,0 +1,23 @@
|
||||
#
|
||||
# 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 server-test :
|
||||
file_body.cpp
|
||||
file_service.cpp
|
||||
framework.cpp
|
||||
http_async_port.cpp
|
||||
http_base.cpp
|
||||
http_sync_port.cpp
|
||||
main.cpp
|
||||
rfc7231.cpp
|
||||
server.cpp
|
||||
service_list.cpp
|
||||
write_msg.cpp
|
||||
ws_async_port.cpp
|
||||
ws_sync_port.cpp
|
||||
ws_upgrade_service.cpp
|
||||
;
|
10
test/server/file_body.cpp
Normal file
10
test/server/file_body.cpp
Normal file
@@ -0,0 +1,10 @@
|
||||
//
|
||||
// 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)
|
||||
//
|
||||
|
||||
// Test that header file is self-contained.
|
||||
#include "../../example/server-framework/file_body.hpp"
|
||||
|
10
test/server/file_service.cpp
Normal file
10
test/server/file_service.cpp
Normal file
@@ -0,0 +1,10 @@
|
||||
//
|
||||
// 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)
|
||||
//
|
||||
|
||||
// Test that header file is self-contained.
|
||||
#include "../../example/server-framework/file_service.hpp"
|
||||
|
10
test/server/framework.cpp
Normal file
10
test/server/framework.cpp
Normal file
@@ -0,0 +1,10 @@
|
||||
//
|
||||
// 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)
|
||||
//
|
||||
|
||||
// Test that header file is self-contained.
|
||||
#include "../../example/server-framework/framework.hpp"
|
||||
|
10
test/server/http_async_port.cpp
Normal file
10
test/server/http_async_port.cpp
Normal file
@@ -0,0 +1,10 @@
|
||||
//
|
||||
// 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)
|
||||
//
|
||||
|
||||
// Test that header file is self-contained.
|
||||
#include "../../example/server-framework/http_async_port.hpp"
|
||||
|
10
test/server/http_base.cpp
Normal file
10
test/server/http_base.cpp
Normal file
@@ -0,0 +1,10 @@
|
||||
//
|
||||
// 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)
|
||||
//
|
||||
|
||||
// Test that header file is self-contained.
|
||||
#include "../../example/server-framework/http_base.hpp"
|
||||
|
10
test/server/http_sync_port.cpp
Normal file
10
test/server/http_sync_port.cpp
Normal file
@@ -0,0 +1,10 @@
|
||||
//
|
||||
// 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)
|
||||
//
|
||||
|
||||
// Test that header file is self-contained.
|
||||
#include "../../example/server-framework/http_sync_port.hpp"
|
||||
|
10
test/server/main.cpp
Normal file
10
test/server/main.cpp
Normal file
@@ -0,0 +1,10 @@
|
||||
//
|
||||
// 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)
|
||||
//
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
}
|
10
test/server/rfc7231.cpp
Normal file
10
test/server/rfc7231.cpp
Normal file
@@ -0,0 +1,10 @@
|
||||
//
|
||||
// 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)
|
||||
//
|
||||
|
||||
// Test that header file is self-contained.
|
||||
#include "../../example/server-framework/rfc7231.hpp"
|
||||
|
10
test/server/server.cpp
Normal file
10
test/server/server.cpp
Normal file
@@ -0,0 +1,10 @@
|
||||
//
|
||||
// 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)
|
||||
//
|
||||
|
||||
// Test that header file is self-contained.
|
||||
#include "../../example/server-framework/server.hpp"
|
||||
|
10
test/server/service_list.cpp
Normal file
10
test/server/service_list.cpp
Normal file
@@ -0,0 +1,10 @@
|
||||
//
|
||||
// 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)
|
||||
//
|
||||
|
||||
// Test that header file is self-contained.
|
||||
#include "../../example/server-framework/service_list.hpp"
|
||||
|
10
test/server/write_msg.cpp
Normal file
10
test/server/write_msg.cpp
Normal file
@@ -0,0 +1,10 @@
|
||||
//
|
||||
// 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)
|
||||
//
|
||||
|
||||
// Test that header file is self-contained.
|
||||
#include "../../example/server-framework/write_msg.hpp"
|
||||
|
10
test/server/ws_async_port.cpp
Normal file
10
test/server/ws_async_port.cpp
Normal file
@@ -0,0 +1,10 @@
|
||||
//
|
||||
// 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)
|
||||
//
|
||||
|
||||
// Test that header file is self-contained.
|
||||
#include "../../example/server-framework/ws_async_port.hpp"
|
||||
|
10
test/server/ws_sync_port.cpp
Normal file
10
test/server/ws_sync_port.cpp
Normal file
@@ -0,0 +1,10 @@
|
||||
//
|
||||
// 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)
|
||||
//
|
||||
|
||||
// Test that header file is self-contained.
|
||||
#include "../../example/server-framework/ws_sync_port.hpp"
|
||||
|
10
test/server/ws_upgrade_service.cpp
Normal file
10
test/server/ws_upgrade_service.cpp
Normal file
@@ -0,0 +1,10 @@
|
||||
//
|
||||
// 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)
|
||||
//
|
||||
|
||||
// Test that header file is self-contained.
|
||||
#include "../../example/server-framework/ws_upgrade_service.hpp"
|
||||
|
Reference in New Issue
Block a user