QtWebApp
httpconnectionhandler.cpp
Go to the documentation of this file.
1
7#include "httpresponse.h"
8
9using namespace stefanfrings;
10
11HttpConnectionHandler::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
44void 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
62void 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
127void 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 if(socket->bytesToWrite())
135 {
136 socket->waitForBytesWritten(1000);
137 }
138 socket->disconnectFromHost();
139 delete currentRequest;
140 currentRequest=nullptr;
141}
142
143
144void HttpConnectionHandler::disconnected()
145{
146 qDebug("HttpConnectionHandler (%p): disconnected", static_cast<void*>(this));
147 socket->close();
148 readTimer.stop();
149 busy = false;
150}
151
152void HttpConnectionHandler::read()
153{
154 // The loop adds support for HTTP pipelinig
155 while (socket->bytesAvailable())
156 {
157 #ifdef SUPERVERBOSE
158 qDebug("HttpConnectionHandler (%p): read input",static_cast<void*>(this));
159 #endif
160
161 // Create new HttpRequest object if necessary
162 if (!currentRequest)
163 {
164 currentRequest=new HttpRequest(settings);
165 }
166
167 // Collect data for the request object
168 while (socket->bytesAvailable() &&
169 currentRequest->getStatus()!=HttpRequest::complete &&
170 currentRequest->getStatus()!=HttpRequest::abort_size &&
171 currentRequest->getStatus()!=HttpRequest::abort_broken)
172 {
173 currentRequest->readFromSocket(socket);
174 if (currentRequest->getStatus()==HttpRequest::waitForBody)
175 {
176 // Restart timer for read timeout, otherwise it would
177 // expire during large file uploads.
178 int readTimeout=settings->value("readTimeout",10000).toInt();
179 readTimer.start(readTimeout);
180 }
181 }
182
183 // If the request is aborted, return error message and close the connection
184 if (currentRequest->getStatus()==HttpRequest::abort_size)
185 {
186 socket->write("HTTP/1.1 413 entity too large\r\nConnection: close\r\n\r\n413 Entity too large\r\n");
187 if(socket->bytesToWrite())
188 {
189 socket->waitForBytesWritten(1000);
190 }
191 socket->disconnectFromHost();
192 delete currentRequest;
193 currentRequest=nullptr;
194 return;
195 }
196
197 // another reson to abort the request
198 else if (currentRequest->getStatus()==HttpRequest::abort_broken)
199 {
200 socket->write("HTTP/1.1 400 bad request\r\nConnection: close\r\n\r\n400 Bad request\r\n");
201 if(socket->bytesToWrite())
202 {
203 socket->waitForBytesWritten(1000);
204 }
205 socket->disconnectFromHost();
206 delete currentRequest;
207 currentRequest=nullptr;
208 return;
209 }
210
211 // If the request is complete, let the request mapper dispatch it
212 else if (currentRequest->getStatus()==HttpRequest::complete)
213 {
214 readTimer.stop();
215 qDebug("HttpConnectionHandler (%p): received request",static_cast<void*>(this));
216
217 // Copy the Connection:close header to the response
218 HttpResponse response(socket);
219 bool closeConnection=QString::compare(currentRequest->getHeader("Connection"),"close",Qt::CaseInsensitive)==0;
220 if (closeConnection)
221 {
222 response.setHeader("Connection","close");
223 }
224
225 // In case of HTTP 1.0 protocol add the Connection:close header.
226 // This ensures that the HttpResponse does not activate chunked mode, which is not spported by HTTP 1.0.
227 else
228 {
229 bool http1_0=QString::compare(currentRequest->getVersion(),"HTTP/1.0",Qt::CaseInsensitive)==0;
230 if (http1_0)
231 {
232 closeConnection=true;
233 response.setHeader("Connection","close");
234 }
235 }
236
237 // Call the request mapper
238 try
239 {
240 requestHandler->service(*currentRequest, response);
241 }
242 catch (...)
243 {
244 qCritical("HttpConnectionHandler (%p): An uncatched exception occured in the request handler",
245 static_cast<void*>(this));
246 }
247
248 // Finalize sending the response if not already done
249 if (!response.hasSentLastPart())
250 {
251 response.write(QByteArray(),true);
252 }
253
254 qDebug("HttpConnectionHandler (%p): finished request",static_cast<void*>(this));
255
256 // Find out whether the connection must be closed
257 if (!closeConnection)
258 {
259 // Maybe the request handler or mapper added a Connection:close header in the meantime
260 bool closeResponse=QString::compare(response.getHeaders().value("Connection"),"close",Qt::CaseInsensitive)==0;
261 if (closeResponse==true)
262 {
263 closeConnection=true;
264 }
265 else
266 {
267 // If we have no Content-Length header and did not use chunked mode, then we have to close the
268 // connection to tell the HTTP client that the end of the response has been reached.
269 bool hasContentLength=response.getHeaders().contains("Content-Length");
270 if (!hasContentLength)
271 {
272 bool hasChunkedMode=QString::compare(response.getHeaders().value("Transfer-Encoding"),"chunked",Qt::CaseInsensitive)==0;
273 if (!hasChunkedMode)
274 {
275 closeConnection=true;
276 }
277 }
278 }
279 }
280
281 // Close the connection or prepare for the next request on the same connection.
282 if (closeConnection)
283 {
284 if(socket->bytesToWrite())
285 {
286 socket->waitForBytesWritten(1000);
287 }
288 socket->disconnectFromHost();
289 }
290 else
291 {
292 // Start timer for next request
293 int readTimeout=settings->value("readTimeout",10000).toInt();
294 readTimer.start(readTimeout);
295 }
296 delete currentRequest;
297 currentRequest=nullptr;
298 }
299 }
300}
bool isBusy()
Returns true, if this handler is in use.
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.
const 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.