summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKonstantin Käfer <mail@kkaefer.com>2014-08-25 16:56:56 +0200
committerKonstantin Käfer <mail@kkaefer.com>2014-09-24 16:12:03 +0200
commitb94635f70430c3659cd57596e49649d376284473 (patch)
tree8d3957bf7c26c8fffeb7f961e0826ab625d5052a
parent28446bed24689b2bac7c8bfbd37741a7fa4fa6be (diff)
downloadqtlocation-mapboxgl-b94635f70430c3659cd57596e49649d376284473.tar.gz
parse cache-control and last-modified headers
-rw-r--r--common/curl_request.cpp31
-rw-r--r--common/foundation_request.mm4
-rw-r--r--include/mbgl/platform/platform.hpp11
-rw-r--r--include/mbgl/platform/response.hpp29
-rw-r--r--linux/mapboxgl-app.gyp2
-rw-r--r--mapboxgl.gyp4
-rw-r--r--src/platform/response.cpp26
-rw-r--r--src/util/filesource.cpp29
8 files changed, 121 insertions, 15 deletions
diff --git a/common/curl_request.cpp b/common/curl_request.cpp
index 416ed90cd1..0957481226 100644
--- a/common/curl_request.cpp
+++ b/common/curl_request.cpp
@@ -286,6 +286,35 @@ size_t curl_write_cb(void *contents, size_t size, size_t nmemb, void *userp) {
return size * nmemb;
}
+// Compares the beginning of the (non-zero-terminated!) data buffer with the (zero-terminated!)
+// header string. If the data buffer contains the header string at the beginning, it returns
+// the length of the header string == begin of the value, otherwise it returns npos.
+// The comparison of the header is ASCII-case-insensitive.
+size_t header_matches(const char *header, const char *buffer, size_t length) {
+ const size_t header_length = strlen(header);
+ if (length < header_length) return std::string::npos;
+ size_t i = 0;
+ while (i < length && i < header_length && std::tolower(buffer[i]) == header[i]) {
+ i++;
+ }
+ return i == header_length ? i : std::string::npos;
+}
+
+size_t curl_header_cb(char *buffer, size_t size, size_t nmemb, void *userp) {
+ const size_t length = size * nmemb;
+
+ size_t begin = std::string::npos;
+ if ((begin = header_matches("last-modified: ", buffer, length)) != std::string::npos) {
+ const std::string value { buffer + begin, length - begin - 2 /* remove \r\n */ };
+ static_cast<Response *>(userp)->setLastModified(value.c_str());
+ } else if ((begin = header_matches("cache-control: ", buffer, length)) != std::string::npos) {
+ const std::string value { buffer + begin, length - begin - 2 /* remove \r\n */ };
+ static_cast<Response *>(userp)->setCacheControl(value.c_str());
+ }
+
+ return length;
+}
+
// This callback is called in the request event loop (on the request thread).
// It initializes newly queued up download requests and adds them to the CURL
// multi handle.
@@ -315,6 +344,8 @@ void async_add_cb(uv_async_t * /*async*/) {
curl_easy_setopt(handle, CURLOPT_URL, (*req)->url.c_str());
curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, curl_write_cb);
curl_easy_setopt(handle, CURLOPT_WRITEDATA, &(*req)->res->body);
+ curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, curl_header_cb);
+ curl_easy_setopt(handle, CURLOPT_HEADERDATA, (*req)->res.get());
curl_easy_setopt(handle, CURLOPT_ACCEPT_ENCODING, "gzip, deflate");
curl_easy_setopt(handle, CURLOPT_SHARE, curl_share);
curl_multi_add_handle(curl_multi, handle);
diff --git a/common/foundation_request.mm b/common/foundation_request.mm
index b7eafdb96c..478dadf4d9 100644
--- a/common/foundation_request.mm
+++ b/common/foundation_request.mm
@@ -91,8 +91,12 @@ mbgl::platform::request_http(const std::string &url,
}
if (!error && [response isKindOfClass:[NSHTTPURLResponse class]]) {
+ NSDictionary *headers = [(NSHTTPURLResponse *)response allHeaderFields];
(*req_ptr)->res->code = [(NSHTTPURLResponse *)response statusCode];
(*req_ptr)->res->body = {(const char *)[data bytes], [data length]};
+ (*req_ptr)->res->setCacheControl([[headers objectForKey:@"Cache-Control"] UTF8String]);
+ (*req_ptr)->res->setLastModified([[headers objectForKey:@"Last-Modified"] UTF8String]);
+
} else {
(*req_ptr)->res->error_message = [[error localizedDescription] UTF8String];
}
diff --git a/include/mbgl/platform/platform.hpp b/include/mbgl/platform/platform.hpp
index 22405a4cfd..e08cac5312 100644
--- a/include/mbgl/platform/platform.hpp
+++ b/include/mbgl/platform/platform.hpp
@@ -1,10 +1,11 @@
#ifndef MBGL_PLATFORM_PLATFORM
#define MBGL_PLATFORM_PLATFORM
+#include <mbgl/platform/response.hpp>
+
#include <mbgl/util/uv.hpp>
#include <memory>
-#include <functional>
#include <string>
namespace mbgl {
@@ -12,14 +13,6 @@ namespace platform {
class Request;
-struct Response {
- Response(std::function<void(Response *)> callback) : callback(callback) {}
- int16_t code = -1;
- std::string body;
- std::string error_message;
- std::function<void(Response *)> callback;
-};
-
// Makes an HTTP request of a URL, preferrably on a background thread, and calls a function with the
// results in the original thread (which runs the libuv loop).
// If the loop pointer is NULL, the callback function will be called on an arbitrary thread.
diff --git a/include/mbgl/platform/response.hpp b/include/mbgl/platform/response.hpp
new file mode 100644
index 0000000000..345a2ee3df
--- /dev/null
+++ b/include/mbgl/platform/response.hpp
@@ -0,0 +1,29 @@
+#ifndef MBGL_PLATFORM_RESPONSE
+#define MBGL_PLATFORM_RESPONSE
+
+#include <string>
+#include <functional>
+#include <ctime>
+
+#include <mbgl/util/noncopyable.hpp>
+
+namespace mbgl {
+namespace platform {
+
+struct Response : private util::noncopyable {
+ Response(std::function<void(Response *)> callback) : callback(callback) {}
+ int16_t code = -1;
+ std::string body;
+ std::string error_message;
+ std::time_t modified;
+ std::time_t expires;
+ std::function<void(Response *)> callback;
+
+ void setCacheControl(const char *value);
+ void setLastModified(const char *value);
+};
+
+}
+}
+
+#endif
diff --git a/linux/mapboxgl-app.gyp b/linux/mapboxgl-app.gyp
index e19768caf9..2a76fea1d6 100644
--- a/linux/mapboxgl-app.gyp
+++ b/linux/mapboxgl-app.gyp
@@ -32,7 +32,6 @@
],
'OTHER_LDFLAGS': [
'<@(glfw3_libraries)',
- '<@(curl_libraries)',
],
}
},
@@ -46,7 +45,6 @@
'link_settings': {
'libraries': [
'<@(glfw3_libraries)',
- '<@(curl_libraries)',
'-lboost_regex'
],
},
diff --git a/mapboxgl.gyp b/mapboxgl.gyp
index 49c4150aa1..6d8bd3f265 100644
--- a/mapboxgl.gyp
+++ b/mapboxgl.gyp
@@ -139,6 +139,7 @@
'OTHER_CPLUSPLUSFLAGS':[
'<@(png_cflags)',
'<@(uv_cflags)',
+ '<@(curl_cflags)',
'<@(sqlite3_cflags)',
'-I<(boost_root)/include',
],
@@ -149,6 +150,7 @@
}, {
'cflags': [
'<@(png_cflags)',
+ '<@(curl_cflags)',
'<@(sqlite3_cflags)',
'-I<(boost_root)/include',
],
@@ -177,12 +179,14 @@
'OTHER_LDFLAGS': [
'<@(png_libraries)',
'<@(uv_libraries)',
+ '<@(curl_libraries)',
]
}
}, {
'libraries': [
'<@(png_libraries)',
'<@(uv_libraries)',
+ '<@(curl_libraries)',
]
}]
]
diff --git a/src/platform/response.cpp b/src/platform/response.cpp
new file mode 100644
index 0000000000..f2117c8894
--- /dev/null
+++ b/src/platform/response.cpp
@@ -0,0 +1,26 @@
+#include <mbgl/platform/response.hpp>
+#include <curl/curl.h>
+
+#include <cstdio>
+
+namespace mbgl {
+namespace platform {
+
+
+void Response::setCacheControl(const char *value) {
+ int seconds = 0;
+ // TODO: cache-control may contain other information as well:
+ // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9
+ if (std::sscanf(value, "max-age=%u", &seconds) == 1) {
+ if (std::time(&expires) != -1) {
+ expires += seconds;
+ }
+ }
+}
+
+void Response::setLastModified(const char *value) {
+ modified = curl_getdate(value, nullptr);
+}
+
+}
+}
diff --git a/src/util/filesource.cpp b/src/util/filesource.cpp
index d626092bce..8c14f6e4e0 100644
--- a/src/util/filesource.cpp
+++ b/src/util/filesource.cpp
@@ -32,6 +32,7 @@ void FileSource::createSchema() {
"`url` TEXT PRIMARY KEY NOT NULL,"
"`code` INTEGER NOT NULL,"
"`type` INTEGER NOT NULL,"
+ "`modified` INTEGER,"
"`expires` INTEGER,"
"`data` BLOB,"
"`compressed` INTEGER NOT NULL DEFAULT 0"
@@ -143,7 +144,7 @@ void FileSource::saveFile(ResourceType type, const std::string &url, platform::R
sqlite3_stmt *stmt = nullptr;
int err;
- err = sqlite3_prepare_v2(db, "REPLACE INTO `http_cache` (`url`, `code`, `type`, `data`, `compressed`) VALUES(?, ?, ?, ?, ?)", -1, &stmt, nullptr);
+ err = sqlite3_prepare_v2(db, "REPLACE INTO `http_cache` (`url`, `code`, `type`, `modified`, `expires`, `data`, `compressed`) VALUES(?, ?, ?, ?, ?, ?, ?)", -1, &stmt, nullptr);
if (err != SQLITE_OK) {
Log::Warning(Event::Database, "%s: %s", sqlite3_errstr(err), sqlite3_errmsg(db));
return;
@@ -179,15 +180,35 @@ void FileSource::saveFile(ResourceType type, const std::string &url, platform::R
return;
}
+ err = sqlite3_bind_int(stmt, 4, res->modified);
+ if (err != SQLITE_OK) {
+ Log::Warning(Event::Database, "%s: %s", sqlite3_errstr(err), sqlite3_errmsg(db));
+ err = sqlite3_finalize(stmt);
+ if (err != SQLITE_OK) {
+ Log::Warning(Event::Database, "%s: %s", sqlite3_errstr(err), sqlite3_errmsg(db));
+ }
+ return;
+ }
+
+ err = sqlite3_bind_int(stmt, 5, res->expires);
+ if (err != SQLITE_OK) {
+ Log::Warning(Event::Database, "%s: %s", sqlite3_errstr(err), sqlite3_errmsg(db));
+ err = sqlite3_finalize(stmt);
+ if (err != SQLITE_OK) {
+ Log::Warning(Event::Database, "%s: %s", sqlite3_errstr(err), sqlite3_errmsg(db));
+ }
+ return;
+ }
+
bool compressed = false;
switch (type) {
case ResourceType::Image:
- err = sqlite3_bind_blob(stmt, 4, res->body.data(), res->body.size(), SQLITE_STATIC);
+ err = sqlite3_bind_blob(stmt, 6, res->body.data(), res->body.size(), SQLITE_STATIC);
break;
default:
const std::string *data = new std::string(std::move(util::compress(res->body)));
compressed = true;
- err = sqlite3_bind_blob(stmt, 4, data->data(), data->size(), [](void *p) { delete (std::string *)p; });
+ err = sqlite3_bind_blob(stmt, 6, data->data(), data->size(), [](void *p) { delete (std::string *)p; });
break;
}
if (err != SQLITE_OK) {
@@ -199,7 +220,7 @@ void FileSource::saveFile(ResourceType type, const std::string &url, platform::R
return;
}
- err = sqlite3_bind_int(stmt, 5, compressed);
+ err = sqlite3_bind_int(stmt, 7, compressed);
if (err != SQLITE_OK) {
Log::Warning(Event::Database, "%s: %s", sqlite3_errstr(err), sqlite3_errmsg(db));
err = sqlite3_finalize(stmt);