]> git.sur5r.net Git - minitube/blob - src/http/src/http.cpp
2326e66033c6135e35d9b96344c5f7142a738bac
[minitube] / src / http / src / http.cpp
1 #include "http.h"
2
3 namespace {
4
5 QNetworkAccessManager *createNetworkAccessManager() {
6     QNetworkAccessManager *nam = new QNetworkAccessManager();
7     return nam;
8 }
9
10 QNetworkAccessManager *networkAccessManager() {
11     static QMap<QThread *, QNetworkAccessManager *> nams;
12     QThread *t = QThread::currentThread();
13     QMap<QThread *, QNetworkAccessManager *>::const_iterator i = nams.constFind(t);
14     if (i != nams.constEnd()) return i.value();
15     QNetworkAccessManager *nam = createNetworkAccessManager();
16     nams.insert(t, nam);
17     return nam;
18 }
19
20 static int defaultReadTimeout = 10000;
21 }
22
23 Http::Http() : requestHeaders(getDefaultRequestHeaders()), readTimeout(defaultReadTimeout) {}
24
25 void Http::setRequestHeaders(const QMap<QByteArray, QByteArray> &headers) {
26     requestHeaders = headers;
27 }
28
29 QMap<QByteArray, QByteArray> &Http::getRequestHeaders() {
30     return requestHeaders;
31 }
32
33 void Http::addRequestHeader(const QByteArray &name, const QByteArray &value) {
34     requestHeaders.insert(name, value);
35 }
36
37 void Http::setReadTimeout(int timeout) {
38     readTimeout = timeout;
39 }
40
41 Http &Http::instance() {
42     static Http *i = new Http();
43     return *i;
44 }
45
46 const QMap<QByteArray, QByteArray> &Http::getDefaultRequestHeaders() {
47     static const QMap<QByteArray, QByteArray> defaultRequestHeaders = [] {
48         QMap<QByteArray, QByteArray> h;
49         h.insert("Accept-Charset", "utf-8");
50         h.insert("Connection", "Keep-Alive");
51         return h;
52     }();
53     return defaultRequestHeaders;
54 }
55
56 void Http::setDefaultReadTimeout(int timeout) {
57     defaultReadTimeout = timeout;
58 }
59
60 QNetworkReply *Http::networkReply(const HttpRequest &req) {
61     QNetworkRequest request(req.url);
62
63     QMap<QByteArray, QByteArray> &headers = requestHeaders;
64     if (!req.headers.isEmpty()) headers = req.headers;
65
66     QMap<QByteArray, QByteArray>::const_iterator it;
67     for (it = headers.constBegin(); it != headers.constEnd(); ++it)
68         request.setRawHeader(it.key(), it.value());
69
70     if (req.offset > 0)
71         request.setRawHeader("Range", QString("bytes=%1-").arg(req.offset).toUtf8());
72
73     QNetworkAccessManager *manager = networkAccessManager();
74
75     QNetworkReply *networkReply = 0;
76     switch (req.operation) {
77     case QNetworkAccessManager::GetOperation:
78         networkReply = manager->get(request);
79         break;
80
81     case QNetworkAccessManager::HeadOperation:
82         networkReply = manager->head(request);
83         break;
84
85     case QNetworkAccessManager::PostOperation:
86         networkReply = manager->post(request, req.body);
87         break;
88
89     default:
90         qWarning() << "Unknown operation:" << req.operation;
91     }
92
93     return networkReply;
94 }
95
96 QObject *Http::request(const HttpRequest &req) {
97     return new NetworkHttpReply(req, *this);
98 }
99
100 QObject *Http::request(const QUrl &url,
101                        QNetworkAccessManager::Operation operation,
102                        const QByteArray &body,
103                        uint offset) {
104     HttpRequest req;
105     req.url = url;
106     req.operation = operation;
107     req.body = body;
108     req.offset = offset;
109     return request(req);
110 }
111
112 QObject *Http::get(const QUrl &url) {
113     return request(url, QNetworkAccessManager::GetOperation);
114 }
115
116 QObject *Http::head(const QUrl &url) {
117     return request(url, QNetworkAccessManager::HeadOperation);
118 }
119
120 QObject *Http::post(const QUrl &url, const QMap<QString, QString> &params) {
121     QByteArray body;
122     QMapIterator<QString, QString> i(params);
123     while (i.hasNext()) {
124         i.next();
125         body += QUrl::toPercentEncoding(i.key()) + '=' + QUrl::toPercentEncoding(i.value()) + '&';
126     }
127     HttpRequest req;
128     req.url = url;
129     req.operation = QNetworkAccessManager::PostOperation;
130     req.body = body;
131     req.headers = requestHeaders;
132     req.headers.insert("Content-Type", "application/x-www-form-urlencoded");
133     return request(req);
134 }
135
136 QObject *Http::post(const QUrl &url, const QByteArray &body, const QByteArray &contentType) {
137     HttpRequest req;
138     req.url = url;
139     req.operation = QNetworkAccessManager::PostOperation;
140     req.body = body;
141     req.headers = requestHeaders;
142     QByteArray cType = contentType;
143     if (cType.isEmpty()) cType = "application/x-www-form-urlencoded";
144     req.headers.insert("Content-Type", cType);
145     return request(req);
146 }
147
148 NetworkHttpReply::NetworkHttpReply(const HttpRequest &req, Http &http)
149     : http(http), req(req), retryCount(0) {
150     if (req.url.isEmpty()) {
151         qWarning() << "Empty URL";
152     }
153
154     networkReply = http.networkReply(req);
155     setParent(networkReply);
156     setupReply();
157
158     readTimeoutTimer = new QTimer(this);
159     readTimeoutTimer->setInterval(http.getReadTimeout());
160     readTimeoutTimer->setSingleShot(true);
161     connect(readTimeoutTimer, SIGNAL(timeout()), SLOT(readTimeout()), Qt::UniqueConnection);
162     readTimeoutTimer->start();
163 }
164
165 void NetworkHttpReply::setupReply() {
166     connect(networkReply, SIGNAL(error(QNetworkReply::NetworkError)),
167             SLOT(replyError(QNetworkReply::NetworkError)), Qt::UniqueConnection);
168     connect(networkReply, SIGNAL(finished()), SLOT(replyFinished()), Qt::UniqueConnection);
169     connect(networkReply, SIGNAL(downloadProgress(qint64, qint64)),
170             SLOT(downloadProgress(qint64, qint64)), Qt::UniqueConnection);
171 }
172
173 QString NetworkHttpReply::errorMessage() {
174     return url().toString() + QLatin1Char(' ') + QString::number(statusCode()) + QLatin1Char(' ') +
175            reasonPhrase();
176 }
177
178 void NetworkHttpReply::emitError() {
179     const QString msg = errorMessage();
180 #ifndef QT_NO_DEBUG_OUTPUT
181     qDebug() << "Http:" << msg;
182     if (!req.body.isEmpty()) qDebug() << "Http:" << req.body;
183 #endif
184     emit error(msg);
185     emitFinished();
186 }
187
188 void NetworkHttpReply::emitFinished() {
189     readTimeoutTimer->stop();
190
191     // disconnect to avoid replyFinished() from being called
192     networkReply->disconnect();
193
194     emit finished(*this);
195
196     // bye bye my reply
197     // this will also delete this object and HttpReply as the QNetworkReply is their parent
198     networkReply->deleteLater();
199 }
200
201 void NetworkHttpReply::replyFinished() {
202     QUrl redirection = networkReply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
203     if (redirection.isValid()) {
204         HttpRequest redirectReq;
205         redirectReq.url = redirection;
206         redirectReq.operation = req.operation;
207         redirectReq.body = req.body;
208         redirectReq.offset = req.offset;
209         QNetworkReply *redirectReply = http.networkReply(redirectReq);
210         setParent(redirectReply);
211         networkReply->deleteLater();
212         networkReply = redirectReply;
213         setupReply();
214         readTimeoutTimer->start();
215         return;
216     }
217
218     if (isSuccessful()) {
219         bytes = networkReply->readAll();
220         emit data(bytes);
221
222 #ifndef QT_NO_DEBUG_OUTPUT
223         if (!networkReply->attribute(QNetworkRequest::SourceIsFromCacheAttribute).toBool())
224             qDebug() << networkReply->url().toString() << statusCode();
225         else
226             qDebug() << "CACHE" << networkReply->url().toString();
227 #endif
228     }
229
230     emitFinished();
231 }
232
233 void NetworkHttpReply::replyError(QNetworkReply::NetworkError code) {
234     Q_UNUSED(code);
235     const int status = statusCode();
236     if (retryCount <= 3 && status >= 500 && status < 600) {
237         qDebug() << "Retrying" << req.url;
238         networkReply->disconnect();
239         networkReply->deleteLater();
240         QNetworkReply *retryReply = http.networkReply(req);
241         setParent(retryReply);
242         networkReply = retryReply;
243         setupReply();
244         retryCount++;
245         readTimeoutTimer->start();
246     } else {
247         emitError();
248         return;
249     }
250 }
251
252 void NetworkHttpReply::downloadProgress(qint64 bytesReceived, qint64 /* bytesTotal */) {
253     // qDebug() << "Downloading" << bytesReceived << bytesTotal << networkReply->url();
254     if (bytesReceived > 0 && readTimeoutTimer->isActive()) {
255         readTimeoutTimer->stop();
256         disconnect(networkReply, SIGNAL(downloadProgress(qint64, qint64)), this,
257                    SLOT(downloadProgress(qint64, qint64)));
258     }
259 }
260
261 void NetworkHttpReply::readTimeout() {
262     if (!networkReply) return;
263     networkReply->disconnect();
264     networkReply->abort();
265     networkReply->deleteLater();
266
267     if (retryCount > 3 && (networkReply->operation() != QNetworkAccessManager::GetOperation &&
268                            networkReply->operation() != QNetworkAccessManager::HeadOperation)) {
269         emitError();
270         emit finished(*this);
271         return;
272     }
273
274     qDebug() << "Timeout" << req.url;
275     QNetworkReply *retryReply = http.networkReply(req);
276     setParent(retryReply);
277     networkReply = retryReply;
278     setupReply();
279     retryCount++;
280     readTimeoutTimer->start();
281 }
282
283 QUrl NetworkHttpReply::url() const {
284     return networkReply->url();
285 }
286
287 int NetworkHttpReply::statusCode() const {
288     return networkReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
289 }
290
291 QString NetworkHttpReply::reasonPhrase() const {
292     return networkReply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString();
293 }
294
295 const QList<QNetworkReply::RawHeaderPair> NetworkHttpReply::headers() const {
296     return networkReply->rawHeaderPairs();
297 }
298
299 QByteArray NetworkHttpReply::header(const QByteArray &headerName) const {
300     return networkReply->rawHeader(headerName);
301 }
302
303 QByteArray NetworkHttpReply::body() const {
304     return bytes;
305 }