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