diff --git a/doc/qbk/06_websocket/02_handshaking.qbk b/doc/qbk/06_websocket/02_handshaking.qbk index efb663ce..5066091a 100644 --- a/doc/qbk/06_websocket/02_handshaking.qbk +++ b/doc/qbk/06_websocket/02_handshaking.qbk @@ -150,6 +150,27 @@ stream: [code_websocket_2_5] +[heading Subprotocols] + +The WebSocket protocol understands the concept of subprotocols. If the client +is requesting one of a set of subprotocols it will set the header +[@https://tools.ietf.org/html/rfc6455#section-11.3.4 Sec-WebSocket-Protocol] +in the initial WebSocket Upgrade HTTP request. It is up to the server to +parse the header and select one of the protocols to accept. The server +indicates the selected protocol by setting the +[@https://tools.ietf.org/html/rfc6455#section-11.3.4 Sec-WebSocket-Protocol] +header in the accept header. + +This is accomplished with a +[link beast.ref.boost__beast__websocket__stream_base__decorator `decorator`]. + +The code that follows demonstrates how a server reads an HTTP request, identifies it +as a WebSocket Upgrade, and then checks for a preferred matching subprotocol before +performing the WebSocket handshake: + +[code_websocket_2_6] + + [/-----------------------------------------------------------------------------] [endsect] diff --git a/test/doc/websocket_2_handshaking.cpp b/test/doc/websocket_2_handshaking.cpp index 0c1eb923..ce6c2be7 100644 --- a/test/doc/websocket_2_handshaking.cpp +++ b/test/doc/websocket_2_handshaking.cpp @@ -132,6 +132,96 @@ snippets() //] } + + { + //[code_websocket_2_6 + + // a function to select the most preferred protocol from a comma-separated list + auto select_protocol = [](string_view offered_tokens) -> std::string + { + // tokenize the Sec-Websocket-Protocol header offered by the client + http::token_list offered( offered_tokens ); + + // an array of protocols supported by this server + // in descending order of preference + static const std::array + supported = { + "v3.my.chat", + "v2.my.chat", + "v1.my.chat" + }; + + std::string result; + + for (auto proto : supported) + { + auto iter = std::find(offered.begin(), offered.end(), proto); + if (iter != offered.end()) + { + // we found a supported protocol in the list offered by the client + result.assign(proto.begin(), proto.end()); + break; + } + } + + return result; + }; + + + // This buffer is required for reading HTTP messages + flat_buffer buffer; + + // Read the HTTP request ourselves + http::request req; + http::read(sock, buffer, req); + + // See if it's a WebSocket upgrade request + if(websocket::is_upgrade(req)) + { + // we store the selected protocol in a std::string here because + // we intend to capture it in the decorator's lambda below + std::string protocol = + select_protocol( + req[http::field::sec_websocket_protocol]); + + if (protocol.empty()) + { + // none of our supported protocols were offered + http::response res; + res.result(http::status::bad_request); + res.body() = "No valid sub-protocol was offered." + " This server implements" + " v3.my.chat," + " v2.my.chat" + " and v1.my.chat"; + http::write(sock, res); + } + else + { + // Construct the stream, transferring ownership of the socket + stream ws(std::move(sock)); + + ws.set_option( + stream_base::decorator( + [protocol](http::response_header<> &hdr) { + hdr.set( + http::field::sec_websocket_protocol, + protocol); + })); + + // Accept the upgrade request + ws.accept(req); + } + } + else + { + // Its not a WebSocket upgrade, so + // handle it like a normal HTTP request. + } + + //] + } + } struct websocket_2_test