From 4a841710dd014ee7d946b4ec42e4f5e2041da897 Mon Sep 17 00:00:00 2001 From: redPanther Date: Tue, 14 Jun 2016 20:14:06 +0200 Subject: [PATCH] webui - "cgi" handler and multiple fixes (#700) * initial commit of webconfig * update example config with webconfig and fix format of file update debian postinst script for install example config * fix compiling add new web server command "serverinfo" to use in webapp to retrieve json port * change web default port to 8099 * add cgi engine to webserver * fix include --- include/hyperion/Hyperion.h | 13 ++++- include/webconfig/WebConfig.h | 7 ++- libsrc/effectengine/Effect.cpp | 1 + libsrc/hyperion/Hyperion.cpp | 4 +- libsrc/utils/Logger.cpp | 2 +- libsrc/webconfig/CMakeLists.txt | 2 + libsrc/webconfig/CgiHandler.cpp | 69 ++++++++++++++++++++++++++ libsrc/webconfig/CgiHandler.h | 34 +++++++++++++ libsrc/webconfig/StaticFileServing.cpp | 33 +++++++----- libsrc/webconfig/StaticFileServing.h | 14 ++++-- libsrc/webconfig/WebConfig.cpp | 32 ++++-------- src/hyperiond/hyperiond.cpp | 49 ++++++++++-------- 12 files changed, 193 insertions(+), 67 deletions(-) create mode 100644 libsrc/webconfig/CgiHandler.cpp create mode 100644 libsrc/webconfig/CgiHandler.h diff --git a/include/hyperion/Hyperion.h b/include/hyperion/Hyperion.h index 34b053a5..467b7557 100644 --- a/include/hyperion/Hyperion.h +++ b/include/hyperion/Hyperion.h @@ -67,7 +67,7 @@ public: /// /// @param[in] jsonConfig The Json configuration /// - Hyperion(const Json::Value& jsonConfig); + Hyperion(const Json::Value& jsonConfig, const std::string configFile); /// /// Destructor; cleans up resourcess @@ -110,6 +110,11 @@ public: /// Get the list of active effects /// @return The list of active effects const std::list &getActiveEffects(); + + /// + const Json::Value& getJsonConfig() { return _jsonConfig; }; + + std::string getConfigFileName() { return _configFile; }; public slots: /// @@ -293,6 +298,12 @@ private: // proto and json Message forwarder MessageForwarder * _messageForwarder; + // json configuration + const Json::Value& _jsonConfig; + + // the name of config file + std::string _configFile; + /// The timer for handling priority channel timeouts QTimer _timer; }; diff --git a/include/webconfig/WebConfig.h b/include/webconfig/WebConfig.h index 80d71cb3..cb02b3d5 100644 --- a/include/webconfig/WebConfig.h +++ b/include/webconfig/WebConfig.h @@ -5,6 +5,7 @@ #include #include #include +#include class StaticFileServing; @@ -12,8 +13,7 @@ class WebConfig : public QObject { Q_OBJECT public: - WebConfig (std::string baseUrl, quint16 port, quint16 jsonPort, QObject * parent = NULL); - WebConfig (const Json::Value &config, QObject * parent = NULL); + WebConfig (Hyperion *hyperion, QObject * parent = NULL); virtual ~WebConfig (void); @@ -21,10 +21,9 @@ public: void stop(); private: - QObject* _parent; + Hyperion* _hyperion; QString _baseUrl; quint16 _port; - quint16 _jsonPort; StaticFileServing* _server; const std::string WEBCONFIG_DEFAULT_PATH = "/usr/share/hyperion/webconfig"; diff --git a/libsrc/effectengine/Effect.cpp b/libsrc/effectengine/Effect.cpp index e95b654c..7f5d3534 100644 --- a/libsrc/effectengine/Effect.cpp +++ b/libsrc/effectengine/Effect.cpp @@ -114,6 +114,7 @@ void Effect::run() { std::cerr << "EFFECTENGINE ERROR: Unable to open script file " << _script << std::endl; } + fclose(file); // Clean up the thread state Py_EndInterpreter(_interpreterThreadState); diff --git a/libsrc/hyperion/Hyperion.cpp b/libsrc/hyperion/Hyperion.cpp index aefa0607..229db2da 100644 --- a/libsrc/hyperion/Hyperion.cpp +++ b/libsrc/hyperion/Hyperion.cpp @@ -615,7 +615,7 @@ MessageForwarder * Hyperion::getForwarder() return _messageForwarder; } -Hyperion::Hyperion(const Json::Value &jsonConfig) : +Hyperion::Hyperion(const Json::Value &jsonConfig, const std::string configFile) : _ledString(createLedString(jsonConfig["leds"], createColorOrder(jsonConfig["device"]))), _muxer(_ledString.leds().size()), _raw2ledTransform(createLedColorsTransform(_ledString.leds().size(), jsonConfig["color"])), @@ -625,6 +625,8 @@ Hyperion::Hyperion(const Json::Value &jsonConfig) : _device(LedDeviceFactory::construct(jsonConfig["device"])), _effectEngine(nullptr), _messageForwarder(createMessageForwarder(jsonConfig["forwarder"])), + _jsonConfig(jsonConfig), + _configFile(configFile), _timer() { if (!_raw2ledAdjustment->verifyAdjustments()) diff --git a/libsrc/utils/Logger.cpp b/libsrc/utils/Logger.cpp index ed9da729..4046d2a1 100644 --- a/libsrc/utils/Logger.cpp +++ b/libsrc/utils/Logger.cpp @@ -46,7 +46,7 @@ Logger::Logger ( std::string name, LogLevel minLevel ): _syslogEnabled(true), _loggerId(loggerId++) { -#ifdef __linux__ +#ifdef __GLIBC__ _appname = std::string(program_invocation_short_name); #else _appname = std::string(getprogname()); diff --git a/libsrc/webconfig/CMakeLists.txt b/libsrc/webconfig/CMakeLists.txt index 25abbbb8..bcf68833 100644 --- a/libsrc/webconfig/CMakeLists.txt +++ b/libsrc/webconfig/CMakeLists.txt @@ -10,6 +10,7 @@ set(WebConfig_QT_HEADERS ${CURRENT_SOURCE_DIR}/QtHttpReply.h ${CURRENT_SOURCE_DIR}/QtHttpRequest.h ${CURRENT_SOURCE_DIR}/QtHttpServer.h + ${CURRENT_SOURCE_DIR}/CgiHandler.h ${CURRENT_SOURCE_DIR}/StaticFileServing.h ${CURRENT_HEADER_DIR}/WebConfig.h ) @@ -23,6 +24,7 @@ set(WebConfig_SOURCES ${CURRENT_SOURCE_DIR}/QtHttpReply.cpp ${CURRENT_SOURCE_DIR}/QtHttpRequest.cpp ${CURRENT_SOURCE_DIR}/QtHttpServer.cpp + ${CURRENT_SOURCE_DIR}/CgiHandler.cpp ${CURRENT_SOURCE_DIR}/StaticFileServing.cpp ${CURRENT_SOURCE_DIR}/WebConfig.cpp ) diff --git a/libsrc/webconfig/CgiHandler.cpp b/libsrc/webconfig/CgiHandler.cpp new file mode 100644 index 00000000..f04571c3 --- /dev/null +++ b/libsrc/webconfig/CgiHandler.cpp @@ -0,0 +1,69 @@ +#include +#include +#include +#include + +#include "CgiHandler.h" + +CgiHandler::CgiHandler (Hyperion * hyperion, QObject * parent) + : QObject(parent) + , _hyperion(hyperion) + , _hyperionConfig(_hyperion->getJsonConfig()) +{ +} + +CgiHandler::~CgiHandler() +{ +} + +void CgiHandler::exec(const QStringList & args, QtHttpReply * reply) +{ + try + { + cmd_cfg_jsonserver(args,reply); + cmd_cfg_hyperion(args,reply); + throw 1; + } + catch(int e) + { + if (e != 0) + throw 1; + } +} + +void CgiHandler::cmd_cfg_jsonserver(const QStringList & args, QtHttpReply * reply) +{ + if ( args.at(0) == "cfg_jsonserver" ) + { + quint16 jsonPort = 19444; + if (_hyperionConfig.isMember("jsonServer")) + { + const Json::Value & jsonConfig = _hyperionConfig["jsonServer"]; + jsonPort = jsonConfig.get("port", jsonPort).asUInt(); + } + + // send result as reply + reply->addHeader ("Content-Type", "text/plain" ); + reply->appendRawData (QByteArrayLiteral(":") % QString::number(jsonPort).toUtf8() ); + throw 0; + } +} + + +void CgiHandler::cmd_cfg_hyperion(const QStringList & args, QtHttpReply * reply) +{ + if ( args.at(0) == "cfg_hyperion" ) + { + QFile file ( _hyperion->getConfigFileName().c_str() ); + if (file.exists ()) + { + if (file.open (QFile::ReadOnly)) { + QByteArray data = file.readAll (); + reply->addHeader ("Content-Type", "text/plain"); + reply->appendRawData (data); + file.close (); + } + } + throw 0; + } +} diff --git a/libsrc/webconfig/CgiHandler.h b/libsrc/webconfig/CgiHandler.h new file mode 100644 index 00000000..8c691a69 --- /dev/null +++ b/libsrc/webconfig/CgiHandler.h @@ -0,0 +1,34 @@ +#ifndef CGIHANDLER_H +#define CGIHANDLER_H + +#include +#include +#include + +#include +#include + +#include "QtHttpReply.h" + +class CgiHandler : public QObject { + Q_OBJECT + +public: + CgiHandler (Hyperion * hyperion, QObject * parent = NULL); + virtual ~CgiHandler (void); + + void exec(const QStringList & args, QtHttpReply * reply); + + // cgi commands + void cmd_cfg_jsonserver(const QStringList & args, QtHttpReply * reply); + void cmd_cfg_hyperion (const QStringList & args, QtHttpReply * reply); + +private: + Hyperion* _hyperion; + QtHttpReply * _reply; + const Json::Value &_hyperionConfig; +}; + +#endif // CGIHANDLER_H + + diff --git a/libsrc/webconfig/StaticFileServing.cpp b/libsrc/webconfig/StaticFileServing.cpp index 30ecc67b..6492f573 100644 --- a/libsrc/webconfig/StaticFileServing.cpp +++ b/libsrc/webconfig/StaticFileServing.cpp @@ -8,12 +8,13 @@ #include #include -StaticFileServing::StaticFileServing (QString baseUrl, quint16 port, quint16 jsonPort, QObject * parent) - : QObject (parent) - , m_baseUrl (baseUrl) - , _jsonPort (jsonPort) +StaticFileServing::StaticFileServing (Hyperion *hyperion, QString baseUrl, quint16 port, QObject * parent) + : QObject (parent) + , _hyperion(hyperion) + , _baseUrl (baseUrl) + , _cgi(hyperion, this) { - m_mimeDb = new QMimeDatabase; + _mimeDb = new QMimeDatabase; _server = new QtHttpServer (this); _server->setServerName (QStringLiteral ("Qt Static HTTP File Server")); @@ -57,23 +58,31 @@ void StaticFileServing::onRequestNeedsReply (QtHttpRequest * request, QtHttpRepl if (command == QStringLiteral ("GET")) { QString path = request->getUrl ().path (); + QStringList uri_parts = path.split('/', QString::SkipEmptyParts); // special uri handling for server commands - if ( path == "/serverinfo" ) + if ( ! uri_parts.empty() && uri_parts.at(0) == "cgi" ) { - reply->addHeader ("Content-Type", "text/plain" ); - reply->appendRawData (QByteArrayLiteral(":") % QString::number(_jsonPort).toUtf8() ); + uri_parts.removeAt(0); + try + { + _cgi.exec(uri_parts, reply); + } + catch(...) + { + printErrorToReply (reply, "cgi script failed (" % path % ")"); + } return; } // get static files - if ( path == "/" || path.isEmpty() || ! QFile::exists(m_baseUrl % "/" % path) ) + if ( path == "/" || path.isEmpty() || ! QFile::exists(_baseUrl % "/" % path) ) path = "index.html"; - QFile file (m_baseUrl % "/" % path); + QFile file (_baseUrl % "/" % path); if (file.exists ()) { - QMimeType mime = m_mimeDb->mimeTypeForFile (file.fileName ()); + QMimeType mime = _mimeDb->mimeTypeForFile (file.fileName ()); if (file.open (QFile::ReadOnly)) { QByteArray data = file.readAll (); reply->addHeader ("Content-Type", mime.name ().toLocal8Bit ()); @@ -82,7 +91,7 @@ void StaticFileServing::onRequestNeedsReply (QtHttpRequest * request, QtHttpRepl } else { - printErrorToReply (reply, "Requested file " % m_baseUrl % "/" % path % " couldn't be open for reading !"); + printErrorToReply (reply, "Requested file " % path % " couldn't be open for reading !"); } } else diff --git a/libsrc/webconfig/StaticFileServing.h b/libsrc/webconfig/StaticFileServing.h index e712b724..438ab16c 100644 --- a/libsrc/webconfig/StaticFileServing.h +++ b/libsrc/webconfig/StaticFileServing.h @@ -8,12 +8,15 @@ #include "QtHttpRequest.h" #include "QtHttpReply.h" #include "QtHttpHeader.h" +#include "CgiHandler.h" + +#include class StaticFileServing : public QObject { Q_OBJECT public: - explicit StaticFileServing (QString baseUrl, quint16 port, quint16 jsonPort, QObject * parent = NULL); + explicit StaticFileServing (Hyperion *hyperion, QString baseUrl, quint16 port, QObject * parent = NULL); virtual ~StaticFileServing (void); public slots: @@ -23,10 +26,11 @@ public slots: void onRequestNeedsReply (QtHttpRequest * request, QtHttpReply * reply); private: - QString m_baseUrl; - QtHttpServer * _server; - QMimeDatabase * m_mimeDb; - quint16 _jsonPort; + Hyperion * _hyperion; + QString _baseUrl; + QtHttpServer * _server; + QMimeDatabase * _mimeDb; + CgiHandler _cgi; }; #endif // STATICFILESERVING_H diff --git a/libsrc/webconfig/WebConfig.cpp b/libsrc/webconfig/WebConfig.cpp index 94375be2..79f2f332 100644 --- a/libsrc/webconfig/WebConfig.cpp +++ b/libsrc/webconfig/WebConfig.cpp @@ -1,23 +1,16 @@ -#include "webconfig/webconfig.h" +#include "webconfig/WebConfig.h" #include "StaticFileServing.h" -WebConfig::WebConfig(std::string baseUrl, quint16 port, quint16 jsonPort, QObject * parent) : - _parent(parent), - _baseUrl(QString::fromStdString(baseUrl)), - _port(port), - _jsonPort(jsonPort), - _server(nullptr) -{ -} - -WebConfig::WebConfig(const Json::Value &config, QObject * parent) : - _parent(parent), - _port(WEBCONFIG_DEFAULT_PORT), - _server(nullptr) +WebConfig::WebConfig(Hyperion *hyperion, QObject * parent) + : QObject(parent) + , _hyperion(hyperion) + , _port(WEBCONFIG_DEFAULT_PORT) + , _server(nullptr) { + const Json::Value &config = hyperion->getJsonConfig(); _baseUrl = QString::fromStdString(WEBCONFIG_DEFAULT_PATH); - _jsonPort = 19444; + bool webconfigEnable = true; if (config.isMember("webConfig")) @@ -28,13 +21,6 @@ WebConfig::WebConfig(const Json::Value &config, QObject * parent) : _baseUrl = QString::fromStdString( webconfigConfig.get("document_root", WEBCONFIG_DEFAULT_PATH).asString() ); } - if (config.isMember("jsonServer")) - { - const Json::Value & jsonConfig = config["jsonServer"]; - _jsonPort = jsonConfig.get("port", 19444).asUInt(); - } - - if ( webconfigEnable ) start(); } @@ -49,7 +35,7 @@ WebConfig::~WebConfig() void WebConfig::start() { if ( _server == nullptr ) - _server = new StaticFileServing (_baseUrl, _port, _jsonPort, this); + _server = new StaticFileServing (_hyperion, _baseUrl, _port, this); } void WebConfig::stop() diff --git a/src/hyperiond/hyperiond.cpp b/src/hyperiond/hyperiond.cpp index d9cd59b0..78f9be40 100644 --- a/src/hyperiond/hyperiond.cpp +++ b/src/hyperiond/hyperiond.cpp @@ -203,7 +203,7 @@ void startNetworkServices(const Json::Value &config, Hyperion &hyperion, JsonSer { const Json::Value & jsonServerConfig = config["jsonServer"]; //jsonEnable = jsonServerConfig.get("enable", true).asBool(); - jsonPort = jsonServerConfig.get("port", 19444).asUInt(); + jsonPort = jsonServerConfig.get("port", jsonPort).asUInt(); } jsonServer = new JsonServer(&hyperion, jsonPort ); @@ -215,7 +215,7 @@ void startNetworkServices(const Json::Value &config, Hyperion &hyperion, JsonSer { const Json::Value & protoServerConfig = config["protoServer"]; //protoEnable = protoServerConfig.get("enable", true).asBool(); - protoPort = protoServerConfig.get("port", 19445).asUInt(); + protoPort = protoServerConfig.get("port", protoPort).asUInt(); } protoServer = new ProtoServer(&hyperion, protoPort ); @@ -231,24 +231,33 @@ void startNetworkServices(const Json::Value &config, Hyperion &hyperion, JsonSer const std::string deviceName = deviceConfig.get("name", "").asString(); const std::string hostname = QHostInfo::localHostName().toStdString(); - const std::string mDNSDescr = jsonServerConfig.get("mDNSDescr", hostname).asString(); - const std::string mDNSService = jsonServerConfig.get("mDNSService", "_hyperiond_json._tcp").asString(); - BonjourServiceRegister *bonjourRegister_json; - bonjourRegister_json = new BonjourServiceRegister(); - bonjourRegister_json->registerService(BonjourRecord((deviceName + " @ " + mDNSDescr).c_str(), mDNSService.c_str(), - QString()), jsonServerConfig["port"].asUInt()); + + std::string mDNSDescr_json = hostname; + std::string mDNSService_json = "_hyperiond_json._tcp"; + if (config.isMember("jsonServer")) + { + const Json::Value & jsonServerConfig = config["jsonServer"]; + mDNSDescr_json = jsonServerConfig.get("mDNSDescr", mDNSDescr_json).asString(); + mDNSService_json = jsonServerConfig.get("mDNSService", mDNSService_json).asString(); + } + + BonjourServiceRegister *bonjourRegister_json = new BonjourServiceRegister(); + bonjourRegister_json->registerService(BonjourRecord((deviceName + " @ " + mDNSDescr_json).c_str(), mDNSService_json.c_str(), + QString()), jsonServer->getPort() ); std::cout << "INFO: Json mDNS responder started" << std::endl; - const Json::Value & deviceConfig = config["device"]; - const std::string deviceName = deviceConfig.get("name", "").asString(); - - const std::string hostname = QHostInfo::localHostName().toStdString(); - const std::string mDNSDescr = protoServerConfig.get("mDNSDescr", hostname).asString(); - const std::string mDNSService = protoServerConfig.get("mDNSService", "_hyperiond_proto._tcp").asString(); - BonjourServiceRegister *bonjourRegister_proto; - bonjourRegister_proto = new BonjourServiceRegister(); - bonjourRegister_proto->registerService(BonjourRecord((deviceName + " @ " + mDNSDescr).c_str(), mDNSService.c_str(), - QString()), protoServerConfig["port"].asUInt()); + std::string mDNSDescr_proto = hostname; + std::string mDNSService_proto = "_hyperiond_proto._tcp"; + if (config.isMember("protoServer")) + { + const Json::Value & protoServerConfig = config["protoServer"]; + mDNSDescr_proto = protoServerConfig.get("mDNSDescr", mDNSDescr_proto).asString(); + mDNSService_proto = protoServerConfig.get("mDNSService", mDNSService_proto).asString(); + } + + BonjourServiceRegister *bonjourRegister_proto = new BonjourServiceRegister(); + bonjourRegister_proto->registerService(BonjourRecord((deviceName + " @ " + mDNSDescr_proto).c_str(), mDNSService_proto.c_str(), + QString()), protoServer->getPort() ); std::cout << "INFO: Proto mDNS responder started" << std::endl; #endif @@ -487,7 +496,7 @@ int main(int argc, char** argv) std::cout << "INFO: Selected configuration file: " << configFile.c_str() << std::endl; const Json::Value config = loadConfig(configFile); - Hyperion hyperion(config); + Hyperion hyperion(config, configFile); std::cout << "INFO: Hyperion started and initialised" << std::endl; @@ -503,7 +512,7 @@ int main(int argc, char** argv) startNetworkServices(config, hyperion, jsonServer, protoServer, boblightServer, xbmcVideoChecker); #ifdef ENABLE_QT5 - WebConfig webConfig(config, &app); + WebConfig webConfig(&hyperion, &app); #endif // ---- grabber -----