added JSONRPC.Version and JSONRPC.SetNotificationStatus

This commit is contained in:
Michael Zanetti 2014-03-30 01:14:39 +01:00
parent 2492b0fdda
commit 61a7a68ee0
11 changed files with 205 additions and 81 deletions

View File

@ -24,23 +24,86 @@
#include <QStringList>
JsonRPCServer::JsonRPCServer(QObject *parent):
QObject(parent),
JsonHandler(parent),
#ifdef TESTING_ENABLED
m_tcpServer(new MockTcpServer(this))
#else
m_tcpServer(new TcpServer(this))
#endif
{
connect(m_tcpServer, SIGNAL(jsonDataAvailable(int,QByteArray)), this, SLOT(processData(int,QByteArray)));
// First, define our own JSONRPC methods
QVariantMap returns;
QVariantMap params;
params.clear(); returns.clear();
setDescription("Introspect", "Introspect this API.");
setParams("Introspect", params);
returns.insert("methods", "object");
returns.insert("types", "object");
setReturns("Introspect", returns);
params.clear(); returns.clear();
setDescription("Version", "Version of this Guh/JSONRPC interface.");
setParams("Version", params);
returns.insert("version", "string");
setReturns("Version", returns);
params.clear(); returns.clear();
setDescription("SetNotificationStatus", "Enable/Disable notifications for this connections.");
setParams("SetNotificationStatus", params);
returns.insert("status", "string");
// returns.insert("enabled", "bool");
setReturns("SetNotificationStatus", returns);
// Now set up the logic
connect(m_tcpServer, SIGNAL(clientConnected(const QUuid &)), this, SLOT(clientConnected(const QUuid &)));
connect(m_tcpServer, SIGNAL(clientDisconnected(const QUuid &)), this, SLOT(clientDisconnected(const QUuid &)));
connect(m_tcpServer, SIGNAL(dataAvailable(const QUuid &, QByteArray)), this, SLOT(processData(const QUuid &, QByteArray)));
m_tcpServer->startServer();
registerHandler(this);
registerHandler(new DeviceHandler(this));
registerHandler(new ActionHandler(this));
registerHandler(new RulesHandler(this));
}
void JsonRPCServer::processData(int clientId, const QByteArray &jsonData)
QString JsonRPCServer::name() const
{
return QStringLiteral("JSONRPC");
}
QVariantMap JsonRPCServer::Introspect(const QVariantMap &params) const
{
QVariantMap data;
data.insert("types", JsonTypes::allTypes());
QVariantMap methods;
foreach (JsonHandler *handler, m_handlers) {
// qDebug() << "got handler" << handler->name() << handler->introspect();
methods.unite(handler->introspect());
}
data.insert("methods", methods);
return data;
}
QVariantMap JsonRPCServer::Version(const QVariantMap &params) const
{
QVariantMap data;
data.insert("version", "0.0.0");
return data;
}
QVariantMap JsonRPCServer::SetNotificationStatus(const QVariantMap &params)
{
QUuid clientId = this->property("clientId").toUuid();
// qDebug() << "got client socket" << clientId;
m_clients[clientId] = params.value("enabled").toBool();
QVariantMap returns;
returns.insert("status", "success");
returns.insert("enabled", m_clients[clientId]);
return returns;
}
void JsonRPCServer::processData(const QUuid &clientId, const QByteArray &jsonData)
{
QJsonParseError error;
QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &error);
@ -48,7 +111,7 @@ void JsonRPCServer::processData(int clientId, const QByteArray &jsonData)
if(error.error != QJsonParseError::NoError) {
qDebug() << "failed to parse data" << jsonData << ":" << error.errorString();
}
qDebug() << "-------------------------\n" << jsonDoc.toJson();
// qDebug() << "-------------------------\n" << jsonDoc.toJson();
QVariantMap message = jsonDoc.toVariant().toMap();
@ -72,21 +135,6 @@ void JsonRPCServer::processData(int clientId, const QByteArray &jsonData)
qDebug() << "got:" << targetNamespace << method << params;
emit commandReceived(targetNamespace, method, params);
if (targetNamespace == "JSONRPC") {
if (method == "Introspect") {
QVariantMap data;
data.insert("types", JsonTypes::allTypes());
QVariantMap methods;
foreach (JsonHandler *handler, m_handlers) {
// qDebug() << "got handler" << handler->name() << handler->introspect();
methods.unite(handler->introspect());
}
data.insert("methods", methods);
sendResponse(clientId, commandId, data);
return;
}
}
JsonHandler *handler = m_handlers.value(targetNamespace);
if (!handler) {
sendErrorResponse(clientId, commandId, "No such namespace");
@ -100,9 +148,13 @@ void JsonRPCServer::processData(int clientId, const QByteArray &jsonData)
sendErrorResponse(clientId, commandId, "Invalid params");
return;
}
// Hack: attach clientId to handler to be able to handle the JSONRPC methods. Do not use this outside of jsonrpcserver
handler->setProperty("clientId", clientId);
QVariantMap returns;
QMetaObject::invokeMethod(handler, method.toLatin1().data(), Q_RETURN_ARG(QVariantMap, returns), Q_ARG(QVariantMap, params));
Q_ASSERT(handler->validateReturns(method, returns));
Q_ASSERT((targetNamespace == "JSONRPC" && method == "Introspect") || handler->validateReturns(method, returns));
sendResponse(clientId, commandId, returns);
}
@ -111,7 +163,18 @@ void JsonRPCServer::registerHandler(JsonHandler *handler)
m_handlers.insert(handler->name(), handler);
}
void JsonRPCServer::sendResponse(int clientId, int commandId, const QVariantMap &params)
void JsonRPCServer::clientConnected(const QUuid &clientId)
{
// Notifications disabled by default
m_clients.insert(clientId, false);
}
void JsonRPCServer::clientDisconnected(const QUuid &clientId)
{
m_clients.remove(clientId);
}
void JsonRPCServer::sendResponse(const QUuid &clientId, int commandId, const QVariantMap &params)
{
QVariantMap rsp;
rsp.insert("id", commandId);
@ -119,10 +182,10 @@ void JsonRPCServer::sendResponse(int clientId, int commandId, const QVariantMap
rsp.insert("params", params);
QJsonDocument jsonDoc = QJsonDocument::fromVariant(rsp);
m_tcpServer->sendResponse(clientId, jsonDoc.toJson());
m_tcpServer->sendData(clientId, jsonDoc.toJson());
}
void JsonRPCServer::sendErrorResponse(int clientId, int commandId, const QString &error)
void JsonRPCServer::sendErrorResponse(const QUuid &clientId, int commandId, const QString &error)
{
QVariantMap rsp;
rsp.insert("id", commandId);
@ -130,7 +193,7 @@ void JsonRPCServer::sendErrorResponse(int clientId, int commandId, const QString
rsp.insert("error", error);
QJsonDocument jsonDoc = QJsonDocument::fromVariant(rsp);
m_tcpServer->sendResponse(clientId, jsonDoc.toJson());
m_tcpServer->sendData(clientId, jsonDoc.toJson());
}

View File

@ -4,6 +4,7 @@
#include "deviceclass.h"
#include "action.h"
#include "event.h"
#include "jsonhandler.h"
#include <QObject>
#include <QVariantMap>
@ -15,25 +16,33 @@ class MockTcpServer;
class TcpServer;
#endif
class Device;
class JsonHandler;
class JsonRPCServer: public QObject
class JsonRPCServer: public JsonHandler
{
Q_OBJECT
public:
JsonRPCServer(QObject *parent = 0);
// JsonHandler API implementation
QString name() const;
Q_INVOKABLE QVariantMap Introspect(const QVariantMap &params) const;
Q_INVOKABLE QVariantMap Version(const QVariantMap &params) const;
Q_INVOKABLE QVariantMap SetNotificationStatus(const QVariantMap &params);
signals:
void commandReceived(const QString &targetNamespace, const QString &command, const QVariantMap &params);
private slots:
void processData(int clientId, const QByteArray &jsonData);
void clientConnected(const QUuid &clientId);
void clientDisconnected(const QUuid &clientId);
void processData(const QUuid &clientId, const QByteArray &jsonData);
private:
void registerHandler(JsonHandler *handler);
void sendResponse(int clientId, int commandId, const QVariantMap &params = QVariantMap());
void sendErrorResponse(int clientId, int commandId, const QString &error);
void sendResponse(const QUuid &clientId, int commandId, const QVariantMap &params = QVariantMap());
void sendErrorResponse(const QUuid &clientId, int commandId, const QString &error);
private:
#ifdef TESTING_ENABLED
@ -42,6 +51,9 @@ private:
TcpServer *m_tcpServer;
#endif
QHash<QString, JsonHandler*> m_handlers;
// clientId, notificationsEnabled
QHash<QUuid, bool> m_clients;
};
#endif

View File

@ -95,7 +95,6 @@ void JsonTypes::init()
s_rule.insert("actions", QVariantList() << actionRef());
s_rule.insert("states", QVariantList() << stateRef());
s_initialized = true;
}

View File

@ -44,7 +44,6 @@ public:
DECLARE_TYPE(basicTypes, "BasicType")
DECLARE_TYPE(ruleTypes, "RuleType")
DECLARE_OBJECT(paramType, "ParamType")
DECLARE_OBJECT(param, "Param")
DECLARE_OBJECT(stateType, "StateType")

View File

@ -17,7 +17,14 @@ TcpServer::TcpServer(QObject *parent) :
}
void TcpServer::sendResponse(int clientId, const QByteArray &data)
void TcpServer::sendData(const QList<QUuid> &clients, const QByteArray &data)
{
foreach (const QUuid &client, clients) {
sendData(client, data);
}
}
void TcpServer::sendData(const QUuid &clientId, const QByteArray &data)
{
QTcpSocket *client = m_clientList.value(clientId);
if (client) {
@ -32,14 +39,15 @@ void TcpServer::newClientConnected()
QTcpSocket *newConnection = server->nextPendingConnection();
qDebug() << "new client connected:" << newConnection->peerAddress().toString();
QUuid clientId = QUuid::createUuid();
// append the new client to the client list
m_clientList.insert(m_clientList.count(), newConnection);
m_clientList.insert(clientId, newConnection);
connect(newConnection, SIGNAL(readyRead()),this,SLOT(readPackage()));
connect(newConnection,SIGNAL(disconnected()),this,SLOT(clientDisconnected()));
connect(newConnection,SIGNAL(disconnected()),this,SLOT(slotClientDisconnected()));
// TODO: properly handle this with jsonrpcserver
newConnection->write("{\n \"id\":0,\n \"status\": \"connected\",\n \"server\":\"Hive JSONRPC Interface\",\n \"version\":\"0.0.0\"\n}\n");
emit clientConnected(clientId);
}
@ -53,16 +61,18 @@ void TcpServer::readPackage()
qDebug() << "line in:" << dataLine;
message.append(dataLine);
if(dataLine.endsWith('\n')){
emit jsonDataAvailable(m_clientList.key(client), message);
emit dataAvailable(m_clientList.key(client), message);
message.clear();
}
}
}
void TcpServer::clientDisconnected()
void TcpServer::slotClientDisconnected()
{
QTcpSocket *client = qobject_cast<QTcpSocket*>(sender());
qDebug() << "client disconnected:" << client->peerAddress().toString();
QUuid clientId = m_clientList.key(client);
m_clientList.take(clientId)->deleteLater();
}
bool TcpServer::startServer()
@ -73,7 +83,7 @@ bool TcpServer::startServer()
if(server->listen(address, 1234)) {
qDebug() << "server listening on" << address.toString();
connect(server, SIGNAL(newConnection()), SLOT(newClientConnected()));
m_serverList.insert(m_serverList.count(), server);
m_serverList.insert(QUuid::createUuid(), server);
} else {
qDebug() << "ERROR: can not listening to" << address.toString();
delete server;
@ -99,11 +109,4 @@ bool TcpServer::stopServer()
return true;
}
void TcpServer::sendToAll(QByteArray data)
{
foreach(QTcpSocket *client,m_clientList){
client->write(data);
}
}

