Logo ROOT   6.14/05
Reference Guide
THttpLongPollEngine.cxx
Go to the documentation of this file.
1 // $Id$
2 // Author: Sergey Linev 8/01/2018
3 
4 /*************************************************************************
5  * Copyright (C) 1995-2013, Rene Brun and Fons Rademakers. *
6  * All rights reserved. *
7  * *
8  * For the licensing terms see $ROOTSYS/LICENSE. *
9  * For the list of contributors see $ROOTSYS/README/CREDITS. *
10  *************************************************************************/
11 
12 #include "THttpLongPollEngine.h"
13 
14 #include "THttpCallArg.h"
15 #include "TSystem.h"
16 
17 #include <cstring>
18 #include <cstdlib>
19 
20 //////////////////////////////////////////////////////////////////////////
21 // //
22 // THttpLongPollEngine //
23 // //
24 // Emulation of websocket with long poll requests //
25 // Allows to send data from server to client without explicit request //
26 // //
27 //////////////////////////////////////////////////////////////////////////
28 
29 const std::string THttpLongPollEngine::gLongPollNope = "<<nope>>";
30 
31 
32 //////////////////////////////////////////////////////////////////////////
33 /// constructor
34 
36 {
37 }
38 
39 //////////////////////////////////////////////////////////////////////////
40 /// returns ID of the engine, created from this pointer
41 
43 {
44  const void *ptr = (const void *)this;
45  return TString::Hash((void *)&ptr, sizeof(void *));
46 }
47 
48 //////////////////////////////////////////////////////////////////////////
49 /// clear request, waiting for next portion of data
50 
52 {
53  if (fPoll) {
54  fPoll->Set404();
55  fPoll->NotifyCondition();
56  fPoll.reset();
57  }
58 }
59 
60 //////////////////////////////////////////////////////////////////////////
61 /// Create raw buffer which should be send as reply
62 /// For the raw mode all information must be send via binary response
63 
64 std::string THttpLongPollEngine::MakeBuffer(const void *buf, int len, const char *hdr)
65 {
66  std::string res;
67 
68  if (!fRaw) {
69  res.resize(len);
70  std::copy((const char *)buf, (const char *)buf + len, res.begin());
71  return res;
72  }
73 
74  int hdrlen = hdr ? strlen(hdr) : 0;
75  std::string hdrstr = "bin:";
76  hdrstr.append(std::to_string(hdrlen));
77 
78  while ((hdrstr.length() + 1 + hdrlen) % 8 != 0)
79  hdrstr.append(" ");
80  hdrstr.append(":");
81  if (hdrlen > 0)
82  hdrstr.append(hdr);
83 
84  res.resize(hdrstr.length() + len);
85  std::copy(hdrstr.begin(), hdrstr.begin() + hdrstr.length(), res.begin());
86  std::copy((const char *)buf, (const char *)buf + len, res.begin() + hdrstr.length());
87 
88  return res;
89 }
90 
91 //////////////////////////////////////////////////////////////////////////
92 /// Send binary data via connection - not supported
93 
94 void THttpLongPollEngine::Send(const void *buf, int len)
95 {
96  std::string buf2 = MakeBuffer(buf, len);
97 
98  if (fPoll) {
99  fPoll->SetBinaryContent(std::move(buf2));
100  fPoll->NotifyCondition();
101  fPoll.reset();
102  } else {
103  fQueue.emplace(true, std::move(buf2));
104  if (fQueue.size() > 100)
105  Error("Send", "Too many send operations %u in the queue, check algorithms", (unsigned) fQueue.size());
106  }
107 }
108 
109 //////////////////////////////////////////////////////////////////////////
110 /// Send binary data with text header via connection - not supported
111 
112 void THttpLongPollEngine::SendHeader(const char *hdr, const void *buf, int len)
113 {
114  std::string buf2 = MakeBuffer(buf, len, hdr);
115 
116  if (fPoll) {
117  fPoll->SetBinaryContent(std::move(buf2));
118  if (!fRaw)
119  fPoll->SetExtraHeader("LongpollHeader", hdr);
120  fPoll->NotifyCondition();
121  fPoll.reset();
122  } else {
123  fQueue.emplace(true, std::move(buf2), hdr);
124  if (fQueue.size() > 100)
125  Error("SendHeader", "Too many send operations %u in the queue, check algorithms", (unsigned) fQueue.size());
126  }
127 }
128 
129 //////////////////////////////////////////////////////////////////////////
130 /// Send const char data
131 /// Either do it immediately or keep in internal buffer
132 
134 {
135  std::string sendbuf(fRaw ? "txt:" : "");
136  sendbuf.append(buf);
137 
138  if (fPoll) {
139  if (fRaw) fPoll->SetBinaryContent(std::move(sendbuf));
140  else fPoll->SetTextContent(std::move(sendbuf));
141  fPoll->NotifyCondition();
142  fPoll.reset();
143  } else {
144  fQueue.emplace(false, std::move(sendbuf));
145  if (fQueue.size() > 100)
146  Error("SendCharStar", "Too many send operations %u in the queue, check algorithms", (unsigned) fQueue.size());
147  }
148 }
149 
150 //////////////////////////////////////////////////////////////////////////////
151 /// Preview data for given socket
152 /// function called in the user code before processing correspondent websocket data
153 /// returns kTRUE when user should ignore such http request - it is for internal use
154 
155 Bool_t THttpLongPollEngine::PreviewData(std::shared_ptr<THttpCallArg> &arg)
156 {
157  if (!strstr(arg->GetQuery(), "&dummy")) {
158  // this is normal request, deliver and process it as any other
159  // put dummy content, it can be overwritten in the future
160  arg->SetTextContent(std::string(gLongPollNope));
161  return kFALSE;
162  }
163 
164  if (arg == fPoll)
165  Fatal("PreviewData", "Submit same THttpCallArg object once again");
166 
167  if (fPoll) {
168  Error("PreviewData", "Get next dummy request when previous not completed");
169  // if there are pending request, reply it immediately
170  if (fRaw) fPoll->SetBinaryContent(std::string("txt:") + gLongPollNope);
171  else fPoll->SetTextContent(std::string(gLongPollNope)); // normally should never happen
172  fPoll->NotifyCondition(); // inform http server that request is processed
173  fPoll.reset();
174  }
175 
176  if (fQueue.size() > 0) {
177  QueueItem &item = fQueue.front();
178  if (item.fBinary) {
179  arg->SetBinaryContent(std::move(item.fData));
180  if (!fRaw && !item.fHdr.empty())
181  arg->SetExtraHeader("LongpollHeader", item.fHdr.c_str());
182  } else {
183  arg->SetTextContent(std::move(item.fData));
184  }
185  fQueue.pop();
186  } else {
187  arg->SetPostponed(); // mark http request as pending, http server should wait for notification
188  fPoll = arg; // keep reference on polling request
189  }
190 
191  // if arguments has "&dummy" string, user should not process it
192  return kTRUE;
193 }
194 
195 //////////////////////////////////////////////////////////////////////////////
196 /// Normally requests from client does not replied directly for longpoll socket
197 /// Therefore one can use such request to send data, which was submitted before to the queue
198 
199 void THttpLongPollEngine::PostProcess(std::shared_ptr<THttpCallArg> &arg)
200 {
201  // request with gLongPollNope content indicates, that "dummy" request was not changed by the user
202  if (!arg->IsText() || (arg->GetContentLength() != (Int_t)gLongPollNope.length()) ||
203  (gLongPollNope.compare((const char *)arg->GetContent()) != 0))
204  return;
205 
206  if (fQueue.size() > 0) {
207  QueueItem &item = fQueue.front();
208  if (item.fBinary) {
209  arg->SetBinaryContent(std::move(item.fData));
210  if (!fRaw && !item.fHdr.empty())
211  arg->SetExtraHeader("LongpollHeader", item.fHdr.c_str());
212  } else {
213  arg->SetTextContent(std::move(item.fData));
214  }
215  fQueue.pop();
216  } else if (fRaw) {
217  arg->SetContent(std::string("txt:") + gLongPollNope);
218  }
219 }
virtual void PostProcess(std::shared_ptr< THttpCallArg > &arg)
Normally requests from client does not replied directly for longpoll socket Therefore one can use suc...
void Fatal(const char *location, const char *msgfmt,...)
virtual UInt_t GetId() const
returns ID of the engine, created from this pointer
std::string fHdr
! optional header for raw data
virtual void Send(const void *buf, int len)
Send binary data via connection - not supported.
int Int_t
Definition: RtypesCore.h:41
bool Bool_t
Definition: RtypesCore.h:59
static const std::string gLongPollNope
!< entries submitted to client
UInt_t Hash(ECaseCompare cmp=kExact) const
Return hash value.
Definition: TString.cxx:626
std::string MakeBuffer(const void *buf, int len, const char *hdr=nullptr)
!< default reply on the longpoll request
std::shared_ptr< THttpCallArg > fPoll
!< if true, only content can be used for data transfer
std::queue< QueueItem > fQueue
!< hold polling request, which can be immediately used for the next sending
void Error(const char *location, const char *msgfmt,...)
virtual void ClearHandle()
clear request, waiting for next portion of data
virtual void SendCharStar(const char *buf)
Send const char data Either do it immediately or keep in internal buffer.
THttpLongPollEngine(bool raw=false)
constructor
unsigned int UInt_t
Definition: RtypesCore.h:42
virtual void SendHeader(const char *hdr, const void *buf, int len)
Send binary data with text header via connection - not supported.
virtual Bool_t PreviewData(std::shared_ptr< THttpCallArg > &arg)
Preview data for given socket function called in the user code before processing correspondent websoc...
const Bool_t kFALSE
Definition: RtypesCore.h:88
std::string fData
! text or binary data
const Bool_t kTRUE
Definition: RtypesCore.h:87