summaryrefslogtreecommitdiff
path: root/platform
diff options
context:
space:
mode:
authorKonstantin Käfer <mail@kkaefer.com>2015-01-16 14:04:41 +0100
committerKonstantin Käfer <mail@kkaefer.com>2015-02-04 10:46:37 +0100
commitb9bf66e67ed1d0d1b1d3163255cab099a6ba4a95 (patch)
tree93ad6df882442e18d9a9771d4b4f06a0a764a0a9 /platform
parent3bfea8bf30c978173f1ec2fab6f89d6b33afea86 (diff)
downloadqtlocation-mapboxgl-b9bf66e67ed1d0d1b1d3163255cab099a6ba4a95.tar.gz
rewrite storage layer to be independent of the Map's event loop
Diffstat (limited to 'platform')
-rw-r--r--platform/darwin/http_request_baton_cocoa.mm155
-rw-r--r--platform/darwin/http_request_cocoa.mm388
-rw-r--r--platform/default/asset_request_libuv.cpp236
-rw-r--r--platform/default/cache_database_tmp.cpp12
-rw-r--r--platform/default/http_request_baton_curl.cpp629
-rw-r--r--platform/default/http_request_curl.cpp640
6 files changed, 1149 insertions, 911 deletions
diff --git a/platform/darwin/http_request_baton_cocoa.mm b/platform/darwin/http_request_baton_cocoa.mm
deleted file mode 100644
index 4a59837e32..0000000000
--- a/platform/darwin/http_request_baton_cocoa.mm
+++ /dev/null
@@ -1,155 +0,0 @@
-#include <mbgl/storage/http_request_baton.hpp>
-#include <mbgl/util/std.hpp>
-#include <mbgl/util/parsedate.h>
-#include <mbgl/util/time.hpp>
-#include <mbgl/util/version.hpp>
-
-#include <uv.h>
-
-#include <mbgl/util/uv.hpp>
-
-#import <Foundation/Foundation.h>
-
-namespace mbgl {
-
-dispatch_once_t request_initialize = 0;
-NSURLSession *session = nullptr;
-
-NSString *userAgent = nil;
-
-void HTTPRequestBaton::start(const util::ptr<HTTPRequestBaton> &ptr) {
- assert(std::this_thread::get_id() == ptr->threadId);
-
- // Starts the request.
- util::ptr<HTTPRequestBaton> baton = ptr;
-
- dispatch_once(&request_initialize, ^{
- NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
- sessionConfig.timeoutIntervalForResource = 30;
- sessionConfig.HTTPMaximumConnectionsPerHost = 8;
- sessionConfig.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
- sessionConfig.URLCache = nil;
-
- session = [NSURLSession sessionWithConfiguration:sessionConfig];
-
- // Write user agent string
- NSDictionary *systemVersion = [NSDictionary dictionaryWithContentsOfFile:@"/System/Library/CoreServices/SystemVersion.plist"];
- userAgent = [NSString stringWithFormat:@"MapboxGL/%d.%d.%d (+https://mapbox.com/mapbox-gl/; %s; %@ %@)",
- version::major, version::minor, version::patch, version::revision,
- [systemVersion objectForKey:@"ProductName"],
- [systemVersion objectForKey:@"ProductVersion"]
- ];
- });
-
- NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:@(baton->path.c_str())]];
- 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 std::string time = util::rfc1123(baton->response->modified);
- [request addValue:@(time.c_str()) forHTTPHeaderField:@"If-Modified-Since"];
- }
- }
-
- [request addValue:userAgent forHTTPHeaderField:@"User-Agent"];
-
- 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.
- 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.
- baton->response = util::make_unique<Response>();
- baton->response->code = [(NSHTTPURLResponse *)res statusCode];
- baton->response->message = [[error localizedDescription] UTF8String];
-
- switch ([error code]) {
- case NSURLErrorBadServerResponse: // 5xx errors
- baton->type = HTTPResponseType::TemporaryError;
- break;
-
- case NSURLErrorTimedOut:
- case NSURLErrorUserCancelledAuthentication:
- baton->type = HTTPResponseType::SingularError; // retry immediately
- break;
-
- case NSURLErrorNetworkConnectionLost:
- case NSURLErrorCannotFindHost:
- case NSURLErrorCannotConnectToHost:
- case NSURLErrorDNSLookupFailed:
- case NSURLErrorNotConnectedToInternet:
- case NSURLErrorInternationalRoamingOff:
- case NSURLErrorCallIsActive:
- case NSURLErrorDataNotAllowed:
- baton->type = HTTPResponseType::ConnectionError;
- break;
-
- default:
- 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(baton->response);
- } else {
- baton->response = util::make_unique<Response>();
- baton->response->code = code;
- baton->response->data = {(const char *)[data bytes], [data length]};
- }
-
- if (code == 304) {
- baton->type = HTTPResponseType::NotModified;
- } else if (code == 200) {
- baton->type = HTTPResponseType::Successful;
- } else {
- baton->type = HTTPResponseType::PermanentError;
- }
-
- NSDictionary *headers = [(NSHTTPURLResponse *)res allHeaderFields];
- NSString *cache_control = [headers objectForKey:@"Cache-Control"];
- if (cache_control) {
- baton->response->expires = Response::parseCacheControl([cache_control UTF8String]);
- }
-
- NSString *last_modified = [headers objectForKey:@"Last-Modified"];
- 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;
- baton->response = util::make_unique<Response>();
- baton->response->code = -1;
- baton->response->message = "response class is not NSHTTPURLResponse";
- }
-
- uv_async_send(baton->async);
- }];
-
- [task resume];
-
- baton->ptr = const_cast<void *>(CFBridgingRetain(task));
-}
-
-void HTTPRequestBaton::stop(const util::ptr<HTTPRequestBaton> &ptr) {
- assert(std::this_thread::get_id() == ptr->threadId);
- assert(ptr->ptr);
-
- NSURLSessionDataTask *task = CFBridgingRelease(ptr->ptr);
- ptr->ptr = nullptr;
- [task cancel];
-}
-
-}
diff --git a/platform/darwin/http_request_cocoa.mm b/platform/darwin/http_request_cocoa.mm
new file mode 100644
index 0000000000..163d4cf2db
--- /dev/null
+++ b/platform/darwin/http_request_cocoa.mm
@@ -0,0 +1,388 @@
+#include <mbgl/storage/default/http_request.hpp>
+#include <mbgl/storage/default/http_context.hpp>
+#include <mbgl/storage/response.hpp>
+#include <mbgl/util/uv.hpp>
+
+#include <mbgl/util/time.hpp>
+#include <mbgl/util/parsedate.h>
+
+#import <Foundation/Foundation.h>
+
+#include <map>
+#include <cassert>
+
+dispatch_once_t request_initialize = 0;
+NSURLSession *session = nullptr;
+NSString *userAgent = nil;
+
+namespace mbgl {
+
+enum class ResponseStatus : uint8_t {
+ // This error probably won't be resolved by retrying anytime soon. We are giving up.
+ PermanentError,
+
+ // This error might be resolved by waiting some time (e.g. server issues).
+ // We are going to do an exponential back-off and will try again in a few seconds.
+ TemporaryError,
+
+ // This error was caused by a temporary error and it is likely that it will be resolved
+ // immediately. We are going to try again right away. This is like the TemporaryError, except
+ // that we will not perform exponential back-off.
+ SingularError,
+
+ // This error might be resolved once the network reachability status changes.
+ // We are going to watch the network status for changes and will retry as soon as the
+ // operating system notifies us of a network status change.
+ ConnectionError,
+
+ // The request was canceled mid-way.
+ Canceled,
+
+ // The request returned data successfully. We retrieved and decoded the data successfully.
+ Successful,
+
+ // The request confirmed that the data wasn't changed. We already have the data.
+ NotModified,
+};
+
+// -------------------------------------------------------------------------------------------------
+
+class HTTPCocoaContext;
+
+class HTTPRequestImpl {
+public:
+ HTTPRequestImpl(HTTPRequest *request, uv_loop_t *loop, std::unique_ptr<Response> response);
+ ~HTTPRequestImpl();
+
+ void cancel();
+
+ void start();
+ void handleResult(NSData *data, NSURLResponse *res, NSError *error);
+ void handleResponse();
+
+ void retry(uint64_t timeout);
+ void retryImmediately();
+ static void restart(uv_timer_t *timer, int);
+
+private:
+ HTTPCocoaContext *context = nullptr;
+ HTTPRequest *request = nullptr;
+ NSURLSessionDataTask *task = nullptr;
+ std::unique_ptr<Response> response;
+ std::unique_ptr<Response> existingResponse;
+ ResponseStatus status = ResponseStatus::PermanentError;
+ uv_async_t *async = nullptr;
+ int attempts = 0;
+ uv_timer_t *timer = nullptr;
+ enum : bool { PreemptImmediately, ExponentialBackoff } strategy = PreemptImmediately;
+
+ static const int maxAttempts = 4;
+};
+
+// -------------------------------------------------------------------------------------------------
+
+class HTTPCocoaContext : public HTTPContext<HTTPCocoaContext> {
+public:
+ HTTPCocoaContext(uv_loop_t *loop);
+};
+
+template<> pthread_key_t HTTPContext<HTTPCocoaContext>::key{};
+template<> pthread_once_t HTTPContext<HTTPCocoaContext>::once = PTHREAD_ONCE_INIT;
+
+HTTPCocoaContext::HTTPCocoaContext(uv_loop_t *loop_) : HTTPContext(loop_) {}
+
+// -------------------------------------------------------------------------------------------------
+
+HTTPRequestImpl::HTTPRequestImpl(HTTPRequest *request_, uv_loop_t *loop,
+ std::unique_ptr<Response> existingResponse_)
+ : context(HTTPCocoaContext::Get(loop)),
+ request(request_),
+ existingResponse(std::move(existingResponse_)),
+ async(new uv_async_t) {
+ assert(request);
+ context->addRequest(request);
+
+ async->data = this;
+ uv_async_init(loop, async, [](uv_async_t *as, int) {
+ auto impl = reinterpret_cast<HTTPRequestImpl *>(as->data);
+ impl->handleResponse();
+ });
+
+ start();
+}
+
+void HTTPRequestImpl::start() {
+ assert(!task);
+
+ attempts++;
+
+ @autoreleasepool {
+ NSMutableURLRequest *req = [[NSMutableURLRequest alloc]
+ initWithURL:[NSURL URLWithString:@(request->resource.url.c_str())]];
+ if (existingResponse) {
+ if (!existingResponse->etag.empty()) {
+ [req addValue:@(existingResponse->etag.c_str()) forHTTPHeaderField:@"If-None-Match"];
+ } else if (existingResponse->modified) {
+ const std::string time = util::rfc1123(existingResponse->modified);
+ [req addValue:@(time.c_str()) forHTTPHeaderField:@"If-Modified-Since"];
+ }
+ }
+
+ [req addValue:userAgent forHTTPHeaderField:@"User-Agent"];
+
+ task = [session dataTaskWithRequest:req
+ completionHandler:^(NSData *data, NSURLResponse *res,
+ NSError *error) { handleResult(data, res, error); }];
+ [req release];
+ [task resume];
+ }
+}
+
+void HTTPRequestImpl::handleResponse() {
+ task = nullptr;
+
+ if (request) {
+ if (status == ResponseStatus::TemporaryError && attempts < maxAttempts) {
+ strategy = ExponentialBackoff;
+ return retry((1 << (attempts - 1)) * 1000);
+ } else if (status == ResponseStatus::ConnectionError && attempts < maxAttempts) {
+ // By default, we will retry every 30 seconds (network change notification will
+ // preempt the timeout).
+ strategy = PreemptImmediately;
+ return retry(30000);
+ }
+
+ // Actually return the response.
+ if (status == ResponseStatus::NotModified) {
+ request->notify(std::move(response), FileCache::Hint::Refresh);
+ } else {
+ request->notify(std::move(response), FileCache::Hint::Full);
+ }
+
+ context->removeRequest(request);
+ delete request;
+ request = nullptr;
+ }
+
+ delete this;
+}
+
+void HTTPRequestImpl::cancel() {
+ context->removeRequest(request);
+ request = nullptr;
+
+ [task cancel];
+ task = nullptr;
+}
+
+HTTPRequestImpl::~HTTPRequestImpl() {
+ assert(!task);
+ assert(async);
+
+ uv::close(async);
+
+ if (request) {
+ context->removeRequest(request);
+ request->ptr = nullptr;
+ }
+}
+
+int64_t parseCacheControl(const char *value) {
+ if (value) {
+ unsigned long long seconds = 0;
+ // TODO: cache-control may contain other information as well:
+ // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9
+ if (std::sscanf(value, "max-age=%llu", &seconds) == 1) {
+ return std::chrono::duration_cast<std::chrono::seconds>(
+ std::chrono::system_clock::now().time_since_epoch()).count() +
+ seconds;
+ }
+ }
+
+ return 0;
+}
+
+void HTTPRequestImpl::handleResult(NSData *data, NSURLResponse *res, NSError *error) {
+ if (error) {
+ if ([error code] == NSURLErrorCancelled) {
+ status = ResponseStatus::Canceled;
+ } else {
+ // TODO: Use different codes for host not found, timeout, invalid URL etc.
+ // These can be categorized in temporary and permanent errors.
+ response = util::make_unique<Response>();
+ response->status = Response::Error;
+ response->message = [[error localizedDescription] UTF8String];
+
+ switch ([error code]) {
+ case NSURLErrorBadServerResponse: // 5xx errors
+ status = ResponseStatus::TemporaryError;
+ break;
+
+ case NSURLErrorTimedOut:
+ case NSURLErrorUserCancelledAuthentication:
+ status = ResponseStatus::SingularError; // retry immediately
+ break;
+
+ case NSURLErrorNetworkConnectionLost:
+ case NSURLErrorCannotFindHost:
+ case NSURLErrorCannotConnectToHost:
+ case NSURLErrorDNSLookupFailed:
+ case NSURLErrorNotConnectedToInternet:
+ case NSURLErrorInternationalRoamingOff:
+ case NSURLErrorCallIsActive:
+ case NSURLErrorDataNotAllowed:
+ status = ResponseStatus::ConnectionError;
+ break;
+
+ default:
+ status = ResponseStatus::PermanentError;
+ }
+ }
+ } else if ([res isKindOfClass:[NSHTTPURLResponse class]]) {
+ const long responseCode = [(NSHTTPURLResponse *)res statusCode];
+
+ response = util::make_unique<Response>();
+ response->data = {(const char *)[data bytes], [data length]};
+
+ NSDictionary *headers = [(NSHTTPURLResponse *)res allHeaderFields];
+ NSString *cache_control = [headers objectForKey:@"Cache-Control"];
+ if (cache_control) {
+ response->expires = parseCacheControl([cache_control UTF8String]);
+ }
+
+ NSString *expires = [headers objectForKey:@"Expires"];
+ if (expires) {
+ response->expires = parse_date([expires UTF8String]);
+ }
+
+ NSString *last_modified = [headers objectForKey:@"Last-Modified"];
+ if (last_modified) {
+ response->modified = parse_date([last_modified UTF8String]);
+ }
+
+ NSString *etag = [headers objectForKey:@"ETag"];
+ if (etag) {
+ response->etag = [etag UTF8String];
+ }
+
+ if (responseCode == 304) {
+ if (existingResponse) {
+ // We're going to reuse the old response object, but need to copy over the new
+ // expires value (if possible).
+ std::swap(response, existingResponse);
+ if (existingResponse->expires) {
+ response->expires = existingResponse->expires;
+ }
+ status = ResponseStatus::NotModified;
+ } else {
+ // This is an unsolicited 304 response and should only happen on malfunctioning
+ // HTTP servers. It likely doesn't include any data, but we don't have much options.
+ response->status = Response::Successful;
+ status = ResponseStatus::Successful;
+ }
+ } else if (responseCode == 200) {
+ response->status = Response::Successful;
+ status = ResponseStatus::Successful;
+ } else if (responseCode >= 500 && responseCode < 600) {
+ // Server errors may be temporary, so back off exponentially.
+ response->status = Response::Error;
+ response->message = "HTTP status code " + std::to_string(responseCode);
+ status = ResponseStatus::TemporaryError;
+ } else {
+ // We don't know how to handle any other errors, so declare them as permanently failing.
+ response->status = Response::Error;
+ response->message = "HTTP status code " + std::to_string(responseCode);
+ status = ResponseStatus::PermanentError;
+ }
+ } else {
+ // This should never happen.
+ status = ResponseStatus::PermanentError;
+ response = util::make_unique<Response>();
+ response->status = Response::Error;
+ response->message = "response class is not NSHTTPURLResponse";
+ }
+
+ uv_async_send(async);
+}
+
+void HTTPRequestImpl::retry(uint64_t timeout) {
+ response.reset();
+
+ assert(!timer);
+ timer = new uv_timer_t;
+ timer->data = this;
+ uv_timer_init(async->loop, timer);
+ uv_timer_start(timer, restart, timeout, 0);
+}
+
+void HTTPRequestImpl::retryImmediately() {
+ // All batons get notified when the network status changed, but some of them
+ // might not actually wait for the network to become available again.
+ if (timer && strategy == PreemptImmediately) {
+ // Triggers the timer upon the next event loop iteration.
+ uv_timer_stop(timer);
+ uv_timer_start(timer, restart, 0, 0);
+ }
+}
+
+void HTTPRequestImpl::restart(uv_timer_t *timer, int) {
+ // Restart the request.
+ auto impl = reinterpret_cast<HTTPRequestImpl *>(timer->data);
+
+ // Get rid of the timer.
+ impl->timer = nullptr;
+ uv::close(timer);
+
+ impl->start();
+}
+
+// -------------------------------------------------------------------------------------------------
+
+HTTPRequest::HTTPRequest(DefaultFileSource *source, const Resource &resource)
+ : SharedRequestBase(source, resource) {
+ // Global initialization.
+ dispatch_once(&request_initialize, ^{
+ NSURLSessionConfiguration *sessionConfig =
+ [NSURLSessionConfiguration defaultSessionConfiguration];
+ sessionConfig.timeoutIntervalForResource = 30;
+ sessionConfig.HTTPMaximumConnectionsPerHost = 8;
+ sessionConfig.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
+ sessionConfig.URLCache = nil;
+
+ session = [NSURLSession sessionWithConfiguration:sessionConfig];
+
+ // Write user agent string
+ userAgent = @"MapboxGL";
+ });
+}
+
+HTTPRequest::~HTTPRequest() {
+ MBGL_VERIFY_THREAD(tid);
+
+ if (ptr) {
+ reinterpret_cast<HTTPRequestImpl *>(ptr)->cancel();
+ }
+}
+
+void HTTPRequest::start(uv_loop_t *loop, std::unique_ptr<Response> response) {
+ MBGL_VERIFY_THREAD(tid);
+
+ assert(!ptr);
+ ptr = new HTTPRequestImpl(this, loop, std::move(response));
+}
+
+void HTTPRequest::retryImmediately() {
+ MBGL_VERIFY_THREAD(tid);
+
+ if (ptr) {
+ reinterpret_cast<HTTPRequestImpl *>(ptr)->retryImmediately();
+ }
+}
+
+void HTTPRequest::cancel() {
+ MBGL_VERIFY_THREAD(tid);
+
+ delete this;
+}
+
+}
diff --git a/platform/default/asset_request_libuv.cpp b/platform/default/asset_request_libuv.cpp
index 0e0b7280a7..36342bc876 100644
--- a/platform/default/asset_request_libuv.cpp
+++ b/platform/default/asset_request_libuv.cpp
@@ -1,19 +1,26 @@
-#include <mbgl/storage/asset_request.hpp>
+#include <mbgl/storage/default/asset_request.hpp>
#include <mbgl/storage/response.hpp>
-#include <mbgl/platform/platform.hpp>
#include <mbgl/util/std.hpp>
+#include <mbgl/util/util.hpp>
+#include <mbgl/util/uv.hpp>
#include <uv.h>
+#include <boost/algorithm/string.hpp>
-#include <limits>
+#include <cassert>
+
+
+namespace algo = boost::algorithm;
namespace mbgl {
-struct AssetRequestBaton {
- AssetRequestBaton(AssetRequest *request_, const std::string &path, uv_loop_t *loop);
- ~AssetRequestBaton();
+class AssetRequestImpl {
+ MBGL_STORE_THREAD(tid)
+
+public:
+ AssetRequestImpl(AssetRequest *request, uv_loop_t *loop);
+ ~AssetRequestImpl();
- void cancel();
static void fileOpened(uv_fs_t *req);
static void fileStated(uv_fs_t *req);
static void fileRead(uv_fs_t *req);
@@ -21,52 +28,32 @@ struct AssetRequestBaton {
static void notifyError(uv_fs_t *req);
static void cleanup(uv_fs_t *req);
- const std::thread::id threadId;
+
AssetRequest *request = nullptr;
+ bool canceled = false;
uv_fs_t req;
uv_file fd = -1;
- bool canceled = false;
- std::string body;
uv_buf_t buffer;
+ std::unique_ptr<Response> response;
};
-AssetRequestBaton::AssetRequestBaton(AssetRequest *request_, const std::string &path, uv_loop_t *loop)
- : threadId(std::this_thread::get_id()), request(request_) {
- req.data = this;
- uv_fs_open(loop, &req, path.c_str(), O_RDONLY, S_IRUSR, fileOpened);
-}
-
-AssetRequestBaton::~AssetRequestBaton() {
-}
-
-void AssetRequestBaton::cancel() {
- canceled = true;
+AssetRequestImpl::~AssetRequestImpl() {
+ MBGL_VERIFY_THREAD(tid);
- // uv_cancel fails frequently when the request has already been started.
- // In that case, we have to let it complete and check the canceled bool
- // instead.
- uv_cancel((uv_req_t *)&req);
+ if (request) {
+ request->ptr = nullptr;
+ }
}
-void AssetRequestBaton::notifyError(uv_fs_t *req) {
- AssetRequestBaton *ptr = reinterpret_cast<AssetRequestBaton *>(req->data);
- assert(std::this_thread::get_id() == ptr->threadId);
-
- if (ptr->request && req->result < 0 && !ptr->canceled && req->result != UV_ECANCELED) {
- ptr->request->response = util::make_unique<Response>();
- ptr->request->response->code = req->result == UV_ENOENT ? 404 : 500;
-#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10
- ptr->request->response->message = uv_strerror(uv_last_error(req->loop));
-#else
- ptr->request->response->message = uv_strerror(int(req->result));
-#endif
- ptr->request->notify();
- }
+AssetRequestImpl::AssetRequestImpl(AssetRequest *request_, uv_loop_t *loop) : request(request_) {
+ req.data = this;
+ uv_fs_open(loop, &req, (request->resource.url.substr(8)).c_str(), O_RDONLY, S_IRUSR, fileOpened);
}
-void AssetRequestBaton::fileOpened(uv_fs_t *req) {
- AssetRequestBaton *ptr = reinterpret_cast<AssetRequestBaton *>(req->data);
- assert(std::this_thread::get_id() == ptr->threadId);
+void AssetRequestImpl::fileOpened(uv_fs_t *req) {
+ assert(req->data);
+ auto self = reinterpret_cast<AssetRequestImpl *>(req->data);
+ MBGL_VERIFY_THREAD(self->tid);
if (req->result < 0) {
// Opening failed or was canceled. There isn't much left we can do.
@@ -78,88 +65,93 @@ void AssetRequestBaton::fileOpened(uv_fs_t *req) {
// We're going to reuse this handle, so we need to cleanup first.
uv_fs_req_cleanup(req);
- if (ptr->canceled || !ptr->request) {
- // Either the AssetRequest object has been destructed, or the
- // request was canceled.
+ if (self->canceled) {
+ // The request was canceled.
uv_fs_close(req->loop, req, fd, fileClosed);
} else {
- ptr->fd = fd;
+ self->fd = fd;
uv_fs_fstat(req->loop, req, fd, fileStated);
}
}
}
-void AssetRequestBaton::fileStated(uv_fs_t *req) {
- AssetRequestBaton *ptr = reinterpret_cast<AssetRequestBaton *>(req->data);
- assert(std::this_thread::get_id() == ptr->threadId);
+void AssetRequestImpl::fileStated(uv_fs_t *req) {
+ assert(req->data);
+ auto self = reinterpret_cast<AssetRequestImpl *>(req->data);
+ MBGL_VERIFY_THREAD(self->tid);
- if (req->result != 0 || ptr->canceled || !ptr->request) {
+ if (req->result != 0 || self->canceled) {
// Stating failed or was canceled. We already have an open file handle
// though, which we'll have to close.
notifyError(req);
uv_fs_req_cleanup(req);
- uv_fs_close(req->loop, req, ptr->fd, fileClosed);
+ uv_fs_close(req->loop, req, self->fd, fileClosed);
} else {
#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10
- const uv_statbuf_t *stat = static_cast<const uv_statbuf_t *>(req->ptr);
+ auto stat = static_cast<const uv_statbuf_t *>(req->ptr);
#else
- const uv_stat_t *stat = static_cast<const uv_stat_t *>(req->ptr);
+ auto stat = static_cast<const uv_stat_t *>(req->ptr);
#endif
if (stat->st_size > std::numeric_limits<int>::max()) {
// File is too large for us to open this way because uv_buf's only support unsigned
// ints as maximum size.
- if (ptr->request) {
- ptr->request->response = util::make_unique<Response>();
- ptr->request->response->code = UV_EFBIG;
+ auto response = util::make_unique<Response>();
+ response->status = Response::Error;
#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10
- ptr->request->response->message = uv_strerror(uv_err_t {UV_EFBIG, 0});
+ response->message = uv_strerror(uv_err_t {UV_EFBIG, 0});
#else
- ptr->request->response->message = uv_strerror(UV_EFBIG);
+ response->message = uv_strerror(UV_EFBIG);
#endif
- ptr->request->notify();
- }
+ assert(self->request);
+ self->request->notify(std::move(response), FileCache::Hint::No);
+ delete self->request;
uv_fs_req_cleanup(req);
- uv_fs_close(req->loop, req, ptr->fd, fileClosed);
+ uv_fs_close(req->loop, req, self->fd, fileClosed);
} else {
- const unsigned int size = (unsigned int)(stat->st_size);
- ptr->body.resize(size);
- ptr->buffer = uv_buf_init(const_cast<char *>(ptr->body.data()), size);
+ self->response = util::make_unique<Response>();
+ self->response->modified = stat->st_mtimespec.tv_sec;
+ self->response->etag = std::to_string(stat->st_ino);
+ const auto size = (unsigned int)(stat->st_size);
+ self->response->data.resize(size);
+ self->buffer = uv_buf_init(const_cast<char *>(self->response->data.data()), size);
uv_fs_req_cleanup(req);
#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10
- uv_fs_read(req->loop, req, ptr->fd, ptr->buffer.base, ptr->buffer.len, -1, fileRead);
+ uv_fs_read(req->loop, req, self->fd, self->buffer.base, self->buffer.len, -1, fileRead);
#else
- uv_fs_read(req->loop, req, ptr->fd, &ptr->buffer, 1, 0, fileRead);
+ uv_fs_read(req->loop, req, self->fd, &self->buffer, 1, 0, fileRead);
#endif
}
}
}
-void AssetRequestBaton::fileRead(uv_fs_t *req) {
- AssetRequestBaton *ptr = reinterpret_cast<AssetRequestBaton *>(req->data);
- assert(std::this_thread::get_id() == ptr->threadId);
+void AssetRequestImpl::fileRead(uv_fs_t *req) {
+ assert(req->data);
+ auto self = reinterpret_cast<AssetRequestImpl *>(req->data);
+ MBGL_VERIFY_THREAD(self->tid);
- if (req->result < 0 || ptr->canceled || !ptr->request) {
- // Reading failed or was canceled. We already have an open file handle
+ if (req->result < 0 || self->canceled) {
+ // Stating failed or was canceled. We already have an open file handle
// though, which we'll have to close.
notifyError(req);
} else {
// File was successfully read.
- if (ptr->request) {
- ptr->request->response = util::make_unique<Response>();
- ptr->request->response->code = 200;
- ptr->request->response->data = std::move(ptr->body);
- ptr->request->notify();
- }
+ self->response->status = Response::Successful;
+ assert(self->request);
+ self->request->notify(std::move(self->response), FileCache::Hint::No);
+ delete self->request;
}
uv_fs_req_cleanup(req);
- uv_fs_close(req->loop, req, ptr->fd, fileClosed);
+ uv_fs_close(req->loop, req, self->fd, fileClosed);
}
-void AssetRequestBaton::fileClosed(uv_fs_t *req) {
- assert(std::this_thread::get_id() == (reinterpret_cast<AssetRequestBaton *>(req->data))->threadId);
+void AssetRequestImpl::fileClosed(uv_fs_t *req) {
+ assert(req->data);
+ auto self = reinterpret_cast<AssetRequestImpl *>(req->data);
+ MBGL_VERIFY_THREAD(self->tid);
+ (void(self)); // Silence unused variable error in Release mode
if (req->result < 0) {
// Closing the file failed. But there isn't anything we can do.
@@ -168,56 +160,70 @@ void AssetRequestBaton::fileClosed(uv_fs_t *req) {
cleanup(req);
}
-void AssetRequestBaton::cleanup(uv_fs_t *req) {
- AssetRequestBaton *ptr = reinterpret_cast<AssetRequestBaton *>(req->data);
- assert(std::this_thread::get_id() == ptr->threadId);
-
- if (ptr->request) {
- ptr->request->ptr = nullptr;
+void AssetRequestImpl::notifyError(uv_fs_t *req) {
+ assert(req->data);
+ auto self = reinterpret_cast<AssetRequestImpl *>(req->data);
+ MBGL_VERIFY_THREAD(self->tid);
+
+ if (req->result < 0 && !self->canceled && req->result != UV_ECANCELED) {
+ auto response = util::make_unique<Response>();
+ response->status = Response::Error;
+ response->message = uv::getFileRequestError(req);
+ assert(self->request);
+ self->request->notify(std::move(response), FileCache::Hint::No);
+ delete self->request;
}
+}
+void AssetRequestImpl::cleanup(uv_fs_t *req) {
+ assert(req->data);
+ auto self = reinterpret_cast<AssetRequestImpl *>(req->data);
+ MBGL_VERIFY_THREAD(self->tid);
uv_fs_req_cleanup(req);
- delete ptr;
- ptr = nullptr;
+ delete self;
}
+// -------------------------------------------------------------------------------------------------
-AssetRequest::AssetRequest(const std::string &path_, uv_loop_t *loop)
- : BaseRequest(path_) {
- if (!path.empty() && path[0] == '/') {
- // This is an absolute path. We don't allow this. Note that this is not a way to absolutely
- // prevent access to resources outside the application bundle; e.g. there could be symlinks
- // in the application bundle that link to outside. We don't care about these.
- response = util::make_unique<Response>();
- response->code = 403;
- response->message = "Path is outside the application bundle";
- notify();
- } else {
- // Note: The AssetRequestBaton object is deleted in AssetRequestBaton::cleanup().
- ptr = new AssetRequestBaton(this, platform::applicationRoot() + "/" + path, loop);
- }
+AssetRequest::AssetRequest(DefaultFileSource *source, const Resource &resource)
+ : SharedRequestBase(source, resource) {
+ assert(algo::starts_with(resource.url, "asset://"));
}
-void AssetRequest::cancel() {
- assert(std::this_thread::get_id() == threadId);
+AssetRequest::~AssetRequest() {
+ MBGL_VERIFY_THREAD(tid);
if (ptr) {
- ptr->cancel();
-
- // When deleting a AssetRequest object with a uv_fs_* call is in progress, we are making sure
- // that the callback doesn't accidentally reference this object again.
- ptr->request = nullptr;
- ptr = nullptr;
+ reinterpret_cast<AssetRequestImpl *>(ptr)->request = nullptr;
}
+}
+
+void AssetRequest::start(uv_loop_t *loop, std::unique_ptr<Response> response) {
+ MBGL_VERIFY_THREAD(tid);
+
+ // We're ignoring the existing response if any.
+ (void(response));
- notify();
+ assert(!ptr);
+ ptr = new AssetRequestImpl(this, loop);
+ // Note: the AssetRequestImpl deletes itself.
}
-AssetRequest::~AssetRequest() {
- assert(std::this_thread::get_id() == threadId);
- cancel();
+void AssetRequest::cancel() {
+ MBGL_VERIFY_THREAD(tid);
- // Note: The AssetRequestBaton object is deleted in AssetRequestBaton::cleanup().
+ if (ptr) {
+ reinterpret_cast<AssetRequestImpl *>(ptr)->canceled = true;
+
+ // uv_cancel fails frequently when the request has already been started.
+ // In that case, we have to let it complete and check the canceled bool
+ // instead. The cancelation callback will delete the AssetRequest object.
+ uv_cancel((uv_req_t *)&reinterpret_cast<AssetRequestImpl *>(ptr)->req);
+ } else {
+ // This request is canceled before we called start. We're safe to delete
+ // ourselves now.
+ delete this;
+ }
}
}
diff --git a/platform/default/cache_database_tmp.cpp b/platform/default/cache_database_tmp.cpp
deleted file mode 100644
index 6132ace692..0000000000
--- a/platform/default/cache_database_tmp.cpp
+++ /dev/null
@@ -1,12 +0,0 @@
-#include <mbgl/platform/platform.hpp>
-
-namespace mbgl {
-namespace platform {
-
-// Returns the path to the default cache database on this system.
-std::string defaultCacheDatabase() {
- return "/tmp/mbgl-cache.db";
-}
-
-}
-}
diff --git a/platform/default/http_request_baton_curl.cpp b/platform/default/http_request_baton_curl.cpp
deleted file mode 100644
index 9e8cf64716..0000000000
--- a/platform/default/http_request_baton_curl.cpp
+++ /dev/null
@@ -1,629 +0,0 @@
-#include <mbgl/mbgl.hpp>
-#include <mbgl/storage/http_request_baton.hpp>
-#include <mbgl/util/uv-messenger.h>
-#include <mbgl/util/time.hpp>
-#include <mbgl/util/string.hpp>
-#include <mbgl/util/std.hpp>
-#include <mbgl/util/version.hpp>
-
-#ifdef __ANDROID__
- #include <mbgl/android/jni.hpp>
- #include <zip.h>
- #include <openssl/ssl.h>
-#endif
-
-#include <uv.h>
-#include <curl/curl.h>
-
-#include <sys/utsname.h>
-
-#include <queue>
-#include <cassert>
-#include <cstring>
-#include <thread>
-
-
-// Check curl library version.
-const static bool curl_version_check = []() {
- const auto version = curl_version_info(CURLVERSION_NOW);
- if (version->version_num != LIBCURL_VERSION_NUM) {
- throw std::runtime_error(mbgl::util::sprintf<96>(
- "libcurl version mismatch: headers report %d.%d.%d, but library reports %d.%d.%d",
- (LIBCURL_VERSION_NUM >> 16) & 0xFF, (LIBCURL_VERSION_NUM >> 8) & 0xFF, LIBCURL_VERSION_NUM & 0xFF,
- (version->version_num >> 16) & 0xFF, (version->version_num >> 8) & 0xFF, version->version_num & 0xFF));
- }
- return true;
-}();
-
-
-// This file contains code from http://curl.haxx.se/libcurl/c/multi-uv.html:
-
-/***************************************************************************
- * _ _ ____ _
- * Project ___| | | | _ \| |
- * / __| | | | |_) | |
- * | (__| |_| | _ <| |___
- * \___|\___/|_| \_\_____|
- *
- * Copyright (C) 1998 - 2014, Daniel Stenberg, <daniel@haxx.se>, et al.
- *
- * This software is licensed as described in the file COPYING, which
- * you should have received as part of this distribution. The terms
- * are also available at http://curl.haxx.se/docs/copyright.html.
- *
- * You may opt to use, copy, modify, merge, publish, distribute and/or sell
- * copies of the Software, and permit persons to whom the Software is
- * furnished to do so, under the terms of the COPYING file.
- *
- * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
- * KIND, either express or implied.
- *
- ***************************************************************************/
-
-/* Example application code using the multi socket interface to download
- multiple files at once, but instead of using curl_multi_perform and
- curl_multi_wait, which uses select(), we use libuv.
- It supports epoll, kqueue, etc. on unixes and fast IO completion ports on
- Windows, which means, it should be very fast on all platforms..
-
- Written by Clemens Gruber, based on an outdated example from uvbook and
- some tests from libuv.
-
- Requires libuv and (of course) libcurl.
-
- See http://nikhilm.github.com/uvbook/ for more information on libuv.
-*/
-
-// Handles the request thread + messaging to the thread.
-static uv_once_t once;
-static uv_loop_t *loop = nullptr;
-static uv_messenger_t start_messenger;
-static uv_messenger_t stop_messenger;
-static uv_thread_t thread;
-static std::thread::id 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<CURL *> handles;
-
-namespace mbgl {
-
-struct Context {
- const util::ptr<HTTPRequestBaton> baton;
- CURL *handle = nullptr;
- curl_slist *headers = nullptr;
-
- Context(const util::ptr<HTTPRequestBaton> &baton_) : baton(baton_) {
- assert(baton);
- baton->ptr = this;
-
- if (!handles.empty()) {
- handle = handles.front();
- handles.pop();
- } else {
- handle = curl_easy_init();
- }
- }
-
- ~Context() {
- baton->ptr = nullptr;
-
- if (headers) {
- curl_slist_free_all(headers);
- headers = nullptr;
- }
-
- CURLMcode error = curl_multi_remove_handle(multi, handle);
- if (error != CURLM_OK) {
- baton->response = util::make_unique<Response>();
- baton->response->code = -1;
- baton->response->message = curl_multi_strerror(error);
- }
-
- curl_easy_setopt(handle, CURLOPT_PRIVATE, nullptr);
- curl_easy_reset(handle);
- handles.push(handle);
- handle = nullptr;
-
- if (baton->async) {
- uv_async_send(baton->async);
- baton->async = nullptr;
- }
- }
-};
-
-struct Socket {
-private:
- uv_poll_t poll_handle;
-
-public:
- const curl_socket_t sockfd = 0;
-
-public:
- Socket(curl_socket_t sockfd_) : sockfd(sockfd_) {
- uv_poll_init_socket(loop, &poll_handle, sockfd);
- poll_handle.data = this;
- }
-
- void start(int events, uv_poll_cb cb) {
- uv_poll_start(&poll_handle, events, cb);
- }
-
- void stop() {
- assert(poll_handle.data);
- poll_handle.data = nullptr;
- uv_poll_stop(&poll_handle);
- uv_close((uv_handle_t *)&poll_handle, [](uv_handle_t *handle) {
- delete (Socket *)handle->data;
- });
- }
-
-private:
- // Make the destructor private to ensure that we can only close the Socket
- // with stop(), and disallow manual deletion.
- ~Socket() {
- assert(!poll_handle.data);
- }
-};
-
-// 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);
-}
-
-void check_multi_info() {
- CURLMsg *message = nullptr;
- int pending = 0;
-
- while ((message = curl_multi_info_read(multi, &pending))) {
- switch (message->msg) {
- case CURLMSG_DONE: {
- Context *context = nullptr;
- curl_easy_getinfo(message->easy_handle, CURLINFO_PRIVATE, (char *)&context);
- assert(context);
-
- auto baton = context->baton;
-
- // This request is complete. We are removing the pointer to the CURL easy handle again
- // to prevent this request from getting canceled.
- context->baton->ptr = nullptr;
-
- // 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");
- }
- }
-
- delete context;
- } break;
-
- default:
- // This should never happen, because there are no other message types.
- throw std::runtime_error("CURLMSG returned unknown message type");
- }
- }
-}
-
-void curl_perform(uv_poll_t *req, int /* status */, int events) {
- int flags = 0;
-
- uv_timer_stop(&timeout);
-
- if (events & UV_READABLE) {
- flags |= CURL_CSELECT_IN;
- }
- if (events & UV_WRITABLE) {
- flags |= CURL_CSELECT_OUT;
- }
-
- Socket *context = (Socket *)req->data;
- int running_handles = 0;
- curl_multi_socket_action(multi, context->sockfd, flags, &running_handles);
-
- check_multi_info();
-}
-
-int handle_socket(CURL * /* handle */, curl_socket_t s, int action, void * /* userp */, void *socketp) {
- Socket *socket = (Socket *)socketp;
- if (!socket && action != CURL_POLL_REMOVE) {
- socket = new Socket(s);
- curl_multi_assign(multi, s, (void *)socket);
- }
-
- switch (action) {
- case CURL_POLL_IN:
- socket->start(UV_READABLE, curl_perform);
- break;
- case CURL_POLL_OUT:
- socket->start(UV_WRITABLE, curl_perform);
- break;
- case CURL_POLL_REMOVE:
- if (socket) {
- socket->stop();
- curl_multi_assign(multi, s, NULL);
- }
- break;
- default:
- abort();
- }
-
- return 0;
-}
-
-#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10
-void on_timeout(uv_timer_t * /* req */, int /* status */) {
-#else
-void on_timeout(uv_timer_t * /* req */) {
-#endif
- 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));
- }
-
- check_multi_info();
-}
-
-void start_timeout(CURLM * /* multi */, long timeout_ms, void * /* userp */) {
- if (timeout_ms <= 0) {
- timeout_ms = 1; /* 0 means directly call socket_action, but we'll do it in a bit */
- }
- uv_timer_start(&timeout, on_timeout, timeout_ms, 0);
-}
-
-void thread_init(void *) {
-#ifdef __APPLE__
- pthread_setname_np("CURL");
-#endif
- thread_id = std::this_thread::get_id();
-
- if (curl_global_init(CURL_GLOBAL_ALL)) {
- throw std::runtime_error("Could not init cURL");
- }
-
- 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 = std::thread::id();
-}
-
-// 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) {
- auto &response = *(std::unique_ptr<Response> *)userp;
- assert(response);
- response->data.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;
-
- auto &response = *(std::unique_ptr<Response> *)userp;
- assert(response);
-
- 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
- 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
- response->expires = Response::parseCacheControl(value.c_str());
- }
-
- return length;
-}
-
-// This function is called to load the CA bundle
-// from http://curl.haxx.se/libcurl/c/cacertinmem.html
-#ifdef __ANDROID__
-static CURLcode sslctx_function(CURL */*curl*/, void *sslctx, void */*parm*/) {
-
- int error = 0;
- struct zip *apk = zip_open(mbgl::android::apkPath.c_str(), 0, &error);
- if (apk == nullptr) {
- return CURLE_SSL_CACERT_BADFILE;
- }
-
- struct zip_file *apkFile = zip_fopen(apk, "assets/ca-bundle.crt", ZIP_FL_NOCASE);
- if (apkFile == nullptr) {
- zip_close(apk);
- apk = nullptr;
- return CURLE_SSL_CACERT_BADFILE;
- }
-
- struct zip_stat stat;
- if (zip_stat(apk, "assets/ca-bundle.crt", ZIP_FL_NOCASE, &stat) != 0) {
- zip_fclose(apkFile);
- apkFile = nullptr;
- zip_close(apk);
- apk = nullptr;
- return CURLE_SSL_CACERT_BADFILE;
- }
-
- if (stat.size > std::numeric_limits<int>::max()) {
- zip_fclose(apkFile);
- apkFile = nullptr;
- zip_close(apk);
- apk = nullptr;
- return CURLE_SSL_CACERT_BADFILE;
- }
-
- const std::unique_ptr<char[]> pem = util::make_unique<char[]>(stat.size);
-
- if (static_cast<zip_uint64_t>(zip_fread(apkFile, reinterpret_cast<void *>(pem.get()), stat.size)) != stat.size) {
- zip_fclose(apkFile);
- apkFile = nullptr;
- zip_close(apk);
- apk = nullptr;
- return CURLE_SSL_CACERT_BADFILE;
- }
-
- // get a pointer to the X509 certificate store (which may be empty!)
- X509_STORE *store = SSL_CTX_get_cert_store((SSL_CTX *)sslctx);
- if (store == nullptr) {
- return CURLE_SSL_CACERT_BADFILE;
- }
-
- // get a BIO
- BIO *bio = BIO_new_mem_buf(pem.get(), static_cast<int>(stat.size));
- if (bio == nullptr) {
- store = nullptr;
- return CURLE_SSL_CACERT_BADFILE;
- }
-
- // use it to read the PEM formatted certificate from memory into an X509
- // structure that SSL can use
- X509 *cert = nullptr;
- while (PEM_read_bio_X509(bio, &cert, 0, nullptr) != nullptr) {
- if (cert == nullptr) {
- BIO_free(bio);
- bio = nullptr;
- store = nullptr;
- return CURLE_SSL_CACERT_BADFILE;
- }
-
- // add our certificate to this store
- if (X509_STORE_add_cert(store, cert) == 0) {
- X509_free(cert);
- cert = nullptr;
- BIO_free(bio);
- bio = nullptr;
- store = nullptr;
- return CURLE_SSL_CACERT_BADFILE;
- }
-
- X509_free(cert);
- cert = nullptr;
- }
-
- // decrease reference counts
- BIO_free(bio);
- bio = nullptr;
-
- zip_fclose(apkFile);
- apkFile = nullptr;
- zip_close(apk);
- apk = nullptr;
-
- // all set to go
- return CURLE_OK;
-}
-#endif
-
-std::string buildUserAgentString() {
-#ifdef __ANDROID__
- return util::sprintf<128>("MapboxGL/%d.%d.%d (+https://mapbox.com/mapbox-gl/; %s; %s %s)",
- version::major, version::minor, version::patch, version::revision, "Android", mbgl::android::androidRelease.c_str());
-#else
- utsname name;
- uname(&name);
- return util::sprintf<128>("MapboxGL/%d.%d.%d (+https://mapbox.com/mapbox-gl/; %s; %s %s)",
- version::major, version::minor, version::patch, version::revision, name.sysname, name.release);
-#endif
-}
-
-// This function must run in the CURL thread.
-void start_request(void *const ptr) {
- assert(std::this_thread::get_id() == thread_id);
- static const std::string userAgent = buildUserAgentString();
-
- // The Context object stores information that we need to retain throughout the request, such
- // as the actual CURL easy handle, the baton, and the list of headers. The Context itself is
- // stored in both the CURL easy handle's PRIVATE field, and the baton's `ptr` field.
- auto context = new Context(*(util::ptr<HTTPRequestBaton> *)ptr);
- delete (util::ptr<HTTPRequestBaton> *)ptr;
-
- if (context->baton->response) {
- if (!context->baton->response->etag.empty()) {
- const std::string header = std::string("If-None-Match: ") + context->baton->response->etag;
- context->headers = curl_slist_append(context->headers, header.c_str());
- } else if (context->baton->response->modified) {
- const std::string time =
- std::string("If-Modified-Since: ") + util::rfc1123(context->baton->response->modified);
- context->headers = curl_slist_append(context->headers, time.c_str());
- }
- }
-
- if (context->headers) {
- curl_easy_setopt(context->handle, CURLOPT_HTTPHEADER, context->headers);
- }
-
- if (!context->baton->response) {
- context->baton->response = util::make_unique<Response>();
- }
-
- // Carry on the shared pointer in the private information of the CURL handle.
- curl_easy_setopt(context->handle, CURLOPT_PRIVATE, context);
-#ifndef __ANDROID__
- curl_easy_setopt(context->handle, CURLOPT_CAINFO, "ca-bundle.crt");
-#else
- curl_easy_setopt(context->handle, CURLOPT_SSLCERTTYPE, "PEM");
- curl_easy_setopt(context->handle, CURLOPT_SSL_CTX_FUNCTION, sslctx_function);
-#endif
- curl_easy_setopt(context->handle, CURLOPT_FOLLOWLOCATION, 1);
- curl_easy_setopt(context->handle, CURLOPT_URL, context->baton->path.c_str());
- curl_easy_setopt(context->handle, CURLOPT_WRITEFUNCTION, curl_write_cb);
- curl_easy_setopt(context->handle, CURLOPT_WRITEDATA, &context->baton->response);
- curl_easy_setopt(context->handle, CURLOPT_HEADERFUNCTION, curl_header_cb);
- curl_easy_setopt(context->handle, CURLOPT_HEADERDATA, &context->baton->response);
- curl_easy_setopt(context->handle, CURLOPT_ACCEPT_ENCODING, "gzip, deflate");
- curl_easy_setopt(context->handle, CURLOPT_USERAGENT, userAgent.c_str());
- curl_easy_setopt(context->handle, CURLOPT_SHARE, share);
-
- // Start requesting the information.
- curl_multi_add_handle(multi, context->handle);
-}
-
-// This function must run in the CURL thread.
-void stop_request(void *const ptr) {
- assert(std::this_thread::get_id() == thread_id);
- auto baton = *(util::ptr<HTTPRequestBaton> *)ptr;
- delete (util::ptr<HTTPRequestBaton> *)ptr;
- assert(baton);
-
- if (baton->async) {
- baton->type = HTTPResponseType::Canceled;
-
- assert(baton->ptr);
-
- // We can still stop the request because it is still in progress.
- delete (Context *)baton->ptr;
- assert(!baton->ptr);
- } 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.
- }
-}
-
-void create_thread() {
- uv_mutex_init(&share_mutex);
-#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10
- loop = uv_loop_new();
-#else
- loop = new uv_loop_t;
- uv_loop_init(loop);
-#endif
- 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<HTTPRequestBaton> &ptr) {
- assert(std::this_thread::get_id() == ptr->threadId);
- uv_once(&once, create_thread);
- uv_messenger_send(&start_messenger, new util::ptr<HTTPRequestBaton>(ptr));
-}
-
-// This function must be run from the main thread (== where the HTTPRequestBaton was created)
-void HTTPRequestBaton::stop(const util::ptr<HTTPRequestBaton> &ptr) {
- assert(std::this_thread::get_id() == ptr->threadId);
- uv_once(&once, create_thread);
- uv_messenger_send(&stop_messenger, new util::ptr<HTTPRequestBaton>(ptr));
-}
-
-}
diff --git a/platform/default/http_request_curl.cpp b/platform/default/http_request_curl.cpp
new file mode 100644
index 0000000000..42764e4927
--- /dev/null
+++ b/platform/default/http_request_curl.cpp
@@ -0,0 +1,640 @@
+#include <mbgl/storage/default/http_request.hpp>
+#include <mbgl/storage/default/http_context.hpp>
+#include <mbgl/storage/response.hpp>
+
+#include <mbgl/util/time.hpp>
+
+#include <curl/curl.h>
+
+#include <queue>
+#include <map>
+#include <cassert>
+
+
+void handleError(CURLMcode code) {
+ if (code != CURLM_OK) {
+ throw std::runtime_error(std::string("CURL multi error: ") + curl_multi_strerror(code));
+ }
+}
+
+void handleError(CURLcode code) {
+ if (code != CURLE_OK) {
+ throw std::runtime_error(std::string("CURL easy error: ") + curl_easy_strerror(code));
+ }
+}
+
+namespace mbgl {
+
+enum class ResponseStatus : int8_t {
+ // This error probably won't be resolved by retrying anytime soon. We are giving up.
+ PermanentError,
+
+ // This error might be resolved by waiting some time (e.g. server issues).
+ // We are going to do an exponential back-off and will try again in a few seconds.
+ TemporaryError,
+
+ // This error might be resolved once the network reachability status changes.
+ // We are going to watch the network status for changes and will retry as soon as the
+ // operating system notifies us of a network status change.
+ ConnectionError,
+
+ // The request returned data successfully. We retrieved and decoded the data successfully.
+ Successful,
+
+ // The request confirmed that the data wasn't changed. We already have the data.
+ NotModified,
+};
+
+class HTTPRequestImpl;
+
+class HTTPCURLContext : public HTTPContext<HTTPCURLContext> {
+
+public:
+ HTTPCURLContext(uv_loop_t *loop);
+ ~HTTPCURLContext();
+
+ static int handleSocket(CURL *handle, curl_socket_t s, int action, void *userp, void *socketp);
+ static void perform(uv_poll_t *req, int status, int events);
+ static int startTimeout(CURLM *multi, long timeout_ms, void *userp);
+#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10
+ static void onTimeout(uv_timer_t *req, int status);
+#else
+ static void onTimeout(uv_timer_t *req);
+#endif
+
+ CURL *getHandle();
+ void returnHandle(CURL *handle);
+ void checkMultiInfo();
+
+public:
+ // Used as the CURL timer function to periodically check for socket updates.
+ uv_timer_t *timeout = nullptr;
+
+ // CURL multi handle that we use to request multiple URLs at the same time, without having to
+ // block and spawn threads.
+ CURLM *multi = nullptr;
+
+ // CURL share handles are used for sharing session state (e.g.)
+ CURLSH *share = nullptr;
+
+ // A queue that we use for storing resuable CURL easy handles to avoid creating and destroying
+ // them all the time.
+ std::queue<CURL *> handles;
+};
+
+
+class HTTPRequestImpl {
+ MBGL_STORE_THREAD(tid)
+
+public:
+ HTTPRequestImpl(HTTPRequest *request, uv_loop_t *loop, std::unique_ptr<Response> response);
+ ~HTTPRequestImpl();
+
+ void handleResult(CURLcode code);
+ void abandon();
+ void retryImmediately();
+
+private:
+ static size_t headerCallback(char *const buffer, const size_t size, const size_t nmemb, void *userp);
+ static size_t writeCallback(void *const contents, const size_t size, const size_t nmemb, void *userp);
+
+ void retry(uint64_t timeout);
+ static void restart(uv_timer_t *timer, int);
+ void finish(ResponseStatus status);
+ void start();
+
+private:
+ HTTPCURLContext *context = nullptr;
+ HTTPRequest *request = nullptr;
+
+ // Will store the current response.
+ std::unique_ptr<Response> response;
+
+ // In case of revalidation requests, this will store the old response.
+ std::unique_ptr<Response> existingResponse;
+
+ CURL *handle = nullptr;
+ curl_slist *headers = nullptr;
+
+ uv_timer_t *timer = nullptr;
+ enum : bool { PreemptImmediately, ExponentialBackoff } strategy = PreemptImmediately;
+ int attempts = 0;
+
+ static const int maxAttempts = 4;
+};
+
+
+
+struct Socket {
+private:
+ uv_poll_t poll;
+
+public:
+ HTTPCURLContext *context = nullptr;
+ const curl_socket_t sockfd = 0;
+
+public:
+ Socket(HTTPCURLContext *context_, curl_socket_t sockfd_) : context(context_), sockfd(sockfd_) {
+ assert(context);
+ uv_poll_init_socket(context->loop, &poll, sockfd);
+ poll.data = this;
+ }
+
+ void start(int events, uv_poll_cb cb) {
+ uv_poll_start(&poll, events, cb);
+ }
+
+ void stop() {
+ assert(poll.data);
+ uv_poll_stop(&poll);
+ uv_close((uv_handle_t *)&poll, [](uv_handle_t *handle) {
+ assert(handle->data);
+ delete reinterpret_cast<Socket *>(handle->data);
+ });
+ }
+
+private:
+ // Make the destructor private to ensure that we can only close the Socket
+ // with stop(), and disallow manual deletion.
+ ~Socket() = default;
+};
+
+// -------------------------------------------------------------------------------------------------
+
+template<> pthread_key_t HTTPContext<HTTPCURLContext>::key{};
+template<> pthread_once_t HTTPContext<HTTPCURLContext>::once = PTHREAD_ONCE_INIT;
+
+HTTPCURLContext::HTTPCURLContext(uv_loop_t *loop_) : HTTPContext(loop_) {
+ if (curl_global_init(CURL_GLOBAL_ALL)) {
+ throw std::runtime_error("Could not init cURL");
+ }
+
+ timeout = new uv_timer_t;
+ timeout->data = this;
+ uv_timer_init(loop, timeout);
+
+ share = curl_share_init();
+
+ multi = curl_multi_init();
+ handleError(curl_multi_setopt(multi, CURLMOPT_SOCKETFUNCTION, handleSocket));
+ handleError(curl_multi_setopt(multi, CURLMOPT_SOCKETDATA, this));
+ handleError(curl_multi_setopt(multi, CURLMOPT_TIMERFUNCTION, startTimeout));
+ handleError(curl_multi_setopt(multi, CURLMOPT_TIMERDATA, this));
+}
+
+HTTPCURLContext::~HTTPCURLContext() {
+ curl_multi_cleanup(multi);
+ multi = nullptr;
+
+ curl_share_cleanup(share);
+ share = nullptr;
+
+ uv_timer_stop(timeout);
+ uv::close(timeout);
+}
+
+CURL *HTTPCURLContext::getHandle() {
+ if (!handles.empty()) {
+ auto handle = handles.front();
+ handles.pop();
+ return handle;
+ } else {
+ return curl_easy_init();
+ }
+}
+
+void HTTPCURLContext::returnHandle(CURL *handle) {
+ curl_easy_reset(handle);
+ handles.push(handle);
+}
+
+void HTTPCURLContext::checkMultiInfo() {
+ MBGL_VERIFY_THREAD(tid);
+ CURLMsg *message = nullptr;
+ int pending = 0;
+
+ while ((message = curl_multi_info_read(multi, &pending))) {
+ switch (message->msg) {
+ case CURLMSG_DONE: {
+ HTTPRequestImpl *baton = nullptr;
+ curl_easy_getinfo(message->easy_handle, CURLINFO_PRIVATE, (char *)&baton);
+ assert(baton);
+ baton->handleResult(message->data.result);
+ } break;
+
+ default:
+ // This should never happen, because there are no other message types.
+ throw std::runtime_error("CURLMsg returned unknown message type");
+ }
+ }
+}
+
+void HTTPCURLContext::perform(uv_poll_t *req, int /* status */, int events) {
+ assert(req->data);
+ auto socket = reinterpret_cast<Socket *>(req->data);
+ auto context = socket->context;
+ MBGL_VERIFY_THREAD(context->tid);
+
+ int flags = 0;
+
+ if (events & UV_READABLE) {
+ flags |= CURL_CSELECT_IN;
+ }
+ if (events & UV_WRITABLE) {
+ flags |= CURL_CSELECT_OUT;
+ }
+
+
+ int running_handles = 0;
+ curl_multi_socket_action(context->multi, socket->sockfd, flags, &running_handles);
+ context->checkMultiInfo();
+}
+
+int HTTPCURLContext::handleSocket(CURL * /* handle */, curl_socket_t s, int action, void *userp,
+ void *socketp) {
+ auto socket = reinterpret_cast<Socket *>(socketp);
+ assert(userp);
+ auto context = reinterpret_cast<HTTPCURLContext *>(userp);
+ MBGL_VERIFY_THREAD(context->tid);
+
+ if (!socket && action != CURL_POLL_REMOVE) {
+ socket = new Socket(context, s);
+ curl_multi_assign(context->multi, s, (void *)socket);
+ }
+
+ switch (action) {
+ case CURL_POLL_IN:
+ socket->start(UV_READABLE, perform);
+ break;
+ case CURL_POLL_OUT:
+ socket->start(UV_WRITABLE, perform);
+ break;
+ case CURL_POLL_REMOVE:
+ if (socket) {
+ socket->stop();
+ curl_multi_assign(context->multi, s, nullptr);
+ }
+ break;
+ default:
+ throw std::runtime_error("Unhandled CURL socket action");
+ }
+
+ return 0;
+}
+
+#if UV_VERSION_MAJOR == 0 && UV_VERSION_MINOR <= 10
+void HTTPCURLContext::onTimeout(uv_timer_t *req, int /* status */) {
+#else
+void HTTPCURLContext::onTimeout(uv_timer_t *req) {
+#endif
+ assert(req->data);
+ auto context = reinterpret_cast<HTTPCURLContext *>(req->data);
+ MBGL_VERIFY_THREAD(context->tid);
+ int running_handles;
+ CURLMcode error = curl_multi_socket_action(context->multi, CURL_SOCKET_TIMEOUT, 0, &running_handles);
+ if (error != CURLM_OK) {
+ throw std::runtime_error(std::string("CURL multi error: ") + curl_multi_strerror(error));
+ }
+ context->checkMultiInfo();
+}
+
+int HTTPCURLContext::startTimeout(CURLM * /* multi */, long timeout_ms, void *userp) {
+ assert(userp);
+ auto context = reinterpret_cast<HTTPCURLContext *>(userp);
+ MBGL_VERIFY_THREAD(context->tid);
+ if (timeout_ms < 0) {
+ // A timeout of 0 ms means that the timer will invoked in the next loop iteration.
+ timeout_ms = 0;
+ }
+ uv_timer_stop(context->timeout);
+ uv_timer_start(context->timeout, onTimeout, timeout_ms, 0);
+ return 0;
+}
+
+// -------------------------------------------------------------------------------------------------
+
+HTTPRequestImpl::HTTPRequestImpl(HTTPRequest *request_, uv_loop_t *loop, std::unique_ptr<Response> response_)
+ : context(HTTPCURLContext::Get(loop)),
+ request(request_),
+ existingResponse(std::move(response_)),
+ handle(context->getHandle()) {
+ assert(request);
+ context->addRequest(request);
+
+ // If there's already a response, set the correct etags/modified headers to make sure we are
+ // getting a 304 response if possible. This avoids redownloading unchanged data.
+ if (existingResponse) {
+ if (!existingResponse->etag.empty()) {
+ const std::string header = std::string("If-None-Match: ") + existingResponse->etag;
+ headers = curl_slist_append(headers, header.c_str());
+ } else if (existingResponse->modified) {
+ const std::string time =
+ std::string("If-Modified-Since: ") + util::rfc1123(existingResponse->modified);
+ headers = curl_slist_append(headers, time.c_str());
+ }
+ }
+
+ if (headers) {
+ curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers);
+ }
+
+ handleError(curl_easy_setopt(handle, CURLOPT_PRIVATE, this));
+ handleError(curl_easy_setopt(handle, CURLOPT_CAINFO, "ca-bundle.crt"));
+ handleError(curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1));
+ handleError(curl_easy_setopt(handle, CURLOPT_URL, request->resource.url.c_str()));
+ handleError(curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, writeCallback));
+ handleError(curl_easy_setopt(handle, CURLOPT_WRITEDATA, this));
+ handleError(curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, headerCallback));
+ handleError(curl_easy_setopt(handle, CURLOPT_HEADERDATA, this));
+ handleError(curl_easy_setopt(handle, CURLOPT_ACCEPT_ENCODING, "gzip, deflate"));
+ handleError(curl_easy_setopt(handle, CURLOPT_USERAGENT, "MapboxGL/1.0"));
+ handleError(curl_easy_setopt(handle, CURLOPT_SHARE, context->share));
+
+ start();
+}
+
+void HTTPRequestImpl::abandon() {
+ if (request) {
+ context->removeRequest(request);
+ request = nullptr;
+ }
+}
+
+void HTTPRequestImpl::start() {
+ // Count up the attempts.
+ attempts++;
+
+ // Start requesting the information.
+ handleError(curl_multi_add_handle(context->multi, handle));
+}
+
+HTTPRequestImpl::~HTTPRequestImpl() {
+ MBGL_VERIFY_THREAD(tid);
+
+ if (request) {
+ context->removeRequest(request);
+ request->ptr = nullptr;
+ }
+
+ handleError(curl_multi_remove_handle(context->multi, handle));
+ context->returnHandle(handle);
+ handle = nullptr;
+
+ if (timer) {
+ // Stop the backoff timer to avoid re-triggering this request.
+ uv_timer_stop(timer);
+ uv::close(timer);
+ timer = nullptr;
+ }
+
+ if (headers) {
+ curl_slist_free_all(headers);
+ headers = nullptr;
+ }
+}
+
+// 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 HTTPRequestImpl::writeCallback(void *const contents, const size_t size, const size_t nmemb, void *userp) {
+ assert(userp);
+ auto impl = reinterpret_cast<HTTPRequestImpl *>(userp);
+ MBGL_VERIFY_THREAD(impl->tid);
+
+ if (!impl->response) {
+ impl->response = util::make_unique<Response>();
+ }
+
+ impl->response->data.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 headerMatches(const char *const header, const char *const buffer, const size_t length) {
+ const size_t headerLength = strlen(header);
+ if (length < headerLength) {
+ return std::string::npos;
+ }
+ size_t i = 0;
+ while (i < length && i < headerLength && std::tolower(buffer[i]) == std::tolower(header[i])) {
+ i++;
+ }
+ return i == headerLength ? i : std::string::npos;
+}
+
+int64_t parseCacheControl(const char *value) {
+ if (value) {
+ unsigned long long seconds = 0;
+ // TODO: cache-control may contain other information as well:
+ // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9
+ if (std::sscanf(value, "max-age=%llu", &seconds) == 1) {
+ return std::chrono::duration_cast<std::chrono::seconds>(
+ std::chrono::system_clock::now().time_since_epoch()).count() +
+ seconds;
+ }
+ }
+
+ return 0;
+}
+
+size_t HTTPRequestImpl::headerCallback(char *const buffer, const size_t size, const size_t nmemb, void *userp) {
+ assert(userp);
+ auto baton = reinterpret_cast<HTTPRequestImpl *>(userp);
+ MBGL_VERIFY_THREAD(baton->tid);
+
+ if (!baton->response) {
+ baton->response = util::make_unique<Response>();
+ }
+
+ const size_t length = size * nmemb;
+ size_t begin = std::string::npos;
+ if ((begin = headerMatches("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
+ baton->response->modified = curl_getdate(value.c_str(), nullptr);
+ } else if ((begin = headerMatches("etag: ", buffer, length)) != std::string::npos) {
+ baton->response->etag = { buffer + begin, length - begin - 2 }; // remove \r\n
+ } else if ((begin = headerMatches("cache-control: ", buffer, length)) != std::string::npos) {
+ const std::string value { buffer + begin, length - begin - 2 }; // remove \r\n
+ baton->response->expires = parseCacheControl(value.c_str());
+ } else if ((begin = headerMatches("expires: ", buffer, length)) != std::string::npos) {
+ const std::string value { buffer + begin, length - begin - 2 }; // remove \r\n
+ baton->response->expires = curl_getdate(value.c_str(), nullptr);
+ }
+
+ return length;
+}
+
+
+void HTTPRequestImpl::retry(uint64_t timeout) {
+ handleError(curl_multi_remove_handle(context->multi, handle));
+
+ response.reset();
+
+ assert(!timer);
+ timer = new uv_timer_t;
+ timer->data = this;
+ uv_timer_init(context->loop, timer);
+ uv_timer_start(timer, restart, timeout, 0);
+}
+
+void HTTPRequestImpl::retryImmediately() {
+ // All batons get notified when the network status changed, but some of them
+ // might not actually wait for the network to become available again.
+ if (timer && strategy == PreemptImmediately) {
+ // Triggers the timer upon the next event loop iteration.
+ uv_timer_stop(timer);
+ uv_timer_start(timer, restart, 0, 0);
+ }
+}
+
+void HTTPRequestImpl::restart(uv_timer_t *timer, int) {
+ // Restart the request.
+ auto baton = reinterpret_cast<HTTPRequestImpl *>(timer->data);
+
+ // Get rid of the timer.
+ baton->timer = nullptr;
+ uv::close(timer);
+
+ baton->start();
+}
+
+void HTTPRequestImpl::finish(ResponseStatus status) {
+ if (status == ResponseStatus::TemporaryError && attempts < maxAttempts) {
+ strategy = ExponentialBackoff;
+ return retry((1 << (attempts - 1)) * 1000);
+ } else if (status == ResponseStatus::ConnectionError && attempts < maxAttempts) {
+ // By default, we will retry every 30 seconds (network change notification will
+ // preempt the timeout).
+ strategy = PreemptImmediately;
+ return retry(30000);
+ }
+
+ // Actually return the response.
+ if (status == ResponseStatus::NotModified) {
+ request->notify(std::move(response), FileCache::Hint::Refresh);
+ } else {
+ request->notify(std::move(response), FileCache::Hint::Full);
+ }
+
+ delete request;
+ delete this;
+}
+
+void HTTPRequestImpl::handleResult(CURLcode code) {
+ MBGL_VERIFY_THREAD(tid);
+
+ if (!request) {
+ // In this case, it doesn't make sense to even process the response even further since
+ // the request was canceled anyway.
+ delete this;
+ return;
+ }
+
+ // Make sure a response object exists in case we haven't got any headers
+ // or content.
+ if (!response) {
+ response = util::make_unique<Response>();
+ }
+
+ // Add human-readable error code
+ if (code != CURLE_OK) {
+ response->message = curl_easy_strerror(code);
+
+ switch (code) {
+ case CURLE_COULDNT_RESOLVE_PROXY:
+ case CURLE_COULDNT_RESOLVE_HOST:
+ case CURLE_COULDNT_CONNECT:
+ response->status = Response::Error;
+ return finish(ResponseStatus::ConnectionError);
+
+ case CURLE_OPERATION_TIMEDOUT:
+ response->status = Response::Error;
+ return finish(ResponseStatus::TemporaryError);
+
+ default:
+ response->status = Response::Error;
+ return finish(ResponseStatus::PermanentError);
+ }
+ } else {
+ long responseCode = 0;
+ curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &responseCode);
+
+ if (responseCode == 304) {
+ if (existingResponse) {
+ // We're going to reuse the old response object, but need to copy over the new
+ // expires value (if possible).
+ std::swap(response, existingResponse);
+ if (existingResponse->expires) {
+ response->expires = existingResponse->expires;
+ }
+ return finish(ResponseStatus::NotModified);
+ } else {
+ // This is an unsolicited 304 response and should only happen on malfunctioning
+ // HTTP servers. It likely doesn't include any data, but we don't have much options.
+ response->status = Response::Successful;
+ return finish(ResponseStatus::Successful);
+ }
+ } else if (responseCode == 200) {
+ response->status = Response::Successful;
+ return finish(ResponseStatus::Successful);
+ } else if (responseCode >= 500 && responseCode < 600) {
+ // Server errors may be temporary, so back off exponentially.
+ response->status = Response::Error;
+ response->message = "HTTP status code " + std::to_string(responseCode);
+ return finish(ResponseStatus::TemporaryError);
+ } else {
+ // We don't know how to handle any other errors, so declare them as permanently failing.
+ response->status = Response::Error;
+ response->message = "HTTP status code " + std::to_string(responseCode);
+ return finish(ResponseStatus::PermanentError);
+ }
+ }
+
+ throw std::runtime_error("Response hasn't been handled");
+}
+
+// -------------------------------------------------------------------------------------------------
+
+HTTPRequest::HTTPRequest(DefaultFileSource *source, const Resource &resource)
+ : SharedRequestBase(source, resource) {
+}
+
+HTTPRequest::~HTTPRequest() {
+ MBGL_VERIFY_THREAD(tid);
+
+ if (ptr) {
+ reinterpret_cast<HTTPRequestImpl *>(ptr)->abandon();
+ }
+}
+
+void HTTPRequest::start(uv_loop_t *loop, std::unique_ptr<Response> response) {
+ MBGL_VERIFY_THREAD(tid);
+
+ assert(!ptr);
+ ptr = new HTTPRequestImpl(this, loop, std::move(response));
+}
+
+void HTTPRequest::retryImmediately() {
+ MBGL_VERIFY_THREAD(tid);
+
+ if (ptr) {
+ reinterpret_cast<HTTPRequestImpl *>(ptr)->retryImmediately();
+ }
+}
+
+void HTTPRequest::cancel() {
+ MBGL_VERIFY_THREAD(tid);
+
+ if (ptr) {
+ delete reinterpret_cast<HTTPRequestImpl *>(ptr);
+ ptr = nullptr;
+ }
+
+ delete this;
+}
+
+}