Logo ROOT   6.30.04
Reference Guide
 All Namespaces Files Pages
THttpServer.cxx
Go to the documentation of this file.
1 // $Id$
2 // Author: Sergey Linev 21/12/2013
3 
4 /*************************************************************************
5  * Copyright (C) 1995-2013, Rene Brun and Fons Rademakers. *
6  * All rights reserved. *
7  * *
8  * For the licensing terms see $ROOTSYS/LICENSE. *
9  * For the list of contributors see $ROOTSYS/README/CREDITS. *
10  *************************************************************************/
11 
12 #include "THttpServer.h"
13 
14 #include "TThread.h"
15 #include "TTimer.h"
16 #include "TSystem.h"
17 #include "TROOT.h"
18 #include "TUrl.h"
19 #include "TEnv.h"
20 #include "TError.h"
21 #include "TClass.h"
22 #include "RConfigure.h"
23 #include "TRegexp.h"
24 
25 #include "THttpEngine.h"
26 #include "THttpLongPollEngine.h"
27 #include "THttpWSHandler.h"
28 #include "TRootSniffer.h"
29 #include "TRootSnifferStore.h"
30 #include "TCivetweb.h"
31 #include "TFastCgi.h"
32 
33 #include <string>
34 #include <cstdlib>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <fstream>
38 #include <chrono>
39 
40 ////////////////////////////////////////////////////////////////////////////////
41 
42 //////////////////////////////////////////////////////////////////////////
43 // //
44 // THttpTimer //
45 // //
46 // Specialized timer for THttpServer //
47 // Provides regular calls of THttpServer::ProcessRequests() method //
48 // //
49 //////////////////////////////////////////////////////////////////////////
50 
51 class THttpTimer : public TTimer {
52 public:
53  THttpServer &fServer; ///!< server processing requests
54 
55  /// constructor
56  THttpTimer(Long_t milliSec, Bool_t mode, THttpServer &serv) : TTimer(milliSec, mode), fServer(serv) {}
57 
58  /// timeout handler
59  /// used to process http requests in main ROOT thread
60  virtual void Timeout() { fServer.ProcessRequests(); }
61 };
62 
63 //////////////////////////////////////////////////////////////////////////////////////////////////
64 
65 //////////////////////////////////////////////////////////////////////////
66 // //
67 // THttpServer //
68 // //
69 // Online http server for arbitrary ROOT application //
70 // //
71 // Idea of THttpServer - provide remote http access to running //
72 // ROOT application and enable HTML/JavaScript user interface. //
73 // Any registered object can be requested and displayed in the browser. //
74 // There are many benefits of such approach: //
75 // * standard http interface to ROOT application //
76 // * no any temporary ROOT files when access data //
77 // * user interface running in all browsers //
78 // //
79 // Starting HTTP server //
80 // //
81 // To start http server, at any time create instance //
82 // of the THttpServer class like: //
83 // serv = new THttpServer("http:8080"); //
84 // //
85 // This will starts civetweb-based http server with http port 8080. //
86 // Than one should be able to open address "http://localhost:8080" //
87 // in any modern browser (IE, Firefox, Chrome) and browse objects, //
88 // created in application. By default, server can access files, //
89 // canvases and histograms via gROOT pointer. All such objects //
90 // can be displayed with JSROOT graphics. //
91 // //
92 // At any time one could register other objects with the command: //
93 // //
94 // TGraph* gr = new TGraph(10); //
95 // gr->SetName("gr1"); //
96 // serv->Register("graphs/subfolder", gr); //
97 // //
98 // If objects content is changing in the application, one could //
99 // enable monitoring flag in the browser - than objects view //
100 // will be regularly updated. //
101 // //
102 // More information: https://root.cern/root/htmldoc/guides/HttpServer/HttpServer.html //
103 // //
104 //////////////////////////////////////////////////////////////////////////
105 
106 ClassImp(THttpServer);
107 
108 ////////////////////////////////////////////////////////////////////////////////
109 /// constructor
110 ///
111 /// As argument, one specifies engine kind which should be
112 /// created like "http:8080". One could specify several engines
113 /// at once, separating them with semicolon (";"). Following engines are supported:
114 ///
115 /// http - TCivetweb, civetweb-based implementation of http protocol
116 /// fastcgi - TFastCgi, special protocol for communicating with web servers
117 ///
118 /// For each created engine one should provide socket port number like "http:8080" or "fastcgi:9000".
119 /// Additional engine-specific options can be supplied with URL syntax like "http:8080?thrds=10".
120 /// Full list of supported options should be checked in engines docu.
121 ///
122 /// One also can configure following options, separated by semicolon:
123 ///
124 /// readonly, ro - set read-only mode (default)
125 /// readwrite, rw - allows methods execution of registered objects
126 /// global - scans global ROOT lists for existing objects (default)
127 /// noglobal - disable scan of global lists
128 /// cors - enable CORS header with origin="*"
129 /// cors=domain - enable CORS header with origin="domain"
130 /// basic_sniffer - use basic sniffer without support of hist, gpad, graph classes
131 ///
132 /// For example, create http server, which allows cors headers and disable scan of global lists,
133 /// one should provide "http:8080;cors;noglobal" as parameter
134 ///
135 /// THttpServer uses JavaScript ROOT (https://root.cern/js) to implement web clients UI.
136 /// Normally JSROOT sources are used from $ROOTSYS/js directory,
137 /// but one could set JSROOTSYS shell variable to specify alternative location
138 
139 THttpServer::THttpServer(const char *engine) : TNamed("http", "ROOT http server")
140 {
141  const char *jsrootsys = gSystem->Getenv("JSROOTSYS");
142  if (!jsrootsys)
143  jsrootsys = gEnv->GetValue("HttpServ.JSRootPath", jsrootsys);
144 
145  if (jsrootsys && *jsrootsys) {
146  if ((strncmp(jsrootsys, "http://", 7)==0) || (strncmp(jsrootsys, "https://", 8)==0))
147  fJSROOT = jsrootsys;
148  else
149  fJSROOTSYS = jsrootsys;
150  }
151 
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",
156  jsdir.Data());
157  fJSROOTSYS = ".";
158  } else {
159  fJSROOTSYS = jsdir;
160  }
161  }
162 
163  AddLocation("currentdir/", ".");
164  AddLocation("jsrootsys/", fJSROOTSYS.Data());
165  AddLocation("rootsys/", TROOT::GetRootSys());
166 
167  fDefaultPage = fJSROOTSYS + "/files/online.htm";
168  fDrawPage = fJSROOTSYS + "/files/draw.htm";
169 
170  TRootSniffer *sniff = nullptr;
171  if (strstr(engine, "basic_sniffer")) {
172  sniff = new TRootSniffer("sniff");
173  sniff->SetScanGlobalDir(kFALSE);
174  sniff->CreateOwnTopFolder(); // use dedicated folder
175  } else {
176  sniff = (TRootSniffer *)gROOT->ProcessLineSync("new TRootSnifferFull(\"sniff\");");
177  }
178 
179  SetSniffer(sniff);
180 
181  // start timer
182  SetTimer(20, kTRUE);
183 
184  if (strchr(engine, ';') == 0) {
185  CreateEngine(engine);
186  } else {
187  TObjArray *lst = TString(engine).Tokenize(";");
188 
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) {
200  SetCors(opt + 5);
201  } else if (strcmp(opt, "cors") == 0) {
202  SetCors("*");
203  } else
204  CreateEngine(opt);
205  }
206 
207  delete lst;
208  }
209 }
210 
211 ////////////////////////////////////////////////////////////////////////////////
212 /// destructor
213 /// delete all http engines and sniffer
214 
215 THttpServer::~THttpServer()
216 {
217  StopServerThread();
218 
219  if (fTerminated) {
220  TIter iter(&fEngines);
221  while (auto engine = dynamic_cast<THttpEngine *>(iter()))
222  engine->Terminate();
223  }
224 
225  fEngines.Delete();
226 
227  SetSniffer(nullptr);
228 
229  SetTimer(0);
230 }
231 
232 ////////////////////////////////////////////////////////////////////////////////
233 /// Set TRootSniffer to the server
234 /// Server takes ownership over sniffer
235 
236 void THttpServer::SetSniffer(TRootSniffer *sniff)
237 {
238  if (fSniffer)
239  delete fSniffer;
240  fSniffer = sniff;
241 }
242 
243 ////////////////////////////////////////////////////////////////////////////////
244 /// Set termination flag,
245 /// No any further requests will be processed, server only can be destroyed afterwards
246 
247 void THttpServer::SetTerminate()
248 {
249  fTerminated = kTRUE;
250 }
251 
252 ////////////////////////////////////////////////////////////////////////////////
253 /// returns read-only mode
254 
255 Bool_t THttpServer::IsReadOnly() const
256 {
257  return fSniffer ? fSniffer->IsReadOnly() : kTRUE;
258 }
259 
260 ////////////////////////////////////////////////////////////////////////////////
261 /// Set read-only mode for the server (default on)
262 /// In read-only server is not allowed to change any ROOT object, registered to the server
263 /// Server also cannot execute objects method via exe.json request
264 
265 void THttpServer::SetReadOnly(Bool_t readonly)
266 {
267  if (fSniffer)
268  fSniffer->SetReadOnly(readonly);
269 }
270 
271 ////////////////////////////////////////////////////////////////////////////////
272 /// add files location, which could be used in the server
273 /// one could map some system folder to the server like AddLocation("mydir/","/home/user/specials");
274 /// Than files from this directory could be addressed via server like
275 /// http://localhost:8080/mydir/myfile.root
276 
277 void THttpServer::AddLocation(const char *prefix, const char *path)
278 {
279  if (!prefix || (*prefix == 0))
280  return;
281 
282  if (!path)
283  fLocations.erase(fLocations.find(prefix));
284  else
285  fLocations[prefix] = path;
286 }
287 
288 ////////////////////////////////////////////////////////////////////////////////
289 /// Set location of JSROOT to use with the server
290 /// One could specify address like:
291 /// https://root.cern.ch/js/5.6.3/
292 /// http://jsroot.gsi.de/5.6.3/
293 /// This allows to get new JSROOT features with old server,
294 /// reduce load on THttpServer instance, also startup time can be improved
295 /// When empty string specified (default), local copy of JSROOT is used (distributed with ROOT)
296 
297 void THttpServer::SetJSROOT(const char *location)
298 {
299  fJSROOT = location ? location : "";
300 }
301 
302 ////////////////////////////////////////////////////////////////////////////////
303 /// Set file name of HTML page, delivered by the server when
304 /// http address is opened in the browser.
305 /// By default, $ROOTSYS/js/files/online.htm page is used
306 /// When empty filename is specified, default page will be used
307 
308 void THttpServer::SetDefaultPage(const std::string &filename)
309 {
310  if (!filename.empty())
311  fDefaultPage = filename;
312  else
313  fDefaultPage = fJSROOTSYS + "/files/online.htm";
314 
315  // force to read page content next time again
316  fDefaultPageCont.clear();
317 }
318 
319 ////////////////////////////////////////////////////////////////////////////////
320 /// Set file name of HTML page, delivered by the server when
321 /// objects drawing page is requested from the browser
322 /// By default, $ROOTSYS/js/files/draw.htm page is used
323 /// When empty filename is specified, default page will be used
324 
325 void THttpServer::SetDrawPage(const std::string &filename)
326 {
327  if (!filename.empty())
328  fDrawPage = filename;
329  else
330  fDrawPage = fJSROOTSYS + "/files/draw.htm";
331 
332  // force to read page content next time again
333  fDrawPageCont.clear();
334 }
335 
336 ////////////////////////////////////////////////////////////////////////////////
337 /// factory method to create different http engines
338 /// At the moment two engine kinds are supported:
339 /// civetweb (default) and fastcgi
340 /// Examples:
341 /// "http:8080" or "civetweb:8080" or ":8080" - creates civetweb web server with http port 8080
342 /// "fastcgi:9000" - creates fastcgi server with port 9000
343 /// One could apply additional parameters, using URL syntax:
344 /// "http:8080?thrds=10"
345 
346 Bool_t THttpServer::CreateEngine(const char *engine)
347 {
348  if (!engine)
349  return kFALSE;
350 
351  const char *arg = strchr(engine, ':');
352  if (!arg)
353  return kFALSE;
354 
355  TString clname;
356  if (arg != engine)
357  clname.Append(engine, arg - engine);
358 
359  THttpEngine *eng = nullptr;
360 
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();
367  }
368 
369  if (!eng) {
370  // ensure that required engine class exists before we try to create it
371  TClass *engine_class = gROOT->LoadClass(clname.Data());
372  if (!engine_class)
373  return kFALSE;
374 
375  eng = (THttpEngine *)engine_class->New();
376  if (!eng)
377  return kFALSE;
378  }
379 
380  eng->SetServer(this);
381 
382  if (!eng->Create(arg + 1)) {
383  delete eng;
384  return kFALSE;
385  }
386 
387  fEngines.Add(eng);
388 
389  return kTRUE;
390 }
391 
392 ////////////////////////////////////////////////////////////////////////////////
393 /// create timer which will invoke ProcessRequests() function periodically
394 /// Timer is required to perform all actions in main ROOT thread
395 /// Method arguments are the same as for TTimer constructor
396 /// By default, sync timer with 100 ms period is created
397 ///
398 /// It is recommended to always use sync timer mode and only change period to
399 /// adjust server reaction time. Use of async timer requires, that application regularly
400 /// calls gSystem->ProcessEvents(). It happens automatically in ROOT interactive shell.
401 /// If milliSec == 0, no timer will be created.
402 /// In this case application should regularly call ProcessRequests() method.
403 ///
404 /// Async timer allows to use THttpServer in applications, which does not have explicit
405 /// gSystem->ProcessEvents() calls. But be aware, that such timer can interrupt any system call
406 /// (like malloc) and can lead to dead locks, especially in multi-threaded applications.
407 
408 void THttpServer::SetTimer(Long_t milliSec, Bool_t mode)
409 {
410  if (fTimer) {
411  fTimer->Stop();
412  delete fTimer;
413  fTimer = nullptr;
414  }
415  if (milliSec > 0) {
416  if (fOwnThread) {
417  Error("SetTimer", "Server runs already in special thread, therefore no any timer can be created");
418  } else {
419  fTimer = new THttpTimer(milliSec, mode, *this);
420  fTimer->TurnOn();
421  }
422  }
423 }
424 
425 ////////////////////////////////////////////////////////////////////////////////
426 /// Creates special thread to process all requests, directed to http server
427 ///
428 /// Should be used with care - only dedicated instance of TRootSniffer is allowed
429 /// By default THttpServer allows to access global lists pointers gROOT or gFile.
430 /// To be on the safe side, all kind of such access performed from the main thread.
431 /// Therefore usage of specialized thread means that no any global pointers will
432 /// be accessible by THttpServer
433 
434 void THttpServer::CreateServerThread()
435 {
436  if (fOwnThread)
437  return;
438 
439  SetTimer(0);
440  fMainThrdId = 0;
441  fOwnThread = true;
442 
443  std::thread thrd([this] {
444  int nempty = 0;
445  while (fOwnThread && !fTerminated) {
446  int nprocess = ProcessRequests();
447  if (nprocess > 0)
448  nempty = 0;
449  else
450  nempty++;
451  if (nempty > 1000) {
452  nempty = 0;
453  std::this_thread::sleep_for(std::chrono::milliseconds(1));
454  }
455  }
456  });
457 
458  fThrd = std::move(thrd);
459 }
460 
461 ////////////////////////////////////////////////////////////////////////////////
462 /// Stop server thread
463 /// Normally called shortly before http server destructor
464 
465 void THttpServer::StopServerThread()
466 {
467  if (!fOwnThread)
468  return;
469 
470  fOwnThread = false;
471  fThrd.join();
472  fMainThrdId = 0;
473 }
474 
475 ////////////////////////////////////////////////////////////////////////////////
476 /// Checked that filename does not contains relative path below current directory
477 /// Used to prevent access to files below current directory
478 
479 Bool_t THttpServer::VerifyFilePath(const char *fname)
480 {
481  if (!fname || (*fname == 0))
482  return kFALSE;
483 
484  Int_t level = 0;
485 
486  while (*fname != 0) {
487 
488  // find next slash or backslash
489  const char *next = strpbrk(fname, "/\\");
490  if (next == 0)
491  return kTRUE;
492 
493  // most important - change to parent dir
494  if ((next == fname + 2) && (*fname == '.') && (*(fname + 1) == '.')) {
495  fname += 3;
496  level--;
497  if (level < 0)
498  return kFALSE;
499  continue;
500  }
501 
502  // ignore current directory
503  if ((next == fname + 1) && (*fname == '.')) {
504  fname += 2;
505  continue;
506  }
507 
508  // ignore slash at the front
509  if (next == fname) {
510  fname++;
511  continue;
512  }
513 
514  fname = next + 1;
515  level++;
516  }
517 
518  return kTRUE;
519 }
520 
521 ////////////////////////////////////////////////////////////////////////////////
522 /// Verifies that request is just file name
523 /// File names typically contains prefix like "jsrootsys/"
524 /// If true, method returns real name of the file,
525 /// which should be delivered to the client
526 /// Method is thread safe and can be called from any thread
527 
528 Bool_t THttpServer::IsFileRequested(const char *uri, TString &res) const
529 {
530  if (!uri || (*uri == 0))
531  return kFALSE;
532 
533  TString fname(uri);
534 
535  for (auto &entry : fLocations) {
536  Ssiz_t pos = fname.Index(entry.first.c_str());
537  if (pos == kNPOS)
538  continue;
539  fname.Remove(0, pos + (entry.first.length() - 1));
540  if (!VerifyFilePath(fname.Data()))
541  return kFALSE;
542  res = entry.second.c_str();
543  if ((fname[0] == '/') && (res[res.Length() - 1] == '/'))
544  res.Resize(res.Length() - 1);
545  res.Append(fname);
546  return kTRUE;
547  }
548 
549  return kFALSE;
550 }
551 
552 ////////////////////////////////////////////////////////////////////////////////
553 /// Executes http request, specified in THttpCallArg structure
554 /// Method can be called from any thread
555 /// Actual execution will be done in main ROOT thread, where analysis code is running.
556 
557 Bool_t THttpServer::ExecuteHttp(std::shared_ptr<THttpCallArg> arg)
558 {
559  if (fTerminated)
560  return kFALSE;
561 
562  if ((fMainThrdId != 0) && (fMainThrdId == TThread::SelfId())) {
563  // should not happen, but one could process requests directly without any signaling
564 
565  ProcessRequest(arg);
566 
567  return kTRUE;
568  }
569 
570  // add call arg to the list
571  std::unique_lock<std::mutex> lk(fMutex);
572  fArgs.push(arg);
573  // and now wait until request is processed
574  arg->fCond.wait(lk);
575 
576  return kTRUE;
577 }
578 
579 ////////////////////////////////////////////////////////////////////////////////
580 /// Submit http request, specified in THttpCallArg structure
581 /// Contrary to ExecuteHttp, it will not block calling thread.
582 /// User should reimplement THttpCallArg::HttpReplied() method
583 /// to react when HTTP request is executed.
584 /// Method can be called from any thread
585 /// Actual execution will be done in main ROOT thread, where analysis code is running.
586 /// When called from main thread and can_run_immediately==kTRUE, will be
587 /// executed immediately.
588 /// Returns kTRUE when was executed.
589 
590 Bool_t THttpServer::SubmitHttp(std::shared_ptr<THttpCallArg> arg, Bool_t can_run_immediately)
591 {
592  if (fTerminated)
593  return kFALSE;
594 
595  if (can_run_immediately && (fMainThrdId != 0) && (fMainThrdId == TThread::SelfId())) {
596  ProcessRequest(arg);
597  arg->NotifyCondition();
598  return kTRUE;
599  }
600 
601  // add call arg to the list
602  std::unique_lock<std::mutex> lk(fMutex);
603  fArgs.push(arg);
604  return kFALSE;
605 }
606 
607 ////////////////////////////////////////////////////////////////////////////////
608 /// Process requests, submitted for execution
609 /// Returns number of processed requests
610 ///
611 /// Normally invoked by THttpTimer, when somewhere in the code
612 /// gSystem->ProcessEvents() is called.
613 /// User can call serv->ProcessRequests() directly, but only from main thread.
614 /// If special server thread is created, called from that thread
615 
616 Int_t THttpServer::ProcessRequests()
617 {
618  if (fMainThrdId == 0)
619  fMainThrdId = TThread::SelfId();
620 
621  if (fMainThrdId != TThread::SelfId()) {
622  Error("ProcessRequests", "Should be called only from main ROOT thread");
623  return 0;
624  }
625 
626  Int_t cnt = 0;
627 
628  std::unique_lock<std::mutex> lk(fMutex, std::defer_lock);
629 
630  // first process requests in the queue
631  while (true) {
632  std::shared_ptr<THttpCallArg> arg;
633 
634  lk.lock();
635  if (!fArgs.empty()) {
636  arg = fArgs.front();
637  fArgs.pop();
638  }
639  lk.unlock();
640 
641  if (!arg)
642  break;
643 
644  if (arg->fFileName == "root_batch_holder.js") {
645  ProcessBatchHolder(arg);
646  continue;
647  }
648 
649  fSniffer->SetCurrentCallArg(arg.get());
650 
651  try {
652  cnt++;
653  ProcessRequest(arg);
654  fSniffer->SetCurrentCallArg(nullptr);
655  } catch (...) {
656  fSniffer->SetCurrentCallArg(nullptr);
657  }
658 
659  arg->NotifyCondition();
660  }
661 
662  // regularly call Process() method of engine to let perform actions in ROOT context
663  TIter iter(&fEngines);
664  THttpEngine *engine = nullptr;
665  while ((engine = (THttpEngine *)iter()) != nullptr) {
666  if (fTerminated)
667  engine->Terminate();
668  engine->Process();
669  }
670 
671  return cnt;
672 }
673 
674 ////////////////////////////////////////////////////////////////////////////////
675 /// Method called when THttpServer cannot process request
676 /// By default such requests replied with 404 code
677 /// One could overwrite with method in derived class to process all kinds of such non-standard requests
678 
679 void THttpServer::MissedRequest(THttpCallArg *arg)
680 {
681  arg->Set404();
682 }
683 
684 ////////////////////////////////////////////////////////////////////////////////
685 /// Process special http request for root_batch_holder.js script
686 /// This kind of requests used to hold web browser running in headless mode
687 /// Intentionally requests does not replied immediately
688 
689 void THttpServer::ProcessBatchHolder(std::shared_ptr<THttpCallArg> &arg)
690 {
691  auto wsptr = FindWS(arg->GetPathName());
692 
693  if (!wsptr || !wsptr->ProcessBatchHolder(arg)) {
694  arg->Set404();
695  arg->NotifyCondition();
696  }
697 }
698 
699 ////////////////////////////////////////////////////////////////////////////////
700 /// Process single http request
701 /// Depending from requested path and filename different actions will be performed.
702 /// In most cases information is provided by TRootSniffer class
703 
704 void THttpServer::ProcessRequest(std::shared_ptr<THttpCallArg> arg)
705 {
706  if (fTerminated) {
707  arg->Set404();
708  return;
709  }
710 
711  if ((arg->fFileName == "root.websocket") || (arg->fFileName == "root.longpoll")) {
712  ExecuteWS(arg);
713  return;
714  }
715 
716  // this is just to support old Process(THttpCallArg*), should be deprecated after 6.20
717  fOldProcessSignature = kTRUE;
718  ProcessRequest(arg.get());
719  if (fOldProcessSignature) {
720  Error("ProcessRequest", "Deprecated signature is used, please used std::shared_ptr<THttpCallArg>");
721  return;
722  }
723 
724  if (arg->fFileName.IsNull() || (arg->fFileName == "index.htm") || (arg->fFileName == "default.htm")) {
725 
726  if (arg->fFileName == "default.htm") {
727 
728  arg->fContent = ReadFileContent((fJSROOTSYS + "/files/online.htm").Data());
729 
730  } else {
731  auto wsptr = FindWS(arg->GetPathName());
732 
733  auto handler = wsptr.get();
734 
735  if (!handler)
736  handler = dynamic_cast<THttpWSHandler *>(fSniffer->FindTObjectInHierarchy(arg->fPathName.Data()));
737 
738  if (handler) {
739 
740  arg->fContent = handler->GetDefaultPageContent().Data();
741 
742  if (arg->fContent.find("file:") == 0) {
743  const char *fname = arg->fContent.c_str() + 5;
744  TString resolve;
745  if (!IsFileRequested(fname, resolve)) resolve = fname;
746  arg->fContent = ReadFileContent(resolve.Data());
747  }
748 
749  handler->VerifyDefaultPageContent(arg);
750 
751  arg->CheckWSPageContent(handler);
752  }
753  }
754 
755  if (arg->fContent.empty()) {
756 
757  if (fDefaultPageCont.empty())
758  fDefaultPageCont = ReadFileContent(fDefaultPage);
759 
760  arg->fContent = fDefaultPageCont;
761  }
762 
763  if (arg->fContent.empty()) {
764  arg->Set404();
765  } else {
766  // replace all references on JSROOT
767  if (fJSROOT.Length() > 0) {
768  std::string repl("=\"");
769  repl.append(fJSROOT.Data());
770  if (repl.back() != '/')
771  repl.append("/");
772  arg->ReplaceAllinContent("=\"jsrootsys/", repl);
773  }
774 
775  const char *hjsontag = "\"$$$h.json$$$\"";
776 
777  // add h.json caching
778  if (arg->fContent.find(hjsontag) != std::string::npos) {
779  TString h_json;
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);
785 
786  arg->ReplaceAllinContent(hjsontag, h_json.Data());
787 
788  arg->AddNoCacheHeader();
789 
790  if (arg->fQuery.Index("nozip") == kNPOS)
791  arg->SetZipping();
792  }
793  arg->SetContentType("text/html");
794  }
795  return;
796  }
797 
798  if (arg->fFileName == "draw.htm") {
799  if (fDrawPageCont.empty())
800  fDrawPageCont = ReadFileContent(fDrawPage);
801 
802  if (fDrawPageCont.empty()) {
803  arg->Set404();
804  } else {
805  const char *rootjsontag = "\"$$$root.json$$$\"";
806  const char *hjsontag = "\"$$$h.json$$$\"";
807 
808  arg->fContent = fDrawPageCont;
809 
810  // replace all references on JSROOT
811  if (fJSROOT.Length() > 0) {
812  std::string repl("=\"");
813  repl.append(fJSROOT.Data());
814  if (repl.back() != '/')
815  repl.append("/");
816  arg->ReplaceAllinContent("=\"jsrootsys/", repl);
817  }
818 
819  if ((arg->fQuery.Index("no_h_json") == kNPOS) && (arg->fQuery.Index("webcanvas") == kNPOS) &&
820  (arg->fContent.find(hjsontag) != std::string::npos)) {
821  TString h_json;
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);
827 
828  arg->ReplaceAllinContent(hjsontag, h_json.Data());
829  }
830 
831  if ((arg->fQuery.Index("no_root_json") == kNPOS) && (arg->fQuery.Index("webcanvas") == kNPOS) &&
832  (arg->fContent.find(rootjsontag) != std::string::npos)) {
833  std::string str;
834  if (fSniffer->Produce(arg->fPathName.Data(), "root.json", "compact=23", str))
835  arg->ReplaceAllinContent(rootjsontag, str);
836  }
837  arg->AddNoCacheHeader();
838  if (arg->fQuery.Index("nozip") == kNPOS)
839  arg->SetZipping();
840  arg->SetContentType("text/html");
841  }
842  return;
843  }
844 
845  if ((arg->fFileName == "favicon.ico") && arg->fPathName.IsNull()) {
846  arg->SetFile(fJSROOTSYS + "/img/RootIcon.ico");
847  return;
848  }
849 
850  TString filename;
851  if (IsFileRequested(arg->fFileName.Data(), filename)) {
852  arg->SetFile(filename);
853  return;
854  }
855 
856  // check if websocket handler may serve file request
857  if (!arg->fPathName.IsNull() && !arg->fFileName.IsNull()) {
858  TString wsname = arg->fPathName, fname;
859  auto pos = wsname.First('/');
860  if (pos == kNPOS) {
861  wsname = arg->fPathName;
862  } else {
863  wsname = arg->fPathName(0, pos);
864  fname = arg->fPathName(pos + 1, arg->fPathName.Length() - pos);
865  fname.Append("/");
866  }
867 
868  fname.Append(arg->fFileName);
869 
870  if (VerifyFilePath(fname.Data())) {
871 
872  auto ws = FindWS(wsname.Data());
873 
874  if (ws && ws->CanServeFiles()) {
875  TString fdir = ws->GetDefaultPageContent();
876  // only when file is specified, can take directory, append prefix and file name
877  if (fdir.Index("file:") == 0) {
878  fdir.Remove(0, 5);
879  auto separ = fdir.Last('/');
880  if (separ != kNPOS)
881  fdir.Resize(separ + 1);
882  else
883  fdir = "./";
884 
885  fdir.Append(fname);
886  arg->SetFile(fdir);
887  return;
888  }
889  }
890  }
891  }
892 
893  filename = arg->fFileName;
894 
895  Bool_t iszip = kFALSE;
896  if (filename.EndsWith(".gz")) {
897  filename.Resize(filename.Length() - 3);
898  iszip = kTRUE;
899  }
900 
901  if ((filename == "h.xml") || (filename == "get.xml")) {
902 
903  Bool_t compact = arg->fQuery.Index("compact") != kNPOS;
904 
905  TString res;
906 
907  res.Form("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
908  if (!compact)
909  res.Append("\n");
910  res.Append("<root>");
911  if (!compact)
912  res.Append("\n");
913  {
914  TRootSnifferStoreXml store(res, compact);
915 
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");
920  }
921 
922  res.Append("</root>");
923  if (!compact)
924  res.Append("\n");
925 
926  arg->SetContent(std::string(res.Data()));
927 
928  arg->SetXml();
929  } else if (filename == "h.json") {
930  TString res;
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()));
937  arg->SetJson();
938  } else if (fSniffer->Produce(arg->fPathName.Data(), filename.Data(), arg->fQuery.Data(), arg->fContent)) {
939  // define content type base on extension
940  arg->SetContentType(GetMimeType(filename.Data()));
941  } else {
942  // miss request, user may process
943  MissedRequest(arg.get());
944  }
945 
946  if (arg->Is404())
947  return;
948 
949  if (iszip)
950  arg->SetZipping(THttpCallArg::kZipAlways);
951 
952  if (filename == "root.bin") {
953  // only for binary data master version is important
954  // it allows to detect if streamer info was modified
955  const char *parname = fSniffer->IsStreamerInfoItem(arg->fPathName.Data()) ? "BVersion" : "MVersion";
956  arg->AddHeader(parname, Form("%u", (unsigned)fSniffer->GetStreamerInfoHash()));
957  }
958 
959  // try to avoid caching on the browser
960  arg->AddNoCacheHeader();
961 
962  // potentially add cors header
963  if (IsCors())
964  arg->AddHeader("Access-Control-Allow-Origin", GetCors());
965 }
966 
967 ////////////////////////////////////////////////////////////////////////////////
968 /// \deprecated One should use signature with std::shared_ptr
969 
970 void THttpServer::ProcessRequest(THttpCallArg *)
971 {
972  fOldProcessSignature = kFALSE;
973 }
974 
975 ////////////////////////////////////////////////////////////////////////////////
976 /// Register object in folders hierarchy
977 ///
978 /// See TRootSniffer::RegisterObject() for more details
979 
980 Bool_t THttpServer::Register(const char *subfolder, TObject *obj)
981 {
982  return fSniffer->RegisterObject(subfolder, obj);
983 }
984 
985 ////////////////////////////////////////////////////////////////////////////////
986 /// Unregister object in folders hierarchy
987 ///
988 /// See TRootSniffer::UnregisterObject() for more details
989 
990 Bool_t THttpServer::Unregister(TObject *obj)
991 {
992  return fSniffer->UnregisterObject(obj);
993 }
994 
995 ////////////////////////////////////////////////////////////////////////////////
996 /// Register WS handler to the THttpServer
997 ///
998 /// Only such handler can be used in multi-threaded processing of websockets
999 
1000 void THttpServer::RegisterWS(std::shared_ptr<THttpWSHandler> ws)
1001 {
1002  std::lock_guard<std::mutex> grd(fWSMutex);
1003  fWSHandlers.emplace_back(ws);
1004 }
1005 
1006 ////////////////////////////////////////////////////////////////////////////////
1007 /// Unregister WS handler to the THttpServer
1008 
1009 void THttpServer::UnregisterWS(std::shared_ptr<THttpWSHandler> ws)
1010 {
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);
1015 }
1016 
1017 ////////////////////////////////////////////////////////////////////////////////
1018 /// Search WS handler with given name
1019 ///
1020 /// Handler must be registered with RegisterWS() method
1021 
1022 std::shared_ptr<THttpWSHandler> THttpServer::FindWS(const char *name)
1023 {
1024  std::lock_guard<std::mutex> grd(fWSMutex);
1025  for (auto &ws : fWSHandlers) {
1026  if (strcmp(name, ws->GetName()) == 0)
1027  return ws;
1028  }
1029 
1030  return nullptr;
1031 }
1032 
1033 ////////////////////////////////////////////////////////////////////////////////
1034 /// Execute WS related operation
1035 
1036 Bool_t THttpServer::ExecuteWS(std::shared_ptr<THttpCallArg> &arg, Bool_t external_thrd, Bool_t wait_process)
1037 {
1038  if (fTerminated) {
1039  arg->Set404();
1040  return kFALSE;
1041  }
1042 
1043  auto wsptr = FindWS(arg->GetPathName());
1044 
1045  auto handler = wsptr.get();
1046 
1047  if (!handler && !external_thrd)
1048  handler = dynamic_cast<THttpWSHandler *>(fSniffer->FindTObjectInHierarchy(arg->fPathName.Data()));
1049 
1050  if (external_thrd && (!handler || !handler->AllowMTProcess())) {
1051  std::unique_lock<std::mutex> lk(fMutex);
1052  fArgs.push(arg);
1053  // and now wait until request is processed
1054  if (wait_process)
1055  arg->fCond.wait(lk);
1056 
1057  return kTRUE;
1058  }
1059 
1060  if (!handler)
1061  return kFALSE;
1062 
1063  Bool_t process = kFALSE;
1064 
1065  if (arg->fFileName == "root.websocket") {
1066  // handling of web socket
1067  process = handler->HandleWS(arg);
1068  } else if (arg->fFileName == "root.longpoll") {
1069  // ROOT emulation of websocket with polling requests
1070  if (arg->fQuery.BeginsWith("raw_connect") || arg->fQuery.BeginsWith("txt_connect")) {
1071  // try to emulate websocket connect
1072  // if accepted, reply with connection id, which must be used in the following communications
1073  arg->SetMethod("WS_CONNECT");
1074 
1075  bool israw = arg->fQuery.BeginsWith("raw_connect");
1076 
1077  // automatically assign engine to arg
1078  arg->CreateWSEngine<THttpLongPollEngine>(israw);
1079 
1080  if (handler->HandleWS(arg)) {
1081  arg->SetMethod("WS_READY");
1082 
1083  if (handler->HandleWS(arg))
1084  arg->SetTextContent(std::string(israw ? "txt:" : "") + std::to_string(arg->GetWSId()));
1085  } else {
1086  arg->TakeWSEngine(); // delete handle
1087  }
1088 
1089  process = arg->IsText();
1090  } else {
1091  TUrl url;
1092  url.SetOptions(arg->fQuery);
1093  url.ParseOptions();
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");
1099  } else {
1100  arg->SetMethod("WS_DATA");
1101  }
1102 
1103  process = handler->HandleWS(arg);
1104  }
1105  }
1106 
1107  if (!process)
1108  arg->Set404();
1109 
1110  return process;
1111 }
1112 
1113 ////////////////////////////////////////////////////////////////////////////////
1114 /// Restrict access to specified object
1115 ///
1116 /// See TRootSniffer::Restrict() for more details
1117 
1118 void THttpServer::Restrict(const char *path, const char *options)
1119 {
1120  fSniffer->Restrict(path, options);
1121 }
1122 
1123 ////////////////////////////////////////////////////////////////////////////////
1124 /// Register command which can be executed from web interface
1125 ///
1126 /// As method one typically specifies string, which is executed with
1127 /// gROOT->ProcessLine() method. For instance
1128 /// serv->RegisterCommand("Invoke","InvokeFunction()");
1129 ///
1130 /// Or one could specify any method of the object which is already registered
1131 /// to the server. For instance:
1132 /// serv->Register("/", hpx);
1133 /// serv->RegisterCommand("/ResetHPX", "/hpx/->Reset()");
1134 /// Here symbols '/->' separates item name from method to be executed
1135 ///
1136 /// One could specify additional arguments in the command with
1137 /// syntax like %arg1%, %arg2% and so on. For example:
1138 /// serv->RegisterCommand("/ResetHPX", "/hpx/->SetTitle(\"%arg1%\")");
1139 /// serv->RegisterCommand("/RebinHPXPY", "/hpxpy/->Rebin2D(%arg1%,%arg2%)");
1140 /// Such parameter(s) will be requested when command clicked in the browser.
1141 ///
1142 /// Once command is registered, one could specify icon which will appear in the browser:
1143 /// serv->SetIcon("/ResetHPX", "rootsys/icons/ed_execute.png");
1144 ///
1145 /// One also can set extra property '_fastcmd', that command appear as
1146 /// tool button on the top of the browser tree:
1147 /// serv->SetItemField("/ResetHPX", "_fastcmd", "true");
1148 /// Or it is equivalent to specifying extra argument when register command:
1149 /// serv->RegisterCommand("/ResetHPX", "/hpx/->Reset()", "button;rootsys/icons/ed_delete.png");
1150 
1151 Bool_t THttpServer::RegisterCommand(const char *cmdname, const char *method, const char *icon)
1152 {
1153  return fSniffer->RegisterCommand(cmdname, method, icon);
1154 }
1155 
1156 ////////////////////////////////////////////////////////////////////////////////
1157 /// hides folder or element from web gui
1158 
1159 Bool_t THttpServer::Hide(const char *foldername, Bool_t hide)
1160 {
1161  return SetItemField(foldername, "_hidden", hide ? "true" : (const char *)0);
1162 }
1163 
1164 ////////////////////////////////////////////////////////////////////////////////
1165 /// set name of icon, used in browser together with the item
1166 ///
1167 /// One could use images from $ROOTSYS directory like:
1168 /// serv->SetIcon("/ResetHPX","/rootsys/icons/ed_execute.png");
1169 
1170 Bool_t THttpServer::SetIcon(const char *fullname, const char *iconname)
1171 {
1172  return SetItemField(fullname, "_icon", iconname);
1173 }
1174 
1175 ////////////////////////////////////////////////////////////////////////////////
1176 
1177 Bool_t THttpServer::CreateItem(const char *fullname, const char *title)
1178 {
1179  return fSniffer->CreateItem(fullname, title);
1180 }
1181 
1182 ////////////////////////////////////////////////////////////////////////////////
1183 
1184 Bool_t THttpServer::SetItemField(const char *fullname, const char *name, const char *value)
1185 {
1186  return fSniffer->SetItemField(fullname, name, value);
1187 }
1188 
1189 ////////////////////////////////////////////////////////////////////////////////
1190 
1191 const char *THttpServer::GetItemField(const char *fullname, const char *name)
1192 {
1193  return fSniffer->GetItemField(fullname, name);
1194 }
1195 
1196 ////////////////////////////////////////////////////////////////////////////////
1197 /// Returns MIME type base on file extension
1198 
1199 const char *THttpServer::GetMimeType(const char *path)
1200 {
1201  static const struct {
1202  const char *extension;
1203  int ext_len;
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"},
1253  {NULL, 0, NULL}};
1254 
1255  int path_len = strlen(path);
1256 
1257  for (int i = 0; builtin_mime_types[i].extension != NULL; i++) {
1258  if (path_len <= builtin_mime_types[i].ext_len)
1259  continue;
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;
1263  }
1264  }
1265 
1266  return "text/plain";
1267 }
1268 
1269 ////////////////////////////////////////////////////////////////////////////////
1270 /// \deprecated reads file content
1271 
1272 char *THttpServer::ReadFileContent(const char *filename, Int_t &len)
1273 {
1274  len = 0;
1275 
1276  std::ifstream is(filename, std::ios::in | std::ios::binary);
1277  if (!is)
1278  return nullptr;
1279 
1280  is.seekg(0, is.end);
1281  len = is.tellg();
1282  is.seekg(0, is.beg);
1283 
1284  char *buf = (char *)malloc(len);
1285  is.read(buf, len);
1286  if (!is) {
1287  free(buf);
1288  len = 0;
1289  return nullptr;
1290  }
1291 
1292  return buf;
1293 }
1294 
1295 ////////////////////////////////////////////////////////////////////////////////
1296 /// reads file content, using std::string as container
1297 
1298 std::string THttpServer::ReadFileContent(const std::string &filename)
1299 {
1300  std::ifstream is(filename, std::ios::in | std::ios::binary);
1301  std::string res;
1302  if (is) {
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());
1307  if (!is)
1308  res.clear();
1309  }
1310  return res;
1311 }