add etag support and if-none-match/if-modified-since to cocoa http handling

This commit is contained in:
Konstantin Käfer
2014-09-26 16:10:39 +02:00
parent 1209744b38
commit b9628c8654
13 changed files with 188 additions and 504 deletions

View File

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

View File

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

View File

@@ -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;
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) {
context = (curl_context *)socketp;
} else if (action != CURL_POLL_REMOVE) {
context = create_curl_context(s);
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() {

View File

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

12
common/linux.cpp Normal file
View File

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

31
common/osx.mm Normal file
View File

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

View File

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

View File

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

View File

@@ -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',
],

View File

@@ -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',
],

View File

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

View File

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