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