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