From 357b730f3160c872e076dd87dd30524f67e67407 Mon Sep 17 00:00:00 2001 From: john30 Date: Sun, 10 May 2015 10:23:14 +0200 Subject: [PATCH] added support for serving files via HTTP port --- src/ebusd/main.cpp | 20 ++- src/ebusd/main.h | 1 + src/ebusd/mainloop.cpp | 285 ++++++++++++++++++++++++++--------------- src/ebusd/mainloop.h | 6 +- 4 files changed, 204 insertions(+), 108 deletions(-) diff --git a/src/ebusd/main.cpp b/src/ebusd/main.cpp index 1f4b5fe7..b99f96ea 100644 --- a/src/ebusd/main.cpp +++ b/src/ebusd/main.cpp @@ -80,6 +80,7 @@ static struct options opt = { 8888, // port false, // localOnly 0, // httpPort + "/var/ebusd/html", // htmlPath PACKAGE_LOGFILE, // logFile false, // logRaw false, // dump @@ -112,11 +113,12 @@ static const char argpdoc[] = #define O_GENSYN 10 #define O_LOCAL 11 #define O_HTTPPT 12 -#define O_LOGARE 13 -#define O_LOGLEV 14 -#define O_LOGRAW 15 -#define O_DMPFIL 16 -#define O_DMPSIZ 17 +#define O_HTMLPA 13 +#define O_LOGARE 14 +#define O_LOGLEV 15 +#define O_LOGRAW 16 +#define O_DMPFIL 17 +#define O_DMPSIZ 18 /** the definition of the known program arguments. */ static const struct argp_option argpoptions[] = { @@ -146,6 +148,7 @@ static const struct argp_option argpoptions[] = { {"port", 'p', "PORT", 0, "Listen for command line connections on PORT [8888]", 0 }, {"localhost", O_LOCAL, NULL, 0, "Listen for command line on 127.0.0.1 interface only", 0 }, {"httpport", O_HTTPPT, "PORT", 0, "Listen for HTTP connections on PORT, 0 to disable [0]", 0 }, + {"htmlpath", O_HTMLPA, "PATH", 0, "Path for HTML files served by HTTP port [/var/ebusd/html]", 0 }, {NULL, 0, NULL, 0, "Log options:", 5 }, {"logfile", 'l', "FILE", 0, "Write log to FILE (only for daemon) [" PACKAGE_LOGFILE "]", 0 }, @@ -281,6 +284,13 @@ error_t parse_opt(int key, char *arg, struct argp_state *state) return EINVAL; } break; + case O_HTMLPA: // --htmlpath=/var/ebusd/html + if (arg == NULL || arg[0] == 0 || strcmp("/", arg) == 0) { + argp_error(state, "invalid htmlpath"); + return EINVAL; + } + opt->htmlPath = arg; + break; // Log options: case 'l': // --logfile=/var/log/ebusd.log diff --git a/src/ebusd/main.h b/src/ebusd/main.h index ca252759..807458c9 100644 --- a/src/ebusd/main.h +++ b/src/ebusd/main.h @@ -51,6 +51,7 @@ struct options uint16_t port; //!< port to listen for command line connections [8888] bool localOnly; //!< listen on 127.0.0.1 interface only uint16_t httpPort; //!< optional port to listen for HTTP connections, 0 to disable [0] + string htmlPath; //!< path for HTML files served by the HTTP port [/var/ebusd/html] const char* logFile; //!< log file name [/var/log/ebusd.log] bool logRaw; //!< log each received/sent byte on the bus diff --git a/src/ebusd/mainloop.cpp b/src/ebusd/mainloop.cpp index ffa70cd8..60be2e7d 100644 --- a/src/ebusd/mainloop.cpp +++ b/src/ebusd/mainloop.cpp @@ -52,6 +52,7 @@ MainLoop::MainLoop(const struct options opt, Device *device, DataFieldTemplates* m_busHandler->start("bushandler"); // create network + m_htmlPath = opt.htmlPath; m_network = new Network(opt.localOnly, opt.port, opt.httpPort, &m_netQueue); m_network->start("network"); } @@ -97,10 +98,13 @@ void MainLoop::run() logDebug(lf_main, ">>> %s", request.c_str()); result = decodeMessage(request, message->isHttp(), connected, listening, running); - logDebug(lf_main, "<<< %s", result.c_str()); + if (result.length() > 100) + logDebug(lf_main, "<<< %s ...", result.substr(0, 100).c_str()); + else + logDebug(lf_main, "<<< %s", result.c_str()); if (result.length() == 0) result = "\n"; - else + else if (!message->isHttp()) result += "\n\n"; } if (listening) { @@ -124,43 +128,44 @@ string MainLoop::decodeMessage(const string& data, const bool isHttp, bool& conn char delim = ' '; while (getline(stream, token, delim) != 0) { - if (isHttp && delim == '/' && token.length() > 0 && token[0] == '?') { - token.erase(0, 1); - delim = '&'; - } - if (escaped) { - args.pop_back(); - if (token.length() > 0 && token[token.length()-1] == '"') { - token.erase(token.length() - 1, 1); - escaped = false; + if (!isHttp) { + if (escaped) { + args.pop_back(); + if (token.length() > 0 && token[token.length()-1] == '"') { + token.erase(token.length() - 1, 1); + escaped = false; + } + token = previous + " " + token; + } + else if (token.length() == 0) // allow multiple space chars for a single delimiter + continue; + else if (token[0] == '"') { + token.erase(0, 1); + if (token.length() > 0 && token[token.length()-1] == '"') + token.erase(token.length() - 1, 1); + else + escaped = true; } - token = previous + " " + token; - } - else if (token.length() == 0) // allow multiple space chars for a single delimiter - continue; - else if (token[0] == '"') { - token.erase(0, 1); - if (token.length() > 0 && token[token.length()-1] == '"') - token.erase(token.length() - 1, 1); - else - escaped = true; } args.push_back(token); previous = token; if (isHttp) - delim = '/'; + delim = (args.size() == 1) ? '?' : '\n'; + } + + if (isHttp) { + const char* str = args.size() > 0 ? args[0].c_str() : ""; + if (strcmp(str, "GET") == 0) + return executeGet(args, connected); + + connected = false; + return "HTTP/1.0 405 Method Not Allowed\r\n\r\n"; } if (args.size() == 0) return executeHelp(); const char* str = args[0].c_str(); - if (isHttp) { - if (strcmp(str, "GET") == 0) - return executeGet(args); - return "HTTP/1.0 405 Method Not Allowed\r\n\r\n"; - } - if (args.size() == 2) { // check for "CMD -h" if (strcasecmp(args[1].c_str(), "-h") == 0 || strcasecmp(args[1].c_str(), "-?") == 0 || strcasecmp(args[1].c_str(), "--help") == 0) @@ -760,86 +765,162 @@ string MainLoop::executeHelp() " help|h Print help help [COMMAND]"; } -string MainLoop::executeGet(vector &args) +string MainLoop::executeGet(vector &args, bool& connected) { - size_t argPos = 1; - bool onlyWithData = false; - - deque messages; - if (args.size() >= argPos+2) - messages = m_messages->findAll(args[argPos], args[argPos+1], -1, false, true, false, true); - else if (args.size() == argPos+1) - messages = m_messages->findAll(args[argPos], "", -1, false, true, false, true); - else - messages = m_messages->findAll("", "", -1, false, true, false, true); - - bool first = true; - ostringstream result; - result << "{"; - string lastCircuit = ""; result_t ret = RESULT_OK; - for (deque::iterator it = messages.begin(); it < messages.end();) { - Message* message = *it++; - time_t lastup = message->getLastUpdateTime(); - if (onlyWithData && lastup == 0) - continue; - unsigned char dstAddress = message->getDstAddress(); - if (dstAddress == SYN) - continue; - if (message->getCircuit() != lastCircuit) { - if (lastCircuit.length() > 0) - result << "\n },"; - lastCircuit = message->getCircuit(); - result << "\n \"" << lastCircuit << "\": {"; - first = true; - } - if (first) - first = false; - else - result << ","; - result << "\n \"" << message->getName() << "\": {"; - result << "\n \"lastup\": " << setw(0) << dec << static_cast(lastup); - if (lastup != 0) { - result << ",\n \"zz\": \"" << setfill('0') << setw(2) << hex << static_cast(dstAddress) << "\""; - result << ",\n \"fields\": ["; - ret = message->decodeLastData(result, false, df_json); - if (ret < RESULT_OK) - break; - result << "\n ]"; - } - result << ",\n \"passive\": " << (message->isPassive() ? "true" : "false"); - result << ",\n \"write\": " << (message->isWrite() ? "true" : "false"); - result << "\n }"; - } - if (lastCircuit.length() > 0) - result << "\n }"; - result << "\n}"; + size_t argPos = 1; + string uri = args[argPos]; + ostringstream result; - if (ret == RESULT_OK ) { - string str = result.str(); - result.str(""); - result.clear(); - result << "HTTP/1.0 200 OK\r\n"; - result << "Content-Type: application/json;charset=utf-8\r\n"; - result << "Access-Control-Allow-Origin: *\r\n"; - result << "Content-Length: " << setw(0) << dec << static_cast(str.length()) << "\r\n"; - result << "\r\n"; - result << str; + if (strncmp(uri.c_str(), "/data/", 6) == 0) { + string clazz = "", name = ""; + size_t pos = uri.find('/', 6); + if (pos == string::npos) { + clazz = uri.substr(6); + } else { + clazz = uri.substr(6, pos-6); + name = uri.substr(pos+1); + } + bool onlyWithData = false; + + deque messages = m_messages->findAll(clazz, name, -1, false, true, false, true); + + bool first = true; + result << "{"; + string lastCircuit = ""; + for (deque::iterator it = messages.begin(); it < messages.end();) { + Message* message = *it++; + time_t lastup = message->getLastUpdateTime(); + if (onlyWithData && lastup == 0) + continue; + unsigned char dstAddress = message->getDstAddress(); + if (dstAddress == SYN) + continue; + if (message->getCircuit() != lastCircuit) { + if (lastCircuit.length() > 0) + result << "\n },"; + lastCircuit = message->getCircuit(); + result << "\n \"" << lastCircuit << "\": {"; + first = true; + } + if (first) + first = false; + else + result << ","; + result << "\n \"" << message->getName() << "\": {"; + result << "\n \"lastup\": " << setw(0) << dec << static_cast(lastup); + if (lastup != 0) { + result << ",\n \"zz\": \"" << setfill('0') << setw(2) << hex << static_cast(dstAddress) << "\""; + result << ",\n \"fields\": ["; + ret = message->decodeLastData(result, false, df_json); + if (ret < RESULT_OK) + break; + result << "\n ]"; + } + result << ",\n \"passive\": " << (message->isPassive() ? "true" : "false"); + result << ",\n \"write\": " << (message->isWrite() ? "true" : "false"); + result << "\n }"; + } + if (lastCircuit.length() > 0) + result << "\n }"; + result << "\n}"; + + if (ret == RESULT_OK) { + string str = result.str(); + result.str(""); + result.clear(); + result << "HTTP/1.0 200 OK\r\n"; + result << "Content-Type: application/json;charset=utf-8\r\n"; + result << "Content-Length: " << setw(0) << dec << static_cast(str.length()); + result << "\r\n\r\n"; + result << str; + return result.str(); + } } else { - result.str(""); - result.clear(); - result << "HTTP/1.0 "; - switch (ret) { - case RESULT_ERR_NOTFOUND: - result << "404 Not Found"; - break; - default: - result << "500 Internal Server Error"; - break; + if (uri.length() < 1 || uri[0] != '/' || uri.find("//") != string::npos || uri.find("..") != string::npos) { + ret = RESULT_ERR_INVALID_ARG; + } else { + string filename = m_htmlPath+uri; + if (uri[uri.length()-1] == '/') + filename += "index.html"; + + int type = -1; + size_t pos = filename.find_last_of('.'); + if (pos != string::npos && pos != filename.length() - 1 && pos >= filename.length() - 5) { + string ext = filename.substr(pos+1); + if (ext == "html") { + type = 0; + } else if (ext == "css") { + type = 1; + } else if (ext == "js") { + type = 2; + } else if (ext == "png") { + type = 3; + } else if (ext == "jpg" || ext == "jpeg") { + type = 4; + } else if (ext == "svg") { + type = 5; + } + } + if (type < 0) { + ret = RESULT_ERR_NOTFOUND; + } else { + ifstream ifs; + ifs.open(filename.c_str(), ifstream::in | ifstream::binary); + if (!ifs.is_open()) { + ret = RESULT_ERR_NOTFOUND; + } else { + ifs >> result.rdbuf(); + ifs.close(); + string str = result.str(); + result.str(""); + result.clear(); + result << "HTTP/1.0 200 OK\r\nContent-Type: "; + switch (type) { + case 1: + result << "text/css"; + break; + case 2: + result << "application/javascript"; + break; + case 3: + result << "image/png"; + break; + case 4: + result << "image/jpeg"; + break; + case 5: + result << "image/svg+xml"; + break; + default: + result << "text/html"; + break; + } + result << "\r\nContent-Length: " << setw(0) << dec << static_cast(str.length()); + result << "\r\nConnection: close\r\n\r\n"; + result << str; + connected = false; + return result.str(); + } + } } - result << "\r\n"; - result << "\r\n"; } + result.str(""); + result.clear(); + result << "HTTP/1.0 "; + switch (ret) { + case RESULT_ERR_NOTFOUND: + result << "404 Not Found"; + break; + case RESULT_ERR_INVALID_ARG: + result << "400 Bad Request"; + break; + default: + result << "500 Internal Server Error"; + break; + } + result << "\r\nConnection: close\r\n\r\n"; + connected = false; return result.str(); } diff --git a/src/ebusd/mainloop.h b/src/ebusd/mainloop.h index a002be13..3945d777 100644 --- a/src/ebusd/mainloop.h +++ b/src/ebusd/mainloop.h @@ -83,6 +83,9 @@ private: /** the queue for @a NetMessage instances. */ WQueue m_netQueue; + /** the path for HTML files served by the HTTP port. */ + string m_htmlPath; + /** * Decode and execute client message. * @param data the data string to decode (may be empty). @@ -196,9 +199,10 @@ private: /** * Execute the HTTP GET command. * @param args the arguments passed to the command (starting with the command itself). + * @param connected set to false when the client connection shall be closed. * @return the result string. */ - string executeGet(vector &args); + string executeGet(vector &args, bool& connected); /** * Get the updates received since the specified time.