Files
boost_beast/doc/websocket.qbk
T
Vinnie Falco 75b0571e83 Refactor type_traits (API Change):
fix #373

* concepts.hpp is renamed to type_traits.hpp

* Body reader and writer concepts are renamed
2017-07-20 08:12:17 -07:00

652 lines
23 KiB
Plaintext

[/
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)
]
[section:websocket WebSocket]
[block '''
<informaltable frame="all"><tgroup cols="1"><colspec colname="a"/><tbody><row><entry valign="top"><simplelist>
<member><link linkend="beast.websocket.creation">Creation</link></member>
<member><link linkend="beast.websocket.connections">Making connections</link></member>
<member><link linkend="beast.websocket.handshaking">Handshaking</link></member>
<member><link linkend="beast.websocket.accepting">Accepting</link></member>
<member><link linkend="beast.websocket.messages">Messages</link></member>
<member><link linkend="beast.websocket.control">Control Frames</link></member>
<member><link linkend="beast.websocket.notes">Notes</link></member>
</simplelist></entry></row></tbody></tgroup></informaltable>
''']
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 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]
[note
The following documentation assumes familiarity with both
Boost.Asio and the WebSocket protocol specification described in __rfc6455__.
]
[section:creation Creation]
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 [link beast.ref.streams.SyncStream [*SyncStream]] if synchronous
operations are performed, or
[link beast.ref.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
```
[warning
Initiating read and write operations on the next layer while
stream operations are being performed can break invariants, and
result in undefined behavior.
]
[endsect]
[section:connections 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());
}
```
When using SSL, which itself wraps a next layer object that is usualy a
TCP/IP socket, multiple calls to retrieve the next layer may be required:
```
beast::websocket::stream<boost::asio::ssl::stream<
boost::asio::ip::tcp::socket>> ws{ios, ctx};
// connect the underlying TCP/IP socket
ws.next_layer().next_layer().connect(ep);
// perform SSL handshake
ws.next_layer().handshake(boost::asio::ssl::stream_base::client);
// perform WebSocket handshake
ws.handshake("localhost", "/");
```
[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 target of the resource to request. The stream member functions
[link beast.ref.websocket__stream.handshake `handshake`] and
[link beast.ref.websocket__stream.async_handshake `async_handshake`]
are used to send the request with the required host and target strings.
```
beast::websocket::stream<boost::asio::ip::tcp::socket> ws{ios};
...
ws.set_option(beast::websocket::keep_alive(true));
ws.handshake("localhost", "/");
```
The implementation will create and send an HTTP request that typically
looks like this:
[table WebSocket Upgrade HTTP Request
[[Serialized Octets][Description]]
[[
```
GET / HTTP/1.1
Host: localhost
Upgrade: websocket
Connection: upgrade
Sec-WebSocket-Key: 2pGeTR0DsE4dfZs2pH+8MA==
Sec-WebSocket-Version: 13
User-Agent: Beast
```
][
The host and target parameters become part of the Host field
and request-target in the resulting HTTP request. The key is
generated by the implementation. Callers may add fields or
modify fields by providing a ['decorator], described below.
]]]
[heading Decorators]
If the caller wishes to add or modify fields, the member functions
[link beast.ref.websocket__stream.handshake_ex `handshake_ex`] and
[link beast.ref.websocket__stream.async_handshake_ex `async_handshake_ex`]
are provided which allow an additional function object, called a
['decorator], to be passed. The decorator is invoked to modify
the HTTP Upgrade request as needed. This example sets a subprotocol
on the request:
```
void decorate(beast::websocket::request_type& req)
{
req.fields.insert("Sec-WebSocket-Protocol", "xmpp;ws-chat");
}
...
ws.handshake_ex("localhost", "/", &decorate);
```
The HTTP Upgrade request produced by the previous call will look thusly:
[table Decorated WebSocket Upgrade HTTP Request
[[Serialized Octets][Description]]
[[
```
GET / HTTP/1.1
Host: localhost
Upgrade: websocket
Connection: upgrade
Sec-WebSocket-Key: 2pGeTR0DsE4dfZs2pH+8MA==
Sec-WebSocket-Version: 13
Sec-WebSocket-Protocol: xmpp;ws-chat
User-Agent: Beast
```
][
Undefined behavior results if the decorator modifies the fields
specific to perform the WebSocket Upgrade , such as the Upgrade
and Connection fields.
]]]
[heading Filtering]
When a client receives an HTTP Upgrade response from the server indicating
a successful upgrade, the caller may wish to perform additional validation
on the received HTTP response message. For example, to check that the
response to a basic authentication challenge is valid. To achieve this,
overloads of the handshake member function allow the caller to store the
received HTTP message in an output reference argument as
[link beast.ref.websocket__response_type `response_type`]
as follows:
```
beast::websocket::response_type res;
ws.handshake(res, "localhost", "/");
if(! res.fields.exists("Sec-WebSocket-Protocol"))
throw std::invalid_argument("missing subprotocols");
```
[endsect]
[section:accepting Accepting]
A [link beast.ref.websocket__stream `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();
```
Successful WebSocket Upgrade responses generated by the implementation will
typically look like this:
[table Decorated WebSocket Upgrade HTTP Request
[[Serialized Octets][Description]]
[[
```
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Server: Beast/40
```
][
The Sec-WebSocket-Accept field value is generated from the
request in a fashion specified by the WebSocket protocol.
]]]
[heading Decorators]
If the caller wishes to add or modify fields, the member functions
[link beast.ref.websocket__stream.accept_ex `accept_ex`] and
[link beast.ref.websocket__stream.async_accept_ex `async_accept_ex`]
are provided which allow an additional function object, called a
['decorator], to be passed. The decorator is invoked to modify
the HTTP Upgrade request as needed. This example sets the Server
field on the response:
```
ws.accept_ex(
[](beast::websocket::response_type& res)
{
res.fields.insert("Server", "AcmeServer");
});
```
The HTTP Upgrade response produced by the previous call will look thusly:
[table Decorated WebSocket Upgrade HTTP Request
[[Serialized Octets][Description]]
[[
```
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Server: AcmeServer
```
][
When the Upgrade request fails, the implementation will still invoke
the decorator to modify the response. In this case, the response
object will have a status code other than 101.
Undefined behavior results when the upgrade request is successful
and the decorator modifies the fields specific to perform the
WebSocket Upgrade , such as the Upgrade and Connection fields.
]]]
[heading Passing HTTP Requests]
When implementing an HTTP server that also supports WebSocket, the
server usually reads the HTTP request from the client. To detect when
the incoming HTTP request is a WebSocket Upgrade request, the function
[link beast.ref.websocket__is_upgrade `is_upgrade`] may be used.
Once the caller determines that the HTTP request is a WebSocket Upgrade,
additional overloads of
[link beast.ref.websocket__stream.accept `accept`],
[link beast.ref.websocket__stream.accept_ex `accept_ex`],
[link beast.ref.websocket__stream.async_accept `async_accept`], and
[link beast.ref.websocket__stream.async_accept_ex `async_accept_ex`]
are provided which receive the entire HTTP request header as an object
to perform the handshake. In this example, the request is first read
in using the HTTP algorithms, and then passed to a newly constructed
stream:
```
void handle_connection(boost::asio::ip::tcp::socket& sock)
{
beast::flat_buffer buffer;
beast::http::request<beast::http::string_body> req;
beast::http::read(sock, buffer, req);
if(beast::websocket::is_upgrade(req))
{
beast::websocket::stream<decltype(sock)> ws{std::move(sock)};
ws.accept(req);
}
}
```
[heading Buffered Handshakes]
Sometimes a server implementation wishes to read octets on the stream
in order to route the incoming request. For example, a server may read
the first 6 octets after accepting an incoming connection to determine
if a TLS protocol is being negotiated, and choose a suitable implementation
at run-time. In the case where the server wishes to accept the incoming
request as an HTTP WebSocket Upgrade request, additional overloads of
[link beast.ref.websocket__stream.accept `accept`],
[link beast.ref.websocket__stream.accept_ex `accept_ex`],
[link beast.ref.websocket__stream.async_accept `async_accept`], and
[link beast.ref.websocket__stream.async_accept_ex `async_accept_ex`]
are provided which receive the additional buffered octects and consume
them as part of the handshake.
In this example, the server reads the initial HTTP message into the
specified dynamic buffer as an octet sequence in the buffer's output
area, and later uses those octets to attempt an HTTP WebSocket Upgrade:
```
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());
...
}
```
[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::multi_buffer b;
beast::websocket::opcode::value op;
ws.read(op, b);
ws.set_option(beast::websocket::message_type{op});
ws.write(b.data());
sb.consume(b.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.
]
[heading 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::multi_buffer b;
beast::websocket::frame_info fi;
for(;;)
{
ws.read_frame(fi, b);
if(fi.fin)
break;
}
ws.set_option(beast::websocket::message_type{fi.op});
beast::consuming_buffers<
beast::multi_buffer::const_buffers_type> cb{b.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:control Control Frames]
Control frames are small (less than 128 bytes) messages entirely contained
in an individual WebSocket frame. They may be sent at any time by either
peer on an established connection, and can appear in between continuation
frames for a message. There are three types of control frames: ping, pong,
and close.
A sent ping indicates a request that the sender wants to receive a pong. A
pong is a response to a ping. Pongs may be sent unsolicited, at any time.
One use for an unsolicited pong is to inform the remote peer that the
session is still active after a long period of inactivity. A close frame
indicates that the remote peer wishes to close the WebSocket connection.
The connection is considered gracefully closed when each side has sent
and received a close frame.
During read operations, Beast automatically reads and processes control
frames. Pings are replied to as soon as possible with a pong, received
ping and pongs are delivered to the ping 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.
A consequence of this automatic behavior is that caller-initiated read
operations can cause socket writes. However, these writes will not
compete with caller-initiated write operations. For the purposes of
correctness with respect to the stream invariants, caller-initiated
read operations still only count as a read. This means that callers can
have a simultaneously active read, write, and ping operation in progress,
while the implementation also automatically handles control frames.
[heading Ping and Pong Frames]
Ping and pong messages are control frames which may be sent at any time
by either peer on an established WebSocket connection. They are sent
using the functions
[link beast.ref.websocket__stream.ping `ping`] and
[link beast.ref.websocket__stream.pong `pong`].
To be notified of ping and pong control frames, callers may register a
"ping callback" using [link beast.ref.websocket__stream.set_option `set_option`].
The object provided with this option should be callable with the following
signature:
```
void on_ping(bool is_pong, ping_data const& payload);
...
ws.set_option(ping_callback{&on_ping});
```
When a ping callback is registered, all pings and pongs received through
either synchronous read functions or asynchronous read functions will
invoke the ping callback, with the value of `is_pong` set to `true` if a
pong was received else `false` if a ping was received. The payload of
the ping or pong control frame is passed in the payload argument.
Unlike regular completion handlers used in calls to asynchronous initiation
functions, the ping callback only needs to be set once. The callback is not
reset when a ping or pong is received. The same callback is used for both
synchronous and asynchronous reads. The ping callback is passive; in order
to receive pings and pongs, a synchronous or asynchronous stream read
function must be active.
[note
When an asynchronous read function receives a ping or pong, the
ping callback is invoked in the same manner as that used to invoke
the final completion handler of the corresponding read function.
]
[heading Close Frames]
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
the [link beast.ref.websocket__stream.close `close`] function:
```
ws.close();
```
When the remote peer initiates a close by sending a close frame, Beast
will handle it for you by causing the next read to return `error::closed`.
When this error code is delivered, it indicates to the application that
the WebSocket connection has been closed cleanly, and that the TCP/IP
connection has been closed. After initiating a close, it is necessary to
continue reading messages until receiving the error `error::closed`. This
is because the remote peer may still be sending message and control frames
before it receives and responds to the close frame.
[important
To receive the [link beast.ref.websocket__error `error::closed`]
error, a read operation is required.
]
[heading Auto-fragment]
To ensure timely delivery of control frames, large messages can be broken up
into smaller sized frames. The automatic fragment option turns on this
feature, and the write buffer size option determines the maximum size of
the fragments:
```
...
ws.set_option(beast::websocket::auto_fragment{true});
ws.set_option(beast::websocket::write_buffer_size{16384});
```
[endsect]
[section:notes Notes]
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 __DynamicBuffer__. This concept is modeled on
[@http://www.boost.org/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.
[heading Asynchronous Operations]
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);
...
}
```
[heading The io_service]
The creation and operation of the
[@http://www.boost.org/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.
[heading 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]