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