PageRenderTime 30ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/src/web/qxtabstracthttpconnector.cpp

https://gitlab.com/jeanim/libqxt
C++ | 313 lines | 145 code | 22 blank | 146 comment | 21 complexity | 705ad5a8fae47969822c1b5609cf5232 MD5 | raw file
  1. /****************************************************************************
  2. ** Copyright (c) 2006 - 2011, the LibQxt project.
  3. ** See the Qxt AUTHORS file for a list of authors and copyright holders.
  4. ** All rights reserved.
  5. **
  6. ** Redistribution and use in source and binary forms, with or without
  7. ** modification, are permitted provided that the following conditions are met:
  8. ** * Redistributions of source code must retain the above copyright
  9. ** notice, this list of conditions and the following disclaimer.
  10. ** * Redistributions in binary form must reproduce the above copyright
  11. ** notice, this list of conditions and the following disclaimer in the
  12. ** documentation and/or other materials provided with the distribution.
  13. ** * Neither the name of the LibQxt project nor the
  14. ** names of its contributors may be used to endorse or promote products
  15. ** derived from this software without specific prior written permission.
  16. **
  17. ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  18. ** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  19. ** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  20. ** DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
  21. ** DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  22. ** (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  23. ** LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  24. ** ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  25. ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  26. ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  27. **
  28. ** <http://libqxt.org> <foundation@libqxt.org>
  29. *****************************************************************************/
  30. /*!
  31. \class QxtAbstractHttpConnector
  32. \inmodule QxtWeb
  33. \brief The QxtAbstractHttpConnector class is a base class for defining
  34. HTTP-based protocols for use with QxtHttpSessionManager
  35. QxtHttpSessionManager does the work of managing sessions and state for the
  36. otherwise stateless HTTP protocol, but it relies on QxtAbstractHttpConnector
  37. subclasses to implement the protocol used to communicate with the web server.
  38. Subclasses are responsible for accepting new connections (by implementing
  39. listen(const QHostAddress&, quint16) and invoking addConnection(QIODevice*)),
  40. for informing the session manager when request headers are available (by
  41. implementing canParseRequest(const QByteArray&)), for parsing the request
  42. headers (by implementing parseRequest(QByteArray&)), and for writing response
  43. headers (by implementing writeHeaders(QIODevice*, const QHttpResponseHeader&)).
  44. \sa QxtHttpSessionManager
  45. */
  46. #include "qxthttpsessionmanager.h"
  47. #include "qxtwebcontent.h"
  48. #include <QReadWriteLock>
  49. #include <QHash>
  50. #include <QIODevice>
  51. #include <QByteArray>
  52. #include <QPointer>
  53. #ifndef QXT_DOXYGEN_RUN
  54. class QxtAbstractHttpConnectorPrivate : public QxtPrivate<QxtAbstractHttpConnector>
  55. {
  56. public:
  57. QxtHttpSessionManager* manager;
  58. QReadWriteLock bufferLock, requestLock;
  59. QHash<QIODevice*, QByteArray> buffers; // connection->buffer
  60. QHash<QIODevice*, QPointer<QxtWebContent> > contents; // connection->content
  61. QHash<quint32, QIODevice*> requests; // requestID->connection
  62. quint32 nextRequestID;
  63. inline quint32 getNextRequestID(QIODevice* connection)
  64. {
  65. QWriteLocker locker(&requestLock);
  66. do
  67. {
  68. nextRequestID++;
  69. if (nextRequestID == 0xFFFFFFFF) nextRequestID = 1;
  70. }
  71. while (requests.contains(nextRequestID)); // yeah, right
  72. requests[nextRequestID] = connection;
  73. return nextRequestID;
  74. }
  75. inline void doneWithBuffer(QIODevice* device)
  76. {
  77. QWriteLocker locker(&bufferLock);
  78. buffers.remove(device);
  79. contents.remove(device);
  80. }
  81. inline void doneWithRequest(quint32 requestID)
  82. {
  83. QWriteLocker locker(&requestLock);
  84. requests.remove(requestID);
  85. }
  86. inline QIODevice* getRequestConnection(quint32 requestID)
  87. {
  88. QReadLocker locker(&requestLock);
  89. return requests[requestID];
  90. }
  91. };
  92. #endif
  93. /*!
  94. * Creates a QxtAbstractHttpConnector with the specified \a parent.
  95. *
  96. * Note that this is an abstract class and cannot be instantiated directly.
  97. */
  98. QxtAbstractHttpConnector::QxtAbstractHttpConnector(QObject* parent) : QObject(parent)
  99. {
  100. QXT_INIT_PRIVATE(QxtAbstractHttpConnector);
  101. qxt_d().nextRequestID = 0;
  102. }
  103. /*!
  104. * \internal
  105. */
  106. void QxtAbstractHttpConnector::setSessionManager(QxtHttpSessionManager* manager)
  107. {
  108. qxt_d().manager = manager;
  109. }
  110. /*!
  111. * Returns the session manager into which the connector is installed.
  112. *
  113. * \sa QxtHttpSessionManager::setConnector()
  114. */
  115. QxtHttpSessionManager* QxtAbstractHttpConnector::sessionManager() const
  116. {
  117. return qxt_d().manager;
  118. }
  119. /*!
  120. * \internal
  121. * Returns the QIODevice associated with a \a requestID.
  122. *
  123. * The request ID is generated internally and used by the session manager.
  124. */
  125. QIODevice* QxtAbstractHttpConnector::getRequestConnection(quint32 requestID)
  126. {
  127. return qxt_d().getRequestConnection(requestID);
  128. }
  129. /*!
  130. * Starts managing a new connection from \a device.
  131. *
  132. * This function should be invoked by a subclass to attach incoming connections
  133. * to the session manager.
  134. */
  135. void QxtAbstractHttpConnector::addConnection(QIODevice* device)
  136. {
  137. if(!device) return;
  138. QWriteLocker locker(&qxt_d().bufferLock);
  139. qxt_d().buffers[device] = QByteArray();
  140. QObject::connect(device, SIGNAL(readyRead()), this, SLOT(incomingData()));
  141. QObject::connect(device, SIGNAL(aboutToClose()), this, SLOT(disconnected()));
  142. QObject::connect(device, SIGNAL(disconnected()), this, SLOT(disconnected()));
  143. QObject::connect(device, SIGNAL(destroyed()), this, SLOT(disconnected()));
  144. }
  145. /*!
  146. * \internal
  147. */
  148. void QxtAbstractHttpConnector::incomingData(QIODevice* device)
  149. {
  150. if (!device)
  151. {
  152. device = qobject_cast<QIODevice*>(sender());
  153. if (!device) return;
  154. }
  155. // Scope things so we don't block access during incomingRequest()
  156. QHttpRequestHeader header;
  157. QxtWebContent *content = 0;
  158. {
  159. // Fetch the incoming data block
  160. QByteArray block = device->readAll();
  161. // Check for a current content "device"
  162. QReadLocker locker(&qxt_d().bufferLock);
  163. content = qxt_d().contents[device];
  164. if(content && (content->wantAll() || content->bytesNeeded() > 0)){
  165. // This block (or part of it) belongs to content device
  166. qint64 needed = block.size();
  167. if(!content->wantAll() && needed > content->bytesNeeded())
  168. needed = content->bytesNeeded();
  169. content->write(block.constData(), needed);
  170. if(block.size() <= needed)
  171. return; // Used it all ...
  172. block.remove(0, needed);
  173. }
  174. // The data received represents a new request (or start thereof)
  175. qxt_d().contents[device] = content = NULL;
  176. QByteArray& buffer = qxt_d().buffers[device];
  177. buffer.append(block);
  178. if (!canParseRequest(buffer)) return;
  179. // Have received all of the headers so we can start processing
  180. header = parseRequest(buffer);
  181. QByteArray start;
  182. int len = header.hasContentLength() ? int(header.contentLength()) : -1;
  183. if(len > 0)
  184. {
  185. if(len <= buffer.size()){
  186. // This request is fully-received & excess is another request
  187. // Leave in buffer & we'll fake a following "readyRead()"
  188. start = buffer.left(len);
  189. buffer = buffer.mid(len);
  190. content = new QxtWebContent(start, this);
  191. if(buffer.size() > 0)
  192. QMetaObject::invokeMethod(this, "incomingData",
  193. Qt::QueuedConnection, Q_ARG(QIODevice*, device));
  194. }
  195. else{
  196. // This request isn't finished yet but may still have one to
  197. // follow it. Remember the content device so we can append to
  198. // it until we've got it all.
  199. start = buffer;
  200. buffer.clear();
  201. qxt_d().contents[device] = content =
  202. new QxtWebContent(len, start, this, device);
  203. }
  204. }
  205. else if (header.hasKey("connection") && header.value("connection").toLower() == "close")
  206. {
  207. // Not pipelining so we want to pass all remaining data to the
  208. // content device. Although 'len' will be -1, we're using an
  209. // explict value for clarity. This causes the content device
  210. // to indicate it wants all remaining data.
  211. start = buffer;
  212. buffer.clear();
  213. qxt_d().contents[device] = content =
  214. new QxtWebContent(-1, start, this, device);
  215. } // else no content
  216. //
  217. // NOTE: Buffer lock goes out of scope after this point
  218. }
  219. // Allocate request ID and process it
  220. quint32 requestID = qxt_d().getNextRequestID(device);
  221. sessionManager()->incomingRequest(requestID, header, content);
  222. }
  223. /*!
  224. * \internal
  225. */
  226. void QxtAbstractHttpConnector::disconnected()
  227. {
  228. quint32 requestID=0;
  229. QIODevice* device = qobject_cast<QIODevice*>(sender());
  230. if (!device) return;
  231. requestID = qxt_d().requests.key(device);
  232. qxt_d().doneWithRequest(requestID);
  233. qxt_d().doneWithBuffer(device);
  234. sessionManager()->disconnected(device);
  235. }
  236. /*!
  237. * Returns the current local server port assigned during binding. This will
  238. * be 0 if the connector isn't currently bound or when a port number isn't
  239. * applicable to the connector in use.
  240. *
  241. * \sa listen()
  242. */
  243. quint16 QxtAbstractHttpConnector::serverPort() const
  244. {
  245. return 0;
  246. }
  247. /*!
  248. * \fn virtual bool QxtAbstractHttpConnector::listen(const QHostAddress& interface, quint16 port)
  249. * Invoked by the session manager to indicate that the connector should listen
  250. * for incoming connections on the specified \a interface and \a port.
  251. *
  252. * If the interface is QHostAddress::Any, the server will listen on all
  253. * network interfaces.
  254. * If the port is explicitly set to 0, the underlying network subsystem will
  255. * assign a port in the dynamic range. In this case, the resulting port number
  256. * may be obtained using the serverPort() method.
  257. *
  258. * Returns true on success, or false if the server could not begin listening.
  259. *
  260. * \sa addConnection(), shutdown()
  261. */
  262. /*!
  263. * \fn virtual bool QxtAbstractHttpConnector::shutdown()
  264. * Invoked by the session manager to indicate that the connector should close
  265. * it's listener port and stop accepting new connections. A shutdown may be
  266. * followed by a new listen (to switch ports or handle a change in the
  267. * machine's IP address).
  268. *
  269. * Returns true on success, or false if the server wasn't listening.
  270. *
  271. * \sa listen()
  272. */
  273. /*!
  274. * \fn virtual bool QxtAbstractHttpConnector::canParseRequest(const QByteArray& buffer)
  275. * Returns true if a complete set of request headers can be extracted from the provided \a buffer.
  276. */
  277. /*!
  278. * \fn virtual QHttpRequestHeader QxtAbstractHttpConnector::parseRequest(QByteArray& buffer)
  279. * Extracts a set of request headers from the provided \a buffer.
  280. *
  281. * Subclasses implementing this function must be sure to remove the parsed data from the buffer.
  282. */
  283. /*!
  284. * \fn virtual void QxtAbstractHttpConnector::writeHeaders(QIODevice* device, const QHttpResponseHeader& header)
  285. * Writes a the response \a header to the specified \a device.
  286. */