diff options
author | Arno Rehn <a.rehn@menlosystems.com> | 2022-01-02 22:31:03 +0100 |
---|---|---|
committer | Arno Rehn <a.rehn@menlosystems.com> | 2022-03-30 17:45:18 +0200 |
commit | cc4c24b99a87629aeb60df5af96d9bb991b56635 (patch) | |
tree | 62459a42457b0680022bb1fcb759f9c4e64d2c4b /tests | |
parent | 8545bb57efbfabf0dc7bc4b76efd6a99b4022669 (diff) |
Add support for WebSocket Sub-Protocols
Sub-Protocol support follows RFC 6455 Sections 4.1 and 4.2. See also
https://datatracker.ietf.org/doc/html/rfc6455.
This patch introduces a new class QWebSocketHandshakeOptions which
collects options for the WebSocket handshake. At the moment, this
contains only accessors for sub-protocols. In the future, it can be
extended with things like WebSocket extensions.
[ChangeLog] Add support for WebSocket Sub-Protocols
Fixes: QTBUG-38742
Change-Id: Ibdcef17f717f0a76caab54f65c550865df1ec78d
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: MÃ¥rten Nordheim <marten.nordheim@qt.io>
Diffstat (limited to 'tests')
3 files changed, 120 insertions, 1 deletions
diff --git a/tests/auto/qml/qmlwebsockets/tst_qmlwebsockets.qml b/tests/auto/qml/qmlwebsockets/tst_qmlwebsockets.qml index 66c1ed0..21d3bf6 100644 --- a/tests/auto/qml/qmlwebsockets/tst_qmlwebsockets.qml +++ b/tests/auto/qml/qmlwebsockets/tst_qmlwebsockets.qml @@ -38,6 +38,8 @@ Rectangle { id: server port: 1337 + supportedSubprotocols: [ "chat", "superchat" ] + onClientConnected: { currentSocket = webSocket; } @@ -48,6 +50,7 @@ Rectangle { WebSocket { id: socket url: server.url + requestedSubprotocols: [ "superchat", "chat" ] } TestCase { @@ -56,6 +59,10 @@ Rectangle { socket.active = true; tryCompare(socket, 'status', WebSocket.Open); verify(server.currentSocket); + + // Handshake should select client's first preference. + compare(socket.negotiatedSubprotocol, "superchat") + compare(server.currentSocket.negotiatedSubprotocol, "superchat") } function ensureDisconnected() { diff --git a/tests/auto/websockets/qwebsocket/tst_qwebsocket.cpp b/tests/auto/websockets/qwebsocket/tst_qwebsocket.cpp index 329419c..4f85430 100644 --- a/tests/auto/websockets/qwebsocket/tst_qwebsocket.cpp +++ b/tests/auto/websockets/qwebsocket/tst_qwebsocket.cpp @@ -25,9 +25,11 @@ ** $QT_END_LICENSE$ ** ****************************************************************************/ +#include <QRegularExpression> #include <QString> #include <QtTest> #include <QtWebSockets/QWebSocket> +#include <QtWebSockets/QWebSocketHandshakeOptions> #include <QtWebSockets/QWebSocketServer> #include <QtWebSockets/qwebsocketprotocol.h> @@ -72,6 +74,8 @@ EchoServer::EchoServer(QObject *parent, quint64 maxAllowedIncomingMessageSize, q m_maxAllowedIncomingFrameSize(maxAllowedIncomingFrameSize), m_clients() { + m_pWebSocketServer->setSupportedSubprotocols({ QStringLiteral("protocol1"), + QStringLiteral("protocol2") }); if (m_pWebSocketServer->listen(QHostAddress(QStringLiteral("127.0.0.1")))) { connect(m_pWebSocketServer, SIGNAL(newConnection()), this, SLOT(onNewConnection())); @@ -146,7 +150,9 @@ private Q_SLOTS: void tst_sendTextMessage(); void tst_sendBinaryMessage(); void tst_errorString(); + void tst_openRequest_data(); void tst_openRequest(); + void tst_protocolAccessor(); void tst_moveToThread(); void tst_moveToThreadNoWarning(); #ifndef QT_NO_NETWORKPROXY @@ -631,8 +637,35 @@ void tst_QWebSocket::tst_errorString() QCOMPARE(socket.errorString(), QStringLiteral("Host not found")); } +void tst_QWebSocket::tst_openRequest_data() +{ + QTest::addColumn<QStringList>("subprotocols"); + QTest::addColumn<QString>("subprotocolHeader"); + QTest::addColumn<QRegularExpression>("warningExpression"); + + QTest::addRow("no subprotocols") << QStringList{} << QString{} << QRegularExpression{}; + QTest::addRow("single subprotocol") << QStringList{"foobar"} << QStringLiteral("foobar") + << QRegularExpression{}; + QTest::addRow("multiple subprotocols") << QStringList{"foo", "bar"} + << QStringLiteral("foo, bar") + << QRegularExpression{}; + QTest::addRow("subprotocol with whitespace") + << QStringList{"chat", "foo\r\nbar with space"} + << QStringLiteral("chat") + << QRegularExpression{".*invalid.*bar with space"}; + + QTest::addRow("subprotocol with invalid chars") + << QStringList{"chat", "foo{}"} + << QStringLiteral("chat") + << QRegularExpression{".*invalid.*foo"}; +} + void tst_QWebSocket::tst_openRequest() { + QFETCH(QStringList, subprotocols); + QFETCH(QString, subprotocolHeader); + QFETCH(QRegularExpression, warningExpression); + EchoServer echoServer; QWebSocket socket; @@ -647,7 +680,13 @@ void tst_QWebSocket::tst_openRequest() url.setQuery(query); QNetworkRequest req(url); req.setRawHeader("X-Custom-Header", "A custom header"); - socket.open(req); + QWebSocketHandshakeOptions options; + options.setSubprotocols(subprotocols); + + if (!warningExpression.pattern().isEmpty()) + QTest::ignoreMessage(QtWarningMsg, warningExpression); + + socket.open(req, options); QTRY_COMPARE(socketConnectedSpy.count(), 1); QTRY_COMPARE(serverRequestSpy.count(), 1); @@ -656,6 +695,34 @@ void tst_QWebSocket::tst_openRequest() QNetworkRequest requestConnected = arguments.at(0).value<QNetworkRequest>(); QCOMPARE(requestConnected.url(), req.url()); QCOMPARE(requestConnected.rawHeader("X-Custom-Header"), req.rawHeader("X-Custom-Header")); + + if (subprotocols.isEmpty()) + QVERIFY(!requestConnected.hasRawHeader("Sec-WebSocket-Protocol")); + else + QCOMPARE(requestConnected.rawHeader("Sec-WebSocket-Protocol"), subprotocolHeader); + + + socket.close(); +} + +void tst_QWebSocket::tst_protocolAccessor() +{ + EchoServer echoServer; + + QWebSocket socket; + + QUrl url = QUrl(QStringLiteral("ws://") + echoServer.hostAddress().toString() + + QLatin1Char(':') + QString::number(echoServer.port())); + + QWebSocketHandshakeOptions options; + options.setSubprotocols({ "foo", "protocol2" }); + + socket.open(url, options); + + QTRY_COMPARE(socket.state(), QAbstractSocket::ConnectedState); + + QCOMPARE(socket.subprotocol(), "protocol2"); + socket.close(); } diff --git a/tests/auto/websockets/qwebsocketserver/tst_qwebsocketserver.cpp b/tests/auto/websockets/qwebsocketserver/tst_qwebsocketserver.cpp index b2dd95f..c25e864 100644 --- a/tests/auto/websockets/qwebsocketserver/tst_qwebsocketserver.cpp +++ b/tests/auto/websockets/qwebsocketserver/tst_qwebsocketserver.cpp @@ -37,6 +37,7 @@ #include <QtNetwork/qsslkey.h> #include <QtNetwork/qsslsocket.h> #endif +#include <QtWebSockets/QWebSocketHandshakeOptions> #include <QtWebSockets/QWebSocketServer> #include <QtWebSockets/QWebSocket> #include <QtWebSockets/QWebSocketCorsAuthenticator> @@ -106,6 +107,8 @@ private Q_SLOTS: void tst_settersAndGetters(); void tst_listening(); void tst_connectivity(); + void tst_protocols_data(); + void tst_protocols(); void tst_preSharedKey(); void tst_maxPendingConnections(); void tst_serverDestroyedWhileSocketConnected(); @@ -375,6 +378,48 @@ void tst_QWebSocketServer::tst_connectivity() QCOMPARE(serverErrorSpy.count(), 0); } +void tst_QWebSocketServer::tst_protocols_data() +{ + QTest::addColumn<QStringList>("clientProtocols"); + QTest::addColumn<QString>("expectedProtocol"); + QTest::addRow("none") << QStringList {} << QString {}; + QTest::addRow("same order as server") + << QStringList { "chat", "superchat" } << QStringLiteral("chat"); + QTest::addRow("different order from server") + << QStringList { "superchat", "chat" } << QStringLiteral("superchat"); + QTest::addRow("unsupported protocol") << QStringList { "foo" } << QString {}; + QTest::addRow("mixed supported/unsupported protocol") + << QStringList { "foo", "chat" } << QStringLiteral("chat"); +} + +void tst_QWebSocketServer::tst_protocols() +{ + QFETCH(QStringList, clientProtocols); + QFETCH(QString, expectedProtocol); + + QWebSocketServer server(QString(), QWebSocketServer::NonSecureMode); + server.setSupportedSubprotocols({ QStringLiteral("chat"), QStringLiteral("superchat") }); + + QSignalSpy newConnectionSpy(&server, &QWebSocketServer::newConnection); + + QVERIFY(server.listen()); + + QWebSocketHandshakeOptions opt; + opt.setSubprotocols(clientProtocols); + QWebSocket client; + client.open(server.serverUrl(), opt); + + QTRY_COMPARE(client.state(), QAbstractSocket::ConnectedState); + QTRY_COMPARE(newConnectionSpy.count(), 1); + + auto *serverSocket = server.nextPendingConnection(); + QVERIFY(serverSocket); + + QCOMPARE(client.subprotocol(), expectedProtocol); + QCOMPARE(serverSocket->subprotocol(), expectedProtocol); + QCOMPARE(serverSocket->handshakeOptions().subprotocols(), clientProtocols); +} + void tst_QWebSocketServer::tst_preSharedKey() { if (m_shouldSkipUnsupportedIpv6Test) |