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 "TError.h"
29#include "TROOT.h"
30#include "TEnv.h"
31#include "TExec.h"
32#include "TSocket.h"
33#include "TThread.h"
34#include "TObjArray.h"
35
36#include <thread>
37#include <chrono>
38#include <iostream>
39
40using namespace ROOT;
41
42///////////////////////////////////////////////////////////////
43/// Parse boolean gEnv variable which should be "yes" or "no"
44/// \return 1 for true or 0 for false
45/// Returns \param dflt if result is not defined
46/// \param name name of the env variable
47
48int RWebWindowWSHandler::GetBoolEnv(const std::string &name, int dflt)
49{
50 const char *undef = "<undefined>";
51 const char *value = gEnv->GetValue(name.c_str(), undef);
52 if (!value) return dflt;
53 std::string svalue = value;
54 if (svalue == undef) return dflt;
55
56 if (svalue == "yes") return 1;
57 if (svalue == "no") return 0;
58
59 R__LOG_ERROR(WebGUILog()) << name << " has to be yes or no";
60 return dflt;
61}
62
63
64/** \class ROOT::RWebWindowsManager
65\ingroup webdisplay
66
67Central instance to create and show web-based windows like Canvas or FitPanel.
68
69Manager responsible to creating THttpServer instance, which is used for RWebWindow's
70communication with clients.
71
72Method RWebWindows::Show() used to show window in specified location.
73*/
74
75//////////////////////////////////////////////////////////////////////////////////////////
76/// Returns default window manager
77/// Used to display all standard ROOT elements like TCanvas or TFitPanel
78
79std::shared_ptr<RWebWindowsManager> &RWebWindowsManager::Instance()
80{
81 static std::shared_ptr<RWebWindowsManager> sInstance = std::make_shared<RWebWindowsManager>();
82 return sInstance;
83}
84
85//////////////////////////////////////////////////////////////////
86/// This thread id used to identify main application thread, where ROOT event processing runs
87/// To inject code in that thread, one should use TTimer (like THttpServer does)
88/// In other threads special run methods have to be invoked like RWebWindow::Run()
89///
90/// TODO: probably detection of main thread should be delivered by central ROOT instances like gApplication or gROOT
91/// Main thread can only make sense if special processing runs there and one can inject own functionality there
92
93static std::thread::id gWebWinMainThrd = std::this_thread::get_id();
94static bool gWebWinMainThrdSet = true;
95static bool gWebWinLoopbackMode = true;
96static bool gWebWinUseSessionKey = true;
97
98//////////////////////////////////////////////////////////////////////////////////////////
99/// Returns true when called from main process
100/// Main process recognized at the moment when library is loaded
101/// It supposed to be a thread where gApplication->Run() will be called
102/// If application runs in separate thread, one have to use AssignMainThrd() method
103/// to let RWebWindowsManager correctly recognize such situation
104
106{
107 return gWebWinMainThrdSet && (std::this_thread::get_id() == gWebWinMainThrd);
108}
109
110//////////////////////////////////////////////////////////////////////////////////////////
111/// Re-assigns main thread id
112/// Normally main thread id recognized at the moment when library is loaded
113/// It supposed to be a thread where gApplication->Run() will be called
114/// If application runs in separate thread, one have to call this method
115/// to let RWebWindowsManager correctly recognize such situation
116
118{
119 gWebWinMainThrdSet = true;
120 gWebWinMainThrd = std::this_thread::get_id();
121}
122
123
124//////////////////////////////////////////////////////////////////////////////////////////
125/// Set loopback mode for THttpServer used for web widgets
126/// By default is on. Only local communication via localhost address is possible
127/// Disable it only if really necessary - it may open unauthorized access to your application from external nodes!!
128
130{
132 if (!on) {
133 printf("\nWARNING!\n");
134 printf("Disabling loopback mode may leads to security problem.\n");
135 printf("See https://root.cern/about/security/ for more information.\n\n");
137 printf("Enforce session key to safely work on public network.\n");
138 printf("One may call RWebWindowsManager::SetUseSessionKey(false); to disable it.\n");
140 }
141 }
142}
143
144//////////////////////////////////////////////////////////////////////////////////////////
145/// Returns true if loopback mode used by THttpServer for web widgets
146
148{
149 return gWebWinLoopbackMode;
150}
151
152//////////////////////////////////////////////////////////////////////////////////////////
153/// Enable or disable usage of session key (default on)
154/// If enabled, secrete session key used to calculate hash sum of each packet send to or from server
155/// This protects ROOT http server from anauthorized usage
156
158{
160}
161
162//////////////////////////////////////////////////////////////////////////////////////////
163/// Enable or disable usage of connection key (default on)
164/// If enabled, each connection (and reconnection) to widget requires unique key
165/// Connection key used together with session key to calculate hash sum of each packet send to or from server
166/// This protects ROOT http server from anauthorized usage
167
169{
170 gEnv->SetValue("WebGui.OnetimeKey", on ? "yes" : "no");
171}
172
173//////////////////////////////////////////////////////////////////////////////////////////
174/// Enable or disable single connection mode (default on)
175/// If enabled, one connection only with any web widget is possible
176/// Any attempt to establish more connections will fail
177/// if this mode is disabled some widgets like geom viewer or web canvas will be able to
178/// to serve several clients - only when they are connected with required authentication keys
179
181{
182 gEnv->SetValue("WebGui.SingleConnMode", on ? "yes" : "no");
183}
184
185//////////////////////////////////////////////////////////////////////////////////////////
186/// Configure server location which can be used for loading of custom scripts or files
187/// When THttpServer instance of RWebWindowsManager will be created,
188/// THttpServer::AddLocation() method with correspondent arguments will be invoked.
189
190void RWebWindowsManager::AddServerLocation(const std::string &server_prefix, const std::string &files_path)
191{
192 if (server_prefix.empty() || files_path.empty())
193 return;
194 auto loc = GetServerLocations();
195 std::string prefix = server_prefix;
196 if (prefix.back() != '/')
197 prefix.append("/");
198 loc[prefix] = files_path;
199
200 // now convert back to plain string
201 TString cfg;
202 for (auto &entry : loc) {
203 if (cfg.Length() > 0)
204 cfg.Append(";");
205 cfg.Append(entry.first.c_str());
206 cfg.Append(":");
207 cfg.Append(entry.second.c_str());
208 }
209
210 gEnv->SetValue("WebGui.ServerLocations", cfg);
211
212 auto serv = Instance()->GetServer();
213 if (serv)
214 serv->AddLocation(prefix.c_str(), files_path.c_str());
215}
216
217//////////////////////////////////////////////////////////////////////////////////////////
218/// Returns server locations as <std::string, std::string>
219/// Key is location name (with slash at the end) and value is file path
220
221std::map<std::string, std::string> RWebWindowsManager::GetServerLocations()
222{
223 std::map<std::string, std::string> res;
224
225 TString cfg = gEnv->GetValue("WebGui.ServerLocations","");
226 auto arr = cfg.Tokenize(";");
227 if (arr) {
228 TIter next(arr);
229 while(auto obj = next()) {
230 TString arg = obj->GetName();
231
232 auto p = arg.First(":");
233 if (p == kNPOS) continue;
234
235 TString prefix = arg(0, p);
236 if (!prefix.EndsWith("/"))
237 prefix.Append("/");
238 TString path = arg(p+1, arg.Length() - p);
239
240 res[prefix.Data()] = path.Data();
241 }
242 delete arr;
243 }
244 return res;
245}
246
247//////////////////////////////////////////////////////////////////////////////////////////
248/// Clear all server locations
249/// Does not change configuration of already running HTTP server
250
252{
253 gEnv->SetValue("WebGui.ServerLocations", "");
254}
255
256//////////////////////////////////////////////////////////////////////////////////////////
257/// Static method to generate cryptographic key
258/// Parameter keylen defines length of cryptographic key in bytes
259/// Output string will be hex formatted and includes "-" separator after every 4 bytes
260/// Example for 16 bytes: "fca45856-41bee066-ff74cc96-9154d405"
261
262std::string RWebWindowsManager::GenerateKey(int keylen)
263{
264 std::vector<unsigned char> buf(keylen, 0);
265 auto res = gSystem->GetCryptoRandom(buf.data(), keylen);
266
267 R__ASSERT(res == keylen && "Error in gSystem->GetCryptoRandom");
268
269 std::string key;
270 for (int n = 0; n < keylen; n++) {
271 if ((n > 0) && (n % 4 == 0))
272 key.append("-");
273 auto t = TString::Itoa(buf[n], 16);
274 if (t.Length() == 1)
275 key.append("0");
276 key.append(t.Data());
277 }
278 return key;
279}
280
281//////////////////////////////////////////////////////////////////////////////////////////
282/// window manager constructor
283/// Required here for correct usage of unique_ptr<THttpServer>
284
286{
289
290 fExternalProcessEvents = RWebWindowWSHandler::GetBoolEnv("WebGui.ExternalProcessEvents") == 1;
293}
294
295//////////////////////////////////////////////////////////////////////////////////////////
296/// window manager destructor
297/// Required here for correct usage of unique_ptr<THttpServer>
298
300{
301 if (gApplication && fServer && !fServer->IsTerminated()) {
302 gApplication->Disconnect("Terminate(Int_t)", fServer.get(), "SetTerminate()");
303 fServer->SetTerminate();
304 }
305}
306
307//////////////////////////////////////////////////////////////////////////////////////////
308/// If ROOT_LISTENER_SOCKET variable is configured,
309/// message will be sent to that unix socket
310
311bool RWebWindowsManager::InformListener(const std::string &msg)
312{
313#ifdef R__WIN32
314 (void) msg;
315 return false;
316
317#else
318
319 const char *fname = gSystem->Getenv("ROOT_LISTENER_SOCKET");
320 if (!fname || !*fname)
321 return false;
322
323 TSocket s(fname);
324 if (!s.IsValid()) {
325 R__LOG_ERROR(WebGUILog()) << "Problem with open listener socket " << fname << ", check ROOT_LISTENER_SOCKET environment variable";
326 return false;
327 }
328
329 int res = s.SendRaw(msg.c_str(), msg.length());
330
331 s.Close();
332
333 if (res > 0) {
334 // workaround to let handle socket by system outside ROOT process
336 gSystem->Sleep(10);
337 }
338
339 return res > 0;
340#endif
341}
342
343
344//////////////////////////////////////////////////////////////////////////////////////////
345/// Creates http server, if required - with real http engine (civetweb)
346/// One could configure concrete HTTP port, which should be used for the server,
347/// provide following entry in rootrc file:
348///
349/// WebGui.HttpPort: 8088
350///
351/// or specify range of http ports, which can be used:
352///
353/// WebGui.HttpPortMin: 8800
354/// WebGui.HttpPortMax: 9800
355///
356/// By default range [8800..9800] is used
357///
358/// One also can bind HTTP server socket to loopback address,
359/// In that case only connection from localhost will be available:
360///
361/// WebGui.HttpLoopback: yes
362///
363/// Or one could specify hostname which should be used for binding of server socket
364///
365/// WebGui.HttpBind: hostname | ipaddress
366///
367/// To use secured protocol, following parameter should be specified
368///
369/// WebGui.UseHttps: yes
370/// WebGui.ServerCert: sertificate_filename.pem
371///
372/// Alternatively, one can specify unix socket to handle requests:
373///
374/// WebGui.UnixSocket: /path/to/unix/socket
375/// WebGui.UnixSocketMode: 0700
376///
377/// Typically one used unix sockets together with server mode like `root --web=server:/tmp/root.socket` and
378/// then redirect it via ssh tunnel (e.g. using `rootssh`) to client node
379///
380/// All incoming requests processed in THttpServer in timer handler with 10 ms timeout.
381/// One may decrease value to improve latency or increase value to minimize CPU load
382///
383/// WebGui.HttpTimer: 10
384///
385/// To processing incoming http requests and websockets, THttpServer allocate 10 threads
386/// One have to increase this number if more simultaneous connections are expected:
387///
388/// WebGui.HttpThrds: 10
389///
390/// One also can configure usage of special thread of processing of http server requests
391///
392/// WebGui.HttpThrd: no
393///
394/// Extra threads can be used to send data to different clients via websocket (default no)
395///
396/// WebGui.SenderThrds: no
397///
398/// If required, one could change websocket timeouts (default is 10000 ms)
399///
400/// WebGui.HttpWSTmout: 10000
401///
402/// By default, THttpServer created in restricted mode which only allows websocket handlers
403/// and processes only very few other related http requests. For security reasons such mode
404/// should be always enabled. Only if it is really necessary to process all other kinds
405/// of HTTP requests, one could specify no for following parameter (default yes):
406///
407/// WebGui.WSOnly: yes
408///
409/// In some applications one may need to force longpoll websocket emulations from the beginning,
410/// for instance when clients connected via proxys. Although JSROOT should automatically fallback
411/// to longpoll engine, one can configure this directly (default no)
412///
413/// WebGui.WSLongpoll: no
414///
415/// Following parameter controls browser max-age caching parameter for files (default 3600)
416/// When 0 is specified, browser cache will be disabled
417///
418/// WebGui.HttpMaxAge: 3600
419///
420/// Also one can provide extra URL options for, see TCivetweb::Create for list of supported options
421///
422/// WebGui.HttpExtraArgs: winsymlinks=no
423///
424/// One also can configure usage of FastCGI server for web windows:
425///
426/// WebGui.FastCgiPort: 4000
427/// WebGui.FastCgiThreads: 10
428///
429/// To be able start web browser for such windows, one can provide real URL of the
430/// web server which will connect with that FastCGI instance:
431///
432/// WebGui.FastCgiServer: https://your_apache_server.com/root_cgi_path
433///
434/// For some custom applications one requires to load JavaScript modules or other files.
435/// For such applications one may require to load files from other locations which can be configured
436/// with AddServerLocation() method or directly via:
437///
438/// WebGui.ServerLocations: location1:/file/path/to/location1;location2:/file/path/to/location2
439
440
441
443{
444 if (gROOT->GetWebDisplay() == "off")
445 return false;
446
447 // explicitly protect server creation
448 std::lock_guard<std::recursive_mutex> grd(fMutex);
449
450 if (!fServer) {
451
452 fServer = std::make_unique<THttpServer>("basic_sniffer");
453
455 fUseHttpThrd = false;
456 } else {
457 auto serv_thrd = RWebWindowWSHandler::GetBoolEnv("WebGui.HttpThrd");
458 if (serv_thrd != -1)
459 fUseHttpThrd = serv_thrd != 0;
460 }
461
462 auto send_thrds = RWebWindowWSHandler::GetBoolEnv("WebGui.SenderThrds");
463 if (send_thrds != -1)
464 fUseSenderThreads = send_thrds != 0;
465
466 if (IsUseHttpThread())
467 fServer->CreateServerThread();
468
469 if (gApplication)
470 gApplication->Connect("Terminate(Int_t)", "THttpServer", fServer.get(), "SetTerminate()");
471
472 fServer->SetWSOnly(RWebWindowWSHandler::GetBoolEnv("WebGui.WSOnly", 1) != 0);
473
474 // this is location where all ROOT UI5 sources are collected
475 // normally it is $ROOTSYS/ui5 or <prefix>/ui5 location
476 TString ui5dir = gSystem->Getenv("ROOTUI5SYS");
477 if (ui5dir.Length() == 0)
478 ui5dir = gEnv->GetValue("WebGui.RootUi5Path","");
479
480 if (ui5dir.Length() == 0)
481 ui5dir.Form("%s/ui5", TROOT::GetDataDir().Data());
482
483 if (gSystem->ExpandPathName(ui5dir)) {
484 R__LOG_ERROR(WebGUILog()) << "Path to ROOT ui5 sources " << ui5dir << " not found, set ROOTUI5SYS correctly";
485 ui5dir = ".";
486 }
487
488 fServer->AddLocation("rootui5sys/", ui5dir.Data());
489
490 auto loc = GetServerLocations();
491 for (auto &entry : loc)
492 fServer->AddLocation(entry.first.c_str(), entry.second.c_str());
493 }
494
495 if (!with_http || fServer->IsAnyEngine())
496 return true;
497
498 int http_port = gEnv->GetValue("WebGui.HttpPort", 0);
499 int http_min = gEnv->GetValue("WebGui.HttpPortMin", 8800);
500 int http_max = gEnv->GetValue("WebGui.HttpPortMax", 9800);
501 int http_timer = gEnv->GetValue("WebGui.HttpTimer", 10);
502 int http_thrds = gEnv->GetValue("WebGui.HttpThreads", 10);
503 int http_wstmout = gEnv->GetValue("WebGui.HttpWSTmout", 10000);
504 int http_maxage = gEnv->GetValue("WebGui.HttpMaxAge", -1);
505 const char *extra_args = gEnv->GetValue("WebGui.HttpExtraArgs", "");
506 int fcgi_port = gEnv->GetValue("WebGui.FastCgiPort", 0);
507 int fcgi_thrds = gEnv->GetValue("WebGui.FastCgiThreads", 10);
508 const char *fcgi_serv = gEnv->GetValue("WebGui.FastCgiServer", "");
509 fLaunchTmout = gEnv->GetValue("WebGui.LaunchTmout", 30.);
510 bool assign_loopback = gWebWinLoopbackMode;
511 const char *http_bind = gEnv->GetValue("WebGui.HttpBind", "");
512 bool use_secure = RWebWindowWSHandler::GetBoolEnv("WebGui.UseHttps", 0) == 1;
513 const char *ssl_cert = gEnv->GetValue("WebGui.ServerCert", "rootserver.pem");
514
515 const char *unix_socket = gSystem->Getenv("ROOT_WEBGUI_SOCKET");
516 if (!unix_socket || !*unix_socket)
517 unix_socket = gEnv->GetValue("WebGui.UnixSocket", "");
518 const char *unix_socket_mode = gEnv->GetValue("WebGui.UnixSocketMode", "0700");
519 bool use_unix_socket = unix_socket && *unix_socket;
520
521 if (use_unix_socket)
522 fcgi_port = http_port = -1;
523
524 if (assign_loopback)
525 fcgi_port = -1;
526
527 int ntry = 100;
528
529 if ((http_port < 0) && (fcgi_port <= 0) && !use_unix_socket) {
530 R__LOG_ERROR(WebGUILog()) << "Not allowed to create HTTP server, check WebGui.HttpPort variable";
531 return false;
532 }
533
534 if ((http_timer > 0) && !IsUseHttpThread())
535 fServer->SetTimer(http_timer);
536
537 if (http_port < 0) {
538 ntry = 0;
539 } else {
540
541 if (http_port == 0)
542 gRandom->SetSeed(0);
543
544 if (http_max - http_min < ntry)
545 ntry = http_max - http_min;
546 }
547
548 if (fcgi_port > 0)
549 ntry++;
550
551 if (use_unix_socket)
552 ntry++;
553
554 while (ntry-- >= 0) {
555 if ((http_port == 0) && (fcgi_port <= 0) && !use_unix_socket) {
556 if ((http_min <= 0) || (http_max <= http_min)) {
557 R__LOG_ERROR(WebGUILog()) << "Wrong HTTP range configuration, check WebGui.HttpPortMin/Max variables";
558 return false;
559 }
560
561 http_port = (int)(http_min + (http_max - http_min) * gRandom->Rndm(1));
562 }
563
564 TString engine, url;
565 if (fcgi_port > 0) {
566 engine.Form("fastcgi:%d?thrds=%d", fcgi_port, fcgi_thrds);
567 if (!fServer->CreateEngine(engine))
568 return false;
569 if (fcgi_serv && (strlen(fcgi_serv) > 0))
570 fAddr = fcgi_serv;
571 if (http_port < 0)
572 return true;
573 fcgi_port = 0;
574 } else {
575 if (use_unix_socket) {
576 engine.Form("socket:%s?socket_mode=%s&", unix_socket, unix_socket_mode);
577 } else {
578 url = use_secure ? "https://" : "http://";
579 engine.Form("%s:%d?", (use_secure ? "https" : "http"), http_port);
580 if (assign_loopback) {
581 engine.Append("loopback&");
582 url.Append("localhost");
583 } else if (http_bind && (strlen(http_bind) > 0)) {
584 engine.Append(TString::Format("bind=%s&", http_bind));
585 url.Append(http_bind);
586 } else {
587 url.Append("localhost");
588 }
589 }
590
591 engine.Append(TString::Format("webgui&top=remote&thrds=%d&websocket_timeout=%d", http_thrds, http_wstmout));
592
593 if (http_maxage >= 0)
594 engine.Append(TString::Format("&max_age=%d", http_maxage));
595
596 if (use_secure && !strchr(ssl_cert,'&')) {
597 engine.Append("&ssl_cert=");
598 engine.Append(ssl_cert);
599 }
600
601 if (!use_unix_socket && !assign_loopback && extra_args && strlen(extra_args) > 0) {
602 engine.Append("&");
603 engine.Append(extra_args);
604 }
605
606 if (fServer->CreateEngine(engine)) {
607 if (use_unix_socket) {
608 fAddr = "socket://"; // fictional socket URL
609 fAddr.append(unix_socket);
610 // InformListener(std::string("socket:") + unix_socket + "\n");
611 } else if (http_port > 0) {
612 fAddr = url.Data();
613 fAddr.append(":");
614 fAddr.append(std::to_string(http_port));
615 // InformListener(std::string("http:") + std::to_string(http_port) + "\n");
616 }
617 return true;
618 }
619 use_unix_socket = false;
620 http_port = 0;
621 }
622 }
623
624 return false;
625}
626
627//////////////////////////////////////////////////////////////////////////////////////////
628/// Creates new window
629/// To show window, RWebWindow::Show() have to be called
630
631std::shared_ptr<RWebWindow> RWebWindowsManager::CreateWindow()
632{
633 // we book manager mutex for a longer operation, locked again in server creation
634 std::lock_guard<std::recursive_mutex> grd(fMutex);
635
636 if (!CreateServer()) {
637 R__LOG_ERROR(WebGUILog()) << "Cannot create server when creating window";
638 return nullptr;
639 }
640
641 std::shared_ptr<RWebWindow> win = std::make_shared<RWebWindow>();
642
643 if (!win) {
644 R__LOG_ERROR(WebGUILog()) << "Fail to create RWebWindow instance";
645 return nullptr;
646 }
647
648 double dflt_tmout = gEnv->GetValue("WebGui.OperationTmout", 50.);
649
650 auto wshandler = win->CreateWSHandler(Instance(), ++fIdCnt, dflt_tmout);
651
652 if (gEnv->GetValue("WebGui.RecordData", 0) > 0) {
653 std::string fname, prefix;
654 if (fIdCnt > 1) {
655 prefix = std::string("f") + std::to_string(fIdCnt) + "_";
656 fname = std::string("protcol") + std::to_string(fIdCnt) + ".json";
657 } else {
658 fname = "protocol.json";
659 }
660 win->RecordData(fname, prefix);
661 }
662
664 // special mode when window communication performed in THttpServer::ProcessRequests
665 // used only with python which create special thread - but is has to be ignored!!!
666 // therefore use main thread id to detect callbacks which are invoked only from that main thread
667 win->fUseProcessEvents = true;
668 win->fCallbacksThrdIdSet = gWebWinMainThrdSet;
669 win->fCallbacksThrdId = gWebWinMainThrd;
670 } else if (IsUseHttpThread())
671 win->UseServerThreads();
672
673 const char *token = gEnv->GetValue("WebGui.ConnToken", "");
674 if (token && *token)
675 win->SetConnToken(token);
676
677 fServer->RegisterWS(wshandler);
678
679 return win;
680}
681
682//////////////////////////////////////////////////////////////////////////////////////////
683/// Release all references to specified window
684/// Called from RWebWindow destructor
685
687{
688 if (win.fWSHandler)
689 fServer->UnregisterWS(win.fWSHandler);
690
691 if (fDeleteCallback)
693}
694
695//////////////////////////////////////////////////////////////////////////
696/// Provide URL address to access specified window from inside or from remote
697
698std::string RWebWindowsManager::GetUrl(RWebWindow &win, bool remote, std::string *produced_key)
699{
700 if (!fServer) {
701 R__LOG_ERROR(WebGUILog()) << "Server instance not exists when requesting window URL";
702 return "";
703 }
704
705 std::string addr = "/";
706 addr.append(win.fWSHandler->GetName());
707 addr.append("/");
708
709 bool qmark = false;
710
711 std::string key;
712
713 if (win.IsRequireAuthKey() || produced_key) {
714 key = win.GenerateKey();
715 R__ASSERT(!key.empty());
716 addr.append("?key=");
717 addr.append(key);
718 qmark = true;
719 std::unique_ptr<ROOT::RWebDisplayHandle> dummy;
720 win.AddDisplayHandle(false, key, dummy);
721 }
722
723 auto token = win.GetConnToken();
724 if (!token.empty()) {
725 addr.append(qmark ? "&" : "?");
726 addr.append("token=");
727 addr.append(token);
728 }
729
730 if (remote) {
731 if (!CreateServer(true) || fAddr.empty()) {
732 R__LOG_ERROR(WebGUILog()) << "Fail to start real HTTP server when requesting URL";
733 if (!key.empty())
734 win.RemoveKey(key);
735 return "";
736 }
737
738 addr = fAddr + addr;
739
740 if (!key.empty() && !fSessionKey.empty() && fUseSessionKey && win.IsRequireAuthKey())
741 addr += "#"s + fSessionKey;
742 }
743
744 if (produced_key)
745 *produced_key = key;
746
747 return addr;
748}
749
750///////////////////////////////////////////////////////////////////////////////////////////////////
751/// Show web window in specified location.
752///
753/// \param[inout] win web window by reference
754/// \param user_args specifies where and how display web window
755///
756/// As display args one can use string like "firefox" or "chrome" - these are two main supported web browsers.
757/// See RWebDisplayArgs::SetBrowserKind() for all available options. Default value for the browser can be configured
758/// when starting root with --web argument like: "root --web=chrome". When root started in web server mode "root --web=server",
759/// no any web browser will be started - just URL will be printout, which can be entered in any running web browser
760///
761/// If allowed, same window can be displayed several times (like for RCanvas or TCanvas)
762///
763/// Following parameters can be configured in rootrc file:
764///
765/// WebGui.Display: kind of display like chrome or firefox or browser, can be overwritten by --web=value command line argument
766/// WebGui.OnetimeKey: if configured requires unique key every time window is connected (default yes)
767/// WebGui.SingleConnMode: if configured the only connection and the only user of any widget is possible (default yes)
768/// WebGui.Chrome: full path to Google Chrome executable
769/// WebGui.ChromeBatch: command to start chrome in batch, used for image production, like "$prog --headless --disable-gpu $geometry $url"
770/// WebGui.ChromeHeadless: command to start chrome in headless mode, like "fork: --headless --disable-gpu $geometry $url"
771/// WebGui.ChromeInteractive: command to start chrome in interactive mode, like "$prog $geometry --app=\'$url\' &"
772/// WebGui.Firefox: full path to Mozilla Firefox executable
773/// WebGui.FirefoxHeadless: command to start Firefox in headless mode, like "fork:--headless --private-window --no-remote $profile $url"
774/// WebGui.FirefoxInteractive: command to start Firefox in interactive mode, like "$prog --private-window \'$url\' &"
775/// WebGui.FirefoxProfile: name of Firefox profile to use
776/// WebGui.FirefoxProfilePath: file path to Firefox profile
777/// WebGui.FirefoxRandomProfile: usage of random Firefox profile -1 never, 0 - only for headless mode (dflt), 1 - always
778/// WebGui.LaunchTmout: time required to start process in seconds (default 30 s)
779/// WebGui.OperationTmout: time required to perform WebWindow operation like execute command or update drawings
780/// WebGui.RecordData: if specified enables data recording for each web window 0 - off, 1 - on
781/// WebGui.JsonComp: compression factor for JSON conversion, if not specified - each widget uses own default values
782/// WebGui.ForceHttp: 0 - off (default), 1 - always create real http server to run web window
783/// WebGui.Console: -1 - output only console.error(), 0 - add console.warn(), 1 - add console.log() output
784/// WebGui.ConnCredits: 10 - number of packets which can be send by server or client without acknowledge from receiving side
785/// WebGui.openui5src: alternative location for openui5 like https://openui5.hana.ondemand.com/1.128.0/
786/// WebGui.openui5libs: list of pre-loaded ui5 libs like sap.m, sap.ui.layout, sap.ui.unified
787/// WebGui.openui5theme: openui5 theme like sap_belize (default) or sap_fiori_3
788///
789/// THttpServer-related parameters documented in \ref CreateServer method
790
792{
793 // silently ignore regular Show() calls in batch mode
794 if (!user_args.IsHeadless() && gROOT->IsWebDisplayBatch())
795 return 0;
796
797 // for embedded window no any browser need to be started
798 // also when off is specified, no browser should be started
799 if ((user_args.GetBrowserKind() == RWebDisplayArgs::kEmbedded) || (user_args.GetBrowserKind() == RWebDisplayArgs::kOff))
800 return 0;
801
802 // catch window showing, used by the RBrowser to embed some of ROOT widgets
803 if (fShowCallback)
804 if (fShowCallback(win, user_args)) {
805 // add dummy handle to pending connections, widget (like TWebCanvas) may wait until connection established
806 auto handle = std::make_unique<RWebDisplayHandle>("");
807 win.AddDisplayHandle(false, "", handle);
808 return 0;
809 }
810
811 if (!fServer) {
812 R__LOG_ERROR(WebGUILog()) << "Server instance not exists to show window";
813 return 0;
814 }
815
816 RWebDisplayArgs args(user_args);
817
818 if (args.IsHeadless() && !args.IsSupportHeadless()) {
819 R__LOG_ERROR(WebGUILog()) << "Cannot use batch mode with " << args.GetBrowserName();
820 return 0;
821 }
822
823 bool normal_http = RWebDisplayHandle::NeedHttpServer(args);
824 if (!normal_http && (gEnv->GetValue("WebGui.ForceHttp", 0) == 1))
825 normal_http = true;
826
827 std::string key;
828
829 std::string url = GetUrl(win, normal_http, &key);
830 // empty url indicates failure, which already printed by GetUrl method
831 if (url.empty())
832 return 0;
833
834 // we book manager mutex for a longer operation,
835 std::lock_guard<std::recursive_mutex> grd(fMutex);
836
837 args.SetUrl(url);
838
839 if (args.GetWidth() <= 0)
840 args.SetWidth(win.GetWidth());
841 if (args.GetHeight() <= 0)
842 args.SetHeight(win.GetHeight());
843 if (args.GetX() < 0)
844 args.SetX(win.GetX());
845 if (args.GetY() < 0)
846 args.SetY(win.GetY());
847
848 if (args.IsHeadless())
849 args.AppendUrlOpt("headless"); // used to create holder request
850
851 if (!args.IsHeadless() && normal_http) {
852 auto winurl = args.GetUrl();
853 winurl.erase(0, fAddr.length());
854 InformListener(std::string("win:") + winurl + "\n");
855 }
856
857 if (!args.IsHeadless() && ((args.GetBrowserKind() == RWebDisplayArgs::kServer) || gROOT->IsWebDisplayBatch()) /*&& (RWebWindowWSHandler::GetBoolEnv("WebGui.OnetimeKey") != 1)*/) {
858 std::cout << "New web window: " << args.GetUrl() << std::endl;
859 return 0;
860 }
861
862 if (fAddr.compare(0,9,"socket://") == 0)
863 return 0;
864
865#if !defined(R__MACOSX) && !defined(R__WIN32)
866 if (args.IsInteractiveBrowser()) {
867 const char *varname = "WebGui.CheckRemoteDisplay";
868 if (RWebWindowWSHandler::GetBoolEnv(varname, 1) == 1) {
869 const char *displ = gSystem->Getenv("DISPLAY");
870 if (displ && *displ && (*displ != ':')) {
871 gEnv->SetValue(varname, "no");
872 std::cout << "\n"
873 "ROOT web-based widget started in the session where DISPLAY set to " << displ << "\n" <<
874 "Means web browser will be displayed on remote X11 server which is usually very inefficient\n"
875 "One can start ROOT session in server mode like \"root -b --web=server:8877\" and forward http port to display node\n"
876 "Or one can use rootssh script to configure port forwarding and display web widgets automatically\n"
877 "Find more info on https://root.cern/for_developers/root7/#rbrowser\n"
878 "This message can be disabled by setting \"" << varname << ": no\" in .rootrc file\n";
879 }
880 }
881 }
882#endif
883
884 auto server = GetServer();
885
886 if (win.IsUseCurrentDir())
887 server->AddLocation("currentdir/", ".");
888
889 if (!normal_http)
890 args.SetHttpServer(server);
891
892 auto handle = RWebDisplayHandle::Display(args);
893
894 if (!handle) {
895 R__LOG_ERROR(WebGUILog()) << "Cannot display window in " << args.GetBrowserName();
896 if (!key.empty())
897 win.RemoveKey(key);
898 return 0;
899 }
900
901 return win.AddDisplayHandle(args.IsHeadless(), key, handle);
902}
903
904//////////////////////////////////////////////////////////////////////////
905/// Waits until provided check function or lambdas returns non-zero value
906/// Regularly calls WebWindow::Sync() method to let run event loop
907/// If call from the main thread, runs system events processing
908/// Check function has following signature: int func(double spent_tm)
909/// Parameter spent_tm is time in seconds, which already spent inside function
910/// Waiting will be continued, if function returns zero.
911/// First non-zero value breaks waiting loop and result is returned (or 0 if time is expired).
912/// If parameter timed is true, timelimit (in seconds) defines how long to wait
913
914int RWebWindowsManager::WaitFor(RWebWindow &win, WebWindowWaitFunc_t check, bool timed, double timelimit)
915{
916 int res = 0, cnt = 0;
917 double spent = 0.;
918
919 auto start = std::chrono::high_resolution_clock::now();
920
921 win.Sync(); // in any case call sync once to ensure
922
923 auto is_main_thread = IsMainThrd();
924
925 while ((res = check(spent)) == 0) {
926
927 if (is_main_thread)
929
930 win.Sync();
931
932 // only when first 1000 events processed, invoke sleep
933 if (++cnt > 1000)
934 std::this_thread::sleep_for(std::chrono::milliseconds(cnt > 5000 ? 10 : 1));
935
936 std::chrono::duration<double, std::milli> elapsed = std::chrono::high_resolution_clock::now() - start;
937
938 spent = elapsed.count() * 1e-3; // use ms precision
939
940 if (timed && (spent > timelimit))
941 return -3;
942 }
943
944 return res;
945}
946
947//////////////////////////////////////////////////////////////////////////
948/// Terminate http server and ROOT application
949
951{
952 if (fServer)
953 fServer->SetTerminate();
954
955 // set flag which sometimes checked in TSystem::ProcessEvents
956 gROOT->SetInterrupt(kTRUE);
957
958 if (gApplication)
959 TTimer::SingleShot(100, "TApplication", gApplication, "Terminate()");
960}
#define R__LOG_ERROR(...)
Definition RLogger.hxx:362
#define e(i)
Definition RSha256.hxx:103
static bool gWebWinMainThrdSet
static std::thread::id gWebWinMainThrd
This thread id used to identify main application thread, where ROOT event processing runs To inject c...
static bool gWebWinLoopbackMode
static bool gWebWinUseSessionKey
constexpr Ssiz_t kNPOS
Definition RtypesCore.h:117
constexpr Bool_t kTRUE
Definition RtypesCore.h:93
R__EXTERN TApplication * gApplication
R__EXTERN TEnv * gEnv
Definition TEnv.h:170
#define R__ASSERT(e)
Checks condition e and reports a fatal error if it's false.
Definition TError.h:125
winID h TVirtualViewer3D TVirtualGLPainter p
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void on
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void value
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t Float_t Float_t Float_t Int_t Int_t UInt_t UInt_t Rectangle_t Int_t Int_t Window_t win
char name[80]
Definition TGX11.cxx:110
#define gROOT
Definition TROOT.h:406
R__EXTERN TRandom * gRandom
Definition TRandom.h:62
R__EXTERN TSystem * gSystem
Definition TSystem.h:561
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
static bool NeedHttpServer(const RWebDisplayArgs &args)
Check if http server required for display.
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.
static void AddServerLocation(const std::string &server_prefix, const std::string &files_path)
Configure server location which can be used for loading of custom scripts or files When THttpServer i...
static std::string GenerateKey(int keylen=32)
Static method to generate cryptographic key Parameter keylen defines length of cryptographic key in b...
bool fUseSessionKey
! is session key has to be used for data signing
bool CreateServer(bool with_http=false)
Creates http server, if required - with real http engine (civetweb) One could configure concrete HTTP...
static void SetUseConnectionKey(bool on=true)
Enable or disable usage of connection key (default on) If enabled, each connection (and reconnection)...
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...
static void ClearServerLocations()
Clear all server locations Does not change configuration of already running HTTP server.
WebWindowShowCallback_t fShowCallback
! function called for each RWebWindow::Show call
WebWindowDeleteCallback_t fDeleteCallback
! function called when RWebWindow is destroyed
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.
std::string fSessionKey
! secret session key used on client to code connections keys
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 ...
static void SetUseSessionKey(bool on=true)
Enable or disable usage of session key (default on) If enabled, secrete session key used to calculate...
static void SetSingleConnMode(bool on=true)
Enable or disable single connection mode (default on) If enabled, one connection only with any web wi...
bool IsUseHttpThread() const
Returns true if http server use special thread for requests processing (default off)
bool fUseSenderThreads
! use extra threads for sending data from RWebWindow to clients
std::unique_ptr< THttpServer > fServer
! central communication with the all used displays
static void SetLoopbackMode(bool on=true)
Set loopback mode for THttpServer used for web widgets By default is on.
static bool IsMainThrd()
Returns true when called from main process Main process recognized at the moment when library is load...
static std::shared_ptr< RWebWindowsManager > & Instance()
Returns default window manager Used to display all standard ROOT elements like TCanvas or TFitPanel.
bool InformListener(const std::string &msg)
If ROOT_LISTENER_SOCKET variable is configured, message will be sent to that unix socket.
float fLaunchTmout
! timeout in seconds to start browser process, default 30s
static std::map< std::string, std::string > GetServerLocations()
Returns server locations as <std::string, std::string> Key is location name (with slash at the end) a...
std::string GetUrl(RWebWindow &win, bool remote=false, std::string *produced_key=nullptr)
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 IsLoopbackMode()
Returns true if loopback mode used by THttpServer for web widgets.
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:3066
virtual void SetSeed(ULong_t seed=0)
Set the random generator seed.
Definition TRandom.cxx:615
Double_t Rndm() override
Machine independent random number generator.
Definition TRandom.cxx:559
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:417
Bool_t EndsWith(const char *pat, ECaseCompare cmp=kExact) const
Return true if string ends with the specified string.
Definition TString.cxx:2244
Ssiz_t First(char c) const
Find first occurrence of a character c.
Definition TString.cxx:538
const char * Data() const
Definition TString.h:376
TObjArray * Tokenize(const TString &delim) const
This function is used to isolate sequential tokens in a TString.
Definition TString.cxx:2264
TString & Append(const char *cs)
Definition TString.h:572
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:2378
static TString Itoa(Int_t value, Int_t base)
Converts an Int_t to a TString with respect to the base specified (2-36).
Definition TString.cxx:2092
void Form(const char *fmt,...)
Formats a string using a printf style format descriptor.
Definition TString.cxx:2356
virtual Bool_t ExpandPathName(TString &path)
Expand a pathname getting rid of special shell characters like ~.
Definition TSystem.cxx:1274
virtual const char * Getenv(const char *env)
Get environment variable.
Definition TSystem.cxx:1665
virtual Int_t GetCryptoRandom(void *buf, Int_t len)
Return cryptographic random number Fill provided buffer with random values Returns number of bytes wr...
Definition TSystem.cxx:266
virtual void Sleep(UInt_t milliSec)
Sleep milliSec milli seconds.
Definition TSystem.cxx:437
virtual Bool_t ProcessEvents()
Process pending events (GUI, timers, sockets).
Definition TSystem.cxx:416
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
const Int_t n
Definition legend1.C:16
tbb::task_arena is an alias of tbb::interface7::task_arena, which doesn't allow to forward declare tb...
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...