Logo ROOT  
Reference Guide
RWebWindowsManager.cxx
Go to the documentation of this file.
1/// \file RWebWindowsManager.cxx
2/// \ingroup WebGui ROOT7
3/// \author Sergey Linev <s.linev@gsi.de>
4/// \date 2017-10-16
5/// \warning This is part of the ROOT 7 prototype! It will change without notice. It might trigger earthquakes. Feedback
6/// is welcome!
7
8/*************************************************************************
9 * Copyright (C) 1995-2019, Rene Brun and Fons Rademakers. *
10 * All rights reserved. *
11 * *
12 * For the licensing terms see $ROOTSYS/LICENSE. *
13 * For the list of contributors see $ROOTSYS/README/CREDITS. *
14 *************************************************************************/
15
17
18#include <ROOT/RLogger.hxx>
21
23
24#include "THttpServer.h"
25
26#include "TSystem.h"
27#include "TRandom.h"
28#include "TString.h"
29#include "TApplication.h"
30#include "TTimer.h"
31#include "TROOT.h"
32#include "TEnv.h"
33
34#include <thread>
35#include <chrono>
36
37
38/** \class ROOT::Experimental::RWebWindowsManager
39\ingroup webdisplay
40
41Central instance to create and show web-based windows like Canvas or FitPanel.
42
43Manager responsible to creating THttpServer instance, which is used for RWebWindow's
44communication with clients.
45
46Method RWebWindowsManager::Show() used to show window in specified location.
47*/
48
49//////////////////////////////////////////////////////////////////////////////////////////
50/// Returns default window manager
51/// Used to display all standard ROOT elements like TCanvas or TFitPanel
52
53std::shared_ptr<ROOT::Experimental::RWebWindowsManager> &ROOT::Experimental::RWebWindowsManager::Instance()
54{
55 static std::shared_ptr<RWebWindowsManager> sInstance = std::make_shared<ROOT::Experimental::RWebWindowsManager>();
56 return sInstance;
57}
58
59//////////////////////////////////////////////////////////////////
60/// This thread id used to identify main application thread, where ROOT event processing runs
61/// To inject code in that thread, one should use TTimer (like THttpServer does)
62/// In other threads special run methods have to be invoked like RWebWindow::Run()
63///
64/// TODO: probably detection of main thread should be delivered by central ROOT instances like gApplication or gROOT
65/// Main thread can only make sense if special processing runs there and one can inject own functionality there
66
67static std::thread::id gWebWinMainThrd = std::this_thread::get_id();
68
69//////////////////////////////////////////////////////////////////////////////////////////
70/// Returns true when called from main process
71/// Main process recognized at the moment when library is loaded
72/// It supposed to be a thread where gApplication->Run() will be called
73/// If application runs in separate thread, one have to use AssignMainThrd() method
74/// to let RWebWindowsManager correctly recognize such situation
75
77{
78 return std::this_thread::get_id() == gWebWinMainThrd;
79}
80
81//////////////////////////////////////////////////////////////////////////////////////////
82/// Re-assigns main thread id
83/// Normally main thread id recognized at the moment when library is loaded
84/// It supposed to be a thread where gApplication->Run() will be called
85/// If application runs in separate thread, one have to call this method
86/// to let RWebWindowsManager correctly recognize such situation
87
89{
90 gWebWinMainThrd = std::this_thread::get_id();
91}
92
93
94//////////////////////////////////////////////////////////////////////////////////////////
95/// window manager constructor
96/// Required here for correct usage of unique_ptr<THttpServer>
97
99
100//////////////////////////////////////////////////////////////////////////////////////////
101/// window manager destructor
102/// Required here for correct usage of unique_ptr<THttpServer>
103
105{
106 if (gApplication && fServer && !fServer->IsTerminated()) {
107 gApplication->Disconnect("Terminate(Int_t)", fServer.get(), "SetTerminate()");
108 fServer->SetTerminate();
109 }
110}
111
112//////////////////////////////////////////////////////////////////////////////////////////
113/// Creates http server, if required - with real http engine (civetweb)
114/// One could configure concrete HTTP port, which should be used for the server,
115/// provide following entry in rootrc file:
116///
117/// WebGui.HttpPort: 8088
118///
119/// or specify range of http ports, which can be used:
120///
121/// WebGui.HttpPortMin: 8800
122/// WebGui.HttpPortMax: 9800
123///
124/// By default range [8800..9800] is used
125///
126/// One also can bind HTTP server socket to loopback address,
127/// In that case only connection from localhost will be available:
128///
129/// WebGui.HttpLoopback: yes
130///
131/// Or one could specify hostname which should be used for binding of server socket
132///
133/// WebGui.HttpBind: hostname | ipaddress
134///
135/// To use secured protocol, following parameter should be specified
136///
137/// WebGui.UseHttps: yes
138/// WebGui.ServerCert: sertificate_filename.pem
139///
140/// One also can configure usage of special thread of processing of http server requests
141///
142/// WebGui.HttpThrd: no
143///
144/// Extra threads can be used to send data to different clients via websocket (default no)
145///
146/// WebGui.SenderThrds: no
147///
148/// If required, one could change websocket timeouts (default is 10000 ms)
149///
150/// WebGui.HttpWSTmout: 10000
151///
152/// Following parameter controls browser max-age caching parameter for files (default 3600)
153///
154/// WebGui.HttpMaxAge: 3600
155
157{
158 // explicitly protect server creation
159 std::lock_guard<std::recursive_mutex> grd(fMutex);
160
161 if (!fServer) {
162
163 fServer = std::make_unique<THttpServer>("basic_sniffer");
164
165 const char *serv_thrd = gEnv->GetValue("WebGui.HttpThrd", "");
166 if (serv_thrd && strstr(serv_thrd, "yes"))
167 fUseHttpThrd = true;
168 else if (serv_thrd && strstr(serv_thrd, "no"))
169 fUseHttpThrd = false;
170
171 const char *send_thrds = gEnv->GetValue("WebGui.SenderThrds", "");
172 if (send_thrds && *send_thrds) {
173 if (strstr(send_thrds, "yes"))
174 fUseSenderThreads = true;
175 else if (strstr(send_thrds, "no"))
176 fUseSenderThreads = false;
177 else
178 R__ERROR_HERE("WebDisplay") << "WebGui.SenderThrds has to be yes or no";
179 }
180
181 if (IsUseHttpThread())
182 fServer->CreateServerThread();
183
184 if (gApplication)
185 gApplication->Connect("Terminate(Int_t)", "THttpServer", fServer.get(), "SetTerminate()");
186
187
188 // this is location where all ROOT UI5 sources are collected
189 // normally it is $ROOTSYS/ui5 or <prefix>/ui5 location
190 TString ui5dir = gSystem->Getenv("ROOTUI5SYS");
191 if (ui5dir.Length() == 0)
192 ui5dir = gEnv->GetValue("WebGui.RootUi5Path","");
193
194 if (ui5dir.Length() == 0)
195 ui5dir.Form("%s/ui5", TROOT::GetDataDir().Data());
196
197 if (gSystem->ExpandPathName(ui5dir)) {
198 R__ERROR_HERE("WebDisplay") << "Path to ROOT ui5 sources " << ui5dir << " not found, set ROOTUI5SYS correctly";
199 ui5dir = ".";
200 }
201
202 fServer->AddLocation("rootui5sys/", ui5dir.Data());
203 }
204
205 if (!with_http || !fAddr.empty())
206 return true;
207
208 int http_port = gEnv->GetValue("WebGui.HttpPort", 0);
209 int http_min = gEnv->GetValue("WebGui.HttpPortMin", 8800);
210 int http_max = gEnv->GetValue("WebGui.HttpPortMax", 9800);
211 int http_wstmout = gEnv->GetValue("WebGui.HttpWSTmout", 10000);
212 int http_maxage = gEnv->GetValue("WebGui.HttpMaxAge", -1);
213 fLaunchTmout = gEnv->GetValue("WebGui.LaunchTmout", 30.);
214 const char *http_loopback = gEnv->GetValue("WebGui.HttpLoopback", "no");
215 const char *http_bind = gEnv->GetValue("WebGui.HttpBind", "");
216 const char *http_ssl = gEnv->GetValue("WebGui.UseHttps", "no");
217 const char *ssl_cert = gEnv->GetValue("WebGui.ServerCert", "rootserver.pem");
218
219 bool assign_loopback = http_loopback && strstr(http_loopback, "yes");
220 bool use_secure = http_ssl && strstr(http_ssl, "yes");
221 int ntry = 100;
222
223 if (http_port < 0) {
224 R__ERROR_HERE("WebDisplay") << "Not allowed to create real HTTP server, check WebGui.HttpPort variable";
225 return false;
226 }
227
228 if (!http_port)
229 gRandom->SetSeed(0);
230
231 if (http_max - http_min < ntry)
232 ntry = http_max - http_min;
233
234 while (ntry-- >= 0) {
235 if (!http_port) {
236 if ((http_min <= 0) || (http_max <= http_min)) {
237 R__ERROR_HERE("WebDisplay") << "Wrong HTTP range configuration, check WebGui.HttpPortMin/Max variables";
238 return false;
239 }
240
241 http_port = (int)(http_min + (http_max - http_min) * gRandom->Rndm(1));
242 }
243
244 TString engine, url(use_secure ? "https://" : "http://");
245 engine.Form("%s:%d?websocket_timeout=%d", (use_secure ? "https" : "http"), http_port, http_wstmout);
246 if (assign_loopback) {
247 engine.Append("&loopback");
248 url.Append("localhost");
249 } else if (http_bind && (strlen(http_bind) > 0)) {
250 engine.Append("&bind=");
251 engine.Append(http_bind);
252 url.Append(http_bind);
253 } else {
254 url.Append("localhost");
255 }
256
257 if (http_maxage >= 0)
258 engine.Append(TString::Format("&max_age=%d", http_maxage));
259
260 if (use_secure) {
261 engine.Append("&ssl_cert=");
262 engine.Append(ssl_cert);
263 }
264
265 if (fServer->CreateEngine(engine)) {
266 fAddr = url.Data();
267 fAddr.append(":");
268 fAddr.append(std::to_string(http_port));
269 return true;
270 }
271
272 http_port = 0;
273 }
274
275 return false;
276}
277
278//////////////////////////////////////////////////////////////////////////////////////////
279/// Creates new window
280/// To show window, RWebWindow::Show() have to be called
281
282std::shared_ptr<ROOT::Experimental::RWebWindow> ROOT::Experimental::RWebWindowsManager::CreateWindow()
283{
284
285 // we book manager mutex for a longer operation, locked again in server creation
286 std::lock_guard<std::recursive_mutex> grd(fMutex);
287
288 if (!CreateServer()) {
289 R__ERROR_HERE("WebDisplay") << "Cannot create server when creating window";
290 return nullptr;
291 }
292
293 std::shared_ptr<ROOT::Experimental::RWebWindow> win = std::make_shared<ROOT::Experimental::RWebWindow>();
294
295 if (!win) {
296 R__ERROR_HERE("WebDisplay") << "Fail to create RWebWindow instance";
297 return nullptr;
298 }
299
300 double dflt_tmout = gEnv->GetValue("WebGui.OperationTmout", 50.);
301
302 auto wshandler = win->CreateWSHandler(Instance(), ++fIdCnt, dflt_tmout);
303
304 if (gEnv->GetValue("WebGui.RecordData", 0) > 0) {
305 std::string fname, prefix;
306 if (fIdCnt > 1) {
307 prefix = std::string("f") + std::to_string(fIdCnt) + "_";
308 fname = std::string("protcol") + std::to_string(fIdCnt) + ".json";
309 } else {
310 fname = "protocol.json";
311 }
312 win->RecordData(fname, prefix);
313 }
314
315 fServer->RegisterWS(wshandler);
316
317 return win;
318}
319
320//////////////////////////////////////////////////////////////////////////////////////////
321/// Release all references to specified window
322/// Called from RWebWindow destructor
323
325{
326 if (win.fWSHandler)
327 fServer->UnregisterWS(win.fWSHandler);
328}
329
330//////////////////////////////////////////////////////////////////////////
331/// Provide URL address to access specified window from inside or from remote
332
334{
335 if (!fServer) {
336 R__ERROR_HERE("WebDisplay") << "Server instance not exists when requesting window URL";
337 return "";
338 }
339
340 std::string addr = "/";
341
342 addr.append(win.fWSHandler->GetName());
343
344 addr.append("/");
345
346 if (remote) {
347 if (!CreateServer(true)) {
348 R__ERROR_HERE("WebDisplay") << "Fail to start real HTTP server when requesting URL";
349 return "";
350 }
351
352 addr = fAddr + addr;
353 }
354
355 return addr;
356}
357
358///////////////////////////////////////////////////////////////////////////////////////////////////
359/// Show web window in specified location.
360///
361/// \param batch_mode indicates that browser will run in headless mode
362/// \param user_args specifies where and how display web window
363///
364/// As display args one can use string like "firefox" or "chrome" - these are two main supported web browsers.
365/// See RWebDisplayArgs::SetBrowserKind() for all available options. Default value for the browser can be configured
366/// when starting root with --web argument like: "root --web=chrome"
367///
368/// If allowed, same window can be displayed several times (like for TCanvas)
369///
370/// Following parameters can be configured in rootrc file:
371///
372/// WebGui.Chrome: full path to Google Chrome executable
373/// WebGui.ChromeBatch: command to start chrome in batch
374/// WebGui.ChromeInteractive: command to start chrome in interactive mode
375/// WebGui.Firefox: full path to Mozialla Firefox executable
376/// WebGui.FirefoxBatch: command to start Firefox in batch mode
377/// WebGui.FirefoxInteractive: command to start Firefox in interactive mode
378/// WebGui.FirefoxProfile: name of Firefox profile to use
379/// WebGui.FirefoxProfilePath: file path to Firefox profile
380/// WebGui.FirefoxRandomProfile: usage of random Firefox profile -1 never, 0 - only for batch mode (dflt), 1 - always
381/// WebGui.LaunchTmout: time required to start process in seconds (default 30 s)
382/// WebGui.OperationTmout: time required to perform WebWindow operation like execute command or update drawings
383/// WebGui.RecordData: if specified enables data recording for each web window 0 - off, 1 - on
384/// WebGui.JsonComp: compression factor for JSON conversion, if not specified - each widget uses own default values
385/// WebGui.ForceHttp: 0 - off (default), 1 - always create real http server to run web window
386/// WebGui.Console: -1 - output only console.error(), 0 - add console.warn(), 1 - add console.log() output
387/// WebGui.openui5src: alternative location for openui5 like https://openui5.hana.ondemand.com/
388/// WebGui.openui5libs: list of pre-loaded ui5 libs like sap.m, sap.ui.layout, sap.ui.unified
389/// WebGui.openui5theme: openui5 theme like sap_belize (default) or sap_fiori_3
390///
391/// HTTP-server related parameters documented in RWebWindowsManager::CreateServer() method
392
394{
395 // silently ignore regular Show() calls in batch mode
396 if (!batch_mode && gROOT->IsWebDisplayBatch())
397 return 0;
398
399 // for embedded window no any browser need to be started
401 return 0;
402
403 // we book manager mutex for a longer operation,
404 std::lock_guard<std::recursive_mutex> grd(fMutex);
405
406 if (!fServer) {
407 R__ERROR_HERE("WebDisplay") << "Server instance not exists to show window";
408 return 0;
409 }
410
411 std::string key;
412 int ntry = 100000;
413
414 do {
415 key = std::to_string(gRandom->Integer(0x100000));
416 } while ((--ntry > 0) && win.HasKey(key));
417 if (ntry == 0) {
418 R__ERROR_HERE("WebDisplay") << "Fail to create unique key for the window";
419 return 0;
420 }
421
422 RWebDisplayArgs args(user_args);
423
424 if (batch_mode && !args.IsSupportHeadless()) {
425 R__ERROR_HERE("WebDisplay") << "Cannot use batch mode with " << args.GetBrowserName();
426 return 0;
427 }
428
429 args.SetHeadless(batch_mode);
430 if (args.GetWidth() <= 0) args.SetWidth(win.GetWidth());
431 if (args.GetHeight() <= 0) args.SetHeight(win.GetHeight());
432
433 bool normal_http = !args.IsLocalDisplay();
434 if (!normal_http && (gEnv->GetValue("WebGui.ForceHttp",0) == 1))
435 normal_http = true;
436
437 std::string url = GetUrl(win, normal_http);
438 if (url.empty()) {
439 R__ERROR_HERE("WebDisplay") << "Cannot create URL for the window";
440 return 0;
441 }
442
443 args.SetUrl(url);
444
445 args.AppendUrlOpt(std::string("key=") + key);
446 if (batch_mode) args.AppendUrlOpt("batch_mode");
447
448 if (!normal_http)
449 args.SetHttpServer(GetServer());
450
451 auto handle = RWebDisplayHandle::Display(args);
452
453 if (!handle) {
454 R__ERROR_HERE("WebDisplay") << "Cannot display window in " << args.GetBrowserName();
455 return 0;
456 }
457
458 return win.AddDisplayHandle(batch_mode, key, handle);
459}
460
461//////////////////////////////////////////////////////////////////////////
462/// Waits until provided check function or lambdas returns non-zero value
463/// Regularly calls WebWindow::Sync() method to let run event loop
464/// If call from the main thread, runs system events processing
465/// Check function has following signature: int func(double spent_tm)
466/// Parameter spent_tm is time in seconds, which already spent inside function
467/// Waiting will be continued, if function returns zero.
468/// First non-zero value breaks waiting loop and result is returned (or 0 if time is expired).
469/// If parameter timed is true, timelimit (in seconds) defines how long to wait
470
472{
473 int res = 0;
474 int cnt = 0;
475 double spent = 0;
476
477 auto start = std::chrono::high_resolution_clock::now();
478
479 win.Sync(); // in any case call sync once to ensure
480
481 while ((res = check(spent)) == 0) {
482
483 if (IsMainThrd())
485
486 win.Sync();
487
488 std::this_thread::sleep_for(std::chrono::milliseconds(1));
489
490 std::chrono::duration<double, std::milli> elapsed = std::chrono::high_resolution_clock::now() - start;
491
492 spent = elapsed.count() * 1e-3; // use ms precision
493
494 if (timed && (spent > timelimit))
495 return -3;
496
497 cnt++;
498 }
499
500 return res;
501}
502
503//////////////////////////////////////////////////////////////////////////
504/// Terminate http server and ROOT application
505
507{
508 if (fServer)
509 fServer->SetTerminate();
510
511 if (gApplication)
513}
#define R__ERROR_HERE(GROUP)
Definition: RLogger.hxx:183
#define e(i)
Definition: RSha256.hxx:103
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:166
R__EXTERN TEnv * gEnv
Definition: TEnv.h:171
XFontStruct * id
Definition: TGX11.cxx:108
#define gROOT
Definition: TROOT.h:406
R__EXTERN TRandom * gRandom
Definition: TRandom.h:62
R__EXTERN TSystem * gSystem
Definition: TSystem.h:556
Holds different arguments for starting browser with RWebDisplayHandle::Display() method.
bool IsSupportHeadless() const
returns true if browser supports headless mode
RWebDisplayArgs & SetUrl(const std::string &url)
set window url
int GetHeight() const
returns preferable web window height
void SetHeadless(bool on=true)
set headless mode
void SetHttpServer(THttpServer *serv)
set http server instance, used for window display
EBrowserKind GetBrowserKind() const
returns configured browser kind, see EBrowserKind for supported values
std::string GetBrowserName() const
Returns configured browser name.
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
void AppendUrlOpt(const std::string &opt)
append extra url options, add "&" as separator if required
RWebDisplayArgs & SetHeight(int h=0)
set preferable web window height
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.
Represents web window, which can be shown in web browser or any other supported environment.
Definition: RWebWindow.hxx:56
bool HasKey(const std::string &key) const
Returns true if provided key value already exists (in processes map or in existing connections)
Definition: RWebWindow.cxx:435
unsigned AddDisplayHandle(bool batch_mode, const std::string &key, std::unique_ptr< RWebDisplayHandle > &handle)
Add display handle and associated key Key is random number generated when starting new window When cl...
Definition: RWebWindow.cxx:417
void Sync()
Special method to process all internal activity when window runs in separate thread.
Definition: RWebWindow.cxx:828
unsigned GetHeight() const
returns configured window height (0 - default)
Definition: RWebWindow.hxx:223
unsigned GetWidth() const
returns configured window width (0 - default) actual window width can be different
Definition: RWebWindow.hxx:219
std::shared_ptr< RWebWindowWSHandler > fWSHandler
! specialize websocket handler for all incoming connections
Definition: RWebWindow.hxx:124
std::shared_ptr< RWebWindow > CreateWindow()
Creates new window To show window, RWebWindow::Show() have to be called.
static std::shared_ptr< RWebWindowsManager > & Instance()
Returns default window manager Used to display all standard ROOT elements like TCanvas or TFitPanel.
std::string GetUrl(const RWebWindow &win, bool remote=false)
Provide URL address to access specified window from inside or from remote.
void Unregister(RWebWindow &win)
Release all references to specified window Called from RWebWindow destructor.
static bool IsMainThrd()
Returns true when called from main process Main process recognized at the moment when library is load...
static void AssignMainThrd()
Re-assigns main thread id Normally main thread id recognized at the moment when library is loaded It ...
RWebWindowsManager()
window manager constructor Required here for correct usage of unique_ptr<THttpServer>
unsigned ShowWindow(RWebWindow &win, bool batch_mode, const RWebDisplayArgs &args)
Show window in specified location, see Show() method for more details.
bool CreateServer(bool with_http=false)
Creates http server, if required - with real http engine (civetweb) One could configure concrete HTTP...
~RWebWindowsManager()
window manager destructor 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...
void Terminate()
Terminate http server and ROOT application.
virtual void Terminate(Int_t status=0)
Terminate the application by call TSystem::Exit() unless application has been told to return from Run...
virtual Int_t GetValue(const char *name, Int_t dflt) const
Returns the integer value for a resource.
Definition: TEnv.cxx:491
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:866
Bool_t Disconnect(const char *signal=0, void *receiver=0, const char *slot=0)
Disconnects signal of this object from slot of receiver.
Definition: TQObject.cxx:1024
static const TString & GetDataDir()
Get the data directory in the installation. Static utility function.
Definition: TROOT.cxx:2949
virtual void SetSeed(ULong_t seed=0)
Set the random generator seed.
Definition: TRandom.cxx:597
virtual Double_t Rndm()
Machine independent random number generator.
Definition: TRandom.cxx:541
virtual UInt_t Integer(UInt_t imax)
Returns a random integer uniformly distributed on the interval [ 0, imax-1 ].
Definition: TRandom.cxx:349
Basic string class.
Definition: TString.h:131
Ssiz_t Length() const
Definition: TString.h:405
const char * Data() const
Definition: TString.h:364
TString & Append(const char *cs)
Definition: TString.h:559
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:2311
void Form(const char *fmt,...)
Formats a string using a printf style format descriptor.
Definition: TString.cxx:2289
virtual Bool_t ExpandPathName(TString &path)
Expand a pathname getting rid of special shell characters like ~.
Definition: TSystem.cxx:1269
virtual const char * Getenv(const char *env)
Get environment variable.
Definition: TSystem.cxx:1658
virtual Bool_t ProcessEvents()
Process pending events (GUI, timers, sockets).
Definition: TSystem.cxx:414
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:51
const char * cnt
Definition: TXMLSetup.cxx:74