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