Skip to content

Commit 4e50432

Browse files
committed
Adding support for websocket subprotocols. Updated our non WinRT websocket_client to support request headers.
1 parent 8e0a969 commit 4e50432

File tree

8 files changed

+250
-55
lines changed

8 files changed

+250
-55
lines changed

Release/include/cpprest/ws_client.h

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,10 @@ enum class websocket_close_status
8383
class websocket_client_config
8484
{
8585
public:
86+
87+
/// <summary>
88+
/// Creates a websocket client configuration with default settings.
89+
/// </summary>
8690
websocket_client_config()
8791
{}
8892

@@ -137,6 +141,21 @@ class websocket_client_config
137141
/// <returns>HTTP headers.</returns>
138142
const web::http::http_headers &headers() const { return m_headers; }
139143

144+
/// <summary>
145+
/// Adds a subprotocol to the request headers.
146+
/// </summary>
147+
/// <param name="name">The name of the subprotocol.</param>
148+
/// <remark>If additional subprotocols have already been specified, the new one will just be added.</remarks>
149+
_ASYNCRTIMP void add_subprotocol(const ::utility::string_t &name);
150+
151+
/// <summary>
152+
/// Gets list of the specified subprotocols.
153+
/// </summary>
154+
/// <returns>Vector of all the subprotocols </returns>
155+
/// <remarks>If you want all the subprotocols in a comma separated string
156+
/// they can be directly directly looked up in the headers using 'Sec-WebSocket-Protocol'.</remarks>
157+
_ASYNCRTIMP std::vector<::utility::string_t> subprotocols() const;
158+
140159
private:
141160
web::web_proxy m_proxy;
142161
web::credentials m_credentials;
@@ -157,6 +176,12 @@ class websocket_exception : public std::exception
157176
websocket_exception(const utility::string_t &whatArg)
158177
: m_msg(utility::conversions::to_utf8string(whatArg)) {}
159178

179+
/// <summary>
180+
/// Creates an <c>websocket_exception</c> with just a string message and no error code.
181+
/// </summary>
182+
/// <param name="whatArg">Error message string.</param>
183+
websocket_exception(std::string whatArg) : m_msg(std::move(whatArg)) {}
184+
160185
/// <summary>
161186
/// Creates a <c>websocket_exception</c> from a error code using the current platform error category.
162187
/// The message of the error code will be used as the what() string message.
@@ -178,6 +203,16 @@ class websocket_exception : public std::exception
178203
m_msg(utility::conversions::to_utf8string(whatArg))
179204
{}
180205

206+
/// <summary>
207+
/// Creates a <c>websocket_exception</c> from a error code and string message.
208+
/// </summary>
209+
/// <param name="errorCode">Error code value.</param>
210+
/// <param name="whatArg">Message to use in what() string.</param>
211+
websocket_exception(int errorCode, std::string whatArg)
212+
: m_errorCode(utility::details::create_error_code(errorCode)),
213+
m_msg(std::move(whatArg))
214+
{}
215+
181216
/// <summary>
182217
/// Creates a <c>websocket_exception</c> from a error code and category. The message of the error code will be used
183218
/// as the <c>what</c> string message.
@@ -189,14 +224,25 @@ class websocket_exception : public std::exception
189224
m_msg = m_errorCode.message();
190225
}
191226

227+
/// <summary>
228+
/// Destroys the <c>websocket_exception</c> object.
229+
/// </summary>
192230
~websocket_exception() _noexcept {}
193231

232+
/// <summary>
233+
/// Gets a string identifying the cause of the exception.
234+
/// </summary>
235+
/// <returns>A null terminated character string.</returns>
194236
const char* what() const _noexcept
195237
{
196238
return m_msg.c_str();
197239
}
198240

199-
const std::error_code & error_code() const
241+
/// <summary>
242+
/// Gets the underlying error code for the cause of the exception.
243+
/// </summary>
244+
/// <returns>The <c>error_code</c> object associated with the exception.</returns>
245+
const std::error_code & error_code() const _noexcept
200246
{
201247
return m_errorCode;
202248
}

Release/src/websockets/client/ws_client.cpp

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -267,14 +267,39 @@ class ws_desktop_client : public _websocket_client_impl, public std::enable_shar
267267
m_state = CLOSED;
268268
});
269269

