/*******************************************************************************
* 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"){
UInt32 i;
BError e;
DataInfo dataInfo;
Data data;
UInt32 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("/tmp/data.txt", "w");
BFile hf(ohtmlDir + "/data.html", "r");
BString s;
BString hstr;
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;
}