1 #include "http_request.hpp"
2 #include "http_file_source.hpp"
3
4 #include <mbgl/storage/response.hpp>
5 #include <mbgl/util/chrono.hpp>
6 #include <mbgl/util/optional.hpp>
7 #include <mbgl/util/http_header.hpp>
8 #include <mbgl/util/string.hpp>
9 #include <mbgl/util/version.hpp>
10
11 #include <QByteArray>
12 #include <QNetworkReply>
13 #include <QPair>
14
15 namespace mbgl {
16
HTTPRequest(HTTPFileSource::Impl * context,const Resource & resource,FileSource::Callback callback)17 HTTPRequest::HTTPRequest(HTTPFileSource::Impl* context, const Resource& resource, FileSource::Callback callback)
18 : m_context(context)
19 , m_resource(resource)
20 , m_callback(callback)
21 {
22 m_context->request(this);
23 }
24
~HTTPRequest()25 HTTPRequest::~HTTPRequest()
26 {
27 if (!m_handled) {
28 m_context->cancel(this);
29 }
30 }
31
requestUrl() const32 QUrl HTTPRequest::requestUrl() const
33 {
34 return QUrl::fromPercentEncoding(QByteArray(m_resource.url.data(), m_resource.url.size()));
35 }
36
networkRequest() const37 QNetworkRequest HTTPRequest::networkRequest() const
38 {
39 QNetworkRequest req = QNetworkRequest(requestUrl());
40 req.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
41
42 static const QByteArray agent = QString("MapboxGL/%1 (Qt %2)").arg(version::revision).arg(QT_VERSION_STR).toLatin1();
43 req.setRawHeader("User-Agent", agent);
44
45 if (m_resource.priorEtag) {
46 const auto etag = m_resource.priorEtag;
47 req.setRawHeader("If-None-Match", QByteArray(etag->data(), etag->size()));
48 } else if (m_resource.priorModified) {
49 req.setRawHeader("If-Modified-Since", util::rfc1123(*m_resource.priorModified).c_str());
50 }
51
52 return req;
53 }
54
handleNetworkReply(QNetworkReply * reply,const QByteArray & data)55 void HTTPRequest::handleNetworkReply(QNetworkReply *reply, const QByteArray& data)
56 {
57 m_handled = true;
58
59 // Calling `callback` may result in deleting `this`.
60 // Copy data to temporaries first.
61 auto callback = m_callback;
62 mbgl::Response response;
63
64 using Error = Response::Error;
65
66 // Handle non-HTTP errors (i.e. like connection).
67 if (reply->error() && reply->error() < 100) {
68 response.error = std::make_unique<Error>(
69 Error::Reason::Connection, reply->errorString().toStdString());
70 callback(response);
71 return;
72 }
73
74 QPair<QByteArray, QByteArray> line;
75 optional<std::string> retryAfter;
76 optional<std::string> xRateLimitReset;
77 foreach(line, reply->rawHeaderPairs()) {
78 QString header = QString(line.first).toLower();
79
80 if (header == "last-modified") {
81 response.modified = util::parseTimestamp(line.second.constData());
82 } else if (header == "etag") {
83 response.etag = std::string(line.second.constData(), line.second.size());
84 } else if (header == "cache-control") {
85 const auto cc = http::CacheControl::parse(line.second.constData());
86 response.expires = cc.toTimePoint();
87 response.mustRevalidate = cc.mustRevalidate;
88 } else if (header == "expires") {
89 response.expires = util::parseTimestamp(line.second.constData());
90 } else if (header == "retry-after") {
91 retryAfter = std::string(line.second.constData(), line.second.size());
92 } else if (header == "x-rate-limit-reset") {
93 xRateLimitReset = std::string(line.second.constData(), line.second.size());
94 }
95 }
96
97 if (reply->url().scheme() == QStringLiteral("data")) {
98 if (data.isEmpty()) {
99 response.data = std::make_shared<std::string>();
100 } else {
101 response.data = std::make_shared<std::string>(data.constData(), data.size());
102 }
103 callback(response);
104 return;
105 }
106
107 int responseCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
108
109 switch(responseCode) {
110 case 200: {
111 if (data.isEmpty()) {
112 response.data = std::make_shared<std::string>();
113 } else {
114 response.data = std::make_shared<std::string>(data.constData(), data.size());
115 }
116 break;
117 }
118 case 204:
119 response.noContent = true;
120 break;
121 case 304:
122 response.notModified = true;
123 break;
124 case 404: {
125 if (m_resource.kind == Resource::Kind::Tile) {
126 response.noContent = true;
127 } else {
128 response.error = std::make_unique<Error>(
129 Error::Reason::NotFound, "HTTP status code 404");
130 }
131 break;
132 }
133 case 429:
134 response.error = std::make_unique<Error>(
135 Error::Reason::RateLimit, "HTTP status code 429",
136 http::parseRetryHeaders(retryAfter, xRateLimitReset));
137 break;
138 default:
139 Response::Error::Reason reason = (responseCode >= 500 && responseCode < 600) ?
140 Error::Reason::Server : Error::Reason::Other;
141
142 response.error = std::make_unique<Error>(
143 reason, "HTTP status code " + util::toString(responseCode));
144 }
145
146 callback(response);
147 }
148
149 } // namespace mbgl
150