/******************************************************************************* * NetworkHttp.cc NetworkHttp class * T.Barnaby, BEAM Ltd, 2006-08-01 ******************************************************************************* */ #include <stdio.h> #include <string.h> #include <unistd.h> #include <time.h> #include <errno.h> #include <sys/time.h> #include <fcntl.h> #include <netdb.h> #include <netinet/in.h> #include <netinet/tcp.h> #include <sys/ioctl.h> #include <math.h> #include <BPoll.h> #include <BFile.h> #include <BHtmlPage.h> #include <BNameValue.h> #include <NetworkHttp.h> #include <Debug.h> #include <Control.h> #include <TmsD.h> #include <TmsC.h> using namespace Tms; using namespace std; static const char* const suffixTable [] = { ".htm.html", "text/html", ".jpg.jpeg", "image/jpeg", ".gif", "image/gif", ".png", "image/png", ".txt.h.c.cc.cpp", "text/plain", ".css", "text/css", ".wav", "audio/wav", ".avi", "video/x-msvideo", ".qt.mov", "video/quicktime", ".mpe.mpeg", "video/mpeg", ".mid.midi", "audio/midi", ".mp3", "audio/mpeg", #if 0 /* unpopular */ ".au", "audio/basic", ".pac", "application/x-ns-proxy-autoconfig", ".vrml.wrl", "model/vrml", #endif 0, "application/octet-stream" /* default */ }; typedef enum { HTTP_OK = 200, HTTP_MOVED_TEMPORARILY = 302, HTTP_BAD_REQUEST = 400, /* malformed syntax */ HTTP_UNAUTHORIZED = 401, /* authentication needed, respond with auth hdr */ HTTP_NOT_FOUND = 404, HTTP_FORBIDDEN = 403, HTTP_REQUEST_TIMEOUT = 408, HTTP_NOT_IMPLEMENTED = 501, /* used for unrecognized requests */ HTTP_INTERNAL_SERVER_ERROR = 500, HTTP_CONTINUE = 100, HTTP_SWITCHING_PROTOCOLS = 101, HTTP_CREATED = 201, HTTP_ACCEPTED = 202, HTTP_NON_AUTHORITATIVE_INFO = 203, HTTP_NO_CONTENT = 204, HTTP_MULTIPLE_CHOICES = 300, HTTP_MOVED_PERMANENTLY = 301, HTTP_NOT_MODIFIED = 304, HTTP_PAYMENT_REQUIRED = 402, HTTP_BAD_GATEWAY = 502, HTTP_SERVICE_UNAVAILABLE = 503, /* overload, maintenance */ HTTP_RESPONSE_SETSIZE = 0xffffffff } HttpResponseNum; typedef struct { HttpResponseNum type; const char* name; const char* info; } HttpEnumString; static const HttpEnumString httpResponseNames[] = { { HTTP_OK, "OK", NULL }, { HTTP_MOVED_TEMPORARILY, "Found", "Directories must end with a slash." }, { HTTP_REQUEST_TIMEOUT, "Request Timeout", "No request appeared within a reasonable time period." }, { HTTP_NOT_IMPLEMENTED, "Not Implemented", "The requested method is not recognized by this server." }, { HTTP_UNAUTHORIZED, "Unauthorized", "" }, { HTTP_NOT_FOUND, "Not Found", "The requested URL was not found on this server." }, { HTTP_BAD_REQUEST, "Bad Request", "Unsupported method." }, { HTTP_FORBIDDEN, "Forbidden", "" }, { HTTP_INTERNAL_SERVER_ERROR, "Internal Server Error", "Internal Server Error" }, #if 0 /* not implemented */ { HTTP_CREATED, "Created" }, { HTTP_ACCEPTED, "Accepted" }, { HTTP_NO_CONTENT, "No Content" }, { HTTP_MULTIPLE_CHOICES, "Multiple Choices" }, { HTTP_MOVED_PERMANENTLY, "Moved Permanently" }, { HTTP_NOT_MODIFIED, "Not Modified" }, { HTTP_BAD_GATEWAY, "Bad Gateway", "" }, { HTTP_SERVICE_UNAVAILABLE, "Service Unavailable", "" }, #endif }; static const char RFC1123FMT[] = "%a, %d %b %Y %H:%M:%S GMT"; NetworkHttpThread::NetworkHttpThread(NetworkHttp& netOutput) : onetOutput(netOutput){ } void* NetworkHttpThread::function(){ dprintf(DBG_THREADS, "NetworkHttpThread: %d\n", gettid()); onetOutput.run(); return 0; } HttpConnection::HttpConnection(Control& control, int fd, BSocketAddressINET from) : BSocket(fd), ocontrol(control), ofrom(from){ BString s; if((s = ocontrol.oconfig.findValue("HtmlDir:")) != "") ohtmlDir = s; else ohtmlDir = "html"; ofile = fdopen(fd, "rw"); } HttpConnection::~HttpConnection(){ fclose(ofile); } BError HttpConnection::recvLine(BString& line){ BError err; char buf[BufSize]; if(fgets(buf, sizeof(buf), ofile)) line = buf; else err.set(1); return err; } BError HttpConnection::sendString(BString line){ BSize n; return send(line, line.len(), n); } BString encodeString(BString str) { int n; char* res = new char [str.len() * 6 + 1]; char* p = res; BString rstr; for(n = 0; n < str.len(); n++){ if(isalnum(str[n])) *p++ = str[n]; else p += sprintf(p, "&#%d;", (unsigned char)str[n]); } *p = '\0'; rstr = res; delete[] res; return rstr; } BString decodeString(BString str, int option) { const char* pin = str.retStr(); char* res = new char [str.len() + 1]; char* pout = res; BString rstr; char c; while((c = *pin++) != '\0') { unsigned value1, value2; if(option && c == '+'){ *pout++ = ' '; continue; } if(c != '%'){ *pout++ = c; continue; } if(sscanf(pin, "%1X", &value1) != 1 || sscanf(pin+1, "%1X", &value2) != 1){ if(!option) return ""; *pout++ = '%'; continue; } value1 = value1 * 16 + value2; if(!option && (value1 == '/' || value1 == '\0')){ return pin + 1; } *pout++ = value1; pin += 2; } *pout = '\0'; rstr = res; delete[] res; return rstr; } BError HttpConnection::sendHttpError(int num, BString str){ BError err; sendString(BString("HTTP/1.0 ") + num + " " + str + "\r\n"); sendString("Content-type: text/html; charset=UTF-8\r\n"); sendString("Connection : close\r\n"); sendString("\r\n"); return err; } BError HttpConnection::sendHeader(int num, BString contentType){ unsigned i; unsigned numNames = sizeof(httpResponseNames) / sizeof(httpResponseNames[0]); BString responseString; BString infoString; time_t timer = time(0); char timeStr[80]; BString header; for(i = 0; i < numNames; i++) { if(int(httpResponseNames[i].type) == num){ responseString = httpResponseNames[i].name; infoString = httpResponseNames[i].info; break; } } strftime(timeStr, sizeof(timeStr), RFC1123FMT, gmtime(&timer)); header = BString("HTTP/1.0 ") + num + " " + responseString + "\r\n"; // header += BString("Content-type: ") + contentType + "\r\n"; header += BString("Content-type: ") + contentType + "; charset=UTF-8\r\n"; header += BString("Date: ") + timeStr + "\r\n"; header += BString("Connection: close\r\n"); #ifdef ZAP strftime(timeStr, sizeof(timeStr), RFC1123FMT, gmtime(&timer)); header += BString("Last-Modified: ") + timeStr + "\r\n"; header += BString("Content-length: ") +contentLength + "\r\n"; #endif header += BString("\r\n"); return sendString(header); } void HttpConnection::start(){ BThread::start(); } void* HttpConnection::function(){ BError err; dprintf(DBG_HTTP, "%s: Starting\n", __FUNCTION__); while(1){ if(err = process()){ break; } } dprintf(DBG_HTTP, "%s: Exiting\n", __FUNCTION__); return 0; } BError HttpConnection::initTms(){ BError err; BString hostName = "localhost"; // Connect to TMS control objects if(err = otmsControl.connectService(BString("//") + hostName + "/tmsControl")){ cerr << "Error: cannot connect to tmsControl: " << err.getString() << "\n"; return err; } if(err = otmsProcess.connectService(BString("//") + hostName + "/tmsProcess")){ cerr << "Error: cannot connect to tmsProcess: " << err.getString() << "\n"; return err; } #ifdef ZAP if(events){ if(err = tmsEventsProcess(tmsProcess, hostName)){ cerr << err.getString() + "\n"; return 1; } } #endif return err; } BError HttpConnection::process(){ BError err; BString line; char cmd[BufSize]; char url[BufSize]; dprintf(DBG_HTTP, "%s: Start\n", __FUNCTION__); // Read command if(err = recvLine(line)) return err; dprintf(DBG_HTTP, "Received: %s\n", __FUNCTION__, line.retStr()); if(sscanf(line, "%[^ ] %[^ ] HTTP/%*d.%*d", cmd, url) != 2){ sendHeader(HTTP_BAD_REQUEST); return err.set(1, "Invalid HTTP request"); } dprintf(DBG_HTTP, "%s: Cmd: %s Url: %s\n", __FUNCTION__, cmd, url); // Read header while(! (err = recvLine(line))){ // printf("LenLen: %d\n", line.len()); hd8(line, line.len()); line.removeNL(); dprintf(DBG_HTTP, "%s: Received: %d %s\n", __FUNCTION__, line.len(), line.retStr()); if(line.len() == 1) break; } if(strcasecmp(cmd, "GET")){ sendHeader(HTTP_NOT_IMPLEMENTED); return err.set(1, "Invalid HTTP request"); } if((BString(url) == "/") || (BString(url) == "index.html") || !strncmp(url, "/config.cgi", 11) || !strncmp(url, "/help.cgi", 9) || !strncmp(url, "/status.cgi", 9) || !strncmp(url, "/statistics.cgi", 9) || !strncmp(url, "/dataraw.cgi", 11) ){ err = processInternal(url); } else { err = processFile(url); } fclose(ofile); close(); err.set(1); return err; } BError HttpConnection::createPage(BHtmlPage& page){ BError err; BHtml h; BHtml t; h = BHtml("div"); h.append("a", "class='plain' href='/'", "Home")->append("br"); h.append("a", "class='plain' href='/config.cgi'", "Config")->append("br"); h.append("a", "class='plain' href='/status.cgi'", "Status")->append("br"); h.append("a", "class='plain' href='/statistics.cgi'", "Statistics")->append("br"); h.append("a", "class='plain' href='/help.cgi'", "Help")->append("br"); h.append("a", "class='plain' href='http://www.beam.ltd.uk'", "BEAM")->append("br"); h.append("a", "class='plain' href='http://www.alphadata.co.uk'", "Alpha Data")->append("br"); h.append("a", "class='plain' href='http://portal.beam.ltd.uk/support/cern'", "TMS Support")->append("br"); page.setLeftSide(h.render()); return err; } BError HttpConnection::processFile(BString url){ BError err; BFile file; BString ext; int n; const char* const* table; const char* try_suffix; BString mimeType; BString s; BString str; BSize nsent; BHtmlPage page; dprintf(DBG_HTTP, "HttpConnection::processFile: Url: (%s)\n", url.retStr()); createPage(page); if((url == "") || (url == "/")) url = "index.html"; n = url.findReverse('.'); if(n >= 0) ext = url.subString(n + 1, -1); for(table = suffixTable; *table; table += 2){ if((try_suffix = strstr(*table, ext)) != 0){ try_suffix += strlen(ext); if(*try_suffix == 0 || *try_suffix == '.') break; } } mimeType = table[1]; if(err = file.open(ohtmlDir + "/" + url, "r")){ sendHeader(HTTP_NOT_FOUND); str = BString("Error: File not found.\r\n"); send(str, str.len(), nsent); return err.set(1, "File not found"); } dprintf(DBG_HTTP, "%s: Send Header Response\n", __FUNCTION__); sendHeader(HTTP_OK, mimeType); if(mimeType == "text/html"){ str = ""; while(file.readString(s) > 0){ str += s; } page.setTitle("TMS File"); page.setContent(htmlStrip(str)); str = page.render(); send(str, str.len(), nsent); } else { dprintf(DBG_HTTP, "%s: SendFile\n", __FUNCTION__); while(1){ char buf[1024]; int n; BSize nw; if((n = file.read(buf, 1024)) > 0) send(buf, n, nw); else break; } } return err; } BError HttpConnection::processInternal(BString url){ BError err; BHtmlPage page; BHtml h; BHtml t; BHtml* r; BString str; BSize nsent; BList<BString> sl; BList<BString> sl1; BString urlBase; BNameValueList<BString> args; BIter i; dprintf(DBG_HTTP, "%s: Start\n", __FUNCTION__); createPage(page); sl = url.getTokenList("?"); if(sl.number() >= 2){ urlBase = sl[0]; sl = sl[1].getTokenList("&"); for(sl.start(i); !sl.isEnd(i); sl.next(i)){ sl1 = sl[i].getTokenList("="); if(sl1.number() == 1){ args.append(BNameValue<BString>("", sl1[0])); } else if(sl1.number() == 2){ args.append(BNameValue<BString>(sl1[0], sl1[1])); } } } else { urlBase = url; } #ifndef ZAP printf("Base: %s\n", urlBase.retStr()); printf("Args: "); for(args.start(i); !args.isEnd(i); args.next(i)){ printf("%s=%s,", args[i].getName().retStr(), args[i].getValue().retStr()); } printf("\n"); #endif err = initTms(); if((urlBase == "/") || (urlBase == "/index.html")){ printf("DoIndex\n"); page.setTitle("TMS Main"); h = BHtml("div"); h.append("h1", "", "TMS Web Interface"); h.append("p", "", "This web interface provides information on the running TMS system "); h.appendText("and provides the ability to fetch data and diagnostics information from the system."); page.setContent(h.render()); str = page.render(); sendHeader(HTTP_OK, "text/html"); send(str, str.len(), nsent); } else if(urlBase == "/config.cgi"){ unsigned int n; BIter i; BError e; ConfigInfo configInfo; page.setTitle("TMS Configuration"); if(e = otmsControl.getConfiguration(configInfo)){ str = BString("TMS Access Error: ") + e.getString() + "\r\n"; sendHeader(HTTP_SERVICE_UNAVAILABLE); send(str, str.len(), nsent); } else { t = BHtml("table", "border=1"); r = t.append("tr"); r->append("th", "", "Channel"); r->append("th", "", "Module Number"); r->append("th", "", "Pupe Number"); r->append("th", "", "Pupe Channel"); for(n = 0; n < configInfo.puReferences.size(); n++){ r = t.append("tr"); r->append("td", "", n + 1); r->append("td", "", configInfo.puReferences[n].moduleNum); r->append("td", "", configInfo.puReferences[n].pupeNum); r->append("td", "", configInfo.puReferences[n].pupeChan); } } page.setContent(t.render()); str = page.render(); sendHeader(HTTP_OK, "text/html"); send(str, str.len(), nsent); } else if(urlBase == "/status.cgi"){ BIter i; BError e; BList<NameValue> list; page.setTitle("TMS Status"); if(e = otmsControl.getStatus(list)){ str = BString("TMS Access Error: ") + e.getString() + "\r\n"; sendHeader(HTTP_SERVICE_UNAVAILABLE); send(str, str.len(), nsent); return err.set(1, "HTTP_SERVICE_UNAVAILABLE"); } else { t = BHtml("table", "border=1"); for(list.start(i); !list.isEnd(i); list.next(i)){ r = t.append("tr"); r->append("td", "", list[i].name); r->append("td", "", list[i].value); } } page.setContent(t.render()); str = page.render(); sendHeader(HTTP_OK, "text/html"); send(str, str.len(), nsent); } else if(urlBase == "/statistics.cgi"){ BIter i; BError e; BList<NameValue> list; page.setTitle("TMS Statistics"); if(e = otmsControl.getStatistics(list)){ str = BString("TMS Access Error: ") + e.getString() + "\r\n"; sendHeader(HTTP_SERVICE_UNAVAILABLE); send(str, str.len(), nsent); return err.set(1, "HTTP_SERVICE_UNAVAILABLE"); } else { t = BHtml("table", "border=1"); for(list.start(i); !list.isEnd(i); list.next(i)){ r = t.append("tr"); r->append("td", "", list[i].name); r->append("td", "", list[i].value); } } page.setContent(t.render()); str = page.render(); sendHeader(HTTP_OK, "text/html"); send(str, str.len(), nsent); } else if(urlBase == "/dataraw.cgi"){ BUInt32 i; BError e; DataInfo dataInfo; Data data; BUInt32 cn = 0; BString ct; BString* v; BString outputType; // Set data require and wait for data if(e = otmsProcess.getCycleInfo(cn, ct)){ str = BString("TMS Access Error: ") + e.getString() + "\r\n"; sendHeader(HTTP_SERVICE_UNAVAILABLE); send(str, str.len(), nsent); return err.set(1, "HTTP_SERVICE_UNAVAILABLE"); } dataInfo.cycleNumber = cn; dataInfo.channel = 1; dataInfo.cyclePeriod = 0; dataInfo.startTime = 0; dataInfo.orbitNumber = 0; dataInfo.bunchNumber = 0; dataInfo.numValues = 0; dataInfo.beyondPeriod = 0; dataInfo.function = 0; dataInfo.argument = 0; if(v = args.find("channel")) dataInfo.channel = v->retInt(); if(v = args.find("cyclePeriod")) dataInfo.cyclePeriod = v->retInt(); if(v = args.find("startTime")) dataInfo.startTime = v->retInt(); if(v = args.find("orbitNumber")) dataInfo.orbitNumber = v->retInt(); if(v = args.find("bunchNumber")) dataInfo.bunchNumber = v->retInt(); if(v = args.find("numValues")) dataInfo.numValues = v->retInt(); if(v = args.find("function")) dataInfo.function = v->retInt(); if(v = args.find("argument")) dataInfo.argument = v->retInt(); if(v = args.find("outputType")) outputType = *v; if(e = otmsProcess.getData(dataInfo, data)){ str = BString("TMS Access Error: ") + e.getString() + "\r\n"; sendHeader(HTTP_SERVICE_UNAVAILABLE); send(str, str.len(), nsent); return err.set(1, "HTTP_SERVICE_UNAVAILABLE"); } else { str = ""; for(i = 0; i < data.dataValues.size(); i++){ str = str + data.dataValues[i].sigma + " " + data.dataValues[i].deltaX + " " + data.dataValues[i].deltaY + "\n"; } } if(outputType == "Graph"){ BFile df; BFile hf; BString s; BString hstr; df.open("/tmp/data.txt", "w"); hf.open(ohtmlDir + "/data.html", "r"); df.writeString(str); df.close(); plotData("/tmp/data.txt", 1, "Sigma", ohtmlDir + "/tmp/data0.png"); plotData("/tmp/data.txt", 2, "DeltaX", ohtmlDir + "/tmp/data1.png"); plotData("/tmp/data.txt", 3, "DeltaY", ohtmlDir + "/tmp/data2.png"); while(hf.readString(s) > 0){ hstr += s; } hstr = htmlStrip(hstr); t = BHtml("table", "border=1"); r = t.append("tr"); r->append("td")->append("img", "src='tmp/data0.png'"); r = t.append("tr"); r->append("td")->append("img", "src='tmp/data1.png'"); r = t.append("tr"); r->append("td")->append("img", "src='tmp/data2.png'"); page.setTitle("TMS Data"); page.setContent(t.render() + hstr); str = page.render(); sendHeader(HTTP_OK, "text/html"); send(str, str.len(), nsent); } else { sendHeader(HTTP_OK, "text/plain"); send(str, str.len(), nsent); } } else if(urlBase == "/help.cgi"){ page.setTitle("TMS Help"); t = BHtml("div"); t.append("li")->append("a", "href='/doc/index.html'", "Local Documentation"); t.append("li")->append("a", "href='http://portal.beam.ltd.uk/support/cern'", "BEAM TMS Support"); page.setContent(t.render()); str = page.render(); sendHeader(HTTP_OK, "text/html"); send(str, str.len(), nsent); } else { sendHeader(HTTP_NOT_FOUND); str = BString("Error: File not found.\r\n"); send(str, str.len(), nsent); return err.set(1, "HTTP_NOT_FOUND"); } return err; } BString HttpConnection::htmlStrip(BString str){ const char* s; const char* e; int start; int num; s = strcasestr(str, "<body>"); e = strcasestr(str, "</body>"); if(s && e){ start = s - str.retStr() + 6; num = e - str.retStr() - start; return str.subString(start, num); } else { return str; } } BError HttpConnection::plotData(BString dataFileName, int channel, BString name, BString outFileName){ BError err; BString cmd; FILE* f; cmd = BString("set term png picsize 640 150; set output '") + outFileName + "';"; cmd = cmd + "set ylabel '" + name + "';"; cmd = cmd + "plot '" + dataFileName + "' using " + channel + " with lines;"; // printf("Cmd: %s\n", cmd.retStr()); f = popen("gnuplot -", "w"); fwrite(cmd, 1, cmd.len(), f); pclose(f); return err; } NetworkHttp::NetworkHttp(Control& control) : BSocket(STREAM), ocontrol(control), orunThread(*this){ } NetworkHttp::~NetworkHttp(){ BIter i; for(oconnections.start(i); !oconnections.isEnd(i); ){ delete oconnections[i]; oconnections.del(i); } } BError NetworkHttp::init(){ BError err; BSocketAddressINET add; int basePort = 80; BString s; dprintf(DBG_HTTP, "%s: Starting\n", __FUNCTION__); if((s = ocontrol.oconfig.findValue("HttpPort:")) != "") basePort = s.retInt(); // Create control socket setReuseAddress(1); // Connect up data socket add.set("", basePort); if(bind(add)) return BError(BString("Unable to bind to socket: ") + basePort + " " + strerror(errno)); listen(); // Don't let the threads have these sockets fcntl(getFd(), F_SETFD, FD_CLOEXEC); orunThread.start(); return err; } BError NetworkHttp::start(){ BError err; return err; } BError NetworkHttp::stop(){ BError err; return err; } BError NetworkHttp::run(){ BError err; BIter i; BPoll poll; int fd; int nfd; HttpConnection* c; BSocketAddressINET from; dprintf(DBG_HTTP, "%s: Thread Started\n", __FUNCTION__); poll.append(getFd()); while(1){ poll.doPoll(fd, 1000000); if(fd == getFd()){ dprintf(DBG_HTTP, "%s: Accept connection\n", __FUNCTION__); accept(nfd, from); c = new HttpConnection(ocontrol, nfd, from); oconnections.append(c); c->start(); } } return err; }