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
|
* String comparisons are public interfaces
|
||||||
* Fix response message type in async websocket accept
|
* 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
|
${PROJECT_SOURCE_DIR}/extras/beast/*.ipp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
file(GLOB_RECURSE SERVER_INCLUDES
|
||||||
|
${PROJECT_SOURCE_DIR}/example/server-framework/*.hpp
|
||||||
|
)
|
||||||
|
|
||||||
add_subdirectory (test)
|
add_subdirectory (test)
|
||||||
add_subdirectory (test/core)
|
|
||||||
add_subdirectory (test/http)
|
|
||||||
add_subdirectory (test/websocket)
|
|
||||||
add_subdirectory (test/zlib)
|
|
||||||
|
|
||||||
add_subdirectory (example)
|
add_subdirectory (example)
|
||||||
|
|
||||||
|
@@ -79,7 +79,7 @@
|
|||||||
[import ../example/doc/http_examples.hpp]
|
[import ../example/doc/http_examples.hpp]
|
||||||
[import ../example/echo-op/echo_op.cpp]
|
[import ../example/echo-op/echo_op.cpp]
|
||||||
[import ../example/http-client/http_client.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 ../example/websocket-client/websocket_client.cpp]
|
||||||
|
|
||||||
[import ../test/core/doc_snippets.cpp]
|
[import ../test/core/doc_snippets.cpp]
|
||||||
|
@@ -42,9 +42,11 @@ standardized implementation of these protocols.
|
|||||||
|
|
||||||
[heading Requirements]
|
[heading Requirements]
|
||||||
|
|
||||||
This library is for programmers familiar with __Asio__. Users who
|
[important
|
||||||
wish to use asynchronous interfaces should already know how to
|
This library is for programmers familiar with __Asio__. Users who
|
||||||
create concurrent network programs using callbacks or coroutines.
|
wish to use asynchronous interfaces should already know how to
|
||||||
|
create concurrent network programs using callbacks or coroutines.
|
||||||
|
]
|
||||||
|
|
||||||
Beast requires:
|
Beast requires:
|
||||||
|
|
||||||
|
@@ -5,7 +5,8 @@
|
|||||||
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
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
|
These complete programs are intended to quickly impress upon readers the
|
||||||
flavor of the library. Source code and build scripts for them are located
|
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:
|
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]
|
[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:
|
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]
|
[example_websocket_client]
|
||||||
|
|
||||||
|
[endsect]
|
||||||
|
|
||||||
[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]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[heading Secure WebSocket]
|
[section WebSocket Client (with SSL)]
|
||||||
|
|
||||||
Establish a WebSocket connection over an encrypted TLS connection,
|
Establish a WebSocket connection over an encrypted TLS connection,
|
||||||
send a message and receive the reply. Requires OpenSSL to build.
|
send a message and receive the reply. Requires OpenSSL to build.
|
||||||
|
|
||||||
* [repo_file example/ssl/websocket_ssl_example.cpp]
|
* [repo_file example/ssl/websocket_ssl_example.cpp]
|
||||||
|
|
||||||
|
[endsect]
|
||||||
|
|
||||||
[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]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[heading HTTP Crawl]
|
[section Server Framework]
|
||||||
|
|
||||||
This example retrieves the page at each of the most popular domains
|
This is a complete program and framework of classes implementing
|
||||||
as measured by Alexa.
|
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]
|
[section Composed Operations]
|
||||||
|
|
||||||
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]
|
|
||||||
|
|
||||||
This program shows how to use Beast's network foundations to build a
|
This program shows how to use Beast's network foundations to build a
|
||||||
composable asynchronous initiation function with associated composed
|
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]
|
* [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
|
Here are all of the example functions and classes presented
|
||||||
throughout the documentation, they can be included and used
|
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]
|
* [repo_file example/doc/http_examples.hpp]
|
||||||
|
|
||||||
|
[endsect]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[endsect]
|
[endsect]
|
||||||
|
@@ -15,6 +15,13 @@
|
|||||||
left to the interfaces already existing on the underlying streams.
|
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
|
Throughout this documentation identifiers with the following names have
|
||||||
special meaning:
|
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]
|
[endsect]
|
||||||
|
@@ -85,7 +85,7 @@ format using __Asio__. Specifically, the library provides:
|
|||||||
[include 5_05_parser_streams.qbk]
|
[include 5_05_parser_streams.qbk]
|
||||||
[include 5_06_serializer_buffers.qbk]
|
[include 5_06_serializer_buffers.qbk]
|
||||||
[include 5_07_parser_buffers.qbk]
|
[include 5_07_parser_buffers.qbk]
|
||||||
[include 5_08_custom_parsers.qbk]
|
[include 5_08_custom_body.qbk]
|
||||||
[include 5_09_custom_body.qbk]
|
[include 5_09_custom_parsers.qbk]
|
||||||
|
|
||||||
[endsect]
|
[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 (echo-op)
|
||||||
add_subdirectory (http-client)
|
add_subdirectory (http-client)
|
||||||
add_subdirectory (http-crawl)
|
add_subdirectory (http-crawl)
|
||||||
add_subdirectory (http-server)
|
add_subdirectory (server-framework)
|
||||||
add_subdirectory (websocket-client)
|
add_subdirectory (websocket-client)
|
||||||
add_subdirectory (websocket-server)
|
|
||||||
|
@@ -8,6 +8,5 @@
|
|||||||
build-project echo-op ;
|
build-project echo-op ;
|
||||||
build-project http-client ;
|
build-project http-client ;
|
||||||
build-project http-crawl ;
|
build-project http-crawl ;
|
||||||
build-project http-server ;
|
build-project server-framework ;
|
||||||
build-project websocket-client ;
|
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)
|
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||||
//
|
//
|
||||||
|
|
||||||
#ifndef URLS_LARGE_DATA_H_INCLUDED
|
#ifndef BEAST_EXAMPLE_HTTP_CRAWL_URLS_LARGE_DATA_HPP
|
||||||
#define URLS_LARGE_DATA_H_INCLUDED
|
#define BEAST_EXAMPLE_HTTP_CRAWL_URLS_LARGE_DATA_HPP
|
||||||
|
|
||||||
#include <vector>
|
#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)
|
# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||||
#
|
#
|
||||||
|
|
||||||
exe http-server :
|
exe server-framework :
|
||||||
main.cpp
|
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)
|
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||||
//
|
//
|
||||||
|
|
||||||
#ifndef BEAST_EXAMPLE_FILE_BODY_H_INCLUDED
|
#ifndef BEAST_EXAMPLE_HTTP_SERVER_FILE_BODY_HPP
|
||||||
#define BEAST_EXAMPLE_FILE_BODY_H_INCLUDED
|
#define BEAST_EXAMPLE_HTTP_SERVER_FILE_BODY_HPP
|
||||||
|
|
||||||
#include <beast/core/error.hpp>
|
#include <beast/core/error.hpp>
|
||||||
#include <beast/http/message.hpp>
|
#include <beast/http/message.hpp>
|
||||||
@@ -18,11 +18,18 @@
|
|||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
namespace beast {
|
|
||||||
namespace http {
|
|
||||||
|
|
||||||
//[example_http_file_body_1
|
//[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
|
struct file_body
|
||||||
{
|
{
|
||||||
/** The type of the @ref message::body member.
|
/** The type of the @ref message::body member.
|
||||||
@@ -71,6 +78,7 @@ struct file_body
|
|||||||
|
|
||||||
//[example_http_file_body_2
|
//[example_http_file_body_2
|
||||||
|
|
||||||
|
inline
|
||||||
std::uint64_t
|
std::uint64_t
|
||||||
file_body::
|
file_body::
|
||||||
size(value_type const& v)
|
size(value_type const& v)
|
||||||
@@ -111,7 +119,7 @@ public:
|
|||||||
// always have the `file_body` as the body type.
|
// always have the `file_body` as the body type.
|
||||||
//
|
//
|
||||||
template<bool isRequest, class Fields>
|
template<bool isRequest, class Fields>
|
||||||
reader(message<isRequest, file_body, Fields> const& m);
|
reader(beast::http::message<isRequest, file_body, Fields> const& m);
|
||||||
|
|
||||||
// Destructor
|
// Destructor
|
||||||
~reader();
|
~reader();
|
||||||
@@ -120,7 +128,7 @@ public:
|
|||||||
// of the body is started.
|
// of the body is started.
|
||||||
//
|
//
|
||||||
void
|
void
|
||||||
init(error_code& ec);
|
init(beast::error_code& ec);
|
||||||
|
|
||||||
// This function is called zero or more times to
|
// This function is called zero or more times to
|
||||||
// retrieve buffers. A return value of `boost::none`
|
// retrieve buffers. A return value of `boost::none`
|
||||||
@@ -129,7 +137,7 @@ public:
|
|||||||
// to serialize, and a `bool` indicating whether
|
// to serialize, and a `bool` indicating whether
|
||||||
// or not there may be additional buffers.
|
// or not there may be additional buffers.
|
||||||
boost::optional<std::pair<const_buffers_type, bool>>
|
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.
|
// This function is called when reading is complete.
|
||||||
// It is an opportunity to perform any final actions
|
// It is an opportunity to perform any final actions
|
||||||
@@ -138,7 +146,7 @@ public:
|
|||||||
// destructors, since an exception thrown from there
|
// destructors, since an exception thrown from there
|
||||||
// would terminate the program.
|
// would terminate the program.
|
||||||
void
|
void
|
||||||
finish(error_code& ec);
|
finish(beast::error_code& ec);
|
||||||
};
|
};
|
||||||
|
|
||||||
//]
|
//]
|
||||||
@@ -151,7 +159,7 @@ public:
|
|||||||
//
|
//
|
||||||
template<bool isRequest, class Fields>
|
template<bool isRequest, class Fields>
|
||||||
file_body::reader::
|
file_body::reader::
|
||||||
reader(message<isRequest, file_body, Fields> const& m)
|
reader(beast::http::message<isRequest, file_body, Fields> const& m)
|
||||||
: path_(m.body)
|
: path_(m.body)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -164,7 +172,7 @@ reader(message<isRequest, file_body, Fields> const& m)
|
|||||||
inline
|
inline
|
||||||
void
|
void
|
||||||
file_body::reader::
|
file_body::reader::
|
||||||
init(error_code& ec)
|
init(beast::error_code& ec)
|
||||||
{
|
{
|
||||||
// Attempt to open the file for reading
|
// Attempt to open the file for reading
|
||||||
file_ = fopen(path_.string().c_str(), "rb");
|
file_ = fopen(path_.string().c_str(), "rb");
|
||||||
@@ -173,7 +181,7 @@ init(error_code& ec)
|
|||||||
{
|
{
|
||||||
// Convert the old-school `errno` into
|
// Convert the old-school `errno` into
|
||||||
// an error code using the system category.
|
// an error code using the system category.
|
||||||
ec = error_code{errno, system_category()};
|
ec = beast::error_code{errno, beast::system_category()};
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,7 +199,7 @@ init(error_code& ec)
|
|||||||
inline
|
inline
|
||||||
auto
|
auto
|
||||||
file_body::reader::
|
file_body::reader::
|
||||||
get(error_code& ec) ->
|
get(beast::error_code& ec) ->
|
||||||
boost::optional<std::pair<const_buffers_type, bool>>
|
boost::optional<std::pair<const_buffers_type, bool>>
|
||||||
{
|
{
|
||||||
// Calculate the smaller of our buffer size,
|
// Calculate the smaller of our buffer size,
|
||||||
@@ -212,7 +220,7 @@ get(error_code& ec) ->
|
|||||||
if(ferror(file_))
|
if(ferror(file_))
|
||||||
{
|
{
|
||||||
// Convert old-school `errno` to error_code
|
// Convert old-school `errno` to error_code
|
||||||
ec = error_code(errno, system_category());
|
ec = beast::error_code(errno, beast::system_category());
|
||||||
return boost::none;
|
return boost::none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,7 +250,7 @@ get(error_code& ec) ->
|
|||||||
inline
|
inline
|
||||||
void
|
void
|
||||||
file_body::reader::
|
file_body::reader::
|
||||||
finish(error_code& ec)
|
finish(beast::error_code& ec)
|
||||||
{
|
{
|
||||||
// Functions which pass back errors are
|
// Functions which pass back errors are
|
||||||
// responsible for clearing the error code.
|
// responsible for clearing the error code.
|
||||||
@@ -281,20 +289,20 @@ public:
|
|||||||
//
|
//
|
||||||
template<bool isRequest, class Fields>
|
template<bool isRequest, class Fields>
|
||||||
explicit
|
explicit
|
||||||
writer(message<isRequest, file_body, Fields>& m);
|
writer(beast::http::message<isRequest, file_body, Fields>& m);
|
||||||
|
|
||||||
// This function is called once before parsing
|
// This function is called once before parsing
|
||||||
// of the body is started.
|
// of the body is started.
|
||||||
//
|
//
|
||||||
void
|
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
|
// This function is called one or more times to store
|
||||||
// buffer sequences corresponding to the incoming body.
|
// buffer sequences corresponding to the incoming body.
|
||||||
//
|
//
|
||||||
template<class ConstBufferSequence>
|
template<class ConstBufferSequence>
|
||||||
void
|
void
|
||||||
put(ConstBufferSequence const& buffers, error_code& ec);
|
put(ConstBufferSequence const& buffers, beast::error_code& ec);
|
||||||
|
|
||||||
// This function is called when writing is complete.
|
// This function is called when writing is complete.
|
||||||
// It is an opportunity to perform any final actions
|
// It is an opportunity to perform any final actions
|
||||||
@@ -304,7 +312,7 @@ public:
|
|||||||
// would terminate the program.
|
// would terminate the program.
|
||||||
//
|
//
|
||||||
void
|
void
|
||||||
finish(error_code& ec);
|
finish(beast::error_code& ec);
|
||||||
|
|
||||||
// Destructor.
|
// Destructor.
|
||||||
//
|
//
|
||||||
@@ -320,7 +328,7 @@ public:
|
|||||||
// Just stash a reference to the path so we can open the file later.
|
// Just stash a reference to the path so we can open the file later.
|
||||||
template<bool isRequest, class Fields>
|
template<bool isRequest, class Fields>
|
||||||
file_body::writer::
|
file_body::writer::
|
||||||
writer(message<isRequest, file_body, Fields>& m)
|
writer(beast::http::message<isRequest, file_body, Fields>& m)
|
||||||
: path_(m.body)
|
: path_(m.body)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -334,7 +342,7 @@ writer(message<isRequest, file_body, Fields>& m)
|
|||||||
inline
|
inline
|
||||||
void
|
void
|
||||||
file_body::writer::
|
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
|
// Attempt to open the file for writing
|
||||||
file_ = fopen(path_.string().c_str(), "wb");
|
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
|
// Convert the old-school `errno` into
|
||||||
// an error code using the system category.
|
// an error code using the system category.
|
||||||
ec = error_code{errno, system_category()};
|
ec = beast::error_code{errno, beast::system_category()};
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -356,7 +364,7 @@ init(boost::optional<std::uint64_t> const& content_length, error_code& ec)
|
|||||||
template<class ConstBufferSequence>
|
template<class ConstBufferSequence>
|
||||||
void
|
void
|
||||||
file_body::writer::
|
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,
|
// Loop over all the buffers in the sequence,
|
||||||
// and write each one to the file.
|
// and write each one to the file.
|
||||||
@@ -372,7 +380,7 @@ put(ConstBufferSequence const& buffers, error_code& ec)
|
|||||||
if(ferror(file_))
|
if(ferror(file_))
|
||||||
{
|
{
|
||||||
// Convert old-school `errno` to error_code
|
// Convert old-school `errno` to error_code
|
||||||
ec = error_code(errno, system_category());
|
ec = beast::error_code(errno, beast::system_category());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -385,8 +393,10 @@ put(ConstBufferSequence const& buffers, error_code& ec)
|
|||||||
inline
|
inline
|
||||||
void
|
void
|
||||||
file_body::writer::
|
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 = {};
|
ec = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -405,7 +415,4 @@ file_body::writer::
|
|||||||
|
|
||||||
//]
|
//]
|
||||||
|
|
||||||
} // http
|
|
||||||
} // beast
|
|
||||||
|
|
||||||
#endif
|
#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
|
# Part of Beast
|
||||||
|
|
||||||
|
add_subdirectory (core)
|
||||||
|
add_subdirectory (http)
|
||||||
|
add_subdirectory (server)
|
||||||
|
add_subdirectory (websocket)
|
||||||
|
add_subdirectory (zlib)
|
||||||
|
|
||||||
GroupSources(extras/beast extras)
|
GroupSources(extras/beast extras)
|
||||||
GroupSources(include/beast beast)
|
GroupSources(include/beast beast)
|
||||||
GroupSources(test "/")
|
GroupSources(test "/")
|
||||||
|
@@ -14,6 +14,8 @@ compile version.cpp : : ;
|
|||||||
compile websocket.cpp : : ;
|
compile websocket.cpp : : ;
|
||||||
compile zlib.cpp : : ;
|
compile zlib.cpp : : ;
|
||||||
|
|
||||||
|
build-project server ;
|
||||||
|
|
||||||
unit-test core-tests :
|
unit-test core-tests :
|
||||||
../extras/beast/unit_test/main.cpp
|
../extras/beast/unit_test/main.cpp
|
||||||
core/async_result.cpp
|
core/async_result.cpp
|
||||||
|
@@ -6,7 +6,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
#include "example/doc/http_examples.hpp"
|
#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/read_size.hpp>
|
||||||
#include <beast/core/detail/clamp.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