parse cache-control and last-modified headers

This commit is contained in:
Konstantin Käfer
2014-08-25 16:56:56 +02:00
parent 28446bed24
commit b94635f704
8 changed files with 121 additions and 15 deletions

View File

@@ -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);

View File

@@ -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];
}

View File

@@ -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.

View 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

View File

@@ -32,7 +32,6 @@
],
'OTHER_LDFLAGS': [
'<@(glfw3_libraries)',
'<@(curl_libraries)',
],
}
},
@@ -46,7 +45,6 @@
'link_settings': {
'libraries': [
'<@(glfw3_libraries)',
'<@(curl_libraries)',
'-lboost_regex'
],
},

View File

@@ -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
View 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);
}
}
}

View File

@@ -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);