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