httpconnectionhandler.cpp 9.8 KB

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