summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMilian Wolff <milian.wolff@kdab.com>2013-01-21 18:39:00 +0100
committerPierre Rossi <pierre.rossi@gmail.com>2013-11-01 13:57:44 +0100
commitac245f3ded33636def0f7e9f3afe914ec80973bf (patch)
treee16f6d88592a53442effcb9a9356efa6abff982c /src
parentfe7c20fbe3e4ada56cd9bc161e1e4376a1bd1019 (diff)
downloadqtwebchannel-ac245f3ded33636def0f7e9f3afe914ec80973bf.tar.gz
Fix long-polling issue of dropped events.
This was especially noticeable when multiple timers where running. In such cases only the very first signal would get submitted, then the poll socket got closed. Consecutive calls to broadcast got lost. Now we save the data passed to QWebChannel::broadcast and submit it all in one go whenever a poll request comes in. This fixes the timer issue and all signals get submitted. In the long term this should all be properly rewritten using a WebSocket server on the QML side and a WebSocket client on the HTML side. Change-Id: I3b26f073978cf14b9bd045ec28b0279128c3c9e6 Reviewed-by: Pierre Rossi <pierre.rossi@gmail.com>
Diffstat (limited to 'src')
-rw-r--r--src/qwebchannel.cpp59
-rw-r--r--src/webchannel-iframe.html50
2 files changed, 79 insertions, 30 deletions
diff --git a/src/qwebchannel.cpp b/src/qwebchannel.cpp
index d51dd72..5bfc0e1 100644
--- a/src/qwebchannel.cpp
+++ b/src/qwebchannel.cpp
@@ -48,6 +48,9 @@
#include <QTcpSocket>
#include <QTimer>
#include <QUuid>
+#include <QJsonDocument>
+#include <QJsonObject>
+#include <QJsonArray>
QWebChannelResponder::QWebChannelResponder(QTcpSocket* s)
: QObject(s)
@@ -121,8 +124,10 @@ public:
QUrl baseUrl;
QTcpServer* server;
QString secret;
- typedef QMultiMap<QString, QPointer<QTcpSocket> > SubscriberMap;
- SubscriberMap subscribers;
+ typedef QPair<QString, QString> Broadcast;
+ typedef QVector< Broadcast > BroadcastList;
+ BroadcastList pendingBroadcasts;
+ QPointer<QTcpSocket> pollSocket;
bool starting;
QMap<QTcpSocket*, HttpRequestData> pendingData;
@@ -149,11 +154,12 @@ public:
void handleHttpRequest(QTcpSocket* socket, const HttpRequestData& data);
+ void submitBroadcasts(QTcpSocket* socket);
+
public slots:
void init();
void broadcast(const QString&, const QString&);
void service();
- void onSocketDelete(QObject*);
void handleSocketData();
void handleSocketData(QTcpSocket*);
@@ -163,28 +169,32 @@ signals:
void noPortAvailable();
};
-void QWebChannelPrivate::onSocketDelete(QObject * o)
+void QWebChannelPrivate::submitBroadcasts(QTcpSocket* socket)
{
- for (SubscriberMap::iterator it = subscribers.begin(); it != subscribers.end(); ++it) {
- if (it.value() != o)
- continue;
- subscribers.erase(it);
- return;
+ socket->write("HTTP/1.1 200 OK\r\n"
+ "Content-Type: text/json\r\n"
+ "\r\n");
+ QJsonArray array;
+ foreach (const Broadcast& broadcast, pendingBroadcasts) {
+ QJsonObject obj;
+ obj.insert(QStringLiteral("id"), broadcast.first);
+ obj.insert(QStringLiteral("data"), broadcast.second);
+ array.append(obj);
}
+ pendingBroadcasts.clear();
+ QJsonDocument doc;
+ doc.setArray(array);
+ socket->write(doc.toJson());
+ socket->close();
}
void QWebChannelPrivate::broadcast(const QString& id, const QString& message)
{
- QList<QPointer<QTcpSocket> > sockets = subscribers.values(id);
+ pendingBroadcasts << qMakePair(id, message);
- foreach(QPointer<QTcpSocket> socket, sockets) {
- if (!socket)
- continue;
- socket->write("HTTP/1.1 200 OK\r\n"
- "Content-Type: text/json\r\n"
- "\r\n");
- socket->write(message.toUtf8());
- socket->close();
+ if (pollSocket) {
+ submitBroadcasts(pollSocket);
+ pollSocket = 0;
}
}
@@ -291,10 +301,15 @@ void QWebChannelPrivate::handleHttpRequest(QTcpSocket *socket, const HttpRequest
if (autoRetain)
responder->retain();
emit execute(data.content, responder);
- } else if (type == "SUBSCRIBE") {
- connect(socket, SIGNAL(disconnected()), socket, SLOT(deleteLater()));
- connect(socket, SIGNAL(destroyed(QObject*)), this, SLOT(onSocketDelete(QObject*)));
- subscribers.insert(data.content, socket);
+ } else if (type == "POLL") {
+ ///FIXME: this should be rewritten using a proper web socket approach
+ if (!pendingBroadcasts.isEmpty()) {
+ submitBroadcasts(socket);
+ } else {
+ // defer transmission until broadcast comes in
+ pollSocket = socket;
+ connect(socket, SIGNAL(disconnected()), socket, SLOT(deleteLater()));
+ }
}
} else if (method == "GET") {
if (type == "webchannel.js") {
diff --git a/src/webchannel-iframe.html b/src/webchannel-iframe.html
index 1f93eac..a6addbe 100644
--- a/src/webchannel-iframe.html
+++ b/src/webchannel-iframe.html
@@ -1,35 +1,69 @@
<html>
<head>
<script>
+ /**
+ * Send a POST request to the QWebChannel socket with the given stringified JSON @p data
+ * and call @p callback when the response if fully available.
+ */
function exec(data, callback, type)
{
type = type || 'EXEC';
var xhr = new XMLHttpRequest;
+
xhr.open("POST", "../" + type, true);
+ xhr.setRequestHeader("Content-type", "text/json");
xhr.onreadystatechange = function() {
if (xhr.readyState != 4 || xhr.status != 200)
return;
+ // data is fully available now
callback(xhr.responseText);
};
xhr.send(data);
}
- function poll(id, callback)
+ /**
+ * maps subscription IDs to list of callbacks.
+ */
+ var subscriptions = {};
+ /**
+ * Poll for new broadcasts and delegate payload to subscribed callbacks
+ */
+ function poll()
{
- exec(id, function(response) {
- callback(response);
- setTimeout(function() { poll(id, callback); }, 0);
- }, 'SUBSCRIBE');
+ exec("", function(response) {
+ var data = JSON.parse(response);
+ for (var i = 0; i < data.length; ++i) {
+ // delegate payload to subscribed callbacks
+ var broadcast = data[i];
+ var callbacks = subscriptions[broadcast.id];
+ for(var j = 0; j < callbacks.length; ++j) {
+ callbacks[j](broadcast.data);
+ }
+ }
+ // reschedule a poll
+ setTimeout(poll, 0);
+ }, 'POLL');
}
window.addEventListener("message", function(event) {
var data = JSON.parse(event.data);
- function callback(r) { window.parent.postMessage(JSON.stringify({ type: "callback", id: data.id, payload: r }), "*"); }
+ function callback(r) {
+ // post message to actual user-defined HTML client
+ window.parent.postMessage(JSON.stringify({ type: "callback", id: data.id, payload: r }), "*");
+ }
if (data.type == "EXEC")
exec(data.payload, callback);
- else if (data.type == "SUBSCRIBE")
- poll(data.id, callback);
+ else if (data.type == "SUBSCRIBE") {
+ if (subscriptions[data.id]) {
+ subscriptions[data.id].append(callback);
+ } else {
+ subscriptions[data.id] = [callback];
+ }
+ }
});
+
+ // start initial polling for data
+ window.onload = poll;
</script>
</head>
<body>