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