Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
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#include "TObjArray.h"
25
26#include "THttpEngine.h"
27#include "THttpLongPollEngine.h"
28#include "THttpWSHandler.h"
29#include "TRootSniffer.h"
30#include "TRootSnifferStore.h"
31#include "TCivetweb.h"
32#include "TFastCgi.h"
33
34#include <chrono>
35#include <cstdlib>
36#include <cstring>
37#include <fstream>
38#include <memory>
39#include <string>
40#include <thread>
41
42class THttpTimer : public TTimer {
46
47public:
48 THttpServer &fServer; ///!< server processing requests
49
50 /// constructor
52
54 {
55 fSlow = flag;
56 fSlowCnt = 0;
58 if (fSlow) {
59 if (ms < 100)
60 ms = 500;
61 else if (ms < 500)
62 ms = 3000;
63 else
64 ms = 10000;
65 }
66
67 SetTime(ms);
68 }
69 Bool_t IsSlow() const { return fSlow; }
70
71 /// timeout handler
72 /// used to process http requests in main ROOT thread
73 void Timeout() override
74 {
76
77 if (nprocess > 0) {
78 fSlowCnt = 0;
79 if (IsSlow())
81 } else if (!IsSlow() && (fSlowCnt++ > 10)) {
83 }
84 }
85};
86
87
88/** \class THttpServer
89\ingroup http
90
91Online http server for arbitrary ROOT application
92
93Idea of THttpServer - provide remote http access to running
94ROOT application and enable HTML/JavaScript user interface.
95Any registered object can be requested and displayed in the browser.
96There are many benefits of such approach:
97
981. standard http interface to ROOT application
992. no any temporary ROOT files when access data
1003. user interface running in all browsers
101
102To start http server simply create instance
103of the THttpServer class like:
104
105 serv = new THttpServer("http:8080");
106
107This will starts civetweb-based http server with http port 8080.
108Than one should be able to open address "http://localhost:8080"
109in any modern web browser (Firefox, Chrome, Opera, ...) and browse objects,
110created in ROOT application. By default, server can access files,
111canvases and histograms via `gROOT` pointer. All such objects
112can be displayed with JSROOT graphics.
113
114At any time one could register other objects with the command:
115
116 TGraph* gr = new TGraph(10);
117 gr->SetName("gr1");
118 serv->Register("graphs/subfolder", gr);
119
120If objects content is changing in the application, one could
121enable monitoring flag in the browser - than objects view
122will be regularly updated.
123
124More information: https://root.cern/root/htmldoc/guides/HttpServer/HttpServer.html
125*/
126
127
128////////////////////////////////////////////////////////////////////////////////
129/// constructor
130///
131/// As argument, one specifies engine kind which should be
132/// created like "http:8080". One could specify several engines
133/// at once, separating them with semicolon (";"). Following engines are supported:
134///
135/// http - TCivetweb, civetweb-based implementation of http protocol
136/// fastcgi - TFastCgi, special protocol for communicating with web servers
137///
138/// For each created engine one should provide socket port number like "http:8080" or "fastcgi:9000".
139/// Additional engine-specific options can be supplied with URL syntax like "http:8080?thrds=10".
140/// Full list of supported options should be checked in engines docu.
141///
142/// One also can configure following options, separated by semicolon:
143///
144/// readonly, ro - set read-only mode (default)
145/// readwrite, rw - allows methods execution of registered objects
146/// global - scans global ROOT lists for existing objects (default)
147/// noglobal - disable scan of global lists
148/// cors - enable CORS header with origin="*"
149/// cors=domain - enable CORS header with origin="domain"
150/// basic_sniffer - use basic sniffer without support of hist, gpad, graph classes
151///
152/// For example, create http server, which allows cors headers and disable scan of global lists,
153/// one should provide "http:8080;cors;noglobal" as parameter
154///
155/// THttpServer uses JavaScript ROOT (https://root.cern/js) to implement web clients UI.
156/// Normally JSROOT sources are used from $ROOTSYS/js directory,
157/// but one could set JSROOTSYS shell variable to specify alternative location
158
159THttpServer::THttpServer(const char *engine) : TNamed("http", "ROOT http server")
160{
161 const char *jsrootsys = gSystem->Getenv("JSROOTSYS");
162 if (!jsrootsys)
163 jsrootsys = gEnv->GetValue("HttpServ.JSRootPath", jsrootsys);
164
165 if (jsrootsys && *jsrootsys) {
166 if ((strncmp(jsrootsys, "http://", 7)==0) || (strncmp(jsrootsys, "https://", 8)==0))
168 else
170 }
171
172 if (fJSROOTSYS.Length() == 0) {
173 TString jsdir = TString::Format("%s/js", TROOT::GetDataDir().Data());
175 ::Warning("THttpServer::THttpServer", "problems resolving '%s', set JSROOTSYS to proper JavaScript ROOT location",
176 jsdir.Data());
177 fJSROOTSYS = ".";
178 } else {
180 }
181 }
182
183 Bool_t basic_sniffer = strstr(engine, "basic_sniffer") != nullptr;
184
185 AddLocation("jsrootsys/", fJSROOTSYS.Data());
186
187 if (basic_sniffer) {
188 AddLocation("rootsys_fonts/", TString::Format("%s/fonts", TROOT::GetDataDir().Data()));
189 } else {
190 AddLocation("currentdir/", ".");
191 AddLocation("rootsys/", TROOT::GetRootSys());
192 }
193
194 fDefaultPage = fJSROOTSYS + "/files/online.htm";
195 fDrawPage = fJSROOTSYS + "/files/draw.htm";
196
197 TRootSniffer *sniff = nullptr;
198 if (!basic_sniffer) {
199 static const TClass *snifferClass = TClass::GetClass("TRootSnifferFull");
200 if (snifferClass && snifferClass->IsLoaded())
201 sniff = (TRootSniffer *)snifferClass->New();
202 else
203 ::Warning("THttpServer::THttpServer", "Fail to load TRootSnifferFull class, use basic functionality");
204 }
205
206 if (!sniff) {
207 sniff = new TRootSniffer();
208 sniff->SetScanGlobalDir(kFALSE);
209 sniff->CreateOwnTopFolder(); // use dedicated folder
210 }
211
213
214 // start timer
215 SetTimer(20, kTRUE);
216
217 if (strchr(engine, ';') == 0) {
218 CreateEngine(engine);
219 } else {
220 TObjArray *lst = TString(engine).Tokenize(";");
221
222 for (Int_t n = 0; n <= lst->GetLast(); n++) {
223 const char *opt = lst->At(n)->GetName();
224 if ((strcmp(opt, "readonly") == 0) || (strcmp(opt, "ro") == 0)) {
226 } else if ((strcmp(opt, "readwrite") == 0) || (strcmp(opt, "rw") == 0)) {
228 } else if (strcmp(opt, "global") == 0) {
230 } else if (strcmp(opt, "noglobal") == 0) {
232 } else if (strncmp(opt, "cors=", 5) == 0) {
233 SetCors(opt + 5);
234 } else if (strcmp(opt, "cors") == 0) {
235 SetCors("*");
236 } else
237 CreateEngine(opt);
238 }
239
240 delete lst;
241 }
242}
243
244////////////////////////////////////////////////////////////////////////////////
245/// destructor
246///
247/// delete all http engines and sniffer
248
250{
252
253 if (fTerminated) {
254 TIter iter(&fEngines);
255 while (auto engine = dynamic_cast<THttpEngine *>(iter()))
256 engine->Terminate();
257 }
258
260
261 SetSniffer(nullptr);
262
263 SetTimer(0);
264}
265
266////////////////////////////////////////////////////////////////////////////////
267/// Set TRootSniffer to the server
268///
269/// Server takes ownership over sniffer
270
275
276////////////////////////////////////////////////////////////////////////////////
277/// Set termination flag,
278///
279/// No any further requests will be processed, server only can be destroyed afterwards
280
285
286////////////////////////////////////////////////////////////////////////////////
287/// returns read-only mode
288
290{
291 return fSniffer ? fSniffer->IsReadOnly() : kTRUE;
292}
293
294////////////////////////////////////////////////////////////////////////////////
295/// Set read-only mode for the server (default on)
296///
297/// In read-only server is not allowed to change any ROOT object, registered to the server
298/// Server also cannot execute objects method via exe.json request
299
301{
302 if (fSniffer)
303 fSniffer->SetReadOnly(readonly);
304}
305
306////////////////////////////////////////////////////////////////////////////////
307/// returns true if only websockets are handled by the server
308///
309/// Typically used by WebGui
310
312{
313 return fWSOnly;
314}
315
316////////////////////////////////////////////////////////////////////////////////
317/// Set websocket-only mode.
318///
319/// If true, server will only handle websockets connection
320/// plus serving file requests to access jsroot/ui5 scripts
321
326
327////////////////////////////////////////////////////////////////////////////////
328/// Add files location, which could be used in the server
329///
330/// One could map some system folder to the server like
331///
332/// serv->AddLocation("mydir/", "/home/user/specials");
333///
334/// Than files from this directory could be addressed via server like `http://localhost:8080/mydir/myfile.root`
335
336void THttpServer::AddLocation(const char *prefix, const char *path)
337{
338 if (!prefix || (*prefix == 0))
339 return;
340
341 if (!path)
342 fLocations.erase(fLocations.find(prefix));
343 else
344 fLocations[prefix] = path;
345}
346
347////////////////////////////////////////////////////////////////////////////////
348/// Set location of JSROOT to use with the server
349///
350/// One could specify address like:
351///
352/// * https://root.cern/js/7.6.0/
353/// * https://jsroot.gsi.de/7.6.0/
354///
355/// This allows to get new JSROOT features with old server,
356/// reduce load on THttpServer instance, also startup time can be improved
357/// When empty string specified (default), local copy of JSROOT is used (distributed with ROOT)
358
359void THttpServer::SetJSROOT(const char *location)
360{
361 fJSROOT = location ? location : "";
362}
363
364////////////////////////////////////////////////////////////////////////////////
365/// Set default HTML page
366///
367/// Sets file name, delivered by the server when http address is opened in the browser.
368///
369/// By default, $ROOTSYS/js/files/online.htm page is used
370/// When empty filename is specified, default page will be used
371
372void THttpServer::SetDefaultPage(const std::string &filename)
373{
374 if (!filename.empty())
376 else
377 fDefaultPage = fJSROOTSYS + "/files/online.htm";
378
379 // force to read page content next time again
380 fDefaultPageCont.clear();
381}
382
383////////////////////////////////////////////////////////////////////////////////
384/// Set drawing HTML page
385///
386/// Set file name of HTML page, delivered by the server when
387/// objects drawing page is requested from the browser
388/// By default, $ROOTSYS/js/files/draw.htm page is used
389/// When empty filename is specified, default page will be used
390
391void THttpServer::SetDrawPage(const std::string &filename)
392{
393 if (!filename.empty())
395 else
396 fDrawPage = fJSROOTSYS + "/files/draw.htm";
397
398 // force to read page content next time again
399 fDrawPageCont.clear();
400}
401
402////////////////////////////////////////////////////////////////////////////////
403/// Factory method to create different http engines
404///
405/// At the moment two engine kinds are supported:
406///
407/// * civetweb or http (default)
408/// * fastcgi
409///
410/// Examples:
411///
412/// // creates civetweb web server with http port 8080
413/// serv->CreateEngine("http:8080");
414/// serv->CreateEngine("civetweb:8080");
415/// serv->CreateEngine(":8080");
416/// // creates fastcgi server with port 9000
417/// serv->CreateEngine("fastcgi:9000");
418///
419/// One could apply additional parameters, using URL syntax:
420///
421/// serv->CreateEngine("http:8080?thrds=10");
422
424{
425 if (!engine)
426 return kFALSE;
427
428 const char *arg = strchr(engine, ':');
429 if (!arg)
430 return kFALSE;
431
433 if (arg != engine)
434 clname.Append(engine, arg - engine);
435 arg++; // skip first :
436
437 THttpEngine *eng = nullptr;
438
439 if ((clname.Length() == 0) || (clname == "http") || (clname == "civetweb")) {
440 eng = new TCivetweb(kFALSE);
441#ifndef R__WIN32
442 } else if (clname == "socket") {
443 eng = new TCivetweb(kFALSE);
444 sarg = "x"; // civetweb require x before socket name
445 sarg.Append(arg);
446 arg = sarg.Data();
447#endif
448 } else if (clname == "https") {
449 eng = new TCivetweb(kTRUE);
450 } else if (clname == "fastcgi") {
451 eng = new TFastCgi();
452 }
453
454 if (!eng) {
455 // ensure that required engine class exists before we try to create it
456 TClass *engine_class = gROOT->LoadClass(clname.Data());
457 if (!engine_class)
458 return kFALSE;
459
460 eng = (THttpEngine *)engine_class->New();
461 if (!eng)
462 return kFALSE;
463 }
464
465 eng->SetServer(this);
466
467 if (!eng->Create(arg)) {
468 delete eng;
469 return kFALSE;
470 }
471
473
474 return kTRUE;
475}
476
477////////////////////////////////////////////////////////////////////////////////
478/// Create timer which will invoke ProcessRequests() function periodically
479///
480/// Timer is required to perform all actions in main ROOT thread
481/// Method arguments are the same as for TTimer constructor
482/// By default, sync timer with 100 ms period is created
483///
484/// It is recommended to always use sync timer mode and only change period to
485/// adjust server reaction time. Use of async timer requires, that application regularly
486/// calls gSystem->ProcessEvents(). It happens automatically in ROOT interactive shell.
487/// If milliSec == 0, no timer will be created.
488/// In this case application should regularly call ProcessRequests() method.
489///
490/// Async timer allows to use THttpServer in applications, which does not have explicit
491/// gSystem->ProcessEvents() calls. But be aware, that such timer can interrupt any system call
492/// (like malloc) and can lead to dead locks, especially in multi-threaded applications.
493
495{
496 if (fTimer) {
497 fTimer->Stop();
498 fTimer.reset();
499 }
500 if (milliSec > 0) {
501 if (fOwnThread) {
502 Error("SetTimer", "Server runs already in special thread, therefore no any timer can be created");
503 } else {
504 fTimer = std::make_unique<THttpTimer>(milliSec, mode, *this);
505 fTimer->TurnOn();
506 }
507 }
508}
509
510////////////////////////////////////////////////////////////////////////////////
511/// Creates special thread to process all requests, directed to http server
512///
513/// Should be used with care - only dedicated instance of TRootSniffer is allowed
514/// By default THttpServer allows to access global lists pointers gROOT or gFile.
515/// To be on the safe side, all kind of such access performed from the main thread.
516/// Therefore usage of specialized thread means that no any global pointers will
517/// be accessible by THttpServer
518
520{
521 if (fOwnThread)
522 return;
523
524 SetTimer(0);
525 fMainThrdId = 0;
526 fOwnThread = true;
527
528 std::thread thrd([this] {
529 int nempty = 0;
530 while (fOwnThread && !fTerminated) {
532 if (nprocess > 0)
533 nempty = 0;
534 else
535 nempty++;
536 if (nempty > 1000) {
537 nempty = 0;
538 std::this_thread::sleep_for(std::chrono::milliseconds(1));
539 }
540 }
541 });
542
543 fThrd = std::move(thrd);
544}
545
546////////////////////////////////////////////////////////////////////////////////
547/// Stop server thread
548///
549/// Normally called shortly before http server destructor
550
552{
553 if (!fOwnThread)
554 return;
555
556 fOwnThread = false;
557 fThrd.join();
558 fMainThrdId = 0;
559}
560
561////////////////////////////////////////////////////////////////////////////////
562/// Checked that filename does not contains relative path below current directory
563///
564/// Used to prevent access to files below current directory
565
567{
568 if (!fname || (*fname == 0))
569 return kFALSE;
570
571 Int_t level = 0;
572
573 while (*fname) {
574
575 // find next slash or backslash
576 const char *next = strpbrk(fname, "/\\");
577 if (next == 0)
578 return kTRUE;
579
580 // most important - change to parent dir
581 if ((next == fname + 2) && (*fname == '.') && (*(fname + 1) == '.')) {
582 fname += 3;
583 level--;
584 if (level < 0)
585 return kFALSE;
586 continue;
587 }
588
589 // ignore current directory
590 if ((next == fname + 1) && (*fname == '.')) {
591 fname += 2;
592 continue;
593 }
594
595 // ignore slash at the front
596 if (next == fname) {
597 fname++;
598 continue;
599 }
600
601 fname = next + 1;
602 level++;
603 }
604
605 return kTRUE;
606}
607
608////////////////////////////////////////////////////////////////////////////////
609/// Verifies that request is just file name
610///
611/// File names typically contains prefix like "jsrootsys/"
612/// If true, method returns real name of the file,
613/// which should be delivered to the client
614/// Method is thread safe and can be called from any thread
615
616Bool_t THttpServer::IsFileRequested(const char *uri, TString &res) const
617{
618 if (!uri || (*uri == 0))
619 return kFALSE;
620
621 TString fname(uri);
622
623 for (auto &entry : fLocations) {
624 Ssiz_t pos = fname.Index(entry.first.c_str());
625 if (pos == kNPOS)
626 continue;
627 fname.Remove(0, pos + (entry.first.length() - 1));
628 if (!VerifyFilePath(fname.Data()))
629 return kFALSE;
630 res = entry.second.c_str();
631 if ((fname[0] == '/') && (res[res.Length() - 1] == '/'))
632 res.Resize(res.Length() - 1);
633 res.Append(fname);
634 return kTRUE;
635 }
636
637 return kFALSE;
638}
639
640////////////////////////////////////////////////////////////////////////////////
641/// Executes http request, specified in THttpCallArg structure
642///
643/// Method can be called from any thread
644/// Actual execution will be done in main ROOT thread, where analysis code is running.
645
646Bool_t THttpServer::ExecuteHttp(std::shared_ptr<THttpCallArg> arg)
647{
648 if (fTerminated)
649 return kFALSE;
650
651 if ((fMainThrdId != 0) && (fMainThrdId == TThread::SelfId())) {
652 // should not happen, but one could process requests directly without any signaling
653
654 ProcessRequest(arg);
655
656 return kTRUE;
657 }
658
659 if (fTimer && fTimer->IsSlow())
660 fTimer->SetSlow(kFALSE);
661
662 // add call arg to the list
663 std::unique_lock<std::mutex> lk(fMutex);
664 arg->fNotifyFlag = kFALSE;
665 fArgs.push(arg);
666 // and now wait until request is processed
667 arg->fCond.wait(lk);
668
669 return kTRUE;
670}
671
672////////////////////////////////////////////////////////////////////////////////
673/// Submit http request, specified in THttpCallArg structure
674///
675/// Contrary to ExecuteHttp, it will not block calling thread.
676/// User should implement THttpCallArg::HttpReplied() method
677/// to react when HTTP request is executed.
678
679/// Method can be called from any thread
680/// Actual execution will be done in main ROOT thread, where analysis code is running.
681/// When called from main thread and can_run_immediately==kTRUE, will be
682/// executed immediately.
683///
684/// Returns kTRUE when was executed.
685
686Bool_t THttpServer::SubmitHttp(std::shared_ptr<THttpCallArg> arg, Bool_t can_run_immediately)
687{
688 if (fTerminated)
689 return kFALSE;
690
692 ProcessRequest(arg);
693 arg->NotifyCondition();
694 return kTRUE;
695 }
696
697 // add call arg to the list
698 std::unique_lock<std::mutex> lk(fMutex);
699 fArgs.push(arg);
700 return kFALSE;
701}
702
703////////////////////////////////////////////////////////////////////////////////
704/// Process requests, submitted for execution
705///
706/// Returns number of processed requests
707///
708/// Normally invoked by THttpTimer, when somewhere in the code
709/// gSystem->ProcessEvents() is called.
710/// User can call serv->ProcessRequests() directly, but only from main thread.
711/// If special server thread is created, called from that thread
712
714{
715 auto id = TThread::SelfId();
716
717 if (fMainThrdId != id) {
718 if (gDebug > 0 && fMainThrdId)
719 Warning("ProcessRequests", "Changing main thread to %ld", (long)id);
720 fMainThrdId = id;
721 }
722
724
725 if (fProcessingThrdId) {
726 if (fProcessingThrdId == id) {
728 } else {
729 Error("ProcessRequests", "Processing already running from %ld thread", (long) fProcessingThrdId);
730 return 0;
731 }
732 }
733
734 if (!recursion)
736
737 Int_t cnt = 0;
738
739 std::unique_lock<std::mutex> lk(fMutex, std::defer_lock);
740
741 // first process requests in the queue
742 while (true) {
743 std::shared_ptr<THttpCallArg> arg;
744
745 lk.lock();
746 if (!fArgs.empty()) {
747 arg = fArgs.front();
748 fArgs.pop();
749 }
750 lk.unlock();
751
752 if (!arg)
753 break;
754
755 if (arg->fFileName == "root_batch_holder.js") {
757 continue;
758 }
759
760 auto prev = fSniffer->SetCurrentCallArg(arg.get());
761
762 try {
763 cnt++;
764 ProcessRequest(arg);
765 fSniffer->SetCurrentCallArg(prev);
766 } catch (...) {
767 fSniffer->SetCurrentCallArg(prev);
768 }
769
770 arg->NotifyCondition();
771 }
772
773 // regularly call Process() method of engine to let perform actions in ROOT context
774 TIter iter(&fEngines);
775 while (auto engine = static_cast<THttpEngine *>(iter())) {
776 if (fTerminated)
777 engine->Terminate();
778 engine->Process();
779 }
780
781 if (!recursion)
783
784 return cnt;
785}
786
787////////////////////////////////////////////////////////////////////////////////
788/// Method called when THttpServer cannot process request
789///
790/// By default such requests replied with 404 code
791/// One could overwrite with method in derived class to process all kinds of such non-standard requests
792
794{
795 arg->Set404();
796}
797
798////////////////////////////////////////////////////////////////////////////////
799/// Process special http request for root_batch_holder.js script
800///
801/// This kind of requests used to hold web browser running in headless mode
802/// Intentionally requests does not replied immediately
803
804void THttpServer::ProcessBatchHolder(std::shared_ptr<THttpCallArg> &arg)
805{
806 auto wsptr = FindWS(arg->GetPathName());
807
808 if (!wsptr || !wsptr->ProcessBatchHolder(arg)) {
809 arg->Set404();
810 arg->NotifyCondition();
811 }
812}
813
814////////////////////////////////////////////////////////////////////////////////
815/// Create summary page with active WS handlers
816
818{
819
820 std::string arr = "[";
821
822 {
823 std::lock_guard<std::mutex> grd(fWSMutex);
824 for (auto &ws : fWSHandlers) {
825 if (arr.length() > 1)
826 arr.append(", ");
827
828 arr.append(TString::Format("{ name: \"%s\", title: \"%s\" }", ws->GetName(), ws->GetTitle()).Data());
829 }
830 }
831
832 arr.append("]");
833
834 std::string res = ReadFileContent((TROOT::GetDataDir() + "/js/files/wslist.htm").Data());
835
836 std::string arg = "\"$$$wslist$$$\"";
837
838 auto pos = res.find(arg);
839 if (pos != std::string::npos)
840 res.replace(pos, arg.length(), arr);
841
842 return res;
843}
844
845////////////////////////////////////////////////////////////////////////////////
846/// Replaces all references like "jsrootsys/..." or other pre-configured pathes
847///
848/// Either using pre-configured JSROOT installation from web or
849/// redirect to jsrootsys from the main server path to benefit from browser caching
850/// Creates appropriate importmap instead of <!--jsroot_importmap--> placeholder
851
852void THttpServer::ReplaceJSROOTLinks(std::shared_ptr<THttpCallArg> &arg, const std::string &version)
853{
854 const std::string place_holder = "<!--jsroot_importmap-->";
855
856 auto p = arg->fContent.find(place_holder);
857
858 bool old_format = (p == std::string::npos);
859
860 // count slashes to handler relative paths to jsroot
861 Int_t slash_cnt = 0;
862 if (arg->fPathName.Length() > 0)
863 slash_cnt++;
864 for (Int_t n = 1; n < arg->fPathName.Length()-1; ++n)
865 if (arg->fPathName[n] == '/') {
866 if (arg->fPathName[n-1] != '/') {
867 slash_cnt++; // normal slash in the middle, count it
868 } else {
869 slash_cnt = 0; // double slash, do not touch such path
870 break;
871 }
872 }
873
874
875 if (old_format) {
876 // old functionality
877
878 if (!version.empty()) {
879 // replace link to JSROOT modules in import statements emulating new version for browser
880 std::string search = "from './jsrootsys/";
881 std::string replace = "from './" + version + "/jsrootsys/";
882 arg->ReplaceAllinContent(search, replace);
883 // replace link to ROOT ui5 modules in import statements emulating new version for browser
884 search = "from './rootui5sys/";
885 replace = "from './" + version + "/rootui5sys/";
886 arg->ReplaceAllinContent(search, replace);
887 // replace link on old JSRoot.core.js script - if still appears
888 search = "jsrootsys/scripts/JSRoot.core.";
889 replace = version + "/jsrootsys/scripts/JSRoot.core.";
890 arg->ReplaceAllinContent(search, replace, true);
891 arg->AddNoCacheHeader();
892 }
893
894 std::string repl;
895
896 if (fJSROOT.Length() > 0) {
897 repl = "=\"";
898 repl.append(fJSROOT.Data());
899 if (repl.back() != '/')
900 repl.append("/");
901 } else {
902 if (slash_cnt > 0) {
903 repl = "=\"";
904 while (slash_cnt-- > 0) repl.append("../");
905 repl.append("jsrootsys/");
906 }
907 }
908
909 if (!repl.empty()) {
910 arg->ReplaceAllinContent("=\"jsrootsys/", repl);
911 arg->ReplaceAllinContent("from './jsrootsys/", TString::Format("from '%s", repl.substr(2).c_str()).Data());
912 }
913 } else {
914 // new functionality creating importmap
915
916 std::string path_prefix, jsroot_prefix;
917 if (slash_cnt > 0) {
918 path_prefix = "";
919 while (slash_cnt-- > 0)
920 path_prefix.append("../");
921 } else {
922 path_prefix = "./";
923 }
924
925 if (!version.empty())
926 path_prefix.append(version + "/");
927
928 if (fJSROOT.Length() > 0) {
930 if (jsroot_prefix.back() != '/')
931 jsroot_prefix.append("/");
932 } else {
933 jsroot_prefix = path_prefix + "jsrootsys/";
934 }
935
936 static std::map<std::string, std::string> modules = {
937 {"jsroot", "main.mjs"}, {"jsroot/core", "core.mjs"},
938 {"jsroot/io", "io.mjs"}, {"jsroot/tree", "tree.mjs"},
939 {"jsroot/draw", "draw.mjs"}, {"jsroot/gui", "gui.mjs"},
940 {"jsroot/d3", "d3.mjs"}, {"jsroot/three", "three.mjs"}, {"jsroot/three_addons", "three_addons.mjs"},
941 {"jsroot/geom", "geom/TGeoPainter.mjs"}, {"jsroot/hpainter", "gui/HierarchyPainter.mjs"},
942 {"jsroot/webwindow", "webwindow.mjs"}
943 };
944
946 auto pspace = p;
947 while ((--pspace > 0) && (arg->fContent[pspace] == ' ') && (space_prefix.Length() < 20))
948 space_prefix.Append(" ");
949
950 bool first = true;
951 TString importmap = "<script type=\"importmap\">\n";
952 importmap += space_prefix + "{\n";
953 importmap += space_prefix + " \"imports\": ";
954 for (auto &entry : modules) {
955 importmap.Append(TString::Format("%s\n%s \"%s\": \"%smodules/%s\"", first ? "{" : ",", space_prefix.Data(), entry.first.c_str(), jsroot_prefix.c_str(), entry.second.c_str()));
956 first = false;
957 }
958 importmap.Append(TString::Format(",\n%s \"jsrootsys/\": \"%s\"", space_prefix.Data(), jsroot_prefix.c_str()));
959
960 for (auto &entry : fLocations)
961 if (entry.first != "jsrootsys/")
962 importmap.Append(TString::Format(",\n%s \"%s\": \"%s%s\"", space_prefix.Data(), entry.first.c_str(), path_prefix.c_str(), entry.first.c_str()));
963 importmap.Append("\n");
964 importmap.Append(space_prefix + " }\n");
965 importmap.Append(space_prefix + "}\n");
966 importmap.Append(space_prefix + "</script>\n");
967
968 arg->fContent.erase(p, place_holder.length());
969
970 arg->fContent.insert(p, importmap.Data());
971 }
972}
973
974////////////////////////////////////////////////////////////////////////////////
975/// Process single http request
976///
977/// Depending from requested path and filename different actions will be performed.
978/// In most cases information is provided by TRootSniffer class
979
980void THttpServer::ProcessRequest(std::shared_ptr<THttpCallArg> arg)
981{
982 if (fTerminated) {
983 arg->Set404();
984 return;
985 }
986
987 if ((arg->fFileName == "root.websocket") || (arg->fFileName == "root.longpoll")) {
988 ExecuteWS(arg);
989 return;
990 }
991
992 if (arg->fFileName.IsNull() || (arg->fFileName == "index.htm") || (arg->fFileName == "default.htm")) {
993
994 std::string version;
995
996 if (arg->fFileName == "default.htm") {
997
998 if (!IsWSOnly())
999 arg->fContent = ReadFileContent((fJSROOTSYS + "/files/online.htm").Data());
1000
1001 } else {
1002 auto wsptr = FindWS(arg->GetPathName());
1003
1004 auto handler = wsptr.get();
1005
1006 if (!handler)
1007 handler = dynamic_cast<THttpWSHandler *>(fSniffer->FindTObjectInHierarchy(arg->fPathName.Data()));
1008
1009 if (handler) {
1010
1011 arg->fContent = handler->GetDefaultPageContent().Data();
1012
1013 if (arg->fContent.find("file:") == 0) {
1014 const char *fname = arg->fContent.c_str() + 5;
1017 arg->fContent = ReadFileContent(resolve.Data());
1018 }
1019
1020 version = handler->GetCodeVersion();
1021
1022 handler->VerifyDefaultPageContent(arg);
1023 }
1024 }
1025
1026 if (arg->fContent.empty() && arg->fFileName.IsNull() && arg->fPathName.IsNull() && IsWSOnly()) {
1027 // Creating page with list of available widgets is disabled now for security reasons
1028 // Later one can provide functionality back only if explicitly desired by the user
1029 // BuildWSEntryPage();
1030
1031 arg->SetContent("refused");
1032 arg->Set404();
1033 }
1034
1035 if (arg->fContent.empty() && !IsWSOnly()) {
1036
1037 if (fDefaultPageCont.empty())
1039
1040 arg->fContent = fDefaultPageCont;
1041 }
1042
1043 if (arg->fContent.empty()) {
1044
1045 arg->Set404();
1046 } else if (!arg->Is404()) {
1047
1049
1050 const char *hjsontag = "\"$$$h.json$$$\"";
1051
1052 // add h.json caching
1053 if (arg->fContent.find(hjsontag) != std::string::npos) {
1056 const char *topname = fTopName.Data();
1057 if (arg->fTopName.Length() > 0)
1058 topname = arg->fTopName.Data();
1059 fSniffer->ScanHierarchy(topname, arg->fPathName.Data(), &store);
1060
1061 arg->ReplaceAllinContent(hjsontag, h_json.Data());
1062
1063 arg->AddNoCacheHeader();
1064
1065 if (arg->fQuery.Index("nozip") == kNPOS)
1066 arg->SetZipping();
1067 }
1068 arg->SetContentType("text/html");
1069 }
1070 return;
1071 }
1072
1073 if ((arg->fFileName == "draw.htm") && !IsWSOnly()) {
1074 if (fDrawPageCont.empty())
1076
1077 if (fDrawPageCont.empty()) {
1078 arg->Set404();
1079 } else {
1080 const char *rootjsontag = "\"$$$root.json$$$\"";
1081 const char *hjsontag = "\"$$$h.json$$$\"";
1082
1083 arg->fContent = fDrawPageCont;
1084
1085 ReplaceJSROOTLinks(arg);
1086
1087 if ((arg->fQuery.Index("no_h_json") == kNPOS) && (arg->fQuery.Index("webcanvas") == kNPOS) &&
1088 (arg->fContent.find(hjsontag) != std::string::npos)) {
1091 const char *topname = fTopName.Data();
1092 if (arg->fTopName.Length() > 0)
1093 topname = arg->fTopName.Data();
1094 fSniffer->ScanHierarchy(topname, arg->fPathName.Data(), &store, kTRUE);
1095
1096 arg->ReplaceAllinContent(hjsontag, h_json.Data());
1097 }
1098
1099 if ((arg->fQuery.Index("no_root_json") == kNPOS) && (arg->fQuery.Index("webcanvas") == kNPOS) &&
1100 (arg->fContent.find(rootjsontag) != std::string::npos)) {
1101 std::string str;
1102 if (fSniffer->Produce(arg->fPathName.Data(), "root.json", "compact=23", str))
1103 arg->ReplaceAllinContent(rootjsontag, str);
1104 }
1105 arg->AddNoCacheHeader();
1106 if (arg->fQuery.Index("nozip") == kNPOS)
1107 arg->SetZipping();
1108 arg->SetContentType("text/html");
1109 }
1110 return;
1111 }
1112
1113 if ((arg->fFileName == "favicon.ico") && arg->fPathName.IsNull()) {
1114 arg->SetFile(fJSROOTSYS + "/img/RootIcon.ico");
1115 return;
1116 }
1117
1119 if (IsFileRequested(arg->fFileName.Data(), filename)) {
1120 arg->SetFile(filename);
1121 return;
1122 }
1123
1124 // check if websocket handler may serve file request
1125 if (!arg->fPathName.IsNull() && !arg->fFileName.IsNull()) {
1126 TString wsname = arg->fPathName, fname;
1127 auto pos = wsname.First('/');
1128 if (pos == kNPOS) {
1129 wsname = arg->fPathName;
1130 } else {
1131 wsname = arg->fPathName(0, pos);
1132 fname = arg->fPathName(pos + 1, arg->fPathName.Length() - pos);
1133 fname.Append("/");
1134 }
1135
1136 fname.Append(arg->fFileName);
1137
1138 if (VerifyFilePath(fname.Data())) {
1139
1140 auto ws = FindWS(wsname.Data());
1141
1142 if (ws && ws->CanServeFiles()) {
1143 TString fdir = ws->GetDefaultPageContent();
1144 // only when file is specified, can take directory, append prefix and file name
1145 if (fdir.Index("file:") == 0) {
1146 fdir.Remove(0, 5);
1147 auto separ = fdir.Last('/');
1148 if (separ != kNPOS)
1149 fdir.Resize(separ + 1);
1150 else
1151 fdir = "./";
1152
1153 fdir.Append(fname);
1154 arg->SetFile(fdir);
1155 return;
1156 }
1157 }
1158 }
1159 }
1160
1161 filename = arg->fFileName;
1162
1164 if (filename.EndsWith(".gz")) {
1165 filename.Resize(filename.Length() - 3);
1166 iszip = kTRUE;
1167 }
1168
1169 if (IsWSOnly()) {
1170 if (arg->fContent.empty())
1171 arg->Set404();
1172 } else if ((filename == "h.xml") || (filename == "get.xml")) {
1173
1174 Bool_t compact = arg->fQuery.Index("compact") != kNPOS;
1175
1176 TString res;
1177
1178 res.Form("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
1179 if (!compact)
1180 res.Append("\n");
1181 res.Append("<root>");
1182 if (!compact)
1183 res.Append("\n");
1184 {
1185 TRootSnifferStoreXml store(res, compact);
1186
1187 const char *topname = fTopName.Data();
1188 if (arg->fTopName.Length() > 0)
1189 topname = arg->fTopName.Data();
1190 fSniffer->ScanHierarchy(topname, arg->fPathName.Data(), &store, filename == "get.xml");
1191 }
1192
1193 res.Append("</root>");
1194 if (!compact)
1195 res.Append("\n");
1196
1197 arg->SetContent(std::string(res.Data()));
1198
1199 arg->SetXml();
1200 } else if (filename == "h.json") {
1201 TString res;
1202 TRootSnifferStoreJson store(res, arg->fQuery.Index("compact") != kNPOS);
1203 const char *topname = fTopName.Data();
1204 if (arg->fTopName.Length() > 0)
1205 topname = arg->fTopName.Data();
1206 fSniffer->ScanHierarchy(topname, arg->fPathName.Data(), &store);
1207 arg->SetContent(std::string(res.Data()));
1208 arg->SetJson();
1209 } else if (fSniffer->Produce(arg->fPathName.Data(), filename.Data(), arg->fQuery.Data(), arg->fContent)) {
1210 // define content type base on extension
1211 arg->SetContentType(GetMimeType(filename.Data()));
1212 } else {
1213 // miss request, user may process
1214 MissedRequest(arg.get());
1215 }
1216
1217 if (arg->Is404())
1218 return;
1219
1220 if (iszip)
1221 arg->SetZipping(THttpCallArg::kZipAlways);
1222
1223 if (filename == "root.bin") {
1224 // only for binary data master version is important
1225 // it allows to detect if streamer info was modified
1226 const char *parname = fSniffer->IsStreamerInfoItem(arg->fPathName.Data()) ? "BVersion" : "MVersion";
1227 arg->AddHeader(parname, TString::Format("%u", (unsigned)fSniffer->GetStreamerInfoHash()).Data());
1228 }
1229
1230 // try to avoid caching on the browser
1231 arg->AddNoCacheHeader();
1232
1233 // potentially add cors headers
1234 if (IsCors())
1235 arg->AddHeader("Access-Control-Allow-Origin", GetCors());
1236 if (IsCorsCredentials()) {
1237 arg->AddHeader("Access-Control-Allow-Credentials", GetCorsCredentials());
1238 arg->AddHeader("Access-Control-Expose-Headers", "Content-Range, Content-Length, Date");
1239 arg->AddHeader("Access-Control-Allow-Methods", "GET, HEAD, OPTIONS");
1240 }
1241}
1242
1243////////////////////////////////////////////////////////////////////////////////
1244/// Register object in folders hierarchy
1245///
1246/// See TRootSniffer::RegisterObject() for more details
1247
1249{
1250 return fSniffer->RegisterObject(subfolder, obj);
1251}
1252
1253////////////////////////////////////////////////////////////////////////////////
1254/// Unregister object in folders hierarchy
1255///
1256/// See TRootSniffer::UnregisterObject() for more details
1257
1259{
1260 return fSniffer->UnregisterObject(obj);
1261}
1262
1263////////////////////////////////////////////////////////////////////////////////
1264/// Register WS handler to the THttpServer
1265///
1266/// Only such handler can be used in multi-threaded processing of websockets
1267
1268void THttpServer::RegisterWS(std::shared_ptr<THttpWSHandler> ws)
1269{
1270 std::lock_guard<std::mutex> grd(fWSMutex);
1271 fWSHandlers.emplace_back(ws);
1272}
1273
1274////////////////////////////////////////////////////////////////////////////////
1275/// Unregister WS handler to the THttpServer
1276
1277void THttpServer::UnregisterWS(std::shared_ptr<THttpWSHandler> ws)
1278{
1279 std::lock_guard<std::mutex> grd(fWSMutex);
1280 for (int n = (int)fWSHandlers.size(); n > 0; --n)
1281 if ((fWSHandlers[n - 1] == ws) || fWSHandlers[n - 1]->IsDisabled())
1282 fWSHandlers.erase(fWSHandlers.begin() + n - 1);
1283}
1284
1285////////////////////////////////////////////////////////////////////////////////
1286/// Search WS handler with given name
1287///
1288/// Handler must be registered with RegisterWS() method
1289
1290std::shared_ptr<THttpWSHandler> THttpServer::FindWS(const char *name)
1291{
1292 std::lock_guard<std::mutex> grd(fWSMutex);
1293 for (auto &ws : fWSHandlers) {
1294 if (strcmp(name, ws->GetName()) == 0)
1295 return ws;
1296 }
1297
1298 return nullptr;
1299}
1300
1301////////////////////////////////////////////////////////////////////////////////
1302/// Execute WS related operation
1303
1305{
1306 if (fTerminated) {
1307 arg->Set404();
1308 return kFALSE;
1309 }
1310
1311 auto wsptr = FindWS(arg->GetPathName());
1312
1313 auto handler = wsptr.get();
1314
1315 if (!handler && !external_thrd)
1316 handler = dynamic_cast<THttpWSHandler *>(fSniffer->FindTObjectInHierarchy(arg->fPathName.Data()));
1317
1318 if (external_thrd && (!handler || !handler->AllowMTProcess())) {
1319
1320 if (fTimer && fTimer->IsSlow())
1321 fTimer->SetSlow(kFALSE);
1322
1323 std::unique_lock<std::mutex> lk(fMutex);
1324 fArgs.push(arg);
1325 // and now wait until request is processed
1326 if (wait_process)
1327 arg->fCond.wait(lk);
1328
1329 return kTRUE;
1330 }
1331
1332 if (!handler)
1333 return kFALSE;
1334
1335 Bool_t process = kFALSE;
1336
1337 if (arg->fFileName == "root.websocket") {
1338 // handling of web socket
1339 process = handler->HandleWS(arg);
1340 } else if (arg->fFileName == "root.longpoll") {
1341 // ROOT emulation of websocket with polling requests
1342 if (arg->fQuery.BeginsWith("raw_connect") || arg->fQuery.BeginsWith("txt_connect")) {
1343 // try to emulate websocket connect
1344 // if accepted, reply with connection id, which must be used in the following communications
1345 arg->SetMethod("WS_CONNECT");
1346
1347 bool israw = arg->fQuery.BeginsWith("raw_connect");
1348
1349 // automatically assign engine to arg
1350 arg->CreateWSEngine<THttpLongPollEngine>(israw);
1351
1352 if (handler->HandleWS(arg)) {
1353 arg->SetMethod("WS_READY");
1354
1355 if (handler->HandleWS(arg))
1356 arg->SetTextContent(std::string(israw ? "txt:" : "") + std::to_string(arg->GetWSId()));
1357 } else {
1358 arg->TakeWSEngine(); // delete handle
1359 }
1360
1361 process = arg->IsText();
1362 } else {
1363 TUrl url;
1364 url.SetOptions(arg->fQuery);
1365 url.ParseOptions();
1366 const char *connid = url.GetValueFromOptions("connection");
1367 if (connid)
1368 arg->SetWSId(std::stoul(connid));
1369 if (url.HasOption("close")) {
1370 arg->SetMethod("WS_CLOSE");
1371 arg->SetTextContent("OK");
1372 } else {
1373 arg->SetMethod("WS_DATA");
1374 }
1375
1376 process = handler->HandleWS(arg);
1377 }
1378 }
1379
1380 if (!process)
1381 arg->Set404();
1382
1383 return process;
1384}
1385
1386////////////////////////////////////////////////////////////////////////////////
1387/// Restrict access to specified object
1388///
1389/// See TRootSniffer::Restrict() for more details
1390
1391void THttpServer::Restrict(const char *path, const char *options)
1392{
1393 fSniffer->Restrict(path, options);
1394}
1395
1396////////////////////////////////////////////////////////////////////////////////
1397/// Register command which can be executed from web interface
1398///
1399/// As method one typically specifies string, which is executed with
1400/// gROOT->ProcessLine() method. For instance:
1401///
1402/// serv->RegisterCommand("Invoke","InvokeFunction()");
1403///
1404/// Or one could specify any method of the object which is already registered
1405/// to the server. For instance:
1406///
1407/// serv->Register("/", hpx);
1408/// serv->RegisterCommand("/ResetHPX", "/hpx/->Reset()");
1409///
1410/// Here symbols '/->' separates item name from method to be executed
1411///
1412/// One could specify additional arguments in the command with
1413/// syntax like %arg1%, %arg2% and so on. For example:
1414///
1415/// serv->RegisterCommand("/ResetHPX", "/hpx/->SetTitle(\"%arg1%\")");
1416/// serv->RegisterCommand("/RebinHPXPY", "/hpxpy/->Rebin2D(%arg1%,%arg2%)");
1417///
1418/// Such parameter(s) will be requested when command clicked in the browser.
1419///
1420/// Once command is registered, one could specify icon which will appear in the browser:
1421///
1422/// serv->SetIcon("/ResetHPX", "rootsys/icons/ed_execute.png");
1423///
1424/// One also can set extra property '_fastcmd', that command appear as
1425/// tool button on the top of the browser tree:
1426///
1427/// serv->SetItemField("/ResetHPX", "_fastcmd", "true");
1428///
1429/// Or it is equivalent to specifying extra argument when register command:
1430///
1431/// serv->RegisterCommand("/ResetHPX", "/hpx/->Reset()", "button;rootsys/icons/ed_delete.png");
1432
1433Bool_t THttpServer::RegisterCommand(const char *cmdname, const char *method, const char *icon)
1434{
1435 return fSniffer->RegisterCommand(cmdname, method, icon);
1436}
1437
1438////////////////////////////////////////////////////////////////////////////////
1439/// Hides folder or element from web gui
1440
1442{
1443 return SetItemField(foldername, "_hidden", hide ? "true" : (const char *)0);
1444}
1445
1446////////////////////////////////////////////////////////////////////////////////
1447/// Set name of icon, used in browser together with the item
1448///
1449/// One could use images from $ROOTSYS directory like:
1450/// serv->SetIcon("/ResetHPX","/rootsys/icons/ed_execute.png");
1451
1452Bool_t THttpServer::SetIcon(const char *fullname, const char *iconname)
1453{
1454 return SetItemField(fullname, "_icon", iconname);
1455}
1456
1457////////////////////////////////////////////////////////////////////////////////
1458/// Create item in sniffer
1459
1460Bool_t THttpServer::CreateItem(const char *fullname, const char *title)
1461{
1462 return fSniffer->CreateItem(fullname, title);
1463}
1464
1465////////////////////////////////////////////////////////////////////////////////
1466/// Set item field in sniffer
1467
1468Bool_t THttpServer::SetItemField(const char *fullname, const char *name, const char *value)
1469{
1470 return fSniffer->SetItemField(fullname, name, value);
1471}
1472
1473////////////////////////////////////////////////////////////////////////////////
1474/// Get item field from sniffer
1475
1476const char *THttpServer::GetItemField(const char *fullname, const char *name)
1477{
1478 return fSniffer->GetItemField(fullname, name);
1479}
1480
1481////////////////////////////////////////////////////////////////////////////////
1482/// Returns MIME type base on file extension
1483
1484const char *THttpServer::GetMimeType(const char *path)
1485{
1486 static const struct {
1487 const char *extension;
1488 int ext_len;
1489 const char *mime_type;
1490 } builtin_mime_types[] = {{".xml", 4, "text/xml"},
1491 {".json", 5, "application/json"},
1492 {".bin", 4, "application/x-binary"},
1493 {".gif", 4, "image/gif"},
1494 {".jpg", 4, "image/jpeg"},
1495 {".png", 4, "image/png"},
1496 {".html", 5, "text/html"},
1497 {".htm", 4, "text/html"},
1498 {".shtm", 5, "text/html"},
1499 {".shtml", 6, "text/html"},
1500 {".css", 4, "text/css"},
1501 {".js", 3, "application/x-javascript"},
1502 {".mjs", 4, "text/javascript"},
1503 {".ico", 4, "image/x-icon"},
1504 {".jpeg", 5, "image/jpeg"},
1505 {".svg", 4, "image/svg+xml"},
1506 {".txt", 4, "text/plain"},
1507 {".torrent", 8, "application/x-bittorrent"},
1508 {".wav", 4, "audio/x-wav"},
1509 {".mp3", 4, "audio/x-mp3"},
1510 {".mid", 4, "audio/mid"},
1511 {".m3u", 4, "audio/x-mpegurl"},
1512 {".ogg", 4, "application/ogg"},
1513 {".ram", 4, "audio/x-pn-realaudio"},
1514 {".xslt", 5, "application/xml"},
1515 {".xsl", 4, "application/xml"},
1516 {".ra", 3, "audio/x-pn-realaudio"},
1517 {".doc", 4, "application/msword"},
1518 {".exe", 4, "application/octet-stream"},
1519 {".zip", 4, "application/x-zip-compressed"},
1520 {".xls", 4, "application/excel"},
1521 {".tgz", 4, "application/x-tar-gz"},
1522 {".tar", 4, "application/x-tar"},
1523 {".gz", 3, "application/x-gunzip"},
1524 {".arj", 4, "application/x-arj-compressed"},
1525 {".rar", 4, "application/x-arj-compressed"},
1526 {".rtf", 4, "application/rtf"},
1527 {".pdf", 4, "application/pdf"},
1528 {".swf", 4, "application/x-shockwave-flash"},
1529 {".mpg", 4, "video/mpeg"},
1530 {".webm", 5, "video/webm"},
1531 {".mpeg", 5, "video/mpeg"},
1532 {".mov", 4, "video/quicktime"},
1533 {".mp4", 4, "video/mp4"},
1534 {".m4v", 4, "video/x-m4v"},
1535 {".asf", 4, "video/x-ms-asf"},
1536 {".avi", 4, "video/x-msvideo"},
1537 {".bmp", 4, "image/bmp"},
1538 {".ttf", 4, "application/x-font-ttf"},
1539 {".woff", 5, "font/woff"},
1540 {".woff2", 6, "font/woff2"},
1541 {NULL, 0, NULL}};
1542
1543 int path_len = strlen(path);
1544
1545 for (int i = 0; builtin_mime_types[i].extension != NULL; i++) {
1547 continue;
1548 const char *ext = path + (path_len - builtin_mime_types[i].ext_len);
1549 if (strcmp(ext, builtin_mime_types[i].extension) == 0) {
1550 return builtin_mime_types[i].mime_type;
1551 }
1552 }
1553
1554 return "text/plain";
1555}
1556
1557////////////////////////////////////////////////////////////////////////////////
1558/// Reads file content
1559///
1560/// @deprecated
1561
1563{
1564 len = 0;
1565
1566 std::ifstream is(filename, std::ios::in | std::ios::binary);
1567 if (!is)
1568 return nullptr;
1569
1570 is.seekg(0, is.end);
1571 len = is.tellg();
1572 is.seekg(0, is.beg);
1573
1574 char *buf = (char *)malloc(len);
1575 is.read(buf, len);
1576 if (!is) {
1577 free(buf);
1578 len = 0;
1579 return nullptr;
1580 }
1581
1582 return buf;
1583}
1584
1585////////////////////////////////////////////////////////////////////////////////
1586/// Reads file content, using std::string as container
1587
1588std::string THttpServer::ReadFileContent(const std::string &filename)
1589{
1590 std::ifstream is(filename, std::ios::in | std::ios::binary);
1591 std::string res;
1592 if (is) {
1593 is.seekg(0, std::ios::end);
1594 res.resize(is.tellg());
1595 is.seekg(0, std::ios::beg);
1596 is.read((char *)res.data(), res.length());
1597 if (!is)
1598 res.clear();
1599 }
1600 return res;
1601}
long Long_t
Signed long integer 4 bytes (long). Size depends on architecture.
Definition RtypesCore.h:68
constexpr Bool_t kFALSE
Definition RtypesCore.h:108
constexpr Ssiz_t kNPOS
The equivalent of std::string::npos for the ROOT class TString.
Definition RtypesCore.h:131
constexpr Bool_t kTRUE
Definition RtypesCore.h:107
ROOT::Detail::TRangeCast< T, true > TRangeDynCast
TRangeDynCast is an adapter class that allows the typed iteration through a TCollection.
R__EXTERN TEnv * gEnv
Definition TEnv.h:170
winID h TVirtualViewer3D TVirtualGLPainter p
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t Float_t Float_t Float_t Int_t Int_t UInt_t UInt_t Rectangle_t Int_t Int_t Window_t TString Int_t GCValues_t GetPrimarySelectionOwner GetDisplay GetScreen GetColormap GetNativeEvent const char const char dpyName wid window const char font_name cursor keysym reg const char only_if_exist regb h Point_t winding char text const char depth char const char Int_t count const char ColorStruct_t color const char filename
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize id
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void on
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void value
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t Float_t Float_t Float_t Int_t Int_t UInt_t UInt_t Rectangle_t Int_t Int_t Window_t TString Int_t GCValues_t GetPrimarySelectionOwner GetDisplay GetScreen GetColormap GetNativeEvent const char const char dpyName wid window const char font_name cursor keysym reg const char only_if_exist regb h Point_t winding char text const char depth char const char Int_t count const char ColorStruct_t color const char Pixmap_t Pixmap_t PictureAttributes_t attr const char char ret_data h unsigned char height h Atom_t Int_t ULong_t ULong_t unsigned char prop_list Atom_t Atom_t Atom_t Time_t UChar_t len
Option_t Option_t TPoint TPoint const char mode
char name[80]
Definition TGX11.cxx:110
Int_t gDebug
Global variable setting the debug level. Set to 0 to disable, increase it in steps of 1 to increase t...
Definition TROOT.cxx:627
#define gROOT
Definition TROOT.h:411
R__EXTERN TSystem * gSystem
Definition TSystem.h:572
const char * mime_type
Definition civetweb.c:8517
size_t ext_len
Definition civetweb.c:8516
#define free
Definition civetweb.c:1578
const char * extension
Definition civetweb.c:8515
#define malloc
Definition civetweb.c:1575
static const struct @140 builtin_mime_types[]
const_iterator end() const
THttpEngine implementation, based on civetweb embedded server.
Definition TCivetweb.h:21
TClass instances represent classes, structs and namespaces in the ROOT type system.
Definition TClass.h:84
static TClass * GetClass(const char *name, Bool_t load=kTRUE, Bool_t silent=kFALSE)
Static method returning pointer to TClass of the specified class name.
Definition TClass.cxx:2973
virtual Int_t GetValue(const char *name, Int_t dflt) const
Returns the integer value for a resource.
Definition TEnv.cxx:490
THttpEngine implementation, based on fastcgi package.
Definition TFastCgi.h:20
Contains arguments for single HTTP call.
void Set404()
mark reply as 404 error - page/request not exists or refused
Abstract class for implementing http protocol for THttpServer.
Definition THttpEngine.h:19
Emulation of websocket with long poll requests.
Online http server for arbitrary ROOT application.
Definition THttpServer.h:31
Bool_t IsReadOnly() const
returns read-only mode
Bool_t RegisterCommand(const char *cmdname, const char *method, const char *icon=nullptr)
Register command which can be executed from web interface.
TString fJSROOT
! location of external JSROOT files
Definition THttpServer.h:46
virtual void ProcessRequest(std::shared_ptr< THttpCallArg > arg)
Process single http request.
std::shared_ptr< THttpWSHandler > FindWS(const char *name)
Find web-socket handler with given name.
std::unique_ptr< TRootSniffer > fSniffer
! sniffer provides access to ROOT objects hierarchy
Definition THttpServer.h:36
void SetTimer(Long_t milliSec=100, Bool_t mode=kTRUE)
Create timer which will invoke ProcessRequests() function periodically.
virtual void ProcessBatchHolder(std::shared_ptr< THttpCallArg > &arg)
Process special http request for root_batch_holder.js script.
std::vector< std::shared_ptr< THttpWSHandler > > fWSHandlers
! list of WS handlers
Definition THttpServer.h:61
virtual ~THttpServer()
destructor
void SetTerminate()
set termination flag, no any further requests will be processed
virtual void MissedRequest(THttpCallArg *arg)
Method called when THttpServer cannot process request.
Bool_t fOwnThread
! true when specialized thread allocated for processing requests
Definition THttpServer.h:40
void SetSniffer(TRootSniffer *sniff)
Set TRootSniffer to the server.
Bool_t IsFileRequested(const char *uri, TString &res) const
Check if file is requested, thread safe.
void SetReadOnly(Bool_t readonly=kTRUE)
Set read-only mode for the server (default on)
const char * GetItemField(const char *fullname, const char *name)
Get item field from sniffer.
const char * GetCors() const
Returns specified CORS domain.
std::thread fThrd
! own thread
Definition THttpServer.h:41
void StopServerThread()
Stop server thread.
Int_t ProcessRequests()
Process submitted requests, must be called from appropriate thread.
Bool_t ExecuteWS(std::shared_ptr< THttpCallArg > &arg, Bool_t external_thrd=kFALSE, Bool_t wait_process=kFALSE)
Execute WS request.
void RegisterWS(std::shared_ptr< THttpWSHandler > ws)
Register WS handler.
Long_t fProcessingThrdId
! id of the thread where events are recently processing
Definition THttpServer.h:39
TString fTopName
! name of top folder, default - "ROOT"
Definition THttpServer.h:45
void SetDrawPage(const std::string &filename="")
Set drawing HTML page.
Bool_t CreateItem(const char *fullname, const char *title)
Create item in sniffer.
Bool_t ExecuteHttp(std::shared_ptr< THttpCallArg > arg)
Execute HTTP request.
Bool_t Hide(const char *fullname, Bool_t hide=kTRUE)
Hides folder or element from web gui.
Bool_t IsCorsCredentials() const
Returns kTRUE if Access-Control-Allow-Credentials header should be used.
void AddLocation(const char *prefix, const char *path)
Add files location, which could be used in the server.
std::map< std::string, std::string > fLocations
! list of local directories, which could be accessed via server
Definition THttpServer.h:48
Bool_t SubmitHttp(std::shared_ptr< THttpCallArg > arg, Bool_t can_run_immediately=kFALSE)
Submit HTTP request.
Long_t fMainThrdId
! id of the thread for processing requests
Definition THttpServer.h:38
TString fJSROOTSYS
! location of local JSROOT files
Definition THttpServer.h:44
std::unique_ptr< THttpTimer > fTimer
! timer used to access main thread
Definition THttpServer.h:35
Bool_t fWSOnly
! when true, handle only websockets / longpoll engine
Definition THttpServer.h:42
Bool_t Register(const char *subfolder, TObject *obj)
Register object in subfolder.
TList fEngines
! engines which runs http server
Definition THttpServer.h:34
void SetCors(const std::string &domain="*")
Enable CORS header to ProcessRequests() responses Specified location (typically "*") add as "Access-C...
Bool_t IsCors() const
Returns kTRUE if CORS was configured.
const char * GetCorsCredentials() const
Returns specified CORS credentials value - if any.
std::queue< std::shared_ptr< THttpCallArg > > fArgs
! submitted arguments
Definition THttpServer.h:58
void SetDefaultPage(const std::string &filename="")
Set default HTML page.
THttpServer(const THttpServer &)=delete
static char * ReadFileContent(const char *filename, Int_t &len)
Reads content of file from the disk.
void CreateServerThread()
Creates special thread to process all requests, directed to http server.
std::string fDrawPageCont
! content of draw html page
Definition THttpServer.h:53
Bool_t Unregister(TObject *obj)
Unregister object.
void SetWSOnly(Bool_t on=kTRUE)
Set websocket-only mode.
std::string BuildWSEntryPage()
Create summary page with active WS handlers.
Bool_t IsWSOnly() const
returns true if only websockets are handled by the server
std::mutex fWSMutex
! mutex to protect WS handler lists
Definition THttpServer.h:60
virtual Bool_t CreateEngine(const char *engine)
Factory method to create different http engines.
Bool_t SetIcon(const char *fullname, const char *iconname)
Set name of icon, used in browser together with the item.
std::string fDrawPage
! file name for drawing of single element
Definition THttpServer.h:52
std::string fDefaultPageCont
! content of default html page
Definition THttpServer.h:51
static Bool_t VerifyFilePath(const char *fname)
Checked that filename does not contains relative path below current directory.
Bool_t SetItemField(const char *fullname, const char *name, const char *value)
Set item field in sniffer.
void SetJSROOT(const char *location)
Set location of JSROOT to use with the server.
std::mutex fMutex
! mutex to protect list with arguments
Definition THttpServer.h:57
std::string fDefaultPage
! file name for default page name
Definition THttpServer.h:50
void UnregisterWS(std::shared_ptr< THttpWSHandler > ws)
Unregister WS handler.
static const char * GetMimeType(const char *path)
Guess mime type base on file extension.
TRootSniffer * GetSniffer() const
returns pointer on objects sniffer
Definition THttpServer.h:89
void ReplaceJSROOTLinks(std::shared_ptr< THttpCallArg > &arg, const std::string &version="")
Replaces all references like "jsrootsys/..." or other pre-configured pathes.
Bool_t fTerminated
! termination flag, disables all requests processing
Definition THttpServer.h:37
void Restrict(const char *path, const char *options)
Restrict access to specified object.
Long_t fNormalTmout
void Timeout() override
timeout handler used to process http requests in main ROOT thread
Bool_t IsSlow() const
void SetSlow(Bool_t flag)
THttpServer & fServer
THttpTimer(Long_t milliSec, Bool_t mode, THttpServer &serv)
!< server processing requests
Class for user-side handling of websocket with THttpServer.
void Add(TObject *obj) override
Definition TList.h:81
void Delete(Option_t *option="") override
Remove all objects from the list AND delete all heap based objects.
Definition TList.cxx:467
The TNamed class is the base class for all named ROOT classes.
Definition TNamed.h:29
An array of TObjects.
Definition TObjArray.h:31
Mother of all ROOT objects.
Definition TObject.h:41
virtual void Warning(const char *method, const char *msgfmt,...) const
Issue warning message.
Definition TObject.cxx:1057
virtual void Error(const char *method, const char *msgfmt,...) const
Issue error message.
Definition TObject.cxx:1071
static const TString & GetRootSys()
Get the rootsys directory in the installation. Static utility function.
Definition TROOT.cxx:2986
static const TString & GetDataDir()
Get the data directory in the installation. Static utility function.
Definition TROOT.cxx:3117
Storage of hierarchy scan in TRootSniffer in JSON format.
Storage of hierarchy scan in TRootSniffer in XML format.
Sniffer of ROOT objects, data provider for THttpServer.
void SetReadOnly(Bool_t on=kTRUE)
When readonly on (default), sniffer is not allowed to change ROOT structures For instance,...
void SetScanGlobalDir(Bool_t on=kTRUE)
When enabled (default), sniffer scans gROOT for files, canvases, histograms.
Basic string class.
Definition TString.h:138
Ssiz_t Length() const
Definition TString.h:425
const char * Data() const
Definition TString.h:384
void Resize(Ssiz_t n)
Resize the string. Truncate or add blanks as necessary.
Definition TString.cxx:1159
Ssiz_t Last(char c) const
Find last occurrence of a character c.
Definition TString.cxx:938
TObjArray * Tokenize(const TString &delim) const
This function is used to isolate sequential tokens in a TString.
Definition TString.cxx:2270
TString & Remove(Ssiz_t pos)
Definition TString.h:693
TString & Append(const char *cs)
Definition TString.h:580
static TString Format(const char *fmt,...)
Static method which formats a string using a printf style format descriptor and return a TString.
Definition TString.cxx:2384
void Form(const char *fmt,...)
Formats a string using a printf style format descriptor.
Definition TString.cxx:2362
Ssiz_t Index(const char *pat, Ssiz_t i=0, ECaseCompare cmp=kExact) const
Definition TString.h:659
virtual Bool_t ExpandPathName(TString &path)
Expand a pathname getting rid of special shell characters like ~.
Definition TSystem.cxx:1285
virtual const char * Getenv(const char *env)
Get environment variable.
Definition TSystem.cxx:1676
static Long_t SelfId()
Static method returning the id for the current thread.
Definition TThread.cxx:552
Handles synchronous and a-synchronous timer events.
Definition TTimer.h:51
void SetTime(Long_t milliSec)
Definition TTimer.h:91
This class represents a WWW compatible URL.
Definition TUrl.h:33
const Int_t n
Definition legend1.C:16