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