diff options
-rw-r--r-- | README.md | 40 | ||||
-rw-r--r-- | examples/qmlwebsocketclient/qml/qmlwebsocketclient/main.qml | 39 | ||||
-rw-r--r-- | examples/qmlwebsocketclient/qmlwebsocketclient.pro | 8 | ||||
-rw-r--r-- | src/imports/qmlwebsockets/plugins.qmltypes | 46 | ||||
-rw-r--r-- | src/imports/qmlwebsockets/qmldir | 4 | ||||
-rw-r--r-- | src/imports/qmlwebsockets/qmlwebsockets.pro | 14 | ||||
-rw-r--r-- | src/imports/qmlwebsockets/qmlwebsockets_plugin.cpp | 10 | ||||
-rw-r--r-- | src/imports/qmlwebsockets/qqmlwebsocket.cpp | 162 | ||||
-rw-r--r-- | src/imports/qmlwebsockets/qqmlwebsocket.h | 55 |
9 files changed, 333 insertions, 45 deletions
@@ -1,38 +1,36 @@ ### Introduction -QWebSockets is a pure Qt implementation of WebSockets - both client and server. -It is implemented as a Qt source code module (.pri file), that can easily be embedded into existing Qt projects. It has no other dependencies that Qt. +`QtWebSockets` is a pure Qt implementation of WebSockets - both client and server. +It is implemented as a Qt add-on module, that can easily be embedded into existing Qt projects. It has no other dependencies than Qt. ### Features +* Client and server capable * Text and binary sockets * Frame-based and message-based signals -* Works through proxies * Strict Unicode checking - -### Restrictions -Non-characters (according [Unicode Standard 6.2](http://www.unicode.org/versions/Unicode6.2.0/)) are rejected by QWebSockets, **even if the UTF-8 sequence is valid**. -##### Rationale -The WebSocket specification is talking about _Valid UTF-8 codes and sequences_. Strictly speaking, UTF-xx encodings are reversible. That means, that the [66 non-character codes](http://www.unicode.org/faq/private_use.html#noncharacters) (including `U+FFFE` and `U+FFFF`), are valid UTF-8, and hence are perfectly acceptable within WebSocket text messages. -According to the Unicode standard, they SHOULD NOT be used in information interchange, but a [recent corrigendum](http://www.unicode.org/versions/corrigendum9.html) clarifies that non-characters CAN be exchanged. -However, non-characters are for internal use, and hence, they are implementation specific (e.g. non-characters can be used to carry meta-information). _They have to be interpreted._ -When used with `QString`, they are replaced with `U+FFFD - REPLACEMENT CHARACTER`, and rendered - non-standard - as a question mark (this is the `QString` rendering of the non-character `U+FFFD`: �). Browsers keep the control characters untouched (this is the browser rendering of the non-character `U+FDD0`: ). - -With QWebSockets, text messages are just that: a collection of human-readable characters. Text messages never have to be interpreted to be rendered correctly. In case, you still want to do implementation specific trickery, use binary messages instead. Indeed, if non-characters were allowed in text messages, then every text message has to be parsed, character-per-character, to find out if it contains special control codes, or a protocol should be devised that indicates whether the message contains that kind of control codes. We keep it simple: text is text and nothing more. +* WSS and proxy support ### Requirements Qt 5.x -### Usage -Include the .pri file into your Qt project -~~~ -include (qwebsockets.pri) -~~~ +### Build And Usage +Checkout the source code from gitorious +Go into the source directory and execute: + + qmake + make + make install + + +The last command will install `QtWebSockets` as a Qt module. + +To use, add `websockets` to the QT variable. + +`QT += websockets` ### Compliance -QWebSockets is compliant with [RFC6455](http://datatracker.ietf.org/doc/rfc6455/?include_text=1) and has been tested with the [Autobahn Testsuite](http://autobahn.ws/testsuite). -Only tests with **Unicode non-characters** do **not** pass from the Autobahn Testsuite (see [Restrictions](#Restrictions)). +`QtWebSockets` is compliant with [RFC6455](http://datatracker.ietf.org/doc/rfc6455/?include_text=1) and has been tested with the [Autobahn Testsuite](http://autobahn.ws/testsuite). ### Missing Features -* WSS protocol * Extensions and sub-protocols ### License diff --git a/examples/qmlwebsocketclient/qml/qmlwebsocketclient/main.qml b/examples/qmlwebsocketclient/qml/qmlwebsocketclient/main.qml index c038788..de3c89e 100644 --- a/examples/qmlwebsocketclient/qml/qmlwebsocketclient/main.qml +++ b/examples/qmlwebsocketclient/qml/qmlwebsocketclient/main.qml @@ -38,27 +38,56 @@ ** $QT_END_LICENSE$ ** ****************************************************************************/ - import QtQuick 2.0 -import Qt.Playground.WebSockets 1.0 +import Qt.WebSockets 1.0 Rectangle { width: 360 height: 360 WebSocket { - + id: socket + url: "ws://echo.websocket.org" + onTextMessageReceived: { + messageBox.text = messageBox.text + "\nReceived message: " + message + } + onStatusChanged: if (socket.status == WebSocket.Error) { + console.log("Error: " + socket.errorString) + } else if (socket.status == WebSocket.Open) { + socket.sendTextMessage("Hello World") + } else if (socket.status == WebSocket.Closed) { + messageBox.text += "\nSocket closed" + } + active: false } + WebSocket { + id: secureWebSocket + url: "wss://echo.websocket.org" + onTextMessageReceived: { + messageBox.text = messageBox.text + "\nReceived secure message: " + message + } + onStatusChanged: if (secureWebSocket.status == WebSocket.Error) { + console.log("Error: " + secureWebSocket.errorString) + } else if (secureWebSocket.status == WebSocket.Open) { + secureWebSocket.sendTextMessage("Hello Secure World") + } else if (secureWebSocket.status == WebSocket.Closed) { + messageBox.text += "\nSecure socket closed" + } + active: false + } Text { - text: qsTr("Hello World") + id: messageBox + text: socket.status == WebSocket.Open ? qsTr("Sending...") : qsTr("Welcome!") anchors.centerIn: parent } MouseArea { anchors.fill: parent onClicked: { - Qt.quit(); + socket.active = !socket.active + secureWebSocket.active = !secureWebSocket.active; + //Qt.quit(); } } } diff --git a/examples/qmlwebsocketclient/qmlwebsocketclient.pro b/examples/qmlwebsocketclient/qmlwebsocketclient.pro index e4a7d13..e7dc82a 100644 --- a/examples/qmlwebsocketclient/qmlwebsocketclient.pro +++ b/examples/qmlwebsocketclient/qmlwebsocketclient.pro @@ -1,11 +1,13 @@ -QT += qml quick +QT += core qml quick websockets TARGET = qmlwebsocketclient +TEMPLATE = app + CONFIG -= app_bundle SOURCES += main.cpp -RESOURCES += \ - data.qrc +RESOURCES += data.qrc +OTHER_FILES += qml/qmlwebsocketclient/main.qml diff --git a/src/imports/qmlwebsockets/plugins.qmltypes b/src/imports/qmlwebsockets/plugins.qmltypes new file mode 100644 index 0000000..8116fce --- /dev/null +++ b/src/imports/qmlwebsockets/plugins.qmltypes @@ -0,0 +1,46 @@ +import QtQuick.tooling 1.1 + +// This file describes the plugin-supplied types contained in the library. +// It is used for QML tooling purposes only. +// +// This file was auto-generated by: +// 'qmlplugindump -notrelocatable Qt.WebSockets 1.0' + +Module { + Component { + name: "QQmlWebSocket" + prototype: "QObject" + exports: ["Qt.WebSockets/WebSocket 1.0"] + exportMetaObjectRevisions: [0] + Enum { + name: "Status" + values: { + "Connecting": 0, + "Open": 1, + "Closing": 2, + "Closed": 3, + "Error": 4 + } + } + Property { name: "url"; type: "QUrl" } + Property { name: "status"; type: "Status"; isReadonly: true } + Property { name: "errorString"; type: "string"; isReadonly: true } + Property { name: "active"; type: "bool" } + Signal { + name: "textMessageReceived" + Parameter { name: "message"; type: "string" } + } + Signal { + name: "statusChanged" + Parameter { name: "status"; type: "Status" } + } + Signal { + name: "activeChanged" + Parameter { name: "isActive"; type: "bool" } + } + Method { + name: "sendTextMessage" + Parameter { name: "message"; type: "string" } + } + } +} diff --git a/src/imports/qmlwebsockets/qmldir b/src/imports/qmlwebsockets/qmldir index 549c286..4ae1035 100644 --- a/src/imports/qmlwebsockets/qmldir +++ b/src/imports/qmlwebsockets/qmldir @@ -1,3 +1,3 @@ -module Qt.Playground.WebSockets - +module Qt.WebSockets plugin declarative_qmlwebsockets +typeinfo plugins.qmltypes diff --git a/src/imports/qmlwebsockets/qmlwebsockets.pro b/src/imports/qmlwebsockets/qmlwebsockets.pro index 18d3713..2d3353e 100644 --- a/src/imports/qmlwebsockets/qmlwebsockets.pro +++ b/src/imports/qmlwebsockets/qmlwebsockets.pro @@ -1,11 +1,15 @@ QT = core websockets qml -TARGETPATH = Qt/Playground/WebSockets +TARGETPATH = Qt/WebSockets -HEADERS += qmlwebsockets_plugin.h \ - qqmlwebsocket.h +HEADERS += qmlwebsockets_plugin.h \ + qqmlwebsocket.h -SOURCES += qmlwebsockets_plugin.cpp \ - qqmlwebsocket.cpp +SOURCES += qmlwebsockets_plugin.cpp \ + qqmlwebsocket.cpp + +OTHER_FILES += qmldir + +DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0 load(qml_plugin) diff --git a/src/imports/qmlwebsockets/qmlwebsockets_plugin.cpp b/src/imports/qmlwebsockets/qmlwebsockets_plugin.cpp index ea2d1b3..c8d2cd6 100644 --- a/src/imports/qmlwebsockets/qmlwebsockets_plugin.cpp +++ b/src/imports/qmlwebsockets/qmlwebsockets_plugin.cpp @@ -45,12 +45,8 @@ void QmlWebsocket_plugin::registerTypes(const char *uri) { - Q_ASSERT(uri == QLatin1String("Qt.Playground.WebSockets")); + Q_ASSERT(uri == QLatin1String("Qt.WebSockets")); - int major = 1; - int minor = 0; - - // @uri Qt.Playground.WebSockets - - qmlRegisterType<QQmlWebSocket>(uri, major, minor, "WebSocket"); + // @uri Qt.WebSockets + qmlRegisterType<QQmlWebSocket>(uri, 1 /*major*/, 0 /*minor*/, "WebSocket"); } diff --git a/src/imports/qmlwebsockets/qqmlwebsocket.cpp b/src/imports/qmlwebsockets/qqmlwebsocket.cpp index 8858f24..49b1dfd 100644 --- a/src/imports/qmlwebsockets/qqmlwebsocket.cpp +++ b/src/imports/qmlwebsockets/qqmlwebsocket.cpp @@ -40,18 +40,176 @@ ****************************************************************************/ #include "qqmlwebsocket.h" +#include <QtWebSockets/QWebSocket> QQmlWebSocket::QQmlWebSocket(QObject *parent) : - QObject(parent) + QObject(parent), + m_webSocket(), + m_status(Closed), + m_url(), + m_isActive(false), + m_componentCompleted(true), + m_errorString() { } -void QQmlWebSocket::classBegin() +QQmlWebSocket::~QQmlWebSocket() +{ +} + +void QQmlWebSocket::sendTextMessage(const QString &message) +{ + if (m_status != Open) { + setErrorString(tr("Messages can only be send when the socket has Open status.")); + setStatus(Error); + return; + } + m_webSocket->write(message); +} + +QUrl QQmlWebSocket::url() const +{ + return m_url; +} + +void QQmlWebSocket::setUrl(const QUrl &url) +{ + if (m_url == url) { + return; + } + if (m_webSocket && (m_status == Open)) { + m_webSocket->close(); + } + m_url = url; + Q_EMIT urlChanged(); + if (m_webSocket) { + m_webSocket->open(m_url); + } +} + +QQmlWebSocket::Status QQmlWebSocket::status() const { + return m_status; +} +QString QQmlWebSocket::errorString() const +{ + return m_errorString; +} + +void QQmlWebSocket::classBegin() +{ + m_componentCompleted = false; + m_errorString = tr("QQmlWebSocket is not ready."); + m_status = Closed; } void QQmlWebSocket::componentComplete() { + m_webSocket.reset(new QWebSocket()); + connect(m_webSocket.data(), SIGNAL(textMessageReceived(QString)), this, SIGNAL(textMessageReceived(QString))); + connect(m_webSocket.data(), SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onError(QAbstractSocket::SocketError))); + connect(m_webSocket.data(), SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(onStateChanged(QAbstractSocket::SocketState))); + + m_componentCompleted = true; + + open(); +} + +void QQmlWebSocket::onError(QAbstractSocket::SocketError error) +{ + Q_UNUSED(error) + setErrorString(m_webSocket->errorString()); + setStatus(Error); +} + +void QQmlWebSocket::onStateChanged(QAbstractSocket::SocketState state) +{ + switch (state) + { + case QAbstractSocket::ConnectingState: + case QAbstractSocket::BoundState: + case QAbstractSocket::HostLookupState: + { + setStatus(Connecting); + break; + } + case QAbstractSocket::UnconnectedState: + { + setStatus(Closed); + break; + } + case QAbstractSocket::ConnectedState: + { + setStatus(Open); + break; + } + case QAbstractSocket::ClosingState: + { + setStatus(Closing); + break; + } + default: + { + setStatus(Connecting); + break; + } + } +} + +void QQmlWebSocket::setStatus(QQmlWebSocket::Status status) +{ + if (m_status == status) { + return; + } + m_status = status; + if (status != Error) { + setErrorString(); + } + Q_EMIT statusChanged(m_status); +} + +void QQmlWebSocket::setActive(bool active) +{ + if (m_isActive == active) { + return; + } + m_isActive = active; + Q_EMIT activeChanged(m_isActive); + if (!m_componentCompleted) { + return; + } + if (m_isActive) { + open(); + } else { + close(); + } +} + +bool QQmlWebSocket::isActive() const +{ + return m_isActive; +} +void QQmlWebSocket::open() +{ + if (m_componentCompleted && m_isActive && m_url.isValid() && m_webSocket) { + m_webSocket->open(m_url); + } +} + +void QQmlWebSocket::close() +{ + if (m_componentCompleted && m_webSocket) { + m_webSocket->close(); + } +} + +void QQmlWebSocket::setErrorString(QString errorString) +{ + if (m_errorString == errorString) { + return; + } + m_errorString = errorString; + Q_EMIT errorStringChanged(m_errorString); } diff --git a/src/imports/qmlwebsockets/qqmlwebsocket.h b/src/imports/qmlwebsockets/qqmlwebsocket.h index 20718c3..47cf6fa 100644 --- a/src/imports/qmlwebsockets/qqmlwebsocket.h +++ b/src/imports/qmlwebsockets/qqmlwebsocket.h @@ -44,6 +44,9 @@ #include <QObject> #include <QQmlParserStatus> +#include <QtQml> +#include <QScopedPointer> +#include <QWebSocket> class QQmlWebSocket : public QObject, public QQmlParserStatus { @@ -51,12 +54,64 @@ class QQmlWebSocket : public QObject, public QQmlParserStatus Q_DISABLE_COPY(QQmlWebSocket) Q_INTERFACES(QQmlParserStatus) + Q_ENUMS(Status) + Q_PROPERTY(QUrl url READ url WRITE setUrl NOTIFY urlChanged) + Q_PROPERTY(Status status READ status NOTIFY statusChanged) + Q_PROPERTY(QString errorString READ errorString NOTIFY errorStringChanged) + Q_PROPERTY(bool active READ isActive WRITE setActive NOTIFY activeChanged) + public: explicit QQmlWebSocket(QObject *parent = Q_NULLPTR); + virtual ~QQmlWebSocket(); + + enum Status + { + Connecting = 0, + Open = 1, + Closing = 2, + Closed = 3, + Error = 4 + }; + + QUrl url() const; + void setUrl(const QUrl &url); + Status status() const; + QString errorString() const; + + void setActive(bool active); + bool isActive() const; + +public Q_SLOTS: + void sendTextMessage(const QString &message); + + +Q_SIGNALS: + void textMessageReceived(QString message); + void statusChanged(Status status); + void activeChanged(bool isActive); + void errorStringChanged(QString errorString); + void urlChanged(); public: void classBegin() Q_DECL_OVERRIDE; void componentComplete() Q_DECL_OVERRIDE; + +private Q_SLOTS: + void onError(QAbstractSocket::SocketError error); + void onStateChanged(QAbstractSocket::SocketState state); + +private: + QScopedPointer<QWebSocket> m_webSocket; + Status m_status; + QUrl m_url; + bool m_isActive; + bool m_componentCompleted; + QString m_errorString; + + void setStatus(Status status); + void open(); + void close(); + void setErrorString(QString errorString = QString()); }; #endif // QQMLWEBSOCKET_H |