From b94635f70430c3659cd57596e49649d376284473 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Mon, 25 Aug 2014 16:56:56 +0200 Subject: parse cache-control and last-modified headers --- common/curl_request.cpp | 31 +++++++++++++++++++++++++++++++ common/foundation_request.mm | 4 ++++ 2 files changed, 35 insertions(+) (limited to 'common') diff --git a/common/curl_request.cpp b/common/curl_request.cpp index 416ed90cd1..0957481226 100644 --- a/common/curl_request.cpp +++ b/common/curl_request.cpp @@ -286,6 +286,35 @@ size_t curl_write_cb(void *contents, size_t size, size_t nmemb, void *userp) { return size * nmemb; } +// Compares the beginning of the (non-zero-terminated!) data buffer with the (zero-terminated!) +// header string. If the data buffer contains the header string at the beginning, it returns +// the length of the header string == begin of the value, otherwise it returns npos. +// The comparison of the header is ASCII-case-insensitive. +size_t header_matches(const char *header, const char *buffer, size_t length) { + const size_t header_length = strlen(header); + if (length < header_length) return std::string::npos; + size_t i = 0; + while (i < length && i < header_length && std::tolower(buffer[i]) == header[i]) { + i++; + } + return i == header_length ? i : std::string::npos; +} + +size_t curl_header_cb(char *buffer, size_t size, size_t nmemb, void *userp) { + const size_t length = size * nmemb; + + size_t begin = std::string::npos; + if ((begin = header_matches("last-modified: ", buffer, length)) != std::string::npos) { + const std::string value { buffer + begin, length - begin - 2 /* remove \r\n */ }; + static_cast(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(userp)->setCacheControl(value.c_str()); + } + + return length; +} + // This callback is called in the request event loop (on the request thread). // It initializes newly queued up download requests and adds them to the CURL // multi handle. @@ -315,6 +344,8 @@ void async_add_cb(uv_async_t * /*async*/) { curl_easy_setopt(handle, CURLOPT_URL, (*req)->url.c_str()); curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, curl_write_cb); curl_easy_setopt(handle, CURLOPT_WRITEDATA, &(*req)->res->body); + curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, curl_header_cb); + curl_easy_setopt(handle, CURLOPT_HEADERDATA, (*req)->res.get()); curl_easy_setopt(handle, CURLOPT_ACCEPT_ENCODING, "gzip, deflate"); curl_easy_setopt(handle, CURLOPT_SHARE, curl_share); curl_multi_add_handle(curl_multi, handle); diff --git a/common/foundation_request.mm b/common/foundation_request.mm index b7eafdb96c..478dadf4d9 100644 --- a/common/foundation_request.mm +++ b/common/foundation_request.mm @@ -91,8 +91,12 @@ mbgl::platform::request_http(const std::string &url, } if (!error && [response isKindOfClass:[NSHTTPURLResponse class]]) { + NSDictionary *headers = [(NSHTTPURLResponse *)response allHeaderFields]; (*req_ptr)->res->code = [(NSHTTPURLResponse *)response statusCode]; (*req_ptr)->res->body = {(const char *)[data bytes], [data length]}; + (*req_ptr)->res->setCacheControl([[headers objectForKey:@"Cache-Control"] UTF8String]); + (*req_ptr)->res->setLastModified([[headers objectForKey:@"Last-Modified"] UTF8String]); + } else { (*req_ptr)->res->error_message = [[error localizedDescription] UTF8String]; } -- cgit v1.2.1 From d9fc7708a2dfb6e2506a5d10d896a813557c056d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Mon, 15 Sep 2014 17:26:44 +0200 Subject: do 304 requests and cache them in sqlite --- common/foundation_request.h | 1 - common/foundation_request.mm | 119 ------------------------------------------- 2 files changed, 120 deletions(-) delete mode 100644 common/foundation_request.h delete mode 100644 common/foundation_request.mm (limited to 'common') diff --git a/common/foundation_request.h b/common/foundation_request.h deleted file mode 100644 index 0b8c4b8fb0..0000000000 --- a/common/foundation_request.h +++ /dev/null @@ -1 +0,0 @@ -#import diff --git a/common/foundation_request.mm b/common/foundation_request.mm deleted file mode 100644 index 478dadf4d9..0000000000 --- a/common/foundation_request.mm +++ /dev/null @@ -1,119 +0,0 @@ -#import "foundation_request.h" - -#include "TargetConditionals.h" -#if TARGET_OS_IPHONE -#import -#include -#endif - -#include -#include -#include -#include -#include -#include -#include - -dispatch_once_t request_initialize = 0; -NSURLSession *session = nullptr; - -#if TARGET_OS_IPHONE -std::atomic active_tasks; -#endif - - -// We're using a child class to make sure ARC is working correctly, as well as to add activity -// indicators on iOS. -class FoundationRequest : public mbgl::platform::Request { -public: - FoundationRequest(const std::string &url, - std::function callback, - std::shared_ptr loop) - : Request(url, callback, loop) { -#if TARGET_OS_IPHONE - active_tasks++; - dispatch_async(dispatch_get_main_queue(), ^(void) { - [[UIApplication sharedApplication] - setNetworkActivityIndicatorVisible:(active_tasks > 0)]; - }); -#endif - } - - ~FoundationRequest() { -#if TARGET_OS_IPHONE - active_tasks--; - dispatch_async(dispatch_get_main_queue(), ^(void) { - [[UIApplication sharedApplication] - setNetworkActivityIndicatorVisible:(active_tasks > 0)]; - }); -#endif - } - - NSURLSessionDataTask *task = nullptr; -}; - -std::shared_ptr -mbgl::platform::request_http(const std::string &url, - std::function callback, - std::shared_ptr loop) { - dispatch_once(&request_initialize, ^{ - NSURLSessionConfiguration *sessionConfig = - [NSURLSessionConfiguration defaultSessionConfiguration]; - sessionConfig.timeoutIntervalForResource = 30; - sessionConfig.HTTPMaximumConnectionsPerHost = 8; - sessionConfig.requestCachePolicy = NSURLRequestUseProtocolCachePolicy; - - session = [NSURLSession sessionWithConfiguration:sessionConfig]; - -#if TARGET_OS_IPHONE - active_tasks = 0; -#endif - }); - - std::shared_ptr req = - std::make_shared(url, callback, loop); - - // Note that we are creating a new shared_ptr pointer(!) to make sure there is at least one - // shared_ptr in existence while the NSURLSession is loading our data. We are making sure in the - // callback that this pointer gets destroyed again. - std::shared_ptr *req_ptr = new std::shared_ptr(req); - - NSURLSessionDataTask *task = [session - dataTaskWithURL:[NSURL URLWithString:@(url.c_str())] - completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { - if ([error code] == NSURLErrorCancelled) { - // We intentionally cancelled this request. Make sure we clear the shared_ptr to resolve - // the circular reference. We're referencing this shared_ptr by value so that the object - // stays around until this completion handler is invoked. - delete req_ptr; - - return; - } - - if (!error && [response isKindOfClass:[NSHTTPURLResponse class]]) { - NSDictionary *headers = [(NSHTTPURLResponse *)response allHeaderFields]; - (*req_ptr)->res->code = [(NSHTTPURLResponse *)response statusCode]; - (*req_ptr)->res->body = {(const char *)[data bytes], [data length]}; - (*req_ptr)->res->setCacheControl([[headers objectForKey:@"Cache-Control"] UTF8String]); - (*req_ptr)->res->setLastModified([[headers objectForKey:@"Last-Modified"] UTF8String]); - - } else { - (*req_ptr)->res->error_message = [[error localizedDescription] UTF8String]; - } - - (*req_ptr)->complete(); - - delete req_ptr; - }]; - - req->task = task; - - [task resume]; - return req; -} - -void mbgl::platform::cancel_request_http(const std::shared_ptr &req) { - if (req) { - [((FoundationRequest *)(req.get()))->task cancel]; - } -} -- cgit v1.2.1 From ba18c08524cffd3fa03adf49f680f3a80390d61d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Wed, 17 Sep 2014 15:29:24 +0200 Subject: add a callback to stop() to allow running the current thread's event loop --- common/glfw_view.cpp | 6 +++++- common/glfw_view.hpp | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) (limited to 'common') diff --git a/common/glfw_view.cpp b/common/glfw_view.cpp index f53090a000..fb54d55f39 100644 --- a/common/glfw_view.cpp +++ b/common/glfw_view.cpp @@ -183,7 +183,7 @@ int GLFWView::run() { glfwWaitEvents(); } - map->stop(); + map->stop(glfwWaitEvents); return 0; } @@ -192,6 +192,10 @@ void GLFWView::make_active() { glfwMakeContextCurrent(window); } +void GLFWView::notify() { + glfwPostEmptyEvent(); +} + void GLFWView::swap() { glfwPostEmptyEvent(); diff --git a/common/glfw_view.hpp b/common/glfw_view.hpp index d2f6872fc7..481b1598bb 100644 --- a/common/glfw_view.hpp +++ b/common/glfw_view.hpp @@ -17,6 +17,7 @@ public: void initialize(mbgl::Map *map); void swap(); void make_active(); + void notify(); void notify_map_change(mbgl::MapChange change, mbgl::timestamp delay = 0); static void key(GLFWwindow *window, int key, int scancode, int action, int mods); -- cgit v1.2.1 From 111fccb76c1f599470074a75ca77e9de016cca73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Wed, 17 Sep 2014 15:39:16 +0200 Subject: option to pass data parameter, and add more explanation --- common/glfw_view.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'common') diff --git a/common/glfw_view.cpp b/common/glfw_view.cpp index fb54d55f39..edfb3a6a6f 100644 --- a/common/glfw_view.cpp +++ b/common/glfw_view.cpp @@ -183,7 +183,9 @@ int GLFWView::run() { glfwWaitEvents(); } - map->stop(glfwWaitEvents); + map->stop([](void *) { + glfwWaitEvents(); + }); return 0; } -- cgit v1.2.1 From ec3183754ead692ee40bdf2887fe65914de1ed7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Mon, 22 Sep 2014 15:01:02 +0200 Subject: move cocoa nsurlrequest code to common folder --- common/http_request_baton_cocoa.mm | 150 +++++++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 common/http_request_baton_cocoa.mm (limited to 'common') diff --git a/common/http_request_baton_cocoa.mm b/common/http_request_baton_cocoa.mm new file mode 100644 index 0000000000..eaf75ad4d1 --- /dev/null +++ b/common/http_request_baton_cocoa.mm @@ -0,0 +1,150 @@ +#include +#include +#include + +#include + +#include + +#import +#include +#include + +namespace mbgl { + +dispatch_once_t request_initialize = 0; +NSURLSession *session = nullptr; + +void HTTPRequestBaton::start() { + // Starts the request. + assert(!ptr); + + // Create a C locale + static locale_t locale = newlocale(LC_ALL_MASK, nullptr, nullptr); + + dispatch_once(&request_initialize, ^{ + NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration]; + sessionConfig.timeoutIntervalForResource = 30; + sessionConfig.HTTPMaximumConnectionsPerHost = 8; + sessionConfig.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData; + + session = [NSURLSession sessionWithConfiguration:sessionConfig]; + // TODO: add a delegate to the session that prohibits caching, since we handle this ourselves. + }); + + NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:@(path.c_str())]]; + if (response && response->modified) { + struct tm *timeinfo; + char buffer[32]; + const time_t modified = 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"]; + } + + NSURLSessionDataTask *task = [session dataTaskWithRequest:request + completionHandler:^(NSData *data, NSURLResponse *res, NSError *error) { + if (error) { + if ([error code] == NSURLErrorCancelled) { + // The response code remains at 0 to indicate cancelation. + // In addition, we don't need any response object. + response.reset(); + type = HTTPResponseType::Canceled; + } else { + // TODO: Use different codes for host not found, timeout, invalid URL etc. + // These can be categorized in temporary and permanent errors. + response = std::make_unique(); + response->code = [(NSHTTPURLResponse *)res statusCode]; + response->message = [[error localizedDescription] UTF8String]; + + switch ([error code]) { + case NSURLErrorBadServerResponse: // 5xx errors + type = HTTPResponseType::TemporaryError; + break; + + case NSURLErrorTimedOut: + case NSURLErrorUserCancelledAuthentication: + type = HTTPResponseType::SingularError; // retry immediately + break; + + case NSURLErrorNetworkConnectionLost: + case NSURLErrorCannotFindHost: + case NSURLErrorCannotConnectToHost: + case NSURLErrorDNSLookupFailed: + case NSURLErrorNotConnectedToInternet: + case NSURLErrorInternationalRoamingOff: + case NSURLErrorCallIsActive: + case NSURLErrorDataNotAllowed: + type = HTTPResponseType::ConnectionError; + break; + + default: + type = HTTPResponseType::PermanentError; + } + } + } else if ([res isKindOfClass:[NSHTTPURLResponse class]]) { + const long code = [(NSHTTPURLResponse *)res statusCode]; + if (code == 304) { + // Assume a Response object already exists. + assert(response); + } else { + response = std::make_unique(); + response->code = code; + response->data = {(const char *)[data bytes], [data length]}; + } + + if (code == 304) { + type = HTTPResponseType::NotModified; + } else if (code == 200) { + type = HTTPResponseType::Successful; + } else { + assert(!"code must be either 200 or 304"); + } + + NSDictionary *headers = [(NSHTTPURLResponse *)res allHeaderFields]; + NSString *cache_control = [headers objectForKey:@"Cache-Control"]; + if (cache_control) { + response->expires = Response::parseCacheControl([cache_control UTF8String]); + } + + NSString *last_modified = [headers objectForKey:@"Last-Modified"]; + if (last_modified) { + response->modified = parse_date([last_modified UTF8String]); + } + } else { + // This should never happen. + type = HTTPResponseType::PermanentError; + response = std::make_unique(); + response->code = -1; + response->message = "response class is not NSHTTPURLResponse"; + } + + uv_async_send(async); + }]; + + [task resume]; + + ptr = const_cast(CFBridgingRetain(task)); +} + +void HTTPRequestBaton::cleanup() { + if (ptr) { + CFBridgingRelease(ptr); + ptr = nullptr; + } +} + +void HTTPRequestBaton::cancel() { + // After this function returns, the HTTPRequestBaton object may cease to exist at any time. + // try to stop the request + if (ptr) { + NSURLSessionDataTask *task = CFBridgingRelease(ptr); + ptr = nullptr; + [task cancel]; + } else { + // Currently, there is no request in progress. We can delete the async right away. + uv_async_send(async); + } +} + +} -- cgit v1.2.1 From 0fc1f6686886e1122757d5cee17e401ece8178bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Tue, 23 Sep 2014 15:03:39 +0200 Subject: whitespace fixes --- common/http_request_baton_cocoa.mm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'common') diff --git a/common/http_request_baton_cocoa.mm b/common/http_request_baton_cocoa.mm index eaf75ad4d1..32b824b7f4 100644 --- a/common/http_request_baton_cocoa.mm +++ b/common/http_request_baton_cocoa.mm @@ -37,8 +37,8 @@ void HTTPRequestBaton::start() { struct tm *timeinfo; char buffer[32]; const time_t modified = response->modified; - timeinfo = std::gmtime (&modified); - strftime_l(buffer, 32 ,"%a, %d %b %Y %H:%M:%S GMT", timeinfo, locale); + 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"]; } -- cgit v1.2.1 From 992d9ff051ecd45f83160930c43c9d5a2da04048 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Wed, 24 Sep 2014 16:09:26 +0200 Subject: add back CURL requesting --- common/curl_request.cpp | 828 ++++++++++++++++++------------------- common/http_request_baton_cocoa.mm | 81 ++-- common/http_request_baton_curl.cpp | 421 +++++++++++++++++++ 3 files changed, 871 insertions(+), 459 deletions(-) create mode 100644 common/http_request_baton_curl.cpp (limited to 'common') diff --git a/common/curl_request.cpp b/common/curl_request.cpp index 0957481226..456bbfd6d1 100644 --- a/common/curl_request.cpp +++ b/common/curl_request.cpp @@ -1,414 +1,414 @@ - -#include -#include -#include -#include - -#include -#include - -#include - -// This file contains code from http://curl.haxx.se/libcurl/c/multi-uv.html: - -/*************************************************************************** - * _ _ ____ _ - * Project ___| | | | _ \| | - * / __| | | | |_) | | - * | (__| |_| | _ <| |___ - * \___|\___/|_| \_\_____| - * - * Copyright (C) 1998 - 2013, Daniel Stenberg, , 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 *> add_queue(8); -static boost::lockfree::queue *> 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_handle_cache; - - -class CURLRequest : public mbgl::platform::Request { -public: - CURLRequest(const std::string &url, - std::function callback, - std::shared_ptr 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 *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(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(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 *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 *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_http(const std::string &url, - std::function callback, - std::shared_ptr loop) { - using namespace request; - init_thread_once(thread_init_cb); - std::shared_ptr req = std::make_shared(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(req)); - uv_async_send(&async_add); - - return req; -} - -// Cancels an HTTP request. -void platform::cancel_request_http(const std::shared_ptr &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(req)); - uv_async_send(&async_cancel); - } -} -} // end namespace mbgl +// +//#include +//#include +//#include +//#include +// +//#include +//#include +// +//#include +// +//// This file contains code from http://curl.haxx.se/libcurl/c/multi-uv.html: +// +///*************************************************************************** +// * _ _ ____ _ +// * Project ___| | | | _ \| | +// * / __| | | | |_) | | +// * | (__| |_| | _ <| |___ +// * \___|\___/|_| \_\_____| +// * +// * Copyright (C) 1998 - 2013, Daniel Stenberg, , 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 *> add_queue(8); +//static boost::lockfree::queue *> 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_handle_cache; +// +// +//class CURLRequest : public mbgl::platform::Request { +//public: +// CURLRequest(const std::string &url, +// std::function callback, +// std::shared_ptr 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 *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(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(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 *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 *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_http(const std::string &url, +// std::function callback, +// std::shared_ptr loop) { +// using namespace request; +// init_thread_once(thread_init_cb); +// std::shared_ptr req = std::make_shared(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(req)); +// uv_async_send(&async_add); +// +// return req; +//} +// +//// Cancels an HTTP request. +//void platform::cancel_request_http(const std::shared_ptr &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(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 32b824b7f4..497d55653a 100644 --- a/common/http_request_baton_cocoa.mm +++ b/common/http_request_baton_cocoa.mm @@ -15,9 +15,11 @@ namespace mbgl { dispatch_once_t request_initialize = 0; NSURLSession *session = nullptr; -void HTTPRequestBaton::start() { +void HTTPRequestBaton::start(const util::ptr &ptr) { + assert(uv_thread_self() == ptr->thread_id); + // Starts the request. - assert(!ptr); + util::ptr baton = ptr; // Create a C locale static locale_t locale = newlocale(LC_ALL_MASK, nullptr, nullptr); @@ -32,11 +34,11 @@ void HTTPRequestBaton::start() { // TODO: add a delegate to the session that prohibits caching, since we handle this ourselves. }); - NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:@(path.c_str())]]; - if (response && response->modified) { + 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 = response->modified; + 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"]; @@ -48,23 +50,23 @@ void HTTPRequestBaton::start() { if ([error code] == NSURLErrorCancelled) { // The response code remains at 0 to indicate cancelation. // In addition, we don't need any response object. - response.reset(); - type = HTTPResponseType::Canceled; + baton->response.reset(); + baton->type = HTTPResponseType::Canceled; } else { // TODO: Use different codes for host not found, timeout, invalid URL etc. // These can be categorized in temporary and permanent errors. - response = std::make_unique(); - response->code = [(NSHTTPURLResponse *)res statusCode]; - response->message = [[error localizedDescription] UTF8String]; + baton->response = std::make_unique(); + baton->response->code = [(NSHTTPURLResponse *)res statusCode]; + baton->response->message = [[error localizedDescription] UTF8String]; switch ([error code]) { case NSURLErrorBadServerResponse: // 5xx errors - type = HTTPResponseType::TemporaryError; + baton->type = HTTPResponseType::TemporaryError; break; case NSURLErrorTimedOut: case NSURLErrorUserCancelledAuthentication: - type = HTTPResponseType::SingularError; // retry immediately + baton->type = HTTPResponseType::SingularError; // retry immediately break; case NSURLErrorNetworkConnectionLost: @@ -75,28 +77,28 @@ void HTTPRequestBaton::start() { case NSURLErrorInternationalRoamingOff: case NSURLErrorCallIsActive: case NSURLErrorDataNotAllowed: - type = HTTPResponseType::ConnectionError; + baton->type = HTTPResponseType::ConnectionError; break; default: - type = HTTPResponseType::PermanentError; + baton->type = HTTPResponseType::PermanentError; } } } else if ([res isKindOfClass:[NSHTTPURLResponse class]]) { const long code = [(NSHTTPURLResponse *)res statusCode]; if (code == 304) { // Assume a Response object already exists. - assert(response); + assert(baton->response); } else { - response = std::make_unique(); - response->code = code; - response->data = {(const char *)[data bytes], [data length]}; + baton->response = std::make_unique(); + baton->response->code = code; + baton->response->data = {(const char *)[data bytes], [data length]}; } if (code == 304) { - type = HTTPResponseType::NotModified; + baton->type = HTTPResponseType::NotModified; } else if (code == 200) { - type = HTTPResponseType::Successful; + baton->type = HTTPResponseType::Successful; } else { assert(!"code must be either 200 or 304"); } @@ -104,47 +106,36 @@ void HTTPRequestBaton::start() { NSDictionary *headers = [(NSHTTPURLResponse *)res allHeaderFields]; NSString *cache_control = [headers objectForKey:@"Cache-Control"]; if (cache_control) { - response->expires = Response::parseCacheControl([cache_control UTF8String]); + baton->response->expires = Response::parseCacheControl([cache_control UTF8String]); } NSString *last_modified = [headers objectForKey:@"Last-Modified"]; if (last_modified) { - response->modified = parse_date([last_modified UTF8String]); + baton->response->modified = parse_date([last_modified UTF8String]); } } else { // This should never happen. - type = HTTPResponseType::PermanentError; - response = std::make_unique(); - response->code = -1; - response->message = "response class is not NSHTTPURLResponse"; + baton->type = HTTPResponseType::PermanentError; + baton->response = std::make_unique(); + baton->response->code = -1; + baton->response->message = "response class is not NSHTTPURLResponse"; } - uv_async_send(async); + uv_async_send(baton->async); }]; [task resume]; - ptr = const_cast(CFBridgingRetain(task)); + baton->ptr = const_cast(CFBridgingRetain(task)); } -void HTTPRequestBaton::cleanup() { - if (ptr) { - CFBridgingRelease(ptr); - ptr = nullptr; - } -} +void HTTPRequestBaton::stop(const util::ptr &ptr) { + assert(uv_thread_self() == ptr->thread_id); + assert(ptr->ptr); -void HTTPRequestBaton::cancel() { - // After this function returns, the HTTPRequestBaton object may cease to exist at any time. - // try to stop the request - if (ptr) { - NSURLSessionDataTask *task = CFBridgingRelease(ptr); - ptr = nullptr; - [task cancel]; - } else { - // Currently, there is no request in progress. We can delete the async right away. - uv_async_send(async); - } + NSURLSessionDataTask *task = CFBridgingRelease(ptr->ptr); + ptr->ptr = nullptr; + [task cancel]; } } diff --git a/common/http_request_baton_curl.cpp b/common/http_request_baton_curl.cpp new file mode 100644 index 0000000000..55147fbfc7 --- /dev/null +++ b/common/http_request_baton_curl.cpp @@ -0,0 +1,421 @@ +#include +#include +#include +#include + +#include +#include + +#include +#include + +// This file contains code from http://curl.haxx.se/libcurl/c/multi-uv.html: + +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2013, Daniel Stenberg, , 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. +*/ + +// Handles the request thread + messaging to the thread. +static uv_once_t once; +static uv_loop_t loop; +static uv_messenger_t start_messenger; +static uv_messenger_t stop_messenger; +static uv_thread_t thread; +static unsigned long thread_id; + +// 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 *multi = nullptr; + +// CURL share handles are used for sharing session state (e.g.) +static uv_mutex_t share_mutex; +static CURLSH *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 handles; + +namespace mbgl { + +struct curl_context { + uv_poll_t poll_handle; + curl_socket_t sockfd; +}; + +// Locks the CURL share handle +void curl_share_lock(CURL *, curl_lock_data, curl_lock_access, void *) { + uv_mutex_lock(&share_mutex); +} + +// Unlocks the CURL share handle +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 &baton) { + assert(uv_thread_self() == thread_id); + if (baton->ptr) { + CURL *handle = (CURL *)baton->ptr; + CURLMcode error = curl_multi_remove_handle(multi, handle); + if (error != CURLM_OK) { + baton->response = std::make_unique(); + baton->response->code = -1; + baton->response->message = curl_multi_strerror(error); + } + + // Destroy the shared pointer. We still have one pointing to it + util::ptr *baton_ptr = nullptr; + curl_easy_getinfo(handle, CURLINFO_PRIVATE, (char *)&baton_ptr); + curl_easy_setopt(handle, CURLOPT_PRIVATE, nullptr); + delete baton_ptr; + + curl_easy_reset(handle); + handles.push(handle); + baton->ptr = nullptr; + } +} + +void curl_perform(uv_poll_t *req, int status, int events) { + int running_handles; + int flags = 0; + curl_context *context = (curl_context *)req; + CURLMsg *message; + int pending; + + uv_timer_stop(&timeout); + + if (events & UV_READABLE) { + flags |= CURL_CSELECT_IN; + } + if (events & UV_WRITABLE) { + flags |= CURL_CSELECT_OUT; + } + + curl_multi_socket_action(multi, context->sockfd, flags, &running_handles); + + while ((message = curl_multi_info_read(multi, &pending))) { + switch (message->msg) { + case CURLMSG_DONE: { + util::ptr *baton_ptr = nullptr; + curl_easy_getinfo(message->easy_handle, CURLINFO_PRIVATE, (char *)&baton_ptr); + util::ptr baton = *baton_ptr; + + // Add human-readable error code + if (message->data.result != CURLE_OK) { + baton->response->message = curl_easy_strerror(message->data.result); + baton->response->code = -1; + + switch (message->data.result) { + case CURLE_COULDNT_RESOLVE_PROXY: + case CURLE_COULDNT_RESOLVE_HOST: + case CURLE_COULDNT_CONNECT: + baton->type = HTTPResponseType::ConnectionError; + break; + + case CURLE_OPERATION_TIMEDOUT: + baton->type = HTTPResponseType::TemporaryError; + break; + + default: + baton->type = HTTPResponseType::PermanentError; + } + } else { + long code = 0; + curl_easy_getinfo(message->easy_handle, CURLINFO_RESPONSE_CODE, &code); + + if (code != 304) { + baton->response->code = code; + } + + if (code == 304) { + baton->type = HTTPResponseType::NotModified; + } else if (code == 200) { + baton->type = HTTPResponseType::Successful; + } else if (code >= 500 && code < 600) { + baton->type = HTTPResponseType::TemporaryError; + } else if (code >= 400 && code < 500) { + baton->type = HTTPResponseType::PermanentError; + } else { + assert(!"code must be either 200 or 304"); + } + } + + // We're currently in the CURL request thread. + finish_request(baton); + + if (baton->async) { + uv_async_send(baton->async); + baton->async = nullptr; + } + + 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(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(multi, s, NULL); + } + } + + return 0; +} + +void on_timeout(uv_timer_t *req) { + int running_handles; + CURLMcode error = curl_multi_socket_action(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); + } +} + +void thread_init(void *) { +#ifdef __APPLE__ + pthread_setname_np("CURL"); +#endif + thread_id = uv_thread_self(); + + uv_timer_init(&loop, &timeout); + + CURLSHcode share_error; + share = curl_share_init(); + + share_error = curl_share_setopt(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(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; + multi = curl_multi_init(); + + multi_error = curl_multi_setopt(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(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(multi); + multi = nullptr; + + curl_share_cleanup(share); + share = nullptr; + + thread_id = -1; +} + +// 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 *const contents, const size_t size, const size_t nmemb, void *const 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 *const header, const char *const buffer, const 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 * const buffer, const size_t size, const size_t nmemb, void *const 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(userp)->modified = curl_getdate(value.c_str(), nullptr); + } 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(userp)->expires = Response::parseCacheControl(value.c_str()); + } + + return length; +} + +// This function must run in the CURL thread. +void start_request(void *const ptr) { + assert(uv_thread_self() == thread_id); + util::ptr &baton = *(util::ptr *)ptr; + assert(baton); + + CURL *handle = nullptr; + if (!handles.empty()) { + handle = handles.front(); + handles.pop(); + } else { + handle = curl_easy_init(); + } + + baton->ptr = handle; + + if (!baton->response) { + baton->response = std::make_unique(); + } + + // 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_CAINFO, "ca-bundle.crt"); + curl_easy_setopt(handle, CURLOPT_URL, baton->path.c_str()); + curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, curl_write_cb); + curl_easy_setopt(handle, CURLOPT_WRITEDATA, &baton->response->data); + curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, curl_header_cb); + curl_easy_setopt(handle, CURLOPT_HEADERDATA, baton->response.get()); + curl_easy_setopt(handle, CURLOPT_ACCEPT_ENCODING, "gzip, deflate"); + curl_easy_setopt(handle, CURLOPT_SHARE, share); + + // Start requesting the information. + curl_multi_add_handle(multi, handle); +} + +// This function must run in the CURL thread. +void stop_request(void *const ptr) { + assert(uv_thread_self() == thread_id); + util::ptr &baton = *(util::ptr *)ptr; + assert(baton); + + if (baton->async) { + baton->type = HTTPResponseType::Canceled; + + // We can still stop the request because it is still in progress. + finish_request(baton); + + uv_async_send(baton->async); + baton->async = nullptr; + } else { + // If the async handle is gone, it means that the actual request has been completed before + // we got a chance to cancel it. In this case, this is a no-op. It is likely that + // 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 *)ptr; +} + +void create_thread() { + uv_mutex_init(&share_mutex); + uv_loop_init(&loop); + uv_messenger_init(&loop, &start_messenger, start_request); + uv_messenger_init(&loop, &stop_messenger, stop_request); + uv_thread_create(&thread, thread_init, nullptr); +} + +// This function must be run from the main thread (== where the HTTPRequestBaton was created) +void HTTPRequestBaton::start(const util::ptr &ptr) { + assert(uv_thread_self() == ptr->thread_id); + uv_once(&once, create_thread); + uv_messenger_send(&start_messenger, new util::ptr(ptr)); +} + +// This function must be run from the main thread (== where the HTTPRequestBaton was created) +void HTTPRequestBaton::stop(const util::ptr &ptr) { + assert(uv_thread_self() == ptr->thread_id); + uv_once(&once, create_thread); + uv_messenger_send(&stop_messenger, new util::ptr(ptr)); +} + +} -- cgit v1.2.1 From 1209744b38da0252731812d4cc5371be960fb3c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Wed, 24 Sep 2014 17:08:44 +0200 Subject: use the default cache location on ios to avoid writing to unwritable file system locations --- common/ios.mm | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 common/ios.mm (limited to 'common') diff --git a/common/ios.mm b/common/ios.mm new file mode 100644 index 0000000000..1076343e24 --- /dev/null +++ b/common/ios.mm @@ -0,0 +1,19 @@ +#import + +#include + +namespace mbgl { +namespace platform { + +// Returns the path to the default cache database on this system. +std::string defaultCacheDatabase() { + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES); + NSString *libraryDirectory = [paths objectAtIndex:0]; + return [[libraryDirectory stringByAppendingPathComponent:@"cache.db"] UTF8String]; +} + +} +} + + +// Returns the path to the default cache database on this system. -- cgit v1.2.1 From b9628c86543ffe819b030c3d84f65fa09e084850 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Fri, 26 Sep 2014 16:10:39 +0200 Subject: add etag support and if-none-match/if-modified-since to cocoa http handling --- common/curl_request.cpp | 414 ------------------------------------- common/http_request_baton_cocoa.mm | 22 +- common/http_request_baton_curl.cpp | 141 ++++++++----- common/ios.mm | 8 +- common/linux.cpp | 12 ++ common/osx.mm | 31 +++ 6 files changed, 155 insertions(+), 473 deletions(-) delete mode 100644 common/curl_request.cpp create mode 100644 common/linux.cpp create mode 100644 common/osx.mm (limited to 'common') 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 -//#include -//#include -//#include -// -//#include -//#include -// -//#include -// -//// This file contains code from http://curl.haxx.se/libcurl/c/multi-uv.html: -// -///*************************************************************************** -// * _ _ ____ _ -// * Project ___| | | | _ \| | -// * / __| | | | |_) | | -// * | (__| |_| | _ <| |___ -// * \___|\___/|_| \_\_____| -// * -// * Copyright (C) 1998 - 2013, Daniel Stenberg, , 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 *> add_queue(8); -//static boost::lockfree::queue *> 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_handle_cache; -// -// -//class CURLRequest : public mbgl::platform::Request { -//public: -// CURLRequest(const std::string &url, -// std::function callback, -// std::shared_ptr 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 *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(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(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 *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 *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_http(const std::string &url, -// std::function callback, -// std::shared_ptr loop) { -// using namespace request; -// init_thread_once(thread_init_cb); -// std::shared_ptr req = std::make_shared(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(req)); -// uv_async_send(&async_add); -// -// return req; -//} -// -//// Cancels an HTTP request. -//void platform::cancel_request_http(const std::shared_ptr &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(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 &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 &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 #include +#include +#include // This file contains code from http://curl.haxx.se/libcurl/c/multi-uv.html: @@ -72,9 +74,25 @@ static std::queue handles; namespace mbgl { -struct curl_context { - uv_poll_t poll_handle; - curl_socket_t sockfd; +struct CURLContext { + util::ptr baton; + uv_poll_t *poll_handle = nullptr; + curl_socket_t sockfd = 0; + curl_slist *headers = nullptr; + + CURLContext(const util::ptr &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 &baton) { @@ -117,10 +119,12 @@ void finish_request(const util::ptr &baton) { } // Destroy the shared pointer. We still have one pointing to it - util::ptr *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 &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 *baton_ptr = nullptr; - curl_easy_getinfo(message->easy_handle, CURLINFO_PRIVATE, (char *)&baton_ptr); - util::ptr 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 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(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(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(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 &baton = *(util::ptr *)ptr; + std::unique_ptr> baton_guard { (util::ptr *)ptr }; + util::ptr &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 context = std::make_unique(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(); } // 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 &baton = *(util::ptr *)ptr; + std::unique_ptr> baton_guard { (util::ptr *)ptr }; + util::ptr &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 *)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 + +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 + +#include + +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]; +} + +} +} -- cgit v1.2.1 From 9e44c0eae8a5e13bfddd00c7888988cb0bfb7c1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Mon, 29 Sep 2014 14:02:42 +0200 Subject: add reachability --- common/Reachability.h | 112 +++++++++++ common/Reachability.m | 527 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 639 insertions(+) create mode 100644 common/Reachability.h create mode 100644 common/Reachability.m (limited to 'common') diff --git a/common/Reachability.h b/common/Reachability.h new file mode 100644 index 0000000000..1cf7d2ecea --- /dev/null +++ b/common/Reachability.h @@ -0,0 +1,112 @@ +/* + Copyright (c) 2011, Tony Million. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + */ + +#import +#import + +#import +#import +#import +#import +#import +#import + +/** + * Does ARC support GCD objects? + * It does if the minimum deployment target is iOS 6+ or Mac OS X 8+ + * + * @see http://opensource.apple.com/source/libdispatch/libdispatch-228.18/os/object.h + **/ +#if OS_OBJECT_USE_OBJC +#define NEEDS_DISPATCH_RETAIN_RELEASE 0 +#else +#define NEEDS_DISPATCH_RETAIN_RELEASE 1 +#endif + +/** + * Create NS_ENUM macro if it does not exist on the targeted version of iOS or OS X. + * + * @see http://nshipster.com/ns_enum-ns_options/ + **/ +#ifndef NS_ENUM +#define NS_ENUM(_type, _name) enum _name : _type _name; enum _name : _type +#endif + +extern NSString *const kReachabilityChangedNotification; + +typedef NS_ENUM(NSInteger, NetworkStatus) { + // Apple NetworkStatus Compatible Names. + NotReachable = 0, + ReachableViaWiFi = 2, + ReachableViaWWAN = 1 +}; + +@class Reachability; + +typedef void (^NetworkReachable)(Reachability * reachability); +typedef void (^NetworkUnreachable)(Reachability * reachability); + +@interface Reachability : NSObject + +@property (nonatomic, copy) NetworkReachable reachableBlock; +@property (nonatomic, copy) NetworkUnreachable unreachableBlock; + + +@property (nonatomic, assign) BOOL reachableOnWWAN; + ++(Reachability*)reachabilityWithHostname:(NSString*)hostname; +// This is identical to the function above, but is here to maintain +//compatibility with Apples original code. (see .m) ++(Reachability*)reachabilityWithHostName:(NSString*)hostname; ++(Reachability*)reachabilityForInternetConnection; ++(Reachability*)reachabilityWithAddress:(const struct sockaddr_in*)hostAddress; ++(Reachability*)reachabilityForLocalWiFi; + +-(Reachability *)initWithReachabilityRef:(SCNetworkReachabilityRef)ref; + +-(BOOL)startNotifier; +-(void)stopNotifier; + +-(BOOL)isReachable; +-(BOOL)isReachableViaWWAN; +-(BOOL)isReachableViaWiFi; + +// WWAN may be available, but not active until a connection has been established. +// WiFi may require a connection for VPN on Demand. +-(BOOL)isConnectionRequired; // Identical DDG variant. +-(BOOL)connectionRequired; // Apple's routine. +// Dynamic, on demand connection? +-(BOOL)isConnectionOnDemand; +// Is user intervention required? +-(BOOL)isInterventionRequired; + +-(NetworkStatus)currentReachabilityStatus; +-(SCNetworkReachabilityFlags)reachabilityFlags; +-(NSString*)currentReachabilityString; +-(NSString*)currentReachabilityFlags; + +@end diff --git a/common/Reachability.m b/common/Reachability.m new file mode 100644 index 0000000000..c6f6388f52 --- /dev/null +++ b/common/Reachability.m @@ -0,0 +1,527 @@ +/* + Copyright (c) 2011, Tony Million. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + */ + +#import "Reachability.h" + + +NSString *const kReachabilityChangedNotification = @"kReachabilityChangedNotification"; + +@interface Reachability () + +@property (nonatomic, assign) SCNetworkReachabilityRef reachabilityRef; + + +#if NEEDS_DISPATCH_RETAIN_RELEASE +@property (nonatomic, assign) dispatch_queue_t reachabilitySerialQueue; +#else +@property (nonatomic, strong) dispatch_queue_t reachabilitySerialQueue; +#endif + + +@property (nonatomic, strong) id reachabilityObject; + +-(void)reachabilityChanged:(SCNetworkReachabilityFlags)flags; +-(BOOL)isReachableWithFlags:(SCNetworkReachabilityFlags)flags; + +@end + +static NSString *reachabilityFlags(SCNetworkReachabilityFlags flags) +{ + return [NSString stringWithFormat:@"%c%c %c%c%c%c%c%c%c", +#if TARGET_OS_IPHONE + (flags & kSCNetworkReachabilityFlagsIsWWAN) ? 'W' : '-', +#else + 'X', +#endif + (flags & kSCNetworkReachabilityFlagsReachable) ? 'R' : '-', + (flags & kSCNetworkReachabilityFlagsConnectionRequired) ? 'c' : '-', + (flags & kSCNetworkReachabilityFlagsTransientConnection) ? 't' : '-', + (flags & kSCNetworkReachabilityFlagsInterventionRequired) ? 'i' : '-', + (flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) ? 'C' : '-', + (flags & kSCNetworkReachabilityFlagsConnectionOnDemand) ? 'D' : '-', + (flags & kSCNetworkReachabilityFlagsIsLocalAddress) ? 'l' : '-', + (flags & kSCNetworkReachabilityFlagsIsDirect) ? 'd' : '-']; +} + +// Start listening for reachability notifications on the current run loop +static void TMReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void* info) +{ +#pragma unused (target) +#if __has_feature(objc_arc) + Reachability *reachability = ((__bridge Reachability*)info); +#else + Reachability *reachability = ((Reachability*)info); +#endif + + // We probably don't need an autoreleasepool here, as GCD docs state each queue has its own autorelease pool, + // but what the heck eh? + @autoreleasepool + { + [reachability reachabilityChanged:flags]; + } +} + + +@implementation Reachability + +@synthesize reachabilityRef; +@synthesize reachabilitySerialQueue; + +@synthesize reachableOnWWAN; + +@synthesize reachableBlock; +@synthesize unreachableBlock; + +@synthesize reachabilityObject; + +#pragma mark - Class Constructor Methods + ++(Reachability*)reachabilityWithHostName:(NSString*)hostname +{ + return [Reachability reachabilityWithHostname:hostname]; +} + ++(Reachability*)reachabilityWithHostname:(NSString*)hostname +{ + SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithName(NULL, [hostname UTF8String]); + if (ref) + { + id reachability = [[self alloc] initWithReachabilityRef:ref]; + +#if __has_feature(objc_arc) + return reachability; +#else + return [reachability autorelease]; +#endif + + } + + return nil; +} + ++(Reachability *)reachabilityWithAddress:(const struct sockaddr_in *)hostAddress +{ + SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr*)hostAddress); + if (ref) + { + id reachability = [[self alloc] initWithReachabilityRef:ref]; + +#if __has_feature(objc_arc) + return reachability; +#else + return [reachability autorelease]; +#endif + } + + return nil; +} + ++(Reachability *)reachabilityForInternetConnection +{ + struct sockaddr_in zeroAddress; + bzero(&zeroAddress, sizeof(zeroAddress)); + zeroAddress.sin_len = sizeof(zeroAddress); + zeroAddress.sin_family = AF_INET; + + return [self reachabilityWithAddress:&zeroAddress]; +} + ++(Reachability*)reachabilityForLocalWiFi +{ + struct sockaddr_in localWifiAddress; + bzero(&localWifiAddress, sizeof(localWifiAddress)); + localWifiAddress.sin_len = sizeof(localWifiAddress); + localWifiAddress.sin_family = AF_INET; + // IN_LINKLOCALNETNUM is defined in as 169.254.0.0 + localWifiAddress.sin_addr.s_addr = htonl(IN_LINKLOCALNETNUM); + + return [self reachabilityWithAddress:&localWifiAddress]; +} + + +// Initialization methods + +-(Reachability *)initWithReachabilityRef:(SCNetworkReachabilityRef)ref +{ + self = [super init]; + if (self != nil) + { + self.reachableOnWWAN = YES; + self.reachabilityRef = ref; + } + + return self; +} + +-(void)dealloc +{ + [self stopNotifier]; + + if(self.reachabilityRef) + { + CFRelease(self.reachabilityRef); + self.reachabilityRef = nil; + } + + self.reachableBlock = nil; + self.unreachableBlock = nil; + +#if !(__has_feature(objc_arc)) + [super dealloc]; +#endif + + +} + +#pragma mark - Notifier Methods + +// Notifier +// NOTE: This uses GCD to trigger the blocks - they *WILL NOT* be called on THE MAIN THREAD +// - In other words DO NOT DO ANY UI UPDATES IN THE BLOCKS. +// INSTEAD USE dispatch_async(dispatch_get_main_queue(), ^{UISTUFF}) (or dispatch_sync if you want) + +-(BOOL)startNotifier +{ + SCNetworkReachabilityContext context = { 0, NULL, NULL, NULL, NULL }; + + // this should do a retain on ourself, so as long as we're in notifier mode we shouldn't disappear out from under ourselves + // woah + self.reachabilityObject = self; + + + + // First, we need to create a serial queue. + // We allocate this once for the lifetime of the notifier. + self.reachabilitySerialQueue = dispatch_queue_create("com.tonymillion.reachability", NULL); + if(self.reachabilitySerialQueue == nil) + { + return NO; + } + +#if __has_feature(objc_arc) + context.info = (__bridge void *)self; +#else + context.info = (void *)self; +#endif + + if (!SCNetworkReachabilitySetCallback(self.reachabilityRef, TMReachabilityCallback, &context)) + { +#ifdef DEBUG + NSLog(@"SCNetworkReachabilitySetCallback() failed: %s", SCErrorString(SCError())); +#endif + + // Clear out the dispatch queue + if(self.reachabilitySerialQueue) + { +#if NEEDS_DISPATCH_RETAIN_RELEASE + dispatch_release(self.reachabilitySerialQueue); +#endif + self.reachabilitySerialQueue = nil; + } + + self.reachabilityObject = nil; + + return NO; + } + + // Set it as our reachability queue, which will retain the queue + if(!SCNetworkReachabilitySetDispatchQueue(self.reachabilityRef, self.reachabilitySerialQueue)) + { +#ifdef DEBUG + NSLog(@"SCNetworkReachabilitySetDispatchQueue() failed: %s", SCErrorString(SCError())); +#endif + + // UH OH - FAILURE! + + // First stop, any callbacks! + SCNetworkReachabilitySetCallback(self.reachabilityRef, NULL, NULL); + + // Then clear out the dispatch queue. + if(self.reachabilitySerialQueue) + { +#if NEEDS_DISPATCH_RETAIN_RELEASE + dispatch_release(self.reachabilitySerialQueue); +#endif + self.reachabilitySerialQueue = nil; + } + + self.reachabilityObject = nil; + + return NO; + } + + return YES; +} + +-(void)stopNotifier +{ + // First stop, any callbacks! + SCNetworkReachabilitySetCallback(self.reachabilityRef, NULL, NULL); + + // Unregister target from the GCD serial dispatch queue. + SCNetworkReachabilitySetDispatchQueue(self.reachabilityRef, NULL); + + if(self.reachabilitySerialQueue) + { +#if NEEDS_DISPATCH_RETAIN_RELEASE + dispatch_release(self.reachabilitySerialQueue); +#endif + self.reachabilitySerialQueue = nil; + } + + self.reachabilityObject = nil; +} + +#pragma mark - reachability tests + +// This is for the case where you flick the airplane mode; +// you end up getting something like this: +//Reachability: WR ct----- +//Reachability: -- ------- +//Reachability: WR ct----- +//Reachability: -- ------- +// We treat this as 4 UNREACHABLE triggers - really apple should do better than this + +#define testcase (kSCNetworkReachabilityFlagsConnectionRequired | kSCNetworkReachabilityFlagsTransientConnection) + +-(BOOL)isReachableWithFlags:(SCNetworkReachabilityFlags)flags +{ + BOOL connectionUP = YES; + + if(!(flags & kSCNetworkReachabilityFlagsReachable)) + connectionUP = NO; + + if( (flags & testcase) == testcase ) + connectionUP = NO; + +#if TARGET_OS_IPHONE + if(flags & kSCNetworkReachabilityFlagsIsWWAN) + { + // We're on 3G. + if(!self.reachableOnWWAN) + { + // We don't want to connect when on 3G. + connectionUP = NO; + } + } +#endif + + return connectionUP; +} + +-(BOOL)isReachable +{ + SCNetworkReachabilityFlags flags; + + if(!SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags)) + return NO; + + return [self isReachableWithFlags:flags]; +} + +-(BOOL)isReachableViaWWAN +{ +#if TARGET_OS_IPHONE + + SCNetworkReachabilityFlags flags = 0; + + if(SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) + { + // Check we're REACHABLE + if(flags & kSCNetworkReachabilityFlagsReachable) + { + // Now, check we're on WWAN + if(flags & kSCNetworkReachabilityFlagsIsWWAN) + { + return YES; + } + } + } +#endif + + return NO; +} + +-(BOOL)isReachableViaWiFi +{ + SCNetworkReachabilityFlags flags = 0; + + if(SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) + { + // Check we're reachable + if((flags & kSCNetworkReachabilityFlagsReachable)) + { +#if TARGET_OS_IPHONE + // Check we're NOT on WWAN + if((flags & kSCNetworkReachabilityFlagsIsWWAN)) + { + return NO; + } +#endif + return YES; + } + } + + return NO; +} + + +// WWAN may be available, but not active until a connection has been established. +// WiFi may require a connection for VPN on Demand. +-(BOOL)isConnectionRequired +{ + return [self connectionRequired]; +} + +-(BOOL)connectionRequired +{ + SCNetworkReachabilityFlags flags; + + if(SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) + { + return (flags & kSCNetworkReachabilityFlagsConnectionRequired); + } + + return NO; +} + +// Dynamic, on demand connection? +-(BOOL)isConnectionOnDemand +{ + SCNetworkReachabilityFlags flags; + + if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) + { + return ((flags & kSCNetworkReachabilityFlagsConnectionRequired) && + (flags & (kSCNetworkReachabilityFlagsConnectionOnTraffic | kSCNetworkReachabilityFlagsConnectionOnDemand))); + } + + return NO; +} + +// Is user intervention required? +-(BOOL)isInterventionRequired +{ + SCNetworkReachabilityFlags flags; + + if (SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) + { + return ((flags & kSCNetworkReachabilityFlagsConnectionRequired) && + (flags & kSCNetworkReachabilityFlagsInterventionRequired)); + } + + return NO; +} + + +#pragma mark - reachability status stuff + +-(NetworkStatus)currentReachabilityStatus +{ + if([self isReachable]) + { + if([self isReachableViaWiFi]) + return ReachableViaWiFi; + +#if TARGET_OS_IPHONE + return ReachableViaWWAN; +#endif + } + + return NotReachable; +} + +-(SCNetworkReachabilityFlags)reachabilityFlags +{ + SCNetworkReachabilityFlags flags = 0; + + if(SCNetworkReachabilityGetFlags(reachabilityRef, &flags)) + { + return flags; + } + + return 0; +} + +-(NSString*)currentReachabilityString +{ + NetworkStatus temp = [self currentReachabilityStatus]; + + if(temp == reachableOnWWAN) + { + // Updated for the fact that we have CDMA phones now! + return NSLocalizedString(@"Cellular", @""); + } + if (temp == ReachableViaWiFi) + { + return NSLocalizedString(@"WiFi", @""); + } + + return NSLocalizedString(@"No Connection", @""); +} + +-(NSString*)currentReachabilityFlags +{ + return reachabilityFlags([self reachabilityFlags]); +} + +#pragma mark - Callback function calls this method + +-(void)reachabilityChanged:(SCNetworkReachabilityFlags)flags +{ + if([self isReachableWithFlags:flags]) + { + if(self.reachableBlock) + { + self.reachableBlock(self); + } + } + else + { + if(self.unreachableBlock) + { + self.unreachableBlock(self); + } + } + + // this makes sure the change notification happens on the MAIN THREAD + dispatch_async(dispatch_get_main_queue(), ^{ + [[NSNotificationCenter defaultCenter] postNotificationName:kReachabilityChangedNotification + object:self]; + }); +} + +#pragma mark - Debug Description + +- (NSString *) description +{ + NSString *description = [NSString stringWithFormat:@"<%@: %#x>", + NSStringFromClass([self class]), (unsigned int) self]; + return description; +} + +@end -- cgit v1.2.1 From 5c6863c5405c6f8dd66ebc65658dd28a7a1e4078 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Mon, 29 Sep 2014 08:10:31 -0700 Subject: add missing include --- common/http_request_baton_curl.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'common') diff --git a/common/http_request_baton_curl.cpp b/common/http_request_baton_curl.cpp index 8cf6a903fe..772aeb86e1 100644 --- a/common/http_request_baton_curl.cpp +++ b/common/http_request_baton_curl.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include -- cgit v1.2.1 From 5d34c18c808f1fb077f74869b08ff3dc959cb638 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Mon, 29 Sep 2014 15:31:26 +0200 Subject: disable URLcache for NSURLRequests --- common/http_request_baton_cocoa.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'common') diff --git a/common/http_request_baton_cocoa.mm b/common/http_request_baton_cocoa.mm index 2ebbec481c..623b2311be 100644 --- a/common/http_request_baton_cocoa.mm +++ b/common/http_request_baton_cocoa.mm @@ -29,9 +29,9 @@ void HTTPRequestBaton::start(const util::ptr &ptr) { sessionConfig.timeoutIntervalForResource = 30; sessionConfig.HTTPMaximumConnectionsPerHost = 8; sessionConfig.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData; + sessionConfig.URLCache = nil; session = [NSURLSession sessionWithConfiguration:sessionConfig]; - // TODO: add a delegate to the session that prohibits caching, since we handle this ourselves. }); NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:@(baton->path.c_str())]]; -- cgit v1.2.1 From 8fcc74e2531f53b6e09584fb58022f852a32b26c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Tue, 30 Sep 2014 17:28:42 +0200 Subject: fix headless tests --- common/headless_view.cpp | 4 ++++ common/headless_view.hpp | 1 + 2 files changed, 5 insertions(+) (limited to 'common') diff --git a/common/headless_view.cpp b/common/headless_view.cpp index ed00f48e82..ace41d38c0 100644 --- a/common/headless_view.cpp +++ b/common/headless_view.cpp @@ -163,6 +163,10 @@ HeadlessView::~HeadlessView() { #endif } +void HeadlessView::notify() { + // no-op +} + void HeadlessView::notify_map_change(mbgl::MapChange /*change*/, mbgl::timestamp /*delay*/) { // no-op } diff --git a/common/headless_view.hpp b/common/headless_view.hpp index a8ce4aa325..42f9c46da2 100644 --- a/common/headless_view.hpp +++ b/common/headless_view.hpp @@ -21,6 +21,7 @@ public: void resize(uint16_t width, uint16_t height, float pixelRatio); + void notify(); void notify_map_change(MapChange change, timestamp delay = 0); void make_active(); void swap(); -- cgit v1.2.1 From f807926f5e06f03c26afc0217f4af9864bf24a13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20Ka=CC=88fer?= Date: Wed, 8 Oct 2014 16:05:27 +0200 Subject: report a permanent error instead of aborting on unknown error codes fixes #479 --- common/http_request_baton_cocoa.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'common') diff --git a/common/http_request_baton_cocoa.mm b/common/http_request_baton_cocoa.mm index 623b2311be..a9992fad8c 100644 --- a/common/http_request_baton_cocoa.mm +++ b/common/http_request_baton_cocoa.mm @@ -103,7 +103,7 @@ void HTTPRequestBaton::start(const util::ptr &ptr) { } else if (code == 200) { baton->type = HTTPResponseType::Successful; } else { - assert(!"code must be either 200 or 304"); + baton->type = HTTPResponseType::PermanentError; } NSDictionary *headers = [(NSHTTPURLResponse *)res allHeaderFields]; -- cgit v1.2.1