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