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