parse cache-control and last-modified headers
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
29
include/mbgl/platform/response.hpp
Normal file
29
include/mbgl/platform/response.hpp
Normal file
@@ -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
|
||||
@@ -32,7 +32,6 @@
|
||||
],
|
||||
'OTHER_LDFLAGS': [
|
||||
'<@(glfw3_libraries)',
|
||||
'<@(curl_libraries)',
|
||||
],
|
||||
}
|
||||
},
|
||||
@@ -46,7 +45,6 @@
|
||||
'link_settings': {
|
||||
'libraries': [
|
||||
'<@(glfw3_libraries)',
|
||||
'<@(curl_libraries)',
|
||||
'-lboost_regex'
|
||||
],
|
||||
},
|
||||
|
||||
@@ -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)',
|
||||
]
|
||||
}]
|
||||
]
|
||||
|
||||
26
src/platform/response.cpp
Normal file
26
src/platform/response.cpp
Normal file
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user