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