diff options
author | Thiago Marcos P. Santos <thiago@mapbox.com> | 2015-10-21 17:24:44 -0700 |
---|---|---|
committer | Thiago Marcos P. Santos <thiago@mapbox.com> | 2016-04-20 20:55:51 +0300 |
commit | 029f9f088331fa0d04a9b75a7dbdae773f749f47 (patch) | |
tree | 265e2f2619804164731a7a1158f3accfc4e28f3b | |
parent | e93d51c922a0d55b6f40b07185452dc54151736d (diff) | |
download | qtlocation-mapboxgl-029f9f088331fa0d04a9b75a7dbdae773f749f47.tar.gz |
[Qt] Introduce the Qt HTTPRequest and HTTPFileSource
-rw-r--r-- | platform/qt/src/http_file_source.cpp | 123 | ||||
-rw-r--r-- | platform/qt/src/http_file_source.hpp | 39 | ||||
-rw-r--r-- | platform/qt/src/http_request.cpp | 122 | ||||
-rw-r--r-- | platform/qt/src/http_request.hpp | 34 | ||||
-rw-r--r-- | src/mbgl/storage/http_file_source.hpp | 4 |
5 files changed, 320 insertions, 2 deletions
diff --git a/platform/qt/src/http_file_source.cpp b/platform/qt/src/http_file_source.cpp new file mode 100644 index 0000000000..a38cf42363 --- /dev/null +++ b/platform/qt/src/http_file_source.cpp @@ -0,0 +1,123 @@ +#include "http_file_source.hpp" +#include "http_request.hpp" + +#include <mbgl/platform/log.hpp> + +#include <QByteArray> +#include <QDir> +#include <QNetworkProxyFactory> +#include <QNetworkReply> +#include <QSslConfiguration> + +namespace mbgl { + +HTTPFileSource::Impl::Impl() : m_manager(new QNetworkAccessManager(this)) +{ + QNetworkProxyFactory::setUseSystemConfiguration(true); + +#if QT_VERSION >= 0x050000 + m_ssl.setProtocol(QSsl::SecureProtocols); +#else + // Qt 4 defines SecureProtocols as TLS1 or SSL3, but we don't want SSL3. + m_ssl.setProtocol(QSsl::TlsV1); +#endif + + m_ssl.setCaCertificates(QSslCertificate::fromPath("ca-bundle.crt")); + if (m_ssl.caCertificates().isEmpty()) { + mbgl::Log::Warning(mbgl::Event::HttpRequest, "Could not load list of certificate authorities"); + } + + connect(m_manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(replyFinish(QNetworkReply*))); +} + +void HTTPFileSource::Impl::request(HTTPRequest* req) +{ + QUrl url = req->requestUrl(); + + QPair<QNetworkReply*, QVector<HTTPRequest*>>& data = m_pending[url]; + QVector<HTTPRequest*>& requestsVector = data.second; + requestsVector.append(req); + + if (requestsVector.size() > 1) { + return; + } + + QNetworkRequest networkRequest = req->networkRequest(); + networkRequest.setSslConfiguration(m_ssl); + + data.first = m_manager->get(networkRequest); +} + +void HTTPFileSource::Impl::cancel(HTTPRequest* req) +{ + QUrl url = req->requestUrl(); + + auto it = m_pending.find(url); + if (it == m_pending.end()) { + return; + } + + QPair<QNetworkReply*, QVector<HTTPRequest*>>& data = it.value(); + QNetworkReply* reply = data.first; + QVector<HTTPRequest*>& requestsVector = data.second; + + for (int i = 0; i < requestsVector.size(); ++i) { + if (req == requestsVector.at(i)) { + requestsVector.remove(i); + break; + } + } + + if (requestsVector.empty()) { + m_pending.erase(it); +#if QT_VERSION >= 0x050000 + reply->abort(); +#else + // XXX: We should be aborting the reply here + // but a bug on Qt4 causes the connection of + // other ongoing requests to drop if we call + // abort() too often (and we do). + Q_UNUSED(reply); +#endif + } +} + +void HTTPFileSource::Impl::replyFinish(QNetworkReply* reply) +{ + const QUrl& url = reply->request().url(); + + auto it = m_pending.find(url); + if (it == m_pending.end()) { + reply->deleteLater(); + return; + } + + QVector<HTTPRequest*>& requestsVector = it.value().second; + for (auto req : requestsVector) { + req->handleNetworkReply(reply); + } + + m_pending.erase(it); + reply->deleteLater(); +} + +HTTPFileSource::HTTPFileSource() + : impl(std::make_unique<Impl>()) { +} + +HTTPFileSource::~HTTPFileSource() = default; + +std::unique_ptr<AsyncRequest> HTTPFileSource::request(const Resource& resource, Callback callback) +{ + return std::make_unique<HTTPRequest>(impl.get(), resource, callback); +} + +uint32_t HTTPFileSource::maximumConcurrentRequests() { +#if QT_VERSION >= 0x050000 + return 20; +#else + return 10; +#endif +} + +} // mbgl diff --git a/platform/qt/src/http_file_source.hpp b/platform/qt/src/http_file_source.hpp new file mode 100644 index 0000000000..274b464026 --- /dev/null +++ b/platform/qt/src/http_file_source.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include <mbgl/storage/http_file_source.hpp> +#include <mbgl/storage/resource.hpp> + +#include <QMap> +#include <QNetworkAccessManager> +#include <QObject> +#include <QPair> +#include <QQueue> +#include <QSslConfiguration> +#include <QUrl> +#include <QVector> + +namespace mbgl { + +class HTTPRequest; + +class HTTPFileSource::Impl : public QObject +{ + Q_OBJECT + +public: + Impl(); + virtual ~Impl() = default; + + void request(HTTPRequest*); + void cancel(HTTPRequest*); + +public slots: + void replyFinish(QNetworkReply* reply); + +private: + QMap<QUrl, QPair<QNetworkReply*, QVector<HTTPRequest*>>> m_pending; + QNetworkAccessManager *m_manager; + QSslConfiguration m_ssl; +}; + +} // namespace mbgl diff --git a/platform/qt/src/http_request.cpp b/platform/qt/src/http_request.cpp new file mode 100644 index 0000000000..a8ef43c9e2 --- /dev/null +++ b/platform/qt/src/http_request.cpp @@ -0,0 +1,122 @@ +#include "http_request.hpp" +#include "http_file_source.hpp" + +#include <mbgl/storage/response.hpp> +#include <mbgl/util/chrono.hpp> +#include <mbgl/util/http_header.hpp> +#include <mbgl/util/string.hpp> + +#include <QByteArray> +#include <QNetworkReply> +#include <QPair> + +namespace mbgl { + +HTTPRequest::HTTPRequest(HTTPFileSource::Impl* context, const Resource& resource, FileSource::Callback callback) + : m_context(context) + , m_resource(resource) + , m_callback(callback) +{ + m_context->request(this); +} + +HTTPRequest::~HTTPRequest() +{ + if (!m_handled) { + m_context->cancel(this); + } +} + +QUrl HTTPRequest::requestUrl() const +{ + return QUrl::fromPercentEncoding(QByteArray(m_resource.url.data(), m_resource.url.size())); +} + +QNetworkRequest HTTPRequest::networkRequest() const +{ + QNetworkRequest req = QNetworkRequest(requestUrl()); + req.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache); + req.setRawHeader("User-Agent", "MapboxGL/1.0 [Qt]"); + + if (m_resource.priorEtag) { + req.setRawHeader("If-None-Match", QByteArray(m_resource.priorEtag->data(), m_resource.priorEtag->size())); + } else if (m_resource.priorModified) { + req.setRawHeader("If-Modified-Since", util::rfc1123(*m_resource.priorModified).c_str()); + } + + return req; +} + +void HTTPRequest::handleNetworkReply(QNetworkReply *reply) +{ + m_handled = true; + + // Calling `callback` may result in deleting `this`. + // Copy data to temporaries first. + auto callback = m_callback; + mbgl::Response response; + + using Error = Response::Error; + + // Handle non-HTTP errors (i.e. like connection). + if (reply->error() && reply->error() < 100) { + response.error = std::make_unique<Error>( + Error::Reason::Connection, reply->errorString().toStdString()); + callback(response); + return; + } + + QPair<QByteArray, QByteArray> line; + foreach(line, reply->rawHeaderPairs()) { + QString header = QString(line.first).toLower(); + + if (header == "last-modified") { + response.modified = util::parseTimePoint(line.second.constData()); + } else if (header == "etag") { + response.etag = std::string(line.second.constData(), line.second.size()); + } else if (header == "cache-control") { + response.expires = http::CacheControl::parse(line.second.constData()).toTimePoint(); + } else if (header == "expires") { + response.expires = util::parseTimePoint(line.second.constData()); + } + } + + int responseCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + switch(responseCode) { + case 200: { + QByteArray bytes = reply->readAll(); + if (bytes.isEmpty()) { + response.data = std::make_shared<std::string>(); + } else { + response.data = std::make_shared<std::string>(bytes.data(), bytes.size()); + } + break; + } + case 204: + response.noContent = true; + break; + case 304: + response.notModified = true; + break; + case 404: { + if (m_resource.kind == Resource::Kind::Tile) { + response.noContent = true; + } else { + response.error = std::make_unique<Error>( + Error::Reason::NotFound, "HTTP status code 404"); + } + break; + } + default: + Response::Error::Reason reason = (responseCode >= 500 && responseCode < 600) ? + Error::Reason::Server : Error::Reason::Other; + + response.error = std::make_unique<Error>( + reason, "HTTP status code " + util::toString(responseCode)); + } + + callback(response); +} + +} // namespace mbgl diff --git a/platform/qt/src/http_request.hpp b/platform/qt/src/http_request.hpp new file mode 100644 index 0000000000..9af219f3c9 --- /dev/null +++ b/platform/qt/src/http_request.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include <mbgl/storage/http_file_source.hpp> +#include <mbgl/util/async_request.hpp> + +#include <QNetworkRequest> +#include <QUrl> + +class QNetworkReply; + +namespace mbgl { + +class Response; + +class HTTPRequest : public AsyncRequest +{ +public: + HTTPRequest(HTTPFileSource::Impl*, const Resource&, FileSource::Callback); + virtual ~HTTPRequest(); + + QUrl requestUrl() const; + QNetworkRequest networkRequest() const; + + void handleNetworkReply(QNetworkReply *reply); + +private: + HTTPFileSource::Impl* m_context; + Resource m_resource; + FileSource::Callback m_callback; + + bool m_handled = false; +}; + +} // namespace mbgl diff --git a/src/mbgl/storage/http_file_source.hpp b/src/mbgl/storage/http_file_source.hpp index 5e306dbfc5..60d37f1a14 100644 --- a/src/mbgl/storage/http_file_source.hpp +++ b/src/mbgl/storage/http_file_source.hpp @@ -14,9 +14,9 @@ public: static uint32_t maximumConcurrentRequests(); -private: - friend class HTTPRequest; class Impl; + +private: std::unique_ptr<Impl> impl; }; |