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