[network] use the new QNetworkReply method for speeding up loading
[qtwebkit:performance.git] / reductions / loading_standalone / QNetworkReplyHandler.cpp
1 /*
2     Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies)
3     Copyright (C) 2007 Staikos Computing Services Inc.  <info@staikos.net>
4     Copyright (C) 2008 Holger Hans Peter Freyther
5
6     This library is free software; you can redistribute it and/or
7     modify it under the terms of the GNU Library General Public
8     License as published by the Free Software Foundation; either
9     version 2 of the License, or (at your option) any later version.
10
11     This library is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14     Library General Public License for more details.
15
16     You should have received a copy of the GNU Library General Public License
17     along with this library; see the file COPYING.LIB.  If not, write to
18     the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19     Boston, MA 02110-1301, USA.
20 */
21 #include "QNetworkReplyHandler.h"
22
23 #include "WebCore_classes.h"
24 #include <QDateTime>
25 #include <QFile>
26 #include <QNetworkReply>
27 #include <QNetworkCookie>
28 #include <qwebframe.h>
29 #include <qwebpage.h>
30
31 #include <QDebug>
32 #include <QCoreApplication>
33
34 namespace WebCore {
35
36 QNetworkReplyHandler::QNetworkReplyHandler(ResourceHandle* handle, LoadMode loadMode)
37     : QObject(0)
38     , m_reply(0)
39     , m_resourceHandle(handle)
40     , m_redirected(false)
41     , m_responseSent(false)
42     , m_responseDataSent(false)
43     , m_loadMode(loadMode)
44     , m_shouldStart(true)
45     , m_shouldFinish(false)
46     , m_shouldSendResponse(false)
47     , m_shouldForwardData(false)
48 {
49     const ResourceRequest &r = m_resourceHandle->request();
50
51     if (r.httpMethod() == "GET")
52         m_method = QNetworkAccessManager::GetOperation;
53     else if (r.httpMethod() == "HEAD")
54         m_method = QNetworkAccessManager::HeadOperation;
55     else if (r.httpMethod() == "POST")
56         m_method = QNetworkAccessManager::PostOperation;
57     else if (r.httpMethod() == "PUT")
58         m_method = QNetworkAccessManager::PutOperation;
59     else
60         m_method = QNetworkAccessManager::UnknownOperation;
61
62     m_request = r.toNetworkRequest();
63
64     if (m_loadMode == LoadNormal)
65         start();
66 }
67
68 void QNetworkReplyHandler::setLoadMode(LoadMode mode)
69 {
70     // https://bugs.webkit.org/show_bug.cgi?id=26556
71     // We cannot call sendQueuedItems() from here, because the signal that 
72     // caused us to get into deferred mode, might not be processed yet.
73     switch (mode) {
74     case LoadNormal:
75         m_loadMode = LoadResuming;
76         emit processQueuedItems();
77         break;
78     case LoadDeferred:
79         m_loadMode = LoadDeferred;
80         break;
81     case LoadResuming:
82         Q_ASSERT(0); // should never happen
83         break;
84     };
85 }
86
87 void QNetworkReplyHandler::abort()
88 {
89     m_resourceHandle = 0;
90     if (m_reply) {
91         QNetworkReply* reply = release();
92         reply->abort();
93         reply->deleteLater();
94         deleteLater();
95     }
96 }
97
98 QNetworkReply* QNetworkReplyHandler::release()
99 {
100     QNetworkReply* reply = m_reply;
101     if (m_reply) {
102         disconnect(m_reply, 0, this, 0);
103         // We have queued connections to the QNetworkReply. Make sure any
104         // posted meta call events that were the result of a signal emission
105         // don't reach the slots in our instance.
106         QCoreApplication::removePostedEvents(this, QEvent::MetaCall);
107         m_reply = 0;
108     }
109     return reply;
110 }
111
112 void QNetworkReplyHandler::finish()
113 {
114     m_shouldFinish = (m_loadMode != LoadNormal);
115     if (m_shouldFinish)
116         return;
117
118     sendResponseIfNeeded();
119
120     if (!m_resourceHandle)
121         return;
122     ResourceHandleClient* client = m_resourceHandle->client();
123     if (!client) {
124         m_reply->deleteLater();
125         m_reply = 0;
126         return;
127     }
128     QNetworkReply* oldReply = m_reply;
129     if (m_redirected) {
130         resetState();
131         start();
132     } else if (m_reply->error() != QNetworkReply::NoError
133                // a web page that returns 401/403/404 can still have content
134                && ((m_reply->error() != QNetworkReply::ContentOperationNotPermittedError
135                && m_reply->error() != QNetworkReply::ContentNotFoundError
136                && m_reply->error() != QNetworkReply::ProtocolUnknownError
137                && m_reply->error() != QNetworkReply::UnknownContentError)
138                // If the web page sent content, let's give it to the user.
139                || !m_responseDataSent)
140                && m_reply->error() != QNetworkReply::AuthenticationRequiredError
141                && m_reply->error() != QNetworkReply::ProxyAuthenticationRequiredError) {
142         QUrl url = m_reply->url();
143         ResourceError error(url.host(), m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(),
144                             url.toString(), m_reply->errorString());
145         client->didFail(m_resourceHandle, error);
146     } else {
147         client->didFinishLoading(m_resourceHandle);
148     }
149     oldReply->deleteLater();
150     if (oldReply == m_reply)
151         m_reply = 0;
152 }
153
154 void QNetworkReplyHandler::sendResponseIfNeeded()
155 {
156     m_shouldSendResponse = (m_loadMode != LoadNormal);
157     if (m_shouldSendResponse)
158         return;
159
160     if (m_responseSent || !m_resourceHandle)
161         return;
162     m_responseSent = true;
163
164     ResourceHandleClient* client = m_resourceHandle->client();
165     if (!client)
166         return;
167
168     QString contentType = m_reply->header(QNetworkRequest::ContentTypeHeader).toString();
169 #if 1
170     QString encoding;
171     QString mimeType;
172 #else
173     QString encoding = extractCharsetFromMediaType(contentType);
174     QString mimeType = extractMIMETypeFromMediaType(contentType);
175
176     if (mimeType.isEmpty()) {
177         // let's try to guess from the extension
178         QString extension = m_reply->url().path();
179         int index = extension.lastIndexOf(QLatin1Char('.'));
180         if (index > 0) {
181             extension = extension.mid(index + 1);
182             mimeType = MIMETypeRegistry::getMIMETypeForExtension(extension);
183         }
184     }
185 #endif
186
187     KURL url(m_reply->url());
188 #if 0
189     QString suggestedFilename = filenameFromHTTPContentDisposition(QString::fromAscii(m_reply->rawHeader("Content-Disposition")));
190
191     if (suggestedFilename.isEmpty())
192         suggestedFilename = url.lastPathComponent();
193 #endif
194
195     ResourceResponse response(url, mimeType,
196                               m_reply->header(QNetworkRequest::ContentLengthHeader).toLongLong(),
197                               encoding,
198                               QString());
199
200     const bool isLocalFileReply = (m_reply->url().scheme() == QLatin1String("file"));
201     int statusCode = m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
202     if (!isLocalFileReply) {
203         response.setHTTPStatusCode(statusCode);
204         response.setHTTPStatusText(m_reply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toByteArray().constData());
205     }
206     else if (m_reply->error() == QNetworkReply::ContentNotFoundError)
207         response.setHTTPStatusCode(404);
208
209
210     /* Fill in the other fields
211      * For local file requests remove the content length and the last-modified
212      * headers as required by fast/dom/xmlhttprequest-get.xhtml
213      */
214 #if 0
215     foreach (const QNetworkReply::RawHeaderPair& headerPair, m_reply->rawHeaderPairs()) {
216         const QByteArray& headerName = headerPair.first;
217         const QByteArray& headerValue = headerPair.second;
218
219         if (isLocalFileReply
220             && (headerName == "Content-Length" || headerName == "Last-Modified"))
221             continue;
222
223         response.setHTTPHeaderField(QString::fromAscii(headerName), QString::fromAscii(headerValue));
224     }
225 #else
226    foreach (QNetworkReply::RawHeaderPair rawHeader, m_reply->rawHeaderPairs()) {
227         const QByteArray headerName = rawHeader.first;
228        if (isLocalFileReply
229            && (headerName == "Content-Length" || headerName == "Last-Modified"))
230            continue;
231
232        response.setHTTPHeaderField(QString::fromAscii(headerName), QString::fromAscii(rawHeader.second));
233    }
234
235 #endif
236
237     if (isLocalFileReply)
238         response.setHTTPHeaderField(QString::fromAscii("Cache-Control"), QString::fromAscii("no-cache"));
239
240     QUrl redirection = m_reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
241     if (redirection.isValid()) {
242         QUrl newUrl = m_reply->url().resolved(redirection);
243         ResourceRequest newRequest = m_resourceHandle->request();
244         newRequest.setURL(newUrl);
245
246         if (((statusCode >= 301 && statusCode <= 303) || statusCode == 307) && m_method == QNetworkAccessManager::PostOperation) {
247             m_method = QNetworkAccessManager::GetOperation;
248             newRequest.setHTTPMethod("GET");
249         }
250
251         client->willSendRequest(m_resourceHandle, newRequest, response);
252         m_redirected = true;
253         m_request = newRequest.toNetworkRequest();
254     } else {
255         client->didReceiveResponse(m_resourceHandle, response);
256     }
257 }
258
259 void QNetworkReplyHandler::forwardData()
260 {
261     m_shouldForwardData = (m_loadMode != LoadNormal);
262     if (m_shouldForwardData)
263         return;
264
265     sendResponseIfNeeded();
266
267     // don't emit the "Document has moved here" type of HTML
268     if (m_redirected)
269         return;
270
271     if (!m_resourceHandle)
272         return;
273
274     QByteArray data = m_reply->read(m_reply->bytesAvailable());
275
276     ResourceHandleClient* client = m_resourceHandle->client();
277     if (!client)
278         return;
279
280     if (!data.isEmpty()) {
281         m_responseDataSent = true;
282         client->didReceiveData(m_resourceHandle, data.constData(), data.length(), data.length() /*FixMe*/);
283     }
284 }
285
286 void QNetworkReplyHandler::start()
287 {
288     m_shouldStart = false;
289
290     QNetworkAccessManager* manager = globalManager;
291 #if 0
292     ResourceHandleInternal* d = m_resourceHandle->getInternal();
293
294     QNetworkAccessManager* manager = d->m_frame->page()->networkAccessManager();
295 #endif
296
297     const QUrl url = m_request.url();
298     const QString scheme = url.scheme();
299     // Post requests on files and data don't really make sense, but for
300     // fast/forms/form-post-urlencoded.html and for fast/forms/button-state-restore.html
301     // we still need to retrieve the file/data, which means we map it to a Get instead.
302     if (m_method == QNetworkAccessManager::PostOperation
303         && (!url.toLocalFile().isEmpty() || url.scheme() == QLatin1String("data")))
304         m_method = QNetworkAccessManager::GetOperation;
305
306     switch (m_method) {
307         case QNetworkAccessManager::GetOperation:
308             m_reply = manager->get(m_request);
309             break;
310         case QNetworkAccessManager::PostOperation: {
311             qFatal("POST OPERATION is not allowed");
312             break;
313         }
314         case QNetworkAccessManager::HeadOperation:
315             m_reply = manager->head(m_request);
316             break;
317         case QNetworkAccessManager::PutOperation: {
318             qFatal("PUT OPERATION is not allowed");
319             break;
320         }
321         case QNetworkAccessManager::UnknownOperation: {
322             m_reply = 0;
323             ResourceHandleClient* client = m_resourceHandle->client();
324             if (client) {
325                 ResourceError error(url.host(), 400 /*bad request*/,
326                                     url.toString(),
327                                     QCoreApplication::translate("QWebPage", "Bad HTTP request"));
328                 client->didFail(m_resourceHandle, error);
329             }
330             return;
331         }
332     }
333
334     m_reply->setParent(this);
335
336     connect(m_reply, SIGNAL(finished()),
337             this, SLOT(finish()), Qt::QueuedConnection);
338
339     // For http(s) we know that the headers are complete upon metaDataChanged() emission, so we
340     // can send the response as early as possible
341     if (scheme == QLatin1String("http") || scheme == QLatin1String("https"))
342         connect(m_reply, SIGNAL(metaDataChanged()),
343                 this, SLOT(sendResponseIfNeeded()), Qt::QueuedConnection);
344
345     connect(m_reply, SIGNAL(readyRead()),
346             this, SLOT(forwardData()), Qt::QueuedConnection);
347     connect(this, SIGNAL(processQueuedItems()),
348             this, SLOT(sendQueuedItems()), Qt::QueuedConnection);
349 }
350
351 void QNetworkReplyHandler::resetState()
352 {
353     m_redirected = false;
354     m_responseSent = false;
355     m_responseDataSent = false;
356     m_shouldStart = true;
357     m_shouldFinish = false;
358     m_shouldSendResponse = false;
359     m_shouldForwardData = false;
360 }
361
362 void QNetworkReplyHandler::sendQueuedItems()
363 {
364     if (m_loadMode != LoadResuming)
365         return;
366     m_loadMode = LoadNormal;
367
368     if (m_shouldStart)
369         start();
370
371     if (m_shouldSendResponse)
372         sendResponseIfNeeded();
373
374     if (m_shouldForwardData)
375         forwardData();
376
377     if (m_shouldFinish)
378         finish(); 
379 }
380
381 }
382
383 #include "moc_QNetworkReplyHandler.cpp"
384