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