View File

@ -5,6 +5,7 @@
#include <QNetworkInterface>
#include <QTcpServer>
#include <QTcpSocket>
#include <QUuid>
class TcpServer : public QObject
{
@ -12,24 +13,26 @@ class TcpServer : public QObject
public:
explicit TcpServer(QObject *parent = 0);
void sendResponse(int clientId, const QByteArray &data);
void sendData(const QUuid &clientId, const QByteArray &data);
void sendData(const QList<QUuid> &clients, const QByteArray &data);
private:
QHash<int, QTcpServer*> m_serverList;
QHash<int, QTcpSocket*> m_clientList;
QHash<QUuid, QTcpServer*> m_serverList;
QHash<QUuid, QTcpSocket*> m_clientList;
signals:
void jsonDataAvailable(int clientId, const QByteArray &data);
void clientConnected(const QUuid &clientId);
void clientDisconnected(const QUuid &clientId);
void dataAvailable(const QUuid &clientId, const QByteArray &data);
private slots:
void newClientConnected();
void readPackage();
void clientDisconnected();
void slotClientDisconnected();
public slots:
bool startServer();
bool stopServer();
void sendToAll(QByteArray data);
};
#endif // TCPSERVER_H

View File

@ -13,7 +13,7 @@ MockTcpServer::~MockTcpServer()
s_allServers.removeAll(this);
}
void MockTcpServer::sendResponse(int clientId, const QByteArray &data)
void MockTcpServer::sendData(const QUuid &clientId, const QByteArray &data)
{
emit outgoingData(clientId, data);
}
@ -23,9 +23,9 @@ QList<MockTcpServer *> MockTcpServer::servers()
return s_allServers;
}
void MockTcpServer::injectData(int clientId, const QByteArray &data)
void MockTcpServer::injectData(const QUuid &clientId, const QByteArray &data)
{
emit jsonDataAvailable(clientId, data);
emit dataAvailable(clientId, data);
}
bool MockTcpServer::startServer()