270-
270+
// Get the connection handle to save for later, have to create temporary
271+
// because type erasure occurs with connection_hdl.
271272
websocketpp::lib::error_code ec;
272273
auto con = m_client.get_connection(utility::conversions::to_utf8string(m_uri.to_string()), ec);
273274
m_con = con;
274275
if (ec.value() != 0)
275276
{
276-
websocket_exception wx(utility::conversions::to_string_t(ec.message()));
277-
return pplx::task_from_exception<void>(wx);
277+
return pplx::task_from_exception<void>(websocket_exception(ec.message()));
278+
}
279+
280+
// Add any request headers specified by the user.
281+
const utility::string_t protocolHeader(_XPLATSTR("Sec-WebSocket-Protocol"));
282+
const auto & headers = m_client_config.headers();
283+
for (const auto & header : headers)
284+
{
285+
if (!utility::details::str_icmp(header.first, protocolHeader))
286+
{
287+
con->append_header(utility::conversions::to_utf8string(header.first), utility::conversions::to_utf8string(header.second));
288+
}
289+
}
290+
291+
// Add any specified subprotocols.
292+
if (headers.has(protocolHeader))
293+
{
294+
const std::vector<utility::string_t> protocols = m_client_config.subprotocols();
295+
for (const auto & value : protocols)
296+
{
297+
con->add_subprotocol(utility::conversions::to_utf8string(value), ec);
298+
if (ec.value())
299+
{
300+
return pplx::task_from_exception<void>(websocket_exception(ec.message()));
301+
}
302+
}
278303
}
279304

280305
m_state = CONNECTING;

