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