mirror of
https://github.com/Xahau/xahaud.git
synced 2025-12-06 17:27:52 +00:00
This solves a problem where clang and gcc locate the deleted version of teardown and async_teardown instead of the overloaded version. It requires overloads to add `teardown_tag` into the signature so that the rules for argument dependent lookup can find the right function. Improve documentation of teardown requirements The documentation is updated to clearly explain the need for including <beast/websocket/ssl.hpp> to use SSL streams with WebSocket. The default implementations of teardown and async_teardown now use static_assert to alert the user of improper usage, with comments providing guidance for resolving the error.
453 lines
15 KiB
Plaintext
453 lines
15 KiB
Plaintext
[/
|
|
Copyright (c) 2013-2016 Vinnie Falco (vinnie dot falco at gmail dot com)
|
|
|
|
Distributed under the Boost Software License, Version 1.0. (See accompanying
|
|
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
|
]
|
|
|
|
[section:websocket WebSocket]
|
|
|
|
The WebSocket Protocol enables two-way communication between a client
|
|
running untrusted code in a controlled environment to a remote host that has
|
|
opted-in to communications from that code. The protocol consists of an opening
|
|
handshake followed by basic message framing, layered over TCP. The goal of
|
|
this technology is to provide a mechanism for browser-based applications that
|
|
need two-way communication with servers that does not rely on opening multiple
|
|
HTTP connections.
|
|
|
|
Beast.WebSocket provides developers with a robust WebSocket implementation
|
|
built on Boost.Asio with a consistent asynchronous model using a modern
|
|
C++ approach.
|
|
|
|
The WebSocket protocol is described fully in
|
|
[@https://tools.ietf.org/html/rfc6455 rfc6455]
|
|
|
|
|
|
|
|
[section:motivation Motivation]
|
|
|
|
Today's web applications increasingly rely on alternatives to standard HTTP
|
|
to achieve performance and/or responsiveness. While WebSocket implementations
|
|
are widely available in common web development languages such as Javascript,
|
|
good implementations in C++ are scarce. A survey of existing C++ WebSocket
|
|
solutions reveals interfaces which lack symmetry, impose performance penalties,
|
|
and needlessly restrict implementation strategies.
|
|
|
|
Beast.WebSocket is built on Boost.Asio, a robust cross platform networking
|
|
framework that is part of Boost and also offered as a standalone library.
|
|
A proposal to add networking functionality to the C++ standard library,
|
|
based on Boost.Asio, is under consideration by the standards committee.
|
|
Since the final approved networking interface for the C++ standard library
|
|
will likely closely resemble the current interface of Boost.Asio, it is
|
|
logical for Beast.WebSocket to use Boost.Asio as its network transport.
|
|
|
|
Beast.WebSocket takes advantage of Boost.Asio's extensible asynchronous
|
|
model, handler allocation, and handler invocation hooks. Calls to
|
|
Beast.WebSocket asynchronous initiation functions allow callers the choice
|
|
of using a completion handler, stackful or stackless coroutines, futures,
|
|
or user defined customizations (for example, Boost.Fiber). The
|
|
implementation uses handler invocation hooks
|
|
([@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/asio_handler_invoke.html `asio_handler_invoke`]),
|
|
providing execution guarantees on composed operations in a manner
|
|
identical to Boost.Asio. The implementation also uses handler allocation hooks
|
|
([@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/asio_handler_allocate.html `asio_handler_allocate`])
|
|
when allocating memory internally for composed operations.
|
|
|
|
There is no need for inheritance or virtual members in a
|
|
[link beast.ref.websocket__stream `beast::websocket::stream`].
|
|
All operations are templated and transparent to the compiler, allowing for
|
|
maximum inlining and optimization.
|
|
|
|
[note The documentation which follows assumes familiarity with
|
|
both Boost.Asio and the WebSocket protocol specification described in
|
|
[@https://tools.ietf.org/html/rfc6455 rfc6455] ]
|
|
|
|
[endsect]
|
|
|
|
|
|
|
|
[section:creation Creation]
|
|
|
|
The interface to Beast's WebSocket implementation is a single template
|
|
class [link beast.ref.websocket__stream `beast::websocket::stream`] which
|
|
wraps a "next layer" object. The next layer object must meet the requirements
|
|
of [link beast.types.streams.SyncStream [*`SyncReadStream`]] if synchronous
|
|
operations are performed, or
|
|
[link beast.types.streams.AsyncStream [*`AsyncStream`]] if asynchronous
|
|
operations are performed, or both. Arguments supplied during construction are
|
|
passed to next layer's constructor. Here we declare a websocket stream over
|
|
a TCP/IP socket with ownership of the socket:
|
|
```
|
|
boost::asio::io_service ios;
|
|
beast::websocket::stream<boost::asio::ip::tcp::socket> ws(ios);
|
|
```
|
|
|
|
[heading Using SSL]
|
|
|
|
To use WebSockets over SSL, choose an SSL stream for the next layer template
|
|
argument when constructing the stream.
|
|
```
|
|
#include <beast/websocket/ssl.hpp>
|
|
#include <beast/websocket.hpp>
|
|
#include <boost/asio/ssl.hpp>
|
|
|
|
boost::asio::io_service ios;
|
|
boost::asio::ssl::context ctx(boost::asio::ssl::context::sslv23);
|
|
beast::websocket::stream<boost::asio::ssl::stream<boost::asio::ip::tcp::socket> ws(ios, ctx);
|
|
```
|
|
|
|
[note
|
|
When creating websocket stream objects using SSL, it is necessary
|
|
to include the file `<beast/websocket/ssl.hpp>`.
|
|
]
|
|
|
|
[heading Non-owning references]
|
|
|
|
For servers that can handshake in multiple protocols, it may be desired
|
|
to wrap an object that already exists. This socket can be moved in:
|
|
```
|
|
boost::asio::ip::tcp::socket&& sock;
|
|
...
|
|
beast::websocket::stream<boost::asio::ip::tcp::socket> ws(std::move(sock));
|
|
```
|
|
|
|
Or, the wrapper can be constructed with a non-owning reference. In
|
|
this case, the caller is responsible for managing the lifetime of the
|
|
underlying socket being wrapped:
|
|
```
|
|
boost::asio::ip::tcp::socket sock;
|
|
...
|
|
beast::websocket::stream<boost::asio::ip::tcp::socket&> ws(sock);
|
|
```
|
|
|
|
The layer being wrapped can be accessed through the websocket's "next layer",
|
|
permitting callers to interact directly with its interface.
|
|
```
|
|
boost::asio::ssl::context ctx(boost::asio::ssl::context::sslv23);
|
|
beast::websocket::stream<boost::asio::ssl::stream<boost::asio::ip::tcp::socket>> ws(ios, ctx);
|
|
...
|
|
ws.next_layer().shutdown(); // ssl::stream shutdown
|
|
```
|
|
|
|
[important Initiating read and write operations on the next layer while
|
|
websocket operations are being performed can break invariants, and
|
|
result in undefined behavior. ]
|
|
|
|
[endsect]
|
|
|
|
|
|
|
|
[section:connecting Making connections]
|
|
|
|
Connections are established by using the interfaces which already exist
|
|
for the next layer. For example, making an outgoing connection:
|
|
```
|
|
std::string const host = "mywebapp.com";
|
|
boost::asio::io_service ios;
|
|
boost::asio::ip::tcp::resolver r(ios);
|
|
beast::websocket::stream<boost::asio::ip::tcp::socket> ws(ios);
|
|
boost::asio::connect(ws.next_layer(),
|
|
r.resolve(boost::asio::ip::tcp::resolver::query{host, "ws"}));
|
|
```
|
|
|
|
Accepting an incoming connection:
|
|
```
|
|
void do_accept(boost::asio::ip::tcp::acceptor& acceptor)
|
|
{
|
|
beast::websocket::stream<boost::asio::ip::tcp::socket> ws(acceptor.get_io_service());
|
|
acceptor.accept(ws.next_layer());
|
|
}
|
|
```
|
|
|
|
[note Examples use synchronous interfaces for clarity of exposition. ]
|
|
|
|
[endsect]
|
|
|
|
|
|
|
|
[section:handshaking Handshaking]
|
|
|
|
A WebSocket session begins when one side sends the HTTP Upgrade request
|
|
for websocket, and the other side sends an appropriate HTTP response
|
|
indicating that the request was accepted and that the connection has
|
|
been upgraded. The HTTP Upgrade request must include the Host HTTP field,
|
|
and the URI of the resource to request. `handshake` is used to send the
|
|
request with the required host and resource strings.
|
|
```
|
|
beast::websocket::stream<boost::asio::ip::tcp::socket> ws(ios);
|
|
...
|
|
ws.set_option(beast::websocket::keep_alive(true));
|
|
ws.handshake("ws.example.com:80", "/cgi-bin/bitcoin-prices");
|
|
```
|
|
|
|
The [link beast.ref.websocket__stream `beast::websocket::stream`] automatically
|
|
handles receiving and processing the HTTP response to the handshake request.
|
|
The call to handshake is successful if a HTTP response is received with the
|
|
101 "Switching Protocols" status code. On failure, an error is returned or an
|
|
exception is thrown. Depending on the keep alive setting, the socket may remain
|
|
open for a subsequent handshake attempt
|
|
|
|
Performing a handshake for an incoming websocket upgrade request operates
|
|
similarly. If the handshake fails, an error is returned or exception thrown:
|
|
```
|
|
beast::websocket::stream<boost::asio::ip::tcp::socket> ws(ios);
|
|
...
|
|
ws.accept();
|
|
```
|
|
|
|
Servers that can handshake in multiple protocols may have already read data
|
|
on the connection, or might have already received an entire HTTP request
|
|
containing the upgrade request. Overloads of `accept` allow callers to
|
|
pass in this additional buffered handshake data.
|
|
```
|
|
void do_accept(boost::asio::ip::tcp::socket& sock)
|
|
{
|
|
boost::asio::streambuf sb;
|
|
boost::asio::read_until(sock, sb, "\r\n\r\n");
|
|
...
|
|
beast::websocket::stream<boost::asio::ip::tcp::socket&> ws(sock);
|
|
ws.accept(sb.data());
|
|
...
|
|
}
|
|
```
|
|
|
|
Alternatively, the caller can pass an entire HTTP request if it was
|
|
obtained elsewhere:
|
|
```
|
|
void do_accept(boost::asio::ip::tcp::socket& sock)
|
|
{
|
|
boost::asio::streambuf sb;
|
|
beast::http::request<http::empty_body> request;
|
|
beast::http::read(sock, request);
|
|
if(beast::http::is_upgrade(request))
|
|
{
|
|
websocket::stream<ip::tcp::socket&> ws(sock);
|
|
ws.accept(request);
|
|
...
|
|
}
|
|
}
|
|
```
|
|
|
|
[endsect]
|
|
|
|
|
|
|
|
[section:messages Messages]
|
|
|
|
After the WebSocket handshake is accomplished, callers may send and receive
|
|
messages using the message oriented interface. This interface requires that
|
|
all of the buffers representing the message are known ahead of time:
|
|
```
|
|
void echo(beast::websocket::stream<boost::asio::ip::tcp::socket>& ws)
|
|
{
|
|
beast::streambuf sb;
|
|
beast::websocket::opcode::value op;
|
|
ws.read(sb);
|
|
|
|
ws.set_option(beast::websocket::message_type(op));
|
|
ws.write(sb.data());
|
|
sb.consume(sb.size());
|
|
}
|
|
```
|
|
|
|
[important Calls to [link beast.ref.websocket__stream.set_option `set_option`]
|
|
must be made from the same implicit or explicit strand as that used to perform
|
|
other operations. ]
|
|
|
|
[endsect]
|
|
|
|
|
|
|
|
[section:frames Frames]
|
|
|
|
Some use-cases make it impractical or impossible to buffer the entire
|
|
message ahead of time:
|
|
|
|
* Streaming multimedia to an endpoint.
|
|
* Sending a message that does not fit in memory at once.
|
|
* Providing incremental results as they become available.
|
|
|
|
For these cases, the frame oriented interface may be used. This
|
|
example reads and echoes a complete message using this interface:
|
|
```
|
|
void echo(beast::websocket::stream<boost::asio::ip::tcp::socket>& ws)
|
|
{
|
|
beast::streambuf sb;
|
|
beast::websocket::frame_info fi;
|
|
for(;;)
|
|
{
|
|
ws.read_frame(fi, sb);
|
|
if(fi.fin)
|
|
break;
|
|
}
|
|
ws.set_option(beast::websocket::message_type(fi.op));
|
|
beast::consuming_buffers<
|
|
beast::streambuf::const_buffers_type> cb(sb.data());
|
|
for(;;)
|
|
{
|
|
using boost::asio::buffer_size;
|
|
std::size_t size = std::min(buffer_size(cb));
|
|
if(size > 512)
|
|
{
|
|
ws.write_frame(false, beast::prepare_buffers(512, cb));
|
|
cb.consume(512);
|
|
}
|
|
else
|
|
{
|
|
ws.write_frame(true, cb);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
[endsect]
|
|
|
|
|
|
|
|
[section:controlframes Control frames]
|
|
|
|
During read operations, the implementation automatically reads and processes
|
|
WebSocket control frames such as ping, pong, and close. Pings are replied
|
|
to as soon as possible, pongs are delivered to the pong callback. The receipt
|
|
of a close frame initiates the WebSocket close procedure, eventually resulting
|
|
in the error code [link beast.ref.websocket__error `error::closed`] being
|
|
delivered to the caller in a subsequent read operation, assuming no other error
|
|
takes place.
|
|
|
|
To ensure timely delivery of control frames, large messages are broken up
|
|
into smaller sized frames. The implementation chooses the size and number
|
|
of the frames making up the message. The automatic fragment size option
|
|
gives callers control over the size of these frames:
|
|
```
|
|
...
|
|
ws.set_option(beast::websocket::auto_fragment_size(8192));
|
|
```
|
|
|
|
The WebSocket protocol defines a procedure and control message for initiating
|
|
a close of the session. Handling of close initiated by the remote end of the
|
|
connection is performed automatically. To manually initiate a close, use
|
|
[link beast.ref.websocket__stream.close `close`]:
|
|
```
|
|
ws.close();
|
|
```
|
|
|
|
[note To receive the [link beast.ref.websocket__error `error::closed`]
|
|
error, a read operation is required. ]
|
|
|
|
[endsect]
|
|
|
|
|
|
|
|
[section:pongs Pong messages]
|
|
|
|
To receive pong control frames, callers may register a "pong callback" using
|
|
[link beast.ref.websocket__stream.set_option `set_option`]:
|
|
|
|
the following signature:
|
|
```
|
|
void on_pong(ping_data const& payload);
|
|
...
|
|
ws.set_option(pong_callback{&on_pong});
|
|
```
|
|
|
|
When a pong callback is registered, any pongs received through either
|
|
synchronous read functions or asynchronous read functions will invoke the
|
|
pong callback, passing the payload in the pong message as the argument.
|
|
|
|
Unlike regular completion handlers used in calls to asynchronous initiation
|
|
functions, the pong callback only needs to be set once. The callback is not
|
|
reset when a pong is received. The same callback is used for both synchronous
|
|
and asynchronous reads. The pong callback is passive; in order to receive
|
|
pongs, a synchronous or asynchronous stream read function must be active.
|
|
|
|
[note When an asynchronous read function receives a pong, the the pong callback
|
|
is invoked in the same manner as that used to invoke the final completion
|
|
handler of the corresponding read function.]
|
|
|
|
[endsect]
|
|
|
|
|
|
|
|
[section:buffers Buffers]
|
|
|
|
Because calls to read data may return a variable amount of bytes, the
|
|
interface to calls that read data require an object that meets the requirements
|
|
of [link beast.types.DynamicBuffer [*`DynamicBuffer`]]. This concept is modeled on
|
|
[@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/basic_streambuf.html `boost::asio::basic_streambuf`].
|
|
|
|
The implementation does not perform queueing or buffering of messages. If
|
|
desired, these features should be provided by callers. The impact of this
|
|
design is that library users are in full control of the allocation strategy
|
|
used to store data and the back-pressure applied on the read and write side
|
|
of the underlying TCP/IP connection.
|
|
|
|
[endsect]
|
|
|
|
|
|
|
|
[section:async Asynchronous interface]
|
|
|
|
Asynchronous versions are available for all functions:
|
|
```
|
|
websocket::opcode op;
|
|
ws.async_read(op, sb,
|
|
[](boost::system::error_code const& ec)
|
|
{
|
|
...
|
|
});
|
|
```
|
|
|
|
Calls to asynchronous initiation functions support the extensible asynchronous
|
|
model developed by the Boost.Asio author, allowing for traditional completion
|
|
handlers, stackful or stackless coroutines, and even futures:
|
|
```
|
|
void echo(websocket::stream<ip::tcp::socket>& ws,
|
|
boost::asio::yield_context yield)
|
|
{
|
|
ws.async_read(sb, yield);
|
|
std::future<websocket::error_code> fut =
|
|
ws.async_write, sb.data(), boost::use_future);
|
|
...
|
|
}
|
|
```
|
|
|
|
[endsect]
|
|
|
|
|
|
|
|
[section:io_service The io_service]
|
|
|
|
The creation and operation of the
|
|
[@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/io_service.html `boost::asio::io_service`]
|
|
associated with the underlying stream is left to the callers, permitting any
|
|
implementation strategy including one that does not require threads for
|
|
environments where threads are unavailable. Beast.WebSocket itself does not
|
|
use or require threads.
|
|
|
|
[endsect]
|
|
|
|
|
|
|
|
[section:safety Thread Safety]
|
|
|
|
Like a regular asio socket, a [link beast.ref.websocket__stream `stream`] is
|
|
not thread safe. Callers are responsible for synchronizing operations on the
|
|
socket using an implicit or explicit strand, as per the Asio documentation.
|
|
The asynchronous interface supports one active read and one active write
|
|
simultaneously. Undefined behavior results if two or more reads or two or
|
|
more writes are attempted concurrently. Caller initiated WebSocket ping, pong,
|
|
and close operations each count as an active write.
|
|
|
|
The implementation uses composed asynchronous operations internally; a high
|
|
level read can cause both reads and writes to take place on the underlying
|
|
stream. This behavior is transparent to callers.
|
|
|
|
[endsect]
|
|
|
|
|
|
|
|
[endsect]
|
|
|
|
[include quickref.xml]
|