PageRenderTime 95ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/thirdparty/qxt/qxtweb-standalone/qxtweb/qxthttpsessionmanager.cpp

http://github.com/tomahawk-player/tomahawk
C++ | 711 lines | 458 code | 63 blank | 190 comment | 97 complexity | 7ec31fed01b28178309f4a2c0dfa3225 MD5 | raw file
Possible License(s): LGPL-2.1, BSD-3-Clause, GPL-3.0, GPL-2.0
  1. /****************************************************************************
  2. **
  3. ** Copyright (C) Qxt Foundation. Some rights reserved.
  4. **
  5. ** This file is part of the QxtWeb module of the Qxt library.
  6. **
  7. ** This library is free software; you can redistribute it and/or modify it
  8. ** under the terms of the Common Public License, version 1.0, as published
  9. ** by IBM, and/or under the terms of the GNU Lesser General Public License,
  10. ** version 2.1, as published by the Free Software Foundation.
  11. **
  12. ** This file is provided "AS IS", without WARRANTIES OR CONDITIONS OF ANY
  13. ** KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY
  14. ** WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR
  15. ** FITNESS FOR A PARTICULAR PURPOSE.
  16. **
  17. ** You should have received a copy of the CPL and the LGPL along with this
  18. ** file. See the LICENSE file and the cpl1.0.txt/lgpl-2.1.txt files
  19. ** included with the source distribution for more information.
  20. ** If you did not receive a copy of the licenses, contact the Qxt Foundation.
  21. **
  22. ** <http://libqxt.org> <foundation@libqxt.org>
  23. **
  24. ****************************************************************************/
  25. /*!
  26. \class QxtHttpSessionManager
  27. \inmodule QxtWeb
  28. \brief The QxtHttpSessionManager class provides a session manager for HTTP-based protocols
  29. QxtHttpSessionManager is a QxtWeb session manager that adds session management
  30. support to the normally stateless HTTP model.
  31. In addition to session management, QxtHttpSessionManager also supports a
  32. static service, which can serve content that does not require session management,
  33. such as static web pages. The static service is also used to respond to HTTP/0.9
  34. clients that do not support cookies and HTTP/1.0 and HTTP/1.1 clients that are
  35. rejecting cookies. If no static service is provided, these clients will only
  36. see an "Internal Configuration Error", so it is recommended to supply a static
  37. service, even one that only returns a more useful error message.
  38. QxtHttpSessionManager attempts to be thread-safe in accepting connections and
  39. posting events. It is reentrant for all other functionality.
  40. \sa QxtAbstractWebService
  41. */
  42. #include "qxthttpsessionmanager.h"
  43. #include "qxtwebevent.h"
  44. #include "qxtwebcontent.h"
  45. #include "qxtabstractwebservice.h"
  46. #include <qxtboundfunction.h>
  47. #include <QMutex>
  48. #include <QList>
  49. #include <QUuid>
  50. #include <QIODevice>
  51. #include <QByteArray>
  52. #include <QPair>
  53. #include <QMetaObject>
  54. #include <QThread>
  55. #include <qxtmetaobject.h>
  56. #include <QTcpSocket>
  57. #ifndef QXT_DOXYGEN_RUN
  58. class QxtHttpSessionManagerPrivate : public QxtPrivate<QxtHttpSessionManager>
  59. {
  60. public:
  61. struct ConnectionState
  62. {
  63. QxtBoundFunction* onBytesWritten;
  64. bool readyRead;
  65. bool finishedTransfer;
  66. bool keepAlive;
  67. bool streaming;
  68. int httpMajorVersion;
  69. int httpMinorVersion;
  70. int sessionID;
  71. };
  72. QxtHttpSessionManagerPrivate() : iface(QHostAddress::Any), port(80), sessionCookieName("sessionID"), connector(0), staticService(0), autoCreateSession(true),
  73. eventLock(QMutex::Recursive), sessionLock(QMutex::Recursive) {}
  74. QXT_DECLARE_PUBLIC(QxtHttpSessionManager)
  75. QHostAddress iface;
  76. quint16 port;
  77. QByteArray sessionCookieName;
  78. QxtAbstractHttpConnector* connector;
  79. QxtAbstractWebService* staticService;
  80. bool autoCreateSession;
  81. QMutex eventLock;
  82. QList<QxtWebEvent*> eventQueue;
  83. QMutex sessionLock;
  84. QHash<QUuid, int> sessionKeys; // sessionKey->sessionID
  85. QHash<QIODevice*, ConnectionState> connectionState; // connection->state
  86. Qt::HANDLE mainThread;
  87. };
  88. #endif
  89. /*!
  90. * Constructs a new QxtHttpSessionManager with the specified \a parent.
  91. */
  92. QxtHttpSessionManager::QxtHttpSessionManager(QObject* parent) : QxtAbstractWebSessionManager(parent)
  93. {
  94. QXT_INIT_PRIVATE(QxtHttpSessionManager);
  95. qxt_d().mainThread = QThread::currentThreadId();
  96. }
  97. /*!
  98. * Returns the interface on which the session manager will listen for incoming connections.
  99. * \sa setInterface
  100. */
  101. QHostAddress QxtHttpSessionManager::listenInterface() const
  102. {
  103. return qxt_d().iface;
  104. }
  105. /*!
  106. * Sets the interface \a iface on which the session manager will listen for incoming
  107. * connections.
  108. *
  109. * The default value is QHostAddress::Any, which will cause the session manager
  110. * to listen on all network interfaces.
  111. *
  112. * \sa QxtAbstractHttpConnector::listen
  113. */
  114. void QxtHttpSessionManager::setListenInterface(const QHostAddress& iface)
  115. {
  116. qxt_d().iface = iface;
  117. }
  118. /*!
  119. * Returns the port on which the session manager will listen for incoming connections.
  120. * \sa setInterface
  121. */
  122. quint16 QxtHttpSessionManager::port() const
  123. {
  124. return qxt_d().port;
  125. }
  126. /*!
  127. * Sets the \a port on which the session manager will listen for incoming connections.
  128. *
  129. * The default value is to listen on port 80. This is an acceptable value when
  130. * using QxtHttpServerConnector, but it is not likely to be desirable for other
  131. * connectors.
  132. *
  133. * \sa port
  134. */
  135. void QxtHttpSessionManager::setPort(quint16 port)
  136. {
  137. qxt_d().port = port;
  138. }
  139. /*!
  140. * \reimp
  141. */
  142. bool QxtHttpSessionManager::start()
  143. {
  144. Q_ASSERT(qxt_d().connector);
  145. return connector()->listen(listenInterface(), port());
  146. }
  147. /*!
  148. * Returns the name of the HTTP cookie used to track sessions in the web browser.
  149. * \sa setSessionCookieName
  150. */
  151. QByteArray QxtHttpSessionManager::sessionCookieName() const
  152. {
  153. return qxt_d().sessionCookieName;
  154. }
  155. /*!
  156. * Sets the \a name of the HTTP cookie used to track sessions in the web browser.
  157. *
  158. * The default value is "sessionID".
  159. *
  160. * \sa sessionCookieName
  161. */
  162. void QxtHttpSessionManager::setSessionCookieName(const QByteArray& name)
  163. {
  164. qxt_d().sessionCookieName = name;
  165. }
  166. /*!
  167. * Sets the \a connector used to manage connections to web browsers.
  168. *
  169. * \sa connector
  170. */
  171. void QxtHttpSessionManager::setConnector(QxtAbstractHttpConnector* connector)
  172. {
  173. connector->setSessionManager(this);
  174. qxt_d().connector = connector;
  175. }
  176. /*!
  177. * Sets the \a connector used to manage connections to web browsers.
  178. *
  179. * This overload is provided for convenience and can construct the predefined
  180. * connectors provided with Qxt.
  181. *
  182. * \sa connector
  183. */
  184. void QxtHttpSessionManager::setConnector(Connector connector)
  185. {
  186. if (connector == HttpServer)
  187. setConnector(new QxtHttpServerConnector(this));
  188. else if (connector == Scgi)
  189. setConnector(new QxtScgiServerConnector(this));
  190. /* commented out pending implementation
  191. else if(connector == Fcgi)
  192. setConnector(new QxtFcgiConnector(this));
  193. */
  194. }
  195. /*!
  196. * Returns the connector used to manage connections to web browsers.
  197. * \sa setConnector
  198. */
  199. QxtAbstractHttpConnector* QxtHttpSessionManager::connector() const
  200. {
  201. return qxt_d().connector;
  202. }
  203. /*!
  204. * Returns \c true if sessions are automatically created for every connection
  205. * that does not already have a session cookie associated with it; otherwise
  206. * returns \c false.
  207. * \sa setAutoCreateSession
  208. */
  209. bool QxtHttpSessionManager::autoCreateSession() const
  210. {
  211. return qxt_d().autoCreateSession;
  212. }
  213. /*!
  214. * Sets \a enabled whether sessions are automatically created for every connection
  215. * that does not already have a session cookie associated with it.
  216. *
  217. * Sessions are only created for clients that support HTTP cookies. HTTP/0.9
  218. * clients will never generate a session.
  219. *
  220. * \sa autoCreateSession
  221. */
  222. void QxtHttpSessionManager::setAutoCreateSession(bool enable)
  223. {
  224. qxt_d().autoCreateSession = enable;
  225. }
  226. /*!
  227. * Returns the QxtAbstractWebService that is used to respond to requests from
  228. * connections that are not associated with a session.
  229. *
  230. * \sa setStaticContentService
  231. */
  232. QxtAbstractWebService* QxtHttpSessionManager::staticContentService() const
  233. {
  234. return qxt_d().staticService;
  235. }
  236. /*!
  237. * Sets the \a service that is used to respond to requests from
  238. * connections that are not associated with a session.
  239. *
  240. * If no static content service is set, connections that are not associated
  241. * with a session will receive an "Internal Configuration Error".
  242. *
  243. * \sa staticContentService
  244. */
  245. void QxtHttpSessionManager::setStaticContentService(QxtAbstractWebService* service)
  246. {
  247. qxt_d().staticService = service;
  248. }
  249. /*!
  250. * \reimp
  251. */
  252. void QxtHttpSessionManager::postEvent(QxtWebEvent* h)
  253. {
  254. qxt_d().eventLock.lock();
  255. qxt_d().eventQueue.append(h);
  256. qxt_d().eventLock.unlock();
  257. // if(h->type() == QxtWebEvent::Page)
  258. QMetaObject::invokeMethod(this, "processEvents", Qt::QueuedConnection);
  259. }
  260. /*!
  261. * Creates a new session and sends the session key to the web browser.
  262. *
  263. * Subclasses may override this function to perform custom session initialization,
  264. * but they must call the base class implementation in order to update the internal
  265. * session database and fetch a new session ID.
  266. */
  267. int QxtHttpSessionManager::newSession()
  268. {
  269. QMutexLocker locker(&qxt_d().sessionLock);
  270. int sessionID = createService();
  271. QUuid key;
  272. do
  273. {
  274. key = QUuid::createUuid();
  275. }
  276. while (qxt_d().sessionKeys.contains(key));
  277. qxt_d().sessionKeys[key] = sessionID;
  278. postEvent(new QxtWebStoreCookieEvent(sessionID, qxt_d().sessionCookieName, key));
  279. return sessionID;
  280. }
  281. /*!
  282. * Handles incoming HTTP requests and dispatches them to the appropriate service.
  283. *
  284. * The \a requestID is an opaque value generated by the connector.
  285. *
  286. * Subclasses may override this function to perform preprocessing on each
  287. * request, but they must call the base class implementation in order to
  288. * generate and dispatch the appropriate events.
  289. */
  290. void QxtHttpSessionManager::incomingRequest(quint32 requestID, const QHttpRequestHeader& header, QxtWebContent* content)
  291. {
  292. QMultiHash<QString, QString> cookies;
  293. foreach(const QString& cookie, header.allValues("cookie")) // QHttpHeader is case-insensitive, thankfully
  294. {
  295. foreach(const QString& kv, cookie.split("; "))
  296. {
  297. int pos = kv.indexOf('=');
  298. if (pos == -1) continue;
  299. cookies.insert(kv.left(pos), kv.mid(pos + 1));
  300. }
  301. }
  302. int sessionID;
  303. QString sessionCookie = cookies.value(qxt_d().sessionCookieName);
  304. qxt_d().sessionLock.lock();
  305. if (qxt_d().sessionKeys.contains(sessionCookie))
  306. {
  307. sessionID = qxt_d().sessionKeys[sessionCookie];
  308. }
  309. else if (header.majorVersion() > 0 && qxt_d().autoCreateSession)
  310. {
  311. sessionID = newSession();
  312. }
  313. else
  314. {
  315. sessionID = 0;
  316. }
  317. QIODevice* device = connector()->getRequestConnection(requestID);
  318. QxtHttpSessionManagerPrivate::ConnectionState& state = qxt_d().connectionState[device];
  319. state.sessionID = sessionID;
  320. state.httpMajorVersion = header.majorVersion();
  321. state.httpMinorVersion = header.minorVersion();
  322. if (state.httpMajorVersion == 0 || (state.httpMajorVersion == 1 && state.httpMinorVersion == 0) || header.value("connection").toLower() == "close")
  323. state.keepAlive = false;
  324. else
  325. state.keepAlive = true;
  326. qxt_d().sessionLock.unlock();
  327. QxtWebRequestEvent* event = new QxtWebRequestEvent(sessionID, requestID, QUrl(header.path()));
  328. QTcpSocket* socket = qobject_cast<QTcpSocket*>(device);
  329. if (socket)
  330. {
  331. event->remoteAddress = socket->peerAddress().toString();
  332. }
  333. event->method = header.method();
  334. event->cookies = cookies;
  335. event->url.setScheme("http");
  336. if (event->url.host().isEmpty())
  337. event->url.setHost(header.value("host"));
  338. if (event->url.port() == -1)
  339. event->url.setPort(port());
  340. event->contentType = header.contentType();
  341. event->content = content;
  342. typedef QPair<QString, QString> StringPair;
  343. foreach(const StringPair& line, header.values())
  344. {
  345. if (line.first.toLower() == "cookie") continue;
  346. event->headers.insert(line.first, line.second);
  347. }
  348. event->headers.insert("X-Request-Protocol", "HTTP/" + QString::number(state.httpMajorVersion) + '.' + QString::number(state.httpMinorVersion));
  349. if (sessionID && session(sessionID))
  350. {
  351. session(sessionID)->pageRequestedEvent(event);
  352. }
  353. else if (qxt_d().staticService)
  354. {
  355. qxt_d().staticService->pageRequestedEvent(event);
  356. }
  357. else
  358. {
  359. postEvent(new QxtWebErrorEvent(0, requestID, 500, "Internal Configuration Error"));
  360. }
  361. }
  362. /*!
  363. * \internal
  364. */
  365. void QxtHttpSessionManager::disconnected(QIODevice* device)
  366. {
  367. QMutexLocker locker(&qxt_d().sessionLock);
  368. if (qxt_d().connectionState.contains(device))
  369. delete qxt_d().connectionState[device].onBytesWritten;
  370. qxt_d().connectionState.remove(device);
  371. }
  372. /*!
  373. * \reimp
  374. */
  375. void QxtHttpSessionManager::processEvents()
  376. {
  377. if (QThread::currentThreadId() != qxt_d().mainThread)
  378. {
  379. QMetaObject::invokeMethod(this, "processEvents", Qt::QueuedConnection);
  380. return;
  381. }
  382. QxtHttpSessionManagerPrivate& d = qxt_d();
  383. QMutexLocker locker(&d.eventLock);
  384. if (!d.eventQueue.count()) return;
  385. int ct = d.eventQueue.count(), sessionID = 0, requestID = 0, pagePos = -1;
  386. QxtWebRedirectEvent* re = 0;
  387. QxtWebPageEvent* pe = 0;
  388. for (int i = 0; i < ct; i++)
  389. {
  390. if (d.eventQueue[i]->type() != QxtWebEvent::Page && d.eventQueue[i]->type() != QxtWebEvent::Redirect) continue;
  391. pagePos = i;
  392. sessionID = d.eventQueue[i]->sessionID;
  393. if (d.eventQueue[pagePos]->type() == QxtWebEvent::Redirect)
  394. {
  395. re = static_cast<QxtWebRedirectEvent*>(d.eventQueue[pagePos]);
  396. }
  397. pe = static_cast<QxtWebPageEvent*>(d.eventQueue[pagePos]);
  398. requestID = pe->requestID;
  399. break;
  400. }
  401. if (pagePos == -1) return; // no pages to send yet
  402. QHttpResponseHeader header;
  403. QList<int> removeIDs;
  404. QxtWebEvent* e = 0;
  405. for (int i = 0; i < pagePos; i++)
  406. {
  407. if (d.eventQueue[i]->sessionID != sessionID) continue;
  408. e = d.eventQueue[i];
  409. if (e->type() == QxtWebEvent::StoreCookie)
  410. {
  411. QxtWebStoreCookieEvent* ce = static_cast<QxtWebStoreCookieEvent*>(e);
  412. QString cookie = ce->name + '=' + ce->data;
  413. if (ce->expiration.isValid())
  414. {
  415. cookie += "; max-age=" + QString::number(QDateTime::currentDateTime().secsTo(ce->expiration))
  416. + "; expires=" + ce->expiration.toUTC().toString("ddd, dd-MMM-YYYY hh:mm:ss GMT");
  417. }
  418. header.addValue("set-cookie", cookie);
  419. removeIDs.push_front(i);
  420. }
  421. else if (e->type() == QxtWebEvent::RemoveCookie)
  422. {
  423. QxtWebRemoveCookieEvent* ce = static_cast<QxtWebRemoveCookieEvent*>(e);
  424. header.addValue("set-cookie", ce->name + "=; max-age=0; expires=" + QDateTime(QDate(1970, 1, 1)).toString("ddd, dd-MMM-YYYY hh:mm:ss GMT"));
  425. removeIDs.push_front(i);
  426. }
  427. }
  428. removeIDs.push_front(pagePos);
  429. QIODevice* device = connector()->getRequestConnection(requestID);
  430. QxtWebContent* content = qobject_cast<QxtWebContent*>(device);
  431. // TODO: This should only be invoked when pipelining occurs
  432. // In theory it shouldn't cause any problems as POST is specced to not be pipelined
  433. if (content) content->ignoreRemainingContent();
  434. QxtHttpSessionManagerPrivate::ConnectionState& state = qxt_d().connectionState[connector()->getRequestConnection(requestID)];
  435. header.setStatusLine(pe->status, pe->statusMessage, state.httpMajorVersion, state.httpMinorVersion);
  436. if (re)
  437. {
  438. header.setValue("location", re->destination);
  439. }
  440. // Set custom header values
  441. for (QMultiHash<QString, QString>::iterator it = pe->headers.begin(); it != pe->headers.end(); ++it)
  442. {
  443. header.setValue(it.key(), it.value());
  444. }
  445. header.setContentType(pe->contentType);
  446. if (state.httpMajorVersion == 0 || (state.httpMajorVersion == 1 && state.httpMinorVersion == 0))
  447. pe->chunked = false;
  448. connector()->setRequestDataSource( pe->requestID, pe->dataSource );
  449. QSharedPointer<QIODevice> source( pe->dataSource );
  450. state.finishedTransfer = false;
  451. bool emptyContent = !source->bytesAvailable() && !pe->streaming;
  452. state.readyRead = source->bytesAvailable();
  453. state.streaming = pe->streaming;
  454. if (emptyContent)
  455. {
  456. header.setValue("connection", "close");
  457. connector()->writeHeaders(device, header);
  458. closeConnection(requestID);
  459. }
  460. else
  461. {
  462. if (state.onBytesWritten) delete state.onBytesWritten; // disconnect old handler
  463. if (!pe->chunked)
  464. {
  465. state.keepAlive = false;
  466. state.onBytesWritten = QxtMetaObject::bind(this, SLOT(sendNextBlock(int)),
  467. Q_ARG(int, requestID));
  468. QxtMetaObject::connect(source.data(), SIGNAL(readyRead()),
  469. QxtMetaObject::bind(this, SLOT(blockReadyRead(int)),
  470. Q_ARG(int, requestID)),
  471. Qt::QueuedConnection);
  472. QxtMetaObject::connect(source.data(), SIGNAL(aboutToClose()),
  473. QxtMetaObject::bind(this, SLOT(closeConnection(int)),
  474. Q_ARG(int, requestID)),
  475. Qt::QueuedConnection);
  476. }
  477. else
  478. {
  479. header.setValue("transfer-encoding", "chunked");
  480. state.onBytesWritten = QxtMetaObject::bind(this, SLOT(sendNextChunk(int)),
  481. Q_ARG(int, requestID));
  482. QxtMetaObject::connect(source.data(), SIGNAL(readyRead()),
  483. QxtMetaObject::bind(this, SLOT(chunkReadyRead(int)),
  484. Q_ARG(int, requestID)),
  485. Qt::QueuedConnection);
  486. QxtMetaObject::connect(source.data(), SIGNAL(aboutToClose()),
  487. QxtMetaObject::bind(this, SLOT(sendEmptyChunk(int)),
  488. Q_ARG(int, requestID)),
  489. Qt::QueuedConnection);
  490. }
  491. QxtMetaObject::connect(device, SIGNAL(bytesWritten(qint64)), state.onBytesWritten, Qt::QueuedConnection);
  492. if (state.keepAlive)
  493. {
  494. header.setValue("connection", "keep-alive");
  495. }
  496. else
  497. {
  498. header.setValue("connection", "close");
  499. }
  500. connector()->writeHeaders(device, header);
  501. if (state.readyRead)
  502. {
  503. if (pe->chunked)
  504. sendNextChunk(requestID);
  505. else
  506. sendNextBlock(requestID);
  507. }
  508. }
  509. foreach(int id, removeIDs)
  510. {
  511. delete d.eventQueue.takeAt(id);
  512. }
  513. if (d.eventQueue.count())
  514. QMetaObject::invokeMethod(this, "processEvents", Qt::QueuedConnection);
  515. }
  516. /*!
  517. * \internal
  518. */
  519. void QxtHttpSessionManager::chunkReadyRead(int requestID)
  520. {
  521. if (!connector()) return;
  522. const QSharedPointer<QIODevice>& dataSource = connector()->getRequestDataSource( requestID );
  523. if (!dataSource->bytesAvailable()) return;
  524. QIODevice* device = connector()->getRequestConnection(requestID);
  525. if (!device) return;
  526. if (!device->bytesToWrite() || qxt_d().connectionState[device].readyRead == false)
  527. {
  528. qxt_d().connectionState[device].readyRead = true;
  529. sendNextChunk(requestID);
  530. }
  531. }
  532. /*!
  533. * \internal
  534. */
  535. void QxtHttpSessionManager::sendNextChunk(int requestID)
  536. {
  537. if ( !connector() )
  538. return;
  539. const QSharedPointer<QIODevice>& dataSource = connector()->getRequestDataSource( requestID );
  540. QIODevice* device = connector()->getRequestConnection(requestID);
  541. QxtHttpSessionManagerPrivate::ConnectionState& state = qxt_d().connectionState[device];
  542. if (state.finishedTransfer)
  543. {
  544. // This is just the last block written; we're done with it
  545. return;
  546. }
  547. if (!dataSource->bytesAvailable())
  548. {
  549. state.readyRead = false;
  550. return;
  551. }
  552. QByteArray chunk = dataSource->read(32768); // this is a good chunk size
  553. if (chunk.size())
  554. {
  555. QByteArray data = QString::number(chunk.size(), 16).toUtf8() + "\r\n" + chunk + "\r\n";
  556. device->write(data);
  557. }
  558. state.readyRead = false;
  559. if (!state.streaming && !dataSource->bytesAvailable())
  560. QMetaObject::invokeMethod(this, "sendEmptyChunk", Q_ARG(int, requestID));
  561. }
  562. /*!
  563. * \internal
  564. */
  565. void QxtHttpSessionManager::sendEmptyChunk(int requestID)
  566. {
  567. QIODevice* device = connector()->getRequestConnection(requestID);
  568. if (!qxt_d().connectionState.contains(device)) return; // in case a disconnect signal and a bytesWritten signal get fired in the wrong order
  569. QxtHttpSessionManagerPrivate::ConnectionState& state = qxt_d().connectionState[device];
  570. if (state.finishedTransfer) return;
  571. state.finishedTransfer = true;
  572. device->write("0\r\n\r\n");
  573. if (state.keepAlive)
  574. {
  575. delete state.onBytesWritten;
  576. state.onBytesWritten = 0;
  577. QSharedPointer<QIODevice>& dataSource = connector()->getRequestDataSource( requestID );
  578. dataSource.clear();
  579. connector()->incomingData(device);
  580. }
  581. else
  582. {
  583. closeConnection(requestID);
  584. }
  585. }
  586. /*!
  587. * \internal
  588. */
  589. void QxtHttpSessionManager::closeConnection(int requestID)
  590. {
  591. QIODevice* device = connector()->getRequestConnection(requestID);
  592. if( !device ) return; // already closing/closed
  593. QxtHttpSessionManagerPrivate::ConnectionState& state = qxt_d().connectionState[device];
  594. state.finishedTransfer = true;
  595. state.onBytesWritten = NULL;
  596. QTcpSocket* socket = qobject_cast<QTcpSocket*>(device);
  597. if (socket)
  598. socket->disconnectFromHost();
  599. else
  600. device->close();
  601. connector()->doneWithRequest( requestID );
  602. }
  603. /*!
  604. * \internal
  605. */
  606. void QxtHttpSessionManager::blockReadyRead(int requestID)
  607. {
  608. const QSharedPointer<QIODevice>& dataSource = connector()->getRequestDataSource( requestID );
  609. if (!dataSource->bytesAvailable()) return;
  610. QIODevice* device = connector()->getRequestConnection(requestID);
  611. if (!device->bytesToWrite() || qxt_d().connectionState[device].readyRead == false)
  612. {
  613. qxt_d().connectionState[device].readyRead = true;
  614. sendNextBlock(requestID);
  615. }
  616. }
  617. /*!
  618. * \internal
  619. */
  620. void QxtHttpSessionManager::sendNextBlock(int requestID)
  621. {
  622. QSharedPointer<QIODevice>& dataSource = connector()->getRequestDataSource( requestID );
  623. QIODevice* device = connector()->getRequestConnection(requestID);
  624. if (!device)
  625. return;
  626. if (!qxt_d().connectionState.contains(device)) return; // in case a disconnect signal and a bytesWritten signal get fired in the wrong order
  627. QxtHttpSessionManagerPrivate::ConnectionState& state = qxt_d().connectionState[device];
  628. if (state.finishedTransfer) return;
  629. if (!dataSource->bytesAvailable())
  630. {
  631. state.readyRead = false;
  632. return;
  633. }
  634. QByteArray chunk = dataSource->read(32768); // this is a good chunk size
  635. device->write(chunk);
  636. state.readyRead = false;
  637. if (!state.streaming && !dataSource->bytesAvailable())
  638. {
  639. closeConnection(requestID);
  640. }
  641. }