summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKonstantin Käfer <mail@kkaefer.com>2014-09-26 16:10:39 +0200
committerKonstantin Käfer <mail@kkaefer.com>2014-09-26 16:10:39 +0200
commitb9628c86543ffe819b030c3d84f65fa09e084850 (patch)
treee98e154b307a6a2b9eaa88c40d9c7fff8702e308
parent1209744b38da0252731812d4cc5371be960fb3c3 (diff)
downloadqtlocation-mapboxgl-b9628c86543ffe819b030c3d84f65fa09e084850.tar.gz
add etag support and if-none-match/if-modified-since to cocoa http handling
-rw-r--r--common/curl_request.cpp414
-rw-r--r--common/http_request_baton_cocoa.mm22
-rw-r--r--common/http_request_baton_curl.cpp141
-rw-r--r--common/ios.mm8
-rw-r--r--common/linux.cpp12
-rw-r--r--common/osx.mm31
-rw-r--r--include/mbgl/storage/http_request.hpp2
-rw-r--r--include/mbgl/storage/response.hpp1
m---------ios/mapbox-gl-cocoa0
-rw-r--r--linux/mapboxgl-app.gyp1
-rw-r--r--macosx/mapboxgl-app.gyp1
-rw-r--r--src/storage/http_request.cpp28
-rw-r--r--src/storage/sqlite_store.cpp31
13 files changed, 188 insertions, 504 deletions
diff --git a/common/curl_request.cpp b/common/curl_request.cpp
deleted file mode 100644
index 456bbfd6d1..0000000000
--- a/common/curl_request.cpp
+++ /dev/null
@@ -1,414 +0,0 @@
-//
-//#include <mbgl/platform/platform.hpp>
-//#include <mbgl/platform/request.hpp>
-//#include <mbgl/util/uv_detail.hpp>
-//#include <mbgl/util/std.hpp>
-//
-//#include <queue>
-//#include <boost/lockfree/queue.hpp>
-//
-//#include <curl/curl.h>
-//
-//// This file contains code from http://curl.haxx.se/libcurl/c/multi-uv.html:
-//
-///***************************************************************************
-// * _ _ ____ _
-// * Project ___| | | | _ \| |
-// * / __| | | | |_) | |
-// * | (__| |_| | _ <| |___
-// * \___|\___/|_| \_\_____|
-// *
-// * Copyright (C) 1998 - 2013, Daniel Stenberg, <daniel@haxx.se>, et al.
-// *
-// * This software is licensed as described in the file COPYING, which
-// * you should have received as part of this distribution. The terms
-// * are also available at http://curl.haxx.se/docs/copyright.html.
-// *
-// * You may opt to use, copy, modify, merge, publish, distribute and/or sell
-// * copies of the Software, and permit persons to whom the Software is
-// * furnished to do so, under the terms of the COPYING file.
-// *
-// * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
-// * KIND, either express or implied.
-// *
-// ***************************************************************************/
-//
-///* Example application code using the multi socket interface to download
-// multiple files at once, but instead of using curl_multi_perform and
-// curl_multi_wait, which uses select(), we use libuv.
-// It supports epoll, kqueue, etc. on unixes and fast IO completion ports on
-// Windows, which means, it should be very fast on all platforms..
-//
-// Written by Clemens Gruber, based on an outdated example from uvbook and
-// some tests from libuv.
-//
-// Requires libuv and (of course) libcurl.
-//
-// See http://nikhilm.github.com/uvbook/ for more information on libuv.
-//*/
-//
-//namespace mbgl {
-//namespace platform {
-//namespace request {
-//
-//struct curl_context {
-// uv_poll_t poll_handle;
-// curl_socket_t sockfd;
-//};
-//
-//// Handles the request thread + messaging to the thread.
-//static uv_thread_t thread;
-//static uv::once init_thread_once;
-//static uv_loop_t *loop = nullptr;
-//static uv_async_t async_add;
-//static uv_async_t async_cancel;
-//
-//// Stores pointers (!) to shared_ptrs. We use shared_ptrs so that request objects don't get
-//// auto-destructed while they're in progress. The TileData object retains a weak_ptr to this
-//// request, so we have to use a shared_ptr here to ensure that this object stays alive.
-//static boost::lockfree::queue<std::shared_ptr<mbgl::platform::Request> *> add_queue(8);
-//static boost::lockfree::queue<std::shared_ptr<mbgl::platform::Request> *> cancel_queue(8);
-//
-//// Used as the CURL timer function to periodically check for socket updates.
-//static uv_timer_t timeout;
-//
-//// CURL multi handle that we use to request multiple URLs at the same time, without having to block
-//// and spawn threads.
-//static CURLM *curl_multi = nullptr;
-//
-//// CURL share handles are used for sharing session state (e.g.)
-//static uv::mutex curl_share_mutex;
-//static CURLSH *curl_share = nullptr;
-//
-//// A queue that we use for storing resuable CURL easy handles to avoid creating and destroying them
-//// all the time.
-//static std::queue<CURL *> curl_handle_cache;
-//
-//
-//class CURLRequest : public mbgl::platform::Request {
-//public:
-// CURLRequest(const std::string &url,
-// std::function<void(mbgl::platform::Response *)> callback,
-// std::shared_ptr<uv::loop> loop)
-// : Request(url, callback, loop) {}
-//
-// CURL *curl = nullptr;
-//};
-//
-//
-//// Implementation starts here.
-//
-//// Locks the CURL share handle
-//void curl_share_lock(CURL *, curl_lock_data, curl_lock_access, void *) { curl_share_mutex.lock(); }
-//
-//// Unlocks the CURL share handle
-//void curl_share_unlock(CURL *, curl_lock_data, void *) { curl_share_mutex.unlock(); }
-//
-//curl_context *create_curl_context(curl_socket_t sockfd) {
-// curl_context *context = new curl_context;
-// context->sockfd = sockfd;
-//
-// uv_poll_init_socket(loop, &context->poll_handle, sockfd);
-// context->poll_handle.data = context;
-//
-// return context;
-//}
-//
-//void curl_close_cb(uv_handle_t *handle) {
-// curl_context *context = (curl_context *)handle->data;
-// free(context);
-//}
-//
-//void destroy_curl_context(curl_context *context) {
-// uv_close((uv_handle_t *)&context->poll_handle, curl_close_cb);
-//}
-//
-//void remove_curl_handle(CURL *handle) {
-// CURLMcode error = curl_multi_remove_handle(curl_multi, handle);
-// if (error != CURLM_OK) {
-// throw std::runtime_error(std::string("CURL multi error: ") + curl_multi_strerror(error));
-// }
-//
-// curl_easy_reset(handle);
-// curl_handle_cache.push(handle);
-//}
-//
-//void curl_perform(uv_poll_t *req, int /*status*/, int events) {
-// int running_handles;
-// int flags = 0;
-// curl_context *context;
-// CURLMsg *message;
-// int pending;
-//
-// uv_timer_stop(&timeout);
-//
-// if (events & UV_READABLE)
-// flags |= CURL_CSELECT_IN;
-// if (events & UV_WRITABLE)
-// flags |= CURL_CSELECT_OUT;
-//
-// context = (curl_context *)req;
-//
-// curl_multi_socket_action(curl_multi, context->sockfd, flags, &running_handles);
-//
-// while ((message = curl_multi_info_read(curl_multi, &pending))) {
-// switch (message->msg) {
-// case CURLMSG_DONE: {
-// std::shared_ptr<Request> *req = nullptr;
-// curl_easy_getinfo(message->easy_handle, CURLINFO_PRIVATE, (char *)&req);
-//
-// // Add human-readable error code
-// if (message->data.result != CURLE_OK) {
-// (*req)->res->error_message = curl_easy_strerror(message->data.result);
-// (*req)->res->code = -1;
-// } else {
-// curl_easy_getinfo(message->easy_handle, CURLINFO_RESPONSE_CODE, &(*req)->res->code);
-// }
-//
-// // We're currently in the CURL request thread. We're going to schedule a uv_work request
-// // that executes the background function in a threadpool, and tell it to call the
-// // after callback back in the main uv loop.
-// (*req)->complete();
-//
-// CURL *handle = message->easy_handle;
-// remove_curl_handle(handle);
-//
-// // We're setting this to NULL because there might still be shared_ptrs around that could
-// // be cancelled.
-// ((CURLRequest *)req->get())->curl = nullptr;
-//
-// // Delete the shared_ptr pointer we created earlier.
-// delete req;
-// break;
-// }
-//
-// default:
-// // This should never happen, because there are no other message types.
-// throw std::runtime_error("CURLMSG returned unknown message type");
-// }
-// }
-//}
-//
-//int handle_socket(CURL * /*easy*/, curl_socket_t s, int action, void * /*userp*/, void *socketp) {
-// curl_context *context = nullptr;
-//
-// if (socketp) {
-// context = (curl_context *)socketp;
-// } else if (action != CURL_POLL_REMOVE) {
-// context = create_curl_context(s);
-// }
-//
-// if (context) {
-// curl_multi_assign(curl_multi, s, (void *)context);
-// if (action == CURL_POLL_IN || action == CURL_POLL_INOUT) {
-// uv_poll_start(&context->poll_handle, UV_READABLE, curl_perform);
-// }
-// if (action == CURL_POLL_OUT || action == CURL_POLL_INOUT) {
-// uv_poll_start(&context->poll_handle, UV_WRITABLE, curl_perform);
-// }
-// if (action == CURL_POLL_REMOVE && socketp) {
-// uv_poll_stop(&context->poll_handle);
-// destroy_curl_context(context);
-// curl_multi_assign(curl_multi, s, NULL);
-// }
-// }
-//
-// return 0;
-//}
-//
-//void on_timeout(uv_timer_t * /*req*/) {
-// int running_handles;
-// CURLMcode error =
-// curl_multi_socket_action(curl_multi, CURL_SOCKET_TIMEOUT, 0, &running_handles);
-// if (error != CURLM_OK) {
-// throw std::runtime_error(std::string("CURL multi error: ") + curl_multi_strerror(error));
-// }
-//}
-//
-//void start_timeout(CURLM * /*multi*/, long timeout_ms, void * /*userp*/) {
-// if (timeout_ms <= 0) {
-// on_timeout(&timeout);
-// } else {
-// uv_timer_start(&timeout, on_timeout, timeout_ms, 0);
-// }
-//}
-//
-//// This function is the first function called in the request thread. It sets up the CURL share/multi
-//// handles and runs the thread loop.
-//void thread_init(void * /*ptr*/) {
-// uv_timer_init(loop, &timeout);
-//
-// CURLSHcode share_error;
-// curl_share = curl_share_init();
-//
-// share_error = curl_share_setopt(curl_share, CURLSHOPT_LOCKFUNC, curl_share_lock);
-// if (share_error != CURLSHE_OK) {
-// throw std::runtime_error(std::string("CURL share error: ") + curl_share_strerror(share_error));
-// }
-//
-// share_error = curl_share_setopt(curl_share, CURLSHOPT_UNLOCKFUNC, curl_share_unlock);
-// if (share_error != CURLSHE_OK) {
-// throw std::runtime_error(std::string("CURL share error: ") + curl_share_strerror(share_error));
-// }
-//
-// CURLMcode multi_error;
-// curl_multi = curl_multi_init();
-//
-// multi_error = curl_multi_setopt(curl_multi, CURLMOPT_SOCKETFUNCTION, handle_socket);
-// if (multi_error != CURLM_OK) {
-// throw std::runtime_error(std::string("CURL multi error: ") + curl_multi_strerror(multi_error));
-// }
-// multi_error = curl_multi_setopt(curl_multi, CURLMOPT_TIMERFUNCTION, start_timeout);
-// if (multi_error != CURLM_OK) {
-// throw std::runtime_error(std::string("CURL multi error: ") + curl_multi_strerror(multi_error));
-//
-// }
-//
-// // Main event loop. This will not return until the request loop is terminated.
-// uv_run(loop, UV_RUN_DEFAULT);
-//
-// curl_multi_cleanup(curl_multi);
-// curl_multi = nullptr;
-// curl_share_cleanup(curl_share);
-// curl_share = nullptr;
-//
-// // Clean up all the CURL easy handles that we kept around for potential future reuse.
-// while (!curl_handle_cache.empty()) {
-// curl_easy_cleanup(curl_handle_cache.front());
-// curl_handle_cache.pop();
-// }
-//}
-//
-//// This function is called when we have new data for a request. We just append it to the string
-//// containing the previous data.
-//size_t curl_write_cb(void *contents, size_t size, size_t nmemb, void *userp) {
-// ((std::string *)userp)->append((char *)contents, size * nmemb);
-// 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.
-//void async_add_cb(uv_async_t * /*async*/) {
-// std::shared_ptr<Request> *req = nullptr;
-// while (add_queue.pop(req)) {
-// // Make sure that we're not starting requests that have been cancelled
-// // already by async_cancel_cb.
-// if ((*req)->cancelled) {
-// delete req;
-// continue;
-// }
-//
-// // Obtain a curl handle (and try to reuse existing handles before creating new ones).
-// CURL *handle = nullptr;
-// if (!curl_handle_cache.empty()) {
-// handle = curl_handle_cache.front();
-// curl_handle_cache.pop();
-// } else {
-// handle = curl_easy_init();
-// }
-//
-// ((CURLRequest *)req->get())->curl = handle;
-//
-// curl_easy_setopt(handle, CURLOPT_PRIVATE, req);
-// curl_easy_setopt(handle, CURLOPT_CAINFO, "ca-bundle.crt");
-// 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);
-// }
-//}
-//
-//void async_cancel_cb(uv_async_t * /*async*/) {
-// std::shared_ptr<Request> *req = nullptr;
-// while (cancel_queue.pop(req)) {
-// // It is possible that the request has not yet been started, but that it already has been
-// // added to the queue for scheduling new requests. In this case, the CURL handle is invalid
-// // and we manually mark the Request as cancelled.
-// CURL *handle = ((CURLRequest *)req->get())->curl;
-// if (handle && !(*req)->cancelled) {
-// remove_curl_handle(handle);
-// ((CURLRequest *)req->get())->curl = nullptr;
-// }
-// (*req)->cancelled = true;
-//
-// delete req;
-// req = nullptr;
-// }
-//}
-//
-//void thread_init_cb() {
-// curl_global_init(CURL_GLOBAL_ALL);
-//
-// loop = uv_loop_new();
-// uv_async_init(loop, &async_add, async_add_cb);
-// uv_async_init(loop, &async_cancel, async_cancel_cb);
-// uv_thread_create(&thread, thread_init, nullptr);
-//}
-//} // end namespace request
-//} // end namespace platform
-//
-//
-//std::shared_ptr<platform::Request>
-//platform::request_http(const std::string &url,
-// std::function<void(Response *)> callback,
-// std::shared_ptr<uv::loop> loop) {
-// using namespace request;
-// init_thread_once(thread_init_cb);
-// std::shared_ptr<CURLRequest> req = std::make_shared<CURLRequest>(url, callback, loop);
-//
-// // Note that we are creating a new shared_ptr pointer(!) because the lockless queue can't store
-// // objects with nontrivial destructors. We have to make absolutely sure that we manually delete
-// // the shared_ptr when we pop it from the queue.
-// add_queue.push(new std::shared_ptr<Request>(req));
-// uv_async_send(&async_add);
-//
-// return req;
-//}
-//
-//// Cancels an HTTP request.
-//void platform::cancel_request_http(const std::shared_ptr<Request> &req) {
-// if (req) {
-// using namespace request;
-//
-// // Note that we are creating a new shared_ptr pointer(!) because the lockless queue can't
-// // store objects with nontrivial destructors. We have to make absolutely shure that we
-// // manually delete the shared_ptr when we pop it from the queue.
-// cancel_queue.push(new std::shared_ptr<Request>(req));
-// uv_async_send(&async_cancel);
-// }
-//}
-//} // end namespace mbgl
diff --git a/common/http_request_baton_cocoa.mm b/common/http_request_baton_cocoa.mm
index 497d55653a..2ebbec481c 100644
--- a/common/http_request_baton_cocoa.mm
+++ b/common/http_request_baton_cocoa.mm
@@ -35,13 +35,16 @@ void HTTPRequestBaton::start(const util::ptr<HTTPRequestBaton> &ptr) {
});
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:@(baton->path.c_str())]];
- if (baton->response && baton->response->modified) {
- struct tm *timeinfo;
- char buffer[32];
- const time_t modified = baton->response->modified;
- timeinfo = std::gmtime(&modified);
- strftime_l(buffer, 32, "%a, %d %b %Y %H:%M:%S GMT", timeinfo, locale);
- [request addValue:@(buffer) forHTTPHeaderField:@"If-Modified-Since"];
+ if (baton->response) {
+ if (!baton->response->etag.empty()) {
+ [request addValue:@(baton->response->etag.c_str()) forHTTPHeaderField:@"If-None-Match"];
+ } else if (baton->response->modified) {
+ const time_t modified = baton->response->modified;
+ struct tm *timeinfo = std::gmtime(&modified);
+ char buffer[32];
+ strftime_l(buffer, 32, "%a, %d %b %Y %H:%M:%S GMT", timeinfo, locale);
+ [request addValue:@(buffer) forHTTPHeaderField:@"If-Modified-Since"];
+ }
}
NSURLSessionDataTask *task = [session dataTaskWithRequest:request
@@ -113,6 +116,11 @@ void HTTPRequestBaton::start(const util::ptr<HTTPRequestBaton> &ptr) {
if (last_modified) {
baton->response->modified = parse_date([last_modified UTF8String]);
}
+
+ NSString *etag = [headers objectForKey:@"ETag"];
+ if (etag) {
+ baton->response->etag = [etag UTF8String];
+ }
} else {
// This should never happen.
baton->type = HTTPResponseType::PermanentError;
diff --git a/common/http_request_baton_curl.cpp b/common/http_request_baton_curl.cpp
index 55147fbfc7..8cf6a903fe 100644
--- a/common/http_request_baton_curl.cpp
+++ b/common/http_request_baton_curl.cpp
@@ -8,6 +8,8 @@
#include <queue>
#include <cassert>
+#include <ctime>
+#include <xlocale.h>
// This file contains code from http://curl.haxx.se/libcurl/c/multi-uv.html:
@@ -72,9 +74,25 @@ static std::queue<CURL *> handles;
namespace mbgl {
-struct curl_context {
- uv_poll_t poll_handle;
- curl_socket_t sockfd;
+struct CURLContext {
+ util::ptr<HTTPRequestBaton> baton;
+ uv_poll_t *poll_handle = nullptr;
+ curl_socket_t sockfd = 0;
+ curl_slist *headers = nullptr;
+
+ CURLContext(const util::ptr<HTTPRequestBaton> &baton_) : baton(baton_) {
+ }
+
+ ~CURLContext() {
+ // We are destructing the poll handle in a CURL callback already.
+ assert(!poll_handle);
+
+ // Once the CURLContext gets destroyed, CURL doesn't need any headers anymore.
+ if (headers) {
+ curl_slist_free_all(headers);
+ headers = nullptr;
+ }
+ }
};
// Locks the CURL share handle
@@ -87,22 +105,6 @@ void curl_share_unlock(CURL *, curl_lock_data, void *) {
uv_mutex_unlock(&share_mutex);
}
-curl_context *create_curl_context(curl_socket_t sockfd) {
- curl_context *context = new curl_context;
- context->sockfd = sockfd;
-
- uv_poll_init_socket(&loop, &context->poll_handle, sockfd);
- context->poll_handle.data = context;
-
- return context;
-}
-
-void destroy_curl_context(curl_context *context) {
- uv_close((uv_handle_t *)&context->poll_handle, [](uv_handle_t *handle) {
- delete (curl_context *)handle->data;
- });
-}
-
// This function must run in the CURL thread.
// It is either called when the request is completed, or when we try to cancel the request.
void finish_request(const util::ptr<HTTPRequestBaton> &baton) {
@@ -117,10 +119,12 @@ void finish_request(const util::ptr<HTTPRequestBaton> &baton) {
}
// Destroy the shared pointer. We still have one pointing to it
- util::ptr<HTTPRequestBaton> *baton_ptr = nullptr;
- curl_easy_getinfo(handle, CURLINFO_PRIVATE, (char *)&baton_ptr);
+ CURLContext *context = nullptr;
+ curl_easy_getinfo(handle, CURLINFO_PRIVATE, (char *)&context);
curl_easy_setopt(handle, CURLOPT_PRIVATE, nullptr);
- delete baton_ptr;
+ delete context;
+
+ // TODO: delete the headers object again.
curl_easy_reset(handle);
handles.push(handle);
@@ -128,10 +132,10 @@ void finish_request(const util::ptr<HTTPRequestBaton> &baton) {
}
}
-void curl_perform(uv_poll_t *req, int status, int events) {
+void curl_perform(uv_poll_t *req, int, int events) {
int running_handles;
int flags = 0;
- curl_context *context = (curl_context *)req;
+ CURLContext *context = (CURLContext *)req->data;
CURLMsg *message;
int pending;
@@ -149,9 +153,11 @@ void curl_perform(uv_poll_t *req, int status, int events) {
while ((message = curl_multi_info_read(multi, &pending))) {
switch (message->msg) {
case CURLMSG_DONE: {
- util::ptr<HTTPRequestBaton> *baton_ptr = nullptr;
- curl_easy_getinfo(message->easy_handle, CURLINFO_PRIVATE, (char *)&baton_ptr);
- util::ptr<HTTPRequestBaton> baton = *baton_ptr;
+ CURLContext *context = nullptr;
+ curl_easy_getinfo(message->easy_handle, CURLINFO_PRIVATE, (char *)&context);
+
+ // Make a copy so that the Baton stays around even after we are calling finish_request
+ util::ptr<HTTPRequestBaton> baton = context->baton;
// Add human-readable error code
if (message->data.result != CURLE_OK) {
@@ -211,34 +217,42 @@ void curl_perform(uv_poll_t *req, int status, int events) {
}
}
-int handle_socket(CURL *easy, curl_socket_t s, int action, void * userp, void *socketp) {
- curl_context *context = nullptr;
-
- if (socketp) {
- context = (curl_context *)socketp;
- } else if (action != CURL_POLL_REMOVE) {
- context = create_curl_context(s);
+int handle_socket(CURL *handle, curl_socket_t sockfd, int action, void *, void *socketp) {
+ CURLContext *context = nullptr;
+ curl_easy_getinfo(handle, CURLINFO_PRIVATE, (char *)&context);
+
+ if (!socketp && action != CURL_POLL_REMOVE) {
+ // We haven't initialized the socket yet, so we need to do this now.
+ assert(context->sockfd == 0);
+ context->sockfd = sockfd;
+ assert(!context->poll_handle);
+ context->poll_handle = new uv_poll_t;
+ uv_poll_init_socket(&loop, context->poll_handle, sockfd);
+ context->poll_handle->data = context;
+ curl_multi_assign(multi, sockfd, context);
}
if (context) {
- curl_multi_assign(multi, s, (void *)context);
if (action == CURL_POLL_IN || action == CURL_POLL_INOUT) {
- uv_poll_start(&context->poll_handle, UV_READABLE, curl_perform);
+ uv_poll_start(context->poll_handle, UV_READABLE, curl_perform);
}
if (action == CURL_POLL_OUT || action == CURL_POLL_INOUT) {
- uv_poll_start(&context->poll_handle, UV_WRITABLE, curl_perform);
+ uv_poll_start(context->poll_handle, UV_WRITABLE, curl_perform);
}
if (action == CURL_POLL_REMOVE && socketp) {
- uv_poll_stop(&context->poll_handle);
- destroy_curl_context(context);
- curl_multi_assign(multi, s, NULL);
+ uv_poll_stop(context->poll_handle);
+ uv_close((uv_handle_t *)context->poll_handle, [](uv_handle_t *handle) {
+ delete (uv_poll_t *)handle;
+ });
+ context->poll_handle = nullptr;
+ curl_multi_assign(multi, sockfd, NULL);
}
}
return 0;
}
-void on_timeout(uv_timer_t *req) {
+void on_timeout(uv_timer_t *) {
int running_handles;
CURLMcode error = curl_multi_socket_action(multi, CURL_SOCKET_TIMEOUT, 0, &running_handles);
if (error != CURLM_OK) {
@@ -246,7 +260,7 @@ void on_timeout(uv_timer_t *req) {
}
}
-void start_timeout(CURLM * multi, long timeout_ms, void * userp) {
+void start_timeout(CURLM *, long timeout_ms, void *) {
if (timeout_ms <= 0) {
on_timeout(&timeout);
} else {
@@ -325,13 +339,19 @@ size_t header_matches(const char *const header, const char *const buffer, const
size_t curl_header_cb(char * const buffer, const size_t size, const size_t nmemb, void *const userp) {
const size_t length = size * nmemb;
+ Response *response = static_cast<Response *>(userp);
+
size_t begin = std::string::npos;
if ((begin = header_matches("last-modified: ", buffer, length)) != std::string::npos) {
+ // Always overwrite the modification date; We might already have a value here from the
+ // Date header, but this one is more accurate.
const std::string value { buffer + begin, length - begin - 2 }; // remove \r\n
- static_cast<Response *>(userp)->modified = curl_getdate(value.c_str(), nullptr);
+ response->modified = curl_getdate(value.c_str(), nullptr);
+ } else if ((begin = header_matches("etag: ", buffer, length)) != std::string::npos) {
+ response->etag = { buffer + begin, length - begin - 2 }; // remove \r\n
} 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)->expires = Response::parseCacheControl(value.c_str());
+ response->expires = Response::parseCacheControl(value.c_str());
}
return length;
@@ -340,9 +360,13 @@ size_t curl_header_cb(char * const buffer, const size_t size, const size_t nmemb
// This function must run in the CURL thread.
void start_request(void *const ptr) {
assert(uv_thread_self() == thread_id);
- util::ptr<HTTPRequestBaton> &baton = *(util::ptr<HTTPRequestBaton> *)ptr;
+ std::unique_ptr<util::ptr<HTTPRequestBaton>> baton_guard { (util::ptr<HTTPRequestBaton> *)ptr };
+ util::ptr<HTTPRequestBaton> &baton = *baton_guard.get();
assert(baton);
+ // Create a C locale
+ static locale_t locale = newlocale(LC_ALL_MASK, nullptr, nullptr);
+
CURL *handle = nullptr;
if (!handles.empty()) {
handle = handles.front();
@@ -353,12 +377,32 @@ void start_request(void *const ptr) {
baton->ptr = handle;
+ // Wrap this in a unique_ptr for now so that it destructs until we assign it the the CURL handle.
+ std::unique_ptr<CURLContext> context = std::make_unique<CURLContext>(baton);
+
+ if (baton->response) {
+ if (!baton->response->etag.empty()) {
+ const std::string header = std::string("If-None-Match: ") + baton->response->etag;
+ context->headers = curl_slist_append(context->headers, header.c_str());
+ } else if (baton->response->modified) {
+ const time_t modified = baton->response->modified;
+ struct tm *timeinfo = std::gmtime(&modified);
+ char buffer[64];
+ strftime_l(buffer, 64, "If-Modified-Since: %a, %d %b %Y %H:%M:%S GMT", timeinfo, locale);
+ context->headers = curl_slist_append(context->headers, buffer);
+ }
+ }
+
+ if (context->headers) {
+ curl_easy_setopt(handle, CURLOPT_HTTPHEADER, context->headers);
+ }
+
if (!baton->response) {
baton->response = std::make_unique<Response>();
}
// Carry on the shared pointer in the private information of the CURL handle.
- curl_easy_setopt(handle, CURLOPT_PRIVATE, ptr);
+ curl_easy_setopt(handle, CURLOPT_PRIVATE, context.release());
curl_easy_setopt(handle, CURLOPT_CAINFO, "ca-bundle.crt");
curl_easy_setopt(handle, CURLOPT_URL, baton->path.c_str());
curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, curl_write_cb);
@@ -375,7 +419,8 @@ void start_request(void *const ptr) {
// This function must run in the CURL thread.
void stop_request(void *const ptr) {
assert(uv_thread_self() == thread_id);
- util::ptr<HTTPRequestBaton> &baton = *(util::ptr<HTTPRequestBaton> *)ptr;
+ std::unique_ptr<util::ptr<HTTPRequestBaton>> baton_guard { (util::ptr<HTTPRequestBaton> *)ptr };
+ util::ptr<HTTPRequestBaton> &baton = *baton_guard.get();
assert(baton);
if (baton->async) {
@@ -392,8 +437,6 @@ void stop_request(void *const ptr) {
// the pointer below is the last lifeline of the HTTPRequestBaton. This means we're going
// to delete the HTTPRequestBaton in the current (CURL) thread.
}
-
- delete (util::ptr<HTTPRequestBaton> *)ptr;
}
void create_thread() {
diff --git a/common/ios.mm b/common/ios.mm
index 1076343e24..7989e73a4e 100644
--- a/common/ios.mm
+++ b/common/ios.mm
@@ -8,12 +8,14 @@ namespace platform {
// Returns the path to the default cache database on this system.
std::string defaultCacheDatabase() {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
+ if ([paths count] == 0) {
+ // Disable the cache if we don't have a location to write.
+ return "";
+ }
+
NSString *libraryDirectory = [paths objectAtIndex:0];
return [[libraryDirectory stringByAppendingPathComponent:@"cache.db"] UTF8String];
}
}
}
-
-
-// Returns the path to the default cache database on this system.
diff --git a/common/linux.cpp b/common/linux.cpp
new file mode 100644
index 0000000000..6132ace692
--- /dev/null
+++ b/common/linux.cpp
@@ -0,0 +1,12 @@
+#include <mbgl/platform/platform.hpp>
+
+namespace mbgl {
+namespace platform {
+
+// Returns the path to the default cache database on this system.
+std::string defaultCacheDatabase() {
+ return "/tmp/mbgl-cache.db";
+}
+
+}
+}
diff --git a/common/osx.mm b/common/osx.mm
new file mode 100644
index 0000000000..974ea537cb
--- /dev/null
+++ b/common/osx.mm
@@ -0,0 +1,31 @@
+#import <Foundation/Foundation.h>
+
+#include <mbgl/platform/platform.hpp>
+
+namespace mbgl {
+namespace platform {
+
+// Returns the path to the default cache database on this system.
+std::string defaultCacheDatabase() {
+ NSArray *paths =
+ NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
+ if ([paths count] == 0) {
+ // Disable the cache if we don't have a location to write.
+ return "";
+ }
+
+ NSString *path = [[paths objectAtIndex:0] stringByAppendingPathComponent:@"Mapbox GL"];
+
+ if (![[NSFileManager defaultManager] createDirectoryAtPath:path
+ withIntermediateDirectories:YES
+ attributes:nil
+ error:nil]) {
+ // Disable the cache if we couldn't create the directory.
+ return "";
+ }
+
+ return [[path stringByAppendingPathComponent:@"cache.db"] UTF8String];
+}
+
+}
+}
diff --git a/include/mbgl/storage/http_request.hpp b/include/mbgl/storage/http_request.hpp
index 9edefcaaae..c81e23ef8d 100644
--- a/include/mbgl/storage/http_request.hpp
+++ b/include/mbgl/storage/http_request.hpp
@@ -30,7 +30,7 @@ private:
void handleCacheResponse(std::unique_ptr<Response> &&response, uv_loop_t *loop);
void handleHTTPResponse(HTTPResponseType responseType, std::unique_ptr<Response> &&response, uv_loop_t *loop);
- void startRequest(uv_loop_t *loop);
+ void startRequest(std::unique_ptr<Response> &&res, uv_loop_t *loop);
void removeCacheBaton();
void removeHTTPBaton();
diff --git a/include/mbgl/storage/response.hpp b/include/mbgl/storage/response.hpp
index 8125656d81..9357ad3c63 100644
--- a/include/mbgl/storage/response.hpp
+++ b/include/mbgl/storage/response.hpp
@@ -13,6 +13,7 @@ public:
long code = 0;
int64_t modified = 0;
int64_t expires = 0;
+ std::string etag;
std::string data;
std::string message;
diff --git a/ios/mapbox-gl-cocoa b/ios/mapbox-gl-cocoa
-Subproject c22686c5bc9f1cafd37b684bab31b70a38ed649
+Subproject 83a5dbfaac85d16e35aca3acade1b5b151ae600
diff --git a/linux/mapboxgl-app.gyp b/linux/mapboxgl-app.gyp
index 0c9f4bf8ab..dac1934d31 100644
--- a/linux/mapboxgl-app.gyp
+++ b/linux/mapboxgl-app.gyp
@@ -16,6 +16,7 @@
'../common/glfw_view.hpp',
'../common/glfw_view.cpp',
'../common/http_request_baton_curl.cpp',
+ '../common/linux.cpp',
'../common/stderr_log.hpp',
'../common/stderr_log.cpp',
],
diff --git a/macosx/mapboxgl-app.gyp b/macosx/mapboxgl-app.gyp
index 110924e1ea..529da011a3 100644
--- a/macosx/mapboxgl-app.gyp
+++ b/macosx/mapboxgl-app.gyp
@@ -16,6 +16,7 @@
'../common/glfw_view.hpp',
'../common/glfw_view.cpp',
'../common/http_request_baton_cocoa.mm',
+ '../common/osx.mm',
'../common/nslog_log.hpp',
'../common/nslog_log.mm',
],
diff --git a/src/storage/http_request.cpp b/src/storage/http_request.cpp
index edcddb2deb..9dc289e141 100644
--- a/src/storage/http_request.cpp
+++ b/src/storage/http_request.cpp
@@ -32,27 +32,23 @@ HTTPRequest::HTTPRequest(ResourceType type_, const std::string &path, uv_loop_t
// Wrap in a unique_ptr, so it'll always get auto-destructed.
std::unique_ptr<CacheRequestBaton> baton((CacheRequestBaton *)ptr);
if (baton->request) {
- assert(uv_thread_self() == baton->request->thread_id);
+ baton->request->cache_baton = nullptr;
baton->request->handleCacheResponse(std::move(response), baton->loop);
- if (baton->request) {
- // If we called notify(), the request object may already have ceased to exist.
- baton->request->cache_baton = nullptr;
- }
}
}, cache_baton);
}
-void HTTPRequest::handleCacheResponse(std::unique_ptr<Response> &&response, uv_loop_t *loop) {
- if (response) {
+void HTTPRequest::handleCacheResponse(std::unique_ptr<Response> &&res, uv_loop_t *loop) {
+ assert(uv_thread_self() == thread_id);
+
+ if (res) {
// This entry was stored in the cache. Now determine if we need to revalidate.
const int64_t now = std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now().time_since_epoch()).count();
- if (response->expires > now) {
- if (cache_baton->request) {
- cache_baton->request->response = std::move(response);
- cache_baton->request->notify();
- // Note: after calling notify(), the request object may cease to exist.
- }
+ if (res->expires > now) {
+ response = std::move(res);
+ notify();
+ // Note: after calling notify(), the request object may cease to exist.
// This HTTPRequest is completed.
return;
} else {
@@ -60,17 +56,17 @@ void HTTPRequest::handleCacheResponse(std::unique_ptr<Response> &&response, uv_l
}
}
- startRequest(loop);
+ startRequest(std::move(res), loop);
}
-void HTTPRequest::startRequest(uv_loop_t *loop) {
+void HTTPRequest::startRequest(std::unique_ptr<Response> &&res, uv_loop_t *loop) {
assert(uv_thread_self() == thread_id);
assert(!http_baton);
http_baton = std::make_shared<HTTPRequestBaton>(path);
http_baton->request = this;
http_baton->async = new uv_async_t;
- http_baton->response = std::move(response);
+ http_baton->response = std::move(res);
http_baton->async->data = new util::ptr<HTTPRequestBaton>(http_baton);
uv_async_init(loop, http_baton->async, [](uv_async_t *async) {
diff --git a/src/storage/sqlite_store.cpp b/src/storage/sqlite_store.cpp
index 1e59cafd09..043c62a514 100644
--- a/src/storage/sqlite_store.cpp
+++ b/src/storage/sqlite_store.cpp
@@ -58,7 +58,7 @@ namespace mbgl {
SQLiteStore::SQLiteStore(uv_loop_t *loop, const std::string &path)
: thread_id(uv_thread_self()),
- db(std::make_shared<Database>(path.c_str(), ReadWrite | Create)) {
+ db(!path.empty() ? std::make_shared<Database>(path.c_str(), ReadWrite | Create) : nullptr) {
createSchema();
worker = new uv_worker_t;
uv_worker_init(worker, loop, 1, "SQLite");
@@ -84,6 +84,7 @@ void SQLiteStore::createSchema() {
" `code` INTEGER NOT NULL,"
" `type` INTEGER NOT NULL,"
" `modified` INTEGER,"
+ " `etag` TEXT,"
" `expires` INTEGER,"
" `data` BLOB,"
" `compressed` INTEGER NOT NULL DEFAULT 0"
@@ -120,8 +121,8 @@ void SQLiteStore::get(const std::string &path, GetCallback callback, void *ptr)
const std::string url = unifyMapboxURLs(baton->path);
// 0 1 2
Statement stmt = baton->db->prepare("SELECT `code`, `type`, `modified`, "
- // 3 4 5
- "`expires`, `data`, `compressed` FROM `http_cache` WHERE `url` = ?");
+ // 3 4 5 6
+ "`etag`, `expires`, `data`, `compressed` FROM `http_cache` WHERE `url` = ?");
stmt.bind(1, url.c_str());
if (stmt.run()) {
@@ -131,9 +132,10 @@ void SQLiteStore::get(const std::string &path, GetCallback callback, void *ptr)
baton->response->code = stmt.get<int>(0);
baton->type = ResourceType(stmt.get<int>(1));
baton->response->modified = stmt.get<int64_t>(2);
- baton->response->expires = stmt.get<int64_t>(3);
- baton->response->data = stmt.get<std::string>(4);
- if (stmt.get<int>(5)) { // == compressed
+ baton->response->etag = stmt.get<std::string>(3);
+ baton->response->expires = stmt.get<int64_t>(4);
+ baton->response->data = stmt.get<std::string>(5);
+ if (stmt.get<int>(6)) { // == compressed
baton->response->data = util::decompress(baton->response->data);
}
} else {
@@ -170,21 +172,22 @@ void SQLiteStore::put(const std::string &path, ResourceType type, const Response
PutBaton *baton = (PutBaton *)data;
const std::string url = unifyMapboxURLs(baton->path);
Statement stmt = baton->db->prepare("REPLACE INTO `http_cache` ("
- // 1 2 3 4 5 6 7
- "`url`, `code`, `type`, `modified`, `expires`, `data`, `compressed`"
- ") VALUES(?, ?, ?, ?, ?, ?, ?)");
+ // 1 2 3 4 5 6 7 8
+ "`url`, `code`, `type`, `modified`, `etag`, `expires`, `data`, `compressed`"
+ ") VALUES(?, ?, ?, ?, ?, ?, ?, ?)");
stmt.bind(1, url.c_str());
stmt.bind(2, int(baton->response.code));
stmt.bind(3, int(baton->type));
stmt.bind(4, baton->response.modified);
- stmt.bind(5, baton->response.expires);
+ stmt.bind(5, baton->response.etag.c_str());
+ stmt.bind(6, baton->response.expires);
if (baton->type == ResourceType::Image) {
- stmt.bind(6, baton->response.data, false); // do not retain the string internally.
- stmt.bind(7, false);
+ stmt.bind(7, baton->response.data, false); // do not retain the string internally.
+ stmt.bind(8, false);
} else {
- stmt.bind(6, util::compress(baton->response.data), true); // retain the string internally.
- stmt.bind(7, true);
+ stmt.bind(7, util::compress(baton->response.data), true); // retain the string internally.
+ stmt.bind(8, true);
}
stmt.run();