diff options
author | Konstantin Käfer <mail@kkaefer.com> | 2014-09-26 16:10:39 +0200 |
---|---|---|
committer | Konstantin Käfer <mail@kkaefer.com> | 2014-09-26 16:10:39 +0200 |
commit | b9628c86543ffe819b030c3d84f65fa09e084850 (patch) | |
tree | e98e154b307a6a2b9eaa88c40d9c7fff8702e308 | |
parent | 1209744b38da0252731812d4cc5371be960fb3c3 (diff) | |
download | qtlocation-mapboxgl-b9628c86543ffe819b030c3d84f65fa09e084850.tar.gz |
add etag support and if-none-match/if-modified-since to cocoa http handling
-rw-r--r-- | common/curl_request.cpp | 414 | ||||
-rw-r--r-- | common/http_request_baton_cocoa.mm | 22 | ||||
-rw-r--r-- | common/http_request_baton_curl.cpp | 141 | ||||
-rw-r--r-- | common/ios.mm | 8 | ||||
-rw-r--r-- | common/linux.cpp | 12 | ||||
-rw-r--r-- | common/osx.mm | 31 | ||||
-rw-r--r-- | include/mbgl/storage/http_request.hpp | 2 | ||||
-rw-r--r-- | include/mbgl/storage/response.hpp | 1 | ||||
m--------- | ios/mapbox-gl-cocoa | 0 | ||||
-rw-r--r-- | linux/mapboxgl-app.gyp | 1 | ||||
-rw-r--r-- | macosx/mapboxgl-app.gyp | 1 | ||||
-rw-r--r-- | src/storage/http_request.cpp | 28 | ||||
-rw-r--r-- | src/storage/sqlite_store.cpp | 31 |
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(); |