22 #include "RConfigure.h"
51 class THttpTimer :
public TTimer {
56 THttpTimer(Long_t milliSec, Bool_t mode, THttpServer &serv) : TTimer(milliSec, mode), fServer(serv) {}
60 virtual void Timeout() { fServer.ProcessRequests(); }
106 ClassImp(THttpServer);
139 THttpServer::THttpServer(
const char *engine) : TNamed(
"http",
"ROOT http server")
141 const char *jsrootsys = gSystem->Getenv(
"JSROOTSYS");
143 jsrootsys = gEnv->GetValue(
"HttpServ.JSRootPath", jsrootsys);
145 if (jsrootsys && *jsrootsys) {
146 if ((strncmp(jsrootsys,
"http://", 7)==0) || (strncmp(jsrootsys,
"https://", 8)==0))
149 fJSROOTSYS = jsrootsys;
152 if (fJSROOTSYS.Length() == 0) {
153 TString jsdir = TString::Format(
"%s/js", TROOT::GetDataDir().Data());
154 if (gSystem->ExpandPathName(jsdir)) {
155 ::Warning(
"THttpServer::THttpServer",
"problems resolving '%s', set JSROOTSYS to proper JavaScript ROOT location",
163 AddLocation(
"currentdir/",
".");
164 AddLocation(
"jsrootsys/", fJSROOTSYS.Data());
165 AddLocation(
"rootsys/", TROOT::GetRootSys());
167 fDefaultPage = fJSROOTSYS +
"/files/online.htm";
168 fDrawPage = fJSROOTSYS +
"/files/draw.htm";
170 TRootSniffer *sniff =
nullptr;
171 if (strstr(engine,
"basic_sniffer")) {
172 sniff =
new TRootSniffer(
"sniff");
173 sniff->SetScanGlobalDir(kFALSE);
174 sniff->CreateOwnTopFolder();
176 sniff = (TRootSniffer *)gROOT->ProcessLineSync(
"new TRootSnifferFull(\"sniff\");");
184 if (strchr(engine,
';') == 0) {
185 CreateEngine(engine);
187 TObjArray *lst = TString(engine).Tokenize(
";");
189 for (Int_t n = 0; n <= lst->GetLast(); n++) {
190 const char *opt = lst->At(n)->GetName();
191 if ((strcmp(opt,
"readonly") == 0) || (strcmp(opt,
"ro") == 0)) {
192 GetSniffer()->SetReadOnly(kTRUE);
193 }
else if ((strcmp(opt,
"readwrite") == 0) || (strcmp(opt,
"rw") == 0)) {
194 GetSniffer()->SetReadOnly(kFALSE);
195 }
else if (strcmp(opt,
"global") == 0) {
196 GetSniffer()->SetScanGlobalDir(kTRUE);
197 }
else if (strcmp(opt,
"noglobal") == 0) {
198 GetSniffer()->SetScanGlobalDir(kFALSE);
199 }
else if (strncmp(opt,
"cors=", 5) == 0) {
201 }
else if (strcmp(opt,
"cors") == 0) {
215 THttpServer::~THttpServer()
220 TIter iter(&fEngines);
221 while (
auto engine = dynamic_cast<THttpEngine *>(iter()))
236 void THttpServer::SetSniffer(TRootSniffer *sniff)
247 void THttpServer::SetTerminate()
255 Bool_t THttpServer::IsReadOnly()
const
257 return fSniffer ? fSniffer->IsReadOnly() : kTRUE;
265 void THttpServer::SetReadOnly(Bool_t readonly)
268 fSniffer->SetReadOnly(readonly);
277 void THttpServer::AddLocation(
const char *prefix,
const char *path)
279 if (!prefix || (*prefix == 0))
283 fLocations.erase(fLocations.find(prefix));
285 fLocations[prefix] = path;
297 void THttpServer::SetJSROOT(
const char *location)
299 fJSROOT = location ? location :
"";
308 void THttpServer::SetDefaultPage(
const std::string &filename)
310 if (!filename.empty())
311 fDefaultPage = filename;
313 fDefaultPage = fJSROOTSYS +
"/files/online.htm";
316 fDefaultPageCont.clear();
325 void THttpServer::SetDrawPage(
const std::string &filename)
327 if (!filename.empty())
328 fDrawPage = filename;
330 fDrawPage = fJSROOTSYS +
"/files/draw.htm";
333 fDrawPageCont.clear();
346 Bool_t THttpServer::CreateEngine(
const char *engine)
351 const char *arg = strchr(engine,
':');
357 clname.Append(engine, arg - engine);
359 THttpEngine *eng =
nullptr;
361 if ((clname.Length() == 0) || (clname ==
"http") || (clname ==
"civetweb")) {
362 eng =
new TCivetweb(kFALSE);
363 }
else if (clname ==
"https") {
364 eng =
new TCivetweb(kTRUE);
365 }
else if (clname ==
"fastcgi") {
366 eng =
new TFastCgi();
371 TClass *engine_class = gROOT->LoadClass(clname.Data());
375 eng = (THttpEngine *)engine_class->New();
380 eng->SetServer(
this);
382 if (!eng->Create(arg + 1)) {
408 void THttpServer::SetTimer(Long_t milliSec, Bool_t mode)
417 Error(
"SetTimer",
"Server runs already in special thread, therefore no any timer can be created");
419 fTimer =
new THttpTimer(milliSec, mode, *
this);
434 void THttpServer::CreateServerThread()
443 std::thread thrd([
this] {
445 while (fOwnThread && !fTerminated) {
446 int nprocess = ProcessRequests();
453 std::this_thread::sleep_for(std::chrono::milliseconds(1));
458 fThrd = std::move(thrd);
465 void THttpServer::StopServerThread()
479 Bool_t THttpServer::VerifyFilePath(
const char *fname)
481 if (!fname || (*fname == 0))
486 while (*fname != 0) {
489 const char *next = strpbrk(fname,
"/\\");
494 if ((next == fname + 2) && (*fname ==
'.') && (*(fname + 1) ==
'.')) {
503 if ((next == fname + 1) && (*fname ==
'.')) {
528 Bool_t THttpServer::IsFileRequested(
const char *uri, TString &res)
const
530 if (!uri || (*uri == 0))
535 for (
auto &entry : fLocations) {
536 Ssiz_t pos = fname.Index(entry.first.c_str());
539 fname.Remove(0, pos + (entry.first.length() - 1));
540 if (!VerifyFilePath(fname.Data()))
542 res = entry.second.c_str();
543 if ((fname[0] ==
'/') && (res[res.Length() - 1] ==
'/'))
544 res.Resize(res.Length() - 1);
557 Bool_t THttpServer::ExecuteHttp(std::shared_ptr<THttpCallArg> arg)
562 if ((fMainThrdId != 0) && (fMainThrdId == TThread::SelfId())) {
571 std::unique_lock<std::mutex> lk(fMutex);
590 Bool_t THttpServer::SubmitHttp(std::shared_ptr<THttpCallArg> arg, Bool_t can_run_immediately)
595 if (can_run_immediately && (fMainThrdId != 0) && (fMainThrdId == TThread::SelfId())) {
597 arg->NotifyCondition();
602 std::unique_lock<std::mutex> lk(fMutex);
616 Int_t THttpServer::ProcessRequests()
618 if (fMainThrdId == 0)
619 fMainThrdId = TThread::SelfId();
621 if (fMainThrdId != TThread::SelfId()) {
622 Error(
"ProcessRequests",
"Should be called only from main ROOT thread");
628 std::unique_lock<std::mutex> lk(fMutex, std::defer_lock);
632 std::shared_ptr<THttpCallArg> arg;
635 if (!fArgs.empty()) {
644 if (arg->fFileName ==
"root_batch_holder.js") {
645 ProcessBatchHolder(arg);
649 fSniffer->SetCurrentCallArg(arg.get());
654 fSniffer->SetCurrentCallArg(
nullptr);
656 fSniffer->SetCurrentCallArg(
nullptr);
659 arg->NotifyCondition();
663 TIter iter(&fEngines);
664 THttpEngine *engine =
nullptr;
665 while ((engine = (THttpEngine *)iter()) !=
nullptr) {
679 void THttpServer::MissedRequest(THttpCallArg *arg)
689 void THttpServer::ProcessBatchHolder(std::shared_ptr<THttpCallArg> &arg)
691 auto wsptr = FindWS(arg->GetPathName());
693 if (!wsptr || !wsptr->ProcessBatchHolder(arg)) {
695 arg->NotifyCondition();
704 void THttpServer::ProcessRequest(std::shared_ptr<THttpCallArg> arg)
711 if ((arg->fFileName ==
"root.websocket") || (arg->fFileName ==
"root.longpoll")) {
717 fOldProcessSignature = kTRUE;
718 ProcessRequest(arg.get());
719 if (fOldProcessSignature) {
720 Error(
"ProcessRequest",
"Deprecated signature is used, please used std::shared_ptr<THttpCallArg>");
724 if (arg->fFileName.IsNull() || (arg->fFileName ==
"index.htm") || (arg->fFileName ==
"default.htm")) {
726 if (arg->fFileName ==
"default.htm") {
728 arg->fContent = ReadFileContent((fJSROOTSYS +
"/files/online.htm").Data());
731 auto wsptr = FindWS(arg->GetPathName());
733 auto handler = wsptr.get();
736 handler =
dynamic_cast<THttpWSHandler *
>(fSniffer->FindTObjectInHierarchy(arg->fPathName.Data()));
740 arg->fContent = handler->GetDefaultPageContent().Data();
742 if (arg->fContent.find(
"file:") == 0) {
743 const char *fname = arg->fContent.c_str() + 5;
745 if (!IsFileRequested(fname, resolve)) resolve = fname;
746 arg->fContent = ReadFileContent(resolve.Data());
749 handler->VerifyDefaultPageContent(arg);
751 arg->CheckWSPageContent(handler);
755 if (arg->fContent.empty()) {
757 if (fDefaultPageCont.empty())
758 fDefaultPageCont = ReadFileContent(fDefaultPage);
760 arg->fContent = fDefaultPageCont;
763 if (arg->fContent.empty()) {
767 if (fJSROOT.Length() > 0) {
768 std::string repl(
"=\"");
769 repl.append(fJSROOT.Data());
770 if (repl.back() !=
'/')
772 arg->ReplaceAllinContent(
"=\"jsrootsys/", repl);
775 const char *hjsontag =
"\"$$$h.json$$$\"";
778 if (arg->fContent.find(hjsontag) != std::string::npos) {
780 TRootSnifferStoreJson store(h_json, kTRUE);
781 const char *topname = fTopName.Data();
782 if (arg->fTopName.Length() > 0)
783 topname = arg->fTopName.Data();
784 fSniffer->ScanHierarchy(topname, arg->fPathName.Data(), &store);
786 arg->ReplaceAllinContent(hjsontag, h_json.Data());
788 arg->AddNoCacheHeader();
790 if (arg->fQuery.Index(
"nozip") == kNPOS)
793 arg->SetContentType(
"text/html");
798 if (arg->fFileName ==
"draw.htm") {
799 if (fDrawPageCont.empty())
800 fDrawPageCont = ReadFileContent(fDrawPage);
802 if (fDrawPageCont.empty()) {
805 const char *rootjsontag =
"\"$$$root.json$$$\"";
806 const char *hjsontag =
"\"$$$h.json$$$\"";
808 arg->fContent = fDrawPageCont;
811 if (fJSROOT.Length() > 0) {
812 std::string repl(
"=\"");
813 repl.append(fJSROOT.Data());
814 if (repl.back() !=
'/')
816 arg->ReplaceAllinContent(
"=\"jsrootsys/", repl);
819 if ((arg->fQuery.Index(
"no_h_json") == kNPOS) && (arg->fQuery.Index(
"webcanvas") == kNPOS) &&
820 (arg->fContent.find(hjsontag) != std::string::npos)) {
822 TRootSnifferStoreJson store(h_json, kTRUE);
823 const char *topname = fTopName.Data();
824 if (arg->fTopName.Length() > 0)
825 topname = arg->fTopName.Data();
826 fSniffer->ScanHierarchy(topname, arg->fPathName.Data(), &store, kTRUE);
828 arg->ReplaceAllinContent(hjsontag, h_json.Data());
831 if ((arg->fQuery.Index(
"no_root_json") == kNPOS) && (arg->fQuery.Index(
"webcanvas") == kNPOS) &&
832 (arg->fContent.find(rootjsontag) != std::string::npos)) {
834 if (fSniffer->Produce(arg->fPathName.Data(),
"root.json",
"compact=23", str))
835 arg->ReplaceAllinContent(rootjsontag, str);
837 arg->AddNoCacheHeader();
838 if (arg->fQuery.Index(
"nozip") == kNPOS)
840 arg->SetContentType(
"text/html");
845 if ((arg->fFileName ==
"favicon.ico") && arg->fPathName.IsNull()) {
846 arg->SetFile(fJSROOTSYS +
"/img/RootIcon.ico");
851 if (IsFileRequested(arg->fFileName.Data(), filename)) {
852 arg->SetFile(filename);
857 if (!arg->fPathName.IsNull() && !arg->fFileName.IsNull()) {
858 TString wsname = arg->fPathName, fname;
859 auto pos = wsname.First(
'/');
861 wsname = arg->fPathName;
863 wsname = arg->fPathName(0, pos);
864 fname = arg->fPathName(pos + 1, arg->fPathName.Length() - pos);
868 fname.Append(arg->fFileName);
870 if (VerifyFilePath(fname.Data())) {
872 auto ws = FindWS(wsname.Data());
874 if (ws && ws->CanServeFiles()) {
875 TString fdir = ws->GetDefaultPageContent();
877 if (fdir.Index(
"file:") == 0) {
879 auto separ = fdir.Last(
'/');
881 fdir.Resize(separ + 1);
893 filename = arg->fFileName;
895 Bool_t iszip = kFALSE;
896 if (filename.EndsWith(
".gz")) {
897 filename.Resize(filename.Length() - 3);
901 if ((filename ==
"h.xml") || (filename ==
"get.xml")) {
903 Bool_t compact = arg->fQuery.Index(
"compact") != kNPOS;
907 res.Form(
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
910 res.Append(
"<root>");
914 TRootSnifferStoreXml store(res, compact);
916 const char *topname = fTopName.Data();
917 if (arg->fTopName.Length() > 0)
918 topname = arg->fTopName.Data();
919 fSniffer->ScanHierarchy(topname, arg->fPathName.Data(), &store, filename ==
"get.xml");
922 res.Append(
"</root>");
926 arg->SetContent(std::string(res.Data()));
929 }
else if (filename ==
"h.json") {
931 TRootSnifferStoreJson store(res, arg->fQuery.Index(
"compact") != kNPOS);
932 const char *topname = fTopName.Data();
933 if (arg->fTopName.Length() > 0)
934 topname = arg->fTopName.Data();
935 fSniffer->ScanHierarchy(topname, arg->fPathName.Data(), &store);
936 arg->SetContent(std::string(res.Data()));
938 }
else if (fSniffer->Produce(arg->fPathName.Data(), filename.Data(), arg->fQuery.Data(), arg->fContent)) {
940 arg->SetContentType(GetMimeType(filename.Data()));
943 MissedRequest(arg.get());
950 arg->SetZipping(THttpCallArg::kZipAlways);
952 if (filename ==
"root.bin") {
955 const char *parname = fSniffer->IsStreamerInfoItem(arg->fPathName.Data()) ?
"BVersion" :
"MVersion";
956 arg->AddHeader(parname, Form(
"%u", (
unsigned)fSniffer->GetStreamerInfoHash()));
960 arg->AddNoCacheHeader();
964 arg->AddHeader(
"Access-Control-Allow-Origin", GetCors());
970 void THttpServer::ProcessRequest(THttpCallArg *)
972 fOldProcessSignature = kFALSE;
980 Bool_t THttpServer::Register(
const char *subfolder, TObject *obj)
982 return fSniffer->RegisterObject(subfolder, obj);
990 Bool_t THttpServer::Unregister(TObject *obj)
992 return fSniffer->UnregisterObject(obj);
1000 void THttpServer::RegisterWS(std::shared_ptr<THttpWSHandler> ws)
1002 std::lock_guard<std::mutex> grd(fWSMutex);
1003 fWSHandlers.emplace_back(ws);
1009 void THttpServer::UnregisterWS(std::shared_ptr<THttpWSHandler> ws)
1011 std::lock_guard<std::mutex> grd(fWSMutex);
1012 for (
int n = (
int)fWSHandlers.size(); n > 0; --n)
1013 if ((fWSHandlers[n - 1] == ws) || fWSHandlers[n - 1]->IsDisabled())
1014 fWSHandlers.erase(fWSHandlers.begin() + n - 1);
1022 std::shared_ptr<THttpWSHandler> THttpServer::FindWS(
const char *name)
1024 std::lock_guard<std::mutex> grd(fWSMutex);
1025 for (
auto &ws : fWSHandlers) {
1026 if (strcmp(name, ws->GetName()) == 0)
1036 Bool_t THttpServer::ExecuteWS(std::shared_ptr<THttpCallArg> &arg, Bool_t external_thrd, Bool_t wait_process)
1043 auto wsptr = FindWS(arg->GetPathName());
1045 auto handler = wsptr.get();
1047 if (!handler && !external_thrd)
1048 handler =
dynamic_cast<THttpWSHandler *
>(fSniffer->FindTObjectInHierarchy(arg->fPathName.Data()));
1050 if (external_thrd && (!handler || !handler->AllowMTProcess())) {
1051 std::unique_lock<std::mutex> lk(fMutex);
1055 arg->fCond.wait(lk);
1063 Bool_t process = kFALSE;
1065 if (arg->fFileName ==
"root.websocket") {
1067 process = handler->HandleWS(arg);
1068 }
else if (arg->fFileName ==
"root.longpoll") {
1070 if (arg->fQuery.BeginsWith(
"raw_connect") || arg->fQuery.BeginsWith(
"txt_connect")) {
1073 arg->SetMethod(
"WS_CONNECT");
1075 bool israw = arg->fQuery.BeginsWith(
"raw_connect");
1078 arg->CreateWSEngine<THttpLongPollEngine>(israw);
1080 if (handler->HandleWS(arg)) {
1081 arg->SetMethod(
"WS_READY");
1083 if (handler->HandleWS(arg))
1084 arg->SetTextContent(std::string(israw ?
"txt:" :
"") + std::to_string(arg->GetWSId()));
1086 arg->TakeWSEngine();
1089 process = arg->IsText();
1092 url.SetOptions(arg->fQuery);
1094 Int_t connid = url.GetIntValueFromOptions(
"connection");
1095 arg->SetWSId((UInt_t)connid);
1096 if (url.HasOption(
"close")) {
1097 arg->SetMethod(
"WS_CLOSE");
1098 arg->SetTextContent(
"OK");
1100 arg->SetMethod(
"WS_DATA");
1103 process = handler->HandleWS(arg);
1118 void THttpServer::Restrict(
const char *path,
const char *options)
1120 fSniffer->Restrict(path, options);
1151 Bool_t THttpServer::RegisterCommand(
const char *cmdname,
const char *method,
const char *icon)
1153 return fSniffer->RegisterCommand(cmdname, method, icon);
1159 Bool_t THttpServer::Hide(
const char *foldername, Bool_t hide)
1161 return SetItemField(foldername,
"_hidden", hide ?
"true" : (
const char *)0);
1170 Bool_t THttpServer::SetIcon(
const char *fullname,
const char *iconname)
1172 return SetItemField(fullname,
"_icon", iconname);
1177 Bool_t THttpServer::CreateItem(
const char *fullname,
const char *title)
1179 return fSniffer->CreateItem(fullname, title);
1184 Bool_t THttpServer::SetItemField(
const char *fullname,
const char *name,
const char *value)
1186 return fSniffer->SetItemField(fullname, name, value);
1191 const char *THttpServer::GetItemField(
const char *fullname,
const char *name)
1193 return fSniffer->GetItemField(fullname, name);
1199 const char *THttpServer::GetMimeType(
const char *path)
1201 static const struct {
1202 const char *extension;
1204 const char *mime_type;
1205 } builtin_mime_types[] = {{
".xml", 4,
"text/xml"},
1206 {
".json", 5,
"application/json"},
1207 {
".bin", 4,
"application/x-binary"},
1208 {
".gif", 4,
"image/gif"},
1209 {
".jpg", 4,
"image/jpeg"},
1210 {
".png", 4,
"image/png"},
1211 {
".html", 5,
"text/html"},
1212 {
".htm", 4,
"text/html"},
1213 {
".shtm", 5,
"text/html"},
1214 {
".shtml", 6,
"text/html"},
1215 {
".css", 4,
"text/css"},
1216 {
".js", 3,
"application/x-javascript"},
1217 {
".ico", 4,
"image/x-icon"},
1218 {
".jpeg", 5,
"image/jpeg"},
1219 {
".svg", 4,
"image/svg+xml"},
1220 {
".txt", 4,
"text/plain"},
1221 {
".torrent", 8,
"application/x-bittorrent"},
1222 {
".wav", 4,
"audio/x-wav"},
1223 {
".mp3", 4,
"audio/x-mp3"},
1224 {
".mid", 4,
"audio/mid"},
1225 {
".m3u", 4,
"audio/x-mpegurl"},
1226 {
".ogg", 4,
"application/ogg"},
1227 {
".ram", 4,
"audio/x-pn-realaudio"},
1228 {
".xslt", 5,
"application/xml"},
1229 {
".xsl", 4,
"application/xml"},
1230 {
".ra", 3,
"audio/x-pn-realaudio"},
1231 {
".doc", 4,
"application/msword"},
1232 {
".exe", 4,
"application/octet-stream"},
1233 {
".zip", 4,
"application/x-zip-compressed"},
1234 {
".xls", 4,
"application/excel"},
1235 {
".tgz", 4,
"application/x-tar-gz"},
1236 {
".tar", 4,
"application/x-tar"},
1237 {
".gz", 3,
"application/x-gunzip"},
1238 {
".arj", 4,
"application/x-arj-compressed"},
1239 {
".rar", 4,
"application/x-arj-compressed"},
1240 {
".rtf", 4,
"application/rtf"},
1241 {
".pdf", 4,
"application/pdf"},
1242 {
".swf", 4,
"application/x-shockwave-flash"},
1243 {
".mpg", 4,
"video/mpeg"},
1244 {
".webm", 5,
"video/webm"},
1245 {
".mpeg", 5,
"video/mpeg"},
1246 {
".mov", 4,
"video/quicktime"},
1247 {
".mp4", 4,
"video/mp4"},
1248 {
".m4v", 4,
"video/x-m4v"},
1249 {
".asf", 4,
"video/x-ms-asf"},
1250 {
".avi", 4,
"video/x-msvideo"},
1251 {
".bmp", 4,
"image/bmp"},
1252 {
".ttf", 4,
"application/x-font-ttf"},
1255 int path_len = strlen(path);
1257 for (
int i = 0; builtin_mime_types[i].extension != NULL; i++) {
1258 if (path_len <= builtin_mime_types[i].ext_len)
1260 const char *ext = path + (path_len - builtin_mime_types[i].ext_len);
1261 if (strcmp(ext, builtin_mime_types[i].extension) == 0) {
1262 return builtin_mime_types[i].mime_type;
1266 return "text/plain";
1272 char *THttpServer::ReadFileContent(
const char *filename, Int_t &len)
1276 std::ifstream is(filename, std::ios::in | std::ios::binary);
1280 is.seekg(0, is.end);
1282 is.seekg(0, is.beg);
1284 char *buf = (
char *)malloc(len);
1298 std::string THttpServer::ReadFileContent(
const std::string &filename)
1300 std::ifstream is(filename, std::ios::in | std::ios::binary);
1303 is.seekg(0, std::ios::end);
1304 res.resize(is.tellg());
1305 is.seekg(0, std::ios::beg);
1306 is.read((
char *)res.data(), res.length());