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 <assert.h>
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 tmout = fMgr->GetLaunchTmout();
647
648 ConnectionsList_t selected;
649
650 {
651 std::lock_guard<std::mutex> grd(fConnMutex);
652
653 auto pred = [&](std::shared_ptr<WebConn> &e) {
654 std::chrono::duration<double> diff = stamp - e->fSendStamp;
655
656 if (diff.count() > tmout) {
657 R__LOG_DEBUG(0, WebGUILog()) << "Remove pending connection " << e->fKey << " after " << diff.count() << " sec";
658 selected.emplace_back(e);
659 return true;
660 }
661
662 return false;
663 };
664
665 fPendingConn.erase(std::remove_if(fPendingConn.begin(), fPendingConn.end(), pred), fPendingConn.end());
666 }
667}
668
669
670//////////////////////////////////////////////////////////////////////////////////////////
671/// Check if there are connection which are inactive for longer time
672/// For instance, batch browser will be stopped if no activity for 30 sec is there
673
675{
676 timestamp_t stamp = std::chrono::system_clock::now();
677
678 double batch_tmout = 20.;
679
680 std::vector<std::shared_ptr<WebConn>> clr;
681
682 {
683 std::lock_guard<std::mutex> grd(fConnMutex);
684
685 auto pred = [&](std::shared_ptr<WebConn> &conn) {
686 std::chrono::duration<double> diff = stamp - conn->fSendStamp;
687 // introduce large timeout
688 if ((diff.count() > batch_tmout) && conn->fHeadlessMode) {
689 conn->fActive = false;
690 clr.emplace_back(conn);
691 return true;
692 }
693 return false;
694 };
695
696 fConn.erase(std::remove_if(fConn.begin(), fConn.end(), pred), fConn.end());
697 }
698
699 for (auto &entry : clr)
700 ClearConnection(entry, true);
701}
702
703/////////////////////////////////////////////////////////////////////////
704/// Configure maximal number of allowed connections - 0 is unlimited
705/// Will not affect already existing connections
706/// Default is 1 - the only client is allowed
707/// Because of security reasons setting number of allowed connections is not sufficient now.
708/// To enable multi-connection mode, one also has to call
709/// `ROOT::RWebWindowsManager::SetSingleConnMode(false);`
710/// before creating of the RWebWindow instance
711
713{
714 bool single_conn_mode = RWebWindowWSHandler::GetBoolEnv("WebGui.SingleConnMode", 1) == 1;
715
716 std::lock_guard<std::mutex> grd(fConnMutex);
717
719}
720
721/////////////////////////////////////////////////////////////////////////
722/// returns configured connections limit (0 - default)
723
725{
726 std::lock_guard<std::mutex> grd(fConnMutex);
727
728 return fConnLimit;
729}
730
731/////////////////////////////////////////////////////////////////////////
732/// Configures connection token (default none)
733/// When specified, in URL of webpage such token should be provided as &token=value parameter,
734/// otherwise web window will refuse connection
735
736void RWebWindow::SetConnToken(const std::string &token)
737{
738 std::lock_guard<std::mutex> grd(fConnMutex);
739
740 fConnToken = token;
741}
742
743/////////////////////////////////////////////////////////////////////////
744/// Returns configured connection token
745
746std::string RWebWindow::GetConnToken() const
747{
748 std::lock_guard<std::mutex> grd(fConnMutex);
749
750 return fConnToken;
751}
752
753//////////////////////////////////////////////////////////////////////////////////////////
754/// Processing of websockets call-backs, invoked from RWebWindowWSHandler
755/// Method invoked from http server thread, therefore appropriate mutex must be used on all relevant data
756
758{
759 if (arg.GetWSId() == 0)
760 return true;
761
762 bool is_longpoll = arg.GetFileName() && ("root.longpoll"s == arg.GetFileName()),
763 is_remote = arg.GetTopName() && ("remote"s == arg.GetTopName());
764
765 // do not allow longpoll requests for loopback device
767 return false;
768
769 if (arg.IsMethod("WS_CONNECT")) {
770 TUrl url;
771 url.SetOptions(arg.GetQuery());
772 std::string key, ntry;
773 if(url.HasOption("key"))
774 key = url.GetValueFromOptions("key");
775 if(url.HasOption("ntry"))
776 ntry = url.GetValueFromOptions("ntry");
777
778 std::lock_guard<std::mutex> grd(fConnMutex);
779
780 if (is_longpoll && !is_remote && ntry == "1"s) {
781 // special workaround for local displays like qt6/cef
782 // they are not disconnected regularly when page reload is invoked
783 // therefore try to detect if new key is applied
784 for (unsigned indx = 0; indx < fConn.size(); indx++) {
785 if (!fConn[indx]->fNewKey.empty() && (key == HMAC(fConn[indx]->fNewKey, ""s, "attempt_1", 9))) {
786 auto conn = std::move(fConn[indx]);
787 fConn.erase(fConn.begin() + indx);
788 conn->fKeyUsed = 0;
789 conn->fKey = conn->fNewKey;
790 conn->fNewKey.clear();
791 conn->fConnId = ++fConnCnt; // change connection id to avoid confusion
792 conn->fWasFirst = indx == 0;
793 conn->ResetData();
794 conn->ResetStamps(); // reset stamps, after timeout connection wll be removed
795 fPendingConn.emplace_back(conn);
796 break;
797 }
798 }
799 }
800
801 // refuse connection when number of connections exceed limit
802 if (fConnLimit && (fConn.size() >= fConnLimit))
803 return false;
804
805 if (!fConnToken.empty()) {
806 // refuse connection which does not provide proper token
807 if (!url.HasOption("token") || (fConnToken != url.GetValueFromOptions("token"))) {
808 R__LOG_DEBUG(0, WebGUILog()) << "Refuse connection without proper token";
809 return false;
810 }
811 }
812
813 if (!IsRequireAuthKey())
814 return true;
815
816 if(key.empty()) {
817 R__LOG_DEBUG(0, WebGUILog()) << "key parameter not provided in url";
818 return false;
819 }
820
821 for (auto &conn : fPendingConn)
822 if (_CanTrustIn(conn, key, ntry, is_remote, true /* test_first_time */))
823 return true;
824
825 return false;
826 }
827
828 if (arg.IsMethod("WS_READY")) {
829
830 if (FindConnection(arg.GetWSId())) {
831 R__LOG_ERROR(WebGUILog()) << "WSHandle with given websocket id " << arg.GetWSId() << " already exists";
832 return false;
833 }
834
835 std::shared_ptr<WebConn> conn;
836 std::string key, ntry;
837
838 TUrl url;
839 url.SetOptions(arg.GetQuery());
840 if (url.HasOption("key"))
841 key = url.GetValueFromOptions("key");
842 if (url.HasOption("ntry"))
843 ntry = url.GetValueFromOptions("ntry");
844
845 std::lock_guard<std::mutex> grd(fConnMutex);
846
847 // check if in pending connections exactly this combination was checked
848 for (size_t n = 0; n < fPendingConn.size(); ++n)
849 if (_CanTrustIn(fPendingConn[n], key, ntry, is_remote, false /* test_first_time */)) {
850 conn = std::move(fPendingConn[n]);
851 fPendingConn.erase(fPendingConn.begin() + n);
852 break;
853 }
854
855 if (conn) {
856 conn->fWSId = arg.GetWSId();
857 conn->fActive = true;
858 conn->fRecvSeq = 0;
859 conn->fSendSeq = 1;
860 // preserve key for longpoll or when with session key used for HMAC hash of messages
861 // conn->fKey.clear();
862 conn->ResetStamps();
863 // remove files which are required for startup
864 if (conn->fDisplayHandle)
865 conn->fDisplayHandle->RemoveStartupFiles();
866 if (conn->fWasFirst)
867 fConn.emplace(fConn.begin(), conn);
868 else
869 fConn.emplace_back(conn);
870 return true;
871 } else if (!IsRequireAuthKey() && (!fConnLimit || (fConn.size() < fConnLimit))) {
872 fConn.emplace_back(std::make_shared<WebConn>(++fConnCnt, arg.GetWSId()));
873 return true;
874 }
875
876 // reject connection, should not really happen
877 return false;
878 }
879
880 // special security check for the longpoll requests
881 if(is_longpoll) {
882 auto conn = FindConnection(arg.GetWSId());
883 if (!conn)
884 return false;
885
886 TUrl url;
887 url.SetOptions(arg.GetQuery());
888
889 std::string key, ntry;
890 if(url.HasOption("key"))
891 key = url.GetValueFromOptions("key");
892 if(url.HasOption("ntry"))
893 ntry = url.GetValueFromOptions("ntry");
894
895 if (!_CanTrustIn(conn, key, ntry, is_remote, true /* test_first_time */))
896 return false;
897 }
898
899 if (arg.IsMethod("WS_CLOSE")) {
900 // connection is closed, one can remove handle, associated window will be closed
901
902 auto conn = RemoveConnection(arg.GetWSId(), true);
903
904 if (conn) {
905 bool do_clear_on_close = false;
906 if (!conn->fNewKey.empty()) {
907 // case when same handle want to be reused by client with new key
908 std::lock_guard<std::mutex> grd(fConnMutex);
909 conn->fKeyUsed = 0;
910 conn->fKey = conn->fNewKey;
911 conn->fNewKey.clear();
912 conn->fConnId = ++fConnCnt; // change connection id to avoid confusion
913 conn->ResetData();
914 conn->ResetStamps(); // reset stamps, after timeout connection wll be removed
915 fPendingConn.emplace_back(conn);
916 } else {
917 std::lock_guard<std::mutex> grd(fConnMutex);
918 do_clear_on_close = (fPendingConn.size() == 0) && (fConn.size() == 0);
919 }
920
922 fClearOnClose.reset();
923 }
924
925 return true;
926 }
927
928 if (!arg.IsMethod("WS_DATA")) {
929 R__LOG_ERROR(WebGUILog()) << "only WS_DATA request expected!";
930 return false;
931 }
932
933 auto conn = FindConnection(arg.GetWSId());
934
935 if (!conn) {
936 R__LOG_ERROR(WebGUILog()) << "Get websocket data without valid connection - ignore!!!";
937 return false;
938 }
939
940 if (arg.GetPostDataLength() <= 0)
941 return true;
942
943 // here start testing of HMAC in the begin of the message
944
945 const char *buf0 = (const char *) arg.GetPostData();
946 Long_t data_len = arg.GetPostDataLength();
947
948 const char *buf = strchr(buf0, ':');
949 if (!buf) {
950 R__LOG_ERROR(WebGUILog()) << "missing separator for HMAC checksum";
951 return false;
952 }
953
954 Int_t code_len = buf - buf0;
955 data_len -= code_len + 1;
956 buf++; // starting of normal message
957
958 if (data_len < 0) {
959 R__LOG_ERROR(WebGUILog()) << "no any data after HMAC checksum";
960 return false;
961 }
962
963 bool is_none = strncmp(buf0, "none:", 5) == 0, is_match = false;
964
965 if (!is_none) {
966 std::string hmac = HMAC(conn->fKey, fMgr->fSessionKey, buf, data_len);
967
968 is_match = (code_len == (Int_t) hmac.length()) && (strncmp(buf0, hmac.c_str(), code_len) == 0);
969 } else if (!fMgr->fUseSessionKey) {
970 // no packet signing without session key
971 is_match = true;
972 }
973
974 // IMPORTANT: final place where integrity of input message is checked!
975 if (!is_match) {
976 // mismatch of HMAC checksum
978 return false;
979 if (!is_none) {
980 R__LOG_ERROR(WebGUILog()) << "wrong HMAC checksum provided";
981 return false;
982 }
983 }
984
985 // here processing of received data should be performed
986 // this is task for the implemented windows
987
988 char *str_end = nullptr;
989
990 unsigned long oper_seq = std::strtoul(buf, &str_end, 10);
991 if (!str_end || *str_end != ':') {
992 R__LOG_ERROR(WebGUILog()) << "missing operation sequence";
993 return false;
994 }
995
996 if (is_remote && (oper_seq <= conn->fRecvSeq)) {
997 R__LOG_ERROR(WebGUILog()) << "supply same package again - MiM attacker?";
998 return false;
999 }
1000
1001 conn->fRecvSeq = oper_seq;
1002
1003 unsigned long ackn_oper = std::strtoul(str_end + 1, &str_end, 10);
1004 if (!str_end || *str_end != ':') {
1005 R__LOG_ERROR(WebGUILog()) << "missing number of acknowledged operations";
1006 return false;
1007 }
1008
1009 unsigned long can_send = std::strtoul(str_end + 1, &str_end, 10);
1010 if (!str_end || *str_end != ':') {
1011 R__LOG_ERROR(WebGUILog()) << "missing can_send counter";
1012 return false;
1013 }
1014
1015 unsigned long nchannel = std::strtoul(str_end + 1, &str_end, 10);
1016 if (!str_end || *str_end != ':') {
1017 R__LOG_ERROR(WebGUILog()) << "missing channel number";
1018 return false;
1019 }
1020
1021 Long_t processed_len = (str_end + 1 - buf);
1022
1023 if (processed_len > data_len) {
1024 R__LOG_ERROR(WebGUILog()) << "corrupted buffer";
1025 return false;
1026 }
1027
1028 std::string cdata(str_end + 1, data_len - processed_len);
1029
1030 timestamp_t stamp = std::chrono::system_clock::now();
1031
1032 {
1033 std::lock_guard<std::mutex> grd(conn->fMutex);
1034
1035 conn->fSendCredits += ackn_oper;
1036 conn->fRecvCount++;
1037 conn->fClientCredits = (int)can_send;
1038 conn->fRecvStamp = stamp;
1039 }
1040
1041 if (fProtocolCnt >= 0)
1042 if (!fProtocolConnId || (conn->fConnId == fProtocolConnId)) {
1043 fProtocolConnId = conn->fConnId; // remember connection
1044
1045 // record send event only for normal channel or very first message via ch0
1046 if ((nchannel != 0) || (cdata.find("READY=") == 0)) {
1047 if (fProtocol.length() > 2)
1048 fProtocol.insert(fProtocol.length() - 1, ",");
1049 fProtocol.insert(fProtocol.length() - 1, "\"send\"");
1050
1051 std::ofstream pfs(fProtocolFileName);
1052 pfs.write(fProtocol.c_str(), fProtocol.length());
1053 pfs.close();
1054 }
1055 }
1056
1057 if (nchannel == 0) {
1058 // special system channel
1059 if ((cdata.compare(0, 6, "READY=") == 0) && !conn->fReady) {
1060
1061 std::string key = cdata.substr(6);
1062 bool new_key = false;
1063 if (key.find("generate_key;") == 0) {
1064 new_key = true;
1065 key = key.substr(13);
1066 }
1067
1068 if (key.empty() && IsNativeOnlyConn()) {
1069 RemoveConnection(conn->fWSId);
1070 return false;
1071 }
1072
1073 if (!key.empty() && !conn->fKey.empty() && (conn->fKey != key)) {
1074 R__LOG_ERROR(WebGUILog()) << "Key mismatch after established connection " << key << " != " << conn->fKey;
1075 RemoveConnection(conn->fWSId);
1076 return false;
1077 }
1078
1079 if (!fPanelName.empty()) {
1080 // initialization not yet finished, appropriate panel should be started
1081 Send(conn->fConnId, "SHOWPANEL:"s + fPanelName);
1082 conn->fReady = 5;
1083 } else {
1084 ProvideQueueEntry(conn->fConnId, kind_Connect, ""s);
1085 conn->fReady = 10;
1086 }
1087 if (new_key && !fMaster) {
1088 conn->fNewKey = GenerateKey();
1089 if(!conn->fNewKey.empty())
1090 SubmitData(conn->fConnId, true, "NEW_KEY="s + conn->fNewKey, 0);
1091 }
1092 } else if (cdata.compare(0, 8, "CLOSECH=") == 0) {
1093 int channel = std::stoi(cdata.substr(8));
1094 auto iter = conn->fEmbed.find(channel);
1095 if (iter != conn->fEmbed.end()) {
1096 iter->second->ProvideQueueEntry(conn->fConnId, kind_Disconnect, ""s);
1097 conn->fEmbed.erase(iter);
1098 }
1099 } else if (cdata.compare(0, 7, "RESIZE=") == 0) {
1100 auto p = cdata.find(",");
1101 if (p != std::string::npos) {
1102 auto width = std::stoi(cdata.substr(7, p - 7));
1103 auto height = std::stoi(cdata.substr(p + 1));
1104 if ((width > 0) && (height > 0) && conn->fDisplayHandle)
1105 conn->fDisplayHandle->Resize(width, height);
1106 }
1107 } else if (cdata == "GENERATE_KEY") {
1108 if (fMaster) {
1109 R__LOG_ERROR(WebGUILog()) << "Not able to generate new key with master connections";
1110 } else {
1111 conn->fNewKey = GenerateKey();
1112 if(!conn->fNewKey.empty())
1113 SubmitData(conn->fConnId, true, "NEW_KEY="s + conn->fNewKey, -1);
1114 }
1115 }
1116 } else if (fPanelName.length() && (conn->fReady < 10)) {
1117 if (cdata == "PANEL_READY") {
1118 R__LOG_DEBUG(0, WebGUILog()) << "Get panel ready " << fPanelName;
1119 ProvideQueueEntry(conn->fConnId, kind_Connect, ""s);
1120 conn->fReady = 10;
1121 } else {
1122 RemoveConnection(conn->fWSId, true);
1123 }
1124 } else if (nchannel == 1) {
1125 ProvideQueueEntry(conn->fConnId, kind_Data, std::move(cdata));
1126 } else if (nchannel > 1) {
1127 // process embed window
1128 auto embed_window = conn->fEmbed[nchannel];
1129 if (embed_window)
1130 embed_window->ProvideQueueEntry(conn->fConnId, kind_Data, std::move(cdata));
1131 }
1132
1134
1135 return true;
1136}
1137
1138//////////////////////////////////////////////////////////////////////////////////////////
1139/// Complete websocket send operation
1140/// Clear "doing send" flag and check if next operation has to be started
1141
1143{
1144 auto conn = FindConnection(wsid);
1145
1146 if (!conn)
1147 return;
1148
1149 {
1150 std::lock_guard<std::mutex> grd(conn->fMutex);
1151 conn->fDoingSend = false;
1152 }
1153
1154 CheckDataToSend(conn);
1155}
1156
1157//////////////////////////////////////////////////////////////////////////////////////////
1158/// Internal method to prepare text part of send data
1159/// Should be called under locked connection mutex
1160
1161std::string RWebWindow::_MakeSendHeader(std::shared_ptr<WebConn> &conn, bool txt, const std::string &data, int chid)
1162{
1163 std::string buf;
1164
1165 if (!conn->fWSId || !fWSHandler) {
1166 R__LOG_ERROR(WebGUILog()) << "try to send text data when connection not established";
1167 return buf;
1168 }
1169
1170 if (conn->fSendCredits <= 0) {
1171 R__LOG_ERROR(WebGUILog()) << "No credits to send text data via connection";
1172 return buf;
1173 }
1174
1175 if (conn->fDoingSend) {
1176 R__LOG_ERROR(WebGUILog()) << "Previous send operation not completed yet";
1177 return buf;
1178 }
1179
1180 if (txt)
1181 buf.reserve(data.length() + 100);
1182
1183 buf.append(std::to_string(conn->fSendSeq++));
1184 buf.append(":");
1185 buf.append(std::to_string(conn->fRecvCount));
1186 buf.append(":");
1187 buf.append(std::to_string(conn->fSendCredits));
1188 buf.append(":");
1189 conn->fRecvCount = 0; // we confirm how many packages was received
1190 conn->fSendCredits--;
1191
1192 buf.append(std::to_string(chid));
1193 buf.append(":");
1194
1195 if (txt) {
1196 buf.append(data);
1197 } else if (data.length()==0) {
1198 buf.append("$$nullbinary$$");
1199 } else {
1200 buf.append("$$binary$$");
1201 if (!conn->fKey.empty() && !fMgr->fSessionKey.empty() && fMgr->fUseSessionKey)
1202 buf.append(HMAC(conn->fKey, fMgr->fSessionKey, data.data(), data.length()));
1203 }
1204
1205 return buf;
1206}
1207
1208//////////////////////////////////////////////////////////////////////////////////////////
1209/// Checks if one should send data for specified connection
1210/// Returns true when send operation was performed
1211
1212bool RWebWindow::CheckDataToSend(std::shared_ptr<WebConn> &conn)
1213{
1214 std::string hdr, data, prefix;
1215
1216 {
1217 std::lock_guard<std::mutex> grd(conn->fMutex);
1218
1219 if (!conn->fActive || (conn->fSendCredits <= 0) || conn->fDoingSend) return false;
1220
1221 if (!conn->fQueue.empty()) {
1222 QueueItem &item = conn->fQueue.front();
1223 hdr = _MakeSendHeader(conn, item.fText, item.fData, item.fChID);
1224 if (!hdr.empty() && !item.fText)
1225 data = std::move(item.fData);
1226 conn->fQueue.pop();
1227 } else if ((conn->fClientCredits < 3) && (conn->fRecvCount > 1)) {
1228 // give more credits to the client
1229 hdr = _MakeSendHeader(conn, true, "KEEPALIVE", 0);
1230 }
1231
1232 if (hdr.empty()) return false;
1233
1234 conn->fDoingSend = true;
1235 }
1236
1237 // add HMAC checksum for string send to client
1238 if (!conn->fKey.empty() && !fMgr->fSessionKey.empty() && fMgr->fUseSessionKey) {
1239 prefix = HMAC(conn->fKey, fMgr->fSessionKey, hdr.c_str(), hdr.length());
1240 } else {
1241 prefix = "none";
1242 }
1243
1244 prefix += ":";
1245 hdr.insert(0, prefix);
1246
1247 int res = 0;
1248
1249 if (data.empty()) {
1250 res = fWSHandler->SendCharStarWS(conn->fWSId, hdr.c_str());
1251 } else {
1252 res = fWSHandler->SendHeaderWS(conn->fWSId, hdr.c_str(), data.data(), data.length());
1253 }
1254
1255 // submit operation, will be processed
1256 if (res >=0) return true;
1257
1258 // failure, clear sending flag
1259 std::lock_guard<std::mutex> grd(conn->fMutex);
1260 conn->fDoingSend = false;
1261 return false;
1262}
1263
1264
1265//////////////////////////////////////////////////////////////////////////////////////////
1266/// Checks if new data can be send (internal use only)
1267/// If necessary, provide credits to the client
1268/// \param only_once if true, data sending performed once or until there is no data to send
1269
1271{
1272 // make copy of all connections to be independent later, only active connections are checked
1273 auto arr = GetWindowConnections(0, true);
1274
1275 do {
1276 bool isany = false;
1277
1278 for (auto &conn : arr)
1279 if (CheckDataToSend(conn))
1280 isany = true;
1281
1282 if (!isany) break;
1283
1284 } while (!only_once);
1285}
1286
1287///////////////////////////////////////////////////////////////////////////////////
1288/// Special method to process all internal activity when window runs in separate thread
1289
1291{
1293
1295
1297
1299}
1300
1301///////////////////////////////////////////////////////////////////////////////////
1302/// Returns window address which is used in URL
1303
1304std::string RWebWindow::GetAddr() const
1305{
1306 return fWSHandler->GetName();
1307}
1308
1309///////////////////////////////////////////////////////////////////////////////////
1310/// DEPRECATED. Use GetUrl method instead while more arguments are required to connect with the widget
1311/// Returns relative URL address for the specified window
1312/// Address can be required if one needs to access data from one window into another window
1313/// Used for instance when inserting panel into canvas
1314
1315std::string RWebWindow::GetRelativeAddr(const std::shared_ptr<RWebWindow> &win) const
1316{
1317 if (fMgr != win->fMgr) {
1318 R__LOG_ERROR(WebGUILog()) << "Same web window manager should be used";
1319 return "";
1320 }
1321
1322 std::string res("../");
1323 res.append(win->GetAddr());
1324 res.append("/");
1325 return res;
1326}
1327
1328///////////////////////////////////////////////////////////////////////////////////
1329/// DEPRECATED. Use GetUrl method instead while more arguments are required to connect with the widget
1330/// Address can be required if one needs to access data from one window into another window
1331/// Used for instance when inserting panel into canvas
1332
1333std::string RWebWindow::GetRelativeAddr(const RWebWindow &win) const
1334{
1335 if (fMgr != win.fMgr) {
1336 R__LOG_ERROR(WebGUILog()) << "Same web window manager should be used";
1337 return "";
1338 }
1339
1340 std::string res("../");
1341 res.append(win.GetAddr());
1342 res.append("/");
1343 return res;
1344}
1345
1346/////////////////////////////////////////////////////////////////////////
1347/// Set client version, used as prefix in scripts URL
1348/// When changed, web browser will reload all related JS files while full URL will be different
1349/// Default is empty value - no extra string in URL
1350/// Version should be string like "1.2" or "ver1.subv2" and not contain any special symbols
1351
1352void RWebWindow::SetClientVersion(const std::string &vers)
1353{
1354 std::lock_guard<std::mutex> grd(fConnMutex);
1356}
1357
1358/////////////////////////////////////////////////////////////////////////
1359/// Returns current client version
1360
1362{
1363 std::lock_guard<std::mutex> grd(fConnMutex);
1364 return fClientVersion;
1365}
1366
1367/////////////////////////////////////////////////////////////////////////
1368/// Set arbitrary JSON data, which is accessible via conn.getUserArgs() method in JavaScript
1369/// This JSON code injected into main HTML document into connectWebWindow({})
1370/// Must be set before RWebWindow::Show() method is called
1371/// \param args - arbitrary JSON data which can be provided to client side
1372
1373void RWebWindow::SetUserArgs(const std::string &args)
1374{
1375 std::lock_guard<std::mutex> grd(fConnMutex);
1376 fUserArgs = args;
1377}
1378
1379/////////////////////////////////////////////////////////////////////////
1380/// Returns configured user arguments for web window
1381/// See \ref SetUserArgs method for more details
1382
1383std::string RWebWindow::GetUserArgs() const
1384{
1385 std::lock_guard<std::mutex> grd(fConnMutex);
1386 return fUserArgs;
1387}
1388
1389///////////////////////////////////////////////////////////////////////////////////
1390/// Returns current number of active clients connections
1391/// \param with_pending if true, also pending (not yet established) connection accounted
1392
1394{
1395 bool is_master = !!fMaster;
1396
1397 std::lock_guard<std::mutex> grd(fConnMutex);
1398
1399 if (is_master)
1400 return fMasterConns.size();
1401
1402 auto sz = fConn.size();
1403 if (with_pending)
1404 sz += fPendingConn.size();
1405 return sz;
1406}
1407
1408///////////////////////////////////////////////////////////////////////////////////
1409/// Configures recording of communication data in protocol file
1410/// Provided filename will be used to store JSON array with names of written files - text or binary
1411/// If data was send from client, "send" entry will be placed. JSON file will look like:
1412///
1413/// ["send", "msg0.txt", "send", "msg1.txt", "msg2.txt"]
1414///
1415/// If empty file name is provided, data recording will be disabled
1416/// Recorded data can be used in JSROOT directly to test client code without running C++ server
1417
1418void RWebWindow::RecordData(const std::string &fname, const std::string &fprefix)
1419{
1421 fProtocolCnt = fProtocolFileName.empty() ? -1 : 0;
1424 fProtocol = "[]"; // empty array
1425}
1426
1427///////////////////////////////////////////////////////////////////////////////////
1428/// Returns connection id for specified connection sequence number
1429/// Only active connections are returned - where clients confirms connection
1430/// Total number of connections can be retrieved with NumConnections() method
1431/// \param num connection sequence number
1432
1433unsigned RWebWindow::GetConnectionId(int num) const
1434{
1435 bool is_master = !!fMaster;
1436
1437 std::lock_guard<std::mutex> grd(fConnMutex);
1438
1439 if (is_master)
1440 return (num >= 0) && (num < (int)fMasterConns.size()) ? fMasterConns[num].connid : 0;
1441
1442 return ((num >= 0) && (num < (int)fConn.size()) && fConn[num]->fActive) ? fConn[num]->fConnId : 0;
1443}
1444
1445///////////////////////////////////////////////////////////////////////////////////
1446/// returns vector with all existing connections ids
1447/// One also can exclude specified connection from return result,
1448/// which can be useful to be able reply too all but this connections
1449
1450std::vector<unsigned> RWebWindow::GetConnections(unsigned excludeid) const
1451{
1452 std::vector<unsigned> res;
1453
1454 bool is_master = !!fMaster;
1455
1456 std::lock_guard<std::mutex> grd(fConnMutex);
1457
1458 if (is_master) {
1459 for (auto & entry : fMasterConns)
1460 if (entry.connid != excludeid)
1461 res.emplace_back(entry.connid);
1462 } else {
1463 for (auto & entry : fConn)
1464 if (entry->fActive && (entry->fConnId != excludeid))
1465 res.emplace_back(entry->fConnId);
1466 }
1467
1468 return res;
1469}
1470
1471///////////////////////////////////////////////////////////////////////////////////
1472/// returns true if specified connection id exists
1473/// \param connid connection id (0 - any)
1474/// \param only_active when true only active connection will be checked, otherwise also pending (not yet established) connections are checked
1475
1476bool RWebWindow::HasConnection(unsigned connid, bool only_active) const
1477{
1478 if (fMaster) {
1479 auto lst = GetMasterConnections(connid);
1480 for (auto & entry : lst)
1481 if (fMaster->HasConnection(entry.connid, only_active))
1482 return true;
1483 return false;
1484 }
1485
1486 std::lock_guard<std::mutex> grd(fConnMutex);
1487
1488 for (auto &conn : fConn) {
1489 if (connid && (conn->fConnId != connid))
1490 continue;
1491 if (conn->fActive || !only_active)
1492 return true;
1493 }
1494
1495 if (!only_active)
1496 for (auto &conn : fPendingConn) {
1497 if (!connid || (conn->fConnId == connid))
1498 return true;
1499 }
1500
1501 return false;
1502}
1503
1504///////////////////////////////////////////////////////////////////////////////////
1505/// Closes all connection to clients
1506/// Normally leads to closing of all correspondent browser windows
1507/// Some browsers (like firefox) do not allow by default to close window
1508
1510{
1511 SubmitData(0, true, "CLOSE", 0);
1512}
1513
1514///////////////////////////////////////////////////////////////////////////////////
1515/// Close specified connection
1516/// \param connid connection id, when 0 - all connections will be closed
1517
1518void RWebWindow::CloseConnection(unsigned connid)
1519{
1520 if (connid)
1521 SubmitData(connid, true, "CLOSE", 0);
1522}
1523
1524///////////////////////////////////////////////////////////////////////////////////
1525/// returns connection list (or all active connections)
1526/// \param connid connection id, when 0 - all existing connections are returned
1527/// \param only_active when true, only active (already established) connections are returned
1528
1530{
1532
1533 {
1534 std::lock_guard<std::mutex> grd(fConnMutex);
1535
1536 for (auto &conn : fConn) {
1537 if ((conn->fActive || !only_active) && (!connid || (conn->fConnId == connid)))
1538 arr.push_back(conn);
1539 }
1540
1541 if (!only_active)
1542 for (auto &conn : fPendingConn)
1543 if (!connid || (conn->fConnId == connid))
1544 arr.push_back(conn);
1545 }
1546
1547 return arr;
1548}
1549
1550///////////////////////////////////////////////////////////////////////////////////
1551/// Returns true if sending via specified connection can be performed
1552/// \param connid connection id, when 0 - all existing connections are checked
1553/// \param direct when true, checks if direct sending (without queuing) is possible
1554
1555bool RWebWindow::CanSend(unsigned connid, bool direct) const
1556{
1557 if (fMaster) {
1558 auto lst = GetMasterConnections(connid);
1559 for (auto &entry : lst) {
1560 if (!fMaster->CanSend(entry.connid, direct))
1561 return false;
1562 }
1563 } else {
1564 auto arr = GetWindowConnections(connid, direct); // for direct sending connection has to be active
1565
1566 auto maxqlen = GetMaxQueueLength();
1567
1568 for (auto &conn : arr) {
1569
1570 std::lock_guard<std::mutex> grd(conn->fMutex);
1571
1572 if (direct && (!conn->fQueue.empty() || (conn->fSendCredits == 0) || conn->fDoingSend))
1573 return false;
1574
1575 if (conn->fQueue.size() >= maxqlen)
1576 return false;
1577 }
1578 }
1579
1580 return true;
1581}
1582
1583///////////////////////////////////////////////////////////////////////////////////
1584/// Returns send queue length for specified connection
1585/// \param connid connection id, 0 - maximal value for all connections is returned
1586/// If wrong connection id specified, -1 is return
1587
1588int RWebWindow::GetSendQueueLength(unsigned connid) const
1589{
1590 int maxq = -1;
1591
1592 if (fMaster) {
1593 auto lst = GetMasterConnections(connid);
1594 for (auto &entry : lst) {
1595 int len = fMaster->GetSendQueueLength(entry.connid);
1596 if (len > maxq)
1597 maxq = len;
1598 }
1599 } else {
1600 auto lst = GetWindowConnections(connid);
1601 for (auto &conn : lst) {
1602 std::lock_guard<std::mutex> grd(conn->fMutex);
1603 int len = conn->fQueue.size();
1604 if (len > maxq)
1605 maxq = len;
1606 }
1607 }
1608
1609 return maxq;
1610}
1611
1612///////////////////////////////////////////////////////////////////////////////////
1613/// Internal method to send data
1614/// \param connid connection id, when 0 - data will be send to all connections
1615/// \param txt is text message that should be sent
1616/// \param data data to be std-moved to SubmitData function
1617/// \param chid channel id, 1 - normal communication, 0 - internal with highest priority
1618
1619void RWebWindow::SubmitData(unsigned connid, bool txt, std::string &&data, int chid)
1620{
1621 if (fMaster) {
1622 auto lst = GetMasterConnections(connid);
1623 auto cnt = lst.size();
1624 for (auto & entry : lst)
1625 if (--cnt)
1626 fMaster->SubmitData(entry.connid, txt, std::string(data), entry.channel);
1627 else
1628 fMaster->SubmitData(entry.connid, txt, std::move(data), entry.channel);
1629 return;
1630 }
1631
1632 auto arr = GetWindowConnections(connid);
1633 auto cnt = arr.size();
1634 auto maxqlen = GetMaxQueueLength();
1635
1636 bool clear_queue = false;
1637
1638 if (chid == -1) {
1639 chid = 0;
1640 clear_queue = true;
1641 }
1642
1643 timestamp_t stamp = std::chrono::system_clock::now();
1644
1645 for (auto &conn : arr) {
1646
1647 if ((fProtocolCnt >= 0) && (chid > 0))
1648 if (!fProtocolConnId || (conn->fConnId == fProtocolConnId)) {
1649 fProtocolConnId = conn->fConnId; // remember connection
1650 std::string fname = fProtocolPrefix;
1651 fname.append("msg");
1652 fname.append(std::to_string(fProtocolCnt++));
1653 if (chid > 1) {
1654 fname.append("_ch");
1655 fname.append(std::to_string(chid));
1656 }
1657 fname.append(txt ? ".txt" : ".bin");
1658
1659 std::ofstream ofs(fname);
1660 ofs.write(data.c_str(), data.length());
1661 ofs.close();
1662
1663 if (fProtocol.length() > 2)
1664 fProtocol.insert(fProtocol.length() - 1, ",");
1665 fProtocol.insert(fProtocol.length() - 1, "\""s + fname + "\""s);
1666
1667 std::ofstream pfs(fProtocolFileName);
1668 pfs.write(fProtocol.c_str(), fProtocol.length());
1669 pfs.close();
1670 }
1671
1672 conn->fSendStamp = stamp;
1673
1674 std::lock_guard<std::mutex> grd(conn->fMutex);
1675
1676 if (clear_queue) {
1677 while (!conn->fQueue.empty())
1678 conn->fQueue.pop();
1679 }
1680
1681 if (conn->fQueue.size() < maxqlen) {
1682 if (--cnt)
1683 conn->fQueue.emplace(chid, txt, std::string(data)); // make copy
1684 else
1685 conn->fQueue.emplace(chid, txt, std::move(data)); // move content
1686 } else {
1687 R__LOG_ERROR(WebGUILog()) << "Maximum queue length " << maxqlen << " achieved, can be changed with SetMaxQueueLength(v) method";
1688 }
1689 }
1690
1692}
1693
1694///////////////////////////////////////////////////////////////////////////////////
1695/// Sends data to specified connection
1696/// \param connid connection id, when 0 - data will be send to all connections
1697/// \param data data to be copied to SubmitData function
1698
1699void RWebWindow::Send(unsigned connid, const std::string &data)
1700{
1701 SubmitData(connid, true, std::string(data), 1);
1702}
1703
1704///////////////////////////////////////////////////////////////////////////////////
1705/// Send binary data to specified connection
1706/// \param connid connection id, when 0 - data will be send to all connections
1707/// \param data data to be std-moved to SubmitData function
1708
1709void RWebWindow::SendBinary(unsigned connid, std::string &&data)
1710{
1711 SubmitData(connid, false, std::move(data), 1);
1712}
1713
1714///////////////////////////////////////////////////////////////////////////////////
1715/// Send binary data to specified connection
1716/// \param connid connection id, when 0 - data will be send to all connections
1717/// \param data pointer to binary data
1718/// \param len number of bytes in data
1719
1720void RWebWindow::SendBinary(unsigned connid, const void *data, std::size_t len)
1721{
1722 std::string buf;
1723 buf.resize(len);
1724 std::copy((const char *)data, (const char *)data + len, buf.begin());
1725 SubmitData(connid, false, std::move(buf), 1);
1726}
1727
1728///////////////////////////////////////////////////////////////////////////////////
1729/// Assign thread id which has to be used for callbacks
1730/// WARNING!!! only for expert use
1731/// Automatically done at the moment when any callback function is invoked
1732/// Can be invoked once again if window Run method will be invoked from other thread
1733/// Normally should be invoked before Show() method is called
1734
1736{
1737 fUseServerThreads = false;
1738 fUseProcessEvents = false;
1739 fProcessMT = false;
1740 fCallbacksThrdIdSet = true;
1741 fCallbacksThrdId = std::this_thread::get_id();
1743 fProcessMT = true;
1744 } else if (fMgr->IsUseHttpThread()) {
1745 // special thread is used by the manager, but main thread used for the canvas - not supported
1746 R__LOG_ERROR(WebGUILog()) << "create web window from main thread when THttpServer created with special thread - not supported";
1747 }
1748}
1749
1750/////////////////////////////////////////////////////////////////////////////////
1751/// Let use THttpServer threads to process requests
1752/// WARNING!!! only for expert use
1753/// Should be only used when application provides proper locking and
1754/// does not block. Such mode provides minimal possible latency
1755/// Must be called before callbacks are assigned
1756
1758{
1759 fUseServerThreads = true;
1760 fUseProcessEvents = false;
1761 fCallbacksThrdIdSet = false;
1762 fProcessMT = true;
1763}
1764
1765/////////////////////////////////////////////////////////////////////////////////
1766/// Start special thread which will be used by the window to handle all callbacks
1767/// One has to be sure, that access to global ROOT structures are minimized and
1768/// protected with ROOT::EnableThreadSafety(); call
1769
1771{
1772 if (fHasWindowThrd) {
1773 R__LOG_WARNING(WebGUILog()) << "thread already started for the window";
1774 return;
1775 }
1776
1777 fHasWindowThrd = true;
1778
1779 std::thread thrd([this] {
1781 while(fHasWindowThrd)
1782 Run(0.1);
1783 fCallbacksThrdIdSet = false;
1784 });
1785
1786 fWindowThrd = std::move(thrd);
1787}
1788
1789/////////////////////////////////////////////////////////////////////////////////
1790/// Stop special thread
1791
1793{
1794 if (!fHasWindowThrd)
1795 return;
1796
1797 fHasWindowThrd = false;
1798 fWindowThrd.join();
1799}
1800
1801
1802/////////////////////////////////////////////////////////////////////////////////
1803/// Set call-back function for data, received from the clients via websocket
1804///
1805/// Function should have signature like void func(unsigned connid, const std::string &data)
1806/// First argument identifies connection (unique for each window), second argument is received data
1807///
1808/// At the moment when callback is assigned, RWebWindow working thread is detected.
1809/// If called not from main application thread, RWebWindow::Run() function must be regularly called from that thread.
1810///
1811/// Most simple way to assign call-back - use of c++11 lambdas like:
1812/// ~~~ {.cpp}
1813/// auto win = RWebWindow::Create();
1814/// win->SetDefaultPage("file:./page.htm");
1815/// win->SetDataCallBack(
1816/// [](unsigned connid, const std::string &data) {
1817/// printf("Conn:%u data:%s\n", connid, data.c_str());
1818/// }
1819/// );
1820/// win->Show();
1821/// ~~~
1822
1829
1830/////////////////////////////////////////////////////////////////////////////////
1831/// Set call-back function for new connection
1832
1839
1840/////////////////////////////////////////////////////////////////////////////////
1841/// Set call-back function for disconnecting
1842
1849
1850/////////////////////////////////////////////////////////////////////////////////
1851/// Set handle which is cleared when last active connection is closed
1852/// Typically can be used to destroy web-based widget at such moment
1853
1854void RWebWindow::SetClearOnClose(const std::shared_ptr<void> &handle)
1855{
1856 fClearOnClose = handle;
1857}
1858
1859/////////////////////////////////////////////////////////////////////////////////
1860/// Set call-backs function for connect, data and disconnect events
1861
1870
1871/////////////////////////////////////////////////////////////////////////////////
1872/// Reset window call-backs and close connections
1873/// Should be invoked in widget destructor to simplify cleanup process
1874
1876{
1878
1879 fConnCallback = nullptr;
1880 fDataCallback = nullptr;
1881 fDisconnCallback = nullptr;
1882
1883 if (fWSHandler)
1884 fWSHandler->SetDisabled();
1885}
1886
1887/////////////////////////////////////////////////////////////////////////////////
1888/// Waits until provided check function or lambdas returns non-zero value
1889/// Check function has following signature: int func(double spent_tm)
1890/// Waiting will be continued, if function returns zero.
1891/// Parameter spent_tm is time in seconds, which already spent inside the function
1892/// First non-zero value breaks loop and result is returned.
1893/// Runs application mainloop and short sleeps in-between
1894
1896{
1897 return fMgr->WaitFor(*this, check);
1898}
1899
1900/////////////////////////////////////////////////////////////////////////////////
1901/// Waits until provided check function or lambdas returns non-zero value
1902/// Check function has following signature: int func(double spent_tm)
1903/// Waiting will be continued, if function returns zero.
1904/// Parameter spent_tm in lambda is time in seconds, which already spent inside the function
1905/// First non-zero value breaks waiting loop and result is returned (or 0 if time is expired).
1906/// Runs application mainloop and short sleeps in-between
1907/// WebGui.OperationTmout rootrc parameter defines waiting time in seconds
1908
1910{
1911 return fMgr->WaitFor(*this, check, true, GetOperationTmout());
1912}
1913
1914/////////////////////////////////////////////////////////////////////////////////
1915/// Waits until provided check function or lambdas returns non-zero value
1916/// Check function has following signature: int func(double spent_tm)
1917/// Waiting will be continued, if function returns zero.
1918/// Parameter spent_tm in lambda is time in seconds, which already spent inside the function
1919/// First non-zero value breaks waiting loop and result is returned (or 0 if time is expired).
1920/// Runs application mainloop and short sleeps in-between
1921/// duration (in seconds) defines waiting time
1922
1924{
1925 return fMgr->WaitFor(*this, check, true, duration);
1926}
1927
1928
1929/////////////////////////////////////////////////////////////////////////////////
1930/// Run window functionality for specified time
1931/// If no action can be performed - just sleep specified time
1932
1934{
1935 if (!fCallbacksThrdIdSet || (fCallbacksThrdId != std::this_thread::get_id())) {
1936 R__LOG_WARNING(WebGUILog()) << "Change thread id where RWebWindow is executed";
1937 fCallbacksThrdIdSet = true;
1938 fCallbacksThrdId = std::this_thread::get_id();
1939 }
1940
1941 if (tm <= 0) {
1942 Sync();
1943 } else {
1944 WaitForTimed([](double) { return 0; }, tm);
1945 }
1946}
1947
1948
1949/////////////////////////////////////////////////////////////////////////////////
1950/// Add embed window
1951
1952unsigned RWebWindow::AddEmbedWindow(std::shared_ptr<RWebWindow> window, unsigned connid, int channel)
1953{
1954 if (channel < 2)
1955 return 0;
1956
1957 auto arr = GetWindowConnections(connid, true);
1958 if (arr.size() == 0)
1959 return 0;
1960
1961 // check if channel already occupied
1962 if (arr[0]->fEmbed.find(channel) != arr[0]->fEmbed.end())
1963 return 0;
1964
1965 arr[0]->fEmbed[channel] = window;
1966
1967 return arr[0]->fConnId;
1968}
1969
1970/////////////////////////////////////////////////////////////////////////////////
1971/// Remove RWebWindow associated with the channel
1972
1973void RWebWindow::RemoveEmbedWindow(unsigned connid, int channel)
1974{
1975 auto arr = GetWindowConnections(connid);
1976
1977 for (auto &conn : arr) {
1978 auto iter = conn->fEmbed.find(channel);
1979 if (iter != conn->fEmbed.end())
1980 conn->fEmbed.erase(iter);
1981 }
1982}
1983
1984
1985/////////////////////////////////////////////////////////////////////////////////
1986/// Create new RWebWindow
1987/// Using default RWebWindowsManager
1988
1989std::shared_ptr<RWebWindow> RWebWindow::Create()
1990{
1991 return RWebWindowsManager::Instance()->CreateWindow();
1992}
1993
1994/////////////////////////////////////////////////////////////////////////////////
1995/// Terminate ROOT session
1996/// Tries to correctly close THttpServer, associated with RWebWindowsManager
1997/// After that exit from process
1998
2000{
2001
2002 // workaround to release all connection-specific handles as soon as possible
2003 // required to work with QWebEngine
2004 // once problem solved, can be removed here
2006
2007 {
2008 std::lock_guard<std::mutex> grd(fConnMutex);
2009 std::swap(arr1, fConn);
2010 std::swap(arr2, fPendingConn);
2011 }
2012
2013 fMgr->Terminate();
2014}
2015
2016/////////////////////////////////////////////////////////////////////////////////
2017/// Static method to show web window
2018/// Has to be used instead of RWebWindow::Show() when window potentially can be embed into other windows
2019/// Soon RWebWindow::Show() method will be done protected
2020
2021unsigned RWebWindow::ShowWindow(std::shared_ptr<RWebWindow> window, const RWebDisplayArgs &args)
2022{
2023 if (!window)
2024 return 0;
2025
2027 auto master = args.fMaster;
2028 while (master && master->fMaster)
2029 master = master->fMaster;
2030
2031 if (master && window->fMaster && window->fMaster != master) {
2032 R__LOG_ERROR(WebGUILog()) << "Cannot use different master for same RWebWindow";
2033 return 0;
2034 }
2035
2036 unsigned connid = master ? master->AddEmbedWindow(window, args.fMasterConnection, args.fMasterChannel) : 0;
2037
2038 if (connid > 0) {
2039
2040 window->RemoveMasterConnection(connid);
2041
2042 window->AddMasterConnection(master, connid, args.fMasterChannel);
2043
2044 // inform client that connection is established and window initialized
2045 master->SubmitData(connid, true, "EMBED_DONE"s, args.fMasterChannel);
2046
2047 // provide call back for window itself that connection is ready
2048 window->ProvideQueueEntry(connid, kind_Connect, ""s);
2049 }
2050
2051 return connid;
2052 }
2053
2054 return window->Show(args);
2055}
2056
2057std::function<bool(const std::shared_ptr<RWebWindow> &, unsigned, const std::string &)> RWebWindow::gStartDialogFunc = nullptr;
2058
2059/////////////////////////////////////////////////////////////////////////////////////
2060/// Configure func which has to be used for starting dialog
2061
2062
2063void RWebWindow::SetStartDialogFunc(std::function<bool(const std::shared_ptr<RWebWindow> &, unsigned, const std::string &)> func)
2064{
2065 gStartDialogFunc = func;
2066}
2067
2068/////////////////////////////////////////////////////////////////////////////////////
2069/// Check if this could be the message send by client to start new file dialog
2070/// If returns true, one can call RWebWindow::EmbedFileDialog() to really create file dialog
2071/// instance inside existing widget
2072
2073bool RWebWindow::IsFileDialogMessage(const std::string &msg)
2074{
2075 return msg.compare(0, 11, "FILEDIALOG:") == 0;
2076}
2077
2078/////////////////////////////////////////////////////////////////////////////////////
2079/// Create dialog instance to use as embedded dialog inside provided widget
2080/// Loads libROOTBrowserv7 and tries to call RFileDialog::Embedded() method
2081/// Embedded dialog started on the client side where FileDialogController.SaveAs() method called
2082/// Such method immediately send message with "FILEDIALOG:" prefix
2083/// On the server side widget should detect such message and call RFileDialog::Embedded()
2084/// providing received string as second argument.
2085/// Returned instance of shared_ptr<RFileDialog> may be used to assign callback when file is selected
2086
2087bool RWebWindow::EmbedFileDialog(const std::shared_ptr<RWebWindow> &window, unsigned connid, const std::string &args)
2088{
2089 if (!gStartDialogFunc)
2090 gSystem->Load("libROOTBrowserv7");
2091
2092 if (!gStartDialogFunc)
2093 return false;
2094
2095 return gStartDialogFunc(window, connid, args);
2096}
2097
2098/////////////////////////////////////////////////////////////////////////////////////
2099/// Calculate HMAC checksum for provided key and message
2100/// Key combined from connection key and session key
2101
2102std::string RWebWindow::HMAC(const std::string &key, const std::string &sessionKey, const char *msg, int msglen)
2103{
2104 using namespace ROOT::Internal::SHA256;
2105
2106 auto get_digest = [](sha256_t &hash, bool as_hex = false) -> std::string {
2107 std::string digest;
2108 digest.resize(32);
2109
2110 sha256_final(&hash, reinterpret_cast<unsigned char *>(digest.data()));
2111
2112 if (!as_hex) return digest;
2113
2114 static const char* digits = "0123456789abcdef";
2115 std::string hex;
2116 for (int n = 0; n < 32; n++) {
2117 unsigned char code = (unsigned char) digest[n];
2118 hex += digits[code / 16];
2119 hex += digits[code % 16];
2120 }
2121 return hex;
2122 };
2123
2124 // calculate hash of sessionKey + key;
2126 sha256_init(&hash1);
2127 sha256_update(&hash1, (const unsigned char *) sessionKey.data(), sessionKey.length());
2128 sha256_update(&hash1, (const unsigned char *) key.data(), key.length());
2129 std::string kbis = get_digest(hash1);
2130
2131 kbis.resize(64, 0); // resize to blocksize 64 bytes required by the sha256
2132
2133 std::string ki = kbis, ko = kbis;
2134 const int opad = 0x5c;
2135 const int ipad = 0x36;
2136 for (size_t i = 0; i < kbis.length(); ++i) {
2137 ko[i] = kbis[i] ^ opad;
2138 ki[i] = kbis[i] ^ ipad;
2139 }
2140
2141 // calculate hash for ko + msg;
2143 sha256_init(&hash2);
2144 sha256_update(&hash2, (const unsigned char *) ki.data(), ki.length());
2145 sha256_update(&hash2, (const unsigned char *) msg, msglen);
2146 std::string m2digest = get_digest(hash2);
2147
2148 // calculate hash for ki + m2_digest;
2150 sha256_init(&hash3);
2151 sha256_update(&hash3, (const unsigned char *) ko.data(), ko.length());
2152 sha256_update(&hash3, (const unsigned char *) m2digest.data(), m2digest.length());
2153
2154 return get_digest(hash3, true);
2155}
2156
2157/////////////////////////////////////////////////////////////////////////////////////
2158/// Set JSROOT settings as json string
2159/// Will be applied for any web window at the connection time
2160/// Can be used to chang `settings` object of JSROOT like:
2161/// ~~~ {.cpp}
2162/// ROOT::RWebWindow::SetJSROOTSettings("{ ToolBar: false, CanEnlarge: false }");
2163/// ~~~
2164
2165void RWebWindow::SetJSROOTSettings(const std::string &json)
2166{
2168}
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
Definition RtypesCore.h:45
long Long_t
Definition RtypesCore.h:54
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:2489
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:2378
virtual int Load(const char *module, const char *entry="", Bool_t system=kFALSE)
Load a shared library.
Definition TSystem.cxx:1870
This class represents a WWW compatible URL.
Definition TUrl.h:33
const Int_t n
Definition legend1.C:16
tbb::task_arena is an alias of tbb::interface7::task_arena, which doesn't allow to forward declare tb...
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.