Json write (#313)

* always output latest version of config file to webui

* fix permissions after default config export

* tune code

* set permissions for exported effects

* use qt setperm instead of chmod
update effects code style a bit

* add fallback when config is not readable

* ui: when sending config, convert to utf8 to save size and avoid jumbo frames (todo: minify it)
jsonclient: add some constants for websocket frames (taken from https://github.com/zaphoyd/websocketpp/blob/master/websocketpp/frame.hpp)

* webui:
refactory of websocket connector
sended json data is always convert to utf8
This commit is contained in:
redPanther 2016-12-05 11:10:44 +01:00 committed by GitHub
parent 8d55154164
commit 9428586195
4 changed files with 149 additions and 48 deletions

View File

@ -116,7 +116,20 @@ function initWebSocket()
} }
} }
function sendToHyperion(command, subcommand, msg)
{
if (typeof subcommand != 'undefined' && subcommand.length > 0)
subcommand = ',"subcommand":"'+subcommand+'"';
else
subcommand = "";
if (typeof msg != 'undefined' && msg.length > 0)
msg = ","+msg;
else
msg = "";
websocket.send(encode_utf8('{"command":"'+command+'", "tan":'+wsTan+subcommand+msg+'}'));
}
// ----------------------------------------------------------- // -----------------------------------------------------------
// wrapped server commands // wrapped server commands
@ -124,55 +137,55 @@ function initWebSocket()
// also used for watchdog // also used for watchdog
function requestServerInfo() { function requestServerInfo() {
watchdog++; watchdog++;
websocket.send('{"command":"serverinfo", "tan":'+wsTan+'}'); sendToHyperion("serverinfo");
} }
function requestServerConfigSchema() { function requestServerConfigSchema() {
websocket.send('{"command":"config", "tan":'+wsTan+',"subcommand":"getschema"}'); sendToHyperion("config","getschema");
} }
function requestServerConfig() { function requestServerConfig() {
websocket.send('{"command":"config", "tan":'+wsTan+',"subcommand":"getconfig"}'); sendToHyperion("config", "getconfig");
} }
function requestServerConfigReload() { function requestServerConfigReload() {
websocket.send('{"command":"config", "tan":'+wsTan+',"subcommand":"reload"}'); sendToHyperion("config", "reload");
} }
function requestLedColorsStart() { function requestLedColorsStart() {
ledStreamActive=true; ledStreamActive=true;
websocket.send('{"command":"ledcolors", "tan":'+wsTan+',"subcommand":"ledstream-start"}'); sendToHyperion("ledcolors", "ledstream-start");
} }
function requestLedColorsStop() { function requestLedColorsStop() {
ledStreamActive=false; ledStreamActive=false;
websocket.send('{"command":"ledcolors", "tan":'+wsTan+',"subcommand":"ledstream-stop"}'); sendToHyperion("ledcolors", "ledstream-stop");
} }
function requestPriorityClear() { function requestPriorityClear() {
websocket.send('{"command":"clear", "tan":'+wsTan+', "priority":1}'); sendToHyperion("clear", "", '"priority":1');
} }
function requestPlayEffect(effectName) { function requestPlayEffect(effectName) {
websocket.send('{"command":"effect", "tan":'+wsTan+',"effect":{"name":"'+effectName+'"},"priority":1}'); sendToHyperion("effect", "", '"effect":{"name":"'+effectName+'"},"priority":1');
} }
function requestSetColor(r,g,b) { function requestSetColor(r,g,b) {
websocket.send('{"command":"color", "tan":'+wsTan+', "color":['+r+','+g+','+b+'], "priority":1}'); sendToHyperion("color", "", '"color":['+r+','+g+','+b+'], "priority":1');
} }
function requestSetComponentState(comp, state){ function requestSetComponentState(comp, state){
state_str = state?"true":"false"; state_str = state ? "true" : "false";
websocket.send('{"command":"componentstate", "tan":'+wsTan+',"componentstate":{"component":"'+comp+'","state":'+state_str+'}}'); sendToHyperion("componentstate", "", '"componentstate":{"component":"'+comp+'","state":'+state_str+'}');
console.log(comp+' state: '+state_str); console.log(comp+' state: '+state_str);
} }
function requestSetSource(prio) function requestSetSource(prio)
{ {
if ( prio == "auto" ) if ( prio == "auto" )
websocket.send('{"command":"sourceselect", "tan":'+wsTan+', "auto" : true}'); sendToHyperion("sourceselect", "", '"auto":true');
else else
websocket.send('{"command":"sourceselect", "tan":'+wsTan+', "priority" : '+prio+'}'); sendToHyperion("sourceselect", "", '"priority":'+prio);
} }
function requestWriteConfig(config) function requestWriteConfig(config)
@ -181,30 +194,36 @@ function requestWriteConfig(config)
jQuery.each(config, function(i, val) { jQuery.each(config, function(i, val) {
complete_config[i] = val; complete_config[i] = val;
}); });
websocket.send('{"command":"config","subcommand":"setconfig", "tan":'+wsTan+', "config":'+JSON.stringify(complete_config)+'}');
var config_str = JSON.stringify(complete_config);
console.log(config_str.length);
sendToHyperion("config","setconfig", '"config":'+config_str);
} }
function requestWriteEffect(effectName,effectPy,effectArgs) function requestWriteEffect(effectName,effectPy,effectArgs)
{ {
var cutArgs = effectArgs.slice(1, -1); var cutArgs = effectArgs.slice(1, -1);
websocket.send('{"command":"create-effect","name":"'+effectName+'", "script":"'+effectPy+'", '+cutArgs+'}'); sendToHyperion("create-effect", "", '"name":"'+effectName+'", "script":"'+effectPy+'", '+cutArgs);
} }
function requestTestEffect(effectName,effectPy,effectArgs) { function requestTestEffect(effectName,effectPy,effectArgs) {
websocket.send('{"command":"effect", "tan":'+wsTan+',"effect":{"name":"'+effectName+'", "args":'+effectArgs+'},"priority":1, "pythonScript":"'+effectPy+'"}'); sendToHyperion("effect", "", '"effect":{"name":"'+effectName+'", "args":'+effectArgs+'},"priority":1, "pythonScript":"'+effectPy+'"}');
} }
function requestDeleteEffect(effectName) { function requestDeleteEffect(effectName)
websocket.send('{"command":"delete-effect", "tan":'+wsTan+',"name":"'+effectName+'"}'); {
sendToHyperion("delete-effect", "", '"name":"'+effectName+'"');
} }
function requestLoggingStart() { function requestLoggingStart()
{
loggingStreamActive=true; loggingStreamActive=true;
websocket.send('{"command":"logging", "tan":'+wsTan+',"subcommand":"start"}'); sendToHyperion("logging", "start");
} }
function requestLoggingStop() { function requestLoggingStop()
{
loggingStreamActive=false; loggingStreamActive=false;
websocket.send('{"command":"logging", "tan":'+wsTan+',"subcommand":"stop"}'); sendToHyperion("logging", "stop");
} }

