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 "TROOT.h"
29#include "TEnv.h"
30#include "TExec.h"
31#include "TSocket.h"
32
33#include <thread>
34#include <chrono>
35#include <iostream>
36
37using namespace ROOT::Experimental;
38
39///////////////////////////////////////////////////////////////
40/// Parse boolean gEnv variable which should be "yes" or "no"
41/// \return 1 for true or 0 for false
42/// Returns \param dflt if result is not defined
43/// \param name name of the env variable
44
45int RWebWindowWSHandler::GetBoolEnv(const std::string &name, int dflt)
46{
47 const char *undef = "<undefined>";
48 const char *value = gEnv->GetValue(name.c_str(), undef);
49 if (!value) return dflt;
50 std::string svalue = value;
51 if (svalue == undef) return dflt;
52
53 if (svalue == "yes") return 1;
54 if (svalue == "no") return 0;
55
56 R__LOG_ERROR(WebGUILog()) << name << " has to be yes or no";
57 return dflt;
58}
59
60
61/** \class ROOT::Experimental::RWebWindowsManager
62\ingroup webdisplay
63
64Central instance to create and show web-based windows like Canvas or FitPanel.
65
66Manager responsible to creating THttpServer instance, which is used for RWebWindow's
67communication with clients.
68
69Method RWebWindows::Show() used to show window in specified location.
70*/
71
72//////////////////////////////////////////////////////////////////////////////////////////
73/// Returns default window manager
74/// Used to display all standard ROOT elements like TCanvas or TFitPanel
75
76std::shared_ptr<RWebWindowsManager> &RWebWindowsManager::Instance()
77{
78 static std::shared_ptr<RWebWindowsManager> sInstance = std::make_shared<RWebWindowsManager>();
79 return sInstance;
80}
81
82//////////////////////////////////////////////////////////////////
83/// This thread id used to identify main application thread, where ROOT event processing runs
84/// To inject code in that thread, one should use TTimer (like THttpServer does)
85/// In other threads special run methods have to be invoked like RWebWindow::Run()
86///
87/// TODO: probably detection of main thread should be delivered by central ROOT instances like gApplication or gROOT
88/// Main thread can only make sense if special processing runs there and one can inject own functionality there
89
90static std::thread::id gWebWinMainThrd = std::this_thread::get_id();
91static bool gWebWinMainThrdSet = true;
92static bool gWebWinLoopbackMode = true;
93
94//////////////////////////////////////////////////////////////////////////////////////////
95/// Returns true when called from main process
96/// Main process recognized at the moment when library is loaded
97/// It supposed to be a thread where gApplication->Run() will be called
98/// If application runs in separate thread, one have to use AssignMainThrd() method
99/// to let RWebWindowsManager correctly recognize such situation
100
102{
103 return gWebWinMainThrdSet && (std::this_thread::get_id() == gWebWinMainThrd);
104}
105
106//////////////////////////////////////////////////////////////////////////////////////////
107/// Re-assigns main thread id
108/// Normally main thread id recognized at the moment when library is loaded
109/// It supposed to be a thread where gApplication->Run() will be called
110/// If application runs in separate thread, one have to call this method
111/// to let RWebWindowsManager correctly recognize such situation
112
114{
115 gWebWinMainThrdSet = true;
116 gWebWinMainThrd = std::this_thread::get_id();
117}
118
119
120//////////////////////////////////////////////////////////////////////////////////////////
121/// Set loopback mode for THttpServer used for web widgets
122/// By default is on. Only local communication via localhost address is possible
123/// Disable it only if really necessary - it may open unauthorized access to your application from external nodes!!
124
126{
128 if (!on) {
129 printf("\nWARNING!\n");
130 printf("Disabling loopback mode may leads to security problem.\n");
131 printf("See https://root.cern/about/security/ for more information.\n\n");
132 }
133}
134
135
136//////////////////////////////////////////////////////////////////////////////////////////
137/// window manager constructor
138/// Required here for correct usage of unique_ptr<THttpServer>
139
141{
142 fExternalProcessEvents = RWebWindowWSHandler::GetBoolEnv("WebGui.ExternalProcessEvents") == 1;
144 gWebWinMainThrdSet = false;
145 fAssgnExec = std::make_unique<TExec>("init_threadid", "ROOT::Experimental::RWebWindowsManager::AssignMainThrd();");
146 TTimer::SingleShot(0, "TExec", fAssgnExec.get(), "Exec()");
147 }
148}
149
150//////////////////////////////////////////////////////////////////////////////////////////
151/// window manager destructor
152/// Required here for correct usage of unique_ptr<THttpServer>
153
155{
156 if (gApplication && fServer && !fServer->IsTerminated()) {
157 gApplication->Disconnect("Terminate(Int_t)", fServer.get(), "SetTerminate()");
158 fServer->SetTerminate();
159 }
160}
161
162//////////////////////////////////////////////////////////////////////////////////////////
163/// Assign thread id for window
164/// Required in case of external process events
165
167{
169 win.fUseServerThreads = false;
170 win.fProcessMT = false;
171 win.fCallbacksThrdIdSet = true;
172 win.fCallbacksThrdId = gWebWinMainThrd;
173 }
174}
175
176//////////////////////////////////////////////////////////////////////////////////////////
177/// If ROOT_LISTENER_SOCKET variable is configured,
178/// message will be sent to that unix socket
179
180bool RWebWindowsManager::InformListener(const std::string &msg)
181{
182#ifdef R__WIN32
183 (void) msg;
184 return false;
185
186#else
187
188 const char *fname = gSystem->Getenv("ROOT_LISTENER_SOCKET");
189 if (!fname || !*fname)
190 return false;
191
192 TSocket s(fname);
193 if (!s.IsValid()) {
194 R__LOG_ERROR(WebGUILog()) << "Problem with open listener socket " << fname << ", check ROOT_LISTENER_SOCKET environment variable";
195 return false;
196 }
197
198 int res = s.SendRaw(msg.c_str(), msg.length());
199
200 s.Close();
201
202 if (res > 0) {
203 // workaround to let handle socket by system outside ROOT process
205 gSystem->Sleep(10);
206 }
207
208 return res > 0;
209#endif
210}
211
212
213//////////////////////////////////////////////////////////////////////////////////////////
214/// Creates http server, if required - with real http engine (civetweb)
215/// One could configure concrete HTTP port, which should be used for the server,
216/// provide following entry in rootrc file:
217///
218/// WebGui.HttpPort: 8088
219///
220/// or specify range of http ports, which can be used:
221///
222/// WebGui.HttpPortMin: 8800
223/// WebGui.HttpPortMax: 9800
224///
225/// By default range [8800..9800] is used
226///
227/// One also can bind HTTP server socket to loopback address,
228/// In that case only connection from localhost will be available:
229///
230/// WebGui.HttpLoopback: yes
231///
232/// Or one could specify hostname which should be used for binding of server socket
233///
234/// WebGui.HttpBind: hostname | ipaddress
235///
236/// To use secured protocol, following parameter should be specified
237///
238/// WebGui.UseHttps: yes
239/// WebGui.ServerCert: sertificate_filename.pem
240///
241/// Alternatively, one can specify unix socket to handle requests:
242///
243/// WebGui.UnixSocket: /path/to/unix/socket
244/// WebGui.UnixSocketMode: 0700
245///
246/// Typically one used unix sockets together with server mode like `root --web=server:/tmp/root.socket` and
247/// then redirect it via ssh tunnel (e.g. using `rootssh`) to client node
248///
249/// All incoming requests processed in THttpServer in timer handler with 10 ms timeout.
250/// One may decrease value to improve latency or increase value to minimize CPU load
251///
252/// WebGui.HttpTimer: 10
253///
254/// To processing incoming http requests and websockets, THttpServer allocate 10 threads
255/// One have to increase this number if more simultaneous connections are expected:
256///
257/// WebGui.HttpThrds: 10
258///
259/// One also can configure usage of special thread of processing of http server requests
260///
261/// WebGui.HttpThrd: no
262///
263/// Extra threads can be used to send data to different clients via websocket (default no)
264///
265/// WebGui.SenderThrds: no
266///
267/// If required, one could change websocket timeouts (default is 10000 ms)
268///
269/// WebGui.HttpWSTmout: 10000
270///
271/// By default, THttpServer created in restricted mode which only allows websocket handlers
272/// and processes only very few other related http requests. For security reasons such mode
273/// should be always enabled. Only if it is really necessary to process all other kinds
274/// of HTTP requests, one could specify no for following parameter (default yes):
275///
276/// WebGui.WSOnly: yes
277///
278/// In some applications one may need to force longpoll websocket emulations from the beginning,
279/// for instance when clients connected via proxys. Although JSROOT should automatically fallback
280/// to longpoll engine, one can configure this directly (default no)
281///
282/// WebGui.WSLongpoll: no
283///
284/// Following parameter controls browser max-age caching parameter for files (default 3600)
285/// When 0 is specified, browser cache will be disabled
286///
287/// WebGui.HttpMaxAge: 3600
288///
289/// Also one can provide extra URL options for, see TCivetweb::Create for list of supported options
290///
291/// WebGui.HttpExtraArgs: winsymlinks=no
292///
293/// One also can configure usage of FastCGI server for web windows:
294///
295/// WebGui.FastCgiPort: 4000
296/// WebGui.FastCgiThreads: 10
297///
298/// To be able start web browser for such windows, one can provide real URL of the
299/// web server which will connect with that FastCGI instance:
300///
301/// WebGui.FastCgiServer: https://your_apache_server.com/root_cgi_path
302///
303
305{
306 if (gROOT->GetWebDisplay() == "off")
307 return false;
308
309 // explicitly protect server creation
310 std::lock_guard<std::recursive_mutex> grd(fMutex);
311
312 if (!fServer) {
313
314 fServer = std::make_unique<THttpServer>("basic_sniffer");
315
317 fUseHttpThrd = false;
318 } else {
319 auto serv_thrd = RWebWindowWSHandler::GetBoolEnv("WebGui.HttpThrd");
320 if (serv_thrd != -1)
321 fUseHttpThrd = serv_thrd != 0;
322 }
323
324 auto send_thrds = RWebWindowWSHandler::GetBoolEnv("WebGui.SenderThrds");
325 if (send_thrds != -1)
326 fUseSenderThreads = send_thrds != 0;
327
328 if (IsUseHttpThread())
329 fServer->CreateServerThread();
330
331 if (gApplication)
332 gApplication->Connect("Terminate(Int_t)", "THttpServer", fServer.get(), "SetTerminate()");
333
334 fServer->SetWSOnly(RWebWindowWSHandler::GetBoolEnv("WebGui.WSOnly", 1) != 0);
335
336 // this is location where all ROOT UI5 sources are collected
337 // normally it is $ROOTSYS/ui5 or <prefix>/ui5 location
338 TString ui5dir = gSystem->Getenv("ROOTUI5SYS");
339 if (ui5dir.Length() == 0)
340 ui5dir = gEnv->GetValue("WebGui.RootUi5Path","");
341
342 if (ui5dir.Length() == 0)
343 ui5dir.Form("%s/ui5", TROOT::GetDataDir().Data());
344
345 if (gSystem->ExpandPathName(ui5dir)) {
346 R__LOG_ERROR(WebGUILog()) << "Path to ROOT ui5 sources " << ui5dir << " not found, set ROOTUI5SYS correctly";
347 ui5dir = ".";
348 }
349
350 fServer->AddLocation("rootui5sys/", ui5dir.Data());
351 }
352
353 if (!with_http || fServer->IsAnyEngine())
354 return true;
355
356 int http_port = gEnv->GetValue("WebGui.HttpPort", 0);
357 int http_min = gEnv->GetValue("WebGui.HttpPortMin", 8800);
358 int http_max = gEnv->GetValue("WebGui.HttpPortMax", 9800);
359 int http_timer = gEnv->GetValue("WebGui.HttpTimer", 10);
360 int http_thrds = gEnv->GetValue("WebGui.HttpThreads", 10);
361 int http_wstmout = gEnv->GetValue("WebGui.HttpWSTmout", 10000);
362 int http_maxage = gEnv->GetValue("WebGui.HttpMaxAge", -1);
363 const char *extra_args = gEnv->GetValue("WebGui.HttpExtraArgs", "");
364 int fcgi_port = gEnv->GetValue("WebGui.FastCgiPort", 0);
365 int fcgi_thrds = gEnv->GetValue("WebGui.FastCgiThreads", 10);
366 const char *fcgi_serv = gEnv->GetValue("WebGui.FastCgiServer", "");
367 fLaunchTmout = gEnv->GetValue("WebGui.LaunchTmout", 30.);
368 bool assign_loopback = gWebWinLoopbackMode;
369 const char *http_bind = gEnv->GetValue("WebGui.HttpBind", "");
370 bool use_secure = RWebWindowWSHandler::GetBoolEnv("WebGui.UseHttps", 0) == 1;
371 const char *ssl_cert = gEnv->GetValue("WebGui.ServerCert", "rootserver.pem");
372
373 const char *unix_socket = gSystem->Getenv("ROOT_WEBGUI_SOCKET");
374 if (!unix_socket || !*unix_socket)
375 unix_socket = gEnv->GetValue("WebGui.UnixSocket", "");
376 const char *unix_socket_mode = gEnv->GetValue("WebGui.UnixSocketMode", "0700");
377 bool use_unix_socket = unix_socket && *unix_socket;
378
379 if (use_unix_socket)
380 fcgi_port = http_port = -1;
381
382 if (assign_loopback)
383 fcgi_port = -1;
384
385 int ntry = 100;
386
387 if ((http_port < 0) && (fcgi_port <= 0) && !use_unix_socket) {
388 R__LOG_ERROR(WebGUILog()) << "Not allowed to create HTTP server, check WebGui.HttpPort variable";
389 return false;
390 }
391
392 if ((http_timer > 0) && !IsUseHttpThread())
393 fServer->SetTimer(http_timer);
394
395 if (http_port < 0) {
396 ntry = 0;
397 } else {
398
399 if (http_port == 0)
400 gRandom->SetSeed(0);
401
402 if (http_max - http_min < ntry)
403 ntry = http_max - http_min;
404 }
405
406 if (fcgi_port > 0)
407 ntry++;
408
409 if (use_unix_socket)
410 ntry++;
411
412 while (ntry-- >= 0) {
413 if ((http_port == 0) && (fcgi_port <= 0) && !use_unix_socket) {
414 if ((http_min <= 0) || (http_max <= http_min)) {
415 R__LOG_ERROR(WebGUILog()) << "Wrong HTTP range configuration, check WebGui.HttpPortMin/Max variables";
416 return false;
417 }
418
419 http_port = (int)(http_min + (http_max - http_min) * gRandom->Rndm(1));
420 }
421
422 TString engine, url;
423 if (fcgi_port > 0) {
424 engine.Form("fastcgi:%d?thrds=%d", fcgi_port, fcgi_thrds);
425 if (!fServer->CreateEngine(engine))
426 return false;
427 if (fcgi_serv && (strlen(fcgi_serv) > 0))
428 fAddr = fcgi_serv;
429 if (http_port < 0)
430 return true;
431 fcgi_port = 0;
432 } else {
433 if (use_unix_socket) {
434 engine.Form("socket:%s?socket_mode=%s&", unix_socket, unix_socket_mode);
435 } else {
436 url = use_secure ? "https://" : "http://";
437 engine.Form("%s:%d?", (use_secure ? "https" : "http"), http_port);
438 if (assign_loopback) {
439 engine.Append("loopback&");
440 url.Append("localhost");
441 } else if (http_bind && (strlen(http_bind) > 0)) {
442 engine.Append(TString::Format("bind=%s&", http_bind));
443 url.Append(http_bind);
444 } else {
445 url.Append("localhost");
446 }
447 }
448
449 engine.Append(TString::Format("thrds=%d&websocket_timeout=%d", http_thrds, http_wstmout));
450
451 if (http_maxage >= 0)
452 engine.Append(TString::Format("&max_age=%d", http_maxage));
453
454 if (use_secure && !strchr(ssl_cert,'&')) {
455 engine.Append("&ssl_cert=");
456 engine.Append(ssl_cert);
457 }
458
459 if (!use_unix_socket && !assign_loopback && extra_args && strlen(extra_args) > 0) {
460 engine.Append("&");
461 engine.Append(extra_args);
462 }
463
464 if (fServer->CreateEngine(engine)) {
465 if (use_unix_socket) {
466 fAddr = "socket://"; // fictional socket URL
467 fAddr.append(unix_socket);
468 // InformListener(std::string("socket:") + unix_socket + "\n");
469 } else if (http_port > 0) {
470 fAddr = url.Data();
471 fAddr.append(":");
472 fAddr.append(std::to_string(http_port));
473 // InformListener(std::string("http:") + std::to_string(http_port) + "\n");
474 }
475 return true;
476 }
477 use_unix_socket = false;
478 http_port = 0;
479 }
480 }
481
482 return false;
483}
484
485//////////////////////////////////////////////////////////////////////////////////////////
486/// Creates new window
487/// To show window, RWebWindow::Show() have to be called
488
489std::shared_ptr<RWebWindow> RWebWindowsManager::CreateWindow()
490{
491 // we book manager mutex for a longer operation, locked again in server creation
492 std::lock_guard<std::recursive_mutex> grd(fMutex);
493
494 if (!CreateServer()) {
495 R__LOG_ERROR(WebGUILog()) << "Cannot create server when creating window";
496 return nullptr;
497 }
498
499 std::shared_ptr<RWebWindow> win = std::make_shared<RWebWindow>();
500
501 if (!win) {
502 R__LOG_ERROR(WebGUILog()) << "Fail to create RWebWindow instance";
503 return nullptr;
504 }
505
506 double dflt_tmout = gEnv->GetValue("WebGui.OperationTmout", 50.);
507
508 auto wshandler = win->CreateWSHandler(Instance(), ++fIdCnt, dflt_tmout);
509
510 if (gEnv->GetValue("WebGui.RecordData", 0) > 0) {
511 std::string fname, prefix;
512 if (fIdCnt > 1) {
513 prefix = std::string("f") + std::to_string(fIdCnt) + "_";
514 fname = std::string("protcol") + std::to_string(fIdCnt) + ".json";
515 } else {
516 fname = "protocol.json";
517 }
518 win->RecordData(fname, prefix);
519 }
520
524 else
525 win->UseServerThreads(); // let run window until thread is obtained
526 } else if (IsUseHttpThread())
527 win->UseServerThreads();
528
529 const char *token = gEnv->GetValue("WebGui.ConnToken", "");
530 if (token && *token)
531 win->SetConnToken(token);
532
533 fServer->RegisterWS(wshandler);
534
535 return win;
536}
537
538//////////////////////////////////////////////////////////////////////////////////////////
539/// Release all references to specified window
540/// Called from RWebWindow destructor
541
543{
544 if (win.fWSHandler)
545 fServer->UnregisterWS(win.fWSHandler);
546}
547
548//////////////////////////////////////////////////////////////////////////
549/// Provide URL address to access specified window from inside or from remote
550
551std::string RWebWindowsManager::GetUrl(const RWebWindow &win, bool remote)
552{
553 if (!fServer) {
554 R__LOG_ERROR(WebGUILog()) << "Server instance not exists when requesting window URL";
555 return "";
556 }
557
558 std::string addr = "/";
559
560 addr.append(win.fWSHandler->GetName());
561
562 addr.append("/");
563
564 if (remote) {
565 if (!CreateServer(true)) {
566 R__LOG_ERROR(WebGUILog()) << "Fail to start real HTTP server when requesting URL";
567 return "";
568 }
569
570 addr = fAddr + addr;
571 }
572
573 return addr;
574}
575
576///////////////////////////////////////////////////////////////////////////////////////////////////
577/// Show web window in specified location.
578///
579/// \param[inout] win web window by reference
580/// \param user_args specifies where and how display web window
581///
582/// As display args one can use string like "firefox" or "chrome" - these are two main supported web browsers.
583/// See RWebDisplayArgs::SetBrowserKind() for all available options. Default value for the browser can be configured
584/// when starting root with --web argument like: "root --web=chrome". When root started in web server mode "root --web=server",
585/// no any web browser will be started - just URL will be printout, which can be entered in any running web browser
586///
587/// If allowed, same window can be displayed several times (like for RCanvas or TCanvas)
588///
589/// Following parameters can be configured in rootrc file:
590///
591/// WebGui.Display: kind of display like chrome or firefox or browser, can be overwritten by --web=value command line argument
592/// WebGui.OnetimeKey: if configured requires unique key every time window is connected (default no)
593/// WebGui.Chrome: full path to Google Chrome executable
594/// WebGui.ChromeBatch: command to start chrome in batch, used for image production, like "$prog --headless --disable-gpu $geometry $url"
595/// WebGui.ChromeHeadless: command to start chrome in headless mode, like "fork: --headless --disable-gpu $geometry $url"
596/// WebGui.ChromeInteractive: command to start chrome in interactive mode, like "$prog $geometry --app=\'$url\' &"
597/// WebGui.Firefox: full path to Mozilla Firefox executable
598/// WebGui.FirefoxHeadless: command to start Firefox in headless mode, like "fork:--headless --private-window --no-remote $profile $url"
599/// WebGui.FirefoxInteractive: command to start Firefox in interactive mode, like "$prog --private-window \'$url\' &"
600/// WebGui.FirefoxProfile: name of Firefox profile to use
601/// WebGui.FirefoxProfilePath: file path to Firefox profile
602/// WebGui.FirefoxRandomProfile: usage of random Firefox profile -1 never, 0 - only for headless mode (dflt), 1 - always
603/// WebGui.LaunchTmout: time required to start process in seconds (default 30 s)
604/// WebGui.OperationTmout: time required to perform WebWindow operation like execute command or update drawings
605/// WebGui.RecordData: if specified enables data recording for each web window 0 - off, 1 - on
606/// WebGui.JsonComp: compression factor for JSON conversion, if not specified - each widget uses own default values
607/// WebGui.ForceHttp: 0 - off (default), 1 - always create real http server to run web window
608/// WebGui.Console: -1 - output only console.error(), 0 - add console.warn(), 1 - add console.log() output
609/// WebGui.ConnCredits: 10 - number of packets which can be send by server or client without acknowledge from receiving side
610/// WebGui.openui5src: alternative location for openui5 like https://openui5.hana.ondemand.com/
611/// WebGui.openui5libs: list of pre-loaded ui5 libs like sap.m, sap.ui.layout, sap.ui.unified
612/// WebGui.openui5theme: openui5 theme like sap_belize (default) or sap_fiori_3
613///
614/// THttpServer-related parameters documented in \ref CreateServer method
615
617{
618 // silently ignore regular Show() calls in batch mode
619 if (!user_args.IsHeadless() && gROOT->IsWebDisplayBatch())
620 return 0;
621
622 // for embedded window no any browser need to be started
623 // also when off is specified, no browser should be started
624 if ((user_args.GetBrowserKind() == RWebDisplayArgs::kEmbedded) || (user_args.GetBrowserKind() == RWebDisplayArgs::kOff))
625 return 0;
626
627 // catch window showing, used by the RBrowser to embed some of ROOT widgets
628 if (fShowCallback)
629 if (fShowCallback(win, user_args))
630 return 0;
631
632 // place here while involves conn mutex
633 auto token = win.GetConnToken();
634
635 // we book manager mutex for a longer operation,
636 std::lock_guard<std::recursive_mutex> grd(fMutex);
637
638 if (!fServer) {
639 R__LOG_ERROR(WebGUILog()) << "Server instance not exists to show window";
640 return 0;
641 }
642
643 std::string key = win.GenerateKey();
644 if (key.empty()) {
645 R__LOG_ERROR(WebGUILog()) << "Fail to create unique key for the window";
646 return 0;
647 }
648
649 RWebDisplayArgs args(user_args);
650
651 if (args.IsHeadless() && !args.IsSupportHeadless()) {
652 R__LOG_ERROR(WebGUILog()) << "Cannot use batch mode with " << args.GetBrowserName();
653 return 0;
654 }
655
656 if (args.GetWidth() <= 0)
657 args.SetWidth(win.GetWidth());
658 if (args.GetHeight() <= 0)
659 args.SetHeight(win.GetHeight());
660
661 bool normal_http = !args.IsLocalDisplay();
662 if (!normal_http && (gEnv->GetValue("WebGui.ForceHttp", 0) == 1))
663 normal_http = true;
664
665 std::string url = GetUrl(win, normal_http);
666 if (url.empty()) {
667 R__LOG_ERROR(WebGUILog()) << "Cannot create URL for the window";
668 return 0;
669 }
670 if (normal_http && fAddr.empty()) {
671 R__LOG_WARNING(WebGUILog()) << "Full URL cannot be produced for window " << url << " to start web browser";
672 return 0;
673 }
674
675 args.SetUrl(url);
676
677 args.AppendUrlOpt(std::string("key=") + key);
678 if (args.IsHeadless())
679 args.AppendUrlOpt("headless"); // used to create holder request
680 if (!token.empty())
681 args.AppendUrlOpt(std::string("token=") + token);
682
683 if (!args.IsHeadless() && normal_http) {
684 auto winurl = args.GetUrl();
685 winurl.erase(0, fAddr.length());
686 InformListener(std::string("win:") + winurl);
687 }
688
689 if (!args.IsHeadless() && ((args.GetBrowserKind() == RWebDisplayArgs::kServer) || gROOT->IsWebDisplayBatch()) /*&& (RWebWindowWSHandler::GetBoolEnv("WebGui.OnetimeKey") != 1)*/) {
690 std::cout << "New web window: " << args.GetUrl() << std::endl;
691 return 0;
692 }
693
694 if (fAddr.compare(0,9,"socket://") == 0)
695 return 0;
696
697#if !defined(R__MACOSX) && !defined(R__WIN32)
698 if (args.IsInteractiveBrowser()) {
699 const char *varname = "WebGui.CheckRemoteDisplay";
700 if (RWebWindowWSHandler::GetBoolEnv(varname, 1) == 1) {
701 const char *displ = gSystem->Getenv("DISPLAY");
702 if (displ && *displ && (*displ != ':')) {
703 gEnv->SetValue(varname, "no");
704 std::cout << "\n"
705 "ROOT web-based widget started in the session where DISPLAY set to " << displ << "\n" <<
706 "Means web browser will be displayed on remote X11 server which is usually very inefficient\n"
707 "One can start ROOT session in server mode like \"root -b --web=server:8877\" and forward http port to display node\n"
708 "Or one can use rootssh script to configure pore forwarding and display web widgets automatically\n"
709 "Find more info on https://root.cern/for_developers/root7/#rbrowser\n"
710 "This message can be disabled by setting \"" << varname << ": no\" in .rootrc file\n";
711 }
712 }
713 }
714#endif
715
716 if (!normal_http)
717 args.SetHttpServer(GetServer());
718
719 auto handle = RWebDisplayHandle::Display(args);
720
721 if (!handle) {
722 R__LOG_ERROR(WebGUILog()) << "Cannot display window in " << args.GetBrowserName();
723 return 0;
724 }
725
726 return win.AddDisplayHandle(args.IsHeadless(), key, handle);
727}
728
729//////////////////////////////////////////////////////////////////////////
730/// Waits until provided check function or lambdas returns non-zero value
731/// Regularly calls WebWindow::Sync() method to let run event loop
732/// If call from the main thread, runs system events processing
733/// Check function has following signature: int func(double spent_tm)
734/// Parameter spent_tm is time in seconds, which already spent inside function
735/// Waiting will be continued, if function returns zero.
736/// First non-zero value breaks waiting loop and result is returned (or 0 if time is expired).
737/// If parameter timed is true, timelimit (in seconds) defines how long to wait
738
739int RWebWindowsManager::WaitFor(RWebWindow &win, WebWindowWaitFunc_t check, bool timed, double timelimit)
740{
741 int res = 0, cnt = 0;
742 double spent = 0.;
743
744 auto start = std::chrono::high_resolution_clock::now();
745
746 win.Sync(); // in any case call sync once to ensure
747
748 auto is_main_thread = IsMainThrd();
749
750 while ((res = check(spent)) == 0) {
751
752 if (is_main_thread)
754
755 win.Sync();
756
757 // only when first 1000 events processed, invoke sleep
758 if (++cnt > 1000)
759 std::this_thread::sleep_for(std::chrono::milliseconds(cnt > 5000 ? 10 : 1));
760
761 std::chrono::duration<double, std::milli> elapsed = std::chrono::high_resolution_clock::now() - start;
762
763 spent = elapsed.count() * 1e-3; // use ms precision
764
765 if (timed && (spent > timelimit))
766 return -3;
767 }
768
769 return res;
770}
771
772//////////////////////////////////////////////////////////////////////////
773/// Terminate http server and ROOT application
774
776{
777 if (fServer)
778 fServer->SetTerminate();
779
780 if (gApplication)
781 TTimer::SingleShot(100, "TApplication", gApplication, "Terminate()");
782}
#define R__LOG_WARNING(...)
Definition RLogger.hxx:363
#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
R__EXTERN TApplication * gApplication
R__EXTERN TEnv * gEnv
Definition TEnv.h:170
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void 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:405
R__EXTERN TRandom * gRandom
Definition TRandom.h:62
R__EXTERN TSystem * gSystem
Definition TSystem.h:560
Holds different arguments for starting browser with RWebDisplayHandle::Display() method.
std::string GetBrowserName() const
Returns configured browser name.
bool IsSupportHeadless() const
returns true if browser supports headless mode
bool IsHeadless() const
returns headless mode
RWebDisplayArgs & SetUrl(const std::string &url)
set window url
bool IsInteractiveBrowser() const
returns true if interactive browser window supposed to be started
int GetHeight() const
returns preferable web window height
void SetHttpServer(THttpServer *serv)
set http server instance, used for window display
EBrowserKind GetBrowserKind() const
returns configured browser kind, see EBrowserKind for supported values
void AppendUrlOpt(const std::string &opt)
append extra url options, add "&" as separator if required
bool IsLocalDisplay() const
returns true if local display like CEF or Qt5 QWebEngine should be used
@ kEmbedded
window will be embedded into other, no extra browser need to be started
@ 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
RWebDisplayArgs & SetHeight(int h=0)
set preferable web window height
const std::string & GetUrl() const
returns window url
int GetWidth() const
returns preferable web window width
RWebDisplayArgs & SetWidth(int w=0)
set preferable web window width
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.
void AssignWindowThreadId(RWebWindow &win)
Assign thread id for window Required in case of external process events.
std::unique_ptr< THttpServer > fServer
! central communication with the all used displays
std::string GetUrl(const RWebWindow &win, bool remote=false)
Provide URL address to access specified window from inside or from remote.
bool CreateServer(bool with_http=false)
Creates http server, if required - with real http engine (civetweb) One could configure concrete HTTP...
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...
std::recursive_mutex fMutex
! main mutex, used for window creations
unsigned fIdCnt
! counter for identifiers
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
bool fUseSenderThreads
! use extra threads for sending data from RWebWindow to clients
void Terminate()
Terminate http server and ROOT application.
~RWebWindowsManager()
window manager destructor Required here for correct usage of unique_ptr<THttpServer>
THttpServer * GetServer() const
Returns THttpServer instance.
WebWindowShowCallback_t fShowCallback
! function called for each RWebWindow::Show call
static void AssignMainThrd()
Re-assigns main thread id Normally main thread id recognized at the moment when library is loaded It ...
std::unique_ptr< TExec > fAssgnExec
! special exec to assign thread id via ProcessEvents
bool IsUseHttpThread() const
Returns true if http server use special thread for requests processing (default off)
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 fUseHttpThrd
! use special thread for THttpServer
bool InformListener(const std::string &msg)
If ROOT_LISTENER_SOCKET variable is configured, message will be sent to that unix socket.
void Unregister(RWebWindow &win)
Release all references to specified window Called from RWebWindow destructor.
float fLaunchTmout
! timeout in seconds to start browser process, default 30s
bool fExternalProcessEvents
! indicate that there are external process events engine
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
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:2994
virtual void SetSeed(ULong_t seed=0)
Set the random generator seed.
Definition TRandom.cxx:608
Double_t Rndm() override
Machine independent random number generator.
Definition TRandom.cxx:552
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:421
const char * Data() const
Definition TString.h:380
TString & Append(const char *cs)
Definition TString.h:576
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:2356
void Form(const char *fmt,...)
Formats a string using a printf style format descriptor.
Definition TString.cxx:2334
virtual Bool_t ExpandPathName(TString &path)
Expand a pathname getting rid of special shell characters like ~.
Definition TSystem.cxx:1277
virtual const char * Getenv(const char *env)
Get environment variable.
Definition TSystem.cxx:1666
virtual void Sleep(UInt_t milliSec)
Sleep milliSec milli seconds.
Definition TSystem.cxx:440
virtual Bool_t ProcessEvents()
Process pending events (GUI, timers, sockets).
Definition TSystem.cxx:419
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
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...