diff --git a/doc/6_3_client.qbk b/doc/6_3_client.qbk index bef35c9d..d1d11e48 100644 --- a/doc/6_3_client.qbk +++ b/doc/6_3_client.qbk @@ -7,13 +7,13 @@ [section:client Handshaking (Clients)] -A WebSocket session begins when a client sends the HTTP +A WebSocket session begins when a client sends the HTTP/1 [@https://tools.ietf.org/html/rfc7230#section-6.7 Upgrade] request for [@https://tools.ietf.org/html/rfc6455#section-1.3 websocket], -and the server sends an appropriate HTTP response indicating that +and the server sends an appropriate response indicating that the request was accepted and that the connection has been upgraded. -The HTTP Upgrade request must include the +The Upgrade request must include the [@https://tools.ietf.org/html/rfc7230#section-5.4 Host] field, and the [@https://tools.ietf.org/html/rfc7230#section-5.3 target] @@ -21,13 +21,10 @@ of the resource to request. The stream member functions [link beast.ref.websocket__stream.handshake.overload1 `handshake`] and [link beast.ref.websocket__stream.async_handshake.overload1 `async_handshake`] are used to send the request with the required host and target strings. -``` - ... - ws.set_option(websocket::keep_alive(true)); - ws.handshake("localhost", "/"); -``` -The implementation will create and send an HTTP request that typically +[ws_snippet_8] + +The implementation will create and send a request that typically looks like this: [table WebSocket Upgrade HTTP Request @@ -58,15 +55,8 @@ 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(websocket::request_type& req) - { - req.insert("Sec-WebSocket-Protocol", "xmpp;ws-chat"); - } - ... - ws.handshake_ex("localhost", "/", &decorate); -``` +[ws_snippet_9] The HTTP Upgrade request produced by the previous call will look thusly: @@ -99,11 +89,7 @@ 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: -``` - websocket::response_type res; - ws.handshake(res, "localhost", "/"); - if(! res.exists("Sec-WebSocket-Protocol")) - throw std::invalid_argument("missing subprotocols"); -``` + +[ws_snippet_10] [endsect] diff --git a/doc/6_4_server.qbk b/doc/6_4_server.qbk index 18bcfe6a..c281f565 100644 --- a/doc/6_4_server.qbk +++ b/doc/6_4_server.qbk @@ -17,10 +17,8 @@ setting, the connection 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: -``` - ... - ws.accept(); -``` + +[ws_snippet_11] Successful WebSocket Upgrade responses generated by the implementation will typically look like this: @@ -49,16 +47,10 @@ 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( - [](websocket::response_type& res) - { - res.insert("Server", "AcmeServer"); - }); -``` +[ws_snippet_12] -The HTTP Upgrade response produced by the previous call will look thusly: +The HTTP Upgrade response produced by the previous call looks like this: [table Decorated WebSocket Upgrade HTTP Request [[Serialized Octets][Description]] @@ -77,7 +69,7 @@ The HTTP Upgrade response produced by the previous call will look thusly: 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. + WebSocket Upgrade, such as the Upgrade and Connection fields. ]]] [heading Passing HTTP Requests] @@ -97,19 +89,8 @@ 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) - { - flat_buffer buffer; - http::request req; - http::read(sock, buffer, req); - if(websocket::is_upgrade(req)) - { - websocket::stream ws{std::move(sock)}; - ws.accept(req); - } - } -``` + +[ws_snippet_13] [heading Buffered Handshakes] @@ -129,16 +110,7 @@ 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"); - ... - websocket::stream ws{sock}; - ws.accept(sb.data()); - ... -} -``` + +[ws_snippet_14] [endsect] diff --git a/doc/6_5_messages.qbk b/doc/6_5_messages.qbk index 801aa3a9..f6c2d512 100644 --- a/doc/6_5_messages.qbk +++ b/doc/6_5_messages.qbk @@ -10,19 +10,8 @@ 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: -``` - template - void echo(websocket::stream& ws) - { - multi_buffer b; - websocket::opcode::value op; - ws.read(op, b); - ws.set_option(websocket::message_type{op}); - ws.write(b.data()); - b.consume(b.size()); - } -``` +[ws_snippet_15] [important Calls to [link beast.ref.websocket__stream.set_option `set_option`] @@ -41,37 +30,7 @@ message ahead of time: For these cases, the frame oriented interface may be used. This example reads and echoes a complete message using this interface: -``` - template - void echo(websocket::stream& ws) - { - multi_buffer b; - websocket::frame_info fi; - for(;;) - { - ws.read_frame(fi, b); - if(fi.fin) - break; - } - ws.set_option(websocket::message_type{fi.op}); - consuming_buffers< - 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, prepare_buffers(512, cb)); - cb.consume(512); - } - else - { - ws.write_frame(true, cb); - break; - } - } - } -``` + +[ws_snippet_16] [endsect] diff --git a/doc/6_6_control.qbk b/doc/6_6_control.qbk index 0836ffab..f15e1071 100644 --- a/doc/6_6_control.qbk +++ b/doc/6_6_control.qbk @@ -49,11 +49,8 @@ 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, websocket::ping_data const& payload); - ... - ws.set_option(ping_callback{&on_ping}); -``` + +[ws_snippet_17] When a ping callback is registered, all pings and pongs received through either synchronous read functions or asynchronous read functions will @@ -80,11 +77,9 @@ 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(); -``` +[link beast.ref.websocket__stream.close `close`] function: + +[ws_snippet_18] 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`. @@ -107,10 +102,7 @@ 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(websocket::auto_fragment{true}); - ws.set_option(websocket::write_buffer_size{16384}); -``` + +[ws_snippet_19] [endsect] diff --git a/doc/6_7_notes.qbk b/doc/6_7_notes.qbk index 7c567976..4c97b30a 100644 --- a/doc/6_7_notes.qbk +++ b/doc/6_7_notes.qbk @@ -20,28 +20,14 @@ 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) - { - ... - }); -``` + +[ws_snippet_20] 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& ws, - boost::asio::yield_context yield) - { - ws.async_read(sb, yield); - std::future fut = - ws.async_write, sb.data(), boost::use_future); - ... - } -``` + +[ws_snippet_21] [heading The io_service] diff --git a/include/beast/http/fields.hpp b/include/beast/http/fields.hpp index 6512d021..2b9cabd2 100644 --- a/include/beast/http/fields.hpp +++ b/include/beast/http/fields.hpp @@ -196,6 +196,16 @@ public: return list_.cend(); } + /// Return `true` if the specified field exists. + bool + exists(field f) const + { + // VFALCO Should we throw here? + if(f == field::unknown) + return false; + return set_.find(to_string(f), less{}) != set_.end(); + } + /// Return `true` if the specified field exists. bool exists(string_view name) const diff --git a/test/websocket/doc_snippets.cpp b/test/websocket/doc_snippets.cpp index 0787c228..4e7a7a30 100644 --- a/test/websocket/doc_snippets.cpp +++ b/test/websocket/doc_snippets.cpp @@ -7,16 +7,20 @@ #include #include +#include +#include +#include +#include #include #include -using namespace beast; - //[ws_snippet_1 #include using namespace beast::websocket; //] +using namespace beast; + namespace doc_ws_snippets { void fxx() { @@ -67,6 +71,166 @@ boost::asio::ip::tcp::socket sock{ios}; //] } +{ + stream ws{ios}; +//[ws_snippet_8 + ws.handshake("localhost", "/"); +//] + +//[ws_snippet_9 + ws.handshake_ex("localhost", "/", + [](request_type& req) + { + req.insert(http::field::sec_websocket_protocol, "xmpp;ws-chat"); + }); +//] + +//[ws_snippet_10 + response_type res; + ws.handshake(res, "localhost", "/"); + if(! res.exists(http::field::sec_websocket_protocol)) + throw std::invalid_argument("missing subprotocols"); +//] + +//[ws_snippet_11 + ws.accept(); +//] + +//[ws_snippet_12 + ws.accept_ex( + [](response_type& res) + { + res.insert(http::field::server, "MyServer"); + }); +//] +} + +{ +//[ws_snippet_13] + // Buffer required for reading HTTP messages + flat_buffer buffer; + + // Read the HTTP request ourselves + http::request req; + http::read(sock, buffer, req); + + // See if its a WebSocket upgrade request + if(websocket::is_upgrade(req)) + { + // Construct the stream, transferring ownership of the socket + stream ws{std::move(sock)}; + + // Accept the request from our message. Clients SHOULD NOT + // begin sending WebSocket frames until the server has + // provided a response, but just in case they did, we pass + // any leftovers in the buffer to the accept function. + // + ws.accept(req, buffer.data()); + } + else + { + // Its not a WebSocket upgrade, so + // handle it like a normal HTTP request. + } +//] +} + +{ + stream ws{ios}; +//[ws_snippet_14 + // Read into our buffer until we reach the end of the HTTP request. + // No parsing takes place here, we are just accumulating data. + boost::asio::streambuf buffer; + boost::asio::read_until(sock, buffer, "\r\n\r\n"); + + // Now accept the connection, using the buffered data. + ws.accept(buffer.data()); +//] +} +{ + stream ws{ios}; +//[ws_snippet_15 + multi_buffer buffer; + opcode op; + ws.read(op, buffer); + + ws.set_option(message_type{op}); + ws.write(buffer.data()); + buffer.consume(buffer.size()); +//] +} + +{ + stream ws{ios}; +//[ws_snippet_16 + multi_buffer buffer; + frame_info fi; + for(;;) + { + ws.read_frame(fi, buffer); + if(fi.fin) + break; + } + ws.set_option(message_type{fi.op}); + consuming_buffers cb{buffer.data()}; + for(;;) + { + using boost::asio::buffer_size; + if(buffer_size(cb) > 512) + { + ws.write_frame(false, buffer_prefix(512, cb)); + cb.consume(512); + } + else + { + ws.write_frame(true, cb); + break; + } + } +//] +} + +{ + stream ws{ios}; +//[ws_snippet_17 + ws.set_option(ping_callback( + [](bool is_pong, ping_data const& payload) + { + // Do something with the payload + })); +//] + +//[ws_snippet_18 + ws.close(close_code::normal); +//] + +//[ws_snippet_19 + ws.set_option(auto_fragment{true}); + ws.set_option(write_buffer_size{16384}); +//] + +//[ws_snippet_20 + opcode op; + multi_buffer buffer; + ws.async_read(op, buffer, + [](error_code ec) + { + // Do something with the buffer + }); +//] +} + } // fxx() +//[ws_snippet_21 +void echo(stream& ws, + multi_buffer& buffer, boost::asio::yield_context yield) +{ + opcode op; + ws.async_read(op, buffer, yield); + std::future fut = + ws.async_write(buffer.data(), boost::asio::use_future); +} +//] + } // doc_ws_snippets