Logo ROOT  
Reference Guide
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 "TROOT.h"
22#include "TRandom3.h"
23
24#include <cstring>
25#include <cstdlib>
26#include <utility>
27#include <assert.h>
28#include <algorithm>
29#include <fstream>
30
31using namespace ROOT::Experimental;
32using namespace std::string_literals;
33
34//////////////////////////////////////////////////////////////////////////////////////////
35/// Destructor for WebConn
36/// Notify special HTTP request which blocks headless browser from exit
37
38RWebWindow::WebConn::~WebConn()
39{
40 if (fHold) {
41 fHold->SetTextContent("console.log('execute holder script'); if (window) setTimeout (window.close, 1000); if (window) window.close();");
42 fHold->NotifyCondition();
43 fHold.reset();
44 }
45}
46
47
48/** \class ROOT::Experimental::RWebWindow
49\ingroup webdisplay
50
51Represents web window, which can be shown in web browser or any other supported environment
52
53Window can be configured to run either in the normal or in the batch (headless) mode.
54In second case no any graphical elements will be created. For the normal window one can configure geometry
55(width and height), which are applied when window shown.
56
57Each window can be shown several times (if allowed) in different places - either as the
58CEF (chromium embedded) window or in the standard web browser. When started, window will open and show
59HTML page, configured with RWebWindow::SetDefaultPage() method.
60
61Typically (but not necessarily) clients open web socket connection to the window and one can exchange data,
62using RWebWindow::Send() method and call-back function assigned via RWebWindow::SetDataCallBack().
63
64*/
65
66
67//////////////////////////////////////////////////////////////////////////////////////////
68/// RWebWindow constructor
69/// Should be defined here because of std::unique_ptr<RWebWindowWSHandler>
70
71RWebWindow::RWebWindow() = default;
72
73//////////////////////////////////////////////////////////////////////////////////////////
74/// RWebWindow destructor
75/// Closes all connections and remove window from manager
76
78{
79 StopThread();
80
81 if (fMaster) {
82 fMaster->RemoveEmbedWindow(fMasterConnId, fMasterChannel);
83 fMaster.reset();
84 fMasterConnId = 0;
85 fMasterChannel = -1;
86 }
87
88 if (fWSHandler)
89 fWSHandler->SetDisabled();
90
91 if (fMgr) {
92
93 // make copy of all connections
94 auto lst = GetConnections();
95
96 {
97 // clear connections vector under mutex
98 std::lock_guard<std::mutex> grd(fConnMutex);
99 fConn.clear();
100 fPendingConn.clear();
101 }
102
103 for (auto &conn : lst) {
104 conn->fActive = false;
105 for (auto &elem: conn->fEmbed) {
106 elem.second->fMaster.reset();
107 elem.second->fMasterConnId = 0;
108 elem.second->fMasterChannel = -1;
109 }
110 conn->fEmbed.clear();
111 }
112
113 fMgr->Unregister(*this);
114 }
115}
116
117//////////////////////////////////////////////////////////////////////////////////////////
118/// Configure window to show some of existing JSROOT panels
119/// It uses "file:rootui5sys/panel/panel.html" as default HTML page
120/// At the moment only FitPanel is existing
121
122void RWebWindow::SetPanelName(const std::string &name)
123{
124 {
125 std::lock_guard<std::mutex> grd(fConnMutex);
126 if (!fConn.empty()) {
127 R__LOG_ERROR(WebGUILog()) << "Cannot configure panel when connection exists";
128 return;
129 }
130 }
131
133 SetDefaultPage("file:rootui5sys/panel/panel.html");
134}
135
136//////////////////////////////////////////////////////////////////////////////////////////
137/// Assigns manager reference, window id and creates websocket handler, used for communication with the clients
138
139std::shared_ptr<RWebWindowWSHandler>
140RWebWindow::CreateWSHandler(std::shared_ptr<RWebWindowsManager> mgr, unsigned id, double tmout)
141{
142 fMgr = mgr;
143 fId = id;
144 fOperationTmout = tmout;
145
146 fSendMT = fMgr->IsUseSenderThreads();
147 fWSHandler = std::make_shared<RWebWindowWSHandler>(*this, Form("win%u", GetId()));
148
149 return fWSHandler;
150}
151
152//////////////////////////////////////////////////////////////////////////////////////////
153/// Return URL string to access web window
154/// \param remote if true, real HTTP server will be started automatically
155
156std::string RWebWindow::GetUrl(bool remote)
157{
158 return fMgr->GetUrl(*this, remote);
159}
160
161//////////////////////////////////////////////////////////////////////////////////////////
162/// Return THttpServer instance serving requests to the window
163
165{
166 return fMgr->GetServer();
167}
168
169//////////////////////////////////////////////////////////////////////////////////////////
170/// Show window in specified location
171/// \see ROOT::Experimental::RWebWindowsManager::Show for more info
172/// \return (future) connection id (or 0 when fails)
173
175{
176 return fMgr->ShowWindow(*this, args);
177}
178
179//////////////////////////////////////////////////////////////////////////////////////////
180/// Start headless browser for specified window
181/// Normally only single instance is used, but many can be created
182/// See ROOT::Experimental::RWebWindowsManager::Show() docu for more info
183/// returns (future) connection id (or 0 when fails)
184
185unsigned RWebWindow::MakeHeadless(bool create_new)
186{
187 unsigned connid = 0;
188 if (!create_new)
189 connid = FindHeadlessConnection();
190 if (!connid) {
191 RWebDisplayArgs args;
192 args.SetHeadless(true);
193 connid = fMgr->ShowWindow(*this, args);
194 }
195 return connid;
196}
197
198//////////////////////////////////////////////////////////////////////////////////////////
199/// Returns connection id of window running in headless mode
200/// This can be special connection which may run picture production jobs in background
201/// Connection to that job may not be initialized yet
202/// If connection does not exists, returns 0
203
205{
206 std::lock_guard<std::mutex> grd(fConnMutex);
207
208 for (auto &entry : fPendingConn) {
209 if (entry->fHeadlessMode)
210 return entry->fConnId;
211 }
212
213 for (auto &conn : fConn) {
214 if (conn->fHeadlessMode)
215 return conn->fConnId;
216 }
217
218 return 0;
219}
220
221//////////////////////////////////////////////////////////////////////////////////////////
222/// Returns first connection id where window is displayed
223/// It could be that connection(s) not yet fully established - but also not timed out
224/// Batch jobs will be ignored here
225/// Returns 0 if connection not exists
226
228{
229 std::lock_guard<std::mutex> grd(fConnMutex);
230
231 for (auto &entry : fPendingConn) {
232 if (!entry->fHeadlessMode)
233 return entry->fConnId;
234 }
235
236 for (auto &conn : fConn) {
237 if (!conn->fHeadlessMode)
238 return conn->fConnId;
239 }
240
241 return 0;
242}
243
244//////////////////////////////////////////////////////////////////////////////////////////
245/// Find connection with given websocket id
246
247std::shared_ptr<RWebWindow::WebConn> RWebWindow::FindOrCreateConnection(unsigned wsid, bool make_new, const char *query)
248{
249 std::lock_guard<std::mutex> grd(fConnMutex);
250
251 for (auto &conn : fConn) {
252 if (conn->fWSId == wsid)
253 return conn;
254 }
255
256 // put code to create new connection here to stay under same locked mutex
257 if (make_new) {
258 // check if key was registered already
259
260 std::shared_ptr<WebConn> key;
261 std::string keyvalue;
262
263 if (query) {
264 TUrl url;
265 url.SetOptions(query);
266 if (url.HasOption("key"))
267 keyvalue = url.GetValueFromOptions("key");
268 }
269
270 if (!keyvalue.empty())
271 for (size_t n = 0; n < fPendingConn.size(); ++n)
272 if (fPendingConn[n]->fKey == keyvalue) {
273 key = std::move(fPendingConn[n]);
274 fPendingConn.erase(fPendingConn.begin() + n);
275 break;
276 }
277
278 if (key) {
279 key->fWSId = wsid;
280 key->fActive = true;
281 key->ResetStamps(); // TODO: probably, can be moved outside locked area
282 fConn.emplace_back(key);
283 } else {
284 fConn.emplace_back(std::make_shared<WebConn>(++fConnCnt, wsid));
285 }
286 }
287
288 return nullptr;
289}
290
291//////////////////////////////////////////////////////////////////////////////////////////
292/// Remove connection with given websocket id
293
294std::shared_ptr<RWebWindow::WebConn> RWebWindow::RemoveConnection(unsigned wsid)
295{
296
297 std::shared_ptr<WebConn> res;
298
299 {
300 std::lock_guard<std::mutex> grd(fConnMutex);
301
302 for (size_t n = 0; n < fConn.size(); ++n)
303 if (fConn[n]->fWSId == wsid) {
304 res = std::move(fConn[n]);
305 fConn.erase(fConn.begin() + n);
306 res->fActive = false;
307 break;
308 }
309 }
310
311 if (res) {
312 for (auto &elem: res->fEmbed) {
313 elem.second->fMaster.reset();
314 elem.second->fMasterConnId = 0;
315 elem.second->fMasterChannel = -1;
316 }
317 res->fEmbed.clear();
318 }
319
320 return res;
321}
322
323//////////////////////////////////////////////////////////////////////////////////////////
324/// Process special http request, used to hold headless browser running
325/// Such requests should not be replied for the long time
326/// Be aware that function called directly from THttpServer thread, which is not same thread as window
327
328bool RWebWindow::ProcessBatchHolder(std::shared_ptr<THttpCallArg> &arg)
329{
330 std::string query = arg->GetQuery();
331
332 if (query.compare(0, 4, "key=") != 0)
333 return false;
334
335 std::string key = query.substr(4);
336
337 std::shared_ptr<THttpCallArg> prev;
338
339 bool found_key = false;
340
341 // use connection mutex to access hold request
342 {
343 std::lock_guard<std::mutex> grd(fConnMutex);
344 for (auto &entry : fPendingConn) {
345 if (entry->fKey == key) {
346 assert(!found_key); // indicate error if many same keys appears
347 found_key = true;
348 prev = std::move(entry->fHold);
349 entry->fHold = arg;
350 }
351 }
352
353 for (auto &conn : fConn) {
354 if (conn->fKey == key) {
355 assert(!found_key); // indicate error if many same keys appears
356 prev = std::move(conn->fHold);
357 conn->fHold = arg;
358 found_key = true;
359 }
360 }
361 }
362
363 if (prev) {
364 prev->SetTextContent("console.log('execute holder script'); if (window) window.close();");
365 prev->NotifyCondition();
366 }
367
368 return found_key;
369}
370
371//////////////////////////////////////////////////////////////////////////////////////////
372/// Provide data to user callback
373/// User callback must be executed in the window thread
374
375void RWebWindow::ProvideQueueEntry(unsigned connid, EQueueEntryKind kind, std::string &&arg)
376{
377 {
378 std::lock_guard<std::mutex> grd(fInputQueueMutex);
379 fInputQueue.emplace(connid, kind, std::move(arg));
380 }
381
383}
384
385//////////////////////////////////////////////////////////////////////////////////////////
386/// Invoke callbacks with existing data
387/// Must be called from appropriate thread
388
390{
391 if (fCallbacksThrdIdSet && (fCallbacksThrdId != std::this_thread::get_id()) && !force)
392 return;
393
394 while (true) {
395 unsigned connid;
396 EQueueEntryKind kind;
397 std::string arg;
398
399 {
400 std::lock_guard<std::mutex> grd(fInputQueueMutex);
401 if (fInputQueue.size() == 0)
402 return;
403 auto &entry = fInputQueue.front();
404 connid = entry.fConnId;
405 kind = entry.fKind;
406 arg = std::move(entry.fData);
407 fInputQueue.pop();
408 }
409
410 switch (kind) {
411 case kind_None: break;
412 case kind_Connect:
413 if (fConnCallback)
414 fConnCallback(connid);
415 break;
416 case kind_Data:
417 if (fDataCallback)
418 fDataCallback(connid, arg);
419 break;
420 case kind_Disconnect:
422 fDisconnCallback(connid);
423 break;
424 }
425 }
426}
427
428//////////////////////////////////////////////////////////////////////////////////////////
429/// Add display handle and associated key
430/// Key is random number generated when starting new window
431/// When client is connected, key should be supplied to correctly identify it
432
433unsigned RWebWindow::AddDisplayHandle(bool headless_mode, const std::string &key, std::unique_ptr<RWebDisplayHandle> &handle)
434{
435 std::lock_guard<std::mutex> grd(fConnMutex);
436
437 auto conn = std::make_shared<WebConn>(++fConnCnt, headless_mode, key);
438
439 std::swap(conn->fDisplayHandle, handle);
440
441 fPendingConn.emplace_back(conn);
442
443 return fConnCnt;
444}
445
446//////////////////////////////////////////////////////////////////////////////////////////
447/// Find connection with specified key.
448/// Must be used under connection mutex lock
449
450std::shared_ptr<RWebWindow::WebConn> RWebWindow::_FindConnWithKey(const std::string &key) const
451{
452 if (key.empty())
453 return nullptr;
454
455 for (auto &entry : fPendingConn) {
456 if (entry->fKey == key)
457 return entry;
458 }
459
460 for (auto &conn : fConn) {
461 if (conn->fKey == key)
462 return conn;
463 }
464
465 return nullptr;
466}
467
468//////////////////////////////////////////////////////////////////////////////////////////
469/// Returns true if provided key value already exists (in processes map or in existing connections)
470
471bool RWebWindow::HasKey(const std::string &key) const
472{
473 std::lock_guard<std::mutex> grd(fConnMutex);
474
475 auto conn = _FindConnWithKey(key);
476
477 return conn ? true : false;
478
479 return false;
480}
481
482//////////////////////////////////////////////////////////////////////////////////////////
483/// Generate new unique key for the window
484
485std::string RWebWindow::GenerateKey() const
486{
487 int ntry = 100000;
488 TRandom3 rnd;
489 rnd.SetSeed();
490 std::string key;
491
492 do {
493 key = std::to_string(rnd.Integer(0x100000));
494 } while ((--ntry > 0) && HasKey(key));
495
496 if (ntry <= 0) key.clear();
497
498 return key;
499}
500
501//////////////////////////////////////////////////////////////////////////////////////////
502/// Check if started process(es) establish connection. After timeout such processed will be killed
503/// Method invoked from http server thread, therefore appropriate mutex must be used on all relevant data
504
506{
507 if (!fMgr) return;
508
509 timestamp_t stamp = std::chrono::system_clock::now();
510
511 float tmout = fMgr->GetLaunchTmout();
512
513 ConnectionsList_t selected;
514
515 {
516 std::lock_guard<std::mutex> grd(fConnMutex);
517
518 auto pred = [&](std::shared_ptr<WebConn> &e) {
519 std::chrono::duration<double> diff = stamp - e->fSendStamp;
520
521 if (diff.count() > tmout) {
522 R__LOG_DEBUG(0, WebGUILog()) << "Halt process after " << diff.count() << " sec";
523 selected.emplace_back(e);
524 return true;
525 }
526
527 return false;
528 };
529
530 fPendingConn.erase(std::remove_if(fPendingConn.begin(), fPendingConn.end(), pred), fPendingConn.end());
531 }
532
533}
534
535
536//////////////////////////////////////////////////////////////////////////////////////////
537/// Check if there are connection which are inactive for longer time
538/// For instance, batch browser will be stopped if no activity for 30 sec is there
539
541{
542 timestamp_t stamp = std::chrono::system_clock::now();
543
544 double batch_tmout = 20.;
545
546 std::vector<std::shared_ptr<WebConn>> clr;
547
548 {
549 std::lock_guard<std::mutex> grd(fConnMutex);
550
551 auto pred = [&](std::shared_ptr<WebConn> &conn) {
552 std::chrono::duration<double> diff = stamp - conn->fSendStamp;
553 // introduce large timeout
554 if ((diff.count() > batch_tmout) && conn->fHeadlessMode) {
555 conn->fActive = false;
556 clr.emplace_back(conn);
557 return true;
558 }
559 return false;
560 };
561
562 fConn.erase(std::remove_if(fConn.begin(), fConn.end(), pred), fConn.end());
563 }
564
565 for (auto &entry : clr)
566 ProvideQueueEntry(entry->fConnId, kind_Disconnect, ""s);
567
568}
569
570/////////////////////////////////////////////////////////////////////////
571/// Configure maximal number of allowed connections - 0 is unlimited
572/// Will not affect already existing connections
573/// Default is 1 - the only client is allowed
574
575void RWebWindow::SetConnLimit(unsigned lmt)
576{
577 std::lock_guard<std::mutex> grd(fConnMutex);
578
579 fConnLimit = lmt;
580}
581
582/////////////////////////////////////////////////////////////////////////
583/// returns configured connections limit (0 - default)
584
586{
587 std::lock_guard<std::mutex> grd(fConnMutex);
588
589 return fConnLimit;
590}
591
592/////////////////////////////////////////////////////////////////////////
593/// Configures connection token (default none)
594/// When specified, in URL of webpage such token should be provided as &token=value parameter,
595/// otherwise web window will refuse connection
596
597void RWebWindow::SetConnToken(const std::string &token)
598{
599 std::lock_guard<std::mutex> grd(fConnMutex);
600
601 fConnToken = token;
602}
603
604/////////////////////////////////////////////////////////////////////////
605/// Returns configured connection token
606
607std::string RWebWindow::GetConnToken() const
608{
609 std::lock_guard<std::mutex> grd(fConnMutex);
610
611 return fConnToken;
612}
613
614//////////////////////////////////////////////////////////////////////////////////////////
615/// Internal method to verify and thread id has to be assigned from manager again
616/// Special case when ProcessMT was enabled just until thread id will be assigned
617
619{
620 if (fProcessMT && fMgr->fExternalProcessEvents)
621 fMgr->AssignWindowThreadId(*this);
622}
623
624//////////////////////////////////////////////////////////////////////////////////////////
625/// Processing of websockets call-backs, invoked from RWebWindowWSHandler
626/// Method invoked from http server thread, therefore appropriate mutex must be used on all relevant data
627
629{
630 if (arg.GetWSId() == 0)
631 return true;
632
633 if (arg.IsMethod("WS_CONNECT")) {
634
635 TUrl url;
636 url.SetOptions(arg.GetQuery());
637 bool check_key = RWebWindowWSHandler::GetBoolEnv("WebGui.OnetimeKey") == 1;
638
639 std::lock_guard<std::mutex> grd(fConnMutex);
640
641 // refuse connection when number of connections exceed limit
642 if (fConnLimit && (fConn.size() >= fConnLimit))
643 return false;
644
645 if (!fConnToken.empty()) {
646 // refuse connection which does not provide proper token
647 if (!url.HasOption("token") || (fConnToken != url.GetValueFromOptions("token"))) {
648 R__LOG_DEBUG(0, WebGUILog()) << "Refuse connection without proper token";
649 return false;
650 }
651 }
652
653 if (check_key) {
654 if(!url.HasOption("key")) {
655 R__LOG_DEBUG(0, WebGUILog()) << "key parameter not provided in url";
656 return false;
657 }
658
659 auto key = url.GetValueFromOptions("key");
660
661 auto conn = _FindConnWithKey(key);
662 if (!conn) {
663 R__LOG_ERROR(WebGUILog()) << "connection with key " << key << " not found ";
664 return false;
665 }
666
667 if (conn->fKeyUsed > 0) {
668 R__LOG_ERROR(WebGUILog()) << "key " << key << " was used for establishing connection, call ShowWindow again";
669 return false;
670 }
671
672 conn->fKeyUsed = 1;
673 }
674
675 return true;
676 }
677
678 if (arg.IsMethod("WS_READY")) {
679 auto conn = FindOrCreateConnection(arg.GetWSId(), true, arg.GetQuery());
680
681 if (conn) {
682 R__LOG_ERROR(WebGUILog()) << "WSHandle with given websocket id " << arg.GetWSId() << " already exists";
683 return false;
684 }
685
686 return true;
687 }
688
689 if (arg.IsMethod("WS_CLOSE")) {
690 // connection is closed, one can remove handle, associated window will be closed
691
692 auto conn = RemoveConnection(arg.GetWSId());
693
694 if (conn) {
695 ProvideQueueEntry(conn->fConnId, kind_Disconnect, ""s);
696 bool do_clear_on_close = false;
697 if (conn->fKeyUsed < 0) {
698 // case when same handle want to be reused by client with new key
699 std::lock_guard<std::mutex> grd(fConnMutex);
700 conn->fKeyUsed = 0;
701 conn->fConnId = ++fConnCnt; // change connection id to avoid confusion
702 conn->ResetData();
703 fPendingConn.emplace_back(conn);
704 } else {
705 std::lock_guard<std::mutex> grd(fConnMutex);
706 do_clear_on_close = (fPendingConn.size() == 0) && (fConn.size() == 0);
707 }
708
709 if (do_clear_on_close)
710 fClearOnClose.reset();
711 }
712
713 return true;
714 }
715
716 if (!arg.IsMethod("WS_DATA")) {
717 R__LOG_ERROR(WebGUILog()) << "only WS_DATA request expected!";
718 return false;
719 }
720
721 auto conn = FindConnection(arg.GetWSId());
722
723 if (!conn) {
724 R__LOG_ERROR(WebGUILog()) << "Get websocket data without valid connection - ignore!!!";
725 return false;
726 }
727
728 if (arg.GetPostDataLength() <= 0)
729 return true;
730
731 // here processing of received data should be performed
732 // this is task for the implemented windows
733
734 const char *buf = (const char *)arg.GetPostData();
735 char *str_end = nullptr;
736
737 unsigned long ackn_oper = std::strtoul(buf, &str_end, 10);
738 if (!str_end || *str_end != ':') {
739 R__LOG_ERROR(WebGUILog()) << "missing number of acknowledged operations";
740 return false;
741 }
742
743 unsigned long can_send = std::strtoul(str_end + 1, &str_end, 10);
744 if (!str_end || *str_end != ':') {
745 R__LOG_ERROR(WebGUILog()) << "missing can_send counter";
746 return false;
747 }
748
749 unsigned long nchannel = std::strtoul(str_end + 1, &str_end, 10);
750 if (!str_end || *str_end != ':') {
751 R__LOG_ERROR(WebGUILog()) << "missing channel number";
752 return false;
753 }
754
755 Long_t processed_len = (str_end + 1 - buf);
756
757 if (processed_len > arg.GetPostDataLength()) {
758 R__LOG_ERROR(WebGUILog()) << "corrupted buffer";
759 return false;
760 }
761
762 std::string cdata(str_end + 1, arg.GetPostDataLength() - processed_len);
763
764 timestamp_t stamp = std::chrono::system_clock::now();
765
766 {
767 std::lock_guard<std::mutex> grd(conn->fMutex);
768
769 conn->fSendCredits += ackn_oper;
770 conn->fRecvCount++;
771 conn->fClientCredits = (int)can_send;
772 conn->fRecvStamp = stamp;
773 }
774
775 if (fProtocolCnt >= 0)
776 if (!fProtocolConnId || (conn->fConnId == fProtocolConnId)) {
777 fProtocolConnId = conn->fConnId; // remember connection
778
779 // record send event only for normal channel or very first message via ch0
780 if ((nchannel != 0) || (cdata.find("READY=") == 0)) {
781 if (fProtocol.length() > 2)
782 fProtocol.insert(fProtocol.length() - 1, ",");
783 fProtocol.insert(fProtocol.length() - 1, "\"send\"");
784
785 std::ofstream pfs(fProtocolFileName);
786 pfs.write(fProtocol.c_str(), fProtocol.length());
787 pfs.close();
788 }
789 }
790
791 if (nchannel == 0) {
792 // special system channel
793 if ((cdata.find("READY=") == 0) && !conn->fReady) {
794 std::string key = cdata.substr(6);
795
796 if (key.empty() && IsNativeOnlyConn()) {
797 RemoveConnection(conn->fWSId);
798 return false;
799 }
800
801 if (!key.empty() && !conn->fKey.empty() && (conn->fKey != key)) {
802 R__LOG_ERROR(WebGUILog()) << "Key mismatch after established connection " << key << " != " << conn->fKey;
803 RemoveConnection(conn->fWSId);
804 return false;
805 }
806
807 if (!fPanelName.empty()) {
808 // initialization not yet finished, appropriate panel should be started
809 Send(conn->fConnId, "SHOWPANEL:"s + fPanelName);
810 conn->fReady = 5;
811 } else {
812 ProvideQueueEntry(conn->fConnId, kind_Connect, ""s);
813 conn->fReady = 10;
814 }
815 } else if (cdata.compare(0,8,"CLOSECH=") == 0) {
816 int channel = std::stoi(cdata.substr(8));
817 auto iter = conn->fEmbed.find(channel);
818 if (iter != conn->fEmbed.end()) {
819 iter->second->ProvideQueueEntry(conn->fConnId, kind_Disconnect, ""s);
820 conn->fEmbed.erase(iter);
821 }
822 } else if (cdata == "GENERATE_KEY") {
823
824 if (fMaster) {
825 R__LOG_ERROR(WebGUILog()) << "Not able to generate new key with master connections";
826 } else {
827 auto newkey = GenerateKey();
828 if(newkey.empty()) {
829 R__LOG_ERROR(WebGUILog()) << "Fail to generate new key by GENERATE_KEY request";
830 } else {
831 SubmitData(conn->fConnId, true, "NEW_KEY="s + newkey, -1);
832 conn->fKey = newkey;
833 conn->fKeyUsed = -1;
834 }
835 }
836 }
837 } else if (fPanelName.length() && (conn->fReady < 10)) {
838 if (cdata == "PANEL_READY") {
839 R__LOG_DEBUG(0, WebGUILog()) << "Get panel ready " << fPanelName;
840 ProvideQueueEntry(conn->fConnId, kind_Connect, ""s);
841 conn->fReady = 10;
842 } else {
843 ProvideQueueEntry(conn->fConnId, kind_Disconnect, ""s);
844 RemoveConnection(conn->fWSId);
845 }
846 } else if (nchannel == 1) {
847 ProvideQueueEntry(conn->fConnId, kind_Data, std::move(cdata));
848 } else if (nchannel > 1) {
849 // process embed window
850 auto embed_window = conn->fEmbed[nchannel];
851 if (embed_window)
852 embed_window->ProvideQueueEntry(conn->fConnId, kind_Data, std::move(cdata));
853 }
854
856
857 return true;
858}
859
860//////////////////////////////////////////////////////////////////////////////////////////
861/// Complete websocket send operation
862/// Clear "doing send" flag and check if next operation has to be started
863
864void RWebWindow::CompleteWSSend(unsigned wsid)
865{
866 auto conn = FindConnection(wsid);
867
868 if (!conn)
869 return;
870
871 {
872 std::lock_guard<std::mutex> grd(conn->fMutex);
873 conn->fDoingSend = false;
874 }
875
876 CheckDataToSend(conn);
877}
878
879//////////////////////////////////////////////////////////////////////////////////////////
880/// Internal method to prepare text part of send data
881/// Should be called under locked connection mutex
882
883std::string RWebWindow::_MakeSendHeader(std::shared_ptr<WebConn> &conn, bool txt, const std::string &data, int chid)
884{
885 std::string buf;
886
887 if (!conn->fWSId || !fWSHandler) {
888 R__LOG_ERROR(WebGUILog()) << "try to send text data when connection not established";
889 return buf;
890 }
891
892 if (conn->fSendCredits <= 0) {
893 R__LOG_ERROR(WebGUILog()) << "No credits to send text data via connection";
894 return buf;
895 }
896
897 if (conn->fDoingSend) {
898 R__LOG_ERROR(WebGUILog()) << "Previous send operation not completed yet";
899 return buf;
900 }
901
902 if (txt)
903 buf.reserve(data.length() + 100);
904
905 buf.append(std::to_string(conn->fRecvCount));
906 buf.append(":");
907 buf.append(std::to_string(conn->fSendCredits));
908 buf.append(":");
909 conn->fRecvCount = 0; // we confirm how many packages was received
910 conn->fSendCredits--;
911
912 buf.append(std::to_string(chid));
913 buf.append(":");
914
915 if (txt) {
916 buf.append(data);
917 } else if (data.length()==0) {
918 buf.append("$$nullbinary$$");
919 } else {
920 buf.append("$$binary$$");
921 }
922
923 return buf;
924}
925
926//////////////////////////////////////////////////////////////////////////////////////////
927/// Checks if one should send data for specified connection
928/// Returns true when send operation was performed
929
930bool RWebWindow::CheckDataToSend(std::shared_ptr<WebConn> &conn)
931{
932 std::string hdr, data;
933
934 {
935 std::lock_guard<std::mutex> grd(conn->fMutex);
936
937 if (!conn->fActive || (conn->fSendCredits <= 0) || conn->fDoingSend) return false;
938
939 if (!conn->fQueue.empty()) {
940 QueueItem &item = conn->fQueue.front();
941 hdr = _MakeSendHeader(conn, item.fText, item.fData, item.fChID);
942 if (!hdr.empty() && !item.fText)
943 data = std::move(item.fData);
944 conn->fQueue.pop();
945 } else if ((conn->fClientCredits < 3) && (conn->fRecvCount > 1)) {
946 // give more credits to the client
947 hdr = _MakeSendHeader(conn, true, "KEEPALIVE", 0);
948 }
949
950 if (hdr.empty()) return false;
951
952 conn->fDoingSend = true;
953 }
954
955 int res = 0;
956
957 if (data.empty()) {
958 res = fWSHandler->SendCharStarWS(conn->fWSId, hdr.c_str());
959 } else {
960 res = fWSHandler->SendHeaderWS(conn->fWSId, hdr.c_str(), data.data(), data.length());
961 }
962
963 // submit operation, will be processed
964 if (res >=0) return true;
965
966 // failure, clear sending flag
967 std::lock_guard<std::mutex> grd(conn->fMutex);
968 conn->fDoingSend = false;
969 return false;
970}
971
972
973//////////////////////////////////////////////////////////////////////////////////////////
974/// Checks if new data can be send (internal use only)
975/// If necessary, provide credits to the client
976/// \param only_once if true, data sending performed once or until there is no data to send
977
978void RWebWindow::CheckDataToSend(bool only_once)
979{
980 // make copy of all connections to be independent later, only active connections are checked
981 auto arr = GetConnections(0, true);
982
983 do {
984 bool isany = false;
985
986 for (auto &conn : arr)
987 if (CheckDataToSend(conn))
988 isany = true;
989
990 if (!isany) break;
991
992 } while (!only_once);
993}
994
995///////////////////////////////////////////////////////////////////////////////////
996/// Special method to process all internal activity when window runs in separate thread
997
999{
1001
1003
1005
1007}
1008
1009///////////////////////////////////////////////////////////////////////////////////
1010/// Returns window address which is used in URL
1011
1012std::string RWebWindow::GetAddr() const
1013{
1014 return fWSHandler->GetName();
1015}
1016
1017///////////////////////////////////////////////////////////////////////////////////
1018/// Returns relative URL address for the specified window
1019/// Address can be required if one needs to access data from one window into another window
1020/// Used for instance when inserting panel into canvas
1021
1022std::string RWebWindow::GetRelativeAddr(const std::shared_ptr<RWebWindow> &win) const
1023{
1024 return GetRelativeAddr(*win);
1025}
1026
1027///////////////////////////////////////////////////////////////////////////////////
1028/// Returns relative URL address for the specified window
1029/// Address can be required if one needs to access data from one window into another window
1030/// Used for instance when inserting panel into canvas
1031
1033{
1034 if (fMgr != win.fMgr) {
1035 R__LOG_ERROR(WebGUILog()) << "Same web window manager should be used";
1036 return "";
1037 }
1038
1039 std::string res("../");
1040 res.append(win.GetAddr());
1041 res.append("/");
1042 return res;
1043}
1044
1045/////////////////////////////////////////////////////////////////////////
1046/// Set client version, used as prefix in scripts URL
1047/// When changed, web browser will reload all related JS files while full URL will be different
1048/// Default is empty value - no extra string in URL
1049/// Version should be string like "1.2" or "ver1.subv2" and not contain any special symbols
1050
1051void RWebWindow::SetClientVersion(const std::string &vers)
1052{
1053 std::lock_guard<std::mutex> grd(fConnMutex);
1054 fClientVersion = vers;
1055}
1056
1057/////////////////////////////////////////////////////////////////////////
1058/// Returns current client version
1059
1061{
1062 std::lock_guard<std::mutex> grd(fConnMutex);
1063 return fClientVersion;
1064}
1065
1066/////////////////////////////////////////////////////////////////////////
1067/// Set arbitrary JSON data, which is accessible via conn.getUserArgs() method in JavaScript
1068/// This JSON code injected into main HTML document into connectWebWindow({})
1069/// Must be set before RWebWindow::Show() method is called
1070/// \param args - arbitrary JSON data which can be provided to client side
1071
1072void RWebWindow::SetUserArgs(const std::string &args)
1073{
1074 std::lock_guard<std::mutex> grd(fConnMutex);
1075 fUserArgs = args;
1076}
1077
1078/////////////////////////////////////////////////////////////////////////
1079/// Returns configured user arguments for web window
1080/// See \ref SetUserArgs method for more details
1081
1082std::string RWebWindow::GetUserArgs() const
1083{
1084 std::lock_guard<std::mutex> grd(fConnMutex);
1085 return fUserArgs;
1086}
1087
1088///////////////////////////////////////////////////////////////////////////////////
1089/// Returns current number of active clients connections
1090/// \param with_pending if true, also pending (not yet established) connection accounted
1091
1092int RWebWindow::NumConnections(bool with_pending) const
1093{
1094 std::lock_guard<std::mutex> grd(fConnMutex);
1095 auto sz = fConn.size();
1096 if (with_pending)
1097 sz += fPendingConn.size();
1098 return sz;
1099}
1100
1101///////////////////////////////////////////////////////////////////////////////////
1102/// Configures recording of communication data in protocol file
1103/// Provided filename will be used to store JSON array with names of written files - text or binary
1104/// If data was send from client, "send" entry will be placed. JSON file will look like:
1105///
1106/// ["send", "msg0.txt", "send", "msg1.txt", "msg2.txt"]
1107///
1108/// If empty file name is provided, data recording will be disabled
1109/// Recorded data can be used in JSROOT directly to test client code without running C++ server
1110
1111void RWebWindow::RecordData(const std::string &fname, const std::string &fprefix)
1112{
1113 fProtocolFileName = fname;
1114 fProtocolCnt = fProtocolFileName.empty() ? -1 : 0;
1116 fProtocolPrefix = fprefix;
1117 fProtocol = "[]"; // empty array
1118}
1119
1120///////////////////////////////////////////////////////////////////////////////////
1121/// Returns connection id for specified connection sequence number
1122/// Only active connections are returned - where clients confirms connection
1123/// Total number of connections can be retrieved with NumConnections() method
1124/// \param num connection sequence number
1125
1126unsigned RWebWindow::GetConnectionId(int num) const
1127{
1128 std::lock_guard<std::mutex> grd(fConnMutex);
1129 return ((num >= 0) && (num < (int)fConn.size()) && fConn[num]->fActive) ? fConn[num]->fConnId : 0;
1130}
1131
1132///////////////////////////////////////////////////////////////////////////////////
1133/// returns true if specified connection id exists
1134/// \param connid connection id (0 - any)
1135/// \param only_active when true only active connection will be checked, otherwise also pending (not yet established) connections are checked
1136
1137bool RWebWindow::HasConnection(unsigned connid, bool only_active) const
1138{
1139 std::lock_guard<std::mutex> grd(fConnMutex);
1140
1141 for (auto &conn : fConn) {
1142 if (connid && (conn->fConnId != connid))
1143 continue;
1144 if (conn->fActive || !only_active)
1145 return true;
1146 }
1147
1148 if (!only_active)
1149 for (auto &conn : fPendingConn) {
1150 if (!connid || (conn->fConnId == connid))
1151 return true;
1152 }
1153
1154 return false;
1155}
1156
1157///////////////////////////////////////////////////////////////////////////////////
1158/// Closes all connection to clients
1159/// Normally leads to closing of all correspondent browser windows
1160/// Some browsers (like firefox) do not allow by default to close window
1161
1163{
1164 SubmitData(0, true, "CLOSE", 0);
1165}
1166
1167///////////////////////////////////////////////////////////////////////////////////
1168/// Close specified connection
1169/// \param connid connection id, when 0 - all connections will be closed
1170
1171void RWebWindow::CloseConnection(unsigned connid)
1172{
1173 if (connid)
1174 SubmitData(connid, true, "CLOSE", 0);
1175}
1176
1177///////////////////////////////////////////////////////////////////////////////////
1178/// returns connection list (or all active connections)
1179/// \param connid connection id, when 0 - all existing connections are returned
1180/// \param only_active when true, only active (already established) connections are returned
1181
1182RWebWindow::ConnectionsList_t RWebWindow::GetConnections(unsigned connid, bool only_active) const
1183{
1185
1186 {
1187 std::lock_guard<std::mutex> grd(fConnMutex);
1188
1189 for (auto &conn : fConn) {
1190 if ((conn->fActive || !only_active) && (!connid || (conn->fConnId == connid)))
1191 arr.push_back(conn);
1192 }
1193
1194 if (!only_active)
1195 for (auto &conn : fPendingConn)
1196 if (!connid || (conn->fConnId == connid))
1197 arr.push_back(conn);
1198 }
1199
1200 return arr;
1201}
1202
1203///////////////////////////////////////////////////////////////////////////////////
1204/// Returns true if sending via specified connection can be performed
1205/// \param connid connection id, when 0 - all existing connections are checked
1206/// \param direct when true, checks if direct sending (without queuing) is possible
1207
1208bool RWebWindow::CanSend(unsigned connid, bool direct) const
1209{
1210 auto arr = GetConnections(connid, direct); // for direct sending connection has to be active
1211
1212 auto maxqlen = GetMaxQueueLength();
1213
1214 for (auto &conn : arr) {
1215
1216 std::lock_guard<std::mutex> grd(conn->fMutex);
1217
1218 if (direct && (!conn->fQueue.empty() || (conn->fSendCredits == 0) || conn->fDoingSend))
1219 return false;
1220
1221 if (conn->fQueue.size() >= maxqlen)
1222 return false;
1223 }
1224
1225 return true;
1226}
1227
1228///////////////////////////////////////////////////////////////////////////////////
1229/// Returns send queue length for specified connection
1230/// \param connid connection id, 0 - maximal value for all connections is returned
1231/// If wrong connection id specified, -1 is return
1232
1233int RWebWindow::GetSendQueueLength(unsigned connid) const
1234{
1235 int maxq = -1;
1236
1237 for (auto &conn : GetConnections(connid)) {
1238 std::lock_guard<std::mutex> grd(conn->fMutex);
1239 int len = conn->fQueue.size();
1240 if (len > maxq) maxq = len;
1241 }
1242
1243 return maxq;
1244}
1245
1246///////////////////////////////////////////////////////////////////////////////////
1247/// Internal method to send data
1248/// \param connid connection id, when 0 - data will be send to all connections
1249/// \param txt is text message that should be sent
1250/// \param data data to be std-moved to SubmitData function
1251/// \param chid channel id, 1 - normal communication, 0 - internal with highest priority
1252
1253void RWebWindow::SubmitData(unsigned connid, bool txt, std::string &&data, int chid)
1254{
1255 if (fMaster)
1256 return fMaster->SubmitData(fMasterConnId, txt, std::move(data), fMasterChannel);
1257
1258 auto arr = GetConnections(connid);
1259 auto cnt = arr.size();
1260 auto maxqlen = GetMaxQueueLength();
1261
1262 bool clear_queue = false;
1263
1264 if (chid == -1) {
1265 chid = 0;
1266 clear_queue = true;
1267 }
1268
1269 timestamp_t stamp = std::chrono::system_clock::now();
1270
1271 for (auto &conn : arr) {
1272
1273 if (fProtocolCnt >= 0)
1274 if (!fProtocolConnId || (conn->fConnId == fProtocolConnId)) {
1275 fProtocolConnId = conn->fConnId; // remember connection
1276 std::string fname = fProtocolPrefix;
1277 fname.append("msg");
1278 fname.append(std::to_string(fProtocolCnt++));
1279 if (chid > 1) {
1280 fname.append("_ch");
1281 fname.append(std::to_string(chid));
1282 }
1283 fname.append(txt ? ".txt" : ".bin");
1284
1285 std::ofstream ofs(fname);
1286 ofs.write(data.c_str(), data.length());
1287 ofs.close();
1288
1289 if (fProtocol.length() > 2)
1290 fProtocol.insert(fProtocol.length() - 1, ",");
1291 fProtocol.insert(fProtocol.length() - 1, "\""s + fname + "\""s);
1292
1293 std::ofstream pfs(fProtocolFileName);
1294 pfs.write(fProtocol.c_str(), fProtocol.length());
1295 pfs.close();
1296 }
1297
1298 conn->fSendStamp = stamp;
1299
1300 std::lock_guard<std::mutex> grd(conn->fMutex);
1301
1302 if (clear_queue) {
1303 while (!conn->fQueue.empty())
1304 conn->fQueue.pop();
1305 }
1306
1307 if (conn->fQueue.size() < maxqlen) {
1308 if (--cnt)
1309 conn->fQueue.emplace(chid, txt, std::string(data)); // make copy
1310 else
1311 conn->fQueue.emplace(chid, txt, std::move(data)); // move content
1312 } else {
1313 R__LOG_ERROR(WebGUILog()) << "Maximum queue length achieved";
1314 }
1315 }
1316
1318}
1319
1320///////////////////////////////////////////////////////////////////////////////////
1321/// Sends data to specified connection
1322/// \param connid connection id, when 0 - data will be send to all connections
1323/// \param data data to be copied to SubmitData function
1324
1325void RWebWindow::Send(unsigned connid, const std::string &data)
1326{
1327 SubmitData(connid, true, std::string(data), 1);
1328}
1329
1330///////////////////////////////////////////////////////////////////////////////////
1331/// Send binary data to specified connection
1332/// \param connid connection id, when 0 - data will be send to all connections
1333/// \param data data to be std-moved to SubmitData function
1334
1335void RWebWindow::SendBinary(unsigned connid, std::string &&data)
1336{
1337 SubmitData(connid, false, std::move(data), 1);
1338}
1339
1340///////////////////////////////////////////////////////////////////////////////////
1341/// Send binary data to specified connection
1342/// \param connid connection id, when 0 - data will be send to all connections
1343/// \param data pointer to binary data
1344/// \param len number of bytes in data
1345
1346void RWebWindow::SendBinary(unsigned connid, const void *data, std::size_t len)
1347{
1348 std::string buf;
1349 buf.resize(len);
1350 std::copy((const char *)data, (const char *)data + len, buf.begin());
1351 SubmitData(connid, false, std::move(buf), 1);
1352}
1353
1354///////////////////////////////////////////////////////////////////////////////////
1355/// Assign thread id which has to be used for callbacks
1356/// WARNING!!! only for expert use
1357/// Automatically done at the moment when any callback function is invoked
1358/// Can be invoked once again if window Run method will be invoked from other thread
1359/// Normally should be invoked before Show() method is called
1360
1362{
1363 fUseServerThreads = false;
1364 fProcessMT = false;
1365 fCallbacksThrdIdSet = true;
1366 fCallbacksThrdId = std::this_thread::get_id();
1368 fProcessMT = true;
1369 } else if (fMgr->IsUseHttpThread()) {
1370 // special thread is used by the manager, but main thread used for the canvas - not supported
1371 R__LOG_ERROR(WebGUILog()) << "create web window from main thread when THttpServer created with special thread - not supported";
1372 }
1373}
1374
1375/////////////////////////////////////////////////////////////////////////////////
1376/// Let use THttpServer threads to process requests
1377/// WARNING!!! only for expert use
1378/// Should be only used when application provides proper locking and
1379/// does not block. Such mode provides minimal possible latency
1380/// Must be called before callbacks are assigned
1381
1383{
1384 fUseServerThreads = true;
1385 fCallbacksThrdIdSet = false;
1386 fProcessMT = true;
1387}
1388
1389/////////////////////////////////////////////////////////////////////////////////
1390/// Start special thread which will be used by the window to handle all callbacks
1391/// One has to be sure, that access to global ROOT structures are minimized and
1392/// protected with ROOT::EnableThreadSafety(); call
1393
1395{
1396 if (fHasWindowThrd) {
1397 R__LOG_WARNING(WebGUILog()) << "thread already started for the window";
1398 return;
1399 }
1400
1401 fHasWindowThrd = true;
1402
1403 std::thread thrd([this] {
1405 while(fHasWindowThrd)
1406 Run(0.1);
1407 fCallbacksThrdIdSet = false;
1408 });
1409
1410 fWindowThrd = std::move(thrd);
1411}
1412
1413/////////////////////////////////////////////////////////////////////////////////
1414/// Stop special thread
1415
1417{
1418 if (!fHasWindowThrd)
1419 return;
1420
1421 fHasWindowThrd = false;
1422 fWindowThrd.join();
1423}
1424
1425
1426/////////////////////////////////////////////////////////////////////////////////
1427/// Set call-back function for data, received from the clients via websocket
1428///
1429/// Function should have signature like void func(unsigned connid, const std::string &data)
1430/// First argument identifies connection (unique for each window), second argument is received data
1431///
1432/// At the moment when callback is assigned, RWebWindow working thread is detected.
1433/// If called not from main application thread, RWebWindow::Run() function must be regularly called from that thread.
1434///
1435/// Most simple way to assign call-back - use of c++11 lambdas like:
1436/// ~~~ {.cpp}
1437/// auto win = RWebWindow::Create();
1438/// win->SetDefaultPage("file:./page.htm");
1439/// win->SetDataCallBack(
1440/// [](unsigned connid, const std::string &data) {
1441/// printf("Conn:%u data:%s\n", connid, data.c_str());
1442/// }
1443/// );
1444/// win->Show();
1445/// ~~~
1446
1448{
1450 fDataCallback = func;
1451}
1452
1453/////////////////////////////////////////////////////////////////////////////////
1454/// Set call-back function for new connection
1455
1457{
1459 fConnCallback = func;
1460}
1461
1462/////////////////////////////////////////////////////////////////////////////////
1463/// Set call-back function for disconnecting
1464
1466{
1468 fDisconnCallback = func;
1469}
1470
1471/////////////////////////////////////////////////////////////////////////////////
1472/// Set handle which is cleared when last active connection is closed
1473/// Typically can be used to destroy web-based widget at such moment
1474
1475void RWebWindow::SetClearOnClose(const std::shared_ptr<void> &handle)
1476{
1477 fClearOnClose = handle;
1478}
1479
1480/////////////////////////////////////////////////////////////////////////////////
1481/// Set call-backs function for connect, data and disconnect events
1482
1484{
1486 fConnCallback = conn;
1488 fDisconnCallback = disconn;
1489}
1490
1491/////////////////////////////////////////////////////////////////////////////////
1492/// Waits until provided check function or lambdas returns non-zero value
1493/// Check function has following signature: int func(double spent_tm)
1494/// Waiting will be continued, if function returns zero.
1495/// Parameter spent_tm is time in seconds, which already spent inside the function
1496/// First non-zero value breaks loop and result is returned.
1497/// Runs application mainloop and short sleeps in-between
1498
1500{
1501 return fMgr->WaitFor(*this, check);
1502}
1503
1504/////////////////////////////////////////////////////////////////////////////////
1505/// Waits until provided check function or lambdas returns non-zero value
1506/// Check function has following signature: int func(double spent_tm)
1507/// Waiting will be continued, if function returns zero.
1508/// Parameter spent_tm in lambda is time in seconds, which already spent inside the function
1509/// First non-zero value breaks waiting loop and result is returned (or 0 if time is expired).
1510/// Runs application mainloop and short sleeps in-between
1511/// WebGui.OperationTmout rootrc parameter defines waiting time in seconds
1512
1514{
1515 return fMgr->WaitFor(*this, check, true, GetOperationTmout());
1516}
1517
1518/////////////////////////////////////////////////////////////////////////////////
1519/// Waits until provided check function or lambdas returns non-zero value
1520/// Check function has following signature: int func(double spent_tm)
1521/// Waiting will be continued, if function returns zero.
1522/// Parameter spent_tm in lambda is time in seconds, which already spent inside the function
1523/// First non-zero value breaks waiting loop and result is returned (or 0 if time is expired).
1524/// Runs application mainloop and short sleeps in-between
1525/// duration (in seconds) defines waiting time
1526
1528{
1529 return fMgr->WaitFor(*this, check, true, duration);
1530}
1531
1532
1533/////////////////////////////////////////////////////////////////////////////////
1534/// Run window functionality for specified time
1535/// If no action can be performed - just sleep specified time
1536
1537void RWebWindow::Run(double tm)
1538{
1539 if (!fCallbacksThrdIdSet || (fCallbacksThrdId != std::this_thread::get_id())) {
1540 R__LOG_WARNING(WebGUILog()) << "Change thread id where RWebWindow is executed";
1541 fCallbacksThrdIdSet = true;
1542 fCallbacksThrdId = std::this_thread::get_id();
1543 }
1544
1545 if (tm <= 0) {
1546 Sync();
1547 } else {
1548 WaitForTimed([](double) { return 0; }, tm);
1549 }
1550}
1551
1552
1553/////////////////////////////////////////////////////////////////////////////////
1554/// Add embed window
1555
1556unsigned RWebWindow::AddEmbedWindow(std::shared_ptr<RWebWindow> window, int channel)
1557{
1558 if (channel < 2)
1559 return 0;
1560
1561 auto arr = GetConnections(0, true);
1562 if (arr.size() == 0)
1563 return 0;
1564
1565 // check if channel already occupied
1566 if (arr[0]->fEmbed.find(channel) != arr[0]->fEmbed.end())
1567 return 0;
1568
1569 arr[0]->fEmbed[channel] = window;
1570
1571 return arr[0]->fConnId;
1572}
1573
1574/////////////////////////////////////////////////////////////////////////////////
1575/// Remove RWebWindow associated with the channelfEmbed
1576
1577void RWebWindow::RemoveEmbedWindow(unsigned connid, int channel)
1578{
1579 auto arr = GetConnections(connid);
1580
1581 for (auto &conn : arr) {
1582 auto iter = conn->fEmbed.find(channel);
1583 if (iter != conn->fEmbed.end())
1584 conn->fEmbed.erase(iter);
1585 }
1586}
1587
1588
1589/////////////////////////////////////////////////////////////////////////////////
1590/// Create new RWebWindow
1591/// Using default RWebWindowsManager
1592
1593std::shared_ptr<RWebWindow> RWebWindow::Create()
1594{
1595 return RWebWindowsManager::Instance()->CreateWindow();
1596}
1597
1598/////////////////////////////////////////////////////////////////////////////////
1599/// Terminate ROOT session
1600/// Tries to correctly close THttpServer, associated with RWebWindowsManager
1601/// After that exit from process
1602
1604{
1605
1606 // workaround to release all connection-specific handles as soon as possible
1607 // required to work with QWebEngine
1608 // once problem solved, can be removed here
1609 ConnectionsList_t arr1, arr2;
1610
1611 {
1612 std::lock_guard<std::mutex> grd(fConnMutex);
1613 std::swap(arr1, fConn);
1614 std::swap(arr2, fPendingConn);
1615 }
1616
1617 fMgr->Terminate();
1618}
1619
1620/////////////////////////////////////////////////////////////////////////////////
1621/// Static method to show web window
1622/// Has to be used instead of RWebWindow::Show() when window potentially can be embed into other windows
1623/// Soon RWebWindow::Show() method will be done protected
1624
1625unsigned RWebWindow::ShowWindow(std::shared_ptr<RWebWindow> window, const RWebDisplayArgs &args)
1626{
1627 if (!window)
1628 return 0;
1629
1631 unsigned connid = args.fMaster ? args.fMaster->AddEmbedWindow(window, args.fMasterChannel) : 0;
1632
1633 if (connid > 0) {
1634 window->fMaster = args.fMaster;
1635 window->fMasterConnId = connid;
1636 window->fMasterChannel = args.fMasterChannel;
1637
1638 // inform client that connection is established and window initialized
1639 args.fMaster->SubmitData(connid, true, "EMBED_DONE"s, args.fMasterChannel);
1640
1641 // provide call back for window itself that connection is ready
1642 window->ProvideQueueEntry(connid, kind_Connect, ""s);
1643 }
1644
1645 return connid;
1646 }
1647
1648 return window->Show(args);
1649}
1650
#define R__LOG_WARNING(...)
Definition: RLogger.hxx:363
#define R__LOG_ERROR(...)
Definition: RLogger.hxx:362
#define R__LOG_DEBUG(DEBUGLEVEL,...)
Definition: RLogger.hxx:365
#define e(i)
Definition: RSha256.hxx:103
long Long_t
Definition: RtypesCore.h:54
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
char name[80]
Definition: TGX11.cxx:110
char * Form(const char *fmt,...)
Formats a string in a circular formatting buffer.
Definition: TString.cxx:2468
Holds different arguments for starting browser with RWebDisplayHandle::Display() method.
void SetHeadless(bool on=true)
set headless mode
int fMasterChannel
! used master channel
EBrowserKind GetBrowserKind() const
returns configured browser kind, see EBrowserKind for supported values
std::shared_ptr< RWebWindow > fMaster
! master window
@ kEmbedded
window will be embedded into other, no extra browser need to be started
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.
Definition: RWebWindow.hxx:53
bool CheckDataToSend(std::shared_ptr< WebConn > &conn)
Checks if one should send data for specified connection Returns true when send operation was performe...
Definition: RWebWindow.cxx:930
std::vector< std::shared_ptr< WebConn > > ConnectionsList_t
Definition: RWebWindow.hxx:123
int WaitFor(WebWindowWaitFunc_t check)
Waits until provided check function or lambdas returns non-zero value Check function has following si...
unsigned AddEmbedWindow(std::shared_ptr< RWebWindow > window, int channel)
Add embed window.
std::shared_ptr< RWebWindow > fMaster
! master window where this window is embedded
Definition: RWebWindow.hxx:126
void CheckInactiveConnections()
Check if there are connection which are inactive for longer time For instance, batch browser will be ...
Definition: RWebWindow.cxx:540
ConnectionsList_t GetConnections(unsigned connid=0, bool only_active=false) const
returns connection list (or all active 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...
std::string fUserArgs
! arbitrary JSON code, which is accessible via conn.getUserArgs() method
Definition: RWebWindow.hxx:162
int fMasterChannel
! channel id in the master window
Definition: RWebWindow.hxx:128
void StartThread()
Start special thread which will be used by the window to handle all callbacks One has to be sure,...
float GetOperationTmout() const
Returns timeout for synchronous WebWindow operations.
Definition: RWebWindow.hxx:310
std::shared_ptr< WebConn > FindConnection(unsigned wsid)
Find connection with specified websocket id.
Definition: RWebWindow.hxx:176
void SetConnToken(const std::string &token="")
Configures connection token (default none) When specified, in URL of webpage such token should be pro...
Definition: RWebWindow.cxx:597
unsigned MakeHeadless(bool create_new=false)
Start headless browser for specified window Normally only single instance is used,...
Definition: RWebWindow.cxx:185
std::string GetUrl(bool remote=true)
Return URL string to access web window.
Definition: RWebWindow.cxx:156
void CloseConnections()
Closes all connection to clients Normally leads to closing of all correspondent browser windows Some ...
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...
Definition: RWebWindow.hxx:234
std::thread fWindowThrd
! special thread for that window
Definition: RWebWindow.hxx:150
int NumConnections(bool with_pending=false) const
Returns current number of active clients connections.
unsigned GetId() const
Returns ID for the window - unique inside window manager.
Definition: RWebWindow.hxx:225
ConnectionsList_t fConn
! list of all accepted connections
Definition: RWebWindow.hxx:138
void InvokeCallbacks(bool force=false)
Invoke callbacks with existing data Must be called from appropriate thread.
Definition: RWebWindow.cxx:389
std::string fProtocolPrefix
! prefix for created files names
Definition: RWebWindow.hxx:160
std::string GetClientVersion() const
Returns current client version.
void SetConnectCallBack(WebWindowConnectCallback_t func)
Set call-back function for new connection.
WebWindowConnectCallback_t fConnCallback
! callback for connect event
Definition: RWebWindow.hxx:144
unsigned GetMaxQueueLength() const
Return maximal queue length of data which can be held by window.
Definition: RWebWindow.hxx:281
void Sync()
Special method to process all internal activity when window runs in separate thread.
Definition: RWebWindow.cxx:998
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...
unsigned fConnLimit
! number of allowed active connections
Definition: RWebWindow.hxx:140
void Send(unsigned connid, const std::string &data)
Sends data to specified connection.
bool fCallbacksThrdIdSet
! flag indicating that thread id is assigned
Definition: RWebWindow.hxx:148
unsigned Show(const RWebDisplayArgs &args="")
Show window in specified location.
Definition: RWebWindow.cxx:174
THttpServer * GetServer()
Return THttpServer instance serving requests to the window.
Definition: RWebWindow.cxx:164
unsigned AddDisplayHandle(bool headless_mode, const std::string &key, std::unique_ptr< RWebDisplayHandle > &handle)
Add display handle and associated key Key is random number generated when starting new window When cl...
Definition: RWebWindow.cxx:433
unsigned fMasterConnId
! master connection id
Definition: RWebWindow.hxx:127
void AssignThreadId()
Assign thread id which has to be used for callbacks WARNING!!! only for expert use Automatically done...
bool fSendMT
! true is special threads should be used for sending data
Definition: RWebWindow.hxx:134
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::thread::id fCallbacksThrdId
! thread id where callbacks should be invoked
Definition: RWebWindow.hxx:147
std::chrono::time_point< std::chrono::system_clock > timestamp_t
Definition: RWebWindow.hxx:60
std::string fClientVersion
! configured client version, used as prefix in scripts URL
Definition: RWebWindow.hxx:156
bool ProcessBatchHolder(std::shared_ptr< THttpCallArg > &arg)
Process special http request, used to hold headless browser running Such requests should not be repli...
Definition: RWebWindow.cxx:328
unsigned fConnCnt
! counter of new connections to assign ids
Definition: RWebWindow.hxx:136
void SetDisconnectCallBack(WebWindowConnectCallback_t func)
Set call-back function for disconnecting.
std::string fPanelName
! panel name which should be shown in the window
Definition: RWebWindow.hxx:130
void SetDataCallBack(WebWindowDataCallback_t func)
Set call-back function for data, received from the clients via websocket.
unsigned fProtocolConnId
! connection id, which is used for writing protocol
Definition: RWebWindow.hxx:159
void SetUserArgs(const std::string &args)
Set arbitrary JSON data, which is accessible via conn.getUserArgs() method in JavaScript This JSON co...
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...
void StopThread()
Stop special thread.
WebWindowDataCallback_t fDataCallback
! main callback when data over channel 1 is arrived
Definition: RWebWindow.hxx:145
void SubmitData(unsigned connid, bool txt, std::string &&data, int chid=1)
Internal method to send data.
~RWebWindow()
RWebWindow destructor Closes all connections and remove window from manager.
Definition: RWebWindow.cxx:77
void CloseConnection(unsigned connid)
Close specified connection.
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.
Definition: RWebWindow.cxx:607
ConnectionsList_t fPendingConn
! list of pending connection with pre-assigned keys
Definition: RWebWindow.hxx:137
void SetConnLimit(unsigned lmt=0)
Configure maximal number of allowed connections - 0 is unlimited Will not affect already existing con...
Definition: RWebWindow.cxx:575
void SetPanelName(const std::string &name)
Configure window to show some of existing JSROOT panels It uses "file:rootui5sys/panel/panel....
Definition: RWebWindow.cxx:122
RWebWindow()
RWebWindow constructor Should be defined here because of std::unique_ptr<RWebWindowWSHandler>
std::shared_ptr< WebConn > FindOrCreateConnection(unsigned wsid, bool make_new, const char *query)
Find connection with given websocket id.
Definition: RWebWindow.cxx:247
bool fHasWindowThrd
! indicate if special window thread was started
Definition: RWebWindow.hxx:149
int GetSendQueueLength(unsigned connid) const
Returns send queue length for specified connection.
std::shared_ptr< WebConn > RemoveConnection(unsigned wsid)
Remove connection with given websocket id.
Definition: RWebWindow.cxx:294
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...
Definition: RWebWindow.cxx:140
std::shared_ptr< WebConn > _FindConnWithKey(const std::string &key) const
Find connection with specified key.
Definition: RWebWindow.cxx:450
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...
void CheckThreadAssign()
Internal method to verify and thread id has to be assigned from manager again Special case when Proce...
Definition: RWebWindow.cxx:618
bool HasKey(const std::string &key) const
Returns true if provided key value already exists (in processes map or in existing connections)
Definition: RWebWindow.cxx:471
unsigned GetDisplayConnection() const
Returns first connection id where window is displayed It could be that connection(s) not yet fully es...
Definition: RWebWindow.cxx:227
unsigned GetConnLimit() const
returns configured connections limit (0 - default)
Definition: RWebWindow.cxx:585
std::string GetRelativeAddr(const std::shared_ptr< RWebWindow > &win) const
Returns relative URL address for the specified window Address can be required if one needs to access ...
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< RWebWindowWSHandler > fWSHandler
! specialize websocket handler for all incoming connections
Definition: RWebWindow.hxx:135
bool fUseServerThreads
! indicates that server thread is using, no special window thread
Definition: RWebWindow.hxx:132
std::string fProtocolFileName
! local file where communication protocol will be written
Definition: RWebWindow.hxx:157
std::shared_ptr< RWebWindowsManager > fMgr
! display manager
Definition: RWebWindow.hxx:125
void CheckPendingConnections()
Check if started process(es) establish connection.
Definition: RWebWindow.cxx:505
std::string fConnToken
! value of "token" URL parameter which should be provided for connecting window
Definition: RWebWindow.hxx:141
std::mutex fInputQueueMutex
! mutex to protect input queue
Definition: RWebWindow.hxx:152
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.
Definition: RWebWindow.cxx:883
bool IsNativeOnlyConn() const
returns true if only native (own-created) connections are allowed
Definition: RWebWindow.hxx:289
bool ProcessWS(THttpCallArg &arg)
Processing of websockets call-backs, invoked from RWebWindowWSHandler Method invoked from http server...
Definition: RWebWindow.cxx:628
bool HasConnection(unsigned connid=0, bool only_active=true) const
returns true if specified connection id exists
int fProtocolCnt
! counter for protocol recording
Definition: RWebWindow.hxx:158
std::queue< QueueEntry > fInputQueue
! input queue for all callbacks
Definition: RWebWindow.hxx:151
bool fProcessMT
! if window event processing performed in dedicated thread
Definition: RWebWindow.hxx:133
std::string fProtocol
! protocol
Definition: RWebWindow.hxx:161
void ProvideQueueEntry(unsigned connid, EQueueEntryKind kind, std::string &&arg)
Provide data to user callback User callback must be executed in the window thread.
Definition: RWebWindow.cxx:375
void CompleteWSSend(unsigned wsid)
Complete websocket send operation Clear "doing send" flag and check if next operation has to be start...
Definition: RWebWindow.cxx:864
unsigned fId
! unique identifier
Definition: RWebWindow.hxx:131
float fOperationTmout
! timeout in seconds to perform synchronous operation, default 50s
Definition: RWebWindow.hxx:155
unsigned FindHeadlessConnection()
Returns connection id of window running in headless mode This can be special connection which may run...
Definition: RWebWindow.cxx:204
int WaitForTimed(WebWindowWaitFunc_t check)
Waits until provided check function or lambdas returns non-zero value Check function has following si...
WebWindowConnectCallback_t fDisconnCallback
! callback for disconnect event
Definition: RWebWindow.hxx:146
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 RemoveEmbedWindow(unsigned connid, int channel)
Remove RWebWindow associated with the channelfEmbed.
void SetCallBacks(WebWindowConnectCallback_t conn, WebWindowDataCallback_t data, WebWindowConnectCallback_t disconn=nullptr)
Set call-backs function for connect, data and disconnect events.
std::string GenerateKey() const
Generate new unique key for the window.
Definition: RWebWindow.cxx:485
std::shared_ptr< void > fClearOnClose
! entry which is cleared when last connection is closed
Definition: RWebWindow.hxx:163
std::mutex fConnMutex
! mutex used to protect connection list
Definition: RWebWindow.hxx:139
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.
Contains arguments for single HTTP call.
Definition: THttpCallArg.h:27
UInt_t GetWSId() const
get web-socket id
Definition: THttpCallArg.h:107
const void * GetPostData() const
return pointer on posted with request data
Definition: THttpCallArg.h:140
const char * GetQuery() const
returns request query (string after ? in request URL)
Definition: THttpCallArg.h:155
Long_t GetPostDataLength() const
return length of posted with request data
Definition: THttpCallArg.h:143
Bool_t IsMethod(const char *name) const
returns kTRUE if post method is used
Definition: THttpCallArg.h:134
Online http server for arbitrary ROOT application.
Definition: THttpServer.h:31
Random number generator class based on M.
Definition: TRandom3.h:27
void SetSeed(ULong_t seed=0) override
Set the random generator sequence if seed is 0 (default value) a TUUID is generated and used to fill ...
Definition: TRandom3.cxx:206
virtual UInt_t Integer(UInt_t imax)
Returns a random integer uniformly distributed on the interval [ 0, imax-1 ].
Definition: TRandom.cxx:360
This class represents a WWW compatible URL.
Definition: TUrl.h:33
const char * GetValueFromOptions(const char *key) const
Return a value for a given key from the URL options.
Definition: TUrl.cxx:660
void SetOptions(const char *opt)
Definition: TUrl.h:87
Bool_t HasOption(const char *key) const
Returns true if the given key appears in the URL options list.
Definition: TUrl.cxx:683
const Int_t n
Definition: legend1.C:16
void swap(RDirectoryEntry &e1, RDirectoryEntry &e2) noexcept
RLogChannel & WebGUILog()
Log channel for WebGUI diagnostics.
std::function< void(unsigned)> WebWindowConnectCallback_t
function signature for connect/disconnect call-backs argument is connection id
Definition: RWebWindow.hxx:37
std::function< void(unsigned, const std::string &)> WebWindowDataCallback_t
function signature for call-backs from the window clients first argument is connection id,...
Definition: RWebWindow.hxx:41
std::function< int(double)> WebWindowWaitFunc_t
function signature for waiting call-backs Such callback used when calling thread need to waits for so...
Definition: RWebWindow.hxx:48
static constexpr double s
const char * cnt
Definition: TXMLSetup.cxx:75
std::string fData
! text or binary data
Definition: RWebWindow.hxx:65
std::shared_ptr< THttpCallArg > fHold
! request used to hold headless browser
Definition: RWebWindow.hxx:75