View File

@ -120,30 +120,41 @@ function createJsonEditor(container,schema,setconfig)
return editor; return editor;
} }
function createSelGroup(group){ function createSelGroup(group)
{
var el = document.createElement('optgroup'); var el = document.createElement('optgroup');
el.setAttribute('label', group); el.setAttribute('label', group);
return el return el;
} }
function createSelOpt(opt){ function createSelOpt(opt)
{
var el = document.createElement('option'); var el = document.createElement('option');
el.setAttribute('value', opt); el.setAttribute('value', opt);
el.innerHTML = opt; el.innerHTML = opt;
return el return el;
} }
function createSel(array, group){ function createSel(array, group)
if (array.length != "0"){ {
var el = createSelGroup(group); if (array.length != "0")
for(var i=0; i<array.length; i++){ {
var el = createSelGroup(group);
for(var i=0; i<array.length; i++)
{
var opt = createSelOpt(array[i]) var opt = createSelOpt(array[i])
el.appendChild(opt); el.appendChild(opt);
} }
return el; return el;
} }
} }
function performTranslation(){ function performTranslation()
{
$('#wrapper').i18n(); $('#wrapper').i18n();
} }
function encode_utf8(s)
{
return unescape(encodeURIComponent(s));
}

View File

@ -74,7 +74,8 @@ void JsonClientConnection::readData()
{ {
// websocket mode, data frame // websocket mode, data frame
handleWebSocketFrame(); handleWebSocketFrame();
} else }
else
{ {
// might be a handshake request or raw socket data // might be a handshake request or raw socket data
if(_receiveBuffer.contains("Upgrade: websocket")) if(_receiveBuffer.contains("Upgrade: websocket"))
@ -104,24 +105,25 @@ void JsonClientConnection::readData()
void JsonClientConnection::handleWebSocketFrame() void JsonClientConnection::handleWebSocketFrame()
{ {
if ((_receiveBuffer.at(0) & 0x80) == 0x80) if ((_receiveBuffer.at(0) & BHB0_FIN) == BHB0_FIN)
{ {
// final bit found, frame complete // final bit found, frame complete
quint8 * maskKey = NULL; quint8 * maskKey = NULL;
quint8 opCode = _receiveBuffer.at(0) & 0x0F; quint8 opCode = _receiveBuffer.at(0) & BHB0_OPCODE;
bool isMasked = (_receiveBuffer.at(1) & 0x80) == 0x80; bool isMasked = (_receiveBuffer.at(1) & BHB0_FIN) == BHB0_FIN;
quint64 payloadLength = _receiveBuffer.at(1) & 0x7F; quint64 payloadLength = _receiveBuffer.at(1) & BHB1_PAYLOAD;
quint32 index = 2; quint32 index = 2;
switch (payloadLength) switch (payloadLength)
{ {
case 126: case payload_size_code_16bit:
payloadLength = ((_receiveBuffer.at(2) << 8) & 0xFF00) | (_receiveBuffer.at(3) & 0xFF); payloadLength = ((_receiveBuffer.at(2) << 8) & 0xFF00) | (_receiveBuffer.at(3) & 0xFF);
index += 2; index += 2;
break; break;
case 127: case payload_size_code_64bit:
payloadLength = 0; payloadLength = 0;
for (uint i=0; i < 8; i++) { for (uint i=0; i < 8; i++)
{
payloadLength |= ((quint64)(_receiveBuffer.at(index+i) & 0xFF)) << (8*(7-i)); payloadLength |= ((quint64)(_receiveBuffer.at(index+i) & 0xFF)) << (8*(7-i));
} }
index += 8; index += 8;
@ -144,7 +146,7 @@ void JsonClientConnection::handleWebSocketFrame()
// check the type of data frame // check the type of data frame
switch (opCode) switch (opCode)
{ {
case 0x01: case OPCODE::TEXT:
{ {
// frame contains text, extract it // frame contains text, extract it
QByteArray result = _receiveBuffer.mid(index, payloadLength); QByteArray result = _receiveBuffer.mid(index, payloadLength);
@ -167,25 +169,26 @@ void JsonClientConnection::handleWebSocketFrame()
handleMessage(QString(result)); handleMessage(QString(result));
} }
break; break;
case 0x08: case OPCODE::CLOSE:
{ {
// close request, confirm // close request, confirm
quint8 close[] = {0x88, 0}; quint8 close[] = {0x88, 0};
_socket->write((const char*)close, 2); _socket->write((const char*)close, 2);
_socket->flush(); _socket->flush();
_socket->close(); _socket->close();
} }
break; break;
case 0x09: case OPCODE::PING:
{ {
// ping received, send pong // ping received, send pong
quint8 pong[] = {0x0A, 0}; quint8 pong[] = {OPCODE::PONG, 0};
_socket->write((const char*)pong, 2); _socket->write((const char*)pong, 2);
_socket->flush(); _socket->flush();
} }
break; break;
} }
} else }
else
{ {
Error(_log, "Someone is sending very big messages over several frames... it's not supported yet"); Error(_log, "Someone is sending very big messages over several frames... it's not supported yet");
quint8 close[] = {0x88, 0}; quint8 close[] = {0x88, 0};
@ -1165,7 +1168,7 @@ void JsonClientConnection::handleConfigSetCommand(const QJsonObject& message, co
QJsonObject hyperionConfig = message["config"].toObject(); QJsonObject hyperionConfig = message["config"].toObject();
QJsonFactory::writeJson(QString::fromStdString(_hyperion->getConfigFileName()), hyperionConfig); QJsonFactory::writeJson(QString::fromStdString(_hyperion->getConfigFileName()), hyperionConfig);
sendSuccessReply(command, tan); sendSuccessReply(command, tan);
} }
} else } else

View File

@ -17,6 +17,63 @@
class ImageProcessor; class ImageProcessor;
/// Constants and utility functions related to WebSocket opcodes
/**
* WebSocket Opcodes are 4 bits. See RFC6455 section 5.2.
*/
namespace OPCODE {
enum value {
CONTINUATION = 0x0,
TEXT = 0x1,
BINARY = 0x2,
RSV3 = 0x3,
RSV4 = 0x4,
RSV5 = 0x5,
RSV6 = 0x6,
RSV7 = 0x7,
CLOSE = 0x8,
PING = 0x9,
PONG = 0xA,
CONTROL_RSVB = 0xB,
CONTROL_RSVC = 0xC,
CONTROL_RSVD = 0xD,
CONTROL_RSVE = 0xE,
CONTROL_RSVF = 0xF
};
/// Check if an opcode is reserved
/**
* @param v The opcode to test.
* @return Whether or not the opcode is reserved.
*/
inline bool reserved(value v) {
return (v >= RSV3 && v <= RSV7) || (v >= CONTROL_RSVB && v <= CONTROL_RSVF);
}
/// Check if an opcode is invalid
/**
* Invalid opcodes are negative or require greater than 4 bits to store.
*
* @param v The opcode to test.
* @return Whether or not the opcode is invalid.
*/
inline bool invalid(value v) {
return (v > 0xF || v < 0);
}
/// Check if an opcode is for a control frame
/**
* @param v The opcode to test.
* @return Whether or not the opcode is a control opcode.
*/
inline bool is_control(value v) {
return v >= 0x8;
}
}
/// ///
/// The Connection object created by \a JsonServer when a new connection is establshed /// The Connection object created by \a JsonServer when a new connection is establshed
/// ///
@ -228,7 +285,6 @@ private:
/// ///
void forwardJsonMessage(const QJsonObject & message); void forwardJsonMessage(const QJsonObject & message);
private:
/// ///
/// Check if a JSON messag is valid according to a given JSON schema /// Check if a JSON messag is valid according to a given JSON schema
/// ///
@ -270,4 +326,16 @@ private:
QJsonObject _streaming_logging_reply; QJsonObject _streaming_logging_reply;
bool _streaming_logging_activated; bool _streaming_logging_activated;
// masks for fields in the basic header
static uint8_t const BHB0_OPCODE = 0x0F;
static uint8_t const BHB0_RSV3 = 0x10;
static uint8_t const BHB0_RSV2 = 0x20;
static uint8_t const BHB0_RSV1 = 0x40;
static uint8_t const BHB0_FIN = 0x80;
static uint8_t const BHB1_PAYLOAD = 0x7F;
static uint8_t const BHB1_MASK = 0x80;
static uint8_t const payload_size_code_16bit = 0x7E; // 126
static uint8_t const payload_size_code_64bit = 0x7F; // 127
}; };