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