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