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// Warning: This is part of the ROOT 7 prototype! It will change without notice. It might trigger earthquakes. Feedback is welcome!
4
5/*************************************************************************
6 * Copyright (C) 1995-2019, Rene Brun and Fons Rademakers. *
7 * All rights reserved. *
8 * *
9 * For the licensing terms see $ROOTSYS/LICENSE. *
10 * For the list of contributors see $ROOTSYS/README/CREDITS. *
11 *************************************************************************/
12
14
15#include <ROOT/RLogger.hxx>
18
20
21#include "THttpServer.h"
22
23#include "TSystem.h"
24#include "TString.h"
25#include "TApplication.h"
26#include "TTimer.h"
27#include "TRandom.h"
28#include "TError.h"
29#include "TROOT.h"
30#include "TEnv.h"
31#include "TExec.h"
32#include "TSocket.h"
33#include "TThread.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 if (!on) {
132 printf("\nWARNING!\n");
133 printf("Disabling loopback mode may leads to security problem.\n");
134 printf("See https://root.cern/about/security/ for more information.\n\n");
136 printf("Enforce session key to safely work on public network.\n");
137 printf("One may call RWebWindowsManager::SetUseSessionKey(false); to disable it.\n");
139 }
140 }
141}
142
143//////////////////////////////////////////////////////////////////////////////////////////
144/// Returns true if loopback mode used by THttpServer for web widgets
145
147{
148 return gWebWinLoopbackMode;
149}
150
151//////////////////////////////////////////////////////////////////////////////////////////
152/// Enable or disable usage of session key (default on)
153/// If enabled, secrete session key used to calculate hash sum of each packet send to or from server
154/// This protects ROOT http server from anauthorized usage
155
157{
159}
160
161//////////////////////////////////////////////////////////////////////////////////////////
162/// Enable or disable usage of connection key (default on)
163/// If enabled, each connection (and reconnection) to widget requires unique key
164/// Connection key used together with session key to calculate hash sum of each packet send to or from server
165/// This protects ROOT http server from anauthorized usage
166
168{
169 gEnv->SetValue("WebGui.OnetimeKey", on ? "yes" : "no");
170}
171
172//////////////////////////////////////////////////////////////////////////////////////////
173/// Static method to generate cryptographic key
174/// Parameter keylen defines length of cryptographic key in bytes
175/// Output string will be hex formatted and includes "-" separator after every 4 bytes
176/// Example for 16 bytes: "fca45856-41bee066-ff74cc96-9154d405"
177
178std::string RWebWindowsManager::GenerateKey(int keylen)
179{
180 std::vector<unsigned char> buf(keylen, 0);
181 auto res = gSystem->GetCryptoRandom(buf.data(), keylen);
182
183 R__ASSERT(res == keylen && "Error in gSystem->GetCryptoRandom");
184
185 std::string key;
186 for (int n = 0; n < keylen; n++) {
187 if ((n > 0) && (n % 4 == 0))
188 key.append("-");
189 auto t = TString::Itoa(buf[n], 16);
190 if (t.Length() == 1)
191 key.append("0");
192 key.append(t.Data());
193 }
194 return key;
195}
196
197//////////////////////////////////////////////////////////////////////////////////////////
198/// window manager constructor
199/// Required here for correct usage of unique_ptr<THttpServer>
200
202{
205
206 fExternalProcessEvents = RWebWindowWSHandler::GetBoolEnv("WebGui.ExternalProcessEvents") == 1;
209}
210
211//////////////////////////////////////////////////////////////////////////////////////////
212/// window manager destructor
213/// Required here for correct usage of unique_ptr<THttpServer>
214
216{
217 if (gApplication && fServer && !fServer->IsTerminated()) {
218 gApplication->Disconnect("Terminate(Int_t)", fServer.get(), "SetTerminate()");
219 fServer->SetTerminate();
220 }
221}
222
223//////////////////////////////////////////////////////////////////////////////////////////
224/// If ROOT_LISTENER_SOCKET variable is configured,
225/// message will be sent to that unix socket
226
227bool RWebWindowsManager::InformListener(const std::string &msg)
228{
229#ifdef R__WIN32
230 (void) msg;
231 return false;
232
233#else
234
235 const char *fname = gSystem->Getenv("ROOT_LISTENER_SOCKET");
236 if (!fname || !*fname)
237 return false;
238
239 TSocket s(fname);
240 if (!s.IsValid()) {
241 R__LOG_ERROR(WebGUILog()) << "Problem with open listener socket " << fname << ", check ROOT_LISTENER_SOCKET environment variable";
242 return false;
243 }
244
245 int res = s.SendRaw(msg.c_str(), msg.length());
246
247 s.Close();
248
249 if (res > 0) {
250 // workaround to let handle socket by system outside ROOT process
252 gSystem->Sleep(10);
253 }
254
255 return res > 0;
256#endif
257}
258
259
260//////////////////////////////////////////////////////////////////////////////////////////
261/// Creates http server, if required - with real http engine (civetweb)
262/// One could configure concrete HTTP port, which should be used for the server,
263/// provide following entry in rootrc file:
264///
265/// WebGui.HttpPort: 8088
266///
267/// or specify range of http ports, which can be used:
268///
269/// WebGui.HttpPortMin: 8800
270/// WebGui.HttpPortMax: 9800
271///
272/// By default range [8800..9800] is used
273///
274/// One also can bind HTTP server socket to loopback address,
275/// In that case only connection from localhost will be available:
276///
277/// WebGui.HttpLoopback: yes
278///
279/// Or one could specify hostname which should be used for binding of server socket
280///
281/// WebGui.HttpBind: hostname | ipaddress
282///
283/// To use secured protocol, following parameter should be specified
284///
285/// WebGui.UseHttps: yes
286/// WebGui.ServerCert: sertificate_filename.pem
287///
288/// Alternatively, one can specify unix socket to handle requests:
289///
290/// WebGui.UnixSocket: /path/to/unix/socket
291/// WebGui.UnixSocketMode: 0700
292///
293/// Typically one used unix sockets together with server mode like `root --web=server:/tmp/root.socket` and
294/// then redirect it via ssh tunnel (e.g. using `rootssh`) to client node
295///
296/// All incoming requests processed in THttpServer in timer handler with 10 ms timeout.
297/// One may decrease value to improve latency or increase value to minimize CPU load
298///
299/// WebGui.HttpTimer: 10
300///
301/// To processing incoming http requests and websockets, THttpServer allocate 10 threads
302/// One have to increase this number if more simultaneous connections are expected:
303///
304/// WebGui.HttpThrds: 10
305///
306/// One also can configure usage of special thread of processing of http server requests
307///
308/// WebGui.HttpThrd: no
309///
310/// Extra threads can be used to send data to different clients via websocket (default no)
311///
312/// WebGui.SenderThrds: no
313///
314/// If required, one could change websocket timeouts (default is 10000 ms)
315///
316/// WebGui.HttpWSTmout: 10000
317///
318/// By default, THttpServer created in restricted mode which only allows websocket handlers
319/// and processes only very few other related http requests. For security reasons such mode
320/// should be always enabled. Only if it is really necessary to process all other kinds
321/// of HTTP requests, one could specify no for following parameter (default yes):
322///
323/// WebGui.WSOnly: yes
324///
325/// In some applications one may need to force longpoll websocket emulations from the beginning,
326/// for instance when clients connected via proxys. Although JSROOT should automatically fallback
327/// to longpoll engine, one can configure this directly (default no)
328///
329/// WebGui.WSLongpoll: no
330///
331/// Following parameter controls browser max-age caching parameter for files (default 3600)
332/// When 0 is specified, browser cache will be disabled
333///
334/// WebGui.HttpMaxAge: 3600
335///
336/// Also one can provide extra URL options for, see TCivetweb::Create for list of supported options
337///
338/// WebGui.HttpExtraArgs: winsymlinks=no
339///
340/// One also can configure usage of FastCGI server for web windows:
341///
342/// WebGui.FastCgiPort: 4000
343/// WebGui.FastCgiThreads: 10
344///
345/// To be able start web browser for such windows, one can provide real URL of the
346/// web server which will connect with that FastCGI instance:
347///
348/// WebGui.FastCgiServer: https://your_apache_server.com/root_cgi_path
349///
350
352{
353 if (gROOT->GetWebDisplay() == "off")
354 return false;
355
356 // explicitly protect server creation
357 std::lock_guard<std::recursive_mutex> grd(fMutex);
358
359 if (!fServer) {
360
361 fServer = std::make_unique<THttpServer>("basic_sniffer");
362
364 fUseHttpThrd = false;
365 } else {
366 auto serv_thrd = RWebWindowWSHandler::GetBoolEnv("WebGui.HttpThrd");
367 if (serv_thrd != -1)
368 fUseHttpThrd = serv_thrd != 0;
369 }
370
371 auto send_thrds = RWebWindowWSHandler::GetBoolEnv("WebGui.SenderThrds");
372 if (send_thrds != -1)
373 fUseSenderThreads = send_thrds != 0;
374
375 if (IsUseHttpThread())
376 fServer->CreateServerThread();
377
378 if (gApplication)
379 gApplication->Connect("Terminate(Int_t)", "THttpServer", fServer.get(), "SetTerminate()");
380
381 fServer->SetWSOnly(RWebWindowWSHandler::GetBoolEnv("WebGui.WSOnly", 1) != 0);
382
383 // this is location where all ROOT UI5 sources are collected
384 // normally it is $ROOTSYS/ui5 or <prefix>/ui5 location
385 TString ui5dir = gSystem->Getenv("ROOTUI5SYS");
386 if (ui5dir.Length() == 0)
387 ui5dir = gEnv->GetValue("WebGui.RootUi5Path","");
388
389 if (ui5dir.Length() == 0)
390 ui5dir.Form("%s/ui5", TROOT::GetDataDir().Data());
391
392 if (gSystem->ExpandPathName(ui5dir)) {
393 R__LOG_ERROR(WebGUILog()) << "Path to ROOT ui5 sources " << ui5dir << " not found, set ROOTUI5SYS correctly";
394 ui5dir = ".";
395 }
396
397 fServer->AddLocation("rootui5sys/", ui5dir.Data());
398 }
399
400 if (!with_http || fServer->IsAnyEngine())
401 return true;
402
403 int http_port = gEnv->GetValue("WebGui.HttpPort", 0);
404 int http_min = gEnv->GetValue("WebGui.HttpPortMin", 8800);
405 int http_max = gEnv->GetValue("WebGui.HttpPortMax", 9800);
406 int http_timer = gEnv->GetValue("WebGui.HttpTimer", 10);
407 int http_thrds = gEnv->GetValue("WebGui.HttpThreads", 10);
408 int http_wstmout = gEnv->GetValue("WebGui.HttpWSTmout", 10000);
409 int http_maxage = gEnv->GetValue("WebGui.HttpMaxAge", -1);
410 const char *extra_args = gEnv->GetValue("WebGui.HttpExtraArgs", "");
411 int fcgi_port = gEnv->GetValue("WebGui.FastCgiPort", 0);
412 int fcgi_thrds = gEnv->GetValue("WebGui.FastCgiThreads", 10);
413 const char *fcgi_serv = gEnv->GetValue("WebGui.FastCgiServer", "");
414 fLaunchTmout = gEnv->GetValue("WebGui.LaunchTmout", 30.);
415 bool assign_loopback = gWebWinLoopbackMode;
416 const char *http_bind = gEnv->GetValue("WebGui.HttpBind", "");
417 bool use_secure = RWebWindowWSHandler::GetBoolEnv("WebGui.UseHttps", 0) == 1;
418 const char *ssl_cert = gEnv->GetValue("WebGui.ServerCert", "rootserver.pem");
419
420 const char *unix_socket = gSystem->Getenv("ROOT_WEBGUI_SOCKET");
421 if (!unix_socket || !*unix_socket)
422 unix_socket = gEnv->GetValue("WebGui.UnixSocket", "");
423 const char *unix_socket_mode = gEnv->GetValue("WebGui.UnixSocketMode", "0700");
424 bool use_unix_socket = unix_socket && *unix_socket;
425
426 if (use_unix_socket)
427 fcgi_port = http_port = -1;
428
429 if (assign_loopback)
430 fcgi_port = -1;
431
432 int ntry = 100;
433
434 if ((http_port < 0) && (fcgi_port <= 0) && !use_unix_socket) {
435 R__LOG_ERROR(WebGUILog()) << "Not allowed to create HTTP server, check WebGui.HttpPort variable";
436 return false;
437 }
438
439 if ((http_timer > 0) && !IsUseHttpThread())
440 fServer->SetTimer(http_timer);
441
442 if (http_port < 0) {
443 ntry = 0;
444 } else {
445
446 if (http_port == 0)
447 gRandom->SetSeed(0);
448
449 if (http_max - http_min < ntry)
450 ntry = http_max - http_min;
451 }
452
453 if (fcgi_port > 0)
454 ntry++;
455
456 if (use_unix_socket)
457 ntry++;
458
459 while (ntry-- >= 0) {
460 if ((http_port == 0) && (fcgi_port <= 0) && !use_unix_socket) {
461 if ((http_min <= 0) || (http_max <= http_min)) {
462 R__LOG_ERROR(WebGUILog()) << "Wrong HTTP range configuration, check WebGui.HttpPortMin/Max variables";
463 return false;
464 }
465
466 http_port = (int)(http_min + (http_max - http_min) * gRandom->Rndm(1));
467 }
468
469 TString engine, url;
470 if (fcgi_port > 0) {
471 engine.Form("fastcgi:%d?thrds=%d", fcgi_port, fcgi_thrds);
472 if (!fServer->CreateEngine(engine))
473 return false;
474 if (fcgi_serv && (strlen(fcgi_serv) > 0))
475 fAddr = fcgi_serv;
476 if (http_port < 0)
477 return true;
478 fcgi_port = 0;
479 } else {
480 if (use_unix_socket) {
481 engine.Form("socket:%s?socket_mode=%s&", unix_socket, unix_socket_mode);
482 } else {
483 url = use_secure ? "https://" : "http://";
484 engine.Form("%s:%d?", (use_secure ? "https" : "http"), http_port);
485 if (assign_loopback) {
486 engine.Append("loopback&");
487 url.Append("localhost");
488 } else if (http_bind && (strlen(http_bind) > 0)) {
489 engine.Append(TString::Format("bind=%s&", http_bind));
490 url.Append(http_bind);
491 } else {
492 url.Append("localhost");
493 }
494 }
495
496 engine.Append(TString::Format("webgui&top=remote&thrds=%d&websocket_timeout=%d", http_thrds, http_wstmout));
497
498 if (http_maxage >= 0)
499 engine.Append(TString::Format("&max_age=%d", http_maxage));
500
501 if (use_secure && !strchr(ssl_cert,'&')) {
502 engine.Append("&ssl_cert=");
503 engine.Append(ssl_cert);
504 }
505
506 if (!use_unix_socket && !assign_loopback && extra_args && strlen(extra_args) > 0) {
507 engine.Append("&");
508 engine.Append(extra_args);
509 }
510
511 if (fServer->CreateEngine(engine)) {
512 if (use_unix_socket) {
513 fAddr = "socket://"; // fictional socket URL
514 fAddr.append(unix_socket);
515 // InformListener(std::string("socket:") + unix_socket + "\n");
516 } else if (http_port > 0) {
517 fAddr = url.Data();
518 fAddr.append(":");
519 fAddr.append(std::to_string(http_port));
520 // InformListener(std::string("http:") + std::to_string(http_port) + "\n");
521 }
522 return true;
523 }
524 use_unix_socket = false;
525 http_port = 0;
526 }
527 }
528
529 return false;
530}
531
532//////////////////////////////////////////////////////////////////////////////////////////
533/// Creates new window
534/// To show window, RWebWindow::Show() have to be called
535
536std::shared_ptr<RWebWindow> RWebWindowsManager::CreateWindow()
537{
538 // we book manager mutex for a longer operation, locked again in server creation
539 std::lock_guard<std::recursive_mutex> grd(fMutex);
540
541 if (!CreateServer()) {
542 R__LOG_ERROR(WebGUILog()) << "Cannot create server when creating window";
543 return nullptr;
544 }
545
546 std::shared_ptr<RWebWindow> win = std::make_shared<RWebWindow>();
547
548 if (!win) {
549 R__LOG_ERROR(WebGUILog()) << "Fail to create RWebWindow instance";
550 return nullptr;
551 }
552
553 double dflt_tmout = gEnv->GetValue("WebGui.OperationTmout", 50.);
554
555 auto wshandler = win->CreateWSHandler(Instance(), ++fIdCnt, dflt_tmout);
556
557 if (gEnv->GetValue("WebGui.RecordData", 0) > 0) {
558 std::string fname, prefix;
559 if (fIdCnt > 1) {
560 prefix = std::string("f") + std::to_string(fIdCnt) + "_";
561 fname = std::string("protcol") + std::to_string(fIdCnt) + ".json";
562 } else {
563 fname = "protocol.json";
564 }
565 win->RecordData(fname, prefix);
566 }
567
569 // special mode when window communication performed in THttpServer::ProcessRequests
570 // used only with python which create special thread - but is has to be ignored!!!
571 // therefore use main thread id to detect callbacks which are invoked only from that main thread
572 win->fUseProcessEvents = true;
573 win->fCallbacksThrdIdSet = gWebWinMainThrdSet;
574 win->fCallbacksThrdId = gWebWinMainThrd;
575 } else if (IsUseHttpThread())
576 win->UseServerThreads();
577
578 const char *token = gEnv->GetValue("WebGui.ConnToken", "");
579 if (token && *token)
580 win->SetConnToken(token);
581
582 fServer->RegisterWS(wshandler);
583
584 return win;
585}
586
587//////////////////////////////////////////////////////////////////////////////////////////
588/// Release all references to specified window
589/// Called from RWebWindow destructor
590
592{
593 if (win.fWSHandler)
594 fServer->UnregisterWS(win.fWSHandler);
595}
596
597//////////////////////////////////////////////////////////////////////////
598/// Provide URL address to access specified window from inside or from remote
599
600std::string RWebWindowsManager::GetUrl(RWebWindow &win, bool remote, std::string *produced_key)
601{
602 if (!fServer) {
603 R__LOG_ERROR(WebGUILog()) << "Server instance not exists when requesting window URL";
604 return "";
605 }
606
607 std::string addr = "/";
608 addr.append(win.fWSHandler->GetName());
609 addr.append("/");
610
611 bool qmark = false;
612
613 std::string key;
614
615 if (win.IsRequireAuthKey() || produced_key) {
616 key = win.GenerateKey();
617 R__ASSERT(!key.empty());
618 addr.append("?key=");
619 addr.append(key);
620 qmark = true;
621 std::unique_ptr<ROOT::RWebDisplayHandle> dummy;
622 win.AddDisplayHandle(false, key, dummy);
623 }
624
625 auto token = win.GetConnToken();
626 if (!token.empty()) {
627 addr.append(qmark ? "&" : "?");
628 addr.append("token=");
629 addr.append(token);
630 }
631
632 if (remote) {
633 if (!CreateServer(true) || fAddr.empty()) {
634 R__LOG_ERROR(WebGUILog()) << "Fail to start real HTTP server when requesting URL";
635 if (!key.empty())
636 win.RemoveKey(key);
637 return "";
638 }
639
640 addr = fAddr + addr;
641
642 if (!key.empty() && !fSessionKey.empty() && fUseSessionKey && win.IsRequireAuthKey())
643 addr += "#"s + fSessionKey;
644 }
645
646 if (produced_key)
647 *produced_key = key;
648
649 return addr;
650}
651
652///////////////////////////////////////////////////////////////////////////////////////////////////
653/// Show web window in specified location.
654///
655/// \param[inout] win web window by reference
656/// \param user_args specifies where and how display web window
657///
658/// As display args one can use string like "firefox" or "chrome" - these are two main supported web browsers.
659/// See RWebDisplayArgs::SetBrowserKind() for all available options. Default value for the browser can be configured
660/// when starting root with --web argument like: "root --web=chrome". When root started in web server mode "root --web=server",
661/// no any web browser will be started - just URL will be printout, which can be entered in any running web browser
662///
663/// If allowed, same window can be displayed several times (like for RCanvas or TCanvas)
664///
665/// Following parameters can be configured in rootrc file:
666///
667/// WebGui.Display: kind of display like chrome or firefox or browser, can be overwritten by --web=value command line argument
668/// WebGui.OnetimeKey: if configured requires unique key every time window is connected (default yes)
669/// WebGui.Chrome: full path to Google Chrome executable
670/// WebGui.ChromeBatch: command to start chrome in batch, used for image production, like "$prog --headless --disable-gpu $geometry $url"
671/// WebGui.ChromeHeadless: command to start chrome in headless mode, like "fork: --headless --disable-gpu $geometry $url"
672/// WebGui.ChromeInteractive: command to start chrome in interactive mode, like "$prog $geometry --app=\'$url\' &"
673/// WebGui.Firefox: full path to Mozilla Firefox executable
674/// WebGui.FirefoxHeadless: command to start Firefox in headless mode, like "fork:--headless --private-window --no-remote $profile $url"
675/// WebGui.FirefoxInteractive: command to start Firefox in interactive mode, like "$prog --private-window \'$url\' &"
676/// WebGui.FirefoxProfile: name of Firefox profile to use
677/// WebGui.FirefoxProfilePath: file path to Firefox profile
678/// WebGui.FirefoxRandomProfile: usage of random Firefox profile -1 never, 0 - only for headless mode (dflt), 1 - always
679/// WebGui.LaunchTmout: time required to start process in seconds (default 30 s)
680/// WebGui.OperationTmout: time required to perform WebWindow operation like execute command or update drawings
681/// WebGui.RecordData: if specified enables data recording for each web window 0 - off, 1 - on
682/// WebGui.JsonComp: compression factor for JSON conversion, if not specified - each widget uses own default values
683/// WebGui.ForceHttp: 0 - off (default), 1 - always create real http server to run web window
684/// WebGui.Console: -1 - output only console.error(), 0 - add console.warn(), 1 - add console.log() output
685/// WebGui.ConnCredits: 10 - number of packets which can be send by server or client without acknowledge from receiving side
686/// WebGui.openui5src: alternative location for openui5 like https://openui5.hana.ondemand.com/
687/// WebGui.openui5libs: list of pre-loaded ui5 libs like sap.m, sap.ui.layout, sap.ui.unified
688/// WebGui.openui5theme: openui5 theme like sap_belize (default) or sap_fiori_3
689///
690/// THttpServer-related parameters documented in \ref CreateServer method
691
693{
694 // silently ignore regular Show() calls in batch mode
695 if (!user_args.IsHeadless() && gROOT->IsWebDisplayBatch())
696 return 0;
697
698 // for embedded window no any browser need to be started
699 // also when off is specified, no browser should be started
700 if ((user_args.GetBrowserKind() == RWebDisplayArgs::kEmbedded) || (user_args.GetBrowserKind() == RWebDisplayArgs::kOff))
701 return 0;
702
703 // catch window showing, used by the RBrowser to embed some of ROOT widgets
704 if (fShowCallback)
705 if (fShowCallback(win, user_args)) {
706 // add dummy handle to pending connections, widget (like TWebCanvas) may wait until connection established
707 auto handle = std::make_unique<RWebDisplayHandle>("");
708 win.AddDisplayHandle(false, "", handle);
709 return 0;
710 }
711
712 if (!fServer) {
713 R__LOG_ERROR(WebGUILog()) << "Server instance not exists to show window";
714 return 0;
715 }
716
717 RWebDisplayArgs args(user_args);
718
719 if (args.IsHeadless() && !args.IsSupportHeadless()) {
720 R__LOG_ERROR(WebGUILog()) << "Cannot use batch mode with " << args.GetBrowserName();
721 return 0;
722 }
723
724 bool normal_http = RWebDisplayHandle::NeedHttpServer(args);
725 if (!normal_http && (gEnv->GetValue("WebGui.ForceHttp", 0) == 1))
726 normal_http = true;
727
728 std::string key;
729
730 std::string url = GetUrl(win, normal_http, &key);
731 // empty url indicates failure, which already printed by GetUrl method
732 if (url.empty())
733 return 0;
734
735 // we book manager mutex for a longer operation,
736 std::lock_guard<std::recursive_mutex> grd(fMutex);
737
738 args.SetUrl(url);
739
740 if (args.GetWidth() <= 0)
741 args.SetWidth(win.GetWidth());
742 if (args.GetHeight() <= 0)
743 args.SetHeight(win.GetHeight());
744 if (args.GetX() < 0)
745 args.SetX(win.GetX());
746 if (args.GetY() < 0)
747 args.SetY(win.GetY());
748
749 if (args.IsHeadless())
750 args.AppendUrlOpt("headless"); // used to create holder request
751
752 if (!args.IsHeadless() && normal_http) {
753 auto winurl = args.GetUrl();
754 winurl.erase(0, fAddr.length());
755 InformListener(std::string("win:") + winurl);
756 }
757
758 if (!args.IsHeadless() && ((args.GetBrowserKind() == RWebDisplayArgs::kServer) || gROOT->IsWebDisplayBatch()) /*&& (RWebWindowWSHandler::GetBoolEnv("WebGui.OnetimeKey") != 1)*/) {
759 std::cout << "New web window: " << args.GetUrl() << std::endl;
760 return 0;
761 }
762
763 if (fAddr.compare(0,9,"socket://") == 0)
764 return 0;
765
766#if !defined(R__MACOSX) && !defined(R__WIN32)
767 if (args.IsInteractiveBrowser()) {
768 const char *varname = "WebGui.CheckRemoteDisplay";
769 if (RWebWindowWSHandler::GetBoolEnv(varname, 1) == 1) {
770 const char *displ = gSystem->Getenv("DISPLAY");
771 if (displ && *displ && (*displ != ':')) {
772 gEnv->SetValue(varname, "no");
773 std::cout << "\n"
774 "ROOT web-based widget started in the session where DISPLAY set to " << displ << "\n" <<
775 "Means web browser will be displayed on remote X11 server which is usually very inefficient\n"
776 "One can start ROOT session in server mode like \"root -b --web=server:8877\" and forward http port to display node\n"
777 "Or one can use rootssh script to configure port forwarding and display web widgets automatically\n"
778 "Find more info on https://root.cern/for_developers/root7/#rbrowser\n"
779 "This message can be disabled by setting \"" << varname << ": no\" in .rootrc file\n";
780 }
781 }
782 }
783#endif
784
785 auto server = GetServer();
786
787 if (win.IsUseCurrentDir())
788 server->AddLocation("currentdir/", ".");
789
790 if (!normal_http)
791 args.SetHttpServer(server);
792
793 auto handle = RWebDisplayHandle::Display(args);
794
795 if (!handle) {
796 R__LOG_ERROR(WebGUILog()) << "Cannot display window in " << args.GetBrowserName();
797 if (!key.empty())
798 win.RemoveKey(key);
799 return 0;
800 }
801
802 return win.AddDisplayHandle(args.IsHeadless(), key, handle);
803}
804
805//////////////////////////////////////////////////////////////////////////
806/// Waits until provided check function or lambdas returns non-zero value
807/// Regularly calls WebWindow::Sync() method to let run event loop
808/// If call from the main thread, runs system events processing
809/// Check function has following signature: int func(double spent_tm)
810/// Parameter spent_tm is time in seconds, which already spent inside function
811/// Waiting will be continued, if function returns zero.
812/// First non-zero value breaks waiting loop and result is returned (or 0 if time is expired).
813/// If parameter timed is true, timelimit (in seconds) defines how long to wait
814
815int RWebWindowsManager::WaitFor(RWebWindow &win, WebWindowWaitFunc_t check, bool timed, double timelimit)
816{
817 int res = 0, cnt = 0;
818 double spent = 0.;
819
820 auto start = std::chrono::high_resolution_clock::now();
821
822 win.Sync(); // in any case call sync once to ensure
823
824 auto is_main_thread = IsMainThrd();
825
826 while ((res = check(spent)) == 0) {
827
828 if (is_main_thread)
830
831 win.Sync();
832
833 // only when first 1000 events processed, invoke sleep
834 if (++cnt > 1000)
835 std::this_thread::sleep_for(std::chrono::milliseconds(cnt > 5000 ? 10 : 1));
836
837 std::chrono::duration<double, std::milli> elapsed = std::chrono::high_resolution_clock::now() - start;
838
839 spent = elapsed.count() * 1e-3; // use ms precision
840
841 if (timed && (spent > timelimit))
842 return -3;
843 }
844
845 return res;
846}
847
848//////////////////////////////////////////////////////////////////////////
849/// Terminate http server and ROOT application
850
852{
853 if (fServer)
854 fServer->SetTerminate();
855
856 if (gApplication)
857 TTimer::SingleShot(100, "TApplication", gApplication, "Terminate()");
858}
#define R__LOG_ERROR(...)
Definition RLogger.hxx:362
#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
R__EXTERN TApplication * gApplication
R__EXTERN TEnv * gEnv
Definition TEnv.h:170
#define R__ASSERT(e)
Definition TError.h:118
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:110
#define gROOT
Definition TROOT.h:406
R__EXTERN TRandom * gRandom
Definition TRandom.h:62
R__EXTERN TSystem * gSystem
Definition TSystem.h:555
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 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...
WebWindowShowCallback_t fShowCallback
! function called for each RWebWindow::Show call
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...
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
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:491
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:736
SCoord_t GetY() const
Definition TPoint.h:47
SCoord_t GetX() const
Definition TPoint.h:46
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:869
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:3062
virtual void SetSeed(ULong_t seed=0)
Set the random generator seed.
Definition TRandom.cxx:615
Double_t Rndm() override
Machine independent random number generator.
Definition TRandom.cxx:559
virtual void Close(Option_t *opt="")
Close the socket.
Definition TSocket.cxx:389
virtual Int_t SendRaw(const void *buffer, Int_t length, ESendRecvOptions opt=kDefault)
Send a raw buffer of specified length.
Definition TSocket.cxx:620
virtual Bool_t IsValid() const
Definition TSocket.h:132
Basic string class.
Definition TString.h:139
Ssiz_t Length() const
Definition TString.h:417
const char * Data() const
Definition TString.h:376
TString & Append(const char *cs)
Definition TString.h:572
static TString Format(const char *fmt,...)
Static method which formats a string using a printf style format descriptor and return a TString.
Definition TString.cxx:2378
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:2092
void Form(const char *fmt,...)
Formats a string using a printf style format descriptor.
Definition TString.cxx:2356
virtual Bool_t ExpandPathName(TString &path)
Expand a pathname getting rid of special shell characters like ~.
Definition TSystem.cxx:1274
virtual const char * Getenv(const char *env)
Get environment variable.
Definition TSystem.cxx:1665
virtual Int_t GetCryptoRandom(void *buf, Int_t len)
Return cryptographic random number Fill provided buffer with random values Returns number of bytes wr...
Definition TSystem.cxx:266
virtual void Sleep(UInt_t milliSec)
Sleep milliSec milli seconds.
Definition TSystem.cxx:437
virtual Bool_t ProcessEvents()
Process pending events (GUI, timers, sockets).
Definition TSystem.cxx:416
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:258
const Int_t n
Definition legend1.C:16
tbb::task_arena is an alias of tbb::interface7::task_arena, which doesn't allow to forward declare tb...
ROOT::Experimental::RLogChannel & WebGUILog()
Log channel for WebGUI diagnostics.
std::function< int(double)> WebWindowWaitFunc_t
function signature for waiting call-backs Such callback used when calling thread need to waits for so...