SDRAngel  4.11.5
Developer docs for <a href="https://github.com/f4exb/sdrangel">SDRangel<\a>, an Open Source Qt5 / OpenGL 3.0+ SDR and signal analyzer frontend to various hardware.
httpconnectionhandler.cpp
Go to the documentation of this file.
1 
7 #include "httpresponse.h"
8 
9 using namespace qtwebapp;
10 
11 HttpConnectionHandler::HttpConnectionHandler(QSettings* settings, HttpRequestHandler* requestHandler, QSslConfiguration* sslConfiguration)
12  : QThread(), useQtSettings(true)
13 {
14  Q_ASSERT(settings != 0);
15  Q_ASSERT(requestHandler != 0);
16  this->settings = settings;
17  this->listenerSettings = 0;
18  this->requestHandler = requestHandler;
19  this->sslConfiguration = sslConfiguration;
20  currentRequest = 0;
21  busy=false;
22 
23  // Create TCP or SSL socket
24  createSocket();
25 
26  // execute signals in my own thread
27  moveToThread(this);
28  socket->moveToThread(this);
29  readTimer.moveToThread(this);
30 
31  // Connect signals
32  connect(socket, SIGNAL(readyRead()), SLOT(read()));
33  connect(socket, SIGNAL(disconnected()), SLOT(disconnected()));
34  connect(&readTimer, SIGNAL(timeout()), SLOT(readTimeout()));
35  readTimer.setSingleShot(true);
36 
37  qDebug("HttpConnectionHandler (%p): constructed", this);
38  this->start();
39 }
40 
42  : QThread(), useQtSettings(false)
43 {
44  Q_ASSERT(settings != 0);
45  Q_ASSERT(requestHandler != 0);
46  this->settings = 0;
47  this->listenerSettings = settings;
48  this->requestHandler = requestHandler;
49  this->sslConfiguration = sslConfiguration;
50  currentRequest = 0;
51  busy = false;
52 
53  // Create TCP or SSL socket
54  createSocket();
55 
56  // execute signals in my own thread
57  moveToThread(this);
58  socket->moveToThread(this);
59  readTimer.moveToThread(this);
60 
61  // Connect signals
62  connect(socket, SIGNAL(readyRead()), SLOT(read()));
63  connect(socket, SIGNAL(disconnected()), SLOT(disconnected()));
64  connect(&readTimer, SIGNAL(timeout()), SLOT(readTimeout()));
65  readTimer.setSingleShot(true);
66 
67  qDebug("HttpConnectionHandler (%p): constructed", this);
68  this->start();
69 }
70 
71 
73 {
74  quit();
75  wait();
76  qDebug("HttpConnectionHandler (%p): destroyed", this);
77 }
78 
79 
81 {
82  // If SSL is supported and configured, then create an instance of QSslSocket
83  #ifndef QT_NO_OPENSSL
84  if (sslConfiguration)
85  {
86  QSslSocket* sslSocket=new QSslSocket();
87  sslSocket->setSslConfiguration(*sslConfiguration);
88  socket=sslSocket;
89  qDebug("HttpConnectionHandler (%p): SSL is enabled", this);
90  return;
91  }
92  #endif
93  // else create an instance of QTcpSocket
94  socket=new QTcpSocket();
95 }
96 
97 
99 {
100 #ifdef SUPERVERBOSE
101  qDebug("HttpConnectionHandler (%p): thread started", this);
102 #endif
103  try
104  {
105  exec();
106  }
107  catch (...)
108  {
109  qCritical("HttpConnectionHandler (%p): an uncatched exception occurred in the thread",this);
110  }
111  socket->close();
112  delete socket;
113  readTimer.stop();
114 #ifdef SUPERVERBOSE
115  qDebug("HttpConnectionHandler (%p): thread stopped", this);
116 #endif
117 }
118 
119 
121 {
122 #ifdef SUPERVERBOSE
123  qDebug("HttpConnectionHandler (%p): handle new connection", this);
124 #endif
125  busy = true;
126  Q_ASSERT(socket->isOpen()==false); // if not, then the handler is already busy
127 
128  //UGLY workaround - we need to clear writebuffer before reusing this socket
129  //https://bugreports.qt-project.org/browse/QTBUG-28914
130  socket->connectToHost("",0);
131  socket->abort();
132 
133  if (!socket->setSocketDescriptor(socketDescriptor))
134  {
135  qCritical("HttpConnectionHandler (%p): cannot initialize socket: %s", this,qPrintable(socket->errorString()));
136  return;
137  }
138 
139  #ifndef QT_NO_OPENSSL
140  // Switch on encryption, if SSL is configured
141  if (sslConfiguration)
142  {
143  qDebug("HttpConnectionHandler (%p): Starting encryption", this);
144  ((QSslSocket*)socket)->startServerEncryption();
145  }
146  #endif
147 
148  // Start timer for read timeout
149  int readTimeout = useQtSettings ? settings->value("readTimeout",10000).toInt() : listenerSettings->readTimeout;
150  readTimer.start(readTimeout);
151  // delete previous request
152  delete currentRequest;
153  currentRequest=0;
154 }
155 
156 
158 {
159  return busy;
160 }
161 
163 {
164  this->busy = true;
165 }
166 
167 
169 {
170  qDebug("HttpConnectionHandler (%p): read timeout occurred",this);
171 
172  //Commented out because QWebView cannot handle this.
173  //socket->write("HTTP/1.1 408 request timeout\r\nConnection: close\r\n\r\n408 request timeout\r\n");
174 
175  while(socket->bytesToWrite()) socket->waitForBytesWritten();
176  socket->disconnectFromHost();
177  delete currentRequest;
178  currentRequest=0;
179 }
180 
181 
183 {
184  qDebug("HttpConnectionHandler (%p): disconnected", this);
185  socket->close();
186  readTimer.stop();
187  busy = false;
188 }
189 
191 {
192  // The loop adds support for HTTP pipelinig
193  while (socket->bytesAvailable())
194  {
195  #ifdef SUPERVERBOSE
196  qDebug("HttpConnectionHandler (%p): read input",this);
197  #endif
198 
199  // Create new HttpRequest object if necessary
200  if (!currentRequest)
201  {
202  if (useQtSettings) {
204  } else {
206  }
207  }
208 
209  // Collect data for the request object
211  {
214  {
215  // Restart timer for read timeout, otherwise it would
216  // expire during large file uploads.
217  int readTimeout = useQtSettings ? settings->value("readTimeout",10000).toInt() : listenerSettings->readTimeout;
218  readTimer.start(readTimeout);
219  }
220  }
221 
222  // If the request is aborted, return error message and close the connection
224  {
225  socket->write("HTTP/1.1 413 entity too large\r\nConnection: close\r\n\r\n413 Entity too large\r\n");
226  while(socket->bytesToWrite()) socket->waitForBytesWritten();
227  socket->disconnectFromHost();
228  delete currentRequest;
229  currentRequest=0;
230  return;
231  }
232 
233  // If the request is complete, let the request mapper dispatch it
235  {
236  readTimer.stop();
237  qDebug("HttpConnectionHandler (%p): received request from %s (%s) %s",
238  this,
239  qPrintable(currentRequest->getPeerAddress().toString()),
240  currentRequest->getMethod().toStdString().c_str(),
241  currentRequest->getPath().toStdString().c_str());
242 
243  // Copy the Connection:close header to the response
244  HttpResponse response(socket);
245  bool closeConnection=QString::compare(currentRequest->getHeader("Connection"),"close",Qt::CaseInsensitive)==0;
246  if (closeConnection)
247  {
248  response.setHeader("Connection","close");
249  }
250 
251  // In case of HTTP 1.0 protocol add the Connection:close header.
252  // This ensures that the HttpResponse does not activate chunked mode, which is not spported by HTTP 1.0.
253  else
254  {
255  bool http1_0=QString::compare(currentRequest->getVersion(),"HTTP/1.0",Qt::CaseInsensitive)==0;
256  if (http1_0)
257  {
258  closeConnection=true;
259  response.setHeader("Connection","close");
260  }
261  }
262 
263  // Call the request mapper
264  try
265  {
267  }
268  catch (...)
269  {
270  qCritical("HttpConnectionHandler (%p): An uncatched exception occurred in the request handler",this);
271  }
272 
273  // Finalize sending the response if not already done
274  if (!response.hasSentLastPart())
275  {
276  response.write(QByteArray(),true);
277  }
278 
279 #ifdef SUPERVERBOSE
280  qDebug("HttpConnectionHandler (%p): finished request",this);
281 #endif
282 
283  // Find out whether the connection must be closed
284  if (!closeConnection)
285  {
286  // Maybe the request handler or mapper added a Connection:close header in the meantime
287  bool closeResponse=QString::compare(response.getHeaders().value("Connection"),"close",Qt::CaseInsensitive)==0;
288  if (closeResponse==true)
289  {
290  closeConnection=true;
291  }
292  else
293  {
294  // If we have no Content-Length header and did not use chunked mode, then we have to close the
295  // connection to tell the HTTP client that the end of the response has been reached.
296  bool hasContentLength=response.getHeaders().contains("Content-Length");
297  if (!hasContentLength)
298  {
299  bool hasChunkedMode=QString::compare(response.getHeaders().value("Transfer-Encoding"),"chunked",Qt::CaseInsensitive)==0;
300  if (!hasChunkedMode)
301  {
302  closeConnection=true;
303  }
304  }
305  }
306  }
307 
308  // Close the connection or prepare for the next request on the same connection.
309  if (closeConnection)
310  {
311  while(socket->bytesToWrite()) socket->waitForBytesWritten();
312  socket->disconnectFromHost();
313  }
314  else
315  {
316  // Start timer for next request
317  int readTimeout = useQtSettings ? settings->value("readTimeout",10000).toInt() : listenerSettings->readTimeout;
318  readTimer.start(readTimeout);
319  }
320  delete currentRequest;
321  currentRequest=0;
322  }
323  }
324 }
RequestStatus getStatus() const
QByteArray getPath() const
HttpConnectionHandler(QSettings *settings, HttpRequestHandler *requestHandler, QSslConfiguration *sslConfiguration=NULL)
void handleConnection(tSocketDescriptor socketDescriptor)
QByteArray getVersion() const
QByteArray getHeader(const QByteArray &name) const
const HttpListenerSettings * listenerSettings
void readFromSocket(QTcpSocket *socket)
QHostAddress getPeerAddress() const
QByteArray getMethod() const
virtual void service(HttpRequest &request, HttpResponse &response)