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