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