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