├── .clang-format
├── .github
└── FUNDING.yml
├── .gitignore
├── CMakeLists.txt
├── LICENSE
├── README.md
├── http.pri
├── http.pro
├── marketplace.json
└── src
├── cachedhttp.cpp
├── cachedhttp.h
├── http.cpp
├── http.h
├── httpreply.cpp
├── httpreply.h
├── httprequest.h
├── localcache.cpp
├── localcache.h
├── networkhttpreply.cpp
├── networkhttpreply.h
├── throttledhttp.cpp
└── throttledhttp.h
/.clang-format:
--------------------------------------------------------------------------------
1 | BasedOnStyle: LLVM
2 | IndentWidth: 4
3 | AccessModifierOffset: -4
4 | ColumnLimit: 100
5 | AllowShortIfStatementsOnASingleLine: true
6 | AllowShortFunctionsOnASingleLine: Inline
7 | KeepEmptyLinesAtTheStartOfBlocks: false
8 | ContinuationIndentWidth: 8
9 | AlignAfterOpenBracket: true
10 | BinPackParameters: false
11 | AllowAllParametersOfDeclarationOnNextLine: false
12 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: flaviotordini
2 | custom: https://flavio.tordini.org/donate
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | cmake-build-debug
3 | *.user
4 | .DS_Store
5 | build
6 |
--------------------------------------------------------------------------------
/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.12)
2 | project(http LANGUAGES CXX)
3 |
4 | set(CMAKE_CXX_STANDARD 14)
5 | set(CMAKE_AUTOMOC ON)
6 |
7 | find_package(Qt5 REQUIRED COMPONENTS Core Network)
8 |
9 | add_library(http
10 | src/cachedhttp.h
11 | src/cachedhttp.cpp
12 | src/http.h
13 | src/http.cpp
14 | src/httpreply.h
15 | src/httprequest.h
16 | src/httpreply.cpp
17 | src/localcache.h
18 | src/localcache.cpp
19 | src/networkhttpreply.h
20 | src/networkhttpreply.cpp
21 | src/throttledhttp.h
22 | src/throttledhttp.cpp
23 | )
24 |
25 | target_link_libraries(http Qt5::Network)
26 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Flavio Tordini
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | # A wrapper for the Qt Network Access API
6 |
7 | This is just a wrapper around Qt's QNetworkAccessManager and friends. I use it in my apps at https://flavio.tordini.org . It has a simpler, higher-level API and some functionality not found in Qt:
8 |
9 | - Throttling (as required by many web APIs nowadays)
10 | - Automatic retries
11 | - User agent and request header defaults
12 | - Partial requests
13 | - Easier POST requests
14 | - Read timeouts (don't let your requests get stuck forever). (now supported by Qt >= 5.15)
15 | - Redirection support (now supported by Qt >= 5.6)
16 | - Disk-based cache implementation similar to Qt's but not strictly a HTTP cache, i.e. it ignores HTTP headers. This is good if want to cache successful requests irrespective of what the origin server says you should do. The cache also fallbacks to stale content when the server returns an error.
17 |
18 | ## Design
19 |
20 | This library uses the [Decorator design pattern](https://en.wikipedia.org/wiki/Decorator_pattern) to modularize features and make it easy to add them and use them as needed. The main class is [Http](https://github.com/flaviotordini/http/blob/master/src/http.h), which implements the base features of a HTTP client. More specialized classes are:
21 |
22 | - [CachedHttp](https://github.com/flaviotordini/http/blob/master/src/cachedhttp.h), a simple disk-based cache
23 | - [ThrottledHttp](https://github.com/flaviotordini/http/blob/master/src/throttledhttp.h), implements request throttling (aka limiting)
24 |
25 | The constructor of these classes takes another Http instance for which they will act as a proxy. (See examples below). Following this design you can create your own Http subclass. For example, a different caching mechanism, an event dispatcher, custom request logging, etc.
26 |
27 |
28 | ## Build Instructions
29 | In order to build this library you can use either `qmake` or `cmake`.
30 |
31 | ### qmake
32 | ```shell
33 | mkdir build
34 | cd build
35 | qmake ..
36 | make
37 | ```
38 |
39 | ### CMake
40 | ```shell
41 | mkdir build
42 | cd build
43 | cmake ..
44 | make
45 | ```
46 |
47 | ## Integration
48 |
49 | You can use this library as a git submodule. For example, add it to your project inside a lib subdirectory:
50 |
51 | ```shell
52 | git submodule add -b master https://github.com/flaviotordini/http lib/http
53 | ```
54 |
55 | Then you can update your git submodules like this:
56 |
57 | ```shell
58 | git submodule update --init --recursive --remote
59 | ```
60 |
61 | To integrate the library in your qmake based project just add this to your .pro file:
62 |
63 | ```qmake
64 | include(lib/http/http.pri)
65 | ```
66 |
67 | qmake builds all object files in the same directory. In order to avoid filename clashes use:
68 |
69 | ```qmake
70 | CONFIG += object_parallel_to_source
71 | ```
72 |
73 | If you are using CMake you can integrate the library by adding the following lines to your CMakeLists.txt:
74 |
75 | ```cmake
76 | add_subdirectory(lib/http)
77 | ...
78 | target_link_library(your_super_cool_project http)
79 | ```
80 | or if you have installed http you can find it via:
81 |
82 | ```cmake
83 | find_library(http REQUIRED)
84 | ...
85 | target_link_library(your_super_cool_project http)
86 | ```
87 |
88 |
89 | ## Examples
90 |
91 | A basic C++14 example:
92 |
93 | ```cpp
94 | #include "http.h"
95 |
96 | auto reply = Http::instance().get("https://flavio.tordini.org/");
97 | connect(reply, &HttpReply::finished, this, [](auto &reply) {
98 | if (reply.isSuccessful()) {
99 | qDebug() << "Feel the bytes!" << reply.body();
100 | } else {
101 | qDebug() << "Something's wrong here" << reply.statusCode() << reply.reasonPhrase();
102 | }
103 | });
104 | ```
105 |
106 | It is important to use the reply object passed by the `finished` signal as it may be a different object than the one used to make the request. For example it may be created by `CacheHttp` or other specialized `Http` subclasses.
107 |
108 | Example using two separate signals for success and failure:
109 | ```cpp
110 | #include "http.h"
111 |
112 | auto reply = Http::instance().get("https://google.com/");
113 | connect(reply, &HttpReply::data, this, [](auto &bytes) {
114 | qDebug() << "Feel the bytes!" << bytes;
115 | });
116 | connect(reply, &HttpReply::error, this, [](auto &msg) {
117 | qDebug() << "Something's wrong here" << msg;
118 | });
119 | ```
120 |
121 | A POST example taking advantage of C++11 initializer lists:
122 |
123 | ```cpp
124 | QUrl url("https://some.domain.url/");
125 | auto reply = Http::instance().post(url, {{"myparam", "paramvalue"}, {"otherparam", "foo"}});
126 | connect(reply, &HttpReply::finished, this, [](auto &reply) {
127 | if (reply.isSuccessful()) {
128 | qDebug() << "Feel the bytes!" << reply.body();
129 | } else {
130 | qDebug() << "Something's wrong here" << reply.statusCode() << reply.reasonPhrase();
131 | }
132 | });
133 | ```
134 |
135 | This is a real-world example of building a Http object with more complex features. It throttles requests, uses a custom user agent and caches results:
136 |
137 | ```cpp
138 | #include "http.h"
139 | #include "cachedhttp.h"
140 | #include "throttledhttp.h"
141 |
142 | Http &myHttp() {
143 | static Http *http = [] {
144 | auto http = new Http;
145 | http->addRequestHeader("User-Agent", "MyUserAgent");
146 |
147 | auto throttledHttp = new ThrottledHttp(*http);
148 | throttledHttp->setMilliseconds(1000);
149 |
150 | auto cachedHttp = new CachedHttp(*throttledHttp, "mycache");
151 | cachedHttp->setMaxSeconds(86400 * 30);
152 |
153 | return cachedHttp;
154 | }();
155 | return *http;
156 | }
157 | ```
158 |
159 | If the full power (and complexity) of QNetworkReply is needed you can always fallback to it:
160 |
161 | ```cpp
162 | #include "http.h"
163 |
164 | HttpRequest req;
165 | req.url = "https://flavio.tordini.org/";
166 | QNetworkReply *reply = Http::instance().networkReply(req);
167 | // Use QNetworkReply as needed...
168 | ```
169 |
170 | Note that features like redirection, retries and read timeouts won't work in this mode.
171 |
172 | ## License
173 |
174 | You can use this library under the MIT license and at your own risk. If you do, you're welcome contributing your changes and fixes.
175 |
--------------------------------------------------------------------------------
/http.pri:
--------------------------------------------------------------------------------
1 | QT *= network
2 |
3 | INCLUDEPATH += $$PWD/src
4 | DEFINES += HTTP
5 |
6 | HEADERS += \
7 | $$PWD/src/cachedhttp.h \
8 | $$PWD/src/http.h \
9 | $$PWD/src/httpreply.h \
10 | $$PWD/src/httprequest.h \
11 | $$PWD/src/localcache.h \
12 | $$PWD/src/networkhttpreply.h \
13 | $$PWD/src/throttledhttp.h
14 |
15 | SOURCES += \
16 | $$PWD/src/cachedhttp.cpp \
17 | $$PWD/src/http.cpp \
18 | $$PWD/src/httpreply.cpp \
19 | $$PWD/src/localcache.cpp \
20 | $$PWD/src/networkhttpreply.cpp \
21 | $$PWD/src/throttledhttp.cpp
22 |
--------------------------------------------------------------------------------
/http.pro:
--------------------------------------------------------------------------------
1 | TEMPLATE = lib
2 | include(http.pri)
3 |
--------------------------------------------------------------------------------
/marketplace.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://qt.io/schema/extension-schema-v1#",
3 | "title": "Http",
4 | "extensionType": [
5 | "library"
6 | ],
7 | "version": "1",
8 | "vendor": {
9 | "name": "Flavio Tordini",
10 | "url": "https://flavio.tordini.org"
11 | },
12 | "contact": "Flavio Tordini ",
13 | "copyright": [
14 | "Flavio Tordini"
15 | ],
16 | "author": "Flavio Tordini",
17 | "icon": "https://flavio.tordini.org/favicon-196x196.png",
18 | "licenses": [
19 | {
20 | "licenseType": "MIT",
21 | "licenseUrl": "https://opensource.org/licenses/MIT"
22 | }
23 | ],
24 | "created": "2016-07-02",
25 | "platforms": [
26 | "Windows",
27 | "Linux",
28 | "macOS",
29 | "Android",
30 | "iOS"
31 | ],
32 | "qtVersions": [
33 | "5.10.0-or-later"
34 | ],
35 | "tags": [
36 | "http,web,rest,networking,freeproduct,tools,utility"
37 | ],
38 | "price": {
39 | "listprice": 0
40 | },
41 | "support": "flavio.tordini@gmail.com",
42 | "bugUrl": "https://github.com/flaviotordini/http/issues",
43 | "sourceRepoUrl": "https://github.com/flaviotordini/http",
44 | "userManuals": [
45 | "https://github.com/flaviotordini/http/blob/master/README.md"
46 | ],
47 | "dependencies": [
48 | "Network"
49 | ]
50 | }
--------------------------------------------------------------------------------
/src/cachedhttp.cpp:
--------------------------------------------------------------------------------
1 | #include "cachedhttp.h"
2 | #include "localcache.h"
3 | #include
4 |
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 |
10 | QByteArray CachedHttpReply::body() const {
11 | return bytes;
12 | }
13 |
14 | void CachedHttpReply::emitSignals() {
15 | emit data(body());
16 | emit finished(*this);
17 | deleteLater();
18 | }
19 |
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, &HttpReply::finished, this, &WrappedHttpReply::originFinished);
26 | }
27 |
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 |
76 | CachedHttp::CachedHttp(Http &http, const char *name)
77 | : http(http), cache(LocalCache::instance(name)), cachePostRequests(false) {}
78 |
79 | void CachedHttp::setMaxSeconds(uint seconds) {
80 | cache->setMaxSeconds(seconds);
81 | }
82 |
83 | void CachedHttp::setMaxSize(uint maxSize) {
84 | cache->setMaxSize(maxSize);
85 | }
86 |
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 |
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 |
--------------------------------------------------------------------------------
/src/cachedhttp.h:
--------------------------------------------------------------------------------
1 | #ifndef CACHEDHTTP_H
2 | #define CACHEDHTTP_H
3 |
4 | #include "http.h"
5 |
6 | class LocalCache;
7 |
8 | class CachedHttp : public Http {
9 | public:
10 | CachedHttp(Http &http = Http::instance(), const char *name = "http");
11 | void setMaxSeconds(uint seconds);
12 | void setMaxSize(uint maxSize);
13 | void setCachePostRequests(bool value) { cachePostRequests = value; }
14 | void setIgnoreHostname(bool value) { ignoreHostname = value; }
15 | auto &getValidators() { return validators; }
16 | HttpReply *request(const HttpRequest &req);
17 |
18 | private:
19 | QByteArray requestHash(const HttpRequest &req);
20 |
21 | Http &http;
22 | LocalCache *cache;
23 | bool cachePostRequests;
24 | bool ignoreHostname = false;
25 |
26 | /// Mapping is MIME -> validating function
27 | /// Use * as MIME to define a catch-all validator
28 | QMap> validators;
29 | };
30 |
31 | class CachedHttpReply : public HttpReply {
32 | Q_OBJECT
33 |
34 | public:
35 | CachedHttpReply(const QByteArray &body, const QUrl &url, bool autoSignals = true);
36 | QUrl url() const { return requestUrl; }
37 | int statusCode() const { return 200; }
38 | QByteArray body() const;
39 |
40 | private slots:
41 | void emitSignals();
42 |
43 | private:
44 | const QByteArray bytes;
45 | const QUrl requestUrl;
46 | };
47 |
48 | class WrappedHttpReply : public HttpReply {
49 | Q_OBJECT
50 |
51 | public:
52 | WrappedHttpReply(CachedHttp &cachedHttp,
53 | LocalCache *cache,
54 | const QByteArray &key,
55 | HttpReply *httpReply);
56 | QUrl url() const { return httpReply->url(); }
57 | int statusCode() const { return httpReply->statusCode(); }
58 | QByteArray body() const { return httpReply->body(); }
59 |
60 | private slots:
61 | void originFinished(const HttpReply &reply);
62 |
63 | private:
64 | CachedHttp &cachedHttp;
65 | LocalCache *cache;
66 | QByteArray key;
67 | HttpReply *httpReply;
68 | };
69 |
70 | #endif // CACHEDHTTP_H
71 |
--------------------------------------------------------------------------------
/src/http.cpp:
--------------------------------------------------------------------------------
1 | #include "http.h"
2 |
3 | #include "networkhttpreply.h"
4 |
5 | namespace {
6 |
7 | QNetworkAccessManager *networkAccessManager() {
8 | static thread_local QNetworkAccessManager *nam = [] {
9 | auto nam = new QNetworkAccessManager();
10 | #if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)
11 | nam->setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy);
12 | #endif
13 | return nam;
14 | }();
15 | return nam;
16 | }
17 |
18 | int defaultReadTimeout = 10000;
19 | int defaultMaxRetries = 3;
20 | } // namespace
21 |
22 | Http::Http()
23 | : requestHeaders(getDefaultRequestHeaders()), readTimeout(defaultReadTimeout),
24 | maxRetries(defaultMaxRetries) {}
25 |
26 | void Http::setRequestHeaders(const QMap &headers) {
27 | requestHeaders = headers;
28 | }
29 |
30 | QMap &Http::getRequestHeaders() {
31 | return requestHeaders;
32 | }
33 |
34 | void Http::addRequestHeader(const QByteArray &name, const QByteArray &value) {
35 | requestHeaders.insert(name, value);
36 | }
37 |
38 | void Http::setReadTimeout(int timeout) {
39 | readTimeout = timeout;
40 | }
41 |
42 | Http &Http::instance() {
43 | static Http i;
44 | return i;
45 | }
46 |
47 | const QMap &Http::getDefaultRequestHeaders() {
48 | static const QMap defaultRequestHeaders = [] {
49 | QMap h;
50 | h.insert("Accept-Charset", "utf-8");
51 | h.insert("Connection", "Keep-Alive");
52 | return h;
53 | }();
54 | return defaultRequestHeaders;
55 | }
56 |
57 | void Http::setDefaultReadTimeout(int timeout) {
58 | defaultReadTimeout = timeout;
59 | }
60 |
61 | QNetworkReply *Http::networkReply(const HttpRequest &req) {
62 | QNetworkRequest request(req.url);
63 |
64 | QMap &headers = requestHeaders;
65 | if (!req.headers.isEmpty()) headers = req.headers;
66 |
67 | QMap::const_iterator it;
68 | for (it = headers.constBegin(); it != headers.constEnd(); ++it)
69 | request.setRawHeader(it.key(), it.value());
70 |
71 | if (req.offset > 0)
72 | request.setRawHeader("Range", QStringLiteral("bytes=%1-").arg(req.offset).toUtf8());
73 |
74 | QNetworkAccessManager *manager = networkAccessManager();
75 |
76 | QNetworkReply *networkReply = nullptr;
77 | switch (req.operation) {
78 | case QNetworkAccessManager::GetOperation:
79 | networkReply = manager->get(request);
80 | break;
81 |
82 | case QNetworkAccessManager::HeadOperation:
83 | networkReply = manager->head(request);
84 | break;
85 |
86 | case QNetworkAccessManager::PostOperation:
87 | networkReply = manager->post(request, req.body);
88 | break;
89 |
90 | case QNetworkAccessManager::PutOperation:
91 | networkReply = manager->put(request, req.body);
92 | break;
93 |
94 | case QNetworkAccessManager::DeleteOperation:
95 | networkReply = manager->deleteResource(request);
96 | break;
97 |
98 | default:
99 | qWarning() << "Unknown operation:" << req.operation;
100 | }
101 |
102 | return networkReply;
103 | }
104 |
105 | HttpReply *Http::request(const HttpRequest &req) {
106 | return new NetworkHttpReply(req, *this);
107 | }
108 |
109 | HttpReply *Http::request(const QUrl &url,
110 | QNetworkAccessManager::Operation operation,
111 | const QByteArray &body,
112 | uint offset) {
113 | HttpRequest req;
114 | req.url = url;
115 | req.operation = operation;
116 | req.body = body;
117 | req.offset = offset;
118 | return request(req);
119 | }
120 |
121 | HttpReply *Http::get(const QUrl &url) {
122 | return request(url, QNetworkAccessManager::GetOperation);
123 | }
124 |
125 | HttpReply *Http::head(const QUrl &url) {
126 | return request(url, QNetworkAccessManager::HeadOperation);
127 | }
128 |
129 | HttpReply *Http::post(const QUrl &url, const QMap ¶ms) {
130 | QByteArray body;
131 | QMapIterator i(params);
132 | while (i.hasNext()) {
133 | i.next();
134 | body += QUrl::toPercentEncoding(i.key()) + '=' + QUrl::toPercentEncoding(i.value()) + '&';
135 | }
136 | HttpRequest req;
137 | req.url = url;
138 | req.operation = QNetworkAccessManager::PostOperation;
139 | req.body = body;
140 | req.headers = requestHeaders;
141 | req.headers.insert("Content-Type", "application/x-www-form-urlencoded");
142 | return request(req);
143 | }
144 |
145 | HttpReply *Http::post(const QUrl &url, const QByteArray &body, const QByteArray &contentType) {
146 | HttpRequest req;
147 | req.url = url;
148 | req.operation = QNetworkAccessManager::PostOperation;
149 | req.body = body;
150 | req.headers = requestHeaders;
151 | QByteArray cType = contentType;
152 | if (cType.isEmpty()) cType = "application/x-www-form-urlencoded";
153 | req.headers.insert("Content-Type", cType);
154 | return request(req);
155 | }
156 |
157 |
158 | HttpReply *Http::put(const QUrl &url, const QByteArray &body, const QByteArray &contentType) {
159 | HttpRequest req;
160 | req.url = url;
161 | req.operation = QNetworkAccessManager::PutOperation;
162 | req.body = body;
163 | req.headers = requestHeaders;
164 | QByteArray cType = contentType;
165 | if (cType.isEmpty()) cType = "application/x-www-form-urlencoded";
166 | req.headers.insert("Content-Type", cType);
167 | return request(req);
168 | }
169 |
170 |
171 | HttpReply *Http::deleteResource(const QUrl &url) {
172 | HttpRequest req;
173 | req.url = url;
174 | req.operation = QNetworkAccessManager::DeleteOperation;
175 | req.headers = requestHeaders;
176 | return request(req);
177 | }
178 |
179 | int Http::getMaxRetries() const {
180 | return maxRetries;
181 | }
182 |
183 | void Http::setMaxRetries(int value) {
184 | maxRetries = value;
185 | }
186 |
--------------------------------------------------------------------------------
/src/http.h:
--------------------------------------------------------------------------------
1 | #ifndef HTTP_H
2 | #define HTTP_H
3 |
4 | #include
5 |
6 | #include "httpreply.h"
7 | #include "httprequest.h"
8 |
9 | class Http {
10 | public:
11 | static Http &instance();
12 | static const QMap &getDefaultRequestHeaders();
13 | static void setDefaultReadTimeout(int timeout);
14 |
15 | Http();
16 |
17 | void setRequestHeaders(const QMap &headers);
18 | QMap &getRequestHeaders();
19 | void addRequestHeader(const QByteArray &name, const QByteArray &value);
20 |
21 | void setReadTimeout(int timeout);
22 | int getReadTimeout() { return readTimeout; }
23 |
24 | int getMaxRetries() const;
25 | void setMaxRetries(int value);
26 |
27 | QNetworkReply *networkReply(const HttpRequest &req);
28 | virtual HttpReply *request(const HttpRequest &req);
29 | HttpReply *
30 | request(const QUrl &url,
31 | QNetworkAccessManager::Operation operation = QNetworkAccessManager::GetOperation,
32 | const QByteArray &body = QByteArray(),
33 | uint offset = 0);
34 | HttpReply *get(const QUrl &url);
35 | HttpReply *head(const QUrl &url);
36 | HttpReply *post(const QUrl &url, const QMap ¶ms);
37 | HttpReply *post(const QUrl &url, const QByteArray &body, const QByteArray &contentType);
38 | HttpReply *put(const QUrl &url, const QByteArray &body, const QByteArray &contentType);
39 | HttpReply *deleteResource(const QUrl &url);
40 |
41 | private:
42 | QMap requestHeaders;
43 | int readTimeout;
44 | int maxRetries;
45 | };
46 |
47 | #endif // HTTP_H
48 |
--------------------------------------------------------------------------------
/src/httpreply.cpp:
--------------------------------------------------------------------------------
1 | #include "httpreply.h"
2 |
3 | HttpReply::HttpReply(QObject *parent) : QObject(parent) {}
4 |
5 | int HttpReply::isSuccessful() const {
6 | return statusCode() >= 200 && statusCode() < 300;
7 | }
8 |
9 | QString HttpReply::reasonPhrase() const {
10 | return QString();
11 | }
12 |
13 | const QList HttpReply::headers() const {
14 | return QList();
15 | }
16 |
17 | QByteArray HttpReply::header(const QByteArray &headerName) const {
18 | Q_UNUSED(headerName);
19 | return QByteArray();
20 | }
21 |
--------------------------------------------------------------------------------
/src/httpreply.h:
--------------------------------------------------------------------------------
1 | #ifndef HTTPREPLY_H
2 | #define HTTPREPLY_H
3 |
4 | #include
5 |
6 | class HttpReply : public QObject {
7 | Q_OBJECT
8 |
9 | public:
10 | HttpReply(QObject *parent = nullptr);
11 | virtual QUrl url() const = 0;
12 | virtual int statusCode() const = 0;
13 | int isSuccessful() const;
14 | virtual QString reasonPhrase() const;
15 | virtual const QList headers() const;
16 | virtual QByteArray header(const QByteArray &headerName) const;
17 | virtual QByteArray body() const = 0;
18 |
19 | template HttpReply &onData(Functor lambda) {
20 | connect(this, &HttpReply::data, this, lambda);
21 | return *this;
22 | }
23 | template HttpReply &onError(Functor lambda) {
24 | connect(this, &HttpReply::error, this, lambda);
25 | return *this;
26 | }
27 | template HttpReply &onFinished(Functor lambda) {
28 | connect(this, &HttpReply::finished, this, lambda);
29 | return *this;
30 | }
31 |
32 | signals:
33 | void data(const QByteArray &bytes);
34 | void error(const QString &message);
35 | void finished(const HttpReply &reply);
36 | };
37 |
38 | #endif // HTTPREPLY_H
39 |
--------------------------------------------------------------------------------
/src/httprequest.h:
--------------------------------------------------------------------------------
1 | #ifndef HTTPREQUEST_H
2 | #define HTTPREQUEST_H
3 |
4 | #include
5 |
6 | class HttpRequest {
7 | public:
8 | HttpRequest() : operation(QNetworkAccessManager::GetOperation), offset(0) {}
9 | QUrl url;
10 | QNetworkAccessManager::Operation operation;
11 | QByteArray body;
12 | uint offset;
13 | QMap headers;
14 | };
15 |
16 | #endif // HTTPREQUEST_H
17 |
--------------------------------------------------------------------------------
/src/localcache.cpp:
--------------------------------------------------------------------------------
1 | #include "localcache.h"
2 |
3 | LocalCache *LocalCache::instance(const char *name) {
4 | static QMap instances;
5 | auto i = instances.constFind(QByteArray::fromRawData(name, strlen(name)));
6 | if (i != instances.constEnd()) return i.value();
7 | LocalCache *instance = new LocalCache(name);
8 | instances.insert(instance->getName(), instance);
9 | return instance;
10 | }
11 |
12 | LocalCache::LocalCache(const QByteArray &name)
13 | : name(name), maxSeconds(86400 * 30), maxSize(1024 * 1024 * 100), size(0), insertCount(0) {
14 | directory = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + QLatin1Char('/') +
15 | QLatin1String(name) + QLatin1Char('/');
16 | #ifndef QT_NO_DEBUG_OUTPUT
17 | hits = 0;
18 | misses = 0;
19 | #endif
20 | }
21 |
22 | LocalCache::~LocalCache() {
23 | #ifndef QT_NO_DEBUG_OUTPUT
24 | debugStats();
25 | #endif
26 | }
27 |
28 | QByteArray LocalCache::hash(const QByteArray &s) {
29 | QCryptographicHash hash(QCryptographicHash::Sha1);
30 | hash.addData(s);
31 | const QByteArray h = QByteArray::number(*(qlonglong *)hash.result().constData(), 36);
32 | static const char sep('/');
33 | QByteArray p;
34 | p.reserve(h.length() + 2);
35 | p.append(h.at(0));
36 | p.append(sep);
37 | p.append(h.at(1));
38 | p.append(sep);
39 | p.append(h.constData() + 2, strlen(h.constData()) - 2); // p.append(h.mid(2));
40 | return p;
41 | }
42 |
43 | bool LocalCache::isCached(const QString &path) {
44 | bool cached = QFile::exists(path) &&
45 | (maxSeconds == 0 || QFileInfo(path).birthTime().secsTo(
46 | QDateTime::currentDateTimeUtc()) < maxSeconds);
47 | #ifndef QT_NO_DEBUG_OUTPUT
48 | if (!cached) misses++;
49 | #endif
50 | return cached;
51 | }
52 |
53 | QByteArray LocalCache::value(const QByteArray &key) {
54 | const QString path = cachePath(key);
55 | if (!isCached(path)) return QByteArray();
56 |
57 | QFile file(path);
58 | if (!file.open(QIODevice::ReadOnly)) {
59 | qWarning() << file.fileName() << file.errorString();
60 | #ifndef QT_NO_DEBUG_OUTPUT
61 | misses++;
62 | #endif
63 | return QByteArray();
64 | }
65 | #ifndef QT_NO_DEBUG_OUTPUT
66 | hits++;
67 | #endif
68 | return file.readAll();
69 | }
70 |
71 | QByteArray LocalCache::possiblyStaleValue(const QByteArray &key) {
72 | const QString path = cachePath(key);
73 | QFile file(path);
74 | if (!file.open(QIODevice::ReadOnly)) {
75 | #ifndef QT_NO_DEBUG_OUTPUT
76 | misses++;
77 | #endif
78 | return QByteArray();
79 | }
80 | #ifndef QT_NO_DEBUG_OUTPUT
81 | hits++;
82 | #endif
83 | return file.readAll();
84 | }
85 |
86 | void LocalCache::insert(const QByteArray &key, const QByteArray &value) {
87 | // qDebug() << "Inserting" << key;
88 | const QString path = cachePath(key);
89 | const QString parentDir = path.left(path.lastIndexOf(QLatin1Char('/')));
90 | if (!QFile::exists(parentDir)) {
91 | QDir().mkpath(parentDir);
92 | }
93 | QFile file(path);
94 | if (!file.open(QIODevice::WriteOnly)) {
95 | qWarning() << "Cannot create" << path;
96 | return;
97 | }
98 | file.write(value);
99 | file.close();
100 | if (size > 0) size += value.size();
101 |
102 | // expire cache every n inserts
103 | if (maxSize > 0 && ++insertCount % 100 == 0) {
104 | if (size == 0 || size > maxSize) expire();
105 | }
106 | }
107 |
108 | void LocalCache::clear() {
109 | #ifndef QT_NO_DEBUG_OUTPUT
110 | hits = 0;
111 | misses = 0;
112 | #endif
113 | size = 0;
114 | insertCount = 0;
115 | mutex.lock();
116 | QDir(directory).removeRecursively();
117 | mutex.unlock();
118 | }
119 |
120 | QString LocalCache::cachePath(const QByteArray &key) const {
121 | return directory + QLatin1String(key);
122 | }
123 |
124 | void LocalCache::expire() {
125 | if (!mutex.tryLock()) return;
126 |
127 | QDir::Filters filters = QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot;
128 | QDirIterator it(directory, filters, QDirIterator::Subdirectories);
129 |
130 | QMultiMap cacheItems;
131 | qint64 totalSize = 0;
132 | while (it.hasNext()) {
133 | QString path = it.next();
134 | QFileInfo info = it.fileInfo();
135 | cacheItems.insert(info.birthTime(), path);
136 | totalSize += info.size();
137 | qApp->processEvents();
138 | }
139 |
140 | int removedFiles = 0;
141 | qint64 goal = (maxSize * 9) / 10;
142 | auto i = cacheItems.constBegin();
143 | while (i != cacheItems.constEnd()) {
144 | if (totalSize < goal) break;
145 | QString name = i.value();
146 | QFile file(name);
147 | qint64 size = file.size();
148 | file.remove();
149 | totalSize -= size;
150 | ++removedFiles;
151 | ++i;
152 | qApp->processEvents();
153 | }
154 | #ifndef QT_NO_DEBUG_OUTPUT
155 | debugStats();
156 | if (removedFiles > 0) {
157 | qDebug() << "Removed:" << removedFiles << "Kept:" << cacheItems.count() - removedFiles
158 | << "New Size:" << totalSize;
159 | }
160 | #endif
161 |
162 | size = totalSize;
163 | mutex.unlock();
164 | }
165 |
166 | #ifndef QT_NO_DEBUG_OUTPUT
167 | void LocalCache::debugStats() {
168 | int total = hits + misses;
169 | if (total > 0) {
170 | qDebug() << "Cache:" << name << '\n'
171 | << "Inserts:" << insertCount << '\n'
172 | << "Requests:" << total << '\n'
173 | << "Hits:" << hits << (hits * 100) / total << "%\n"
174 | << "Misses:" << misses << (misses * 100) / total << "%";
175 | }
176 | }
177 | #endif
178 |
--------------------------------------------------------------------------------
/src/localcache.h:
--------------------------------------------------------------------------------
1 | #ifndef LOCALCACHE_H
2 | #define LOCALCACHE_H
3 |
4 | #include
5 |
6 | /**
7 | * @brief Not thread-safe
8 | */
9 | class LocalCache {
10 | public:
11 | static LocalCache *instance(const char *name);
12 | ~LocalCache();
13 | static QByteArray hash(const QByteArray &s);
14 |
15 | const QByteArray &getName() const { return name; }
16 |
17 | void setMaxSeconds(uint value) { maxSeconds = value; }
18 | void setMaxSize(uint value) { maxSize = value; }
19 |
20 | QByteArray value(const QByteArray &key);
21 | QByteArray possiblyStaleValue(const QByteArray &key);
22 | void insert(const QByteArray &key, const QByteArray &value);
23 | void clear();
24 |
25 | private:
26 | LocalCache(const QByteArray &name);
27 | QString cachePath(const QByteArray &key) const;
28 | bool isCached(const QString &path);
29 | void expire();
30 | #ifndef QT_NO_DEBUG_OUTPUT
31 | void debugStats();
32 | #endif
33 |
34 | QByteArray name;
35 | QString directory;
36 | uint maxSeconds;
37 | qint64 maxSize;
38 | qint64 size;
39 | QMutex mutex;
40 | uint insertCount;
41 |
42 | #ifndef QT_NO_DEBUG_OUTPUT
43 | uint hits;
44 | uint misses;
45 | #endif
46 | };
47 |
48 | #endif // LOCALCACHE_H
49 |
--------------------------------------------------------------------------------
/src/networkhttpreply.cpp:
--------------------------------------------------------------------------------
1 | #include "networkhttpreply.h"
2 |
3 | NetworkHttpReply::NetworkHttpReply(const HttpRequest &req, Http &http)
4 | : http(http), req(req), retryCount(0) {
5 | if (req.url.isEmpty()) {
6 | qWarning() << "Empty URL";
7 | }
8 |
9 | networkReply = http.networkReply(req);
10 | setParent(networkReply);
11 | setupReply();
12 |
13 | readTimeoutTimer = new QTimer(this);
14 | readTimeoutTimer->setInterval(http.getReadTimeout());
15 | readTimeoutTimer->setSingleShot(true);
16 | connect(readTimeoutTimer, &QTimer::timeout, this, &NetworkHttpReply::readTimeout,
17 | Qt::UniqueConnection);
18 | readTimeoutTimer->start();
19 | }
20 |
21 | void NetworkHttpReply::setupReply() {
22 | #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
23 | connect(networkReply, &QNetworkReply::errorOccurred, this, &NetworkHttpReply::replyError,
24 | Qt::UniqueConnection);
25 | #else
26 | connect(networkReply, SIGNAL(error(QNetworkReply::NetworkError)),
27 | SLOT(replyError(QNetworkReply::NetworkError)), Qt::UniqueConnection);
28 | #endif
29 | connect(networkReply, &QNetworkReply::finished, this, &NetworkHttpReply::replyFinished,
30 | Qt::UniqueConnection);
31 | connect(networkReply, &QNetworkReply::downloadProgress, this,
32 | &NetworkHttpReply::downloadProgress, Qt::UniqueConnection);
33 | }
34 |
35 | QString NetworkHttpReply::errorMessage() {
36 | return url().toString() + QLatin1Char(' ') + QString::number(statusCode()) + QLatin1Char(' ') +
37 | reasonPhrase();
38 | }
39 |
40 | void NetworkHttpReply::emitError() {
41 | const QString msg = errorMessage();
42 | #ifndef QT_NO_DEBUG_OUTPUT
43 | qDebug() << "Http:" << msg;
44 | if (!req.body.isEmpty()) qDebug() << "Http:" << req.body;
45 | #endif
46 | emit error(msg);
47 | emitFinished();
48 | }
49 |
50 | void NetworkHttpReply::emitFinished() {
51 | readTimeoutTimer->stop();
52 |
53 | // disconnect to avoid replyFinished() from being called
54 | networkReply->disconnect();
55 |
56 | emit finished(*this);
57 |
58 | // bye bye my reply
59 | // this will also delete this object and HttpReply as the QNetworkReply is their parent
60 | networkReply->deleteLater();
61 | }
62 |
63 | void NetworkHttpReply::replyFinished() {
64 | #if QT_VERSION < QT_VERSION_CHECK(5, 9, 0)
65 | QUrl redirection = networkReply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
66 | if (redirection.isValid()) {
67 | HttpRequest redirectReq;
68 | if (redirection.isRelative()) redirection = networkReply->url().resolved(redirection);
69 | redirectReq.url = redirection;
70 | qDebug() << "Redirected to" << redirectReq.url;
71 | redirectReq.operation = req.operation;
72 | redirectReq.body = req.body;
73 | redirectReq.offset = req.offset;
74 | QNetworkReply *redirectReply = http.networkReply(redirectReq);
75 | setParent(redirectReply);
76 | networkReply->deleteLater();
77 | networkReply = redirectReply;
78 | setupReply();
79 | readTimeoutTimer->start();
80 | return;
81 | }
82 | #endif
83 |
84 | if (isSuccessful()) {
85 | bytes = networkReply->readAll();
86 | emit data(bytes);
87 |
88 | #ifndef QT_NO_DEBUG_OUTPUT
89 | if (!networkReply->attribute(QNetworkRequest::SourceIsFromCacheAttribute).toBool())
90 | qDebug() << statusCode() << networkReply->url().toString();
91 | else
92 | qDebug() << "CACHE" << networkReply->url().toString();
93 | #endif
94 | }
95 |
96 | emitFinished();
97 | }
98 |
99 | void NetworkHttpReply::replyError(QNetworkReply::NetworkError code) {
100 | Q_UNUSED(code);
101 | const int status = statusCode();
102 | if (retryCount <= http.getMaxRetries() && status >= 500 && status < 600 &&
103 | (networkReply->operation() == QNetworkAccessManager::GetOperation ||
104 | networkReply->operation() == QNetworkAccessManager::HeadOperation)) {
105 | qDebug() << "Retrying" << status << QVariant(req.operation).toString() << req.url;
106 | networkReply->disconnect();
107 | networkReply->deleteLater();
108 | QNetworkReply *retryReply = http.networkReply(req);
109 | setParent(retryReply);
110 | networkReply = retryReply;
111 | setupReply();
112 | retryCount++;
113 | readTimeoutTimer->start();
114 | } else {
115 | emitError();
116 | return;
117 | }
118 | }
119 |
120 | void NetworkHttpReply::downloadProgress(qint64 bytesReceived, qint64 /* bytesTotal */) {
121 | // qDebug() << "Downloading" << bytesReceived << bytesTotal << networkReply->url();
122 | if (bytesReceived > 0 && readTimeoutTimer->isActive()) {
123 | readTimeoutTimer->stop();
124 | disconnect(networkReply, &QNetworkReply::downloadProgress, this,
125 | &NetworkHttpReply::downloadProgress);
126 | }
127 | }
128 |
129 | void NetworkHttpReply::readTimeout() {
130 | qDebug() << "Timeout" << req.url;
131 |
132 | if (!networkReply) return;
133 |
134 | bool shouldRetry = (networkReply->operation() == QNetworkAccessManager::GetOperation ||
135 | networkReply->operation() == QNetworkAccessManager::HeadOperation) &&
136 | retryCount < http.getMaxRetries();
137 |
138 | networkReply->disconnect();
139 | networkReply->abort();
140 | networkReply->deleteLater();
141 |
142 | if (!shouldRetry) {
143 | emitError();
144 | emit finished(*this);
145 | return;
146 | }
147 |
148 | retryCount++;
149 | QNetworkReply *retryReply = http.networkReply(req);
150 | setParent(retryReply);
151 | networkReply = retryReply;
152 | setupReply();
153 | readTimeoutTimer->start();
154 | }
155 |
156 | QUrl NetworkHttpReply::url() const {
157 | return networkReply->url();
158 | }
159 |
160 | int NetworkHttpReply::statusCode() const {
161 | return networkReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
162 | }
163 |
164 | QString NetworkHttpReply::reasonPhrase() const {
165 | return networkReply->attribute(QNetworkRequest::HttpReasonPhraseAttribute).toString();
166 | }
167 |
168 | const QList NetworkHttpReply::headers() const {
169 | return networkReply->rawHeaderPairs();
170 | }
171 |
172 | QByteArray NetworkHttpReply::header(const QByteArray &headerName) const {
173 | return networkReply->rawHeader(headerName);
174 | }
175 |
176 | QByteArray NetworkHttpReply::body() const {
177 | return bytes;
178 | }
179 |
--------------------------------------------------------------------------------
/src/networkhttpreply.h:
--------------------------------------------------------------------------------
1 | #ifndef NETWORKHTTPREPLY_H
2 | #define NETWORKHTTPREPLY_H
3 |
4 | #include
5 |
6 | #include "http.h"
7 | #include "httpreply.h"
8 | #include "httprequest.h"
9 |
10 | class NetworkHttpReply : public HttpReply {
11 | Q_OBJECT
12 |
13 | public:
14 | NetworkHttpReply(const HttpRequest &req, Http &http);
15 | QUrl url() const;
16 | int statusCode() const;
17 | QString reasonPhrase() const;
18 | const QList headers() const;
19 | QByteArray header(const QByteArray &headerName) const;
20 | QByteArray body() const;
21 |
22 | private slots:
23 | void replyFinished();
24 | void replyError(QNetworkReply::NetworkError);
25 | void downloadProgress(qint64 bytesReceived, qint64 bytesTotal);
26 | void readTimeout();
27 |
28 | private:
29 | void setupReply();
30 | QString errorMessage();
31 | void emitError();
32 | void emitFinished();
33 |
34 | Http &http;
35 | HttpRequest req;
36 | QNetworkReply *networkReply;
37 | QTimer *readTimeoutTimer;
38 | int retryCount;
39 | QByteArray bytes;
40 | };
41 |
42 | #endif // NETWORKHTTPREPLY_H
43 |
--------------------------------------------------------------------------------
/src/throttledhttp.cpp:
--------------------------------------------------------------------------------
1 | #include "throttledhttp.h"
2 |
3 | ThrottledHttp::ThrottledHttp(Http &http) : http(http), milliseconds(1000) {
4 | elapsedTimer.start();
5 | }
6 |
7 | HttpReply *ThrottledHttp::request(const HttpRequest &req) {
8 | return new ThrottledHttpReply(http, req, milliseconds, elapsedTimer);
9 | }
10 |
11 | ThrottledHttpReply::ThrottledHttpReply(Http &http,
12 | const HttpRequest &req,
13 | int milliseconds,
14 | QElapsedTimer &elapsedTimer)
15 | : http(http), req(req), milliseconds(milliseconds), elapsedTimer(elapsedTimer), timer(nullptr) {
16 | checkElapsed();
17 | }
18 |
19 | void ThrottledHttpReply::checkElapsed() {
20 | /*
21 | static QMutex mutex;
22 | QMutexLocker locker(&mutex);
23 | */
24 |
25 | const qint64 elapsedSinceLastRequest = elapsedTimer.elapsed();
26 | if (elapsedSinceLastRequest < milliseconds) {
27 | if (!timer) {
28 | timer = new QTimer(this);
29 | timer->setSingleShot(true);
30 | timer->setTimerType(Qt::PreciseTimer);
31 | connect(timer, &QTimer::timeout, this, &ThrottledHttpReply::checkElapsed);
32 | }
33 | // qDebug() << "Throttling" << req.url << QStringLiteral("%1ms").arg(milliseconds -
34 | // elapsedSinceLastRequest);
35 | timer->setInterval(milliseconds - elapsedSinceLastRequest);
36 | timer->start();
37 | return;
38 | }
39 | elapsedTimer.start();
40 | doRequest();
41 | }
42 |
43 | void ThrottledHttpReply::doRequest() {
44 | auto reply = http.request(req);
45 | connect(reply, &HttpReply::data, this, &HttpReply::data);
46 | connect(reply, &HttpReply::error, this, &HttpReply::error);
47 | connect(reply, &HttpReply::finished, this, &HttpReply::finished);
48 |
49 | // this will cause the deletion of this object once the request is finished
50 | setParent(reply);
51 | }
52 |
--------------------------------------------------------------------------------
/src/throttledhttp.h:
--------------------------------------------------------------------------------
1 | #ifndef THROTTLEDHTTP_H
2 | #define THROTTLEDHTTP_H
3 |
4 | #include "http.h"
5 | #include
6 | #include
7 |
8 | class ThrottledHttp : public Http {
9 | public:
10 | ThrottledHttp(Http &http = Http::instance());
11 | void setMilliseconds(int milliseconds) { this->milliseconds = milliseconds; }
12 | HttpReply *request(const HttpRequest &req);
13 |
14 | private:
15 | Http &http;
16 | int milliseconds;
17 | QElapsedTimer elapsedTimer;
18 | };
19 |
20 | class ThrottledHttpReply : public HttpReply {
21 | Q_OBJECT
22 |
23 | public:
24 | ThrottledHttpReply(Http &http,
25 | const HttpRequest &req,
26 | int milliseconds,
27 | QElapsedTimer &elapsedTimer);
28 | QUrl url() const { return req.url; }
29 | int statusCode() const { return 200; }
30 | QByteArray body() const { return QByteArray(); }
31 |
32 | private slots:
33 | void checkElapsed();
34 |
35 | private:
36 | void doRequest();
37 | Http &http;
38 | HttpRequest req;
39 | int milliseconds;
40 | QElapsedTimer &elapsedTimer;
41 | QTimer *timer;
42 | };
43 |
44 | #endif // THROTTLEDHTTP_H
45 |
--------------------------------------------------------------------------------