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 {
43
44public:
45 THttpServer &fServer; ///!< server processing requests
46
47 /// constructor
49
50
51 /// timeout handler
52 /// used to process http requests in main ROOT thread
53 void Timeout() override
54 {
56 }
57};
58
59/**
60\class THttpServer
61\ingroup http
62\brief Online http server for arbitrary ROOT application
63\note This class provides HTTP access to ROOT objects. The user is entirely responsible for the security of the server.
64It is strongly recommended to use the server only within an isolated network
65or to enable proper authentication to prevent unauthorized remote access.
66
67Idea of THttpServer - provide remote http access to running
68ROOT application and enable HTML/JavaScript user interface.
69Any registered object can be requested and displayed in the browser.
70There are many benefits of such approach:
71
721. standard http interface to ROOT application
732. no any temporary ROOT files when access data
743. user interface running in all browsers
75
76To start http server simply create instance
77of the THttpServer class like:
78
79 serv = new THttpServer("http:8080");
80
81This will starts civetweb-based http server with http port 8080.
82Than one should be able to open address "http://localhost:8080"
83in any modern web browser (Firefox, Chrome, Opera, ...) and browse objects,
84created in ROOT application. By default, server can access files,
85canvases and histograms via `gROOT` pointer. All such objects
86can be displayed with JSROOT graphics.
87
88At any time one could register other objects with the command:
89
90 TGraph* gr = new TGraph(10);
91 gr->SetName("gr1");
92 serv->Register("graphs/subfolder", gr);
93
94If objects content is changing in the application, one could
95enable monitoring flag in the browser - than objects view
96will be regularly updated.
97
98More information: https://root.cern/root/htmldoc/guides/HttpServer/HttpServer.html
99*/
100
101
102////////////////////////////////////////////////////////////////////////////////
103/// constructor
104///
105/// As argument, one specifies engine kind which should be
106/// created like "http:8080". One could specify several engines
107/// at once, separating them with semicolon (";"). Following engines are supported:
108///
109/// http - TCivetweb, civetweb-based implementation of http protocol
110/// fastcgi - TFastCgi, special protocol for communicating with web servers
111///
112/// For each created engine one should provide socket port number like "http:8080" or "fastcgi:9000".
113/// Additional engine-specific options can be supplied with URL syntax like "http:8080?thrds=10".
114/// Full list of supported options should be checked in engines docu.
115///
116/// One also can configure following options, separated by semicolon:
117///
118/// readonly, ro - set read-only mode (default)
119/// readwrite, rw - allows methods execution of registered objects
120/// global - scans global ROOT lists for existing objects (default)
121/// noglobal - disable scan of global lists
122/// cors - enable CORS header with origin="*"
123/// cors=domain - enable CORS header with origin="domain"
124/// basic_sniffer - use basic sniffer without support of hist, gpad, graph classes
125///
126/// For example, create http server, which allows cors headers and disable scan of global lists,
127/// one should provide "http:8080;cors;noglobal" as parameter
128///
129/// THttpServer uses JavaScript ROOT (https://root.cern/js) to implement web clients UI.
130/// Normally JSROOT sources are used from $ROOTSYS/js directory,
131/// but one could set JSROOTSYS shell variable to specify alternative location
132
133THttpServer::THttpServer(const char *engine) : TNamed("http", "ROOT http server")
134{
135 const char *jsrootsys = gSystem->Getenv("JSROOTSYS");
136 if (!jsrootsys)
137 jsrootsys = gEnv->GetValue("HttpServ.JSRootPath", jsrootsys);
138
139 if (jsrootsys && *jsrootsys) {
140 if ((strncmp(jsrootsys, "http://", 7)==0) || (strncmp(jsrootsys, "https://", 8)==0))
142 else
144 }
145
146 if (fJSROOTSYS.Length() == 0) {
147 TString jsdir = TString::Format("%s/js", TROOT::GetDataDir().Data());
149 ::Warning("THttpServer::THttpServer", "problems resolving '%s', set JSROOTSYS to proper JavaScript ROOT location",
150 jsdir.Data());
151 fJSROOTSYS = ".";
152 } else {
154 }
155 }
156
157 Bool_t basic_sniffer = strstr(engine, "basic_sniffer") != nullptr;
158
159 AddLocation("jsrootsys/", fJSROOTSYS.Data());
160
161 if (basic_sniffer) {
162 AddLocation("rootsys_fonts/", TString::Format("%s/fonts", TROOT::GetDataDir().Data()));
163 } else {
164 AddLocation("currentdir/", ".");
165 AddLocation("rootsys/", TROOT::GetRootSys());
166 }
167
168 fDefaultPage = fJSROOTSYS + "/files/online.htm";
169 fDrawPage = fJSROOTSYS + "/files/draw.htm";
170
171 TRootSniffer *sniff = nullptr;
172 if (!basic_sniffer) {
173 static const TClass *snifferClass = TClass::GetClass("TRootSnifferFull");
174 if (snifferClass && snifferClass->IsLoaded())
175 sniff = (TRootSniffer *)snifferClass->New();
176 else
177 ::Warning("THttpServer::THttpServer", "Fail to load TRootSnifferFull class, use basic functionality");
178 }
179
180 if (!sniff) {
181 sniff = new TRootSniffer();
182 sniff->SetScanGlobalDir(kFALSE);
183 sniff->CreateOwnTopFolder(); // use dedicated folder
184 }
185
187
188 // start timer
189 SetTimer(20, kTRUE);
190
191 if (strchr(engine, ';') == 0) {
192 CreateEngine(engine);
193 } else {
194 TObjArray *lst = TString(engine).Tokenize(";");
195
196 for (Int_t n = 0; n <= lst->GetLast(); n++) {
197 const char *opt = lst->At(n)->GetName();
198 if ((strcmp(opt, "readonly") == 0) || (strcmp(opt, "ro") == 0)) {
200 } else if ((strcmp(opt, "readwrite") == 0) || (strcmp(opt, "rw") == 0)) {
202 } else if (strcmp(opt, "global") == 0) {
204 } else if (strcmp(opt, "noglobal") == 0) {
206 } else if (strncmp(opt, "cors=", 5) == 0) {
207 SetCors(opt + 5);
208 } else if (strcmp(opt, "cors") == 0) {
209 SetCors("*");
210 } else
211 CreateEngine(opt);
212 }
213
214 delete lst;
215 }
216}
217
218////////////////////////////////////////////////////////////////////////////////
219/// destructor
220///
221/// delete all http engines and sniffer
222
224{
226
227 if (fTerminated) {
228 TIter iter(&fEngines);
229 while (auto engine = dynamic_cast<THttpEngine *>(iter()))
230 engine->Terminate();
231 }
232
234
235 SetSniffer(nullptr);
236
237 SetTimer(0);
238}
239
240////////////////////////////////////////////////////////////////////////////////
241/// Set TRootSniffer to the server
242///
243/// Server takes ownership over sniffer
244
249
250////////////////////////////////////////////////////////////////////////////////
251/// Set termination flag,
252///
253/// No any further requests will be processed, server only can be destroyed afterwards
254
259
260////////////////////////////////////////////////////////////////////////////////
261/// returns read-only mode
262
264{
265 return fSniffer ? fSniffer->IsReadOnly() : kTRUE;
266}
267
268////////////////////////////////////////////////////////////////////////////////
269/// Set read-only mode for the server (default on)
270///
271/// In read-only server is not allowed to change any ROOT object, registered to the server
272/// Server also cannot execute objects method via exe.json request
273
275{
276 if (fSniffer)
277 fSniffer->SetReadOnly(readonly);
278}
279
280////////////////////////////////////////////////////////////////////////////////
281/// Returns true if server accept object content in POST reequests
282
284{
285 return fSniffer ? fSniffer->IsAllowPostObject() : kFALSE;
286}
287
288////////////////////////////////////////////////////////////////////////////////
289/// Set flag to allow receive and desereilize objects in POST requests
290///
291/// When object methods are executed via exe.json request,
292/// one can supply object as binary/json/xml in the body of POST request
293/// To allow creation of such object, one need to enable this flag
294/// Use of exe.json only possible in not-readonly mode
295///
296/// CAUTION! This is sensitive functionality and therefore should be
297/// used only when server not exposed to publicaly-accessed netowork.
298
300{
301 if (fSniffer)
302 fSniffer->SetAllowPostObject(allow_post_obj);
303}
304
305////////////////////////////////////////////////////////////////////////////////
306/// returns true if only websockets are handled by the server
307///
308/// Typically used by WebGui
309
311{
312 return fWSOnly;
313}
314
315////////////////////////////////////////////////////////////////////////////////
316/// Set websocket-only mode.
317///
318/// If true, server will only handle websockets connection
319/// plus serving file requests to access jsroot/ui5 scripts
320
325
326////////////////////////////////////////////////////////////////////////////////
327/// Add files location, which could be used in the server
328///
329/// One could map some system folder to the server like
330///
331/// serv->AddLocation("mydir/", "/home/user/specials");
332///
333/// Than files from this directory could be addressed via server like `http://localhost:8080/mydir/myfile.root`
334
335void THttpServer::AddLocation(const char *prefix, const char *path)
336{
337 if (!prefix || (*prefix == 0))
338 return;
339
340 if (!path)
341 fLocations.erase(fLocations.find(prefix));
342 else
343 fLocations[prefix] = path;
344}
345
346////////////////////////////////////////////////////////////////////////////////
347/// Set location of JSROOT to use with the server
348///
349/// One could specify address like:
350///
351/// * https://root.cern/js/7.6.0/
352/// * https://jsroot.gsi.de/7.6.0/
353///
354/// This allows to get new JSROOT features with old server,
355/// reduce load on THttpServer instance, also startup time can be improved
356/// When empty string specified (default), local copy of JSROOT is used (distributed with ROOT)
357
358void THttpServer::SetJSROOT(const char *location)
359{
360 fJSROOT = location ? location : "";
361}
362
363////////////////////////////////////////////////////////////////////////////////
364/// Set default HTML page
365///
366/// Sets file name, delivered by the server when http address is opened in the browser.
367///
368/// By default, $ROOTSYS/js/files/online.htm page is used
369/// When empty filename is specified, default page will be used
370
371void THttpServer::SetDefaultPage(const std::string &filename)
372{
373 if (!filename.empty())
375 else
376 fDefaultPage = fJSROOTSYS + "/files/online.htm";
377
378 // force to read page content next time again
379 fDefaultPageCont.clear();
380}
381
382////////////////////////////////////////////////////////////////////////////////
383/// Set drawing HTML page
384///
385/// Set file name of HTML page, delivered by the server when
386/// objects drawing page is requested from the browser
387/// By default, $ROOTSYS/js/files/draw.htm page is used
388/// When empty filename is specified, default page will be used
389
390void THttpServer::SetDrawPage(const std::string &filename)
391{
392 if (!filename.empty())
394 else
395 fDrawPage = fJSROOTSYS + "/files/draw.htm";
396
397 // force to read page content next time again
398 fDrawPageCont.clear();
399}
400
401////////////////////////////////////////////////////////////////////////////////
402/// Factory method to create different http engines
403///
404/// At the moment two engine kinds are supported:
405///
406/// * civetweb or http (default)
407/// * fastcgi
408///
409/// Examples:
410///
411/// // creates civetweb web server with http port 8080
412/// serv->CreateEngine("http:8080");
413/// serv->CreateEngine("civetweb:8080");
414/// serv->CreateEngine(":8080");
415/// // creates fastcgi server with port 9000
416/// serv->CreateEngine("fastcgi:9000");
417///
418/// One could apply additional parameters, using URL syntax:
419///
420/// serv->CreateEngine("http:8080?thrds=10");
421
423{
424 if (!engine)
425 return kFALSE;
426
427 const char *arg = strchr(engine, ':');
428 if (!arg)
429 return kFALSE;
430
432 if (arg != engine)
433 clname.Append(engine, arg - engine);
434 arg++; // skip first :
435
436 THttpEngine *eng = nullptr;
437
438 if ((clname.Length() == 0) || (clname == "http") || (clname == "civetweb")) {
439 eng = new TCivetweb(kFALSE);
440#ifndef R__WIN32
441 } else if (clname == "socket") {
442 eng = new TCivetweb(kFALSE);
443 sarg = "x"; // civetweb require x before socket name
444 sarg.Append(arg);
445 arg = sarg.Data();
446#endif
447 } else if (clname == "https") {
448 eng = new TCivetweb(kTRUE);
449 } else if (clname == "fastcgi") {
450 eng = new TFastCgi();
451 }
452
453 if (!eng) {
454 // ensure that required engine class exists before we try to create it
455 TClass *engine_class = gROOT->LoadClass(clname.Data());
456 if (!engine_class)
457 return kFALSE;
458
459 eng = (THttpEngine *)engine_class->New();
460 if (!eng)
461 return kFALSE;
462 }
463
464 eng->SetServer(this);
465
466 if (!eng->Create(arg)) {
467 delete eng;
468 return kFALSE;
469 }
470
472
473 return kTRUE;
474}
475
476////////////////////////////////////////////////////////////////////////////////
477/// Create timer which will invoke ProcessRequests() function periodically
478///
479/// Timer is required to perform all actions in main ROOT thread
480/// Method arguments are the same as for TTimer constructor
481/// By default, sync timer with 100 ms period is created
482///
483/// It is recommended to always use sync timer mode and only change period to
484/// adjust server reaction time. Use of async timer requires, that application regularly
485/// calls gSystem->ProcessEvents(). It happens automatically in ROOT interactive shell.
486/// If milliSec == 0, no timer will be created.
487/// In this case application should regularly call ProcessRequests() method.
488///
489/// Async timer allows to use THttpServer in applications, which does not have explicit
490/// gSystem->ProcessEvents() calls. But be aware, that such timer can interrupt any system call
491/// (like malloc) and can lead to dead locks, especially in multi-threaded applications.
492
494{
495 if (fTimer) {
496 fTimer->Stop();
497 fTimer.reset();
498 }
499 if (milliSec > 0) {
500 if (fOwnThread) {
501 Error("SetTimer", "Server runs already in special thread, therefore no any timer can be created");
502 } else {
503 fTimer = std::make_unique<THttpTimer>(milliSec, mode, *this);
504 fTimer->TurnOn();
505 }
506 }
507}
508
509////////////////////////////////////////////////////////////////////////////////
510/// Creates special thread to process all requests, directed to http server
511///
512/// Should be used with care - only dedicated instance of TRootSniffer is allowed
513/// By default THttpServer allows to access global lists pointers gROOT or gFile.
514/// To be on the safe side, all kind of such access performed from the main thread.
515/// Therefore usage of specialized thread means that no any global pointers will
516/// be accessible by THttpServer
517
519{
520 if (fOwnThread)
521 return;
522
523 SetTimer(0);
524 fMainThrdId = 0;
525 fOwnThread = true;
526
527 std::thread thrd([this] {
528 int nempty = 0;
529 while (fOwnThread && !fTerminated) {
531 if (nprocess > 0)
532 nempty = 0;
533 else
534 nempty++;
535 if (nempty > 1000) {
536 nempty = 0;
537 std::this_thread::sleep_for(std::chrono::milliseconds(1));
538 }
539 }
540 });
541
542 fThrd = std::move(thrd);
543}
544
545////////////////////////////////////////////////////////////////////////////////
546/// Stop server thread
547///
548/// Normally called shortly before http server destructor
549
551{
552 if (!fOwnThread)
553 return;
554
555 fOwnThread = false;
556 fThrd.join();
557 fMainThrdId = 0;
558}
559
560////////////////////////////////////////////////////////////////////////////////
561/// Checked that filename does not contains relative path below current directory
562///
563/// Used to prevent access to files below current directory
564
566{
567 if (!fname || (*fname == 0))
568 return kFALSE;
569
570 Int_t level = 0;
571
572 while (*fname) {
573
574 // find next slash or backslash
575 const char *next = strpbrk(fname, "/\\");
576 if (next == 0)
577 return kTRUE;
578
579 // most important - change to parent dir
580 if ((next == fname + 2) && (*fname == '.') && (*(fname + 1) == '.')) {
581 fname += 3;
582 level--;
583 if (level < 0)
584 return kFALSE;
585 continue;
586 }
587
588 // ignore current directory
589 if ((next == fname + 1) && (*fname == '.')) {
590 fname += 2;
591 continue;
592 }
593
594 // ignore slash at the front
595 if (next == fname) {
596 fname++;
597 continue;
598 }
599
600 fname = next + 1;
601 level++;
602 }
603
604 return kTRUE;
605}
606
607////////////////////////////////////////////////////////////////////////////////
608/// Verifies that request is just file name
609///
610/// File names typically contains prefix like "jsrootsys/"
611/// If true, method returns real name of the file,
612/// which should be delivered to the client
613/// Method is thread safe and can be called from any thread
614
615Bool_t THttpServer::IsFileRequested(const char *uri, TString &res) const
616{
617 if (!uri || (*uri == 0))
618 return kFALSE;
619
620 TString fname(uri);
621
622 for (auto &entry : fLocations) {
623 Ssiz_t pos = fname.Index(entry.first.c_str());
624 if (pos == kNPOS)
625 continue;
626 fname.Remove(0, pos + (entry.first.length() - 1));
627 if (!VerifyFilePath(fname.Data()))
628 return kFALSE;
629 res = entry.second.c_str();
630 if ((fname[0] == '/') && (res[res.Length() - 1] == '/'))
631 res.Resize(res.Length() - 1);
632 res.Append(fname);
633 return kTRUE;
634 }
635
636 return kFALSE;
637}
638
639////////////////////////////////////////////////////////////////////////////////
640/// Executes http request, specified in THttpCallArg structure
641///
642/// Method can be called from any thread
643/// Actual execution will be done in main ROOT thread, where analysis code is running.
644
645Bool_t THttpServer::ExecuteHttp(std::shared_ptr<THttpCallArg> arg)
646{
647 if (fTerminated)
648 return kFALSE;
649
650 if ((fMainThrdId != 0) && (fMainThrdId == TThread::SelfId())) {
651 // should not happen, but one could process requests directly without any signaling
652
653 ProcessRequest(arg);
654
655 return kTRUE;
656 }
657
658 // add call arg to the list
659 std::unique_lock<std::mutex> lk(fMutex);
660 arg->fNotifyFlag = kFALSE;
661 fArgs.push(arg);
662 // and now wait until request is processed
663 arg->fCond.wait(lk);
664
665 return kTRUE;
666}
667
668////////////////////////////////////////////////////////////////////////////////
669/// Submit http request, specified in THttpCallArg structure
670///
671/// Contrary to ExecuteHttp, it will not block calling thread.
672/// User should implement THttpCallArg::HttpReplied() method
673/// to react when HTTP request is executed.
674
675/// Method can be called from any thread
676/// Actual execution will be done in main ROOT thread, where analysis code is running.
677/// When called from main thread and can_run_immediately==kTRUE, will be
678/// executed immediately.
679///
680/// Returns kTRUE when was executed.
681
682Bool_t THttpServer::SubmitHttp(std::shared_ptr<THttpCallArg> arg, Bool_t can_run_immediately)
683{
684 if (fTerminated)
685 return kFALSE;
686
688 ProcessRequest(arg);
689 arg->NotifyCondition();
690 return kTRUE;
691 }
692
693 // add call arg to the list
694 std::unique_lock<std::mutex> lk(fMutex);
695 fArgs.push(arg);
696 return kFALSE;
697}
698
699////////////////////////////////////////////////////////////////////////////////
700/// Process requests, submitted for execution
701///
702/// Returns number of processed requests
703///
704/// Normally invoked by THttpTimer, when somewhere in the code
705/// gSystem->ProcessEvents() is called.
706/// User can call serv->ProcessRequests() directly, but only from main thread.
707/// If special server thread is created, called from that thread
708
710{
711 auto id = TThread::SelfId();
712
713 if (fMainThrdId != id) {
714 if (gDebug > 0 && fMainThrdId)
715 Warning("ProcessRequests", "Changing main thread to %ld", (long)id);
716 fMainThrdId = id;
717 }
718
720
721 if (fProcessingThrdId) {
722 if (fProcessingThrdId == id) {
724 } else {
725 Error("ProcessRequests", "Processing already running from %ld thread", (long) fProcessingThrdId);
726 return 0;
727 }
728 }
729
730 if (!recursion)
732
733 Int_t cnt = 0;
734
735 std::unique_lock<std::mutex> lk(fMutex, std::defer_lock);
736
737 // first process requests in the queue
738 while (true) {
739 std::shared_ptr<THttpCallArg> arg;
740
741 lk.lock();
742 if (!fArgs.empty()) {
743 arg = fArgs.front();
744 fArgs.pop();
745 }
746 lk.unlock();
747
748 if (!arg)
749 break;
750
751 if (arg->fFileName == "root_batch_holder.js") {
753 continue;
754 }
755
756 auto prev = fSniffer->SetCurrentCallArg(arg.get());
757
758 try {
759 cnt++;
760 ProcessRequest(arg);
761 fSniffer->SetCurrentCallArg(prev);
762 } catch (...) {
763 fSniffer->SetCurrentCallArg(prev);
764 }
765
766 arg->NotifyCondition();
767 }
768
769 // regularly call Process() method of engine to let perform actions in ROOT context
770 TIter iter(&fEngines);
771 while (auto engine = static_cast<THttpEngine *>(iter())) {
772 if (fTerminated)
773 engine->Terminate();
774 engine->Process();
775 }
776
777 if (!recursion)
779
780 return cnt;
781}
782
783////////////////////////////////////////////////////////////////////////////////
784/// Method called when THttpServer cannot process request
785///
786/// By default such requests replied with 404 code
787/// One could overwrite with method in derived class to process all kinds of such non-standard requests
788
790{
791 arg->Set404();
792}
793
794////////////////////////////////////////////////////////////////////////////////
795/// Process special http request for root_batch_holder.js script
796///
797/// This kind of requests used to hold web browser running in headless mode
798/// Intentionally requests does not replied immediately
799
800void THttpServer::ProcessBatchHolder(std::shared_ptr<THttpCallArg> &arg)
801{
802 auto wsptr = FindWS(arg->GetPathName());
803
804 if (!wsptr || !wsptr->ProcessBatchHolder(arg)) {
805 arg->Set404();
806 arg->NotifyCondition();
807 }
808}
809
810////////////////////////////////////////////////////////////////////////////////
811/// Create summary page with active WS handlers
812
814{
815
816 std::string arr = "[";
817
818 {
819 std::lock_guard<std::mutex> grd(fWSMutex);
820 for (auto &ws : fWSHandlers) {
821 if (arr.length() > 1)
822 arr.append(", ");
823
824 arr.append(TString::Format("{ name: \"%s\", title: \"%s\" }", ws->GetName(), ws->GetTitle()).Data());
825 }
826 }
827
828 arr.append("]");
829
830 std::string res = ReadFileContent((TROOT::GetDataDir() + "/js/files/wslist.htm").Data());
831
832 std::string arg = "\"$$$wslist$$$\"";
833
834 auto pos = res.find(arg);
835 if (pos != std::string::npos)
836 res.replace(pos, arg.length(), arr);
837
838 return res;
839}
840
841////////////////////////////////////////////////////////////////////////////////
842/// Replaces all references like "jsrootsys/..." or other pre-configured pathes
843///
844/// Either using pre-configured JSROOT installation from web or
845/// redirect to jsrootsys from the main server path to benefit from browser caching
846/// Creates appropriate importmap instead of <!--jsroot_importmap--> placeholder
847
848void THttpServer::ReplaceJSROOTLinks(std::shared_ptr<THttpCallArg> &arg, const std::string &version)
849{
850 const std::string place_holder = "<!--jsroot_importmap-->";
851
852 auto p = arg->fContent.find(place_holder);
853
854 bool old_format = (p == std::string::npos);
855
856 // count slashes to handler relative paths to jsroot
857 Int_t slash_cnt = 0;
858 if (arg->fPathName.Length() > 0)
859 slash_cnt++;
860 for (Int_t n = 1; n < arg->fPathName.Length()-1; ++n)
861 if (arg->fPathName[n] == '/') {
862 if (arg->fPathName[n-1] != '/') {
863 slash_cnt++; // normal slash in the middle, count it
864 } else {
865 slash_cnt = 0; // double slash, do not touch such path
866 break;
867 }
868 }
869
870
871 if (old_format) {
872 // old functionality
873
874 if (!version.empty()) {
875 // replace link to JSROOT modules in import statements emulating new version for browser
876 std::string search = "from './jsrootsys/";
877 std::string replace = "from './" + version + "/jsrootsys/";
878 arg->ReplaceAllinContent(search, replace);
879 // replace link to ROOT ui5 modules in import statements emulating new version for browser
880 search = "from './rootui5sys/";
881 replace = "from './" + version + "/rootui5sys/";
882 arg->ReplaceAllinContent(search, replace);
883 // replace link on old JSRoot.core.js script - if still appears
884 search = "jsrootsys/scripts/JSRoot.core.";
885 replace = version + "/jsrootsys/scripts/JSRoot.core.";
886 arg->ReplaceAllinContent(search, replace, true);
887 arg->AddNoCacheHeader();
888 }
889
890 std::string repl;
891
892 if (fJSROOT.Length() > 0) {
893 repl = "=\"";
894 repl.append(fJSROOT.Data());
895 if (repl.back() != '/')
896 repl.append("/");
897 } else {
898 if (slash_cnt > 0) {
899 repl = "=\"";
900 while (slash_cnt-- > 0) repl.append("../");
901 repl.append("jsrootsys/");
902 }
903 }
904
905 if (!repl.empty()) {
906 arg->ReplaceAllinContent("=\"jsrootsys/", repl);
907 arg->ReplaceAllinContent("from './jsrootsys/", TString::Format("from '%s", repl.substr(2).c_str()).Data());
908 }
909 } else {
910 // new functionality creating importmap
911
912 std::string path_prefix, jsroot_prefix;
913 if (slash_cnt > 0) {
914 path_prefix = "";
915 while (slash_cnt-- > 0)
916 path_prefix.append("../");
917 } else {
918 path_prefix = "./";
919 }
920
921 if (!version.empty())
922 path_prefix.append(version + "/");
923
924 if (fJSROOT.Length() > 0) {
926 if (jsroot_prefix.back() != '/')
927 jsroot_prefix.append("/");
928 } else {
929 jsroot_prefix = path_prefix + "jsrootsys/";
930 }
931
932 static std::map<std::string, std::string> modules = {
933 {"jsroot", "main.mjs"}, {"jsroot/core", "core.mjs"},
934 {"jsroot/io", "io.mjs"}, {"jsroot/tree", "tree.mjs"},
935 {"jsroot/draw", "draw.mjs"}, {"jsroot/gui", "gui.mjs"},
936 {"jsroot/d3", "d3.mjs"}, {"jsroot/three", "three.mjs"}, {"jsroot/three_addons", "three_addons.mjs"},
937 {"jsroot/geom", "geom/TGeoPainter.mjs"}, {"jsroot/hpainter", "gui/HierarchyPainter.mjs"},
938 {"jsroot/webwindow", "webwindow.mjs"}
939 };
940
942 auto pspace = p;
943 while ((--pspace > 0) && (arg->fContent[pspace] == ' ') && (space_prefix.Length() < 20))
944 space_prefix.Append(" ");
945
946 bool first = true;
947 TString importmap = "<script type=\"importmap\">\n";
948 importmap += space_prefix + "{\n";
949 importmap += space_prefix + " \"imports\": ";
950 for (auto &entry : modules) {
951 importmap.Append(TString::Format("%s\n%s \"%s\": \"%smodules/%s\"", first ? "{" : ",", space_prefix.Data(), entry.first.c_str(), jsroot_prefix.c_str(), entry.second.c_str()));
952 first = false;
953 }
954 importmap.Append(TString::Format(",\n%s \"jsrootsys/\": \"%s\"", space_prefix.Data(), jsroot_prefix.c_str()));
955
956 for (auto &entry : fLocations)
957 if (entry.first != "jsrootsys/")
958 importmap.Append(TString::Format(",\n%s \"%s\": \"%s%s\"", space_prefix.Data(), entry.first.c_str(), path_prefix.c_str(), entry.first.c_str()));
959 importmap.Append("\n");
960 importmap.Append(space_prefix + " }\n");
961 importmap.Append(space_prefix + "}\n");
962 importmap.Append(space_prefix + "</script>\n");
963
964 arg->fContent.erase(p, place_holder.length());
965
966 arg->fContent.insert(p, importmap.Data());
967 }
968}
969
970////////////////////////////////////////////////////////////////////////////////
971/// Process single http request
972///
973/// Depending from requested path and filename different actions will be performed.
974/// In most cases information is provided by TRootSniffer class
975
976void THttpServer::ProcessRequest(std::shared_ptr<THttpCallArg> arg)
977{
978 if (fTerminated) {
979 arg->Set404();
980 return;
981 }
982
983 if ((arg->fFileName == "root.websocket") || (arg->fFileName == "root.longpoll")) {
984 ExecuteWS(arg);
985 return;
986 }
987
988 if (arg->fFileName.IsNull() || (arg->fFileName == "index.htm") || (arg->fFileName == "default.htm")) {
989
990 std::string version;
991
992 if (arg->fFileName == "default.htm") {
993
994 if (!IsWSOnly())
995 arg->fContent = ReadFileContent((fJSROOTSYS + "/files/online.htm").Data());
996
997 } else {
998 auto wsptr = FindWS(arg->GetPathName());
999
1000 auto handler = wsptr.get();
1001
1002 if (!handler)
1003 handler = dynamic_cast<THttpWSHandler *>(fSniffer->FindTObjectInHierarchy(arg->fPathName.Data()));
1004
1005 if (handler) {
1006
1007 arg->fContent = handler->GetDefaultPageContent().Data();
1008
1009 if (arg->fContent.find("file:") == 0) {
1010 const char *fname = arg->fContent.c_str() + 5;
1013 arg->fContent = ReadFileContent(resolve.Data());
1014 }
1015
1016 version = handler->GetCodeVersion();
1017
1018 handler->VerifyDefaultPageContent(arg);
1019 }
1020 }
1021
1022 if (arg->fContent.empty() && arg->fFileName.IsNull() && arg->fPathName.IsNull() && IsWSOnly()) {
1023 // Creating page with list of available widgets is disabled now for security reasons
1024 // Later one can provide functionality back only if explicitly desired by the user
1025 // BuildWSEntryPage();
1026
1027 arg->SetContent("refused");
1028 arg->Set404();
1029 }
1030
1031 if (arg->fContent.empty() && !IsWSOnly()) {
1032
1033 if (fDefaultPageCont.empty())
1035
1036 arg->fContent = fDefaultPageCont;
1037 }
1038
1039 if (arg->fContent.empty()) {
1040
1041 arg->Set404();
1042 } else if (!arg->Is404()) {
1043
1045
1046 const char *hjsontag = "\"$$$h.json$$$\"";
1047
1048 // add h.json caching
1049 if (arg->fContent.find(hjsontag) != std::string::npos) {
1052 const char *topname = fTopName.Data();
1053 if (arg->fTopName.Length() > 0)
1054 topname = arg->fTopName.Data();
1055 fSniffer->ScanHierarchy(topname, arg->fPathName.Data(), &store);
1056
1057 arg->ReplaceAllinContent(hjsontag, h_json.Data());
1058
1059 arg->AddNoCacheHeader();
1060
1061 if (arg->fQuery.Index("nozip") == kNPOS)
1062 arg->SetZipping();
1063 }
1064 arg->SetContentType("text/html");
1065 }
1066 return;
1067 }
1068
1069 if ((arg->fFileName == "draw.htm") && !IsWSOnly()) {
1070 if (fDrawPageCont.empty())
1072
1073 if (fDrawPageCont.empty()) {
1074 arg->Set404();
1075 } else {
1076 const char *rootjsontag = "\"$$$root.json$$$\"";
1077 const char *hjsontag = "\"$$$h.json$$$\"";
1078
1079 arg->fContent = fDrawPageCont;
1080
1081 ReplaceJSROOTLinks(arg);
1082
1083 if ((arg->fQuery.Index("no_h_json") == kNPOS) && (arg->fQuery.Index("webcanvas") == kNPOS) &&
1084 (arg->fContent.find(hjsontag) != std::string::npos)) {
1087 const char *topname = fTopName.Data();
1088 if (arg->fTopName.Length() > 0)
1089 topname = arg->fTopName.Data();
1090 fSniffer->ScanHierarchy(topname, arg->fPathName.Data(), &store, kTRUE);
1091
1092 arg->ReplaceAllinContent(hjsontag, h_json.Data());
1093 }
1094
1095 if ((arg->fQuery.Index("no_root_json") == kNPOS) && (arg->fQuery.Index("webcanvas") == kNPOS) &&
1096 (arg->fContent.find(rootjsontag) != std::string::npos)) {
1097 std::string str;
1098 if (fSniffer->Produce(arg->fPathName.Data(), "root.json", "compact=23", str))
1099 arg->ReplaceAllinContent(rootjsontag, str);
1100 }
1101 arg->AddNoCacheHeader();
1102 if (arg->fQuery.Index("nozip") == kNPOS)
1103 arg->SetZipping();
1104 arg->SetContentType("text/html");
1105 }
1106 return;
1107 }
1108
1109 if ((arg->fFileName == "favicon.ico") && arg->fPathName.IsNull()) {
1110 arg->SetFile(fJSROOTSYS + "/img/RootIcon.ico");
1111 return;
1112 }
1113
1115 if (IsFileRequested(arg->fFileName.Data(), filename)) {
1116 arg->SetFile(filename);
1117 return;
1118 }
1119
1120 // check if websocket handler may serve file request
1121 if (!arg->fPathName.IsNull() && !arg->fFileName.IsNull()) {
1122 TString wsname = arg->fPathName, fname;
1123 auto pos = wsname.First('/');
1124 if (pos == kNPOS) {
1125 wsname = arg->fPathName;
1126 } else {
1127 wsname = arg->fPathName(0, pos);
1128 fname = arg->fPathName(pos + 1, arg->fPathName.Length() - pos);
1129 fname.Append("/");
1130 }
1131
1132 fname.Append(arg->fFileName);
1133
1134 if (VerifyFilePath(fname.Data())) {
1135
1136 auto ws = FindWS(wsname.Data());
1137
1138 if (ws && ws->CanServeFiles()) {
1139 TString fdir = ws->GetDefaultPageContent();
1140 // only when file is specified, can take directory, append prefix and file name
1141 if (fdir.Index("file:") == 0) {
1142 fdir.Remove(0, 5);
1143 auto separ = fdir.Last('/');
1144 if (separ != kNPOS)
1145 fdir.Resize(separ + 1);
1146 else
1147 fdir = "./";
1148
1149 fdir.Append(fname);
1150 arg->SetFile(fdir);
1151 return;
1152 }
1153 }
1154 }
1155 }
1156
1157 filename = arg->fFileName;
1158
1160 if (filename.EndsWith(".gz")) {
1161 filename.Resize(filename.Length() - 3);
1162 iszip = kTRUE;
1163 }
1164
1165 if (IsWSOnly()) {
1166 if (arg->fContent.empty())
1167 arg->Set404();
1168 } else if ((filename == "h.xml") || (filename == "get.xml")) {
1169
1170 Bool_t compact = arg->fQuery.Index("compact") != kNPOS;
1171
1172 TString res;
1173
1174 res.Form("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
1175 if (!compact)
1176 res.Append("\n");
1177 res.Append("<root>");
1178 if (!compact)
1179 res.Append("\n");
1180 {
1181 TRootSnifferStoreXml store(res, compact);
1182
1183 const char *topname = fTopName.Data();
1184 if (arg->fTopName.Length() > 0)
1185 topname = arg->fTopName.Data();
1186 fSniffer->ScanHierarchy(topname, arg->fPathName.Data(), &store, filename == "get.xml");
1187 }
1188
1189 res.Append("</root>");
1190 if (!compact)
1191 res.Append("\n");
1192
1193 arg->SetContent(std::string(res.Data()));
1194
1195 arg->SetXml();
1196 } else if (filename == "h.json") {
1197 TString res;
1198 TRootSnifferStoreJson store(res, arg->fQuery.Index("compact") != kNPOS);
1199 const char *topname = fTopName.Data();
1200 if (arg->fTopName.Length() > 0)
1201 topname = arg->fTopName.Data();
1202 fSniffer->ScanHierarchy(topname, arg->fPathName.Data(), &store);
1203 arg->SetContent(std::string(res.Data()));
1204 arg->SetJson();
1205 } else if (fSniffer->Produce(arg->fPathName.Data(), filename.Data(), arg->fQuery.Data(), arg->fContent)) {
1206 // define content type base on extension
1207 arg->SetContentType(GetMimeType(filename.Data()));
1208 } else {
1209 // miss request, user may process
1210 MissedRequest(arg.get());
1211 }
1212
1213 if (arg->Is404())
1214 return;
1215
1216 if (iszip)
1217 arg->SetZipping(THttpCallArg::kZipAlways);
1218
1219 if (filename == "root.bin") {
1220 // only for binary data master version is important
1221 // it allows to detect if streamer info was modified
1222 const char *parname = fSniffer->IsStreamerInfoItem(arg->fPathName.Data()) ? "BVersion" : "MVersion";
1223 arg->AddHeader(parname, TString::Format("%u", (unsigned)fSniffer->GetStreamerInfoHash()).Data());
1224 }
1225
1226 // try to avoid caching on the browser
1227 arg->AddNoCacheHeader();
1228
1229 // potentially add cors headers
1230 if (IsCors())
1231 arg->AddHeader("Access-Control-Allow-Origin", GetCors());
1232 if (IsCorsCredentials()) {
1233 arg->AddHeader("Access-Control-Allow-Credentials", GetCorsCredentials());
1234 arg->AddHeader("Access-Control-Expose-Headers", "Content-Range, Content-Length, Date");
1235 arg->AddHeader("Access-Control-Allow-Methods", "GET, HEAD, OPTIONS");
1236 }
1237}
1238
1239////////////////////////////////////////////////////////////////////////////////
1240/// Register object in folders hierarchy
1241///
1242/// See TRootSniffer::RegisterObject() for more details
1243
1245{
1246 return fSniffer->RegisterObject(subfolder, obj);
1247}
1248
1249////////////////////////////////////////////////////////////////////////////////
1250/// Unregister object in folders hierarchy
1251///
1252/// See TRootSniffer::UnregisterObject() for more details
1253
1255{
1256 return fSniffer->UnregisterObject(obj);
1257}
1258
1259////////////////////////////////////////////////////////////////////////////////
1260/// Register WS handler to the THttpServer
1261///
1262/// Only such handler can be used in multi-threaded processing of websockets
1263
1264void THttpServer::RegisterWS(std::shared_ptr<THttpWSHandler> ws)
1265{
1266 std::lock_guard<std::mutex> grd(fWSMutex);
1267 fWSHandlers.emplace_back(ws);
1268}
1269
1270////////////////////////////////////////////////////////////////////////////////
1271/// Unregister WS handler to the THttpServer
1272
1273void THttpServer::UnregisterWS(std::shared_ptr<THttpWSHandler> ws)
1274{
1275 std::lock_guard<std::mutex> grd(fWSMutex);
1276 for (int n = (int)fWSHandlers.size(); n > 0; --n)
1277 if ((fWSHandlers[n - 1] == ws) || fWSHandlers[n - 1]->IsDisabled())
1278 fWSHandlers.erase(fWSHandlers.begin() + n - 1);
1279}
1280
1281////////////////////////////////////////////////////////////////////////////////
1282/// Search WS handler with given name
1283///
1284/// Handler must be registered with RegisterWS() method
1285
1286std::shared_ptr<THttpWSHandler> THttpServer::FindWS(const char *name)
1287{
1288 std::lock_guard<std::mutex> grd(fWSMutex);
1289 for (auto &ws : fWSHandlers) {
1290 if (strcmp(name, ws->GetName()) == 0)
1291 return ws;
1292 }
1293
1294 return nullptr;
1295}
1296
1297////////////////////////////////////////////////////////////////////////////////
1298/// Execute WS related operation
1299
1301{
1302 if (fTerminated) {
1303 arg->Set404();
1304 return kFALSE;
1305 }
1306
1307 auto wsptr = FindWS(arg->GetPathName());
1308
1309 auto handler = wsptr.get();
1310
1311 if (!handler && !external_thrd)
1312 handler = dynamic_cast<THttpWSHandler *>(fSniffer->FindTObjectInHierarchy(arg->fPathName.Data()));
1313
1314 if (external_thrd && (!handler || !handler->AllowMTProcess())) {
1315
1316 std::unique_lock<std::mutex> lk(fMutex);
1317 fArgs.push(arg);
1318 // and now wait until request is processed
1319 if (wait_process)
1320 arg->fCond.wait(lk);
1321
1322 return kTRUE;
1323 }
1324
1325 if (!handler) {
1326 arg->Set404();
1327 return kFALSE;
1328 }
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
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++) {
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
Signed long integer 4 bytes (long). Size depends on architecture.
Definition RtypesCore.h:68
constexpr Bool_t kFALSE
Definition RtypesCore.h:108
constexpr Ssiz_t kNPOS
The equivalent of std::string::npos for the ROOT class TString.
Definition RtypesCore.h:131
constexpr Bool_t kTRUE
Definition RtypesCore.h:107
ROOT::Detail::TRangeCast< T, true > TRangeDynCast
TRangeDynCast is an adapter class that allows the typed iteration through a TCollection.
R__EXTERN TEnv * gEnv
Definition TEnv.h:170
winID h TVirtualViewer3D TVirtualGLPainter p
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t Float_t Float_t Float_t Int_t Int_t UInt_t UInt_t Rectangle_t Int_t Int_t Window_t TString Int_t GCValues_t GetPrimarySelectionOwner GetDisplay GetScreen GetColormap GetNativeEvent const char const char dpyName wid window const char font_name cursor keysym reg const char only_if_exist regb h Point_t winding char text const char depth char const char Int_t count const char ColorStruct_t color const char filename
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize id
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void on
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void value
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t Float_t Float_t Float_t Int_t Int_t UInt_t UInt_t Rectangle_t Int_t Int_t Window_t TString Int_t GCValues_t GetPrimarySelectionOwner GetDisplay GetScreen GetColormap GetNativeEvent const char const char dpyName wid window const char font_name cursor keysym reg const char only_if_exist regb h Point_t winding char text const char depth char const char Int_t count const char ColorStruct_t color const char Pixmap_t Pixmap_t PictureAttributes_t attr const char char ret_data h unsigned char height h Atom_t Int_t ULong_t ULong_t unsigned char prop_list Atom_t Atom_t Atom_t Time_t UChar_t len
Option_t Option_t TPoint TPoint const char mode
char name[80]
Definition TGX11.cxx:110
Int_t gDebug
Global variable setting the debug level. Set to 0 to disable, increase it in steps of 1 to increase t...
Definition TROOT.cxx:627
#define gROOT
Definition TROOT.h:411
R__EXTERN TSystem * gSystem
Definition TSystem.h:572
const char * mime_type
Definition civetweb.c:8517
size_t ext_len
Definition civetweb.c:8516
#define free
Definition civetweb.c:1578
const char * extension
Definition civetweb.c:8515
#define malloc
Definition civetweb.c:1575
static const struct @140 builtin_mime_types[]
const_iterator end() const
THttpEngine implementation, based on civetweb embedded server.
Definition TCivetweb.h:21
TClass instances represent classes, structs and namespaces in the ROOT type system.
Definition TClass.h:84
static TClass * GetClass(const char *name, Bool_t load=kTRUE, Bool_t silent=kFALSE)
Static method returning pointer to TClass of the specified class name.
Definition TClass.cxx:2973
virtual Int_t GetValue(const char *name, Int_t dflt) const
Returns the integer value for a resource.
Definition TEnv.cxx:505
THttpEngine implementation, based on fastcgi package.
Definition TFastCgi.h:20
Contains arguments for single HTTP call.
void Set404()
mark reply as 404 error - page/request not exists or refused
Abstract class for implementing http protocol for THttpServer.
Definition THttpEngine.h:19
Emulation of websocket with long poll requests.
Online http server for arbitrary ROOT application.
Definition THttpServer.h:31
Bool_t IsReadOnly() const
returns read-only mode
Bool_t RegisterCommand(const char *cmdname, const char *method, const char *icon=nullptr)
Register command which can be executed from web interface.
TString fJSROOT
! location of external JSROOT files
Definition THttpServer.h:46
virtual void ProcessRequest(std::shared_ptr< THttpCallArg > arg)
Process single http request.
std::shared_ptr< THttpWSHandler > FindWS(const char *name)
Find web-socket handler with given name.
std::unique_ptr< TRootSniffer > fSniffer
! sniffer provides access to ROOT objects hierarchy
Definition THttpServer.h:36
void SetTimer(Long_t milliSec=100, Bool_t mode=kTRUE)
Create timer which will invoke ProcessRequests() function periodically.
virtual void ProcessBatchHolder(std::shared_ptr< THttpCallArg > &arg)
Process special http request for root_batch_holder.js script.
std::vector< std::shared_ptr< THttpWSHandler > > fWSHandlers
! list of WS handlers
Definition THttpServer.h:61
virtual ~THttpServer()
destructor
void SetTerminate()
set termination flag, no any further requests will be processed
virtual void MissedRequest(THttpCallArg *arg)
Method called when THttpServer cannot process request.
Bool_t fOwnThread
! true when specialized thread allocated for processing requests
Definition THttpServer.h:40
void SetSniffer(TRootSniffer *sniff)
Set TRootSniffer to the server.
Bool_t IsFileRequested(const char *uri, TString &res) const
Check if file is requested, thread safe.
void SetReadOnly(Bool_t readonly=kTRUE)
Set read-only mode for the server (default on)
const char * GetItemField(const char *fullname, const char *name)
Get item field from sniffer.
const char * GetCors() const
Returns specified CORS domain.
std::thread fThrd
! own thread
Definition THttpServer.h:41
void StopServerThread()
Stop server thread.
Int_t ProcessRequests()
Process submitted requests, must be called from appropriate thread.
Bool_t ExecuteWS(std::shared_ptr< THttpCallArg > &arg, Bool_t external_thrd=kFALSE, Bool_t wait_process=kFALSE)
Execute WS request.
void RegisterWS(std::shared_ptr< THttpWSHandler > ws)
Register WS handler.
Long_t fProcessingThrdId
! id of the thread where events are recently processing
Definition THttpServer.h:39
TString fTopName
! name of top folder, default - "ROOT"
Definition THttpServer.h:45
void SetDrawPage(const std::string &filename="")
Set drawing HTML page.
Bool_t CreateItem(const char *fullname, const char *title)
Create item in sniffer.
Bool_t ExecuteHttp(std::shared_ptr< THttpCallArg > arg)
Execute HTTP request.
Bool_t Hide(const char *fullname, Bool_t hide=kTRUE)
Hides folder or element from web gui.
Bool_t IsCorsCredentials() const
Returns kTRUE if Access-Control-Allow-Credentials header should be used.
void AddLocation(const char *prefix, const char *path)
Add files location, which could be used in the server.
std::map< std::string, std::string > fLocations
! list of local directories, which could be accessed via server
Definition THttpServer.h:48
Bool_t SubmitHttp(std::shared_ptr< THttpCallArg > arg, Bool_t can_run_immediately=kFALSE)
Submit HTTP request.
Long_t fMainThrdId
! id of the thread for processing requests
Definition THttpServer.h:38
TString fJSROOTSYS
! location of local JSROOT files
Definition THttpServer.h:44
std::unique_ptr< THttpTimer > fTimer
! timer used to access main thread
Definition THttpServer.h:35
Bool_t fWSOnly
! when true, handle only websockets / longpoll engine
Definition THttpServer.h:42
Bool_t Register(const char *subfolder, TObject *obj)
Register object in subfolder.
TList fEngines
! engines which runs http server
Definition THttpServer.h:34
void SetCors(const std::string &domain="*")
Enable CORS header to ProcessRequests() responses Specified location (typically "*") add as "Access-C...
Bool_t IsCors() const
Returns kTRUE if CORS was configured.
const char * GetCorsCredentials() const
Returns specified CORS credentials value - if any.
std::queue< std::shared_ptr< THttpCallArg > > fArgs
! submitted arguments
Definition THttpServer.h:58
void SetDefaultPage(const std::string &filename="")
Set default HTML page.
THttpServer(const THttpServer &)=delete
static char * ReadFileContent(const char *filename, Int_t &len)
Reads content of file from the disk.
void CreateServerThread()
Creates special thread to process all requests, directed to http server.
std::string fDrawPageCont
! content of draw html page
Definition THttpServer.h:53
Bool_t Unregister(TObject *obj)
Unregister object.
void SetWSOnly(Bool_t on=kTRUE)
Set websocket-only mode.
std::string BuildWSEntryPage()
Create summary page with active WS handlers.
Bool_t IsWSOnly() const
returns true if only websockets are handled by the server
std::mutex fWSMutex
! mutex to protect WS handler lists
Definition THttpServer.h:60
virtual Bool_t CreateEngine(const char *engine)
Factory method to create different http engines.
Bool_t SetIcon(const char *fullname, const char *iconname)
Set name of icon, used in browser together with the item.
std::string fDrawPage
! file name for drawing of single element
Definition THttpServer.h:52
std::string fDefaultPageCont
! content of default html page
Definition THttpServer.h:51
static Bool_t VerifyFilePath(const char *fname)
Checked that filename does not contains relative path below current directory.
Bool_t IsAllowPostObject() const
Returns true if server accept object content in POST reequests.
Bool_t SetItemField(const char *fullname, const char *name, const char *value)
Set item field in sniffer.
void SetAllowPostObject(Bool_t allow_post_obj)
Set flag to allow receive and desereilize objects in POST requests.
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.
void Timeout() override
timeout handler used to process http requests in main ROOT thread
THttpServer & fServer
THttpTimer(Long_t milliSec, Bool_t mode, THttpServer &serv)
!< server processing requests
Class for user-side handling of websocket with THttpServer.
void Add(TObject *obj) override
Definition TList.h:81
void Delete(Option_t *option="") override
Remove all objects from the list AND delete all heap based objects.
Definition TList.cxx:467
The TNamed class is the base class for all named ROOT classes.
Definition TNamed.h:29
An array of TObjects.
Definition TObjArray.h:31
Mother of all ROOT objects.
Definition TObject.h:41
virtual void Warning(const char *method, const char *msgfmt,...) const
Issue warning message.
Definition TObject.cxx:1057
virtual void Error(const char *method, const char *msgfmt,...) const
Issue error message.
Definition TObject.cxx:1071
static const TString & GetRootSys()
Get the rootsys directory in the installation. Static utility function.
Definition TROOT.cxx:2988
static const TString & GetDataDir()
Get the data directory in the installation. Static utility function.
Definition TROOT.cxx:3217
Storage of hierarchy scan in TRootSniffer in JSON format.
Storage of hierarchy scan in TRootSniffer in XML format.
Sniffer of ROOT objects, data provider for THttpServer.
void SetReadOnly(Bool_t on=kTRUE)
When readonly on (default), sniffer is not allowed to change ROOT structures For instance,...
void SetScanGlobalDir(Bool_t on=kTRUE)
When enabled (default), sniffer scans gROOT for files, canvases, histograms.
Basic string class.
Definition TString.h:138
Ssiz_t Length() const
Definition TString.h:425
const char * Data() const
Definition TString.h:384
void Resize(Ssiz_t n)
Resize the string. Truncate or add blanks as necessary.
Definition TString.cxx:1159
Ssiz_t Last(char c) const
Find last occurrence of a character c.
Definition TString.cxx:938
TObjArray * Tokenize(const TString &delim) const
This function is used to isolate sequential tokens in a TString.
Definition TString.cxx:2270
TString & Remove(Ssiz_t pos)
Definition TString.h:694
TString & Append(const char *cs)
Definition TString.h:581
static TString Format(const char *fmt,...)
Static method which formats a string using a printf style format descriptor and return a TString.
Definition TString.cxx:2384
void Form(const char *fmt,...)
Formats a string using a printf style format descriptor.
Definition TString.cxx:2362
Ssiz_t Index(const char *pat, Ssiz_t i=0, ECaseCompare cmp=kExact) const
Definition TString.h:660
virtual Bool_t ExpandPathName(TString &path)
Expand a pathname getting rid of special shell characters like ~.
Definition TSystem.cxx:1289
virtual const char * Getenv(const char *env)
Get environment variable.
Definition TSystem.cxx:1680
static Long_t SelfId()
Static method returning the id for the current thread.
Definition TThread.cxx:552
Handles synchronous and a-synchronous timer events.
Definition TTimer.h:51
This class represents a WWW compatible URL.
Definition TUrl.h:33
const Int_t n
Definition legend1.C:16