QtWebApp
httpconnectionhandler.cpp
Go to the documentation of this file.
1 
7 #include "httpresponse.h"
8 
9 using namespace stefanfrings;
10 
11 HttpConnectionHandler::HttpConnectionHandler(const QSettings *settings, HttpRequestHandler *requestHandler, const QSslConfiguration* sslConfiguration)
12  : QObject()
13 {
14  Q_ASSERT(settings!=nullptr);
15  Q_ASSERT(requestHandler!=nullptr);
16  this->settings=settings;
17  this->requestHandler=requestHandler;
18  this->sslConfiguration=sslConfiguration;
19  currentRequest=nullptr;
20  busy=false;
21 
22  // execute signals in a new thread
23  thread = new QThread();
24  thread->start();
25  qDebug("HttpConnectionHandler (%p): thread started", static_cast<void*>(this));
26  moveToThread(thread);
27  readTimer.moveToThread(thread);
28  readTimer.setSingleShot(true);
29 
30  // Create TCP or SSL socket
31  createSocket();
32  socket->moveToThread(thread);
33 
34  // Connect signals
35  connect(socket, SIGNAL(readyRead()), SLOT(read()));
36  connect(socket, SIGNAL(disconnected()), SLOT(disconnected()));
37  connect(&readTimer, SIGNAL(timeout()), SLOT(readTimeout()));
38  connect(thread, SIGNAL(finished()), this, SLOT(thread_done()));
39 
40  qDebug("HttpConnectionHandler (%p): constructed", static_cast<void*>(this));
41 }
42 
43 
44 void HttpConnectionHandler::thread_done()
45 {
46  readTimer.stop();
47  socket->close();
48  delete socket;
49  qDebug("HttpConnectionHandler (%p): thread stopped", static_cast<void*>(this));
50 }
51 
52 
54 {
55  thread->quit();
56  thread->wait();
57  thread->deleteLater();
58  qDebug("HttpConnectionHandler (%p): destroyed", static_cast<void*>(this));
59 }
60 
61 
62 void HttpConnectionHandler::createSocket()
63 {
64  // If SSL is supported and configured, then create an instance of QSslSocket
65  #ifndef QT_NO_SSL
66  if (sslConfiguration)
67  {
68  QSslSocket* sslSocket=new QSslSocket();
69  sslSocket->setSslConfiguration(*sslConfiguration);
70  socket=sslSocket;
71  qDebug("HttpConnectionHandler (%p): SSL is enabled", static_cast<void*>(this));
72  return;
73  }
74  #endif
75  // else create an instance of QTcpSocket
76  socket=new QTcpSocket();
77 }
78 
79 
81 {
82  qDebug("HttpConnectionHandler (%p): handle new connection", static_cast<void*>(this));
83  busy = true;
84  Q_ASSERT(socket->isOpen()==false); // if not, then the handler is already busy
85 
86  //UGLY workaround - we need to clear writebuffer before reusing this socket
87  //https://bugreports.qt-project.org/browse/QTBUG-28914
88  socket->connectToHost("",0);
89  socket->abort();
90 
91  if (!socket->setSocketDescriptor(socketDescriptor))
92  {
93  qCritical("HttpConnectionHandler (%p): cannot initialize socket: %s",
94  static_cast<void*>(this),qPrintable(socket->errorString()));
95  return;
96  }
97 
98  #ifndef QT_NO_SSL
99  // Switch on encryption, if SSL is configured
100  if (sslConfiguration)
101  {
102  qDebug("HttpConnectionHandler (%p): Starting encryption", static_cast<void*>(this));
103  (static_cast<QSslSocket*>(socket))->startServerEncryption();
104  }
105  #endif
106 
107  // Start timer for read timeout
108  int readTimeout=settings->value("readTimeout",10000).toInt();
109  readTimer.start(readTimeout);
110  // delete previous request
111  delete currentRequest;
112  currentRequest=nullptr;
113 }
114 
115 
117 {
118  return busy;
119 }
120 
122 {
123  this->busy = true;
124 }
125 
126 
127 void HttpConnectionHandler::readTimeout()
128 {
129  qDebug("HttpConnectionHandler (%p): read timeout occured",static_cast<void*>(this));
130 
131  //Commented out because QWebView cannot handle this.
132  //socket->write("HTTP/1.1 408 request timeout\r\nConnection: close\r\n\r\n408 request timeout\r\n");
133 
134  while(socket->bytesToWrite()) socket->waitForBytesWritten();
135  socket->disconnectFromHost();
136  delete currentRequest;
137  currentRequest=nullptr;
138 }
139 
140 
141 void HttpConnectionHandler::disconnected()
142 {
143  qDebug("HttpConnectionHandler (%p): disconnected", static_cast<void*>(this));
144  socket->close();
145  readTimer.stop();
146  busy = false;
147 }
148 
149 void HttpConnectionHandler::read()
150 {
151  // The loop adds support for HTTP pipelinig
152  while (socket->bytesAvailable())
153  {
154  #ifdef SUPERVERBOSE
155  qDebug("HttpConnectionHandler (%p): read input",static_cast<void*>(this));
156  #endif
157 
158  // Create new HttpRequest object if necessary
159  if (!currentRequest)
160  {
161  currentRequest=new HttpRequest(settings);
162  }
163 
164  // Collect data for the request object
165  while (socket->bytesAvailable() && currentRequest->getStatus()!=HttpRequest::complete && currentRequest->getStatus()!=HttpRequest::abort)
166  {
167  currentRequest->readFromSocket(socket);
168  if (currentRequest->getStatus()==HttpRequest::waitForBody)
169  {
170  // Restart timer for read timeout, otherwise it would
171  // expire during large file uploads.
172  int readTimeout=settings->value("readTimeout",10000).toInt();
173  readTimer.start(readTimeout);
174  }
175  }
176 
177  // If the request is aborted, return error message and close the connection
178  if (currentRequest->getStatus()==HttpRequest::abort)
179  {
180  socket->write("HTTP/1.1 413 entity too large\r\nConnection: close\r\n\r\n413 Entity too large\r\n");
181  while(socket->bytesToWrite()) socket->waitForBytesWritten();
182  socket->disconnectFromHost();
183  delete currentRequest;
184  currentRequest=nullptr;
185  return;
186  }
187 
188  // If the request is complete, let the request mapper dispatch it
189  if (currentRequest->getStatus()==HttpRequest::complete)
190  {
191  readTimer.stop();
192  qDebug("HttpConnectionHandler (%p): received request",static_cast<void*>(this));
193 
194  // Copy the Connection:close header to the response
195  HttpResponse response(socket);
196  bool closeConnection=QString::compare(currentRequest->getHeader("Connection"),"close",Qt::CaseInsensitive)==0;
197  if (closeConnection)
198  {
199  response.setHeader("Connection","close");
200  }
201 
202  // In case of HTTP 1.0 protocol add the Connection:close header.
203  // This ensures that the HttpResponse does not activate chunked mode, which is not spported by HTTP 1.0.
204  else
205  {
206  bool http1_0=QString::compare(currentRequest->getVersion(),"HTTP/1.0",Qt::CaseInsensitive)==0;
207  if (http1_0)
208  {
209  closeConnection=true;
210  response.setHeader("Connection","close");
211  }
212  }
213 
214  // Call the request mapper
215  try
216  {
217  requestHandler->service(*currentRequest, response);
218  }
219  catch (...)
220  {
221  qCritical("HttpConnectionHandler (%p): An uncatched exception occured in the request handler",
222  static_cast<void*>(this));
223  }
224 
225  // Finalize sending the response if not already done
226  if (!response.hasSentLastPart())
227  {
228  response.write(QByteArray(),true);
229  }
230 
231  qDebug("HttpConnectionHandler (%p): finished request",static_cast<void*>(this));
232 
233  // Find out whether the connection must be closed
234  if (!closeConnection)
235  {
236  // Maybe the request handler or mapper added a Connection:close header in the meantime
237  bool closeResponse=QString::compare(response.getHeaders().value("Connection"),"close",Qt::CaseInsensitive)==0;
238  if (closeResponse==true)
239  {
240  closeConnection=true;
241  }
242  else
243  {
244  // If we have no Content-Length header and did not use chunked mode, then we have to close the
245  // connection to tell the HTTP client that the end of the response has been reached.
246  bool hasContentLength=response.getHeaders().contains("Content-Length");
247  if (!hasContentLength)
248  {
249  bool hasChunkedMode=QString::compare(response.getHeaders().value("Transfer-Encoding"),"chunked",Qt::CaseInsensitive)==0;
250  if (!hasChunkedMode)
251  {
252  closeConnection=true;
253  }
254  }
255  }
256  }
257 
258  // Close the connection or prepare for the next request on the same connection.
259  if (closeConnection)
260  {
261  while(socket->bytesToWrite()) socket->waitForBytesWritten();
262  socket->disconnectFromHost();
263  }
264  else
265  {
266  // Start timer for next request
267  int readTimeout=settings->value("readTimeout",10000).toInt();
268  readTimer.start(readTimeout);
269  }
270  delete currentRequest;
271  currentRequest=nullptr;
272  }
273  }
274 }
bool isBusy()
Returns true, if this handler is in use.
HttpConnectionHandler(const QSettings *settings, HttpRequestHandler *requestHandler, const QSslConfiguration *sslConfiguration=nullptr)
Constructor.
void setBusy()
Mark this handler as busy.
void handleConnection(const tSocketDescriptor socketDescriptor)
Received from from the listener, when the handler shall start processing a new connection.
The request handler generates a response for each HTTP request.
virtual void service(HttpRequest &request, HttpResponse &response)
Generate a response for an incoming HTTP request.
This object represents a single HTTP request.
Definition: httprequest.h:37
RequestStatus getStatus() const
Get the status of this reqeust.
QByteArray getHeader(const QByteArray &name) const
Get the value of a HTTP request header.
void readFromSocket(QTcpSocket *socket)
Read the HTTP request from a socket.
QByteArray getVersion() const
Get the version of the HTPP request (e.g.
This object represents a HTTP response, used to return something to the web client.
Definition: httpresponse.h:36
qintptr tSocketDescriptor
Alias type definition, for compatibility to different Qt versions.