]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
1 | [/ |
2 | Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) | |
3 | ||
4 | Distributed under the Boost Software License, Version 1.0. (See accompanying | |
5 | file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | |
6 | ] | |
7 | ||
8 | [section:websocket WebSocket] | |
9 | ||
10 | [block ''' | |
11 | <informaltable frame="all"><tgroup cols="1"><colspec colname="a"/><tbody><row><entry valign="top"><simplelist> | |
12 | <member><link linkend="beast.websocket.creation">Creation</link></member> | |
13 | <member><link linkend="beast.websocket.connections">Making connections</link></member> | |
14 | <member><link linkend="beast.websocket.handshaking">Handshaking</link></member> | |
15 | <member><link linkend="beast.websocket.messages">Messages</link></member> | |
16 | <member><link linkend="beast.websocket.frames">Frames</link></member> | |
17 | <member><link linkend="beast.websocket.control">Control Frames</link></member> | |
18 | <member><link linkend="beast.websocket.buffers">Buffers</link></member> | |
19 | <member><link linkend="beast.websocket.async">Asynchronous interface</link></member> | |
20 | <member><link linkend="beast.websocket.io_service">The io_service</link></member> | |
21 | <member><link linkend="beast.websocket.threads">Thread Safety</link></member> | |
22 | </simplelist></entry></row></tbody></tgroup></informaltable> | |
23 | '''] | |
24 | ||
25 | The WebSocket Protocol enables two-way communication between a client | |
26 | running untrusted code in a controlled environment to a remote host that has | |
27 | opted-in to communications from that code. The protocol consists of an opening | |
28 | handshake followed by basic message framing, layered over TCP. The goal of | |
29 | this technology is to provide a mechanism for browser-based applications that | |
30 | need two-way communication with servers that does not rely on opening multiple | |
31 | HTTP connections. | |
32 | ||
33 | Beast.WebSocket provides developers with a robust WebSocket implementation | |
34 | built on Boost.Asio with a consistent asynchronous model using a modern | |
35 | C++ approach. | |
36 | ||
37 | The WebSocket protocol is described fully in | |
38 | [@https://tools.ietf.org/html/rfc6455 rfc6455] | |
39 | ||
40 | [note | |
41 | The following documentation assumes familiarity with both | |
42 | Boost.Asio and the WebSocket protocol specification described in __rfc6455__. | |
43 | ] | |
44 | ||
45 | ||
46 | ||
47 | ||
48 | [section:creation Creation] | |
49 | ||
50 | The interface to Beast's WebSocket implementation is a single template | |
51 | class [link beast.ref.websocket__stream `beast::websocket::stream`] which | |
52 | wraps a "next layer" object. The next layer object must meet the requirements | |
53 | of [link beast.ref.streams.SyncStream [*`SyncStream`]] if synchronous | |
54 | operations are performed, or | |
55 | [link beast.ref.streams.AsyncStream [*`AsyncStream`]] if asynchronous | |
56 | operations are performed, or both. Arguments supplied during construction are | |
57 | passed to next layer's constructor. Here we declare a websocket stream over | |
58 | a TCP/IP socket with ownership of the socket: | |
59 | ``` | |
60 | boost::asio::io_service ios; | |
61 | beast::websocket::stream<boost::asio::ip::tcp::socket> ws{ios}; | |
62 | ``` | |
63 | ||
64 | [heading Using SSL] | |
65 | ||
66 | To use WebSockets over SSL, choose an SSL stream for the next layer template | |
67 | argument when constructing the stream. | |
68 | ``` | |
69 | #include <beast/websocket/ssl.hpp> | |
70 | #include <beast/websocket.hpp> | |
71 | #include <boost/asio/ssl.hpp> | |
72 | ||
73 | boost::asio::io_service ios; | |
74 | boost::asio::ssl::context ctx{boost::asio::ssl::context::sslv23}; | |
75 | beast::websocket::stream<boost::asio::ssl::stream<boost::asio::ip::tcp::socket> ws{ios, ctx}; | |
76 | ``` | |
77 | ||
78 | [note | |
79 | When creating websocket stream objects using SSL, it is necessary | |
80 | to include the file `<beast/websocket/ssl.hpp>`. | |
81 | ] | |
82 | ||
83 | [heading Non-owning references] | |
84 | ||
85 | For servers that can handshake in multiple protocols, it may be desired | |
86 | to wrap an object that already exists. This socket can be moved in: | |
87 | ``` | |
88 | boost::asio::ip::tcp::socket&& sock; | |
89 | ... | |
90 | beast::websocket::stream<boost::asio::ip::tcp::socket> ws{std::move(sock)}; | |
91 | ``` | |
92 | ||
93 | Or, the wrapper can be constructed with a non-owning reference. In | |
94 | this case, the caller is responsible for managing the lifetime of the | |
95 | underlying socket being wrapped: | |
96 | ``` | |
97 | boost::asio::ip::tcp::socket sock; | |
98 | ... | |
99 | beast::websocket::stream<boost::asio::ip::tcp::socket&> ws{sock}; | |
100 | ``` | |
101 | ||
102 | The layer being wrapped can be accessed through the websocket's "next layer", | |
103 | permitting callers to interact directly with its interface. | |
104 | ``` | |
105 | boost::asio::ssl::context ctx{boost::asio::ssl::context::sslv23}; | |
106 | beast::websocket::stream<boost::asio::ssl::stream<boost::asio::ip::tcp::socket>> ws{ios, ctx}; | |
107 | ... | |
108 | ws.next_layer().shutdown(); // ssl::stream shutdown | |
109 | ``` | |
110 | ||
111 | [warning | |
112 | Initiating read and write operations on the next layer while | |
113 | stream operations are being performed can break invariants, and | |
114 | result in undefined behavior. | |
115 | ] | |
116 | ||
117 | [endsect] | |
118 | ||
119 | ||
120 | ||
121 | [section:connections Making connections] | |
122 | ||
123 | Connections are established by using the interfaces which already exist | |
124 | for the next layer. For example, making an outgoing connection: | |
125 | ``` | |
126 | std::string const host = "mywebapp.com"; | |
127 | boost::asio::io_service ios; | |
128 | boost::asio::ip::tcp::resolver r{ios}; | |
129 | beast::websocket::stream<boost::asio::ip::tcp::socket> ws{ios}; | |
130 | boost::asio::connect(ws.next_layer(), | |
131 | r.resolve(boost::asio::ip::tcp::resolver::query{host, "ws"})); | |
132 | ``` | |
133 | ||
134 | Accepting an incoming connection: | |
135 | ``` | |
136 | void do_accept(boost::asio::ip::tcp::acceptor& acceptor) | |
137 | { | |
138 | beast::websocket::stream<boost::asio::ip::tcp::socket> ws{acceptor.get_io_service()}; | |
139 | acceptor.accept(ws.next_layer()); | |
140 | } | |
141 | ``` | |
142 | ||
143 | [note | |
144 | Examples use synchronous interfaces for clarity of exposition. | |
145 | ] | |
146 | ||
147 | [endsect] | |
148 | ||
149 | ||
150 | ||
151 | [section:handshaking Handshaking] | |
152 | ||
153 | A WebSocket session begins when one side sends the HTTP Upgrade request | |
154 | for websocket, and the other side sends an appropriate HTTP response | |
155 | indicating that the request was accepted and that the connection has | |
156 | been upgraded. The HTTP Upgrade request must include the Host HTTP field, | |
157 | and the URI of the resource to request. | |
158 | [link beast.ref.websocket__stream.handshake `handshake`] is used to send the | |
159 | request with the required host and resource strings. | |
160 | ``` | |
161 | beast::websocket::stream<boost::asio::ip::tcp::socket> ws{ios}; | |
162 | ... | |
163 | ws.set_option(beast::websocket::keep_alive(true)); | |
164 | ws.handshake("ws.example.com:80", "/cgi-bin/bitcoin-prices"); | |
165 | ``` | |
166 | ||
167 | The [link beast.ref.websocket__stream `stream`] automatically | |
168 | handles receiving and processing the HTTP response to the handshake request. | |
169 | The call to handshake is successful if a HTTP response is received with the | |
170 | 101 "Switching Protocols" status code. On failure, an error is returned or an | |
171 | exception is thrown. Depending on the keep alive setting, the socket may remain | |
172 | open for a subsequent handshake attempt | |
173 | ||
174 | Performing a handshake for an incoming websocket upgrade request operates | |
175 | similarly. If the handshake fails, an error is returned or exception thrown: | |
176 | ``` | |
177 | beast::websocket::stream<boost::asio::ip::tcp::socket> ws{ios}; | |
178 | ... | |
179 | ws.accept(); | |
180 | ``` | |
181 | ||
182 | Servers that can handshake in multiple protocols may have already read data | |
183 | on the connection, or might have already received an entire HTTP request | |
184 | containing the upgrade request. Overloads of `accept` allow callers to | |
185 | pass in this additional buffered handshake data. | |
186 | ``` | |
187 | void do_accept(boost::asio::ip::tcp::socket& sock) | |
188 | { | |
189 | boost::asio::streambuf sb; | |
190 | boost::asio::read_until(sock, sb, "\r\n\r\n"); | |
191 | ... | |
192 | beast::websocket::stream<boost::asio::ip::tcp::socket&> ws{sock}; | |
193 | ws.accept(sb.data()); | |
194 | ... | |
195 | } | |
196 | ``` | |
197 | ||
198 | Alternatively, the caller can pass an entire HTTP request if it was | |
199 | obtained elsewhere: | |
200 | ``` | |
201 | void do_accept(boost::asio::ip::tcp::socket& sock) | |
202 | { | |
203 | boost::asio::streambuf sb; | |
204 | beast::http::request<http::empty_body> request; | |
205 | beast::http::read(sock, sb, request); | |
206 | if(beast::http::is_upgrade(request)) | |
207 | { | |
208 | websocket::stream<ip::tcp::socket&> ws{sock}; | |
209 | ws.accept(request); | |
210 | ... | |
211 | } | |
212 | } | |
213 | ``` | |
214 | ||
215 | [endsect] | |
216 | ||
217 | ||
218 | ||
219 | [section:messages Messages] | |
220 | ||
221 | After the WebSocket handshake is accomplished, callers may send and receive | |
222 | messages using the message oriented interface. This interface requires that | |
223 | all of the buffers representing the message are known ahead of time: | |
224 | ``` | |
225 | void echo(beast::websocket::stream<boost::asio::ip::tcp::socket>& ws) | |
226 | { | |
227 | beast::streambuf sb; | |
228 | beast::websocket::opcode::value op; | |
229 | ws.read(op, sb); | |
230 | ||
231 | ws.set_option(beast::websocket::message_type{op}); | |
232 | ws.write(sb.data()); | |
233 | sb.consume(sb.size()); | |
234 | } | |
235 | ``` | |
236 | ||
237 | [important | |
238 | Calls to [link beast.ref.websocket__stream.set_option `set_option`] | |
239 | must be made from the same implicit or explicit strand as that used | |
240 | to perform other operations. | |
241 | ] | |
242 | ||
243 | [endsect] | |
244 | ||
245 | ||
246 | ||
247 | [section:frames Frames] | |
248 | ||
249 | Some use-cases make it impractical or impossible to buffer the entire | |
250 | message ahead of time: | |
251 | ||
252 | * Streaming multimedia to an endpoint. | |
253 | * Sending a message that does not fit in memory at once. | |
254 | * Providing incremental results as they become available. | |
255 | ||
256 | For these cases, the frame oriented interface may be used. This | |
257 | example reads and echoes a complete message using this interface: | |
258 | ``` | |
259 | void echo(beast::websocket::stream<boost::asio::ip::tcp::socket>& ws) | |
260 | { | |
261 | beast::streambuf sb; | |
262 | beast::websocket::frame_info fi; | |
263 | for(;;) | |
264 | { | |
265 | ws.read_frame(fi, sb); | |
266 | if(fi.fin) | |
267 | break; | |
268 | } | |
269 | ws.set_option(beast::websocket::message_type{fi.op}); | |
270 | beast::consuming_buffers< | |
271 | beast::streambuf::const_buffers_type> cb{sb.data()}; | |
272 | for(;;) | |
273 | { | |
274 | using boost::asio::buffer_size; | |
275 | std::size_t size = std::min(buffer_size(cb)); | |
276 | if(size > 512) | |
277 | { | |
278 | ws.write_frame(false, beast::prepare_buffers(512, cb)); | |
279 | cb.consume(512); | |
280 | } | |
281 | else | |
282 | { | |
283 | ws.write_frame(true, cb); | |
284 | break; | |
285 | } | |
286 | } | |
287 | } | |
288 | ``` | |
289 | ||
290 | [endsect] | |
291 | ||
292 | ||
293 | ||
294 | [section:control Control Frames] | |
295 | ||
296 | Control frames are small (less than 128 bytes) messages entirely contained | |
297 | in an individual WebSocket frame. They may be sent at any time by either | |
298 | peer on an established connection, and can appear in between continuation | |
299 | frames for a message. There are three types of control frames: ping, pong, | |
300 | and close. | |
301 | ||
302 | A sent ping indicates a request that the sender wants to receive a pong. A | |
303 | pong is a response to a ping. Pongs may be sent unsolicited, at any time. | |
304 | One use for an unsolicited pong is to inform the remote peer that the | |
305 | session is still active after a long period of inactivity. A close frame | |
306 | indicates that the remote peer wishes to close the WebSocket connection. | |
307 | The connection is considered gracefully closed when each side has sent | |
308 | and received a close frame. | |
309 | ||
310 | During read operations, Beast automatically reads and processes control | |
311 | frames. Pings are replied to as soon as possible with a pong, received | |
312 | ping and pongs are delivered to the ping callback. The receipt of a close | |
313 | frame initiates the WebSocket close procedure, eventually resulting in the | |
314 | error code [link beast.ref.websocket__error `error::closed`] being delivered | |
315 | to the caller in a subsequent read operation, assuming no other error | |
316 | takes place. | |
317 | ||
318 | A consequence of this automatic behavior is that caller-initiated read | |
319 | operations can cause socket writes. However, these writes will not | |
320 | compete with caller-initiated write operations. For the purposes of | |
321 | correctness with respect to the stream invariants, caller-initiated | |
322 | read operations still only count as a read. This means that callers can | |
323 | have a simultaneously active read, write, and ping operation in progress, | |
324 | while the implementation also automatically handles control frames. | |
325 | ||
326 | [heading Ping and Pong Frames] | |
327 | ||
328 | Ping and pong messages are control frames which may be sent at any time | |
329 | by either peer on an established WebSocket connection. They are sent | |
330 | using the functions | |
331 | [link beast.ref.websocket__stream.ping `ping`] and | |
332 | [link beast.ref.websocket__stream.pong `pong`]. | |
333 | ||
334 | To be notified of ping and pong control frames, callers may register a | |
335 | "ping callback" using [link beast.ref.websocket__stream.set_option `set_option`]. | |
336 | The object provided with this option should be callable with the following | |
337 | signature: | |
338 | ``` | |
339 | void on_ping(bool is_pong, ping_data const& payload); | |
340 | ... | |
341 | ws.set_option(ping_callback{&on_ping}); | |
342 | ``` | |
343 | ||
344 | When a ping callback is registered, all pings and pongs received through | |
345 | either synchronous read functions or asynchronous read functions will | |
346 | invoke the ping callback, with the value of `is_pong` set to `true` if a | |
347 | pong was received else `false` if a ping was received. The payload of | |
348 | the ping or pong control frame is passed in the payload argument. | |
349 | ||
350 | Unlike regular completion handlers used in calls to asynchronous initiation | |
351 | functions, the ping callback only needs to be set once. The callback is not | |
352 | reset when a ping or pong is received. The same callback is used for both | |
353 | synchronous and asynchronous reads. The ping callback is passive; in order | |
354 | to receive pings and pongs, a synchronous or asynchronous stream read | |
355 | function must be active. | |
356 | ||
357 | [note | |
358 | When an asynchronous read function receives a ping or pong, the | |
359 | ping callback is invoked in the same manner as that used to invoke | |
360 | the final completion handler of the corresponding read function. | |
361 | ] | |
362 | ||
363 | [heading Close Frames] | |
364 | ||
365 | The WebSocket protocol defines a procedure and control message for initiating | |
366 | a close of the session. Handling of close initiated by the remote end of the | |
367 | connection is performed automatically. To manually initiate a close, use | |
368 | the [link beast.ref.websocket__stream.close `close`] function: | |
369 | ``` | |
370 | ws.close(); | |
371 | ``` | |
372 | ||
373 | When the remote peer initiates a close by sending a close frame, Beast | |
374 | will handle it for you by causing the next read to return `error::closed`. | |
375 | When this error code is delivered, it indicates to the application that | |
376 | the WebSocket connection has been closed cleanly, and that the TCP/IP | |
377 | connection has been closed. After initiating a close, it is necessary to | |
378 | continue reading messages until receiving the error `error::closed`. This | |
379 | is because the remote peer may still be sending message and control frames | |
380 | before it receives and responds to the close frame. | |
381 | ||
382 | [important | |
383 | To receive the [link beast.ref.websocket__error `error::closed`] | |
384 | error, a read operation is required. | |
385 | ] | |
386 | ||
387 | [heading Auto-fragment] | |
388 | ||
389 | To ensure timely delivery of control frames, large messages can be broken up | |
390 | into smaller sized frames. The automatic fragment option turns on this | |
391 | feature, and the write buffer size option determines the maximum size of | |
392 | the fragments: | |
393 | ``` | |
394 | ... | |
395 | ws.set_option(beast::websocket::auto_fragment{true}); | |
396 | ws.set_option(beast::websocket::write_buffer_size{16384}); | |
397 | ``` | |
398 | ||
399 | [endsect] | |
400 | ||
401 | ||
402 | ||
403 | [section:buffers Buffers] | |
404 | ||
405 | Because calls to read data may return a variable amount of bytes, the | |
406 | interface to calls that read data require an object that meets the requirements | |
407 | of [link beast.ref.DynamicBuffer [*`DynamicBuffer`]]. This concept is modeled on | |
408 | [@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/basic_streambuf.html `boost::asio::basic_streambuf`]. | |
409 | ||
410 | The implementation does not perform queueing or buffering of messages. If | |
411 | desired, these features should be provided by callers. The impact of this | |
412 | design is that library users are in full control of the allocation strategy | |
413 | used to store data and the back-pressure applied on the read and write side | |
414 | of the underlying TCP/IP connection. | |
415 | ||
416 | [endsect] | |
417 | ||
418 | ||
419 | ||
420 | [section:async Asynchronous interface] | |
421 | ||
422 | Asynchronous versions are available for all functions: | |
423 | ``` | |
424 | websocket::opcode op; | |
425 | ws.async_read(op, sb, | |
426 | [](boost::system::error_code const& ec) | |
427 | { | |
428 | ... | |
429 | }); | |
430 | ``` | |
431 | ||
432 | Calls to asynchronous initiation functions support the extensible asynchronous | |
433 | model developed by the Boost.Asio author, allowing for traditional completion | |
434 | handlers, stackful or stackless coroutines, and even futures: | |
435 | ``` | |
436 | void echo(websocket::stream<ip::tcp::socket>& ws, | |
437 | boost::asio::yield_context yield) | |
438 | { | |
439 | ws.async_read(sb, yield); | |
440 | std::future<websocket::error_code> fut = | |
441 | ws.async_write, sb.data(), boost::use_future); | |
442 | ... | |
443 | } | |
444 | ``` | |
445 | ||
446 | [endsect] | |
447 | ||
448 | ||
449 | ||
450 | [section:io_service The io_service] | |
451 | ||
452 | The creation and operation of the | |
453 | [@http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/io_service.html `boost::asio::io_service`] | |
454 | associated with the underlying stream is left to the callers, permitting any | |
455 | implementation strategy including one that does not require threads for | |
456 | environments where threads are unavailable. Beast.WebSocket itself does not | |
457 | use or require threads. | |
458 | ||
459 | [endsect] | |
460 | ||
461 | ||
462 | ||
463 | [section:threads Thread Safety] | |
464 | ||
465 | Like a regular asio socket, a [link beast.ref.websocket__stream `stream`] is | |
466 | not thread safe. Callers are responsible for synchronizing operations on the | |
467 | socket using an implicit or explicit strand, as per the Asio documentation. | |
468 | The asynchronous interface supports one active read and one active write | |
469 | simultaneously. Undefined behavior results if two or more reads or two or | |
470 | more writes are attempted concurrently. Caller initiated WebSocket ping, pong, | |
471 | and close operations each count as an active write. | |
472 | ||
473 | The implementation uses composed asynchronous operations internally; a high | |
474 | level read can cause both reads and writes to take place on the underlying | |
475 | stream. This behavior is transparent to callers. | |
476 | ||
477 | [endsect] | |
478 | ||
479 | ||
480 | ||
481 | [endsect] | |
482 | ||
483 | [include quickref.xml] |