Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
RWebWindowsManager.cxx
Go to the documentation of this file.
1// Author: Sergey Linev <s.linev@gsi.de>
2// Date: 2017-10-16
3
4/*************************************************************************
5 * Copyright (C) 1995-2019, 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
13
14#include <ROOT/RLogger.hxx>
17
19
20#include "THttpServer.h"
21
22#include "TSystem.h"
23#include "TString.h"
24#include "TApplication.h"
25#include "TTimer.h"
26#include "TRandom3.h"
27#include "TError.h"
28#include "TROOT.h"
29#include "TEnv.h"
30#include "TExec.h"
31#include "TSocket.h"
32#include "TThread.h"
33#include "TObjArray.h"
34
35#include <thread>
36#include <chrono>
37#include <iostream>
38
39using namespace ROOT;
40
41///////////////////////////////////////////////////////////////
42/// Parse boolean gEnv variable which should be "yes" or "no"
43/// \return 1 for true or 0 for false
44/// Returns \param dflt if result is not defined
45/// \param name name of the env variable
46
47int RWebWindowWSHandler::GetBoolEnv(const std::string &name, int dflt)
48{
49 const char *undef = "<undefined>";
50 const char *value = gEnv->GetValue(name.c_str(), undef);
51 if (!value) return dflt;
52 std::string svalue = value;
53 if (svalue == undef) return dflt;
54
55 if (svalue == "yes") return 1;
56 if (svalue == "no") return 0;
57
58 R__LOG_ERROR(WebGUILog()) << name << " has to be yes or no";
59 return dflt;
60}
61
62
63/** \class ROOT::RWebWindowsManager
64\ingroup webdisplay
65
66Central instance to create and show web-based windows like Canvas or FitPanel.
67
68Manager responsible to creating THttpServer instance, which is used for RWebWindow's
69communication with clients.
70
71Method RWebWindows::Show() used to show window in specified location.
72*/
73
74//////////////////////////////////////////////////////////////////////////////////////////
75/// Returns default window manager
76/// Used to display all standard ROOT elements like TCanvas or TFitPanel
77
78std::shared_ptr<RWebWindowsManager> &RWebWindowsManager::Instance()
79{
80 static std::shared_ptr<RWebWindowsManager> sInstance = std::make_shared<RWebWindowsManager>();
81 return sInstance;
82}
83
84//////////////////////////////////////////////////////////////////
85/// This thread id used to identify main application thread, where ROOT event processing runs
86/// To inject code in that thread, one should use TTimer (like THttpServer does)
87/// In other threads special run methods have to be invoked like RWebWindow::Run()
88///
89/// TODO: probably detection of main thread should be delivered by central ROOT instances like gApplication or gROOT
90/// Main thread can only make sense if special processing runs there and one can inject own functionality there
91
92static std::thread::id gWebWinMainThrd = std::this_thread::get_id();
93static bool gWebWinMainThrdSet = true;
94static bool gWebWinLoopbackMode = true;
95static bool gWebWinUseSessionKey = true;
96
97//////////////////////////////////////////////////////////////////////////////////////////
98/// Returns true when called from main process
99/// Main process recognized at the moment when library is loaded
100/// It supposed to be a thread where gApplication->Run() will be called
101/// If application runs in separate thread, one have to use AssignMainThrd() method
102/// to let RWebWindowsManager correctly recognize such situation
103
105{
106 return gWebWinMainThrdSet && (std::this_thread::get_id() == gWebWinMainThrd);
107}
108
109//////////////////////////////////////////////////////////////////////////////////////////
110/// Re-assigns main thread id
111/// Normally main thread id recognized at the moment when library is loaded
112/// It supposed to be a thread where gApplication->Run() will be called
113/// If application runs in separate thread, one have to call this method
114/// to let RWebWindowsManager correctly recognize such situation
115
117{
118 gWebWinMainThrdSet = true;
119 gWebWinMainThrd = std::this_thread::get_id();
120}
121
122
123//////////////////////////////////////////////////////////////////////////////////////////
124/// Set loopback mode for THttpServer used for web widgets
125/// By default is on. Only local communication via localhost address is possible
126/// Disable it only if really necessary - it may open unauthorized access to your application from external nodes!!
127
129{
131 bool print_warning = RWebWindowWSHandler::GetBoolEnv("WebGui.Warning", 1) == 1;
132 if (!on) {
133 if (print_warning) {
134 printf("\nWARNING!\n");
135 printf("Disabling loopback mode may leads to security problem.\n");
136 printf("See https://root.cern/about/security/ for more information.\n\n");
137 }
139 if (print_warning) {
140 printf("Enforce session key to safely work on public network.\n");
141 printf("One may call RWebWindowsManager::SetUseSessionKey(false); to disable it.\n");
142 }
144 }
145 }
146}
147
148//////////////////////////////////////////////////////////////////////////////////////////
149/// Returns true if loopback mode used by THttpServer for web widgets
150
155
156//////////////////////////////////////////////////////////////////////////////////////////
157/// Enable or disable usage of session key (default on)
158/// If enabled, secrete session key used to calculate hash sum of each packet send to or from server
159/// This protects ROOT http server from anauthorized usage
160
165
166//////////////////////////////////////////////////////////////////////////////////////////
167/// Enable or disable usage of connection key (default on)
168/// If enabled, each connection (and reconnection) to widget requires unique key
169/// Connection key used together with session key to calculate hash sum of each packet send to or from server
170/// This protects ROOT http server from anauthorized usage
171
173{
174 gEnv->SetValue("WebGui.OnetimeKey", on ? "yes" : "no");
175}
176
177//////////////////////////////////////////////////////////////////////////////////////////
178/// Enable or disable single connection mode (default on)
179/// If enabled, one connection only with any web widget is possible
180/// Any attempt to establish more connections will fail
181/// if this mode is disabled some widgets like geom viewer or web canvas will be able to
182/// to serve several clients - only when they are connected with required authentication keys
183
185{
186 gEnv->SetValue("WebGui.SingleConnMode", on ? "yes" : "no");
187}
188
189//////////////////////////////////////////////////////////////////////////////////////////
190/// Configure server location which can be used for loading of custom scripts or files
191/// When THttpServer instance of RWebWindowsManager will be created,
192/// THttpServer::AddLocation() method with correspondent arguments will be invoked.
193
194void RWebWindowsManager::AddServerLocation(const std::string &server_prefix, const std::string &files_path)
195{
196 if (server_prefix.empty() || files_path.empty())
197 return;
198 auto loc = GetServerLocations();
199 std::string prefix = server_prefix;
200 if (prefix.back() != '/')
201 prefix.append("/");
202 loc[prefix] = files_path;
203
204 // now convert back to plain string
205 TString cfg;
206 for (auto &entry : loc) {
207 if (cfg.Length() > 0)
208 cfg.Append(";");
209 cfg.Append(entry.first.c_str());
210 cfg.Append(":");
211 cfg.Append(entry.second.c_str());
212 }
213
214 gEnv->SetValue("WebGui.ServerLocations", cfg);
215
216 auto serv = Instance()->GetServer();
217 if (serv)
218 serv->AddLocation(prefix.c_str(), files_path.c_str());
219}
220
221//////////////////////////////////////////////////////////////////////////////////////////
222/// Returns server locations as <std::string, std::string>
223/// Key is location name (with slash at the end) and value is file path
224
225std::map<std::string, std::string> RWebWindowsManager::GetServerLocations()
226{
227 std::map<std::string, std::string> res;
228
229 TString cfg = gEnv->GetValue("WebGui.ServerLocations","");
230 auto arr = cfg.Tokenize(";");
231 if (arr) {
232 TIter next(arr);
233 while(auto obj = next()) {
234 TString arg = obj->GetName();
235
236 auto p = arg.First(":");
237 if (p == kNPOS) continue;
238
239 TString prefix = arg(0, p);
240 if (!prefix.EndsWith("/"))
241 prefix.Append("/");
242 TString path = arg(p+1, arg.Length() - p);
243
244 res[prefix.Data()] = path.Data();
245 }
246 delete arr;
247 }
248 return res;
249}
250
251//////////////////////////////////////////////////////////////////////////////////////////
252/// Clear all server locations
253/// Does not change configuration of already running HTTP server
254
256{
257 gEnv->SetValue("WebGui.ServerLocations", "");
258}
259
260//////////////////////////////////////////////////////////////////////////////////////////
261/// Static method to generate cryptographic key
262/// Parameter keylen defines length of cryptographic key in bytes
263/// Output string will be hex formatted and includes "-" separator after every 4 bytes
264/// Example for 16 bytes: "fca45856-41bee066-ff74cc96-9154d405"
265
267{
268 std::vector<unsigned char> buf(keylen, 0);
269 auto res = gSystem->GetCryptoRandom(buf.data(), keylen);
270
271 R__ASSERT(res == keylen && "Error in gSystem->GetCryptoRandom");
272
273 std::string key;
274 for (int n = 0; n < keylen; n++) {
275 if ((n > 0) && (n % 4 == 0))
276 key.append("-");
277 auto t = TString::Itoa(buf[n], 16);
278 if (t.Length() == 1)
279 key.append("0");
280 key.append(t.Data());
281 }
282 return key;
283}
284
285//////////////////////////////////////////////////////////////////////////////////////////
286/// window manager constructor
287/// Required here for correct usage of unique_ptr<THttpServer>
288
298
299//////////////////////////////////////////////////////////////////////////////////////////
300/// window manager destructor
301/// Required here for correct usage of unique_ptr<THttpServer>
302
304{
305 if (gApplication && fServer && !fServer->IsTerminated()) {
306 gApplication->Disconnect("Terminate(Int_t)", fServer.get(), "SetTerminate()");
307 fServer->SetTerminate();
308 }
309}
310
311//////////////////////////////////////////////////////////////////////////////////////////
312/// If ROOT_LISTENER_SOCKET variable is configured,
313/// message will be sent to that unix socket
314
316{
317#ifdef R__WIN32
318 (void) msg;
319 return false;
320
321#else
322
323 const char *fname = gSystem->Getenv("ROOT_LISTENER_SOCKET");
324 if (!fname || !*fname)
325 return false;
326
327 TSocket s(fname);
328 if (!s.IsValid()) {
329 R__LOG_ERROR(WebGUILog()) << "Problem with open listener socket " << fname << ", check ROOT_LISTENER_SOCKET environment variable";
330 return false;
331 }
332
333 int res = s.SendRaw(msg.c_str(), msg.length());
334
335 s.Close();
336
337 if (res > 0) {
338 // workaround to let handle socket by system outside ROOT process
340 gSystem->Sleep(10);
341 }
342
343 return res > 0;
344#endif
345}
346
347
348//////////////////////////////////////////////////////////////////////////////////////////
349/// Creates http server, if required - with real http engine (civetweb)
350/// One could configure concrete HTTP port, which should be used for the server,
351/// provide following entry in rootrc file:
352///
353/// WebGui.HttpPort: 8088
354///
355/// or specify range of http ports, which can be used:
356///
357/// WebGui.HttpPortMin: 8800
358/// WebGui.HttpPortMax: 9800
359///
360/// By default range [8800..9800] is used
361///
362/// One also can bind HTTP server socket to loopback address,
363/// In that case only connection from localhost will be available:
364///
365/// WebGui.HttpLoopback: yes
366///
367/// Or one could specify hostname which should be used for binding of server socket
368///
369/// WebGui.HttpBind: hostname | ipaddress
370///
371/// To use secured protocol, following parameter should be specified
372///
373/// WebGui.UseHttps: yes
374/// WebGui.ServerCert: sertificate_filename.pem
375///
376/// Alternatively, one can specify unix socket to handle requests:
377///
378/// WebGui.UnixSocket: /path/to/unix/socket
379/// WebGui.UnixSocketMode: 0700
380///
381/// Typically one used unix sockets together with server mode like `root --web=server:/tmp/root.socket` and
382/// then redirect it via ssh tunnel (e.g. using `rootssh`) to client node
383///
384/// All incoming requests processed in THttpServer in timer handler with 10 ms timeout.
385/// One may decrease value to improve latency or increase value to minimize CPU load
386///
387/// WebGui.HttpTimer: 10
388///
389/// To processing incoming http requests and websockets, THttpServer allocate 10 threads
390/// One have to increase this number if more simultaneous connections are expected:
391///
392/// WebGui.HttpThrds: 10
393///
394/// One also can configure usage of special thread of processing of http server requests
395///
396/// WebGui.HttpThrd: no
397///
398/// Extra threads can be used to send data to different clients via websocket (default no)
399///
400/// WebGui.SenderThrds: no
401///
402/// If required, one could change websocket timeouts (default is 10000 ms)
403///
404/// WebGui.HttpWSTmout: 10000
405///
406/// By default, THttpServer created in restricted mode which only allows websocket handlers
407/// and processes only very few other related http requests. For security reasons such mode
408/// should be always enabled. Only if it is really necessary to process all other kinds
409/// of HTTP requests, one could specify no for following parameter (default yes):
410///
411/// WebGui.WSOnly: yes
412///
413/// In some applications one may need to force longpoll websocket emulations from the beginning,
414/// for instance when clients connected via proxys. Although JSROOT should automatically fallback
415/// to longpoll engine, one can configure this directly (default no)
416///
417/// WebGui.WSLongpoll: no
418///
419/// Following parameter controls browser max-age caching parameter for files (default 3600)
420/// When 0 is specified, browser cache will be disabled
421///
422/// WebGui.HttpMaxAge: 3600
423///
424/// Also one can provide extra URL options for, see TCivetweb::Create for list of supported options
425///
426/// WebGui.HttpExtraArgs: winsymlinks=no
427///
428/// One also can configure usage of FastCGI server for web windows:
429///
430/// WebGui.FastCgiPort: 4000
431/// WebGui.FastCgiThreads: 10
432///
433/// To be able start web browser for such windows, one can provide real URL of the
434/// web server which will connect with that FastCGI instance:
435///
436/// WebGui.FastCgiServer: https://your_apache_server.com/root_cgi_path
437///
438/// For some custom applications one requires to load JavaScript modules or other files.
439/// For such applications one may require to load files from other locations which can be configured
440/// with AddServerLocation() method or directly via:
441///
442/// WebGui.ServerLocations: location1:/file/path/to/location1;location2:/file/path/to/location2
443
445{
446 if (gROOT->GetWebDisplay() == "off")
447 return false;
448
449 // explicitly protect server creation
450 std::lock_guard<std::recursive_mutex> grd(fMutex);
451
452 if (!fServer) {
453
454 fServer = std::make_unique<THttpServer>("basic_sniffer");
455
457 fUseHttpThrd = false;
458 } else {
459 auto serv_thrd = RWebWindowWSHandler::GetBoolEnv("WebGui.HttpThrd");
460 if (serv_thrd != -1)
461 fUseHttpThrd = serv_thrd != 0;
462 }
463
464 auto send_thrds = RWebWindowWSHandler::GetBoolEnv("WebGui.SenderThrds");
465 if (send_thrds != -1)
467
468 if (IsUseHttpThread())
469 fServer->CreateServerThread();
470
471 if (gApplication)
472 gApplication->Connect("Terminate(Int_t)", "THttpServer", fServer.get(), "SetTerminate()");
473
474 fServer->SetWSOnly(RWebWindowWSHandler::GetBoolEnv("WebGui.WSOnly", 1) != 0);
475
476 // this is location where all ROOT UI5 sources are collected
477 // normally it is $ROOTSYS/ui5 or <prefix>/ui5 location
478 TString ui5dir = gSystem->Getenv("ROOTUI5SYS");
479 if (ui5dir.Length() == 0)
480 ui5dir = gEnv->GetValue("WebGui.RootUi5Path","");
481
482 if (ui5dir.Length() == 0)
483 ui5dir.Form("%s/ui5", TROOT::GetDataDir().Data());
484
486 R__LOG_ERROR(WebGUILog()) << "Path to ROOT ui5 sources " << ui5dir << " not found, set ROOTUI5SYS correctly";
487 ui5dir = ".";
488 }
489
490 fServer->AddLocation("rootui5sys/", ui5dir.Data());
491
492 auto loc = GetServerLocations();
493 for (auto &entry : loc)
494 fServer->AddLocation(entry.first.c_str(), entry.second.c_str());
495 }
496
497 if (!with_http || fServer->IsAnyEngine())
498 return true;
499
500 int http_port = gEnv->GetValue("WebGui.HttpPort", 0);
501 int http_min = gEnv->GetValue("WebGui.HttpPortMin", 8800);
502 int http_max = gEnv->GetValue("WebGui.HttpPortMax", 9800);
503 int http_timer = gEnv->GetValue("WebGui.HttpTimer", 10);
504 int http_thrds = gEnv->GetValue("WebGui.HttpThreads", 10);
505 int http_wstmout = gEnv->GetValue("WebGui.HttpWSTmout", 10000);
506 int http_maxage = gEnv->GetValue("WebGui.HttpMaxAge", -1);
507 const char *extra_args = gEnv->GetValue("WebGui.HttpExtraArgs", "");
508 int fcgi_port = gEnv->GetValue("WebGui.FastCgiPort", 0);
509 int fcgi_thrds = gEnv->GetValue("WebGui.FastCgiThreads", 10);
510 const char *fcgi_serv = gEnv->GetValue("WebGui.FastCgiServer", "");
511 fLaunchTmout = gEnv->GetValue("WebGui.LaunchTmout", 30.);
512 fReconnectTmout = gEnv->GetValue("WebGui.ReconnectTmout", 15.);
514 const char *http_bind = gEnv->GetValue("WebGui.HttpBind", "");
515 bool use_secure = RWebWindowWSHandler::GetBoolEnv("WebGui.UseHttps", 0) == 1;
516 const char *ssl_cert = gEnv->GetValue("WebGui.ServerCert", "rootserver.pem");
517
518 const char *unix_socket = gSystem->Getenv("ROOT_WEBGUI_SOCKET");
519 if (!unix_socket || !*unix_socket)
520 unix_socket = gEnv->GetValue("WebGui.UnixSocket", "");
521 const char *unix_socket_mode = gEnv->GetValue("WebGui.UnixSocketMode", "0700");
523
524 if (use_unix_socket)
525 fcgi_port = http_port = -1;
526
527 if (assign_loopback)
528 fcgi_port = -1;
529
530 int ntry = 100;
531
532 if ((http_port < 0) && (fcgi_port <= 0) && !use_unix_socket) {
533 R__LOG_ERROR(WebGUILog()) << "Not allowed to create HTTP server, check WebGui.HttpPort variable";
534 return false;
535 }
536
537 if ((http_timer > 0) && !IsUseHttpThread())
538 fServer->SetTimer(http_timer);
539
541
542 if (http_port < 0) {
543 ntry = 0;
544 } else {
545 if (http_port == 0)
546 rnd.SetSeed(0);
547 if (http_max - http_min < ntry)
549 }
550
551 if (fcgi_port > 0)
552 ntry++;
553
554 if (use_unix_socket)
555 ntry++;
556
557 while (ntry-- >= 0) {
558 if ((http_port == 0) && (fcgi_port <= 0) && !use_unix_socket) {
559 if ((http_min <= 0) || (http_max <= http_min)) {
560 R__LOG_ERROR(WebGUILog()) << "Wrong HTTP range configuration, check WebGui.HttpPortMin/Max variables";
561 return false;
562 }
563
564 http_port = (int)(http_min + (http_max - http_min) * rnd.Rndm(1));
565 }
566
567 TString engine, url;
568 if (fcgi_port > 0) {
569 engine.Form("fastcgi:%d?thrds=%d", fcgi_port, fcgi_thrds);
570 if (!fServer->CreateEngine(engine))
571 return false;
572 if (fcgi_serv && (strlen(fcgi_serv) > 0))
574 if (http_port < 0)
575 return true;
576 fcgi_port = 0;
577 } else {
578 if (use_unix_socket) {
579 engine.Form("socket:%s?socket_mode=%s&", unix_socket, unix_socket_mode);
580 } else {
581 url = use_secure ? "https://" : "http://";
582 engine.Form("%s:%d?", (use_secure ? "https" : "http"), http_port);
583 if (assign_loopback) {
584 engine.Append("loopback&");
585 url.Append("localhost");
586 } else if (http_bind && (strlen(http_bind) > 0)) {
587 engine.Append(TString::Format("bind=%s&", http_bind));
588 url.Append(http_bind);
589 } else {
590 url.Append("localhost");
591 }
592 }
593
594 engine.Append(TString::Format("webgui&top=remote&thrds=%d&websocket_timeout=%d", http_thrds, http_wstmout));
595
596 if (http_maxage >= 0)
597 engine.Append(TString::Format("&max_age=%d", http_maxage));
598
599 if (use_secure && !strchr(ssl_cert,'&')) {
600 engine.Append("&ssl_cert=");
601 engine.Append(ssl_cert);
602 }
603
605 engine.Append("&");
606 engine.Append(extra_args);
607 }
608
609 if (fServer->CreateEngine(engine)) {
610 if (use_unix_socket) {
611 fAddr = "socket://"; // fictional socket URL
612 fAddr.append(unix_socket);
613 // InformListener(std::string("socket:") + unix_socket + "\n");
614 } else if (http_port > 0) {
615 fAddr = url.Data();
616 fAddr.append(":");
617 fAddr.append(std::to_string(http_port));
618 // InformListener(std::string("http:") + std::to_string(http_port) + "\n");
619 }
620 return true;
621 }
622 use_unix_socket = false;
623 http_port = 0;
624 }
625 }
626
627 return false;
628}
629
630//////////////////////////////////////////////////////////////////////////////////////////
631/// Creates new window
632/// To show window, RWebWindow::Show() have to be called
633
634std::shared_ptr<RWebWindow> RWebWindowsManager::CreateWindow()
635{
636 // we book manager mutex for a longer operation, locked again in server creation
637 std::lock_guard<std::recursive_mutex> grd(fMutex);
638
639 if (!CreateServer()) {
640 R__LOG_ERROR(WebGUILog()) << "Cannot create server when creating window";
641 return nullptr;
642 }
643
644 std::shared_ptr<RWebWindow> win = std::make_shared<RWebWindow>();
645
646 if (!win) {
647 R__LOG_ERROR(WebGUILog()) << "Fail to create RWebWindow instance";
648 return nullptr;
649 }
650
651 double dflt_tmout = gEnv->GetValue("WebGui.OperationTmout", 50.);
652
653 auto wshandler = win->CreateWSHandler(Instance(), ++fIdCnt, dflt_tmout);
654
655 if (RWebWindowWSHandler::GetBoolEnv("WebGui.RecordData") > 0) {
656 std::string fname, prefix;
657 if (fIdCnt > 1) {
658 prefix = std::string("f") + std::to_string(fIdCnt) + "_";
659 fname = std::string("protcol") + std::to_string(fIdCnt) + ".json";
660 } else {
661 fname = "protocol.json";
662 }
663 win->RecordData(fname, prefix);
664 }
665
666 int queuelen = gEnv->GetValue("WebGui.QueueLength", 10);
667 if (queuelen > 0)
668 win->SetMaxQueueLength(queuelen);
669
671 // special mode when window communication performed in THttpServer::ProcessRequests
672 // used only with python which create special thread - but is has to be ignored!!!
673 // therefore use main thread id to detect callbacks which are invoked only from that main thread
674 win->fUseProcessEvents = true;
675 win->fCallbacksThrdIdSet = gWebWinMainThrdSet;
676 win->fCallbacksThrdId = gWebWinMainThrd;
677 } else if (IsUseHttpThread())
678 win->UseServerThreads();
679
680 const char *token = gEnv->GetValue("WebGui.ConnToken", "");
681 if (token && *token)
682 win->SetConnToken(token);
683
684 fServer->RegisterWS(wshandler);
685
686 return win;
687}
688
689//////////////////////////////////////////////////////////////////////////////////////////
690/// Release all references to specified window
691/// Called from RWebWindow destructor
692
694{
695 if (win.fWSHandler)
696 fServer->UnregisterWS(win.fWSHandler);
697
698 if (fDeleteCallback)
700}
701
702//////////////////////////////////////////////////////////////////////////
703/// Provide URL address to access specified window from inside or from remote
704
706{
707 if (!fServer) {
708 R__LOG_ERROR(WebGUILog()) << "Server instance not exists when requesting window URL";
709 return "";
710 }
711
712 std::string addr = "/";
713 addr.append(win.fWSHandler->GetName());
714 addr.append("/");
715
716 bool qmark = false;
717
718 std::string key;
719
720 if (win.IsRequireAuthKey() || produced_key) {
721 key = win.GenerateKey();
722 R__ASSERT(!key.empty());
723 addr.append("?key=");
724 addr.append(key);
725 qmark = true;
726 std::unique_ptr<ROOT::RWebDisplayHandle> dummy;
727 win.AddDisplayHandle(false, key, dummy);
728 }
729
730 auto token = win.GetConnToken();
731 if (!token.empty()) {
732 addr.append(qmark ? "&" : "?");
733 addr.append("token=");
734 addr.append(token);
735 }
736
737 if (remote) {
738 if (!CreateServer(true) || fAddr.empty()) {
739 R__LOG_ERROR(WebGUILog()) << "Fail to start real HTTP server when requesting URL";
740 if (!key.empty())
741 win.RemoveKey(key);
742 return "";
743 }
744
745 addr = fAddr + addr;
746
747 if (!key.empty() && !fSessionKey.empty() && fUseSessionKey && win.IsRequireAuthKey())
748 addr += "#"s + fSessionKey;
749 }
750
751 if (produced_key)
752 *produced_key = key;
753
754 return addr;
755}
756
757///////////////////////////////////////////////////////////////////////////////////////////////////
758/// Show web window in specified location.
759///
760/// \param[inout] win web window by reference
761/// \param user_args specifies where and how display web window
762///
763/// As display args one can use string like "firefox" or "chrome" - these are two main supported web browsers.
764/// See RWebDisplayArgs::SetBrowserKind() for all available options. Default value for the browser can be configured
765/// when starting root with --web argument like: "root --web=chrome". When root started in web server mode "root --web=server",
766/// no web browser will be started - just the URL will be printed, which can be opened in any running web browser.
767/// Also configurable via ROOT_WEBDISPLAY environment variable taking the same options.
768///
769/// If allowed, same window can be displayed several times (like for RCanvas or TCanvas)
770///
771/// Following parameters can be configured in rootrc file:
772///
773/// WebGui.Display: kind of display, identical to --web option and ROOT_WEBDISPLAY environment variable documented above
774/// WebGui.OnetimeKey: if configured requires unique key every time window is connected (default yes)
775/// WebGui.SingleConnMode: if configured the only connection and the only user of any widget is possible (default yes)
776/// WebGui.Chrome: full path to Google Chrome executable
777/// WebGui.ChromeBatch: command to start chrome in batch, used for image production, like "$prog --headless --disable-gpu $geometry $url"
778/// WebGui.ChromeHeadless: command to start chrome in headless mode, like "fork: --headless --disable-gpu $geometry $url"
779/// WebGui.ChromeInteractive: command to start chrome in interactive mode, like "$prog $geometry --app=\'$url\' &"
780/// WebGui.Firefox: full path to Mozilla Firefox executable
781/// WebGui.FirefoxHeadless: command to start Firefox in headless mode, like "fork:--headless --private-window --no-remote $profile $url"
782/// WebGui.FirefoxInteractive: command to start Firefox in interactive mode, like "$prog --private-window \'$url\' &"
783/// WebGui.FirefoxProfile: name of Firefox profile to use
784/// WebGui.FirefoxProfilePath: file path to Firefox profile
785/// WebGui.FirefoxRandomProfile: usage of random Firefox profile "no" - disabled, "yes" - enabled (default)
786/// WebGui.LaunchTmout: time required to start process in seconds (default 30 s)
787/// WebGui.ReconnectTmout: time to reconnect for already existing connection, if negative - no reconnecting possible (default 15 s)
788/// WebGui.CefTimer: periodic time to run CEF event loop (default 10 ms)
789/// WebGui.CefUseViews: "yes" - enable / "no" - disable usage of CEF views frameworks (default is platform/version dependent)
790/// WebGui.CefLogSeveriry: "disable", "fatal", "error", "warning", "info", "verbose" (default is "fatal")
791/// WebGui.CefHeadlessTimeout: timeout to wait produce result of headless output (default is 30 s)
792/// WebGui.OperationTmout: time required to perform WebWindow operation like execute command or update drawings
793/// WebGui.RecordData: if specified enables data recording for each web window; "yes" or "no" (default)
794/// WebGui.JsonComp: compression factor for JSON conversion, if not specified - each widget uses own default values
795/// WebGui.ForceHttp: "no" (default), "yes" - always create real http server to run web window
796/// WebGui.Console: -1 - output only console.error(), 0 - add console.warn(), 1 - add console.log() output
797/// WebGui.Debug: "no" (default), "yes" - enable more debug output on JSROOT side
798/// WebGui.ConnCredits: 10 - number of packets which can be send by server or client without acknowledge from receiving side
799/// WebGui.QueueLength: 10 - maximal number of entires in window send queue
800/// WebGui.openui5src: alternative location for openui5 like https://openui5.hana.ondemand.com/1.135.0/
801/// WebGui.openui5libs: list of pre-loaded ui5 libs like sap.m, sap.ui.layout, sap.ui.unified
802/// WebGui.openui5theme: openui5 theme like sap_fiori_3 (default) or sap_horizon
803/// WebGui.DarkMode: "no" (default), "yes" - switch to JSROOT dark mode and will use sap_fiori_3_dark theme
804///
805/// THttpServer-related parameters documented in \ref CreateServer method
806///
807/// In case of using web browsers based on snap sandboxing, if you see a runtime error about unauthorized access to the system
808/// `/tmp/` folder, try callign `export TMPDIR=/home/user/` (adapt path to a real folder) before running ROOT. This workaround should
809/// no longer be needed for recognized snap-installed firefox or chrome browsers if ROOT version >= 6.38
810
812{
813 // silently ignore regular Show() calls in batch mode
814 if (!user_args.IsHeadless() && gROOT->IsWebDisplayBatch())
815 return 0;
816
817 // for embedded window no any browser need to be started
818 // also when off is specified, no browser should be started
819 if ((user_args.GetBrowserKind() == RWebDisplayArgs::kEmbedded) || (user_args.GetBrowserKind() == RWebDisplayArgs::kOff))
820 return 0;
821
822 // catch window showing, used by the RBrowser to embed some of ROOT widgets
823 if (fShowCallback)
825 // add dummy handle to pending connections, widget (like TWebCanvas) may wait until connection established
826 auto handle = std::make_unique<RWebDisplayHandle>("");
827 win.AddDisplayHandle(false, "", handle);
828 return 0;
829 }
830
831 if (!fServer) {
832 R__LOG_ERROR(WebGUILog()) << "Server instance not exists to show window";
833 return 0;
834 }
835
837
838 if (args.IsHeadless() && !args.IsSupportHeadless()) {
839 R__LOG_ERROR(WebGUILog()) << "Cannot use batch mode with " << args.GetBrowserName();
840 return 0;
841 }
842
844 if (!normal_http && (RWebWindowWSHandler::GetBoolEnv("WebGui.ForceHttp") > 0))
845 normal_http = true;
846
847 std::string key;
848
849 std::string url = GetUrl(win, normal_http, &key);
850 // empty url indicates failure, which already printed by GetUrl method
851 if (url.empty())
852 return 0;
853
854 // we book manager mutex for a longer operation,
855 std::lock_guard<std::recursive_mutex> grd(fMutex);
856
857 args.SetUrl(url);
858
859 if (args.GetWidth() <= 0)
860 args.SetWidth(win.GetWidth());
861 if (args.GetHeight() <= 0)
862 args.SetHeight(win.GetHeight());
863 if (args.GetX() < 0)
864 args.SetX(win.GetX());
865 if (args.GetY() < 0)
866 args.SetY(win.GetY());
867
868 if (args.IsHeadless())
869 args.AppendUrlOpt("headless"); // used to create holder request
870
871 if (!args.IsHeadless() && normal_http) {
872 auto winurl = args.GetUrl();
873 winurl.erase(0, fAddr.length());
874 InformListener(std::string("win:") + winurl + "\n");
875 }
876
877 auto server = GetServer();
878
879 if (win.IsUseCurrentDir() && server)
880 server->AddLocation("currentdir/", ".");
881
882 if (!args.IsHeadless() && ((args.GetBrowserKind() == RWebDisplayArgs::kServer) || gROOT->IsWebDisplayBatch()) /*&& (RWebWindowWSHandler::GetBoolEnv("WebGui.OnetimeKey") != 1)*/) {
883 std::cout << "New web window: " << args.GetUrl() << std::endl;
884 return 0;
885 }
886
887 if (fAddr.compare(0,9,"socket://") == 0)
888 return 0;
889
890#if !defined(R__MACOSX) && !defined(R__WIN32)
891 if (args.IsInteractiveBrowser()) {
892 const char *varname = "WebGui.CheckRemoteDisplay";
894 const char *displ = gSystem->Getenv("DISPLAY");
895 if (displ && *displ && (*displ != ':')) {
896 gEnv->SetValue(varname, "no");
897 std::cout << "\n"
898 "ROOT web-based widget started in the session where DISPLAY set to " << displ << "\n" <<
899 "Means web browser will be displayed on remote X11 server which is usually very inefficient\n"
900 "One can start ROOT session in server mode like \"root -b --web=server:8877\" and forward http port to display node\n"
901 "Or one can use rootssh script to configure port forwarding and display web widgets automatically\n"
902 "Find more info on https://root.cern/for_developers/root7/#rbrowser\n"
903 "This message can be disabled by setting \"" << varname << ": no\" in .rootrc file\n";
904 }
905 }
906 }
907#endif
908
909 if (!normal_http)
910 args.SetHttpServer(server);
911
912 auto handle = RWebDisplayHandle::Display(args);
913
914 if (!handle) {
915 R__LOG_ERROR(WebGUILog()) << "Cannot display window in " << args.GetBrowserName();
916 if (!key.empty())
917 win.RemoveKey(key);
918 return 0;
919 }
920
921 return win.AddDisplayHandle(args.IsHeadless(), key, handle);
922}
923
924//////////////////////////////////////////////////////////////////////////
925/// Waits until provided check function or lambdas returns non-zero value
926/// Regularly calls WebWindow::Sync() method to let run event loop
927/// If call from the main thread, runs system events processing
928/// Check function has following signature: int func(double spent_tm)
929/// Parameter spent_tm is time in seconds, which already spent inside function
930/// Waiting will be continued, if function returns zero.
931/// First non-zero value breaks waiting loop and result is returned (or 0 if time is expired).
932/// If parameter timed is true, timelimit (in seconds) defines how long to wait
933
935{
936 int res = 0, cnt = 0;
937 double spent = 0.;
938
939 auto start = std::chrono::high_resolution_clock::now();
940
941 win.Sync(); // in any case call sync once to ensure
942
943 auto is_main_thread = IsMainThrd();
944
945 while ((res = check(spent)) == 0) {
946
947 if (is_main_thread)
949
950 win.Sync();
951
952 // only when first 1000 events processed, invoke sleep
953 if (++cnt > 1000)
954 std::this_thread::sleep_for(std::chrono::milliseconds(cnt > 5000 ? 10 : 1));
955
956 std::chrono::duration<double, std::milli> elapsed = std::chrono::high_resolution_clock::now() - start;
957
958 spent = elapsed.count() * 1e-3; // use ms precision
959
960 if (timed && (spent > timelimit))
961 return -3;
962 }
963
964 return res;
965}
966
967//////////////////////////////////////////////////////////////////////////
968/// Terminate http server and ROOT application
969
971{
972 if (fServer)
973 fServer->SetTerminate();
974
975 // set flag which sometimes checked in TSystem::ProcessEvents
976 gROOT->SetInterrupt(kTRUE);
977
978 if (gApplication)
979 TTimer::SingleShot(100, "TApplication", gApplication, "Terminate()");
980}
#define R__LOG_ERROR(...)
Definition RLogger.hxx:356
#define e(i)
Definition RSha256.hxx:103
static bool gWebWinMainThrdSet
static std::thread::id gWebWinMainThrd
This thread id used to identify main application thread, where ROOT event processing runs To inject c...
static bool gWebWinLoopbackMode
static bool gWebWinUseSessionKey
constexpr Ssiz_t kNPOS
The equivalent of std::string::npos for the ROOT class TString.
Definition RtypesCore.h:132
constexpr Bool_t kTRUE
Definition RtypesCore.h:108
R__EXTERN TApplication * gApplication
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:126
#define R__ASSERT(e)
Checks condition e and reports a fatal error if it's false.
Definition TError.h:125
winID h TVirtualViewer3D TVirtualGLPainter p
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 win
char name[80]
Definition TGX11.cxx:148
#define gROOT
Definition TROOT.h:417
R__EXTERN TSystem * gSystem
Definition TSystem.h:582
Holds different arguments for starting browser with RWebDisplayHandle::Display() method.
std::string GetBrowserName() const
Returns configured browser name.
EBrowserKind GetBrowserKind() const
returns configured browser kind, see EBrowserKind for supported values
RWebDisplayArgs & SetX(int x=-1)
set preferable web window x position, negative is default
bool IsSupportHeadless() const
returns true if browser supports headless mode
RWebDisplayArgs & SetUrl(const std::string &url)
set window url
int GetWidth() const
returns preferable web window width
const std::string & GetUrl() const
returns window url
void AppendUrlOpt(const std::string &opt)
append extra url options, add "&" as separator if required
int GetY() const
set preferable web window y position
int GetHeight() const
returns preferable web window height
void SetHttpServer(THttpServer *serv)
set http server instance, used for window display
RWebDisplayArgs & SetWidth(int w=0)
set preferable web window width
bool IsInteractiveBrowser() const
returns true if interactive browser window supposed to be started
RWebDisplayArgs & SetY(int y=-1)
set preferable web window y position, negative is default
bool IsHeadless() const
returns headless mode
RWebDisplayArgs & SetHeight(int h=0)
set preferable web window height
@ kServer
indicates that ROOT runs as server and just printouts window URL, browser should be started by the us...
@ kOff
disable web display, do not start any browser
@ kEmbedded
window will be embedded into other, no extra browser need to be started
int GetX() const
set preferable web window x position
static bool NeedHttpServer(const RWebDisplayArgs &args)
Check if http server required for display.
static std::unique_ptr< RWebDisplayHandle > Display(const RWebDisplayArgs &args)
Create web display.
static int GetBoolEnv(const std::string &name, int dfl=-1)
Parse boolean gEnv variable which should be "yes" or "no".
Represents web window, which can be shown in web browser or any other supported environment.
static void AddServerLocation(const std::string &server_prefix, const std::string &files_path)
Configure server location which can be used for loading of custom scripts or files When THttpServer i...
static std::string GenerateKey(int keylen=32)
Static method to generate cryptographic key Parameter keylen defines length of cryptographic key in b...
bool fUseSessionKey
! is session key has to be used for data signing
bool CreateServer(bool with_http=false)
Creates http server, if required - with real http engine (civetweb) One could configure concrete HTTP...
static void SetUseConnectionKey(bool on=true)
Enable or disable usage of connection key (default on) If enabled, each connection (and reconnection)...
bool fExternalProcessEvents
! indicate that there are external process events engine
std::recursive_mutex fMutex
! main mutex, used for window creations
RWebWindowsManager()
window manager constructor Required here for correct usage of unique_ptr<THttpServer>
int WaitFor(RWebWindow &win, WebWindowWaitFunc_t check, bool timed=false, double tm=-1)
Waits until provided check function or lambdas returns non-zero value Regularly calls WebWindow::Sync...
static void ClearServerLocations()
Clear all server locations Does not change configuration of already running HTTP server.
WebWindowShowCallback_t fShowCallback
! function called for each RWebWindow::Show call
WebWindowDeleteCallback_t fDeleteCallback
! function called when RWebWindow is destroyed
unsigned ShowWindow(RWebWindow &win, const RWebDisplayArgs &args)
Show window in specified location, see Show() method for more details.
std::string fAddr
! HTTP address of the server
void Terminate()
Terminate http server and ROOT application.
unsigned fIdCnt
! counter for identifiers
~RWebWindowsManager()
window manager destructor Required here for correct usage of unique_ptr<THttpServer>
THttpServer * GetServer() const
Returns THttpServer instance.
std::string fSessionKey
! secret session key used on client to code connections keys
bool fUseHttpThrd
! use special thread for THttpServer
static void AssignMainThrd()
Re-assigns main thread id Normally main thread id recognized at the moment when library is loaded It ...
static void SetUseSessionKey(bool on=true)
Enable or disable usage of session key (default on) If enabled, secrete session key used to calculate...
static void SetSingleConnMode(bool on=true)
Enable or disable single connection mode (default on) If enabled, one connection only with any web wi...
float fReconnectTmout
! timeout in seconds to reconnect connection, default 15s
bool IsUseHttpThread() const
Returns true if http server use special thread for requests processing (default off)
bool fUseSenderThreads
! use extra threads for sending data from RWebWindow to clients
std::unique_ptr< THttpServer > fServer
! central communication with the all used displays
static void SetLoopbackMode(bool on=true)
Set loopback mode for THttpServer used for web widgets By default is on.
static bool IsMainThrd()
Returns true when called from main process Main process recognized at the moment when library is load...
static std::shared_ptr< RWebWindowsManager > & Instance()
Returns default window manager Used to display all standard ROOT elements like TCanvas or TFitPanel.
bool InformListener(const std::string &msg)
If ROOT_LISTENER_SOCKET variable is configured, message will be sent to that unix socket.
float fLaunchTmout
! timeout in seconds to start browser process, default 30s
static std::map< std::string, std::string > GetServerLocations()
Returns server locations as <std::string, std::string> Key is location name (with slash at the end) a...
std::string GetUrl(RWebWindow &win, bool remote=false, std::string *produced_key=nullptr)
Provide URL address to access specified window from inside or from remote.
void Unregister(RWebWindow &win)
Release all references to specified window Called from RWebWindow destructor.
static bool IsLoopbackMode()
Returns true if loopback mode used by THttpServer for web widgets.
std::shared_ptr< RWebWindow > CreateWindow()
Creates new window To show window, RWebWindow::Show() have to be called.
virtual Int_t GetValue(const char *name, Int_t dflt) const
Returns the integer value for a resource.
Definition TEnv.cxx:511
virtual void SetValue(const char *name, const char *value, EEnvLevel level=kEnvChange, const char *type=nullptr)
Set the value of a resource or create a new resource.
Definition TEnv.cxx:752
Bool_t Connect(const char *signal, const char *receiver_class, void *receiver, const char *slot)
Non-static method is used to connect from the signal of this object to the receiver slot.
Definition TQObject.cxx:865
Bool_t Disconnect(const char *signal=nullptr, void *receiver=nullptr, const char *slot=nullptr)
Disconnects signal of this object from slot of receiver.
static const TString & GetDataDir()
Get the data directory in the installation. Static utility function.
Definition TROOT.cxx:3391
Random number generator class based on M.
Definition TRandom3.h:27
This class implements client sockets.
Definition TSocket.h:39
virtual void Close(Option_t *opt="")
Close the socket.
Definition TSocket.cxx:378
virtual Int_t SendRaw(const void *buffer, Int_t length, ESendRecvOptions opt=kDefault)
Send a raw buffer of specified length.
Definition TSocket.cxx:609
virtual Bool_t IsValid() const
Definition TSocket.h:131
Basic string class.
Definition TString.h:138
Ssiz_t Length() const
Definition TString.h:427
Bool_t EndsWith(const char *pat, ECaseCompare cmp=kExact) const
Return true if string ends with the specified string.
Definition TString.cxx:2324
Ssiz_t First(char c) const
Find first occurrence of a character c.
Definition TString.cxx:545
const char * Data() const
Definition TString.h:386
TObjArray * Tokenize(const TString &delim) const
This function is used to isolate sequential tokens in a TString.
Definition TString.cxx:2344
TString & Append(const char *cs)
Definition TString.h:583
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:2459
static TString Itoa(Int_t value, Int_t base)
Converts an Int_t to a TString with respect to the base specified (2-36).
Definition TString.cxx:2172
void Form(const char *fmt,...)
Formats a string using a printf style format descriptor.
Definition TString.cxx:2437
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 Int_t GetCryptoRandom(void *buf, Int_t len)
Return cryptographic random number Fill provided buffer with random values.
Definition TSystem.cxx:265
virtual void Sleep(UInt_t milliSec)
Sleep milliSec milli seconds.
Definition TSystem.cxx:439
virtual Bool_t ProcessEvents()
Process pending events (GUI, timers, sockets).
Definition TSystem.cxx:418
static void SingleShot(Int_t milliSec, const char *receiver_class, void *receiver, const char *method)
This static function calls a slot after a given time interval.
Definition TTimer.cxx:261
const Int_t n
Definition legend1.C:16
std::function< int(double)> WebWindowWaitFunc_t
function signature for waiting call-backs Such callback used when calling thread need to waits for so...
ROOT::RLogChannel & WebGUILog()
Log channel for WebGUI diagnostics.