source: trunk/src/network/access/qhttpnetworkconnectionchannel.cpp@ 561

Last change on this file since 561 was 561, checked in by Dmitry A. Kuminov, 15 years ago

trunk: Merged in qt 4.6.1 sources.

  • Property svn:eol-style set to native
File size: 35.5 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
4** All rights reserved.
5** Contact: Nokia Corporation ([email protected])
6**
7** This file is part of the QtNetwork module of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:LGPL$
10** Commercial Usage
11** Licensees holding valid Qt Commercial licenses may use this file in
12** accordance with the Qt Commercial License Agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and Nokia.
15**
16** GNU Lesser General Public License Usage
17** Alternatively, this file may be used under the terms of the GNU Lesser
18** General Public License version 2.1 as published by the Free Software
19** Foundation and appearing in the file LICENSE.LGPL included in the
20** packaging of this file. Please review the following information to
21** ensure the GNU Lesser General Public License version 2.1 requirements
22** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23**
24** In addition, as a special exception, Nokia gives you certain additional
25** rights. These rights are described in the Nokia Qt LGPL Exception
26** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27**
28** GNU General Public License Usage
29** Alternatively, this file may be used under the terms of the GNU
30** General Public License version 3.0 as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL included in the
32** packaging of this file. Please review the following information to
33** ensure the GNU General Public License version 3.0 requirements will be
34** met: http://www.gnu.org/copyleft/gpl.html.
35**
36** If you have questions regarding the use of this file, please contact
37** Nokia at [email protected].
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42#include "qhttpnetworkconnection_p.h"
43#include "qhttpnetworkconnectionchannel_p.h"
44#include "private/qnoncontiguousbytedevice_p.h"
45
46#include <qpair.h>
47#include <qdebug.h>
48
49#ifndef QT_NO_HTTP
50
51#ifndef QT_NO_OPENSSL
52# include <QtNetwork/qsslkey.h>
53# include <QtNetwork/qsslcipher.h>
54# include <QtNetwork/qsslconfiguration.h>
55#endif
56
57QT_BEGIN_NAMESPACE
58
59// TODO: Put channel specific stuff here so it does not polute qhttpnetworkconnection.cpp
60
61void QHttpNetworkConnectionChannel::init()
62{
63#ifndef QT_NO_OPENSSL
64 if (connection->d_func()->encrypt)
65 socket = new QSslSocket;
66 else
67 socket = new QTcpSocket;
68#else
69 socket = new QTcpSocket;
70#endif
71
72 // limit the socket read buffer size. we will read everything into
73 // the QHttpNetworkReply anyway, so let's grow only that and not
74 // here and there.
75 socket->setReadBufferSize(64*1024);
76
77 QObject::connect(socket, SIGNAL(bytesWritten(qint64)),
78 this, SLOT(_q_bytesWritten(qint64)),
79 Qt::DirectConnection);
80 QObject::connect(socket, SIGNAL(connected()),
81 this, SLOT(_q_connected()),
82 Qt::DirectConnection);
83 QObject::connect(socket, SIGNAL(readyRead()),
84 this, SLOT(_q_readyRead()),
85 Qt::DirectConnection);
86 QObject::connect(socket, SIGNAL(disconnected()),
87 this, SLOT(_q_disconnected()),
88 Qt::DirectConnection);
89 QObject::connect(socket, SIGNAL(error(QAbstractSocket::SocketError)),
90 this, SLOT(_q_error(QAbstractSocket::SocketError)),
91 Qt::DirectConnection);
92#ifndef QT_NO_NETWORKPROXY
93 QObject::connect(socket, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)),
94 this, SLOT(_q_proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)),
95 Qt::DirectConnection);
96#endif
97
98#ifndef QT_NO_OPENSSL
99 QSslSocket *sslSocket = qobject_cast<QSslSocket*>(socket);
100 if (sslSocket) {
101 // won't be a sslSocket if encrypt is false
102 QObject::connect(sslSocket, SIGNAL(encrypted()),
103 this, SLOT(_q_encrypted()),
104 Qt::DirectConnection);
105 QObject::connect(sslSocket, SIGNAL(sslErrors(QList<QSslError>)),
106 this, SLOT(_q_sslErrors(QList<QSslError>)),
107 Qt::DirectConnection);
108 QObject::connect(sslSocket, SIGNAL(encryptedBytesWritten(qint64)),
109 this, SLOT(_q_encryptedBytesWritten(qint64)),
110 Qt::DirectConnection);
111 }
112#endif
113}
114
115
116void QHttpNetworkConnectionChannel::close()
117{
118 socket->blockSignals(true);
119 socket->close();
120 socket->blockSignals(false);
121 state = QHttpNetworkConnectionChannel::IdleState;
122}
123
124
125bool QHttpNetworkConnectionChannel::sendRequest()
126{
127 switch (state) {
128 case QHttpNetworkConnectionChannel::IdleState: { // write the header
129 if (!ensureConnection()) {
130 // wait for the connection (and encryption) to be done
131 // sendRequest will be called again from either
132 // _q_connected or _q_encrypted
133 return false;
134 }
135 written = 0; // excluding the header
136 bytesTotal = 0;
137 if (reply) {
138 reply->d_func()->clear();
139 reply->d_func()->connection = connection;
140 reply->d_func()->autoDecompress = request.d->autoDecompress;
141 reply->d_func()->pipeliningUsed = false;
142 }
143 state = QHttpNetworkConnectionChannel::WritingState;
144 pendingEncrypt = false;
145 // if the url contains authentication parameters, use the new ones
146 // both channels will use the new authentication parameters
147 if (!request.url().userInfo().isEmpty()) {
148 QUrl url = request.url();
149 QAuthenticator &auth = authenticator;
150 if (url.userName() != auth.user()
151 || (!url.password().isEmpty() && url.password() != auth.password())) {
152 auth.setUser(url.userName());
153 auth.setPassword(url.password());
154 connection->d_func()->copyCredentials(connection->d_func()->indexOf(socket), &auth, false);
155 }
156 // clear the userinfo, since we use the same request for resending
157 // userinfo in url can conflict with the one in the authenticator
158 url.setUserInfo(QString());
159 request.setUrl(url);
160 }
161 connection->d_func()->createAuthorization(socket, request);
162#ifndef QT_NO_NETWORKPROXY
163 QByteArray header = QHttpNetworkRequestPrivate::header(request,
164 (connection->d_func()->networkProxy.type() != QNetworkProxy::NoProxy));
165#else
166 QByteArray header = QHttpNetworkRequestPrivate::header(request, false);
167#endif
168 socket->write(header);
169 QNonContiguousByteDevice* uploadByteDevice = request.uploadByteDevice();
170 if (uploadByteDevice) {
171 // connect the signals so this function gets called again
172 QObject::connect(uploadByteDevice, SIGNAL(readyRead()),this, SLOT(_q_uploadDataReadyRead()));
173
174 bytesTotal = request.contentLength();
175 } else {
176 state = QHttpNetworkConnectionChannel::WaitingState;
177 sendRequest();
178 break;
179 }
180 // write the initial chunk together with the headers
181 // fall through
182 }
183 case QHttpNetworkConnectionChannel::WritingState:
184 {
185 // write the data
186 QNonContiguousByteDevice* uploadByteDevice = request.uploadByteDevice();
187 if (!uploadByteDevice || bytesTotal == written) {
188 if (uploadByteDevice)
189 emit reply->dataSendProgress(written, bytesTotal);
190 state = QHttpNetworkConnectionChannel::WaitingState; // now wait for response
191 sendRequest();
192 break;
193 }
194
195 // only feed the QTcpSocket buffer when there is less than 32 kB in it
196 const qint64 socketBufferFill = 32*1024;
197 const qint64 socketWriteMaxSize = 16*1024;
198
199
200#ifndef QT_NO_OPENSSL
201 QSslSocket *sslSocket = qobject_cast<QSslSocket*>(socket);
202 // if it is really an ssl socket, check more than just bytesToWrite()
203 while ((socket->bytesToWrite() + (sslSocket ? sslSocket->encryptedBytesToWrite() : 0))
204 <= socketBufferFill && bytesTotal != written)
205#else
206 while (socket->bytesToWrite() <= socketBufferFill
207 && bytesTotal != written)
208#endif
209 {
210 // get pointer to upload data
211 qint64 currentReadSize;
212 qint64 desiredReadSize = qMin(socketWriteMaxSize, bytesTotal - written);
213 const char *readPointer = uploadByteDevice->readPointer(desiredReadSize, currentReadSize);
214
215 if (currentReadSize == -1) {
216 // premature eof happened
217 connection->d_func()->emitReplyError(socket, reply, QNetworkReply::UnknownNetworkError);
218 return false;
219 break;
220 } else if (readPointer == 0 || currentReadSize == 0) {
221 // nothing to read currently, break the loop
222 break;
223 } else {
224 qint64 currentWriteSize = socket->write(readPointer, currentReadSize);
225 if (currentWriteSize == -1 || currentWriteSize != currentReadSize) {
226 // socket broke down
227 connection->d_func()->emitReplyError(socket, reply, QNetworkReply::UnknownNetworkError);
228 return false;
229 } else {
230 written += currentWriteSize;
231 uploadByteDevice->advanceReadPointer(currentWriteSize);
232
233 emit reply->dataSendProgress(written, bytesTotal);
234
235 if (written == bytesTotal) {
236 // make sure this function is called once again
237 state = QHttpNetworkConnectionChannel::WaitingState;
238 sendRequest();
239 break;
240 }
241 }
242 }
243 }
244 break;
245 }
246
247 case QHttpNetworkConnectionChannel::WaitingState:
248 {
249 QNonContiguousByteDevice* uploadByteDevice = request.uploadByteDevice();
250 if (uploadByteDevice) {
251 QObject::disconnect(uploadByteDevice, SIGNAL(readyRead()), this, SLOT(_q_uploadDataReadyRead()));
252 }
253
254 // HTTP pipelining
255 connection->d_func()->fillPipeline(socket);
256 socket->flush();
257
258 // ensure we try to receive a reply in all cases, even if _q_readyRead_ hat not been called
259 // this is needed if the sends an reply before we have finished sending the request. In that
260 // case receiveReply had been called before but ignored the server reply
261 receiveReply();
262 break;
263 }
264 case QHttpNetworkConnectionChannel::ReadingState:
265 case QHttpNetworkConnectionChannel::Wait4AuthState:
266 // ignore _q_bytesWritten in these states
267 // fall through
268 default:
269 break;
270 }
271 return true;
272}
273
274
275void QHttpNetworkConnectionChannel::receiveReply()
276{
277 Q_ASSERT(socket);
278
279 qint64 bytes = 0;
280 QAbstractSocket::SocketState socketState = socket->state();
281
282 // connection might be closed to signal the end of data
283 if (socketState == QAbstractSocket::UnconnectedState) {
284 if (!socket->bytesAvailable()) {
285 if (reply && reply->d_func()->state == QHttpNetworkReplyPrivate::ReadingDataState) {
286 reply->d_func()->state = QHttpNetworkReplyPrivate::AllDoneState;
287 this->state = QHttpNetworkConnectionChannel::IdleState;
288 allDone();
289 } else {
290 // try to reconnect/resend before sending an error.
291 if (reconnectAttempts-- > 0) {
292 closeAndResendCurrentRequest();
293 } else if (reply) {
294 reply->d_func()->errorString = connection->d_func()->errorDetail(QNetworkReply::RemoteHostClosedError, socket);
295 emit reply->finishedWithError(QNetworkReply::RemoteHostClosedError, reply->d_func()->errorString);
296 QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection);
297 }
298 }
299 }
300 }
301
302 // read loop for the response
303 while (socket->bytesAvailable()) {
304 QHttpNetworkReplyPrivate::ReplyState state = reply ? reply->d_func()->state : QHttpNetworkReplyPrivate::AllDoneState;
305 switch (state) {
306 case QHttpNetworkReplyPrivate::NothingDoneState:
307 case QHttpNetworkReplyPrivate::ReadingStatusState: {
308 eatWhitespace();
309 qint64 statusBytes = reply->d_func()->readStatus(socket);
310 if (statusBytes == -1 && reconnectAttempts <= 0) {
311 // too many errors reading/receiving/parsing the status, close the socket and emit error
312 close();
313 reply->d_func()->errorString = connection->d_func()->errorDetail(QNetworkReply::ProtocolFailure, socket);
314 emit reply->finishedWithError(QNetworkReply::ProtocolFailure, reply->d_func()->errorString);
315 QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection);
316 break;
317 } else if (statusBytes == -1) {
318 reconnectAttempts--;
319 reply->d_func()->clear();
320 closeAndResendCurrentRequest();
321 break;
322 }
323 bytes += statusBytes;
324 lastStatus = reply->d_func()->statusCode;
325 break;
326 }
327 case QHttpNetworkReplyPrivate::ReadingHeaderState:
328 bytes += reply->d_func()->readHeader(socket);
329 if (reply->d_func()->state == QHttpNetworkReplyPrivate::ReadingDataState) {
330 if (reply->d_func()->isGzipped() && reply->d_func()->autoDecompress) {
331 // remove the Content-Length from header
332 reply->d_func()->removeAutoDecompressHeader();
333 } else {
334 reply->d_func()->autoDecompress = false;
335 }
336 if (reply && reply->d_func()->statusCode == 100) {
337 reply->d_func()->state = QHttpNetworkReplyPrivate::ReadingStatusState;
338 break; // ignore
339 }
340 if (reply->d_func()->shouldEmitSignals())
341 emit reply->headerChanged();
342 if (!reply->d_func()->expectContent()) {
343 reply->d_func()->state = QHttpNetworkReplyPrivate::AllDoneState;
344 this->state = QHttpNetworkConnectionChannel::IdleState;
345 allDone();
346 return;
347 }
348 }
349 break;
350 case QHttpNetworkReplyPrivate::ReadingDataState: {
351 if (!reply->d_func()->isChunked() && !reply->d_func()->autoDecompress
352 && reply->d_func()->bodyLength > 0) {
353 // bulk files like images should fulfill these properties and
354 // we can therefore save on memory copying
355 bytes = reply->d_func()->readBodyFast(socket, &reply->d_func()->responseData);
356 reply->d_func()->totalProgress += bytes;
357 if (reply->d_func()->shouldEmitSignals()) {
358 QPointer<QHttpNetworkReply> replyPointer = reply;
359 emit reply->readyRead();
360 // make sure that the reply is valid
361 if (replyPointer.isNull())
362 return;
363 emit reply->dataReadProgress(reply->d_func()->totalProgress, reply->d_func()->bodyLength);
364 // make sure that the reply is valid
365 if (replyPointer.isNull())
366 return;
367 }
368 }
369 else
370 {
371 // use the traditional slower reading (for compressed encoding, chunked encoding,
372 // no content-length etc)
373 QByteDataBuffer byteDatas;
374 bytes = reply->d_func()->readBody(socket, &byteDatas);
375 if (bytes) {
376 if (reply->d_func()->autoDecompress)
377 reply->d_func()->appendCompressedReplyData(byteDatas);
378 else
379 reply->d_func()->appendUncompressedReplyData(byteDatas);
380
381 if (!reply->d_func()->autoDecompress) {
382 reply->d_func()->totalProgress += bytes;
383 if (reply->d_func()->shouldEmitSignals()) {
384 QPointer<QHttpNetworkReply> replyPointer = reply;
385 // important: At the point of this readyRead(), the byteDatas list must be empty,
386 // else implicit sharing will trigger memcpy when the user is reading data!
387 emit reply->readyRead();
388 // make sure that the reply is valid
389 if (replyPointer.isNull())
390 return;
391 emit reply->dataReadProgress(reply->d_func()->totalProgress, reply->d_func()->bodyLength);
392 // make sure that the reply is valid
393 if (replyPointer.isNull())
394 return;
395 }
396 }
397#ifndef QT_NO_COMPRESS
398 else if (!expand(false)) { // expand a chunk if possible
399 return; // ### expand failed
400 }
401#endif
402 }
403 }
404 if (reply->d_func()->state == QHttpNetworkReplyPrivate::ReadingDataState)
405 break;
406 // everything done, fall through
407 }
408 case QHttpNetworkReplyPrivate::AllDoneState:
409 this->state = QHttpNetworkConnectionChannel::IdleState;
410 allDone();
411 break;
412 default:
413 break;
414 }
415 }
416}
417
418bool QHttpNetworkConnectionChannel::ensureConnection()
419{
420 // make sure that this socket is in a connected state, if not initiate
421 // connection to the host.
422 if (socket->state() != QAbstractSocket::ConnectedState) {
423 // connect to the host if not already connected.
424 // resend this request after we receive the disconnected signal
425 if (socket->state() == QAbstractSocket::ClosingState) {
426 resendCurrent = true;
427 return false;
428 }
429 state = QHttpNetworkConnectionChannel::ConnectingState;
430 pendingEncrypt = connection->d_func()->encrypt;
431
432 // reset state
433 pipeliningSupported = PipeliningSupportUnknown;
434
435 // This workaround is needed since we use QAuthenticator for NTLM authentication. The "phase == Done"
436 // is the usual criteria for emitting authentication signals. The "phase" is set to "Done" when the
437 // last header for Authorization is generated by the QAuthenticator. Basic & Digest logic does not
438 // check the "phase" for generating the Authorization header. NTLM authentication is a two stage
439 // process & needs the "phase". To make sure the QAuthenticator uses the current username/password
440 // the phase is reset to Start.
441 QAuthenticatorPrivate *priv = QAuthenticatorPrivate::getPrivate(authenticator);
442 if (priv && priv->phase == QAuthenticatorPrivate::Done)
443 priv->phase = QAuthenticatorPrivate::Start;
444 priv = QAuthenticatorPrivate::getPrivate(proxyAuthenticator);
445 if (priv && priv->phase == QAuthenticatorPrivate::Done)
446 priv->phase = QAuthenticatorPrivate::Start;
447
448 QString connectHost = connection->d_func()->hostName;
449 qint16 connectPort = connection->d_func()->port;
450
451#ifndef QT_NO_NETWORKPROXY
452 // HTTPS always use transparent proxy.
453 if (connection->d_func()->networkProxy.type() != QNetworkProxy::NoProxy && !connection->d_func()->encrypt) {
454 connectHost = connection->d_func()->networkProxy.hostName();
455 connectPort = connection->d_func()->networkProxy.port();
456 }
457#endif
458 if (connection->d_func()->encrypt) {
459#ifndef QT_NO_OPENSSL
460 QSslSocket *sslSocket = qobject_cast<QSslSocket*>(socket);
461 sslSocket->connectToHostEncrypted(connectHost, connectPort);
462 if (ignoreAllSslErrors)
463 sslSocket->ignoreSslErrors();
464 sslSocket->ignoreSslErrors(ignoreSslErrorsList);
465#else
466 connection->d_func()->emitReplyError(socket, reply, QNetworkReply::ProtocolUnknownError);
467#endif
468 } else {
469 socket->connectToHost(connectHost, connectPort);
470 }
471 return false;
472 }
473 return true;
474}
475
476
477#ifndef QT_NO_COMPRESS
478bool QHttpNetworkConnectionChannel::expand(bool dataComplete)
479{
480 Q_ASSERT(socket);
481 Q_ASSERT(reply);
482
483 qint64 total = reply->d_func()->compressedData.size();
484 if (total >= CHUNK || dataComplete) {
485 // uncompress the data
486 QByteArray content, inflated;
487 content = reply->d_func()->compressedData;
488 reply->d_func()->compressedData.clear();
489
490 int ret = Z_OK;
491 if (content.size())
492 ret = reply->d_func()->gunzipBodyPartially(content, inflated);
493 int retCheck = (dataComplete) ? Z_STREAM_END : Z_OK;
494 if (ret >= retCheck) {
495 if (inflated.size()) {
496 reply->d_func()->totalProgress += inflated.size();
497 reply->d_func()->appendUncompressedReplyData(inflated);
498 if (reply->d_func()->shouldEmitSignals()) {
499 QPointer<QHttpNetworkReply> replyPointer = reply;
500 // important: At the point of this readyRead(), inflated must be cleared,
501 // else implicit sharing will trigger memcpy when the user is reading data!
502 emit reply->readyRead();
503 // make sure that the reply is valid
504 if (replyPointer.isNull())
505 return true;
506 emit reply->dataReadProgress(reply->d_func()->totalProgress, 0);
507 // make sure that the reply is valid
508 if (replyPointer.isNull())
509 return true;
510
511 }
512 }
513 } else {
514 connection->d_func()->emitReplyError(socket, reply, QNetworkReply::ProtocolFailure);
515 return false;
516 }
517 }
518 return true;
519}
520#endif
521
522
523void QHttpNetworkConnectionChannel::allDone()
524{
525#ifndef QT_NO_COMPRESS
526 // expand the whole data.
527 if (reply->d_func()->expectContent() && reply->d_func()->autoDecompress && !reply->d_func()->streamEnd)
528 expand(true); // ### if expand returns false, its an error
529#endif
530 // while handling 401 & 407, we might reset the status code, so save this.
531 bool emitFinished = reply->d_func()->shouldEmitSignals();
532 handleStatus();
533 // ### at this point there should be no more data on the socket
534 // close if server requested
535 if (reply->d_func()->isConnectionCloseEnabled())
536 close();
537 // queue the finished signal, this is required since we might send new requests from
538 // slot connected to it. The socket will not fire readyRead signal, if we are already
539 // in the slot connected to readyRead
540 if (emitFinished)
541 QMetaObject::invokeMethod(reply, "finished", Qt::QueuedConnection);
542 // reset the reconnection attempts after we receive a complete reply.
543 // in case of failures, each channel will attempt two reconnects before emitting error.
544 reconnectAttempts = 2;
545
546 detectPipeliningSupport();
547
548 // move next from pipeline to current request
549 if (!alreadyPipelinedRequests.isEmpty()) {
550 if (resendCurrent || reply->d_func()->isConnectionCloseEnabled() || socket->state() != QAbstractSocket::ConnectedState) {
551 // move the pipelined ones back to the main queue
552 requeueCurrentlyPipelinedRequests();
553 close();
554 } else {
555 // there were requests pipelined in and we can continue
556 HttpMessagePair messagePair = alreadyPipelinedRequests.takeFirst();
557
558 request = messagePair.first;
559 reply = messagePair.second;
560 state = QHttpNetworkConnectionChannel::ReadingState;
561 resendCurrent = false;
562
563 written = 0; // message body, excluding the header, irrelevant here
564 bytesTotal = 0; // message body total, excluding the header, irrelevant here
565
566 // pipeline even more
567 connection->d_func()->fillPipeline(socket);
568
569 // continue reading
570 receiveReply();
571 }
572 } else if (alreadyPipelinedRequests.isEmpty() && socket->bytesAvailable() > 0) {
573 eatWhitespace();
574 // this is weird. we had nothing pipelined but still bytes available. better close it.
575 if (socket->bytesAvailable() > 0)
576 close();
577 QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection);
578 } else if (alreadyPipelinedRequests.isEmpty()) {
579 QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection);
580 }
581}
582
583void QHttpNetworkConnectionChannel::detectPipeliningSupport()
584{
585 // detect HTTP Pipelining support
586 QByteArray serverHeaderField = reply->headerField("Server");
587 if (
588 // check for broken servers in server reply header
589 // this is adapted from http://mxr.mozilla.org/firefox/ident?i=SupportsPipelining
590 (!serverHeaderField.contains("Microsoft-IIS/4."))
591 && (!serverHeaderField.contains("Microsoft-IIS/5."))
592 && (!serverHeaderField.contains("Netscape-Enterprise/3."))
593 // check for HTTP/1.1
594 && (reply->d_func()->majorVersion == 1 && reply->d_func()->minorVersion == 1)
595 // check for not having connection close
596 && (!reply->d_func()->isConnectionCloseEnabled())
597 // check if it is still connected
598 && (socket->state() == QAbstractSocket::ConnectedState)
599 ) {
600 pipeliningSupported = QHttpNetworkConnectionChannel::PipeliningProbablySupported;
601 } else {
602 pipeliningSupported = QHttpNetworkConnectionChannel::PipeliningSupportUnknown;
603 }
604}
605
606// called when the connection broke and we need to queue some pipelined requests again
607void QHttpNetworkConnectionChannel::requeueCurrentlyPipelinedRequests()
608{
609 for (int i = 0; i < alreadyPipelinedRequests.length(); i++)
610 connection->d_func()->requeueRequest(alreadyPipelinedRequests.at(i));
611 alreadyPipelinedRequests.clear();
612
613 QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection);
614}
615
616void QHttpNetworkConnectionChannel::eatWhitespace()
617{
618 char c;
619 while (socket->bytesAvailable()) {
620 if (socket->peek(&c, 1) != 1)
621 return;
622
623 // read all whitespace and line endings
624 if (c == 11 || c == '\n' || c == '\r' || c == ' ' || c == 31) {
625 socket->read(&c, 1);
626 continue;
627 } else {
628 break;
629 }
630 }
631}
632
633void QHttpNetworkConnectionChannel::handleStatus()
634{
635 Q_ASSERT(socket);
636 Q_ASSERT(reply);
637
638 int statusCode = reply->statusCode();
639 bool resend = false;
640
641 switch (statusCode) {
642 case 401: // auth required
643 case 407: // proxy auth required
644 if (connection->d_func()->handleAuthenticateChallenge(socket, reply, (statusCode == 407), resend)) {
645 if (resend) {
646 QNonContiguousByteDevice* uploadByteDevice = request.uploadByteDevice();
647 if (uploadByteDevice) {
648 if (uploadByteDevice->reset()) {
649 written = 0;
650 } else {
651 connection->d_func()->emitReplyError(socket, reply, QNetworkReply::ContentReSendError);
652 break;
653 }
654 }
655
656 reply->d_func()->eraseData();
657
658 if (alreadyPipelinedRequests.isEmpty()) {
659 // this does a re-send without closing the connection
660 resendCurrent = true;
661 QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection);
662 } else {
663 // we had requests pipelined.. better close the connection in closeAndResendCurrentRequest
664 closeAndResendCurrentRequest();
665 QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection);
666 }
667 }
668 } else {
669 emit reply->headerChanged();
670 emit reply->readyRead();
671 QNetworkReply::NetworkError errorCode = (statusCode == 407)
672 ? QNetworkReply::ProxyAuthenticationRequiredError
673 : QNetworkReply::AuthenticationRequiredError;
674 reply->d_func()->errorString = connection->d_func()->errorDetail(errorCode, socket);
675 emit connection->error(errorCode, reply->d_func()->errorString);
676 emit reply->finished();
677 }
678 break;
679 default:
680 QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection);
681 }
682}
683
684void QHttpNetworkConnectionChannel::pipelineInto(HttpMessagePair &pair)
685{
686 // this is only called for simple GET
687
688 QHttpNetworkRequest &request = pair.first;
689 QHttpNetworkReply *reply = pair.second;
690 if (reply) {
691 reply->d_func()->clear();
692 reply->d_func()->connection = connection;
693 reply->d_func()->autoDecompress = request.d->autoDecompress;
694 reply->d_func()->pipeliningUsed = true;
695 }
696
697#ifndef QT_NO_NETWORKPROXY
698 QByteArray header = QHttpNetworkRequestPrivate::header(request,
699 (connection->d_func()->networkProxy.type() != QNetworkProxy::NoProxy));
700#else
701 QByteArray header = QHttpNetworkRequestPrivate::header(request, false);
702#endif
703 socket->write(header);
704
705 alreadyPipelinedRequests.append(pair);
706}
707
708void QHttpNetworkConnectionChannel::closeAndResendCurrentRequest()
709{
710 requeueCurrentlyPipelinedRequests();
711 close();
712 resendCurrent = true;
713 QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection);
714}
715
716bool QHttpNetworkConnectionChannel::isSocketBusy() const
717{
718 return (state & QHttpNetworkConnectionChannel::BusyState);
719}
720
721bool QHttpNetworkConnectionChannel::isSocketWriting() const
722{
723 return (state & QHttpNetworkConnectionChannel::WritingState);
724}
725
726bool QHttpNetworkConnectionChannel::isSocketWaiting() const
727{
728 return (state & QHttpNetworkConnectionChannel::WaitingState);
729}
730
731bool QHttpNetworkConnectionChannel::isSocketReading() const
732{
733 return (state & QHttpNetworkConnectionChannel::ReadingState);
734}
735
736//private slots
737void QHttpNetworkConnectionChannel::_q_readyRead()
738{
739 if (isSocketWaiting() || isSocketReading()) {
740 state = QHttpNetworkConnectionChannel::ReadingState;
741 if (reply)
742 receiveReply();
743 }
744}
745
746void QHttpNetworkConnectionChannel::_q_bytesWritten(qint64 bytes)
747{
748 Q_UNUSED(bytes);
749 // bytes have been written to the socket. write even more of them :)
750 if (isSocketWriting())
751 sendRequest();
752 // otherwise we do nothing
753}
754
755void QHttpNetworkConnectionChannel::_q_disconnected()
756{
757 // read the available data before closing
758 if (isSocketWaiting() || isSocketReading()) {
759 state = QHttpNetworkConnectionChannel::ReadingState;
760 if (reply)
761 receiveReply();
762 } else if (state == QHttpNetworkConnectionChannel::IdleState && resendCurrent) {
763 // re-sending request because the socket was in ClosingState
764 QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection);
765 }
766 state = QHttpNetworkConnectionChannel::IdleState;
767
768 requeueCurrentlyPipelinedRequests();
769 close();
770}
771
772
773void QHttpNetworkConnectionChannel::_q_connected()
774{
775 // improve performance since we get the request sent by the kernel ASAP
776 socket->setSocketOption(QAbstractSocket::LowDelayOption, 1);
777 // not sure yet if it helps, but it makes sense
778 socket->setSocketOption(QAbstractSocket::KeepAliveOption, 1);
779
780 pipeliningSupported = QHttpNetworkConnectionChannel::PipeliningSupportUnknown;
781
782 // ### FIXME: if the server closes the connection unexpectedly, we shouldn't send the same broken request again!
783 //channels[i].reconnectAttempts = 2;
784 if (!pendingEncrypt) {
785 state = QHttpNetworkConnectionChannel::IdleState;
786 if (reply)
787 sendRequest();
788 else
789 close();
790 }
791}
792
793
794void QHttpNetworkConnectionChannel::_q_error(QAbstractSocket::SocketError socketError)
795{
796 if (!socket)
797 return;
798 bool send2Reply = false;
799 QNetworkReply::NetworkError errorCode = QNetworkReply::UnknownNetworkError;
800
801 switch (socketError) {
802 case QAbstractSocket::HostNotFoundError:
803 errorCode = QNetworkReply::HostNotFoundError;
804 break;
805 case QAbstractSocket::ConnectionRefusedError:
806 errorCode = QNetworkReply::ConnectionRefusedError;
807 break;
808 case QAbstractSocket::RemoteHostClosedError:
809 // try to reconnect/resend before sending an error.
810 // while "Reading" the _q_disconnected() will handle this.
811 if (state != QHttpNetworkConnectionChannel::IdleState && state != QHttpNetworkConnectionChannel::ReadingState) {
812 if (reconnectAttempts-- > 0) {
813 closeAndResendCurrentRequest();
814 return;
815 } else {
816 send2Reply = true;
817 errorCode = QNetworkReply::RemoteHostClosedError;
818 }
819 } else {
820 return;
821 }
822 break;
823 case QAbstractSocket::SocketTimeoutError:
824 // try to reconnect/resend before sending an error.
825 if (state == QHttpNetworkConnectionChannel::WritingState && (reconnectAttempts-- > 0)) {
826 closeAndResendCurrentRequest();
827 return;
828 }
829 send2Reply = true;
830 errorCode = QNetworkReply::TimeoutError;
831 break;
832 case QAbstractSocket::ProxyAuthenticationRequiredError:
833 errorCode = QNetworkReply::ProxyAuthenticationRequiredError;
834 break;
835 case QAbstractSocket::SslHandshakeFailedError:
836 errorCode = QNetworkReply::SslHandshakeFailedError;
837 break;
838 default:
839 // all other errors are treated as NetworkError
840 errorCode = QNetworkReply::UnknownNetworkError;
841 break;
842 }
843 QPointer<QObject> that = connection;
844 QString errorString = connection->d_func()->errorDetail(errorCode, socket);
845 if (send2Reply) {
846 if (reply) {
847 reply->d_func()->errorString = errorString;
848 // this error matters only to this reply
849 emit reply->finishedWithError(errorCode, errorString);
850 }
851 // send the next request
852 QMetaObject::invokeMethod(that, "_q_startNextRequest", Qt::QueuedConnection);
853 } else {
854 // the failure affects all requests.
855 emit connection->error(errorCode, errorString);
856 }
857 if (that) //signal emission triggered event loop
858 close();
859}
860
861#ifndef QT_NO_NETWORKPROXY
862void QHttpNetworkConnectionChannel::_q_proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator* auth)
863{
864 connection->d_func()->emitProxyAuthenticationRequired(this, proxy, auth);
865}
866#endif
867
868void QHttpNetworkConnectionChannel::_q_uploadDataReadyRead()
869{
870 sendRequest();
871}
872
873#ifndef QT_NO_OPENSSL
874void QHttpNetworkConnectionChannel::_q_encrypted()
875{
876 if (!socket)
877 return; // ### error
878 state = QHttpNetworkConnectionChannel::IdleState;
879 sendRequest();
880}
881
882void QHttpNetworkConnectionChannel::_q_sslErrors(const QList<QSslError> &errors)
883{
884 if (!socket)
885 return;
886 //QNetworkReply::NetworkError errorCode = QNetworkReply::ProtocolFailure;
887 emit connection->sslErrors(errors);
888}
889
890void QHttpNetworkConnectionChannel::_q_encryptedBytesWritten(qint64 bytes)
891{
892 Q_UNUSED(bytes);
893 // bytes have been written to the socket. write even more of them :)
894 if (isSocketWriting())
895 sendRequest();
896 // otherwise we do nothing
897}
898#endif
899
900QT_END_NAMESPACE
901
902#include "moc_qhttpnetworkconnectionchannel_p.cpp"
903
904#endif // QT_NO_HTTP
Note: See TracBrowser for help on using the repository browser.