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