Release/src/websockets/client/ws_msg.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,31 @@ namespace web_sockets
4646
namespace client
4747
{
4848

49+
static ::utility::string_t g_subProtocolHeader = _XPLATSTR("Sec-WebSocket-Protocol");
50+
void websocket_client_config::add_subprotocol(const ::utility::string_t &name)
51+
{
52+
m_headers.add(g_subProtocolHeader, name);
53+
}
54+
55+
std::vector<::utility::string_t> websocket_client_config::subprotocols() const
56+
{
57+
std::vector<::utility::string_t> values;
58+
if (m_headers.has(g_subProtocolHeader))
59+
{
60+
utility::stringstream_t header(m_headers.find(g_subProtocolHeader)->second);
61+
utility::string_t token;
62+
while (std::getline(header, token, U(',')))
63+
{
64+
http::details::trim_whitespace(token);
65+
if (!token.empty())
66+
{
67+
values.push_back(token);
68+
}
69+
}
70+
}
71+
return values;
72+
}
73+
4974
void details::_websocket_message::set_body(streams::istream instream)
5075
{
5176
set_streambuf(instream.streambuf());

Release/src/websockets/client/ws_winrt.cpp

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
#include "stdafx.h"
2828
#include <concrt.h>
2929

30-
// ws_winrt only available for windows storea app or window phone8.1
30+
// ws_winrt only available for windows store app or window phone 8.1
3131
#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_PC_APP) || (WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_PHONE_APP) && _WIN32_WINNT == _WIN32_WINNT_WINBLUE)
3232

3333
using namespace ::Windows::Foundation;
@@ -79,11 +79,26 @@ class winrt_client : public _websocket_client_impl, public std::enable_shared_fr
7979
m_msg_websocket = ref new MessageWebSocket();
8080

8181
// Sets the HTTP request headers to the HTTP request message used in the WebSocket protocol handshake
82-
for (auto iter = client_config.headers().begin(); iter != client_config.headers().end(); ++iter)
82+
const utility::string_t protocolHeader(_XPLATSTR("Sec-WebSocket-Protocol"));
83+
const auto & headers = config().headers();
84+
for (const auto & header : headers)
8385
{
84-
Platform::String^ name = ref new Platform::String(iter->first.c_str());
85-
Platform::String^ val = ref new Platform::String(iter->second.c_str());
86-
m_msg_websocket->SetRequestHeader(name, val);
86+
// Unfortunately the MessageWebSocket API throws a COMException if you try to set the
87+
// 'Sec-WebSocket-Protocol' header here. It requires you to go through their API instead.
88+
if (!utility::details::str_icmp(header.first, protocolHeader))
89+
{
90+
m_msg_websocket->SetRequestHeader(Platform::StringReference(header.first.c_str()), Platform::StringReference(header.second.c_str()));
91+
}
92+
}
93+
94+
// Add any specified subprotocols.
95+
if (headers.has(protocolHeader))
96+
{
97+
const std::vector<utility::string_t> protocols = this->config().subprotocols();
98+
for (const auto & value : protocols)
99+
{
100+
m_msg_websocket->Control->SupportedProtocols->Append(Platform::StringReference(value.c_str()));
101+
}
87102
}
88103

89104
if (client_config.credentials().is_set())

Release/tests/Functional/websockets/client/authentication_tests.cpp

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,18 +37,20 @@ namespace tests { namespace functional { namespace websocket { namespace client
3737
SUITE(authentication_tests)
3838
{
3939

40+
// Authorization not implemented in non WinRT websocket_client yet - CodePlex 254
41+
#if defined(__cplusplus_winrt)
4042
void auth_helper(test_websocket_server& server, const utility::string_t &username = U(""), const utility::string_t &password = U(""))
4143
{
42-
server.set_http_handler([username, password](test_http_request& request)
44+
server.set_http_handler([username, password](test_http_request request)
4345
{
4446
test_http_response resp;
45-
if (request.username().empty()) // No credentials -> challenge the request
47+
if (request->username().empty()) // No credentials -> challenge the request
4648
{
4749
resp.set_status_code(401); // Unauthorized.
4850
resp.set_realm("My Realm");
4951
}
50-
else if (request.username().compare(utility::conversions::to_utf8string(username))
51-
|| request.password().compare(utility::conversions::to_utf8string(password)))
52+
else if (request->username().compare(utility::conversions::to_utf8string(username))
53+
|| request->password().compare(utility::conversions::to_utf8string(password)))
5254
{
5355
resp.set_status_code(403); // User name/password did not match: Forbidden - auth failure.
5456
}
@@ -61,7 +63,7 @@ void auth_helper(test_websocket_server& server, const utility::string_t &usernam
6163
}
6264

6365
// connect without credentials, when the server expects credentials
64-
TEST_FIXTURE(uri_address, auth_no_credentials, "Ignore", "245", "Ignore:Linux", "NYI", "Ignore:Apple", "NYI")
66+
TEST_FIXTURE(uri_address, auth_no_credentials, "Ignore", "245")
6567
{
6668
test_websocket_server server;
6769
websocket_client client;
@@ -70,7 +72,7 @@ TEST_FIXTURE(uri_address, auth_no_credentials, "Ignore", "245", "Ignore:Linux",
7072
}
7173

7274
// Connect with credentials
73-
TEST_FIXTURE(uri_address, auth_with_credentials, "Ignore", "245", "Ignore:Linux", "NYI", "Ignore:Apple", "NYI")
75+
TEST_FIXTURE(uri_address, auth_with_credentials, "Ignore", "245")
7476
{
7577
test_websocket_server server;
7678
websocket_client_config config;
@@ -82,9 +84,12 @@ TEST_FIXTURE(uri_address, auth_with_credentials, "Ignore", "245", "Ignore:Linux"
8284
client.connect(m_uri).wait();
8385
client.close().wait();
8486
}
87+
#endif
8588

89+
// Secure websockets on implemented yet on non-WinRT - 255
90+
#if defined(__cplusplus_winrt)
8691
// Send and receive text message over SSL
87-
TEST_FIXTURE(uri_address, ssl_test, "Ignore", "NYI on desktop/apple/linux/android")
92+
TEST_FIXTURE(uri_address, ssl_test)
8893
{
8994
websocket_client client;
9095
client.connect(U("wss://echo.websocket.org/")).wait();
@@ -106,6 +111,7 @@ TEST_FIXTURE(uri_address, ssl_test, "Ignore", "NYI on desktop/apple/linux/androi
106111
receive_task.wait();
107112
client.close().wait();
108113
}
114+
#endif
109115

110116
} // SUITE(authentication_tests)
111117

Release/tests/Functional/websockets/client/client_construction.cpp

Lines changed: 54 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
*
1919
* client_construction.cpp
2020
*
21-
* Tests cases for covering creating http_clients.
21+
* Tests cases for covering creating websocket_clients.
2222
*
2323
* =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
2424
****/
@@ -29,13 +29,6 @@
2929

3030
using namespace concurrency::streams;
3131

32-
#if defined(WINAPI_FAMILY) && WINAPI_FAMILY != WINAPI_FAMILY_DESKTOP_APP
33-
34-
using namespace Windows::Foundation;
35-
using namespace Windows::System::Threading;
36-
37-
#endif
38-
3932
using namespace web;
4033
using namespace web::experimental::web_sockets;
4134
using namespace web::experimental::web_sockets::client;
@@ -172,24 +165,72 @@ TEST_FIXTURE(uri_address, move_operations)
172165
client.close().wait();
173166
}
174167

175-
TEST_FIXTURE(uri_address, connect_with_headers)
168+
void header_test_impl(const uri &address, const utility::string_t &headerName, const utility::string_t &headerValue, const utility::string_t &expectedHeaderValue = U(""))
176169
{
177170
test_websocket_server server;
178171
websocket_client_config config;
179-
utility::string_t header_name(U("HeaderTest"));
180-
utility::string_t header_val(U("ConnectSuccessfully"));
181-
config.headers().add(header_name, header_val);
172+
utility::string_t expectedValue = headerValue;
173+
if (!expectedHeaderValue.empty())
174+
{
175+
expectedValue = expectedHeaderValue;
176+
}
177+
config.headers().add(headerName, headerValue);
182178
websocket_client client(config);
183179

184180
server.set_http_handler([&](test_http_request request)
185181
{
186182
test_http_response resp;
187-
if (request.get_header_val(utility::conversions::to_utf8string(header_name)).compare(utility::conversions::to_utf8string(header_val)) == 0)
183+
if (request->get_header_val(utility::conversions::to_utf8string(headerName)).compare(utility::conversions::to_utf8string(expectedValue)) == 0)
188184
resp.set_status_code(200); // Handshake request will be completed only if header match succeeds.
189185
else
190186
resp.set_status_code(400); // Else fail the handshake, websocket client connect will fail in this case.
191187
return resp;
192188
});
189+
client.connect(address).wait();
190+
client.close().wait();
191+
}
192+
193+
TEST_FIXTURE(uri_address, connect_with_headers)
194+
{
195+
header_test_impl(m_uri, U("HeaderTest"), U("ConnectSuccessfully"));
196+
}
197+
198+
TEST_FIXTURE(uri_address, manually_set_protocol_header)
199+
{
200+
utility::string_t headerName(U("Sec-WebSocket-Protocol"));
201+
header_test_impl(m_uri, headerName, U("myprotocol"));
202+
header_test_impl(m_uri, headerName, U("myprotocol2,"), U("myprotocol2"));
203+
header_test_impl(m_uri, headerName, U("myprotocol2,protocol3"), U("myprotocol2, protocol3"));
204+
header_test_impl(m_uri, headerName, U("myprotocol2, protocol3, protocol6,,"), U("myprotocol2, protocol3, protocol6"));
205+
}
206+
207+
TEST_FIXTURE(uri_address, set_subprotocol)
208+
{
209+
test_websocket_server server;
210+
websocket_client_config config;
211+
212+
utility::string_t expected1(U("pro1"));
213+
config.add_subprotocol(expected1);
214+
VERIFY_ARE_EQUAL(1, config.subprotocols().size());
215+
VERIFY_ARE_EQUAL(expected1, config.subprotocols()[0]);
216+
217+
utility::string_t expected2(U("second"));
218+
config.add_subprotocol(expected2);
219+
VERIFY_ARE_EQUAL(2, config.subprotocols().size());
220+
VERIFY_ARE_EQUAL(expected1, config.subprotocols()[0]);
221+
VERIFY_ARE_EQUAL(expected2, config.subprotocols()[1]);
222+
223+
websocket_client client(config);
224+
server.set_http_handler([&](test_http_request request)
225+
{
226+
test_http_response resp;
227+
if (request->get_header_val(utility::conversions::to_utf8string(U("Sec-WebSocket-Protocol"))).compare(utility::conversions::to_utf8string(expected1 + U(", ") + expected2)) == 0)
228+
resp.set_status_code(200); // Handshake request will be completed only if header match succeeds.
229+
else
230+
resp.set_status_code(400); // Else fail the handshake, websocket client connect will fail in this case.
231+
return resp;
232+
});
233+
193234
client.connect(m_uri).wait();
194235
client.close().wait();
195236
}

0 commit comments

Comments
 (0)