#include "THttpServer.h"
#include "TTimer.h"
#include "TSystem.h"
#include "TImage.h"
#include "TROOT.h"
#include "TClass.h"
#include "THttpEngine.h"
#include "TRootSniffer.h"
#include "TRootSnifferStore.h"
#include <string>
#include <cstdlib>
THttpCallArg::THttpCallArg() :
TObject(),
fTopName(),
fPathName(),
fFileName(),
fQuery(),
fCond(),
fContentType(),
fContentEncoding(),
fContent(),
fBinData(0),
fBinDataLength(0)
{
}
THttpCallArg::~THttpCallArg()
{
if (fBinData) {
free(fBinData);
fBinData = 0;
}
}
void THttpCallArg::SetPathAndFileName(const char *fullpath)
{
fPathName.Clear();
fFileName.Clear();
if (fullpath == 0) return;
const char *rslash = strrchr(fullpath, '/');
if (rslash == 0) {
fFileName = fullpath;
} else {
while ((fullpath != rslash) && (*fullpath == '/')) fullpath++;
fPathName.Append(fullpath, rslash - fullpath);
if (fPathName == "/") fPathName.Clear();
fFileName = rslash + 1;
}
}
void THttpCallArg::FillHttpHeader(TString &hdr, Bool_t normal)
{
const char *header = normal ? "HTTP/1.1" : "Status:";
if ((fContentType.Length() == 0) || Is404()) {
hdr.Form("%s 404 Not Found\r\n"
"Content-Length: 0\r\n"
"Connection: close\r\n\r\n", header);
return;
}
hdr.Form("%s 200 OK\r\n"
"Content-Type: %s\r\n"
"Connection: keep-alive\r\n"
"Content-Length: %ld\r\n",
header,
GetContentType(),
GetContentLength());
if (fContentEncoding.Length() > 0)
hdr.Append(TString::Format("Content-Encoding: %s\r\n", fContentEncoding.Data()));
hdr.Append("\r\n");
}
class THttpTimer : public TTimer {
public:
THttpServer *fServer;
THttpTimer(Long_t milliSec, Bool_t mode, THttpServer *serv) :
TTimer(milliSec, mode), fServer(serv)
{
}
virtual ~THttpTimer()
{
}
virtual void Timeout()
{
if (fServer) fServer->ProcessRequests();
}
};
THttpServer::THttpServer(const char *engine) :
TNamed("http", "ROOT http server"),
fEngines(),
fTimer(0),
fSniffer(0),
fMainThrdId(0),
fHttpSys(),
fRootSys(),
fJSRootIOSys(),
fTopName("ROOT"),
fMutex(),
fCallArgs()
{
fMainThrdId = TThread::SelfId();
const char *rootsys = gSystem->Getenv("ROOTSYS");
if (rootsys != 0) fRootSys = rootsys;
#ifdef COMPILED_WITH_DABC
const char *dabcsys = gSystem->Getenv("DABCSYS");
if (dabcsys != 0) fHttpSys = TString::Format("%s/plugins/http", dabcsys);
#else
if (fRootSys.Length() > 0)
fHttpSys = TString::Format("%s/etc/http", fRootSys.Data());
#endif
if (fHttpSys.IsNull()) fHttpSys = ".";
const char *jsrootiosys = gSystem->Getenv("JSROOTIOSYS");
if (jsrootiosys != 0)
fJSRootIOSys = jsrootiosys;
else
fJSRootIOSys = fHttpSys + "/JSRootIO";
fDefaultPage = fHttpSys + "/files/main.htm";
fDrawPage = fHttpSys + "/files/single.htm";
SetSniffer(new TRootSniffer("sniff"));
SetTimer(100, kTRUE);
CreateEngine(engine);
}
THttpServer::~THttpServer()
{
fEngines.Delete();
SetSniffer(0);
SetTimer(0);
}
void THttpServer::SetSniffer(TRootSniffer *sniff)
{
if (fSniffer) delete fSniffer;
fSniffer = sniff;
}
Bool_t THttpServer::CreateEngine(const char *engine)
{
if (engine == 0) return kFALSE;
const char *arg = strchr(engine, ':');
if (arg == 0) return kFALSE;
TString clname;
if (arg != engine) clname.Append(engine, arg - engine);
if ((clname.Length() == 0) || (clname == "http") || (clname == "civetweb"))
clname = "TCivetweb";
else if (clname == "fastcgi")
clname = "TFastCgi";
else if (clname == "dabc")
clname = "TDabcEngine";
TClass *engine_class = gROOT->LoadClass(clname.Data());
if (engine_class == 0) return kFALSE;
THttpEngine *eng = (THttpEngine *) engine_class->New();
if (eng == 0) return kFALSE;
eng->SetServer(this);
if (!eng->Create(arg + 1)) {
delete eng;
return kFALSE;
}
fEngines.Add(eng);
return kTRUE;
}
void THttpServer::SetTimer(Long_t milliSec, Bool_t mode)
{
if (fTimer) {
fTimer->Stop();
delete fTimer;
fTimer = 0;
}
if (milliSec > 0) {
fTimer = new THttpTimer(milliSec, mode, this);
fTimer->TurnOn();
}
}
Bool_t THttpServer::IsFileRequested(const char *uri, TString &res) const
{
if ((uri == 0) || (strlen(uri) == 0)) return kFALSE;
std::string fname = uri;
size_t pos = fname.rfind("httpsys/");
if (pos != std::string::npos) {
fname.erase(0, pos + 7);
res = fHttpSys + fname.c_str();
return kTRUE;
}
if (!fRootSys.IsNull()) {
pos = fname.rfind("rootsys/");
if (pos != std::string::npos) {
fname.erase(0, pos + 7);
res = fRootSys + fname.c_str();
return kTRUE;
}
}
if (!fJSRootIOSys.IsNull()) {
pos = fname.rfind("jsrootiosys/");
if (pos != std::string::npos) {
fname.erase(0, pos + 11);
res = fJSRootIOSys + fname.c_str();
return kTRUE;
}
}
return kFALSE;
}
Bool_t THttpServer::ExecuteHttp(THttpCallArg *arg)
{
if (fMainThrdId == TThread::SelfId()) {
ProcessRequest(arg);
return kTRUE;
}
fMutex.Lock();
fCallArgs.Add(arg);
fMutex.UnLock();
arg->fCond.Wait();
return kTRUE;
}
void THttpServer::ProcessRequests()
{
if (fMainThrdId != TThread::SelfId()) {
Error("ProcessRequests", "Should be called only from main ROOT thread");
return;
}
while (true) {
THttpCallArg *arg = 0;
fMutex.Lock();
if (fCallArgs.GetSize() > 0) {
arg = (THttpCallArg *) fCallArgs.First();
fCallArgs.RemoveFirst();
}
fMutex.UnLock();
if (arg == 0) break;
ProcessRequest(arg);
arg->fCond.Signal();
}
TIter iter(&fEngines);
THttpEngine *engine = 0;
while ((engine = (THttpEngine *)iter()) != 0)
engine->Process();
}
void THttpServer::ProcessRequest(THttpCallArg *arg)
{
if (arg->fFileName.IsNull() || (arg->fFileName == "index.htm")) {
Bool_t usedefaultpage = kTRUE;
if (!fSniffer->CanExploreItem(arg->fPathName.Data()) &&
fSniffer->CanDrawItem(arg->fPathName.Data())) usedefaultpage = kFALSE;
if (usedefaultpage)
arg->fContent = fDefaultPage;
else
arg->fContent = fDrawPage;
arg->SetFile();
return;
}
TString buf = arg->fFileName;
if (IsFileRequested(arg->fFileName.Data(), buf)) {
arg->fContent = buf;
arg->SetFile();
return;
}
if (arg->fFileName == "h.xml") {
arg->fContent.Form(
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<dabc version=\"2\" xmlns:dabc=\"http://dabc.gsi.de/xhtml\" path=\"%s\">\n", arg->fPathName.Data());
{
TRootSnifferStoreXml store(arg->fContent);
const char *topname = fTopName.Data();
if (arg->fTopName.Length() > 0) topname = arg->fTopName.Data();
fSniffer->ScanHierarchy(topname, arg->fPathName.Data(), &store);
}
arg->fContent.Append("</dabc>\n");
arg->SetXml();
return;
}
if (arg->fFileName == "h.json") {
arg->fContent.Append("{\n");
{
TRootSnifferStoreJson store(arg->fContent);
const char *topname = fTopName.Data();
if (arg->fTopName.Length() > 0) topname = arg->fTopName.Data();
fSniffer->ScanHierarchy(topname, arg->fPathName.Data(), &store);
}
arg->fContent.Append("\n}\n");
arg->SetJson();
return;
}
if (fSniffer->Produce(arg->fPathName.Data(), arg->fFileName.Data(), arg->fQuery.Data(), arg->fBinData, arg->fBinDataLength)) {
arg->SetContentType(GetMimeType(arg->fFileName.Data()));
return;
}
arg->Set404();
}
Bool_t THttpServer::Register(const char *subfolder, TObject *obj)
{
return fSniffer->RegisterObject(subfolder, obj);
}
Bool_t THttpServer::Unregister(TObject *obj)
{
return fSniffer->UnregisterObject(obj);
}
const char *THttpServer::GetMimeType(const char *path)
{
static const struct {
const char *extension;
int ext_len;
const char *mime_type;
} builtin_mime_types[] = {
{".xml", 4, "text/xml"},
{".json", 5, "application/json"},
{".bin", 4, "application/x-binary"},
{".gif", 4, "image/gif"},
{".jpg", 4, "image/jpeg"},
{".png", 4, "image/png"},
{".html", 5, "text/html"},
{".htm", 4, "text/html"},
{".shtm", 5, "text/html"},
{".shtml", 6, "text/html"},
{".css", 4, "text/css"},
{".js", 3, "application/x-javascript"},
{".ico", 4, "image/x-icon"},
{".jpeg", 5, "image/jpeg"},
{".svg", 4, "image/svg+xml"},
{".txt", 4, "text/plain"},
{".torrent", 8, "application/x-bittorrent"},
{".wav", 4, "audio/x-wav"},
{".mp3", 4, "audio/x-mp3"},
{".mid", 4, "audio/mid"},
{".m3u", 4, "audio/x-mpegurl"},
{".ogg", 4, "application/ogg"},
{".ram", 4, "audio/x-pn-realaudio"},
{".xslt", 5, "application/xml"},
{".xsl", 4, "application/xml"},
{".ra", 3, "audio/x-pn-realaudio"},
{".doc", 4, "application/msword"},
{".exe", 4, "application/octet-stream"},
{".zip", 4, "application/x-zip-compressed"},
{".xls", 4, "application/excel"},
{".tgz", 4, "application/x-tar-gz"},
{".tar", 4, "application/x-tar"},
{".gz", 3, "application/x-gunzip"},
{".arj", 4, "application/x-arj-compressed"},
{".rar", 4, "application/x-arj-compressed"},
{".rtf", 4, "application/rtf"},
{".pdf", 4, "application/pdf"},
{".swf", 4, "application/x-shockwave-flash"},
{".mpg", 4, "video/mpeg"},
{".webm", 5, "video/webm"},
{".mpeg", 5, "video/mpeg"},
{".mov", 4, "video/quicktime"},
{".mp4", 4, "video/mp4"},
{".m4v", 4, "video/x-m4v"},
{".asf", 4, "video/x-ms-asf"},
{".avi", 4, "video/x-msvideo"},
{".bmp", 4, "image/bmp"},
{".ttf", 4, "application/x-font-ttf"},
{NULL, 0, NULL}
};
int path_len = strlen(path);
for (int i = 0; builtin_mime_types[i].extension != NULL; i++) {
if (path_len <= builtin_mime_types[i].ext_len) continue;
const char *ext = path + (path_len - builtin_mime_types[i].ext_len);
if (strcmp(ext, builtin_mime_types[i].extension) == 0) {
return builtin_mime_types[i].mime_type;
}
}
return "text/plain";
}