View File

@ -12,17 +12,17 @@ public:
explicit MockTcpServer(QObject *parent = 0);
~MockTcpServer();
void sendResponse(int clientId, const QByteArray &data);
void sendData(const QUuid &clientId, const QByteArray &data);
/************** Used for testing **************************/
static QList<MockTcpServer*> servers();
void injectData(int clientId, const QByteArray &data);
void injectData(const QUuid &clientId, const QByteArray &data);
signals:
void outgoingData(int clientId, const QByteArray &data);
void outgoingData(const QUuid &clientId, const QByteArray &data);
/************** Used for testing **************************/
signals:
void jsonDataAvailable(int clientId, const QByteArray &data);
void dataAvailable(const QUuid &clientId, const QByteArray &data);
public slots:
bool startServer();

View File

@ -13,12 +13,22 @@ class TestJSONRPC: public QObject
Q_OBJECT
private slots:
void initTestcase();
void introspect();
void getSupportedDevices();
void enableDisableNotifications_data();
void enableDisableNotifications();
void version();
private:
QVariant injectAndWait(const QByteArray data);
private:
MockTcpServer *m_mockTcpServer;
int m_clientId;
QUuid m_clientId;
};
void TestJSONRPC::initTestcase()
@ -36,12 +46,29 @@ void TestJSONRPC::initTestcase()
// If Hive should create more than one TcpServer at some point, this needs to be updated.
QCOMPARE(MockTcpServer::servers().count(), 1);
m_mockTcpServer = MockTcpServer::servers().first();
m_clientId = 0;
m_clientId = QUuid::createUuid();
}
QVariant TestJSONRPC::injectAndWait(const QByteArray data)
{
QSignalSpy spy(m_mockTcpServer, SIGNAL(outgoingData(QUuid,QByteArray)));
m_mockTcpServer->injectData(m_clientId, data);
if (spy.count() == 0) {
spy.wait();
}
// Make sure the response it a valid JSON string
QJsonParseError error;
QJsonDocument jsonDoc = QJsonDocument::fromJson(spy.takeFirst().last().toByteArray(), &error);
return jsonDoc.toVariant();
}
void TestJSONRPC::introspect()
{
QSignalSpy spy(m_mockTcpServer, SIGNAL(outgoingData(int,QByteArray)));
QSignalSpy spy(m_mockTcpServer, SIGNAL(outgoingData(QUuid,QByteArray)));
QVERIFY(spy.isValid());
m_mockTcpServer->injectData(m_clientId, "{\"id\":42, \"method\":\"JSONRPC.Introspect\"}");
@ -54,7 +81,7 @@ void TestJSONRPC::introspect()
QVERIFY(spy.count() == 1);
// Make sure the introspect response goes to the correct clientId
QCOMPARE(spy.first().first().toInt(), m_clientId);
QCOMPARE(spy.first().first().toString(), m_clientId.toString());
// Make sure the response it a valid JSON string
QJsonParseError error;
@ -67,22 +94,7 @@ void TestJSONRPC::introspect()
void TestJSONRPC::getSupportedDevices()
{
QSignalSpy spy(m_mockTcpServer, SIGNAL(outgoingData(int,QByteArray)));
m_mockTcpServer->injectData(m_clientId, "{\"id\":1, \"method\":\"Devices.GetSupportedDevices\"}");
if (spy.count() == 0) {
spy.wait();
}
// Make sure the response it a valid JSON string
QJsonParseError error;
QJsonDocument jsonDoc = QJsonDocument::fromJson(spy.first().last().toByteArray(), &error);
QCOMPARE(error.error, QJsonParseError::NoError);
QVariant supportedDevices = jsonDoc.toVariant();
qDebug() << spy.first().last();
QVariant supportedDevices = injectAndWait("{\"id\":1, \"method\":\"Devices.GetSupportedDevices\"}");
// Make sure there is exactly 1 supported device class with the name Mock Wifi Device
QCOMPARE(supportedDevices.toMap().value("params").toMap().value("deviceClasses").toList().count(), 1);
@ -90,5 +102,31 @@ void TestJSONRPC::getSupportedDevices()
QCOMPARE(deviceName, QString("Mock WiFi Device"));
}
void TestJSONRPC::enableDisableNotifications_data()
{
QTest::addColumn<QString>("enabled");
QTest::newRow("enabled") << "true";
QTest::newRow("disabled") << "false";
}
void TestJSONRPC::enableDisableNotifications()
{
QFETCH(QString, enabled);
QVariant response = injectAndWait(QString("{\"id\":1, \"method\":\"JSONRPC.SetNotificationStatus\", \"params\":{\"enabled\": " + enabled + " }}").toLatin1());
QCOMPARE(response.toMap().value("params").toMap().value("status").toString(), QString("success"));
QCOMPARE(response.toMap().value("params").toMap().value("enabled").toString(), enabled);
}
void TestJSONRPC::version()
{
QVariant response = injectAndWait("{\"id\":1, \"method\":\"JSONRPC.Version\"}");
QCOMPARE(response.toMap().value("params").toMap().value("version").toString(), QString("0.0.0"));
}
QTEST_MAIN(TestJSONRPC)
#include "testjsonrpc.moc"

View File

@ -3,5 +3,5 @@
if [ -z $2 ]; then
echo "usage: $0 host deviceClassId"
else
(echo '{"id":1, "method":"Devices.GetTriggerTypes", "params":{"deviceClassId":"'$2'"}}'; sleep 1) | nc $1 1234
(echo '{"id":1, "method":"Devices.GetEventTypes", "params":{"deviceClassId":"'$2'"}}'; sleep 1) | nc $1 1234
fi

7
tests/scripts/getversion.sh Executable file
View File

@ -0,0 +1,7 @@
#!/bin/bash
if [ -z $1 ]; then
echo "usage: $0 host"
else
(echo '{"id":1, "method": "JSONRPC.Version"}'; sleep 1) | nc $1 1234
fi