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