mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-20 11:05:54 +00:00
beast doc/test work
This commit is contained in:
395
doc/websocket.qbk
Normal file
395
doc/websocket.qbk
Normal file
@@ -0,0 +1,395 @@
|
||||
[/
|
||||
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 (`asio_handler_invoke`),
|
||||
providing execution guarantees on composed operations in a manner
|
||||
identical to Boost.Asio. The implementation also uses handler allocation
|
||||
hooks (`asio_handler_allocate`) when allocating memory internally for
|
||||
composed operations.
|
||||
|
||||
There is no need for inheritance or virtual members in `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:creating Creating the socket]
|
||||
|
||||
The interface to Beast's WebSocket implementation is a single template
|
||||
class [link beast.ref.websocket__stream `websocket::stream`] which wraps a
|
||||
"next layer" object. The next layer object must meet the requirements of
|
||||
`SyncReadStream` and `SyncWriteStream` if synchronous operations are performed,
|
||||
`AsyncReadStream` and `AsyncWriteStream` is asynchronous operations are
|
||||
performed, or both. Arguments supplied during construction are passed to
|
||||
next layer's constructor. Here we declare two websockets which have ownership
|
||||
of the next layer:
|
||||
```
|
||||
io_service ios;
|
||||
websocket::stream<ip::tcp::socket> ws(ios);
|
||||
|
||||
ssl::context ctx(ssl::context::sslv23);
|
||||
websocket::stream<ssl::stream<ip::tcp::socket>> wss(ios, ctx);
|
||||
```
|
||||
|
||||
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:
|
||||
```
|
||||
tcp::socket&& sock;
|
||||
...
|
||||
websocket::stream<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:
|
||||
```
|
||||
tcp::socket sock;
|
||||
...
|
||||
websocket::stream<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.
|
||||
```
|
||||
ssl::context ctx(ssl::context::sslv23);
|
||||
websocket::stream<ssl::stream<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";
|
||||
io_service ios;
|
||||
tcp::resolver r(ios);
|
||||
websocket::stream<ip::tcp::socket> ws(ios);
|
||||
connect(ws.next_layer(), r.resolve(tcp::resolver::query{host, "ws"}));
|
||||
```
|
||||
|
||||
Accepting an incoming connection:
|
||||
```
|
||||
void do_accept(tcp::acceptor& acceptor)
|
||||
{
|
||||
websocket::stream<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. `hanshake` is used to send the
|
||||
request with the required host and resource strings.
|
||||
```
|
||||
websocket::stream<ip::tcp::socket> ws(ios);
|
||||
...
|
||||
ws.set_option(websocket::keep_alive(true));
|
||||
ws.handshake("ws.mywebapp.com:80", "/cgi-bin/bitcoin-prices");
|
||||
```
|
||||
|
||||
The `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:
|
||||
```
|
||||
websocket::stream<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(tcp::socket& sock)
|
||||
{
|
||||
boost::asio::streambuf sb;
|
||||
read_until(sock, sb, "\r\n\r\n");
|
||||
...
|
||||
websocket::stream<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(tcp::socket& sock)
|
||||
{
|
||||
boost::asio::streambuf sb;
|
||||
http::request<http::empty_body> request;
|
||||
http::read(sock, request);
|
||||
if(http::is_upgrade(request))
|
||||
{
|
||||
websocket::stream<ip::tcp::socket&> ws(sock);
|
||||
ws.accept(request);
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
[note Identifiers in the `http` namespace are part of Beast.HTTP. ]
|
||||
|
||||
[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(websocket::stream<ip::tcp::socket>& ws)
|
||||
{
|
||||
streambuf sb;
|
||||
websocket::opcode::value op;
|
||||
ws.read(sb);
|
||||
|
||||
ws.set_option(websocket::message_type(op));
|
||||
websocket::write(ws, sb.data());
|
||||
sb.consume(sb.size());
|
||||
}
|
||||
```
|
||||
|
||||
[important Calls to `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(websocket::stream<ip::tcp::socket>& ws)
|
||||
{
|
||||
streambuf sb;
|
||||
websocket::frame_info fi;
|
||||
for(;;)
|
||||
{
|
||||
ws.read_frame(fi, sb);
|
||||
if(fi.fin)
|
||||
break;
|
||||
}
|
||||
ws.set_option(websocket::message_type(fi.op));
|
||||
consuming_buffers<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 noted. The receipt of a close frame
|
||||
initiates the WebSocket close procedure, eventually resulting in the
|
||||
error code `websocket::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(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
|
||||
`websocket::stream::close`:
|
||||
```
|
||||
ws.close();
|
||||
```
|
||||
|
||||
[note To receive the `websocket::error::closed` error, a read operation
|
||||
is required. ]
|
||||
|
||||
[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 `Streambuf`. This concept is modeled on `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 io_service]
|
||||
|
||||
The creation and operation of the `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.WSProto itself does not use or require threads.
|
||||
|
||||
[endsect]
|
||||
|
||||
|
||||
|
||||
[section:safety Thread Safety]
|
||||
|
||||
Like a regular asio socket, a `websocket::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]
|
||||
Reference in New Issue
Block a user