Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
RWebWindow.cxx
Go to the documentation of this file.
1// Author: Sergey Linev <s.linev@gsi.de>
2// Date: 2017-10-16
3
4/*************************************************************************
5 * Copyright (C) 1995-2019, Rene Brun and Fons Rademakers. *
6 * All rights reserved. *
7 * *
8 * For the licensing terms see $ROOTSYS/LICENSE. *
9 * For the list of contributors see $ROOTSYS/README/CREDITS. *
10 *************************************************************************/
11
12#include <ROOT/RWebWindow.hxx>
13
15#include <ROOT/RLogger.hxx>
16
18#include "THttpCallArg.h"
19#include "TUrl.h"
20#include "TError.h"
21#include "TROOT.h"
22#include "TSystem.h"
23
24#include <cstring>
25#include <cstdlib>
26#include <utility>
27#include <cassert>
28#include <algorithm>
29#include <fstream>
30
31// must be here because of defines
33
34using namespace ROOT;
35using namespace std::string_literals;
36
37//////////////////////////////////////////////////////////////////////////////////////////
38/// Destructor for WebConn
39/// Notify special HTTP request which blocks headless browser from exit
40
42{
43 if (fHold) {
44 fHold->SetTextContent("console.log('execute holder script'); if (window) setTimeout (window.close, 1000); if (window) window.close();");
45 fHold->NotifyCondition();
46 fHold.reset();
47 }
48}
49
50
51std::string RWebWindow::gJSROOTsettings = "";
52
53
54/** \class ROOT::RWebWindow
55\ingroup webdisplay
56
57Represents web window, which can be shown in web browser or any other supported environment
58
59Window can be configured to run either in the normal or in the batch (headless) mode.
60In second case no any graphical elements will be created. For the normal window one can configure geometry
61(width and height), which are applied when window shown.
62
63Each window can be shown several times (if allowed) in different places - either as the
64CEF (chromium embedded) window or in the standard web browser. When started, window will open and show
65HTML page, configured with RWebWindow::SetDefaultPage() method.
66
67Typically (but not necessarily) clients open web socket connection to the window and one can exchange data,
68using RWebWindow::Send() method and call-back function assigned via RWebWindow::SetDataCallBack().
69
70*/
71
72
73//////////////////////////////////////////////////////////////////////////////////////////
74/// RWebWindow constructor
75/// Should be defined here because of std::unique_ptr<RWebWindowWSHandler>
76
78{
79 fRequireAuthKey = RWebWindowWSHandler::GetBoolEnv("WebGui.OnetimeKey", 1) == 1; // does authentication key really required
80}
81
82//////////////////////////////////////////////////////////////////////////////////////////
83/// RWebWindow destructor
84/// Closes all connections and remove window from manager
85
87{
88 StopThread();
89
90 if (fMaster) {
91 std::vector<MasterConn> lst;
92 {
93 std::lock_guard<std::mutex> grd(fConnMutex);
94 std::swap(lst, fMasterConns);
95 }
96
97 for (auto &entry : lst)
98 fMaster->RemoveEmbedWindow(entry.connid, entry.channel);
99 fMaster.reset();
100 }
101
102 if (fWSHandler)
103 fWSHandler->SetDisabled();
104
105 if (fMgr) {
106
107 // make copy of all connections
108 auto lst = GetWindowConnections();
109
110 {
111 // clear connections vector under mutex
112 std::lock_guard<std::mutex> grd(fConnMutex);
113 fConn.clear();
114 fPendingConn.clear();
115 }
116
117 for (auto &conn : lst) {
118 conn->fActive = false;
119 for (auto &elem: conn->fEmbed)
120 elem.second->RemoveMasterConnection();
121 conn->fEmbed.clear();
122 }
123
124 fMgr->Unregister(*this);
125 }
126}
127
128//////////////////////////////////////////////////////////////////////////////////////////
129/// Configure window to show some of existing JSROOT panels
130/// It uses "file:rootui5sys/panel/panel.html" as default HTML page
131/// At the moment only FitPanel is existing
132
133void RWebWindow::SetPanelName(const std::string &name)
134{
135 {
136 std::lock_guard<std::mutex> grd(fConnMutex);
137 if (!fConn.empty()) {
138 R__LOG_ERROR(WebGUILog()) << "Cannot configure panel when connection exists";
139 return;
140 }
141 }
142
144 SetDefaultPage("file:rootui5sys/panel/panel.html");
145 if (fPanelName.find("localapp.") == 0)
146 SetUseCurrentDir(true);
147}
148
149//////////////////////////////////////////////////////////////////////////////////////////
150/// Assigns manager reference, window id and creates websocket handler, used for communication with the clients
151
152std::shared_ptr<RWebWindowWSHandler>
153RWebWindow::CreateWSHandler(std::shared_ptr<RWebWindowsManager> mgr, unsigned id, double tmout)
154{
155 fMgr = mgr;
156 fId = id;
158
159 fSendMT = fMgr->IsUseSenderThreads();
160 fWSHandler = std::make_shared<RWebWindowWSHandler>(*this, Form("win%u", GetId()));
161
162 return fWSHandler;
163}
164
165//////////////////////////////////////////////////////////////////////////////////////////
166/// Return URL string to connect web window
167/// URL typically includes extra parameters required for connection with the window like
168/// `http://localhost:9635/win1/?key=<connection_key>#<session_key>`
169/// When \param remote is true, real HTTP server will be started automatically and
170/// widget can be connected from the web browser. If \param remote is false,
171/// HTTP server will not be started and window can be connected only from ROOT application itself.
172/// !!! WARNING - do not invoke this method without real need, each URL consumes resources in widget and in http server
173
174std::string RWebWindow::GetUrl(bool remote)
175{
176 return fMgr->GetUrl(*this, remote);
177}
178
179//////////////////////////////////////////////////////////////////////////////////////////
180/// Return THttpServer instance serving requests to the window
181
183{
184 return fMgr->GetServer();
185}
186
187//////////////////////////////////////////////////////////////////////////////////////////
188/// Show window in specified location
189/// \see ROOT::RWebWindowsManager::Show
190/// \return (future) connection id (or 0 when fails)
191
193{
194 return fMgr->ShowWindow(*this, args);
195}
196
197//////////////////////////////////////////////////////////////////////////////////////////
198/// Start headless browser for specified window
199/// Normally only single instance is used, but many can be created
200/// \see ROOT::RWebWindowsManager::Show
201/// \return (future) connection id (or 0 when fails)
202
204{
205 unsigned connid = 0;
206 if (!create_new)
207 connid = FindHeadlessConnection();
208 if (!connid) {
209 RWebDisplayArgs args;
210 args.SetHeadless(true);
211 connid = fMgr->ShowWindow(*this, args);
212 }
213 return connid;
214}
215
216//////////////////////////////////////////////////////////////////////////////////////////
217/// Returns connection id of window running in headless mode
218/// This can be special connection which may run picture production jobs in background
219/// Connection to that job may not be initialized yet
220/// If connection does not exists, returns 0
221
223{
224 std::lock_guard<std::mutex> grd(fConnMutex);
225
226 for (auto &entry : fPendingConn) {
227 if (entry->fHeadlessMode)
228 return entry->fConnId;
229 }
230
231 for (auto &conn : fConn) {
232 if (conn->fHeadlessMode)
233 return conn->fConnId;
234 }
235
236 return 0;
237}
238
239//////////////////////////////////////////////////////////////////////////////////////////
240/// Returns first connection id where window is displayed
241/// It could be that connection(s) not yet fully established - but also not timed out
242/// Batch jobs will be ignored here
243/// Returns 0 if connection not exists
244
246{
247 std::lock_guard<std::mutex> grd(fConnMutex);
248
249 for (auto &entry : fPendingConn) {
250 if (!entry->fHeadlessMode)
251 return entry->fConnId;
252 }
253
254 for (auto &conn : fConn) {
255 if (!conn->fHeadlessMode)
256 return conn->fConnId;
257 }
258
259 return 0;
260}
261
262//////////////////////////////////////////////////////////////////////////////////////////
263/// Find connection with given websocket id
264
265std::shared_ptr<RWebWindow::WebConn> RWebWindow::FindConnection(unsigned wsid)
266{
267 std::lock_guard<std::mutex> grd(fConnMutex);
268
269 for (auto &conn : fConn) {
270 if (conn->fWSId == wsid)
271 return conn;
272 }
273
274 return nullptr;
275}
276
277//////////////////////////////////////////////////////////////////////////////////////////
278/// Signal that connection is closing
279
280void RWebWindow::ClearConnection(std::shared_ptr<WebConn> &conn, bool provide_signal)
281{
282 if (!conn)
283 return;
284
285 if (provide_signal)
286 ProvideQueueEntry(conn->fConnId, kind_Disconnect, ""s);
287 for (auto &elem: conn->fEmbed) {
288 if (provide_signal)
289 elem.second->ProvideQueueEntry(conn->fConnId, kind_Disconnect, ""s);
290 elem.second->RemoveMasterConnection(conn->fConnId);
291 }
292
293 conn->fEmbed.clear();
294}
295
296//////////////////////////////////////////////////////////////////////////////////////////
297/// Remove connection with given websocket id
298
299std::shared_ptr<RWebWindow::WebConn> RWebWindow::RemoveConnection(unsigned wsid, bool provide_signal)
300{
301 std::shared_ptr<WebConn> res;
302
303 {
304 std::lock_guard<std::mutex> grd(fConnMutex);
305
306 for (size_t n = 0; n < fConn.size(); ++n)
307 if (fConn[n]->fWSId == wsid) {
308 res = std::move(fConn[n]);
309 fConn.erase(fConn.begin() + n);
310 res->fActive = false;
311 res->fWasFirst = (n == 0);
312 break;
313 }
314 }
315
317
318 return res;
319}
320
321//////////////////////////////////////////////////////////////////////////////////////////
322/// Add new master connection
323/// If there are many connections - only same master is allowed
324
325void RWebWindow::AddMasterConnection(std::shared_ptr<RWebWindow> window, unsigned connid, int channel)
326{
327 if (fMaster && fMaster != window)
328 R__LOG_ERROR(WebGUILog()) << "Cannot configure different masters at the same time";
329
330 fMaster = window;
331
332 std::lock_guard<std::mutex> grd(fConnMutex);
333
334 fMasterConns.emplace_back(connid, channel);
335}
336
337//////////////////////////////////////////////////////////////////////////////////////////
338/// Get list of master connections
339
340std::vector<RWebWindow::MasterConn> RWebWindow::GetMasterConnections(unsigned connid) const
341{
342 std::vector<MasterConn> lst;
343 if (!fMaster)
344 return lst;
345
346 std::lock_guard<std::mutex> grd(fConnMutex);
347
348 for (auto & entry : fMasterConns)
349 if (!connid || entry.connid == connid)
350 lst.emplace_back(entry);
351
352 return lst;
353}
354
355//////////////////////////////////////////////////////////////////////////////////////////
356/// Remove master connection - if any
357
359{
360 if (!fMaster) return;
361
362 bool isany = false;
363
364 {
365 std::lock_guard<std::mutex> grd(fConnMutex);
366
367 if (connid == 0) {
368 fMasterConns.clear();
369 } else {
370 for (auto iter = fMasterConns.begin(); iter != fMasterConns.end(); ++iter)
371 if (iter->connid == connid) {
372 fMasterConns.erase(iter);
373 break;
374 }
375 }
376
377 isany = fMasterConns.size() > 0;
378 }
379
380 if (!isany)
381 fMaster.reset();
382}
383
384//////////////////////////////////////////////////////////////////////////////////////////
385/// Process special http request, used to hold headless browser running
386/// Such requests should not be replied for the long time
387/// Be aware that function called directly from THttpServer thread, which is not same thread as window
388
389bool RWebWindow::ProcessBatchHolder(std::shared_ptr<THttpCallArg> &arg)
390{
391 std::string query = arg->GetQuery();
392
393 if (query.compare(0, 4, "key=") != 0)
394 return false;
395
396 std::string key = query.substr(4);
397
398 std::shared_ptr<THttpCallArg> prev;
399
400 bool found_key = false;
401
402 // use connection mutex to access hold request
403 {
404 std::lock_guard<std::mutex> grd(fConnMutex);
405 for (auto &entry : fPendingConn) {
406 if (entry->fKey == key) {
407 assert(!found_key); // indicate error if many same keys appears
408 found_key = true;
409 prev = std::move(entry->fHold);
410 entry->fHold = arg;
411 }
412 }
413
414 for (auto &conn : fConn) {
415 if (conn->fKey == key) {
416 assert(!found_key); // indicate error if many same keys appears
417 prev = std::move(conn->fHold);
418 conn->fHold = arg;
419 found_key = true;
420 }
421 }
422 }
423
424 if (prev) {
425 prev->SetTextContent("console.log('execute holder script'); if (window) window.close();");
426 prev->NotifyCondition();
427 }
428
429 return found_key;
430}
431
432//////////////////////////////////////////////////////////////////////////////////////////
433/// Provide data to user callback
434/// User callback must be executed in the window thread
435
436void RWebWindow::ProvideQueueEntry(unsigned connid, EQueueEntryKind kind, std::string &&arg)
437{
438 {
439 std::lock_guard<std::mutex> grd(fInputQueueMutex);
440 fInputQueue.emplace(connid, kind, std::move(arg));
441 }
442
443 // if special python mode is used, process events called from special thread
444 // there is no other way to get regular calls in main python thread,
445 // therefore invoke widgets callbacks directly - which potentially can be dangerous
447}
448
449//////////////////////////////////////////////////////////////////////////////////////////
450/// Invoke callbacks with existing data
451/// Must be called from appropriate thread
452
454{
455 if (fCallbacksThrdIdSet && (fCallbacksThrdId != std::this_thread::get_id()) && !force)
456 return;
457
458 while (true) {
459 unsigned connid;
460 EQueueEntryKind kind;
461 std::string arg;
462
463 {
464 std::lock_guard<std::mutex> grd(fInputQueueMutex);
465 if (fInputQueue.size() == 0)
466 return;
467 auto &entry = fInputQueue.front();
468 connid = entry.fConnId;
469 kind = entry.fKind;
470 arg = std::move(entry.fData);
471 fInputQueue.pop();
472 }
473
474 switch (kind) {
475 case kind_None: break;
476 case kind_Connect:
477 if (fConnCallback)
478 fConnCallback(connid);
479 break;
480 case kind_Data:
481 if (fDataCallback)
482 fDataCallback(connid, arg);
483 break;
484 case kind_Disconnect:
486 fDisconnCallback(connid);
487 break;
488 }
489 }
490}
491
492//////////////////////////////////////////////////////////////////////////////////////////
493/// Add display handle and associated key
494/// Key is large random string generated when starting new window
495/// When client is connected, key should be supplied to correctly identify it
496
497unsigned RWebWindow::AddDisplayHandle(bool headless_mode, const std::string &key, std::unique_ptr<RWebDisplayHandle> &handle)
498{
499 std::lock_guard<std::mutex> grd(fConnMutex);
500
501 for (auto &entry : fPendingConn) {
502 if (entry->fKey == key) {
503 entry->fHeadlessMode = headless_mode;
504 std::swap(entry->fDisplayHandle, handle);
505 return entry->fConnId;
506 }
507 }
508
509 auto conn = std::make_shared<WebConn>(++fConnCnt, headless_mode, key);
510
511 std::swap(conn->fDisplayHandle, handle);
512
513 fPendingConn.emplace_back(conn);
514
515 return fConnCnt;
516}
517
518
519//////////////////////////////////////////////////////////////////////////////////////////
520/// Check if provided hash, ntry parameters from the connection request could be accepted
521/// \param conn shared pointer to the web connection
522/// \param hash provided hash value which should match with HMAC hash for generated before connection key
523/// \param ntry connection attempt number provided together with request, must come in increasing order
524/// \param remote boolean flag indicating if request comming from remote (via real http),
525/// for local displays like Qt5 or CEF simpler connection rules are applied
526/// \param test_first_time true if hash/ntry tested for the first time, false appears only with
527/// websocket when connection accepted by server
528
529bool RWebWindow::_CanTrustIn(std::shared_ptr<WebConn> &conn, const std::string &hash, const std::string &ntry, bool remote, bool test_first_time)
530{
531 if (!conn)
532 return false;
533
534 int intry = ntry.empty() ? -1 : std::stoi(ntry);
535
536 auto msg = TString::Format("attempt_%s", ntry.c_str());
537 auto expected = HMAC(conn->fKey, fMgr->fUseSessionKey && remote ? fMgr->fSessionKey : ""s, msg.Data(), msg.Length());
538
539 if (!IsRequireAuthKey())
540 return (conn->fKey.empty() && hash.empty()) || (hash == conn->fKey) || (hash == expected);
541
542 // for local connection simple key can be used
543 if (!remote && ((hash == conn->fKey) || (hash == expected)))
544 return true;
545
546 if (hash == expected) {
547 if (test_first_time) {
548 if (conn->fKeyUsed >= intry) {
549 // this is indication of main in the middle, already checked hashed value was shown again!!!
550 // client sends id with increasing counter, if previous value is presented it is BAD
551 R__LOG_ERROR(WebGUILog()) << "Detect connection hash send before, possible replay attack!!!";
552 return false;
553 }
554 // remember counter, it should prevent trying previous hash values
555 conn->fKeyUsed = intry;
556 } else {
557 if (conn->fKeyUsed != intry) {
558 // this is rather error condition, should never happen
559 R__LOG_ERROR(WebGUILog()) << "Connection failure with HMAC signature check";
560 return false;
561 }
562 }
563 return true;
564 }
565
566 return false;
567}
568
569
570//////////////////////////////////////////////////////////////////////////////////////////
571/// Returns true if provided key value already exists (in processes map or in existing connections)
572/// In special cases one also can check if key value exists as newkey
573
574bool RWebWindow::HasKey(const std::string &key, bool also_newkey) const
575{
576 if (key.empty())
577 return false;
578
579 std::lock_guard<std::mutex> grd(fConnMutex);
580
581 for (auto &entry : fPendingConn) {
582 if (entry->fKey == key)
583 return true;
584 }
585
586 for (auto &conn : fConn) {
587 if (conn->fKey == key)
588 return true;
589 if (also_newkey && (conn->fNewKey == key))
590 return true;
591 }
592
593 return false;
594}
595
596//////////////////////////////////////////////////////////////////////////////////////////
597/// Removes all connections with the key
598
599void RWebWindow::RemoveKey(const std::string &key)
600{
602
603 {
604 std::lock_guard<std::mutex> grd(fConnMutex);
605
606 auto pred = [&](std::shared_ptr<WebConn> &e) {
607 if (e->fKey == key) {
608 lst.emplace_back(e);
609 return true;
610 }
611 return false;
612 };
613
614 fPendingConn.erase(std::remove_if(fPendingConn.begin(), fPendingConn.end(), pred), fPendingConn.end());
615 fConn.erase(std::remove_if(fConn.begin(), fConn.end(), pred), fConn.end());
616 }
617
618 for (auto &conn : lst)
619 ClearConnection(conn, conn->fActive);
620}
621
622
623//////////////////////////////////////////////////////////////////////////////////////////
624/// Generate new unique key for the window
625
626std::string RWebWindow::GenerateKey() const
627{
629
630 R__ASSERT((!IsRequireAuthKey() || (!HasKey(key) && (key != fMgr->fSessionKey))) && "Fail to generate window connection key");
631
632 return key;
633}
634
635//////////////////////////////////////////////////////////////////////////////////////////
636/// Check if started process(es) establish connection. After timeout such processed will be killed
637/// Method invoked from http server thread, therefore appropriate mutex must be used on all relevant data
638
640{
641 if (!fMgr) return;
642
643 timestamp_t stamp = std::chrono::system_clock::now();
644
645 float launchTmout = fMgr->GetLaunchTmout();
646 float reconnectTmout = fMgr->GetReconnectTmout();
647
648 ConnectionsList_t selected;
649
650 bool doClearOnClose = false;
651
652 {
653 std::lock_guard<std::mutex> grd(fConnMutex);
654
655 auto pred = [&](std::shared_ptr<WebConn> &e) {
656 std::chrono::duration<double> diff = stamp - e->fSendStamp;
657
658 float tmout = e->fWasEstablished ? reconnectTmout : launchTmout;
659
660 if (diff.count() > tmout) {
661 R__LOG_DEBUG(0, WebGUILog()) << "Remove pending connection " << e->fKey << " after " << diff.count() << " sec";
662 selected.emplace_back(e);
663 return true;
664 }
665
666 return false;
667 };
668
669 fPendingConn.erase(std::remove_if(fPendingConn.begin(), fPendingConn.end(), pred), fPendingConn.end());
670
671 doClearOnClose = (selected.size() > 0) && (fPendingConn.size() == 0) && (fConn.size() == 0);
672 }
673
674 if (doClearOnClose)
675 fClearOnClose.reset();
676}
677
678
679//////////////////////////////////////////////////////////////////////////////////////////
680/// Check if there are connection which are inactive for longer time
681/// For instance, batch browser will be stopped if no activity for 30 sec is there
682
684{
685 timestamp_t stamp = std::chrono::system_clock::now();
686
687 double batch_tmout = 20.;
688
689 std::vector<std::shared_ptr<WebConn>> clr;
690
691 {
692 std::lock_guard<std::mutex> grd(fConnMutex);
693
694 auto pred = [&](std::shared_ptr<WebConn> &conn) {
695 std::chrono::duration<double> diff = stamp - conn->fSendStamp;
696 // introduce large timeout
697 if ((diff.count() > batch_tmout) && conn->fHeadlessMode) {
698 conn->fActive = false;
699 clr.emplace_back(conn);
700 return true;
701 }
702 return false;
703 };
704
705 fConn.erase(std::remove_if(fConn.begin(), fConn.end(), pred), fConn.end());
706 }
707
708 for (auto &entry : clr)
709 ClearConnection(entry, true);
710}
711
712/////////////////////////////////////////////////////////////////////////
713/// Configure maximal number of allowed connections - 0 is unlimited
714/// Will not affect already existing connections
715/// Default is 1 - the only client is allowed
716/// Because of security reasons setting number of allowed connections is not sufficient now.
717/// To enable multi-connection mode, one also has to call
718/// `ROOT::RWebWindowsManager::SetSingleConnMode(false);`
719/// before creating of the RWebWindow instance
720
722{
723 bool single_conn_mode = RWebWindowWSHandler::GetBoolEnv("WebGui.SingleConnMode", 1) == 1;
724
725 std::lock_guard<std::mutex> grd(fConnMutex);
726
728}
729
730/////////////////////////////////////////////////////////////////////////
731/// returns configured connections limit (0 - default)
732
734{
735 std::lock_guard<std::mutex> grd(fConnMutex);
736
737 return fConnLimit;
738}
739
740/////////////////////////////////////////////////////////////////////////
741/// Configures connection token (default none)
742/// When specified, in URL of webpage such token should be provided as &token=value parameter,
743/// otherwise web window will refuse connection
744
745void RWebWindow::SetConnToken(const std::string &token)
746{
747 std::lock_guard<std::mutex> grd(fConnMutex);
748
749 fConnToken = token;
750}
751
752/////////////////////////////////////////////////////////////////////////
753/// Returns configured connection token
754
755std::string RWebWindow::GetConnToken() const
756{
757 std::lock_guard<std::mutex> grd(fConnMutex);
758
759 return fConnToken;
760}
761
762//////////////////////////////////////////////////////////////////////////////////////////
763/// Processing of websockets call-backs, invoked from RWebWindowWSHandler
764/// Method invoked from http server thread, therefore appropriate mutex must be used on all relevant data
765
767{
768 if (arg.GetWSId() == 0)
769 return true;
770
771 bool is_longpoll = arg.GetFileName() && ("root.longpoll"s == arg.GetFileName()),
772 is_remote = arg.GetTopName() && ("remote"s == arg.GetTopName());
773
774 // do not allow longpoll requests for loopback device
776 return false;
777
778 if (arg.IsMethod("WS_CONNECT")) {
779 TUrl url;
780 url.SetOptions(arg.GetQuery());
781 std::string key, ntry;
782 if(url.HasOption("key"))
783 key = url.GetValueFromOptions("key");
784 if(url.HasOption("ntry"))
785 ntry = url.GetValueFromOptions("ntry");
786
787 std::lock_guard<std::mutex> grd(fConnMutex);
788
789 if (is_longpoll && !is_remote && ntry == "1"s) {
790 // special workaround for local displays like qt6/cef
791 // they are not disconnected regularly when page reload is invoked
792 // therefore try to detect if new key is applied
793 for (unsigned indx = 0; indx < fConn.size(); indx++) {
794 if (!fConn[indx]->fNewKey.empty() && (key == HMAC(fConn[indx]->fNewKey, ""s, "attempt_1", 9))) {
795 auto conn = std::move(fConn[indx]);
796 fConn.erase(fConn.begin() + indx);
797 conn->fKeyUsed = 0;
798 conn->fKey = conn->fNewKey;
799 conn->fNewKey.clear();
800 conn->fConnId = ++fConnCnt; // change connection id to avoid confusion
801 conn->fWasFirst = indx == 0;
802 conn->ResetData();
803 conn->ResetStamps(); // reset stamps, after timeout connection wll be removed
804 fPendingConn.emplace_back(conn);
805 break;
806 }
807 }
808 }
809
810 // refuse connection when number of connections exceed limit
811 if (fConnLimit && (fConn.size() >= fConnLimit))
812 return false;
813
814 if (!fConnToken.empty()) {
815 // refuse connection which does not provide proper token
816 if (!url.HasOption("token") || (fConnToken != url.GetValueFromOptions("token"))) {
817 R__LOG_DEBUG(0, WebGUILog()) << "Refuse connection without proper token";
818 return false;
819 }
820 }
821
822 if (!IsRequireAuthKey())
823 return true;
824
825 if(key.empty()) {
826 R__LOG_DEBUG(0, WebGUILog()) << "key parameter not provided in url";
827 return false;
828 }
829
830 for (auto &conn : fPendingConn)
831 if (_CanTrustIn(conn, key, ntry, is_remote, true /* test_first_time */))
832 return true;
833
834 return false;
835 }
836
837 if (arg.IsMethod("WS_READY")) {
838
839 if (FindConnection(arg.GetWSId())) {
840 R__LOG_ERROR(WebGUILog()) << "WSHandle with given websocket id " << arg.GetWSId() << " already exists";
841 return false;
842 }
843
844 std::shared_ptr<WebConn> conn;
845 std::string key, ntry;
846
847 TUrl url;
848 url.SetOptions(arg.GetQuery());
849 if (url.HasOption("key"))
850 key = url.GetValueFromOptions("key");
851 if (url.HasOption("ntry"))
852 ntry = url.GetValueFromOptions("ntry");
853
854 std::lock_guard<std::mutex> grd(fConnMutex);
855
856 // check if in pending connections exactly this combination was checked
857 for (size_t n = 0; n < fPendingConn.size(); ++n)
858 if (_CanTrustIn(fPendingConn[n], key, ntry, is_remote, false /* test_first_time */)) {
859 conn = std::move(fPendingConn[n]);
860 fPendingConn.erase(fPendingConn.begin() + n);
861 break;
862 }
863
864 if (conn) {
865 conn->fWSId = arg.GetWSId();
866 conn->fActive = true;
867 conn->fWasEstablished = true;
868 conn->fRecvSeq = 0;
869 conn->fSendSeq = 1;
870 // preserve key for longpoll or when with session key used for HMAC hash of messages
871 // conn->fKey.clear();
872 conn->ResetStamps();
873 // remove files which are required for startup
874 if (conn->fDisplayHandle)
875 conn->fDisplayHandle->RemoveStartupFiles();
876 if (conn->fWasFirst)
877 fConn.emplace(fConn.begin(), conn);
878 else
879 fConn.emplace_back(conn);
880 return true;
881 } else if (!IsRequireAuthKey() && (!fConnLimit || (fConn.size() < fConnLimit))) {
882 fConn.emplace_back(std::make_shared<WebConn>(++fConnCnt, arg.GetWSId()));
883 return true;
884 }
885
886 // reject connection, should not really happen
887 return false;
888 }
889
890 // special security check for the longpoll requests
891 if(is_longpoll) {
892 auto conn = FindConnection(arg.GetWSId());
893 if (!conn)
894 return false;
895
896 TUrl url;
897 url.SetOptions(arg.GetQuery());
898
899 std::string key, ntry;
900 if(url.HasOption("key"))
901 key = url.GetValueFromOptions("key");
902 if(url.HasOption("ntry"))
903 ntry = url.GetValueFromOptions("ntry");
904
905 if (!_CanTrustIn(conn, key, ntry, is_remote, true /* test_first_time */))
906 return false;
907 }
908
909 if (arg.IsMethod("WS_CLOSE")) {
910 // connection is closed, one can remove handle, associated window will be closed
911
912 auto conn = RemoveConnection(arg.GetWSId(), true);
913
914 if (conn) {
915 bool doClearOnClose = false;
916 if (!conn->fNewKey.empty() && (fMgr->GetReconnectTmout() > 0)) {
917 // case when same handle want to be reused by client with new key
918 std::lock_guard<std::mutex> grd(fConnMutex);
919 conn->fKeyUsed = 0;
920 conn->fKey = conn->fNewKey;
921 conn->fNewKey.clear();
922 conn->fConnId = ++fConnCnt; // change connection id to avoid confusion
923 conn->fWasEstablished = true;
924 conn->ResetData();
925 conn->ResetStamps(); // reset stamps, after timeout connection wll be removed
926 fPendingConn.emplace_back(conn);
927 } else {
928 std::lock_guard<std::mutex> grd(fConnMutex);
929 doClearOnClose = (fPendingConn.size() == 0) && (fConn.size() == 0);
930 }
931
932 if (doClearOnClose)
933 fClearOnClose.reset();
934 }
935
936 return true;
937 }
938
939 if (!arg.IsMethod("WS_DATA")) {
940 R__LOG_ERROR(WebGUILog()) << "only WS_DATA request expected!";
941 return false;
942 }
943
944 auto conn = FindConnection(arg.GetWSId());
945
946 if (!conn) {
947 R__LOG_ERROR(WebGUILog()) << "Get websocket data without valid connection - ignore!!!";
948 return false;
949 }
950
951 if (arg.GetPostDataLength() <= 0)
952 return true;
953
954 // here start testing of HMAC in the begin of the message
955
956 const char *buf0 = (const char *) arg.GetPostData();
957 Long_t data_len = arg.GetPostDataLength();
958
959 const char *buf = strchr(buf0, ':');
960 if (!buf) {
961 R__LOG_ERROR(WebGUILog()) << "missing separator for HMAC checksum";
962 return false;
963 }
964
965 Int_t code_len = buf - buf0;
966 data_len -= code_len + 1;
967 buf++; // starting of normal message
968
969 if (data_len < 0) {
970 R__LOG_ERROR(WebGUILog()) << "no any data after HMAC checksum";
971 return false;
972 }
973
974 bool is_none = strncmp(buf0, "none:", 5) == 0, is_match = false;
975
976 if (!is_none) {
977 std::string hmac = HMAC(conn->fKey, fMgr->fSessionKey, buf, data_len);
978
979 is_match = (code_len == (Int_t) hmac.length()) && (strncmp(buf0, hmac.c_str(), code_len) == 0);
980 } else if (!fMgr->fUseSessionKey) {
981 // no packet signing without session key
982 is_match = true;
983 }
984
985 // IMPORTANT: final place where integrity of input message is checked!
986 if (!is_match) {
987 // mismatch of HMAC checksum
989 return false;
990 if (!is_none) {
991 R__LOG_ERROR(WebGUILog()) << "wrong HMAC checksum provided";
992 return false;
993 }
994 }
995
996 // here processing of received data should be performed
997 // this is task for the implemented windows
998
999 char *str_end = nullptr;
1000
1001 unsigned long oper_seq = std::strtoul(buf, &str_end, 10);
1002 if (!str_end || *str_end != ':') {
1003 R__LOG_ERROR(WebGUILog()) << "missing operation sequence";
1004 return false;
1005 }
1006
1007 if (is_remote && (oper_seq <= conn->fRecvSeq)) {
1008 R__LOG_ERROR(WebGUILog()) << "supply same package again - MiM attacker?";
1009 return false;
1010 }
1011
1012 conn->fRecvSeq = oper_seq;
1013
1014 unsigned long ackn_oper = std::strtoul(str_end + 1, &str_end, 10);
1015 if (!str_end || *str_end != ':') {
1016 R__LOG_ERROR(WebGUILog()) << "missing number of acknowledged operations";
1017 return false;
1018 }
1019
1020 unsigned long can_send = std::strtoul(str_end + 1, &str_end, 10);
1021 if (!str_end || *str_end != ':') {
1022 R__LOG_ERROR(WebGUILog()) << "missing can_send counter";
1023 return false;
1024 }
1025
1026 unsigned long nchannel = std::strtoul(str_end + 1, &str_end, 10);
1027 if (!str_end || *str_end != ':') {
1028 R__LOG_ERROR(WebGUILog()) << "missing channel number";
1029 return false;
1030 }
1031
1032 Long_t processed_len = (str_end + 1 - buf);
1033
1034 if (processed_len > data_len) {
1035 R__LOG_ERROR(WebGUILog()) << "corrupted buffer";
1036 return false;
1037 }
1038
1039 std::string cdata(str_end + 1, data_len - processed_len);
1040
1041 timestamp_t stamp = std::chrono::system_clock::now();
1042
1043 {
1044 std::lock_guard<std::mutex> grd(conn->fMutex);
1045
1046 conn->fSendCredits += ackn_oper;
1047 conn->fRecvCount++;
1048 conn->fClientCredits = (int)can_send;
1049 conn->fRecvStamp = stamp;
1050 }
1051
1052 if (fProtocolCnt >= 0)
1053 if (!fProtocolConnId || (conn->fConnId == fProtocolConnId)) {
1054 fProtocolConnId = conn->fConnId; // remember connection
1055
1056 // record send event only for normal channel or very first message via ch0
1057 if ((nchannel != 0) || (cdata.find("READY=") == 0)) {
1058 if (fProtocol.length() > 2)
1059 fProtocol.insert(fProtocol.length() - 1, ",");
1060 fProtocol.insert(fProtocol.length() - 1, "\"send\"");
1061
1062 std::ofstream pfs(fProtocolFileName);
1063 pfs.write(fProtocol.c_str(), fProtocol.length());
1064 pfs.close();
1065 }
1066 }
1067
1068 if (nchannel == 0) {
1069 // special system channel
1070 if ((cdata.compare(0, 6, "READY=") == 0) && !conn->fReady) {
1071
1072 std::string key = cdata.substr(6);
1073 bool new_key = false;
1074 if (key.find("generate_key;") == 0) {
1075 new_key = true;
1076 key = key.substr(13);
1077 }
1078
1079 if (key.empty() && IsNativeOnlyConn()) {
1080 RemoveConnection(conn->fWSId);
1081 return false;
1082 }
1083
1084 if (!key.empty() && !conn->fKey.empty() && (conn->fKey != key)) {
1085 R__LOG_ERROR(WebGUILog()) << "Key mismatch after established connection " << key << " != " << conn->fKey;
1086 RemoveConnection(conn->fWSId);
1087 return false;
1088 }
1089
1090 if (!fPanelName.empty()) {
1091 // initialization not yet finished, appropriate panel should be started
1092 Send(conn->fConnId, "SHOWPANEL:"s + fPanelName);
1093 conn->fReady = 5;
1094 } else {
1095 ProvideQueueEntry(conn->fConnId, kind_Connect, ""s);
1096 conn->fReady = 10;
1097 }
1098 if (new_key && !fMaster) {
1099 conn->fNewKey = GenerateKey();
1100 if(!conn->fNewKey.empty())
1101 SubmitData(conn->fConnId, true, "NEW_KEY="s + conn->fNewKey, 0);
1102 }
1103 } else if (cdata.compare(0, 8, "CLOSECH=") == 0) {
1104 int channel = std::stoi(cdata.substr(8));
1105 auto iter = conn->fEmbed.find(channel);
1106 if (iter != conn->fEmbed.end()) {
1107 iter->second->ProvideQueueEntry(conn->fConnId, kind_Disconnect, ""s);
1108 conn->fEmbed.erase(iter);
1109 }
1110 } else if (cdata.compare(0, 7, "RESIZE=") == 0) {
1111 auto p = cdata.find(",");
1112 if (p != std::string::npos) {
1113 auto width = std::stoi(cdata.substr(7, p - 7));
1114 auto height = std::stoi(cdata.substr(p + 1));
1115 if ((width > 0) && (height > 0) && conn->fDisplayHandle)
1116 conn->fDisplayHandle->Resize(width, height);
1117 }
1118 } else if (cdata == "GENERATE_KEY") {
1119 if (fMaster) {
1120 R__LOG_ERROR(WebGUILog()) << "Not able to generate new key with master connections";
1121 } else {
1122 conn->fNewKey = GenerateKey();
1123 if(!conn->fNewKey.empty())
1124 SubmitData(conn->fConnId, true, "NEW_KEY="s + conn->fNewKey, -1);
1125 }
1126 }
1127 } else if (fPanelName.length() && (conn->fReady < 10)) {
1128 if (cdata == "PANEL_READY") {
1129 R__LOG_DEBUG(0, WebGUILog()) << "Get panel ready " << fPanelName;
1130 ProvideQueueEntry(conn->fConnId, kind_Connect, ""s);
1131 conn->fReady = 10;
1132 } else {
1133 RemoveConnection(conn->fWSId, true);
1134 }
1135 } else if (nchannel == 1) {
1136 ProvideQueueEntry(conn->fConnId, kind_Data, std::move(cdata));
1137 } else if (nchannel > 1) {
1138 // process embed window
1139 auto embed_window = conn->fEmbed[nchannel];
1140 if (embed_window)
1141 embed_window->ProvideQueueEntry(conn->fConnId, kind_Data, std::move(cdata));
1142 }
1143
1145
1146 return true;
1147}
1148
1149//////////////////////////////////////////////////////////////////////////////////////////
1150/// Complete websocket send operation
1151/// Clear "doing send" flag and check if next operation has to be started
1152
1154{
1155 auto conn = FindConnection(wsid);
1156
1157 if (!conn)
1158 return;
1159
1160 {
1161 std::lock_guard<std::mutex> grd(conn->fMutex);
1162 conn->fDoingSend = false;
1163 }
1164
1165 CheckDataToSend(conn);
1166}
1167
1168//////////////////////////////////////////////////////////////////////////////////////////
1169/// Internal method to prepare text part of send data
1170/// Should be called under locked connection mutex
1171
1172std::string RWebWindow::_MakeSendHeader(std::shared_ptr<WebConn> &conn, bool txt, const std::string &data, int chid)
1173{
1174 std::string buf;
1175
1176 if (!conn->fWSId || !fWSHandler) {
1177 R__LOG_ERROR(WebGUILog()) << "try to send text data when connection not established";
1178 return buf;
1179 }
1180
1181 if (conn->fSendCredits <= 0) {
1182 R__LOG_ERROR(WebGUILog()) << "No credits to send text data via connection";
1183 return buf;
1184 }
1185
1186 if (conn->fDoingSend) {
1187 R__LOG_ERROR(WebGUILog()) << "Previous send operation not completed yet";
1188 return buf;
1189 }
1190
1191 if (txt)
1192 buf.reserve(data.length() + 100);
1193
1194 buf.append(std::to_string(conn->fSendSeq++));
1195 buf.append(":");
1196 buf.append(std::to_string(conn->fRecvCount));
1197 buf.append(":");
1198 buf.append(std::to_string(conn->fSendCredits));
1199 buf.append(":");
1200 conn->fRecvCount = 0; // we confirm how many packages was received
1201 conn->fSendCredits--;
1202
1203 buf.append(std::to_string(chid));
1204 buf.append(":");
1205
1206 if (txt) {
1207 buf.append(data);
1208 } else if (data.length()==0) {
1209 buf.append("$$nullbinary$$");
1210 } else {
1211 buf.append("$$binary$$");
1212 if (!conn->fKey.empty() && !fMgr->fSessionKey.empty() && fMgr->fUseSessionKey)
1213 buf.append(HMAC(conn->fKey, fMgr->fSessionKey, data.data(), data.length()));
1214 }
1215
1216 return buf;
1217}
1218
1219//////////////////////////////////////////////////////////////////////////////////////////
1220/// Checks if one should send data for specified connection
1221/// Returns true when send operation was performed
1222
1223bool RWebWindow::CheckDataToSend(std::shared_ptr<WebConn> &conn)
1224{
1225 std::string hdr, data, prefix;
1226
1227 {
1228 std::lock_guard<std::mutex> grd(conn->fMutex);
1229
1230 if (!conn->fActive || (conn->fSendCredits <= 0) || conn->fDoingSend) return false;
1231
1232 if (!conn->fQueue.empty()) {
1233 QueueItem &item = conn->fQueue.front();
1234 hdr = _MakeSendHeader(conn, item.fText, item.fData, item.fChID);
1235 if (!hdr.empty() && !item.fText)
1236 data = std::move(item.fData);
1237 conn->fQueue.pop();
1238 } else if ((conn->fClientCredits < 3) && (conn->fRecvCount > 1)) {
1239 // give more credits to the client
1240 hdr = _MakeSendHeader(conn, true, "KEEPALIVE", 0);
1241 }
1242
1243 if (hdr.empty()) return false;
1244
1245 conn->fDoingSend = true;
1246 }
1247
1248 // add HMAC checksum for string send to client
1249 if (!conn->fKey.empty() && !fMgr->fSessionKey.empty() && fMgr->fUseSessionKey) {
1250 prefix = HMAC(conn->fKey, fMgr->fSessionKey, hdr.c_str(), hdr.length());
1251 } else {
1252 prefix = "none";
1253 }
1254
1255 prefix += ":";
1256 hdr.insert(0, prefix);
1257
1258 int res = 0;
1259
1260 if (data.empty()) {
1261 res = fWSHandler->SendCharStarWS(conn->fWSId, hdr.c_str());
1262 } else {
1263 res = fWSHandler->SendHeaderWS(conn->fWSId, hdr.c_str(), data.data(), data.length());
1264 }
1265
1266 // submit operation, will be processed
1267 if (res >=0) return true;
1268
1269 // failure, clear sending flag
1270 std::lock_guard<std::mutex> grd(conn->fMutex);
1271 conn->fDoingSend = false;
1272 return false;
1273}
1274
1275
1276//////////////////////////////////////////////////////////////////////////////////////////
1277/// Checks if new data can be send (internal use only)
1278/// If necessary, provide credits to the client
1279/// \param only_once if true, data sending performed once or until there is no data to send
1280
1282{
1283 // make copy of all connections to be independent later, only active connections are checked
1284 auto arr = GetWindowConnections(0, true);
1285
1286 do {
1287 bool isany = false;
1288
1289 for (auto &conn : arr)
1290 if (CheckDataToSend(conn))
1291 isany = true;
1292
1293 if (!isany) break;
1294
1295 } while (!only_once);
1296}
1297
1298///////////////////////////////////////////////////////////////////////////////////
1299/// Special method to process all internal activity when window runs in separate thread
1300
1302{
1304
1306
1308
1310}
1311
1312///////////////////////////////////////////////////////////////////////////////////
1313/// Returns window address which is used in URL
1314
1315std::string RWebWindow::GetAddr() const
1316{
1317 return fWSHandler->GetName();
1318}
1319
1320///////////////////////////////////////////////////////////////////////////////////
1321/// DEPRECATED. Use GetUrl method instead while more arguments are required to connect with the widget
1322/// Returns relative URL address for the specified window
1323/// Address can be required if one needs to access data from one window into another window
1324/// Used for instance when inserting panel into canvas
1325
1326std::string RWebWindow::GetRelativeAddr(const std::shared_ptr<RWebWindow> &win) const
1327{
1328 if (fMgr != win->fMgr) {
1329 R__LOG_ERROR(WebGUILog()) << "Same web window manager should be used";
1330 return "";
1331 }
1332
1333 std::string res("../");
1334 res.append(win->GetAddr());
1335 res.append("/");
1336 return res;
1337}
1338
1339///////////////////////////////////////////////////////////////////////////////////
1340/// DEPRECATED. Use GetUrl method instead while more arguments are required to connect with the widget
1341/// Address can be required if one needs to access data from one window into another window
1342/// Used for instance when inserting panel into canvas
1343
1344std::string RWebWindow::GetRelativeAddr(const RWebWindow &win) const
1345{
1346 if (fMgr != win.fMgr) {
1347 R__LOG_ERROR(WebGUILog()) << "Same web window manager should be used";
1348 return "";
1349 }
1350
1351 std::string res("../");
1352 res.append(win.GetAddr());
1353 res.append("/");
1354 return res;
1355}
1356
1357/////////////////////////////////////////////////////////////////////////
1358/// Set client version, used as prefix in scripts URL
1359/// When changed, web browser will reload all related JS files while full URL will be different
1360/// Default is empty value - no extra string in URL
1361/// Version should be string like "1.2" or "ver1.subv2" and not contain any special symbols
1362
1363void RWebWindow::SetClientVersion(const std::string &vers)
1364{
1365 std::lock_guard<std::mutex> grd(fConnMutex);
1367}
1368
1369/////////////////////////////////////////////////////////////////////////
1370/// Returns current client version
1371
1373{
1374 std::lock_guard<std::mutex> grd(fConnMutex);
1375 return fClientVersion;
1376}
1377
1378/////////////////////////////////////////////////////////////////////////
1379/// Set arbitrary JSON data, which is accessible via conn.getUserArgs() method in JavaScript
1380/// This JSON code injected into main HTML document into connectWebWindow({})
1381/// Must be set before RWebWindow::Show() method is called
1382/// \param args - arbitrary JSON data which can be provided to client side
1383
1384void RWebWindow::SetUserArgs(const std::string &args)
1385{
1386 std::lock_guard<std::mutex> grd(fConnMutex);
1387 fUserArgs = args;
1388}
1389
1390/////////////////////////////////////////////////////////////////////////
1391/// Returns configured user arguments for web window
1392/// See \ref SetUserArgs method for more details
1393
1394std::string RWebWindow::GetUserArgs() const
1395{
1396 std::lock_guard<std::mutex> grd(fConnMutex);
1397 return fUserArgs;
1398}
1399
1400///////////////////////////////////////////////////////////////////////////////////
1401/// Returns current number of active clients connections
1402/// \param with_pending if true, also pending (not yet established) connection accounted
1403
1405{
1406 bool is_master = !!fMaster;
1407
1408 std::lock_guard<std::mutex> grd(fConnMutex);
1409
1410 if (is_master)
1411 return fMasterConns.size();
1412
1413 auto sz = fConn.size();
1414 if (with_pending)
1415 sz += fPendingConn.size();
1416 return sz;
1417}
1418
1419///////////////////////////////////////////////////////////////////////////////////
1420/// Configures recording of communication data in protocol file
1421/// Provided filename will be used to store JSON array with names of written files - text or binary
1422/// If data was send from client, "send" entry will be placed. JSON file will look like:
1423///
1424/// ["send", "msg0.txt", "send", "msg1.txt", "msg2.txt"]
1425///
1426/// If empty file name is provided, data recording will be disabled
1427/// Recorded data can be used in JSROOT directly to test client code without running C++ server
1428
1429void RWebWindow::RecordData(const std::string &fname, const std::string &fprefix)
1430{
1432 fProtocolCnt = fProtocolFileName.empty() ? -1 : 0;
1435 fProtocol = "[]"; // empty array
1436}
1437
1438///////////////////////////////////////////////////////////////////////////////////
1439/// Returns connection id for specified connection sequence number
1440/// Only active connections are returned - where clients confirms connection
1441/// Total number of connections can be retrieved with NumConnections() method
1442/// \param num connection sequence number
1443
1444unsigned RWebWindow::GetConnectionId(int num) const
1445{
1446 bool is_master = !!fMaster;
1447
1448 std::lock_guard<std::mutex> grd(fConnMutex);
1449
1450 if (is_master)
1451 return (num >= 0) && (num < (int)fMasterConns.size()) ? fMasterConns[num].connid : 0;
1452
1453 return ((num >= 0) && (num < (int)fConn.size()) && fConn[num]->fActive) ? fConn[num]->fConnId : 0;
1454}
1455
1456///////////////////////////////////////////////////////////////////////////////////
1457/// returns vector with all existing connections ids
1458/// One also can exclude specified connection from return result,
1459/// which can be useful to be able reply too all but this connections
1460
1461std::vector<unsigned> RWebWindow::GetConnections(unsigned excludeid) const
1462{
1463 std::vector<unsigned> res;
1464
1465 bool is_master = !!fMaster;
1466
1467 std::lock_guard<std::mutex> grd(fConnMutex);
1468
1469 if (is_master) {
1470 for (auto & entry : fMasterConns)
1471 if (entry.connid != excludeid)
1472 res.emplace_back(entry.connid);
1473 } else {
1474 for (auto & entry : fConn)
1475 if (entry->fActive && (entry->fConnId != excludeid))
1476 res.emplace_back(entry->fConnId);
1477 }
1478
1479 return res;
1480}
1481
1482///////////////////////////////////////////////////////////////////////////////////
1483/// returns true if specified connection id exists
1484/// \param connid connection id (0 - any)
1485/// \param only_active when true only active connection will be checked, otherwise also pending (not yet established) connections are checked
1486
1487bool RWebWindow::HasConnection(unsigned connid, bool only_active) const
1488{
1489 if (fMaster) {
1490 auto lst = GetMasterConnections(connid);
1491 for (auto & entry : lst)
1492 if (fMaster->HasConnection(entry.connid, only_active))
1493 return true;
1494 return false;
1495 }
1496
1497 std::lock_guard<std::mutex> grd(fConnMutex);
1498
1499 for (auto &conn : fConn) {
1500 if (connid && (conn->fConnId != connid))
1501 continue;
1502 if (conn->fActive || !only_active)
1503 return true;
1504 }
1505
1506 if (!only_active)
1507 for (auto &conn : fPendingConn) {
1508 if (!connid || (conn->fConnId == connid))
1509 return true;
1510 }
1511
1512 return false;
1513}
1514
1515///////////////////////////////////////////////////////////////////////////////////
1516/// Closes all connection to clients
1517/// Normally leads to closing of all correspondent browser windows
1518/// Some browsers (like firefox) do not allow by default to close window
1519
1521{
1522 SubmitData(0, true, "CLOSE", 0);
1523}
1524
1525///////////////////////////////////////////////////////////////////////////////////
1526/// Close specified connection
1527/// \param connid connection id, when 0 - all connections will be closed
1528
1529void RWebWindow::CloseConnection(unsigned connid)
1530{
1531 if (connid)
1532 SubmitData(connid, true, "CLOSE", 0);
1533}
1534
1535///////////////////////////////////////////////////////////////////////////////////
1536/// returns connection list (or all active connections)
1537/// \param connid connection id, when 0 - all existing connections are returned
1538/// \param only_active when true, only active (already established) connections are returned
1539
1541{
1543
1544 {
1545 std::lock_guard<std::mutex> grd(fConnMutex);
1546
1547 for (auto &conn : fConn) {
1548 if ((conn->fActive || !only_active) && (!connid || (conn->fConnId == connid)))
1549 arr.push_back(conn);
1550 }
1551
1552 if (!only_active)
1553 for (auto &conn : fPendingConn)
1554 if (!connid || (conn->fConnId == connid))
1555 arr.push_back(conn);
1556 }
1557
1558 return arr;
1559}
1560
1561///////////////////////////////////////////////////////////////////////////////////
1562/// Returns true if sending via specified connection can be performed
1563/// \param connid connection id, when 0 - all existing connections are checked
1564/// \param direct when true, checks if direct sending (without queuing) is possible
1565
1566bool RWebWindow::CanSend(unsigned connid, bool direct) const
1567{
1568 if (fMaster) {
1569 auto lst = GetMasterConnections(connid);
1570 for (auto &entry : lst) {
1571 if (!fMaster->CanSend(entry.connid, direct))
1572 return false;
1573 }
1574 } else {
1575 auto arr = GetWindowConnections(connid, direct); // for direct sending connection has to be active
1576
1577 auto maxqlen = GetMaxQueueLength();
1578
1579 for (auto &conn : arr) {
1580
1581 std::lock_guard<std::mutex> grd(conn->fMutex);
1582
1583 if (direct && (!conn->fQueue.empty() || (conn->fSendCredits == 0) || conn->fDoingSend))
1584 return false;
1585
1586 if (conn->fQueue.size() >= maxqlen)
1587 return false;
1588 }
1589 }
1590
1591 return true;
1592}
1593
1594///////////////////////////////////////////////////////////////////////////////////
1595/// Returns send queue length for specified connection
1596/// \param connid connection id, 0 - maximal value for all connections is returned
1597/// If wrong connection id specified, -1 is return
1598
1599int RWebWindow::GetSendQueueLength(unsigned connid) const
1600{
1601 int maxq = -1;
1602
1603 if (fMaster) {
1604 auto lst = GetMasterConnections(connid);
1605 for (auto &entry : lst) {
1606 int len = fMaster->GetSendQueueLength(entry.connid);
1607 if (len > maxq)
1608 maxq = len;
1609 }
1610 } else {
1611 auto lst = GetWindowConnections(connid);
1612 for (auto &conn : lst) {
1613 std::lock_guard<std::mutex> grd(conn->fMutex);
1614 int len = conn->fQueue.size();
1615 if (len > maxq)
1616 maxq = len;
1617 }
1618 }
1619
1620 return maxq;
1621}
1622
1623///////////////////////////////////////////////////////////////////////////////////
1624/// Internal method to send data
1625/// \param connid connection id, when 0 - data will be send to all connections
1626/// \param txt is text message that should be sent
1627/// \param data data to be std-moved to SubmitData function
1628/// \param chid channel id, 1 - normal communication, 0 - internal with highest priority
1629
1630void RWebWindow::SubmitData(unsigned connid, bool txt, std::string &&data, int chid)
1631{
1632 if (fMaster) {
1633 auto lst = GetMasterConnections(connid);
1634 auto cnt = lst.size();
1635 for (auto & entry : lst)
1636 if (--cnt)
1637 fMaster->SubmitData(entry.connid, txt, std::string(data), entry.channel);
1638 else
1639 fMaster->SubmitData(entry.connid, txt, std::move(data), entry.channel);
1640 return;
1641 }
1642
1643 auto arr = GetWindowConnections(connid);
1644 auto cnt = arr.size();
1645 auto maxqlen = GetMaxQueueLength();
1646
1647 bool clear_queue = false;
1648
1649 if (chid == -1) {
1650 chid = 0;
1651 clear_queue = true;
1652 }
1653
1654 timestamp_t stamp = std::chrono::system_clock::now();
1655
1656 for (auto &conn : arr) {
1657
1658 if ((fProtocolCnt >= 0) && (chid > 0))
1659 if (!fProtocolConnId || (conn->fConnId == fProtocolConnId)) {
1660 fProtocolConnId = conn->fConnId; // remember connection
1661 std::string fname = fProtocolPrefix;
1662 fname.append("msg");
1663 fname.append(std::to_string(fProtocolCnt++));
1664 if (chid > 1) {
1665 fname.append("_ch");
1666 fname.append(std::to_string(chid));
1667 }
1668 fname.append(txt ? ".txt" : ".bin");
1669
1670 std::ofstream ofs(fname);
1671 ofs.write(data.c_str(), data.length());
1672 ofs.close();
1673
1674 if (fProtocol.length() > 2)
1675 fProtocol.insert(fProtocol.length() - 1, ",");
1676 fProtocol.insert(fProtocol.length() - 1, "\""s + fname + "\""s);
1677
1678 std::ofstream pfs(fProtocolFileName);
1679 pfs.write(fProtocol.c_str(), fProtocol.length());
1680 pfs.close();
1681 }
1682
1683 conn->fSendStamp = stamp;
1684
1685 std::lock_guard<std::mutex> grd(conn->fMutex);
1686
1687 if (clear_queue) {
1688 while (!conn->fQueue.empty())
1689 conn->fQueue.pop();
1690 }
1691
1692 if (conn->fQueue.size() < maxqlen) {
1693 if (--cnt)
1694 conn->fQueue.emplace(chid, txt, std::string(data)); // make copy
1695 else
1696 conn->fQueue.emplace(chid, txt, std::move(data)); // move content
1697 } else {
1698 R__LOG_ERROR(WebGUILog()) << "Maximum queue length " << maxqlen << " achieved, can be changed with SetMaxQueueLength(v) method";
1699 }
1700 }
1701
1703}
1704
1705///////////////////////////////////////////////////////////////////////////////////
1706/// Sends data to specified connection
1707/// \param connid connection id, when 0 - data will be send to all connections
1708/// \param data data to be copied to SubmitData function
1709
1710void RWebWindow::Send(unsigned connid, const std::string &data)
1711{
1712 SubmitData(connid, true, std::string(data), 1);
1713}
1714
1715///////////////////////////////////////////////////////////////////////////////////
1716/// Send binary data to specified connection
1717/// \param connid connection id, when 0 - data will be send to all connections
1718/// \param data data to be std-moved to SubmitData function
1719
1720void RWebWindow::SendBinary(unsigned connid, std::string &&data)
1721{
1722 SubmitData(connid, false, std::move(data), 1);
1723}
1724
1725///////////////////////////////////////////////////////////////////////////////////
1726/// Send binary data to specified connection
1727/// \param connid connection id, when 0 - data will be send to all connections
1728/// \param data pointer to binary data
1729/// \param len number of bytes in data
1730
1731void RWebWindow::SendBinary(unsigned connid, const void *data, std::size_t len)
1732{
1733 std::string buf;
1734 buf.resize(len);
1735 std::copy((const char *)data, (const char *)data + len, buf.begin());
1736 SubmitData(connid, false, std::move(buf), 1);
1737}
1738
1739///////////////////////////////////////////////////////////////////////////////////
1740/// Assign thread id which has to be used for callbacks
1741/// WARNING!!! only for expert use
1742/// Automatically done at the moment when any callback function is invoked
1743/// Can be invoked once again if window Run method will be invoked from other thread
1744/// Normally should be invoked before Show() method is called
1745
1747{
1748 fUseServerThreads = false;
1749 fUseProcessEvents = false;
1750 fProcessMT = false;
1751 fCallbacksThrdIdSet = true;
1752 fCallbacksThrdId = std::this_thread::get_id();
1754 fProcessMT = true;
1755 } else if (fMgr->IsUseHttpThread()) {
1756 // special thread is used by the manager, but main thread used for the canvas - not supported
1757 R__LOG_ERROR(WebGUILog()) << "create web window from main thread when THttpServer created with special thread - not supported";
1758 }
1759}
1760
1761/////////////////////////////////////////////////////////////////////////////////
1762/// Let use THttpServer threads to process requests
1763/// WARNING!!! only for expert use
1764/// Should be only used when application provides proper locking and
1765/// does not block. Such mode provides minimal possible latency
1766/// Must be called before callbacks are assigned
1767
1769{
1770 fUseServerThreads = true;
1771 fUseProcessEvents = false;
1772 fCallbacksThrdIdSet = false;
1773 fProcessMT = true;
1774}
1775
1776/////////////////////////////////////////////////////////////////////////////////
1777/// Start special thread which will be used by the window to handle all callbacks
1778/// One has to be sure, that access to global ROOT structures are minimized and
1779/// protected with ROOT::EnableThreadSafety(); call
1780
1782{
1783 if (fHasWindowThrd) {
1784 R__LOG_WARNING(WebGUILog()) << "thread already started for the window";
1785 return;
1786 }
1787
1788 fHasWindowThrd = true;
1789
1790 std::thread thrd([this] {
1792 while(fHasWindowThrd)
1793 Run(0.1);
1794 fCallbacksThrdIdSet = false;
1795 });
1796
1797 fWindowThrd = std::move(thrd);
1798}
1799
1800/////////////////////////////////////////////////////////////////////////////////
1801/// Stop special thread
1802
1804{
1805 if (!fHasWindowThrd)
1806 return;
1807
1808 fHasWindowThrd = false;
1809 fWindowThrd.join();
1810}
1811
1812
1813/////////////////////////////////////////////////////////////////////////////////
1814/// Set call-back function for data, received from the clients via websocket
1815///
1816/// Function should have signature like void func(unsigned connid, const std::string &data)
1817/// First argument identifies connection (unique for each window), second argument is received data
1818///
1819/// At the moment when callback is assigned, RWebWindow working thread is detected.
1820/// If called not from main application thread, RWebWindow::Run() function must be regularly called from that thread.
1821///
1822/// Most simple way to assign call-back - use of c++11 lambdas like:
1823/// ~~~ {.cpp}
1824/// auto win = RWebWindow::Create();
1825/// win->SetDefaultPage("file:./page.htm");
1826/// win->SetDataCallBack(
1827/// [](unsigned connid, const std::string &data) {
1828/// printf("Conn:%u data:%s\n", connid, data.c_str());
1829/// }
1830/// );
1831/// win->Show();
1832/// ~~~
1833
1840
1841/////////////////////////////////////////////////////////////////////////////////
1842/// Set call-back function for new connection
1843
1850
1851/////////////////////////////////////////////////////////////////////////////////
1852/// Set call-back function for disconnecting
1853
1860
1861/////////////////////////////////////////////////////////////////////////////////
1862/// Set handle which is cleared when last active connection is closed
1863/// Typically can be used to destroy web-based widget at such moment
1864
1865void RWebWindow::SetClearOnClose(const std::shared_ptr<void> &handle)
1866{
1867 fClearOnClose = handle;
1868}
1869
1870/////////////////////////////////////////////////////////////////////////////////
1871/// Set call-backs function for connect, data and disconnect events
1872
1881
1882/////////////////////////////////////////////////////////////////////////////////
1883/// Reset window call-backs and close connections
1884/// Should be invoked in widget destructor to simplify cleanup process
1885
1887{
1889
1890 fConnCallback = nullptr;
1891 fDataCallback = nullptr;
1892 fDisconnCallback = nullptr;
1893
1894 if (fWSHandler)
1895 fWSHandler->SetDisabled();
1896}
1897
1898/////////////////////////////////////////////////////////////////////////////////
1899/// Waits until provided check function or lambdas returns non-zero value
1900/// Check function has following signature: int func(double spent_tm)
1901/// Waiting will be continued, if function returns zero.
1902/// Parameter spent_tm is time in seconds, which already spent inside the function
1903/// First non-zero value breaks loop and result is returned.
1904/// Runs application mainloop and short sleeps in-between
1905
1907{
1908 return fMgr->WaitFor(*this, check);
1909}
1910
1911/////////////////////////////////////////////////////////////////////////////////
1912/// Waits until provided check function or lambdas returns non-zero value
1913/// Check function has following signature: int func(double spent_tm)
1914/// Waiting will be continued, if function returns zero.
1915/// Parameter spent_tm in lambda is time in seconds, which already spent inside the function
1916/// First non-zero value breaks waiting loop and result is returned (or 0 if time is expired).
1917/// Runs application mainloop and short sleeps in-between
1918/// WebGui.OperationTmout rootrc parameter defines waiting time in seconds
1919
1921{
1922 return fMgr->WaitFor(*this, check, true, GetOperationTmout());
1923}
1924
1925/////////////////////////////////////////////////////////////////////////////////
1926/// Waits until provided check function or lambdas returns non-zero value
1927/// Check function has following signature: int func(double spent_tm)
1928/// Waiting will be continued, if function returns zero.
1929/// Parameter spent_tm in lambda is time in seconds, which already spent inside the function
1930/// First non-zero value breaks waiting loop and result is returned (or 0 if time is expired).
1931/// Runs application mainloop and short sleeps in-between
1932/// duration (in seconds) defines waiting time
1933
1935{
1936 return fMgr->WaitFor(*this, check, true, duration);
1937}
1938
1939
1940/////////////////////////////////////////////////////////////////////////////////
1941/// Run window functionality for specified time
1942/// If no action can be performed - just sleep specified time
1943
1945{
1946 if (!fCallbacksThrdIdSet || (fCallbacksThrdId != std::this_thread::get_id())) {
1947 R__LOG_WARNING(WebGUILog()) << "Change thread id where RWebWindow is executed";
1948 fCallbacksThrdIdSet = true;
1949 fCallbacksThrdId = std::this_thread::get_id();
1950 }
1951
1952 if (tm <= 0) {
1953 Sync();
1954 } else {
1955 WaitForTimed([](double) { return 0; }, tm);
1956 }
1957}
1958
1959
1960/////////////////////////////////////////////////////////////////////////////////
1961/// Add embed window
1962
1963unsigned RWebWindow::AddEmbedWindow(std::shared_ptr<RWebWindow> window, unsigned connid, int channel)
1964{
1965 if (channel < 2)
1966 return 0;
1967
1968 auto arr = GetWindowConnections(connid, true);
1969 if (arr.size() == 0)
1970 return 0;
1971
1972 // check if channel already occupied
1973 if (arr[0]->fEmbed.find(channel) != arr[0]->fEmbed.end())
1974 return 0;
1975
1976 arr[0]->fEmbed[channel] = window;
1977
1978 return arr[0]->fConnId;
1979}
1980
1981/////////////////////////////////////////////////////////////////////////////////
1982/// Remove RWebWindow associated with the channel
1983
1984void RWebWindow::RemoveEmbedWindow(unsigned connid, int channel)
1985{
1986 auto arr = GetWindowConnections(connid);
1987
1988 for (auto &conn : arr) {
1989 auto iter = conn->fEmbed.find(channel);
1990 if (iter != conn->fEmbed.end())
1991 conn->fEmbed.erase(iter);
1992 }
1993}
1994
1995
1996/////////////////////////////////////////////////////////////////////////////////
1997/// Create new RWebWindow
1998/// Using default RWebWindowsManager
1999
2000std::shared_ptr<RWebWindow> RWebWindow::Create()
2001{
2002 return RWebWindowsManager::Instance()->CreateWindow();
2003}
2004
2005/////////////////////////////////////////////////////////////////////////////////
2006/// Terminate ROOT session
2007/// Tries to correctly close THttpServer, associated with RWebWindowsManager
2008/// After that exit from process
2009
2011{
2012
2013 // workaround to release all connection-specific handles as soon as possible
2014 // required to work with QWebEngine
2015 // once problem solved, can be removed here
2017
2018 {
2019 std::lock_guard<std::mutex> grd(fConnMutex);
2020 std::swap(arr1, fConn);
2021 std::swap(arr2, fPendingConn);
2022 }
2023
2024 fMgr->Terminate();
2025}
2026
2027/////////////////////////////////////////////////////////////////////////////////
2028/// Static method to show web window
2029/// Has to be used instead of RWebWindow::Show() when window potentially can be embed into other windows
2030/// Soon RWebWindow::Show() method will be done protected
2031
2032unsigned RWebWindow::ShowWindow(std::shared_ptr<RWebWindow> window, const RWebDisplayArgs &args)
2033{
2034 if (!window)
2035 return 0;
2036
2038 auto master = args.fMaster;
2039 while (master && master->fMaster)
2040 master = master->fMaster;
2041
2042 if (master && window->fMaster && window->fMaster != master) {
2043 R__LOG_ERROR(WebGUILog()) << "Cannot use different master for same RWebWindow";
2044 return 0;
2045 }
2046
2047 unsigned connid = master ? master->AddEmbedWindow(window, args.fMasterConnection, args.fMasterChannel) : 0;
2048
2049 if (connid > 0) {
2050
2051 window->RemoveMasterConnection(connid);
2052
2053 window->AddMasterConnection(master, connid, args.fMasterChannel);
2054
2055 // inform client that connection is established and window initialized
2056 master->SubmitData(connid, true, "EMBED_DONE"s, args.fMasterChannel);
2057
2058 // provide call back for window itself that connection is ready
2059 window->ProvideQueueEntry(connid, kind_Connect, ""s);
2060 }
2061
2062 return connid;
2063 }
2064
2065 return window->Show(args);
2066}
2067
2068std::function<bool(const std::shared_ptr<RWebWindow> &, unsigned, const std::string &)> RWebWindow::gStartDialogFunc = nullptr;
2069
2070/////////////////////////////////////////////////////////////////////////////////////
2071/// Configure func which has to be used for starting dialog
2072
2073
2074void RWebWindow::SetStartDialogFunc(std::function<bool(const std::shared_ptr<RWebWindow> &, unsigned, const std::string &)> func)
2075{
2076 gStartDialogFunc = func;
2077}
2078
2079/////////////////////////////////////////////////////////////////////////////////////
2080/// Check if this could be the message send by client to start new file dialog
2081/// If returns true, one can call RWebWindow::EmbedFileDialog() to really create file dialog
2082/// instance inside existing widget
2083
2084bool RWebWindow::IsFileDialogMessage(const std::string &msg)
2085{
2086 return msg.compare(0, 11, "FILEDIALOG:") == 0;
2087}
2088
2089/////////////////////////////////////////////////////////////////////////////////////
2090/// Create dialog instance to use as embedded dialog inside provided widget
2091/// Loads libROOTBrowserv7 and tries to call RFileDialog::Embedded() method
2092/// Embedded dialog started on the client side where FileDialogController.SaveAs() method called
2093/// Such method immediately send message with "FILEDIALOG:" prefix
2094/// On the server side widget should detect such message and call RFileDialog::Embedded()
2095/// providing received string as second argument.
2096/// Returned instance of shared_ptr<RFileDialog> may be used to assign callback when file is selected
2097
2098bool RWebWindow::EmbedFileDialog(const std::shared_ptr<RWebWindow> &window, unsigned connid, const std::string &args)
2099{
2100 if (!gStartDialogFunc)
2101 gSystem->Load("libROOTBrowserv7");
2102
2103 if (!gStartDialogFunc)
2104 return false;
2105
2106 return gStartDialogFunc(window, connid, args);
2107}
2108
2109/////////////////////////////////////////////////////////////////////////////////////
2110/// Calculate HMAC checksum for provided key and message
2111/// Key combined from connection key and session key
2112
2113std::string RWebWindow::HMAC(const std::string &key, const std::string &sessionKey, const char *msg, int msglen)
2114{
2115 using namespace ROOT::Internal::SHA256;
2116
2117 auto get_digest = [](sha256_t &hash, bool as_hex = false) -> std::string {
2118 std::string digest;
2119 digest.resize(32);
2120
2121 sha256_final(&hash, reinterpret_cast<unsigned char *>(digest.data()));
2122
2123 if (!as_hex) return digest;
2124
2125 static const char* digits = "0123456789abcdef";
2126 std::string hex;
2127 for (int n = 0; n < 32; n++) {
2128 unsigned char code = (unsigned char) digest[n];
2129 hex += digits[code / 16];
2130 hex += digits[code % 16];
2131 }
2132 return hex;
2133 };
2134
2135 // calculate hash of sessionKey + key;
2137 sha256_init(&hash1);
2138 sha256_update(&hash1, (const unsigned char *) sessionKey.data(), sessionKey.length());
2139 sha256_update(&hash1, (const unsigned char *) key.data(), key.length());
2140 std::string kbis = get_digest(hash1);
2141
2142 kbis.resize(64, 0); // resize to blocksize 64 bytes required by the sha256
2143
2144 std::string ki = kbis, ko = kbis;
2145 const int opad = 0x5c;
2146 const int ipad = 0x36;
2147 for (size_t i = 0; i < kbis.length(); ++i) {
2148 ko[i] = kbis[i] ^ opad;
2149 ki[i] = kbis[i] ^ ipad;
2150 }
2151
2152 // calculate hash for ko + msg;
2154 sha256_init(&hash2);
2155 sha256_update(&hash2, (const unsigned char *) ki.data(), ki.length());
2156 sha256_update(&hash2, (const unsigned char *) msg, msglen);
2157 std::string m2digest = get_digest(hash2);
2158
2159 // calculate hash for ki + m2_digest;
2161 sha256_init(&hash3);
2162 sha256_update(&hash3, (const unsigned char *) ko.data(), ko.length());
2163 sha256_update(&hash3, (const unsigned char *) m2digest.data(), m2digest.length());
2164
2165 return get_digest(hash3, true);
2166}
2167
2168/////////////////////////////////////////////////////////////////////////////////////
2169/// Set JSROOT settings as json string
2170/// Will be applied for any web window at the connection time
2171/// Can be used to chang `settings` object of JSROOT like:
2172/// ~~~ {.cpp}
2173/// ROOT::RWebWindow::SetJSROOTSettings("{ ToolBar: false, CanEnlarge: false }");
2174/// ~~~
2175
2176void RWebWindow::SetJSROOTSettings(const std::string &json)
2177{
2179}
nlohmann::json json
#define R__LOG_WARNING(...)
Definition RLogger.hxx:357
#define R__LOG_ERROR(...)
Definition RLogger.hxx:356
#define R__LOG_DEBUG(DEBUGLEVEL,...)
Definition RLogger.hxx:359
#define e(i)
Definition RSha256.hxx:103
int Int_t
Signed integer 4 bytes (int)
Definition RtypesCore.h:60
long Long_t
Signed long integer 4 bytes (long). Size depends on architecture.
Definition RtypesCore.h:69
ROOT::Detail::TRangeCast< T, true > TRangeDynCast
TRangeDynCast is an adapter class that allows the typed iteration through a TCollection.
#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
winID h direct
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void data
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 TString Int_t GCValues_t GetPrimarySelectionOwner GetDisplay GetScreen GetColormap GetNativeEvent const char const char dpyName wid window const char font_name cursor keysym reg const char only_if_exist regb h Point_t winding char text const char depth char const char Int_t count const char ColorStruct_t color const char Pixmap_t Pixmap_t PictureAttributes_t attr const char char ret_data h unsigned char height h Atom_t Int_t ULong_t ULong_t unsigned char prop_list Atom_t Atom_t Atom_t Time_t stamp
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize id
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 TString Int_t GCValues_t GetPrimarySelectionOwner GetDisplay GetScreen GetColormap GetNativeEvent const char const char dpyName wid window const char font_name cursor keysym reg const char only_if_exist regb h Point_t winding char text const char depth char const char Int_t count const char ColorStruct_t color const char Pixmap_t Pixmap_t PictureAttributes_t attr const char char ret_data h unsigned char height h Atom_t Int_t ULong_t ULong_t unsigned char prop_list Atom_t Atom_t Atom_t Time_t UChar_t len
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
Option_t Option_t width
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t height
char name[80]
Definition TGX11.cxx:148
char * Form(const char *fmt,...)
Formats a string in a circular formatting buffer.
Definition TString.cxx:2570
R__EXTERN TSystem * gSystem
Definition TSystem.h:582
const_iterator end() const
Holds different arguments for starting browser with RWebDisplayHandle::Display() method.
EBrowserKind GetBrowserKind() const
returns configured browser kind, see EBrowserKind for supported values
unsigned fMasterConnection
! used master connection
int fMasterChannel
! used master channel
std::shared_ptr< RWebWindow > fMaster
! master window
@ kEmbedded
window will be embedded into other, no extra browser need to be started
void SetHeadless(bool on=true)
set headless mode
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.
bool CheckDataToSend(std::shared_ptr< WebConn > &conn)
Checks if one should send data for specified connection Returns true when send operation was performe...
int WaitFor(WebWindowWaitFunc_t check)
Waits until provided check function or lambdas returns non-zero value Check function has following si...
unsigned GetId() const
Returns ID for the window - unique inside window manager.
std::vector< MasterConn > GetMasterConnections(unsigned connid=0) const
Get list of master connections.
void AddMasterConnection(std::shared_ptr< RWebWindow > window, unsigned connid, int channel)
Add new master connection If there are many connections - only same master is allowed.
std::mutex fConnMutex
! mutex used to protect connection list
WebWindowDataCallback_t fDataCallback
! main callback when data over channel 1 is arrived
void CheckInactiveConnections()
Check if there are connection which are inactive for longer time For instance, batch browser will be ...
unsigned fId
! unique identifier
static void SetJSROOTSettings(const std::string &json)
Set JSROOT settings as json string Will be applied for any web window at the connection time Can be u...
bool fHasWindowThrd
! indicate if special window thread was started
std::vector< MasterConn > fMasterConns
! master connections
void SetClearOnClose(const std::shared_ptr< void > &handle=nullptr)
Set handle which is cleared when last active connection is closed Typically can be used to destroy we...
void StartThread()
Start special thread which will be used by the window to handle all callbacks One has to be sure,...
unsigned fConnCnt
! counter of new connections to assign ids
unsigned fProtocolConnId
! connection id, which is used for writing protocol
ConnectionsList_t GetWindowConnections(unsigned connid=0, bool only_active=false) const
returns connection list (or all active connections)
bool fSendMT
! true is special threads should be used for sending data
std::thread::id fCallbacksThrdId
! thread id where callbacks should be invoked
void RemoveKey(const std::string &key)
Removes all connections with the key.
std::queue< QueueEntry > fInputQueue
! input queue for all callbacks
bool _CanTrustIn(std::shared_ptr< WebConn > &conn, const std::string &key, const std::string &ntry, bool remote, bool test_first_time)
Check if provided hash, ntry parameters from the connection request could be accepted.
void SetConnToken(const std::string &token="")
Configures connection token (default none) When specified, in URL of webpage such token should be pro...
unsigned MakeHeadless(bool create_new=false)
Start headless browser for specified window Normally only single instance is used,...
std::string GetUrl(bool remote=true)
Return URL string to connect web window URL typically includes extra parameters required for connecti...
void CloseConnections()
Closes all connection to clients Normally leads to closing of all correspondent browser windows Some ...
std::shared_ptr< RWebWindow > fMaster
! master window where this window is embedded
int NumConnections(bool with_pending=false) const
Returns current number of active clients connections.
bool fCallbacksThrdIdSet
! flag indicating that thread id is assigned
std::string fUserArgs
! arbitrary JSON code, which is accessible via conn.getUserArgs() method
void SetDefaultPage(const std::string &page)
Set content of default window HTML page This page returns when URL address of the window will be requ...
unsigned fConnLimit
! number of allowed active connections
void InvokeCallbacks(bool force=false)
Invoke callbacks with existing data Must be called from appropriate thread.
std::shared_ptr< WebConn > FindConnection(unsigned wsid)
Find connection with specified websocket id.
std::string GetClientVersion() const
Returns current client version.
void SetConnectCallBack(WebWindowConnectCallback_t func)
Set call-back function for new connection.
std::vector< std::shared_ptr< WebConn > > ConnectionsList_t
void Sync()
Special method to process all internal activity when window runs in separate thread.
void UseServerThreads()
Let use THttpServer threads to process requests WARNING!!! only for expert use Should be only used wh...
void TerminateROOT()
Terminate ROOT session Tries to correctly close THttpServer, associated with RWebWindowsManager After...
void Send(unsigned connid, const std::string &data)
Sends data to specified connection.
unsigned Show(const RWebDisplayArgs &args="")
Show window in specified location.
THttpServer * GetServer()
Return THttpServer instance serving requests to the window.
unsigned AddDisplayHandle(bool headless_mode, const std::string &key, std::unique_ptr< RWebDisplayHandle > &handle)
Add display handle and associated key Key is large random string generated when starting new window W...
void AssignThreadId()
Assign thread id which has to be used for callbacks WARNING!!! only for expert use Automatically done...
bool IsNativeOnlyConn() const
returns true if only native (own-created) connections are allowed
void SendBinary(unsigned connid, const void *data, std::size_t len)
Send binary data to specified connection.
static std::shared_ptr< RWebWindow > Create()
Create new RWebWindow Using default RWebWindowsManager.
std::string fClientVersion
! configured client version, used as prefix in scripts URL
bool ProcessBatchHolder(std::shared_ptr< THttpCallArg > &arg)
Process special http request, used to hold headless browser running Such requests should not be repli...
unsigned AddEmbedWindow(std::shared_ptr< RWebWindow > window, unsigned connid, int channel)
Add embed window.
void SetDisconnectCallBack(WebWindowConnectCallback_t func)
Set call-back function for disconnecting.
std::vector< unsigned > GetConnections(unsigned excludeid=0) const
returns vector with all existing connections ids One also can exclude specified connection from retur...
void SetDataCallBack(WebWindowDataCallback_t func)
Set call-back function for data, received from the clients via websocket.
float fOperationTmout
! timeout in seconds to perform synchronous operation, default 50s
bool fRequireAuthKey
! defines if authentication key always required when connect to the widget
void SetUserArgs(const std::string &args)
Set arbitrary JSON data, which is accessible via conn.getUserArgs() method in JavaScript This JSON co...
std::string fConnToken
! value of "token" URL parameter which should be provided for connecting window
static unsigned ShowWindow(std::shared_ptr< RWebWindow > window, const RWebDisplayArgs &args="")
Static method to show web window Has to be used instead of RWebWindow::Show() when window potentially...
std::shared_ptr< RWebWindowWSHandler > fWSHandler
! specialize websocket handler for all incoming connections
void StopThread()
Stop special thread.
void SubmitData(unsigned connid, bool txt, std::string &&data, int chid=1)
Internal method to send data.
static std::string HMAC(const std::string &key, const std::string &sessionKey, const char *msg, int msglen)
Calculate HMAC checksum for provided key and message Key combined from connection key and session key...
~RWebWindow()
RWebWindow destructor Closes all connections and remove window from manager.
static bool EmbedFileDialog(const std::shared_ptr< RWebWindow > &window, unsigned connid, const std::string &args)
Create dialog instance to use as embedded dialog inside provided widget Loads libROOTBrowserv7 and tr...
void CloseConnection(unsigned connid)
Close specified connection.
ConnectionsList_t fPendingConn
! list of pending connection with pre-assigned keys
unsigned GetConnectionId(int num=0) const
Returns connection id for specified connection sequence number Only active connections are returned -...
void Reset()
Reset window call-backs and close connections Should be invoked in widget destructor to simplify clea...
std::string GetConnToken() const
Returns configured connection token.
float GetOperationTmout() const
Returns timeout for synchronous WebWindow operations.
void SetConnLimit(unsigned lmt=0)
Configure maximal number of allowed connections - 0 is unlimited Will not affect already existing con...
void SetPanelName(const std::string &name)
Configure window to show some of existing JSROOT panels It uses "file:rootui5sys/panel/panel....
std::shared_ptr< WebConn > RemoveConnection(unsigned wsid, bool provide_signal=false)
Remove connection with given websocket id.
bool IsRequireAuthKey() const
returns true if authentication string is required
RWebWindow()
RWebWindow constructor Should be defined here because of std::unique_ptr<RWebWindowWSHandler>
std::string fProtocolPrefix
! prefix for created files names
int GetSendQueueLength(unsigned connid) const
Returns send queue length for specified connection.
std::shared_ptr< RWebWindowWSHandler > CreateWSHandler(std::shared_ptr< RWebWindowsManager > mgr, unsigned id, double tmout)
Assigns manager reference, window id and creates websocket handler, used for communication with the c...
std::string fProtocol
! protocol
bool CanSend(unsigned connid, bool direct=true) const
Returns true if sending via specified connection can be performed.
std::string GetUserArgs() const
Returns configured user arguments for web window See SetUserArgs method for more details.
void RecordData(const std::string &fname="protocol.json", const std::string &fprefix="")
Configures recording of communication data in protocol file Provided filename will be used to store J...
bool fUseProcessEvents
! all window functionality will run through process events
unsigned GetDisplayConnection() const
Returns first connection id where window is displayed It could be that connection(s) not yet fully es...
unsigned GetConnLimit() const
returns configured connections limit (0 - default)
static void SetStartDialogFunc(std::function< bool(const std::shared_ptr< RWebWindow > &, unsigned, const std::string &)>)
Configure func which has to be used for starting dialog.
std::string fPanelName
! panel name which should be shown in the window
void Run(double tm=0.)
Run window functionality for specified time If no action can be performed - just sleep specified time...
std::string GetAddr() const
Returns window address which is used in URL.
std::shared_ptr< RWebWindowsManager > fMgr
! display manager
std::string fProtocolFileName
! local file where communication protocol will be written
ConnectionsList_t fConn
! list of all accepted connections
WebWindowConnectCallback_t fConnCallback
! callback for connect event
void CheckPendingConnections()
Check if started process(es) establish connection.
std::shared_ptr< void > fClearOnClose
! entry which is cleared when last connection is closed
std::mutex fInputQueueMutex
! mutex to protect input queue
static std::function< bool(const std::shared_ptr< RWebWindow > &, unsigned, const std::string &) gStartDialogFunc)
std::string _MakeSendHeader(std::shared_ptr< WebConn > &conn, bool txt, const std::string &data, int chid)
Internal method to prepare text part of send data Should be called under locked connection mutex.
std::chrono::time_point< std::chrono::system_clock > timestamp_t
bool ProcessWS(THttpCallArg &arg)
Processing of websockets call-backs, invoked from RWebWindowWSHandler Method invoked from http server...
bool HasConnection(unsigned connid=0, bool only_active=true) const
returns true if specified connection id exists
std::thread fWindowThrd
! special thread for that window
void ProvideQueueEntry(unsigned connid, EQueueEntryKind kind, std::string &&arg)
Provide data to user callback User callback must be executed in the window thread.
bool HasKey(const std::string &key, bool also_newkey=false) const
Returns true if provided key value already exists (in processes map or in existing connections) In sp...
void CompleteWSSend(unsigned wsid)
Complete websocket send operation Clear "doing send" flag and check if next operation has to be start...
bool fUseServerThreads
! indicates that server thread is using, no special window thread
unsigned FindHeadlessConnection()
Returns connection id of window running in headless mode This can be special connection which may run...
int WaitForTimed(WebWindowWaitFunc_t check)
Waits until provided check function or lambdas returns non-zero value Check function has following si...
bool fProcessMT
! if window event processing performed in dedicated thread
void ClearConnection(std::shared_ptr< WebConn > &conn, bool provide_signal=false)
Signal that connection is closing.
int fProtocolCnt
! counter for protocol recording
void SetClientVersion(const std::string &vers)
Set client version, used as prefix in scripts URL When changed, web browser will reload all related J...
void RemoveMasterConnection(unsigned connid=0)
Remove master connection - if any.
void RemoveEmbedWindow(unsigned connid, int channel)
Remove RWebWindow associated with the channel.
_R__DEPRECATED_LATER("Use GetUrl() to get valid connection URL") std _R__DEPRECATED_LATER("Use GetAddr() to get valid connection URL") std void SetCallBacks(WebWindowConnectCallback_t conn, WebWindowDataCallback_t data, WebWindowConnectCallback_t disconn=nullptr)
Set call-backs function for connect, data and disconnect events.
static std::string gJSROOTsettings
! custom settings for JSROOT
std::string GenerateKey() const
Generate new unique key for the window.
void SetUseCurrentDir(bool on=true)
Configure if window can access local files via currentdir/ path of http server.
WebWindowConnectCallback_t fDisconnCallback
! callback for disconnect event
unsigned GetMaxQueueLength() const
Return maximal queue length of data which can be held by window.
static bool IsFileDialogMessage(const std::string &msg)
Check if this could be the message send by client to start new file dialog If returns true,...
static std::string GenerateKey(int keylen=32)
Static method to generate cryptographic key Parameter keylen defines length of cryptographic key in b...
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.
static bool IsLoopbackMode()
Returns true if loopback mode used by THttpServer for web widgets.
Contains arguments for single HTTP call.
UInt_t GetWSId() const
get web-socket id
const char * GetTopName() const
returns engine-specific top-name
const void * GetPostData() const
return pointer on posted with request data
const char * GetQuery() const
returns request query (string after ? in request URL)
Long_t GetPostDataLength() const
return length of posted with request data
Bool_t IsMethod(const char *name) const
returns kTRUE if post method is used
const char * GetFileName() const
returns file name from request URL
Online http server for arbitrary ROOT application.
Definition THttpServer.h:31
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:2459
virtual int Load(const char *module, const char *entry="", Bool_t system=kFALSE)
Load a shared library.
Definition TSystem.cxx:1872
This class represents a WWW compatible URL.
Definition TUrl.h:33
const Int_t n
Definition legend1.C:16
std::function< void(unsigned, const std::string &)> WebWindowDataCallback_t
function signature for call-backs from the window clients first argument is connection id,...
std::function< void(unsigned)> WebWindowConnectCallback_t
function signature for connect/disconnect call-backs argument is connection id
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.
std::shared_ptr< THttpCallArg > fHold
! request used to hold headless browser
~WebConn()
Destructor for WebConn Notify special HTTP request which blocks headless browser from exit.