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