PageRenderTime 1609ms CodeModel.GetById 101ms app.highlight 1264ms RepoModel.GetById 126ms app.codeStats 1ms

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