1 #include "cachedhttp.h"
2 #include "localcache.h"
3 #include <QtNetwork>
4 
CachedHttpReply(const QByteArray & body,const QUrl & url,bool autoSignals)5 CachedHttpReply::CachedHttpReply(const QByteArray &body, const QUrl &url, bool autoSignals)
6     : bytes(body), requestUrl(url) {
7     if (autoSignals) QTimer::singleShot(0, this, SLOT(emitSignals()));
8 }
9 
body() const10 QByteArray CachedHttpReply::body() const {
11     return bytes;
12 }
13 
emitSignals()14 void CachedHttpReply::emitSignals() {
15     emit data(body());
16     emit finished(*this);
17     deleteLater();
18 }
19 
WrappedHttpReply(CachedHttp & cachedHttp,LocalCache * cache,const QByteArray & key,HttpReply * httpReply)20 WrappedHttpReply::WrappedHttpReply(CachedHttp &cachedHttp,
21                                    LocalCache *cache,
22                                    const QByteArray &key,
23                                    HttpReply *httpReply)
24     : HttpReply(httpReply), cachedHttp(cachedHttp), cache(cache), key(key), httpReply(httpReply) {
25     connect(httpReply, SIGNAL(finished(HttpReply)), SLOT(originFinished(HttpReply)));
26 }
27 
originFinished(const HttpReply & reply)28 void WrappedHttpReply::originFinished(const HttpReply &reply) {
29     qDebug() << reply.statusCode() << reply.url();
30     bool success = reply.isSuccessful();
31     if (!success) {
32         // Fallback to stale cached data on HTTP error
33         const QByteArray value = cache->possiblyStaleValue(key);
34         if (!value.isNull()) {
35             qDebug() << "Using stale cache value" << reply.url();
36             emit data(value);
37             auto replyFromCache = new CachedHttpReply(value, reply.url(), false);
38             emit finished(*replyFromCache);
39             replyFromCache->deleteLater();
40             return;
41         }
42     }
43 
44     bool doCache = success;
45     if (doCache) {
46         const auto &validators = cachedHttp.getValidators();
47         if (!validators.isEmpty()) {
48             const QByteArray &mime = reply.header("Content-Type");
49             auto i = validators.constFind(mime);
50             if (i != validators.constEnd()) {
51                 auto validator = i.value();
52                 doCache = validator(reply);
53             } else {
54                 i = validators.constFind("*");
55                 if (i != validators.constEnd()) {
56                     auto validator = i.value();
57                     doCache = validator(reply);
58                 }
59             }
60         }
61     }
62 
63     if (doCache)
64         cache->insert(key, reply.body());
65     else
66         qDebug() << "Not caching" << reply.statusCode() << reply.url();
67 
68     if (success) {
69         emit data(reply.body());
70     } else {
71         emit error(reply.reasonPhrase());
72     }
73     emit finished(reply);
74 }
75 
CachedHttp(Http & http,const char * name)76 CachedHttp::CachedHttp(Http &http, const char *name)
77     : http(http), cache(LocalCache::instance(name)), cachePostRequests(false) {}
78 
setMaxSeconds(uint seconds)79 void CachedHttp::setMaxSeconds(uint seconds) {
80     cache->setMaxSeconds(seconds);
81 }
82 
setMaxSize(uint maxSize)83 void CachedHttp::setMaxSize(uint maxSize) {
84     cache->setMaxSize(maxSize);
85 }
86 
request(const HttpRequest & req)87 HttpReply *CachedHttp::request(const HttpRequest &req) {
88     bool cacheable = req.operation == QNetworkAccessManager::GetOperation ||
89                      (cachePostRequests && req.operation == QNetworkAccessManager::PostOperation);
90     if (!cacheable) {
91         qDebug() << "Not cacheable" << req.url;
92         return http.request(req);
93     }
94     const QByteArray key = requestHash(req);
95     const QByteArray value = cache->value(key);
96     if (!value.isNull()) {
97         qDebug() << "HIT" << key << req.url;
98         return new CachedHttpReply(value, req.url);
99     }
100     // qDebug() << "MISS" << key << req.url;
101     return new WrappedHttpReply(*this, cache, key, http.request(req));
102 }
103 
requestHash(const HttpRequest & req)104 QByteArray CachedHttp::requestHash(const HttpRequest &req) {
105     const char sep = '|';
106 
107     QByteArray s;
108     if (ignoreHostname) {
109         s = (req.url.scheme() + sep + req.url.path() + sep + req.url.query()).toUtf8();
110     } else {
111         s = req.url.toEncoded();
112     }
113     s += sep + req.body + sep + QByteArray::number(req.offset);
114     if (req.operation == QNetworkAccessManager::PostOperation) {
115         s.append(sep);
116         s.append("POST");
117     }
118     return LocalCache::hash(s);
119 }
120