diff options
author | John Firebaugh <john.firebaugh@gmail.com> | 2016-02-15 17:35:13 -0800 |
---|---|---|
committer | John Firebaugh <john.firebaugh@gmail.com> | 2016-04-14 14:18:19 -0700 |
commit | 3af3e72bb3cb3f05b33be304d59e66cc244ef4d9 (patch) | |
tree | ef1d237c3083694307081c219c9da94ae43fe540 /platform | |
parent | 6d88a23a60bc0a6dc9945bf09a659d15dd827192 (diff) | |
download | qtlocation-mapboxgl-3af3e72bb3cb3f05b33be304d59e66cc244ef4d9.tar.gz |
[all] Replace HTTPContextBase/HTTPRequestBase with FileSource
Diffstat (limited to 'platform')
-rw-r--r-- | platform/android/platform.gyp | 2 | ||||
-rw-r--r-- | platform/android/src/http_file_source.cpp (renamed from platform/android/src/http_request_android.cpp) | 100 | ||||
-rw-r--r-- | platform/darwin/src/http_file_source.mm | 228 | ||||
-rw-r--r-- | platform/darwin/src/http_request_nsurl.mm | 243 | ||||
-rw-r--r-- | platform/default/http_file_source.cpp (renamed from platform/default/http_request_curl.cpp) | 255 | ||||
-rw-r--r-- | platform/default/online_file_source.cpp | 15 | ||||
-rw-r--r-- | platform/ios/platform.gyp | 2 | ||||
-rw-r--r-- | platform/linux/platform.gyp | 2 | ||||
-rw-r--r-- | platform/osx/platform.gyp | 2 |
9 files changed, 340 insertions, 509 deletions
diff --git a/platform/android/platform.gyp b/platform/android/platform.gyp index 4954c7031c..599aa586ce 100644 --- a/platform/android/platform.gyp +++ b/platform/android/platform.gyp @@ -27,7 +27,7 @@ 'src/jni.cpp', 'src/attach_env.cpp', 'src/log_android.cpp', - 'src/http_request_android.cpp', + 'src/http_file_source.cpp', 'src/asset_file_source.cpp', '../default/thread.cpp', '../default/string_stdlib.cpp', diff --git a/platform/android/src/http_request_android.cpp b/platform/android/src/http_file_source.cpp index 1e0039cdf8..ceb241af4e 100644 --- a/platform/android/src/http_request_android.cpp +++ b/platform/android/src/http_file_source.cpp @@ -1,5 +1,4 @@ -#include <mbgl/storage/http_context_base.hpp> -#include <mbgl/storage/http_request_base.hpp> +#include <mbgl/storage/http_file_source.hpp> #include <mbgl/storage/resource.hpp> #include <mbgl/storage/response.hpp> #include <mbgl/platform/log.hpp> @@ -7,26 +6,24 @@ #include <mbgl/util/async_task.hpp> #include <mbgl/util/util.hpp> #include <mbgl/util/string.hpp> +#include <mbgl/util/http_header.hpp> #include <jni/jni.hpp> #include "attach_env.hpp" namespace mbgl { -namespace android { -class HTTPContext : public HTTPContextBase { +class HTTPFileSource::Impl { public: - HTTPRequestBase* createRequest(const Resource&, HTTPRequestBase::Callback) final; - UniqueEnv env { android::AttachEnv() }; + android::UniqueEnv env { android::AttachEnv() }; }; -class HTTPRequest : public HTTPRequestBase { +class HTTPRequest : public AsyncRequest { public: static constexpr auto Name() { return "com/mapbox/mapboxsdk/http/HTTPRequest"; }; - HTTPRequest(jni::JNIEnv&, const Resource&, Callback); - - void cancel() final; + HTTPRequest(jni::JNIEnv&, const Resource&, FileSource::Callback); + ~HTTPRequest(); void onFailure(jni::JNIEnv&, int type, jni::String message); void onResponse(jni::JNIEnv&, int code, @@ -38,12 +35,16 @@ public: jni::UniqueObject<HTTPRequest> javaRequest; private: - void finish(); + Resource resource; + FileSource::Callback callback; + Response response; - std::unique_ptr<Response> response; - const std::shared_ptr<const Response> existingResponse; - - util::AsyncTask async; + util::AsyncTask async { [this] { + // Calling `callback` may result in deleting `this`. Copy data to temporaries first. + auto callback_ = callback; + auto response_ = response; + callback_(response_); + } }; static const int connectionError = 0; static const int temporaryError = 1; @@ -52,6 +53,8 @@ private: jni::Class<HTTPRequest> HTTPRequest::javaClass; +namespace android { + void RegisterNativeHTTPRequest(jni::JNIEnv& env) { HTTPRequest::javaClass = *jni::Class<HTTPRequest>::Find(env).NewGlobalRef(env).release(); @@ -62,15 +65,11 @@ void RegisterNativeHTTPRequest(jni::JNIEnv& env) { METHOD(&HTTPRequest::onResponse, "nativeOnResponse")); } -// ------------------------------------------------------------------------------------------------- - -HTTPRequestBase* HTTPContext::createRequest(const Resource& resource, HTTPRequestBase::Callback callback) { - return new HTTPRequest(*env, resource, callback); -} +} // namespace android -HTTPRequest::HTTPRequest(jni::JNIEnv& env, const Resource& resource_, Callback callback_) - : HTTPRequestBase(resource_, callback_), - async([this] { finish(); }) { +HTTPRequest::HTTPRequest(jni::JNIEnv& env, const Resource& resource_, FileSource::Callback callback_) + : resource(resource_), + callback(callback_) { std::string etagStr; std::string modifiedStr; @@ -93,63 +92,53 @@ HTTPRequest::HTTPRequest(jni::JNIEnv& env, const Resource& resource_, Callback c jni::Make<jni::String>(env, modifiedStr)).NewGlobalRef(env); } -void HTTPRequest::cancel() { - UniqueEnv env = android::AttachEnv(); +HTTPRequest::~HTTPRequest() { + android::UniqueEnv env = android::AttachEnv(); static auto cancel = javaClass.GetMethod<void ()>(*env, "cancel"); javaRequest->Call(*env, cancel); - - delete this; -} - -void HTTPRequest::finish() { - assert(response); - notify(*response); - - delete this; } void HTTPRequest::onResponse(jni::JNIEnv& env, int code, jni::String etag, jni::String modified, jni::String cacheControl, jni::String expires, jni::Array<jni::jbyte> body) { - response = std::make_unique<Response>(); using Error = Response::Error; if (etag) { - response->etag = jni::Make<std::string>(env, etag); + response.etag = jni::Make<std::string>(env, etag); } if (modified) { - response->modified = util::parseTimePoint(jni::Make<std::string>(env, modified).c_str()); + response.modified = util::parseTimePoint(jni::Make<std::string>(env, modified).c_str()); } if (cacheControl) { - response->expires = parseCacheControl(jni::Make<std::string>(env, cacheControl).c_str()); + response.expires = http::CacheControl::parse(jni::Make<std::string>(env, cacheControl).c_str()).toTimePoint(); } if (expires) { - response->expires = util::parseTimePoint(jni::Make<std::string>(env, expires).c_str()); + response.expires = util::parseTimePoint(jni::Make<std::string>(env, expires).c_str()); } if (code == 200) { if (body) { auto data = std::make_shared<std::string>(body.Length(env), char()); jni::GetArrayRegion(env, *body, 0, data->size(), reinterpret_cast<jbyte*>(&(*data)[0])); - response->data = data; + response.data = data; } else { - response->data = std::make_shared<std::string>(); + response.data = std::make_shared<std::string>(); } } else if (code == 204 || (code == 404 && resource.kind == Resource::Kind::Tile)) { - response->noContent = true; + response.noContent = true; } else if (code == 304) { - response->notModified = true; + response.notModified = true; } else if (code == 404) { - response->error = std::make_unique<Error>(Error::Reason::NotFound, "HTTP status code 404"); + response.error = std::make_unique<Error>(Error::Reason::NotFound, "HTTP status code 404"); } else if (code >= 500 && code < 600) { - response->error = std::make_unique<Error>(Error::Reason::Server, std::string{ "HTTP status code " } + std::to_string(code)); + response.error = std::make_unique<Error>(Error::Reason::Server, std::string{ "HTTP status code " } + std::to_string(code)); } else { - response->error = std::make_unique<Error>(Error::Reason::Other, std::string{ "HTTP status code " } + std::to_string(code)); + response.error = std::make_unique<Error>(Error::Reason::Other, std::string{ "HTTP status code " } + std::to_string(code)); } async.send(); @@ -158,30 +147,33 @@ void HTTPRequest::onResponse(jni::JNIEnv& env, int code, void HTTPRequest::onFailure(jni::JNIEnv& env, int type, jni::String message) { std::string messageStr = jni::Make<std::string>(env, message); - response = std::make_unique<Response>(); using Error = Response::Error; switch (type) { case connectionError: - response->error = std::make_unique<Error>(Error::Reason::Connection, messageStr); + response.error = std::make_unique<Error>(Error::Reason::Connection, messageStr); break; case temporaryError: - response->error = std::make_unique<Error>(Error::Reason::Server, messageStr); + response.error = std::make_unique<Error>(Error::Reason::Server, messageStr); break; default: - response->error = std::make_unique<Error>(Error::Reason::Other, messageStr); + response.error = std::make_unique<Error>(Error::Reason::Other, messageStr); } async.send(); } -} // namespace android +HTTPFileSource::HTTPFileSource() + : impl(std::make_unique<Impl>()) { +} + +HTTPFileSource::~HTTPFileSource() = default; -std::unique_ptr<HTTPContextBase> HTTPContextBase::createContext() { - return std::make_unique<android::HTTPContext>(); +std::unique_ptr<AsyncRequest> HTTPFileSource::request(const Resource& resource, Callback callback) { + return std::make_unique<HTTPRequest>(*impl->env, resource, callback); } -uint32_t HTTPContextBase::maximumConcurrentRequests() { +uint32_t HTTPFileSource::maximumConcurrentRequests() { return 20; } diff --git a/platform/darwin/src/http_file_source.mm b/platform/darwin/src/http_file_source.mm new file mode 100644 index 0000000000..c7fdd2aae9 --- /dev/null +++ b/platform/darwin/src/http_file_source.mm @@ -0,0 +1,228 @@ +#include <mbgl/storage/http_file_source.hpp> +#include <mbgl/storage/resource.hpp> +#include <mbgl/storage/response.hpp> + +#include <mbgl/util/http_header.hpp> +#include <mbgl/util/async_task.hpp> + +#import <Foundation/Foundation.h> + +#include <mutex> + +namespace mbgl { + +// Data that is shared between the requesting thread and the thread running the completion handler. +class HTTPRequestShared { +public: + HTTPRequestShared(Response& response_, util::AsyncTask& async_) + : response(response_), + async(async_) { + } + + void notify(const Response& response_) { + std::lock_guard<std::mutex> lock(mutex); + if (!cancelled) { + response = response_; + async.send(); + } + } + + void cancel() { + std::lock_guard<std::mutex> lock(mutex); + cancelled = true; + } + +private: + std::mutex mutex; + bool cancelled = false; + + Response& response; + util::AsyncTask& async; +}; + +class HTTPRequest : public AsyncRequest { +public: + HTTPRequest(FileSource::Callback callback_) + : shared(std::make_shared<HTTPRequestShared>(response, async)), + callback(callback_) { + } + + ~HTTPRequest() override { + shared->cancel(); + if (task) { + [task cancel]; + } + } + + std::shared_ptr<HTTPRequestShared> shared; + NSURLSessionDataTask* task = nil; + +private: + FileSource::Callback callback; + Response response; + + util::AsyncTask async { [this] { + // Calling `callback` may result in deleting `this`. Copy data to temporaries first. + auto callback_ = callback; + auto response_ = response; + callback_(response_); + } }; +}; + +class HTTPFileSource::Impl { +public: + Impl() { + @autoreleasepool { + 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"; + + accountType = [[NSUserDefaults standardUserDefaults] integerForKey:@"MGLMapboxAccountType"]; + } + } + + NSURLSession* session = nil; + NSString* userAgent = nil; + NSInteger accountType = 0; +}; + +HTTPFileSource::HTTPFileSource() + : impl(std::make_unique<Impl>()) { +} + +HTTPFileSource::~HTTPFileSource() = default; + +uint32_t HTTPFileSource::maximumConcurrentRequests() { + return 20; +} + +std::unique_ptr<AsyncRequest> HTTPFileSource::request(const Resource& resource, Callback callback) { + auto request = std::make_unique<HTTPRequest>(callback); + auto shared = request->shared; // Explicit copy so that it also gets copied into the completion handler block below. + + @autoreleasepool { + NSURL* url = [NSURL URLWithString:@(resource.url.c_str())]; + if (impl->accountType == 0 && + ([url.host isEqualToString:@"mapbox.com"] || [url.host hasSuffix:@".mapbox.com"])) { + NSString* absoluteString = [url.absoluteString + stringByAppendingFormat:(url.query ? @"&%@" : @"?%@"), @"events=true"]; + url = [NSURL URLWithString:absoluteString]; + } + + NSMutableURLRequest* req = [NSMutableURLRequest requestWithURL:url]; + if (resource.priorEtag) { + [req addValue:@(resource.priorEtag->c_str()) + forHTTPHeaderField:@"If-None-Match"]; + } else if (resource.priorModified) { + [req addValue:@(util::rfc1123(*resource.priorModified).c_str()) + forHTTPHeaderField:@"If-Modified-Since"]; + } + + [req addValue:impl->userAgent forHTTPHeaderField:@"User-Agent"]; + + request->task = [impl->session + dataTaskWithRequest:req + completionHandler:^(NSData* data, NSURLResponse* res, NSError* error) { + if (error && [error code] == NSURLErrorCancelled) { + return; + } + + Response response; + using Error = Response::Error; + + if (error) { + if (data) { + response.data = + std::make_shared<std::string>((const char*)[data bytes], [data length]); + } + + switch ([error code]) { + case NSURLErrorBadServerResponse: // 5xx errors + response.error = std::make_unique<Error>( + Error::Reason::Server, [[error localizedDescription] UTF8String]); + break; + + case NSURLErrorNetworkConnectionLost: + case NSURLErrorCannotFindHost: + case NSURLErrorCannotConnectToHost: + case NSURLErrorDNSLookupFailed: + case NSURLErrorNotConnectedToInternet: + case NSURLErrorInternationalRoamingOff: + case NSURLErrorCallIsActive: + case NSURLErrorDataNotAllowed: + case NSURLErrorTimedOut: + response.error = std::make_unique<Error>( + Error::Reason::Connection, [[error localizedDescription] UTF8String]); + break; + + default: + response.error = std::make_unique<Error>( + Error::Reason::Other, [[error localizedDescription] UTF8String]); + break; + } + } else if ([res isKindOfClass:[NSHTTPURLResponse class]]) { + const long responseCode = [(NSHTTPURLResponse *)res statusCode]; + + NSDictionary *headers = [(NSHTTPURLResponse *)res allHeaderFields]; + NSString *cache_control = [headers objectForKey:@"Cache-Control"]; + if (cache_control) { + response.expires = http::CacheControl::parse([cache_control UTF8String]).toTimePoint(); + } + + NSString *expires = [headers objectForKey:@"Expires"]; + if (expires) { + response.expires = util::parseTimePoint([expires UTF8String]); + } + + NSString *last_modified = [headers objectForKey:@"Last-Modified"]; + if (last_modified) { + response.modified = util::parseTimePoint([last_modified UTF8String]); + } + + NSString *etag = [headers objectForKey:@"ETag"]; + if (etag) { + response.etag = std::string([etag UTF8String]); + } + + if (responseCode == 200) { + response.data = std::make_shared<std::string>((const char *)[data bytes], [data length]); + } else if (responseCode == 204 || (responseCode == 404 && resource.kind == Resource::Kind::Tile)) { + response.noContent = true; + } else if (responseCode == 304) { + response.notModified = true; + } else if (responseCode == 404) { + response.error = + std::make_unique<Error>(Error::Reason::NotFound, "HTTP status code 404"); + } else if (responseCode >= 500 && responseCode < 600) { + response.error = + std::make_unique<Error>(Error::Reason::Server, std::string{ "HTTP status code " } + + std::to_string(responseCode)); + } else { + response.error = + std::make_unique<Error>(Error::Reason::Other, std::string{ "HTTP status code " } + + std::to_string(responseCode)); + } + } else { + // This should never happen. + response.error = std::make_unique<Error>(Error::Reason::Other, + "Response class is not NSHTTPURLResponse"); + } + + shared->notify(response); + }]; + + [request->task resume]; + } + + return std::move(request); +} + +} diff --git a/platform/darwin/src/http_request_nsurl.mm b/platform/darwin/src/http_request_nsurl.mm deleted file mode 100644 index edd8341709..0000000000 --- a/platform/darwin/src/http_request_nsurl.mm +++ /dev/null @@ -1,243 +0,0 @@ -#include <mbgl/storage/http_context_base.hpp> -#include <mbgl/storage/http_request_base.hpp> -#include <mbgl/storage/resource.hpp> -#include <mbgl/storage/response.hpp> - -#include <mbgl/util/async_task.hpp> -#include <mbgl/util/run_loop.hpp> - -#import <Foundation/Foundation.h> - -#include <map> -#include <cassert> -#include <mutex> - -namespace mbgl { - -class HTTPNSURLContext; - -class HTTPNSURLRequest : public HTTPRequestBase { -public: - HTTPNSURLRequest(HTTPNSURLContext*, Resource, Callback); - - void cancel() final; - -private: - static std::unique_ptr<Response> handleResult(NSData *data, NSURLResponse *res, NSError *error, Resource); - void handleResponse(); - - HTTPNSURLContext *context = nullptr; - std::shared_ptr<std::pair<bool, std::mutex>> cancelled; - NSURLSessionDataTask *task = nullptr; - std::unique_ptr<Response> response; - util::AsyncTask async; -}; - -// ------------------------------------------------------------------------------------------------- - -class HTTPNSURLContext : public HTTPContextBase { -public: - HTTPNSURLContext(); - - HTTPRequestBase* createRequest(const Resource&, HTTPRequestBase::Callback) final; - - NSURLSession *session = nil; - NSString *userAgent = nil; - NSInteger accountType = 0; -}; - -HTTPNSURLContext::HTTPNSURLContext() { - @autoreleasepool { - 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"; - - accountType = [[NSUserDefaults standardUserDefaults] integerForKey:@"MGLMapboxAccountType"]; - } -} - -HTTPRequestBase* HTTPNSURLContext::createRequest(const Resource& resource, HTTPRequestBase::Callback callback) { - return new HTTPNSURLRequest(this, resource, callback); -} - -// ------------------------------------------------------------------------------------------------- - -HTTPNSURLRequest::HTTPNSURLRequest(HTTPNSURLContext* context_, - Resource resource_, - Callback callback_) - : HTTPRequestBase(resource_, callback_), - context(context_), - async([this] { handleResponse(); }) { - - // Ensure that a stack-allocated std::shared_ptr gets copied into the Objective-C - // block used as the completion handler below. Objective-C will implicitly copy captured - // stack variables. For member variable access it will implicitly copy the this pointer. - // That wouldn't work here because we need the block to have its own shared_ptr. - auto cancelled_ = cancelled = std::make_shared<std::pair<bool, std::mutex>>(); - cancelled->first = false; - - @autoreleasepool { - NSURL* url = [NSURL URLWithString:@(resource.url.c_str())]; - if (context->accountType == 0 && - ([url.host isEqualToString:@"mapbox.com"] || [url.host hasSuffix:@".mapbox.com"])) { - NSString* absoluteString = [url.absoluteString - stringByAppendingFormat:(url.query ? @"&%@" : @"?%@"), @"events=true"]; - url = [NSURL URLWithString:absoluteString]; - } - - NSMutableURLRequest* req = [NSMutableURLRequest requestWithURL:url]; - if (resource.priorEtag) { - [req addValue:@(resource.priorEtag->c_str()) - forHTTPHeaderField:@"If-None-Match"]; - } else if (resource.priorModified) { - [req addValue:@(util::rfc1123(*resource.priorModified).c_str()) - forHTTPHeaderField:@"If-Modified-Since"]; - } - - [req addValue:context->userAgent forHTTPHeaderField:@"User-Agent"]; - - task = [context->session - dataTaskWithRequest:req - completionHandler:^(NSData* data, NSURLResponse* res, NSError* error) { - std::unique_ptr<Response> response_ = HTTPNSURLRequest::handleResult(data, res, error, resource_); - std::lock_guard<std::mutex> lock(cancelled_->second); - if (!cancelled_->first) { - response = std::move(response_); - async.send(); - } - }]; - [task resume]; - } -} - -void HTTPNSURLRequest::handleResponse() { - assert(response); - notify(*response); - - delete this; -} - -void HTTPNSURLRequest::cancel() { - [task cancel]; - task = nil; - - { - std::lock_guard<std::mutex> lock(cancelled->second); - cancelled->first = true; - } - - // The lock is in place to enforce that `async` is not accessed if the request has been - // cancelled. Therefore it's not necessary to hold the lock beyond setting cancelled to - // true, and in fact it's unsafe to so: if this is the last remaining shared reference, - // `delete this` will destroy the mutex. If the lock was held, it would then be orphaned. - - delete this; -} - -std::unique_ptr<Response> HTTPNSURLRequest::handleResult(NSData *data, NSURLResponse *res, NSError *error, Resource resource) { - std::unique_ptr<Response> response = std::make_unique<Response>(); - using Error = Response::Error; - - if (error) { - if ([error code] == NSURLErrorCancelled) { - response.reset(); - - } else { - if (data) { - response->data = - std::make_shared<std::string>((const char*)[data bytes], [data length]); - } - - switch ([error code]) { - case NSURLErrorBadServerResponse: // 5xx errors - response->error = std::make_unique<Error>( - Error::Reason::Server, [[error localizedDescription] UTF8String]); - break; - - case NSURLErrorNetworkConnectionLost: - case NSURLErrorCannotFindHost: - case NSURLErrorCannotConnectToHost: - case NSURLErrorDNSLookupFailed: - case NSURLErrorNotConnectedToInternet: - case NSURLErrorInternationalRoamingOff: - case NSURLErrorCallIsActive: - case NSURLErrorDataNotAllowed: - case NSURLErrorTimedOut: - response->error = std::make_unique<Error>( - Error::Reason::Connection, [[error localizedDescription] UTF8String]); - break; - - default: - response->error = std::make_unique<Error>( - Error::Reason::Other, [[error localizedDescription] UTF8String]); - break; - } - } - } else if ([res isKindOfClass:[NSHTTPURLResponse class]]) { - const long responseCode = [(NSHTTPURLResponse *)res statusCode]; - - 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 = util::parseTimePoint([expires UTF8String]); - } - - NSString *last_modified = [headers objectForKey:@"Last-Modified"]; - if (last_modified) { - response->modified = util::parseTimePoint([last_modified UTF8String]); - } - - NSString *etag = [headers objectForKey:@"ETag"]; - if (etag) { - response->etag = std::string([etag UTF8String]); - } - - if (responseCode == 200) { - response->data = std::make_shared<std::string>((const char *)[data bytes], [data length]); - } else if (responseCode == 204 || (responseCode == 404 && resource.kind == Resource::Kind::Tile)) { - response->noContent = true; - } else if (responseCode == 304) { - response->notModified = true; - } else if (responseCode == 404) { - response->error = - std::make_unique<Error>(Error::Reason::NotFound, "HTTP status code 404"); - } else if (responseCode >= 500 && responseCode < 600) { - response->error = - std::make_unique<Error>(Error::Reason::Server, std::string{ "HTTP status code " } + - std::to_string(responseCode)); - } else { - response->error = - std::make_unique<Error>(Error::Reason::Other, std::string{ "HTTP status code " } + - std::to_string(responseCode)); - } - } else { - // This should never happen. - response->error = std::make_unique<Error>(Error::Reason::Other, - "Response class is not NSHTTPURLResponse"); - } - - return response; -} - -std::unique_ptr<HTTPContextBase> HTTPContextBase::createContext() { - return std::make_unique<HTTPNSURLContext>(); -} - -uint32_t HTTPContextBase::maximumConcurrentRequests() { - return 20; -} - -} diff --git a/platform/default/http_request_curl.cpp b/platform/default/http_file_source.cpp index fc57e4c137..3250a77c80 100644 --- a/platform/default/http_request_curl.cpp +++ b/platform/default/http_file_source.cpp @@ -1,5 +1,4 @@ -#include <mbgl/storage/http_context_base.hpp> -#include <mbgl/storage/http_request_base.hpp> +#include <mbgl/storage/http_file_source.hpp> #include <mbgl/storage/resource.hpp> #include <mbgl/storage/response.hpp> #include <mbgl/platform/log.hpp> @@ -9,28 +8,23 @@ #include <mbgl/util/string.hpp> #include <mbgl/util/timer.hpp> #include <mbgl/util/chrono.hpp> +#include <mbgl/util/http_header.hpp> #include <curl/curl.h> -#ifdef __ANDROID__ -#include <mbgl/android/jni.hpp> -#include <zip.h> -#include <openssl/ssl.h> -#endif - #include <queue> #include <map> #include <cassert> #include <cstring> #include <cstdio> -void handleError(CURLMcode code) { +static 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) { +static void handleError(CURLcode code) { if (code != CURLE_OK) { throw std::runtime_error(std::string("CURL easy error: ") + curl_easy_strerror(code)); } @@ -38,20 +32,14 @@ void handleError(CURLcode code) { namespace mbgl { -class HTTPCURLRequest; - -class HTTPCURLContext : public HTTPContextBase { - MBGL_STORE_THREAD(tid) - +class HTTPFileSource::Impl { public: - HTTPCURLContext(); - ~HTTPCURLContext(); - - HTTPRequestBase* createRequest(const Resource&, HTTPRequestBase::Callback) final; + Impl(); + ~Impl(); static int handleSocket(CURL *handle, curl_socket_t s, int action, void *userp, void *socketp); static int startTimeout(CURLM *multi, long timeout_ms, void *userp); - static void onTimeout(HTTPCURLContext *context); + static void onTimeout(HTTPFileSource::Impl *context); void perform(curl_socket_t s, util::RunLoop::Event event); CURL *getHandle(); @@ -73,14 +61,10 @@ public: std::queue<CURL *> handles; }; -class HTTPCURLRequest : public HTTPRequestBase { - MBGL_STORE_THREAD(tid) - +class HTTPRequest : public AsyncRequest { public: - HTTPCURLRequest(HTTPCURLContext*, const Resource&, Callback); - ~HTTPCURLRequest(); - - void cancel() final; + HTTPRequest(HTTPFileSource::Impl*, const Resource&, FileSource::Callback); + ~HTTPRequest(); void handleResult(CURLcode code); @@ -88,24 +72,21 @@ 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); - HTTPCURLContext *context = nullptr; + HTTPFileSource::Impl* context = nullptr; + Resource resource; + FileSource::Callback callback; // Will store the current response. std::shared_ptr<std::string> data; std::unique_ptr<Response> response; - // In case of revalidation requests, this will store the old response. - const std::shared_ptr<const Response> existingResponse; - CURL *handle = nullptr; curl_slist *headers = nullptr; - char error[CURL_ERROR_SIZE]; + char error[CURL_ERROR_SIZE] = { 0 }; }; -// ------------------------------------------------------------------------------------------------- - -HTTPCURLContext::HTTPCURLContext() { +HTTPFileSource::Impl::Impl() { if (curl_global_init(CURL_GLOBAL_ALL)) { throw std::runtime_error("Could not init cURL"); } @@ -119,7 +100,7 @@ HTTPCURLContext::HTTPCURLContext() { handleError(curl_multi_setopt(multi, CURLMOPT_TIMERDATA, this)); } -HTTPCURLContext::~HTTPCURLContext() { +HTTPFileSource::Impl::~Impl() { while (!handles.empty()) { curl_easy_cleanup(handles.front()); handles.pop(); @@ -134,11 +115,7 @@ HTTPCURLContext::~HTTPCURLContext() { timeout.stop(); } -HTTPRequestBase* HTTPCURLContext::createRequest(const Resource& resource, HTTPRequestBase::Callback callback) { - return new HTTPCURLRequest(this, resource, callback); -} - -CURL *HTTPCURLContext::getHandle() { +CURL *HTTPFileSource::Impl::getHandle() { if (!handles.empty()) { auto handle = handles.front(); handles.pop(); @@ -148,20 +125,19 @@ CURL *HTTPCURLContext::getHandle() { } } -void HTTPCURLContext::returnHandle(CURL *handle) { +void HTTPFileSource::Impl::returnHandle(CURL *handle) { curl_easy_reset(handle); handles.push(handle); } -void HTTPCURLContext::checkMultiInfo() { - MBGL_VERIFY_THREAD(tid); +void HTTPFileSource::Impl::checkMultiInfo() { CURLMsg *message = nullptr; int pending = 0; while ((message = curl_multi_info_read(multi, &pending))) { switch (message->msg) { case CURLMSG_DONE: { - HTTPCURLRequest *baton = nullptr; + HTTPRequest *baton = nullptr; curl_easy_getinfo(message->easy_handle, CURLINFO_PRIVATE, (char *)&baton); assert(baton); baton->handleResult(message->data.result); @@ -174,9 +150,7 @@ void HTTPCURLContext::checkMultiInfo() { } } -void HTTPCURLContext::perform(curl_socket_t s, util::RunLoop::Event events) { - MBGL_VERIFY_THREAD(tid); - +void HTTPFileSource::Impl::perform(curl_socket_t s, util::RunLoop::Event events) { int flags = 0; if (events == util::RunLoop::Event::Read) { @@ -192,23 +166,22 @@ void HTTPCURLContext::perform(curl_socket_t s, util::RunLoop::Event events) { checkMultiInfo(); } -int HTTPCURLContext::handleSocket(CURL * /* handle */, curl_socket_t s, int action, void *userp, +int HTTPFileSource::Impl::handleSocket(CURL * /* handle */, curl_socket_t s, int action, void *userp, void * /* socketp */) { assert(userp); - auto context = reinterpret_cast<HTTPCURLContext *>(userp); - MBGL_VERIFY_THREAD(context->tid); + auto context = reinterpret_cast<Impl *>(userp); switch (action) { case CURL_POLL_IN: { using namespace std::placeholders; util::RunLoop::Get()->addWatch(s, util::RunLoop::Event::Read, - std::bind(&HTTPCURLContext::perform, context, _1, _2)); + std::bind(&Impl::perform, context, _1, _2)); break; } case CURL_POLL_OUT: { using namespace std::placeholders; util::RunLoop::Get()->addWatch(s, util::RunLoop::Event::Write, - std::bind(&HTTPCURLContext::perform, context, _1, _2)); + std::bind(&Impl::perform, context, _1, _2)); break; } case CURL_POLL_REMOVE: @@ -221,8 +194,7 @@ int HTTPCURLContext::handleSocket(CURL * /* handle */, curl_socket_t s, int acti return 0; } -void HTTPCURLContext::onTimeout(HTTPCURLContext *context) { - MBGL_VERIFY_THREAD(context->tid); +void HTTPFileSource::Impl::onTimeout(Impl *context) { int running_handles; CURLMcode error = curl_multi_socket_action(context->multi, CURL_SOCKET_TIMEOUT, 0, &running_handles); if (error != CURLM_OK) { @@ -231,127 +203,27 @@ void HTTPCURLContext::onTimeout(HTTPCURLContext *context) { context->checkMultiInfo(); } -int HTTPCURLContext::startTimeout(CURLM * /* multi */, long timeout_ms, void *userp) { +int HTTPFileSource::Impl::startTimeout(CURLM * /* multi */, long timeout_ms, void *userp) { assert(userp); - auto context = reinterpret_cast<HTTPCURLContext *>(userp); - MBGL_VERIFY_THREAD(context->tid); + auto context = reinterpret_cast<Impl *>(userp); + if (timeout_ms < 0) { // A timeout of 0 ms means that the timer will invoked in the next loop iteration. timeout_ms = 0; } + context->timeout.stop(); context->timeout.start(mbgl::Milliseconds(timeout_ms), Duration::zero(), - std::bind(&HTTPCURLContext::onTimeout, context)); + std::bind(&Impl::onTimeout, context)); return 0; } -// ------------------------------------------------------------------------------------------------- - -#ifdef __ANDROID__ - -// This function is called to load the CA bundle -// from http://curl.haxx.se/libcurl/c/cacertinmem.html¯ -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 auto pem = std::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 - -HTTPCURLRequest::HTTPCURLRequest(HTTPCURLContext* context_, const Resource& resource_, Callback callback_) - : HTTPRequestBase(resource_, callback_), - context(context_), +HTTPRequest::HTTPRequest(HTTPFileSource::Impl* context_, const Resource& resource_, FileSource::Callback callback_) + : context(context_), + resource(resource_), + callback(callback_), handle(context->getHandle()) { - // Zero out the error buffer. - memset(error, 0, sizeof(error)); // 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. @@ -370,12 +242,7 @@ HTTPCURLRequest::HTTPCURLRequest(HTTPCURLContext* context_, const Resource& reso handleError(curl_easy_setopt(handle, CURLOPT_PRIVATE, this)); handleError(curl_easy_setopt(handle, CURLOPT_ERRORBUFFER, error)); -#ifdef __ANDROID__ - handleError(curl_easy_setopt(handle, CURLOPT_SSLCERTTYPE, "PEM")); - handleError(curl_easy_setopt(handle, CURLOPT_SSL_CTX_FUNCTION, sslctx_function)); -#else handleError(curl_easy_setopt(handle, CURLOPT_CAINFO, "ca-bundle.crt")); -#endif handleError(curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1)); handleError(curl_easy_setopt(handle, CURLOPT_URL, resource.url.c_str())); handleError(curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, writeCallback)); @@ -394,9 +261,7 @@ HTTPCURLRequest::HTTPCURLRequest(HTTPCURLContext* context_, const Resource& reso handleError(curl_multi_add_handle(context->multi, handle)); } -HTTPCURLRequest::~HTTPCURLRequest() { - MBGL_VERIFY_THREAD(tid); - +HTTPRequest::~HTTPRequest() { handleError(curl_multi_remove_handle(context->multi, handle)); context->returnHandle(handle); handle = nullptr; @@ -407,16 +272,11 @@ HTTPCURLRequest::~HTTPCURLRequest() { } } -void HTTPCURLRequest::cancel() { - delete this; -} - // 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 HTTPCURLRequest::writeCallback(void *const contents, const size_t size, const size_t nmemb, void *userp) { +size_t HTTPRequest::writeCallback(void *const contents, const size_t size, const size_t nmemb, void *userp) { assert(userp); - auto impl = reinterpret_cast<HTTPCURLRequest *>(userp); - MBGL_VERIFY_THREAD(impl->tid); + auto impl = reinterpret_cast<HTTPRequest *>(userp); if (!impl->data) { impl->data = std::make_shared<std::string>(); @@ -442,10 +302,9 @@ size_t headerMatches(const char *const header, const char *const buffer, const s return i == headerLength ? i : std::string::npos; } -size_t HTTPCURLRequest::headerCallback(char *const buffer, const size_t size, const size_t nmemb, void *userp) { +size_t HTTPRequest::headerCallback(char *const buffer, const size_t size, const size_t nmemb, void *userp) { assert(userp); - auto baton = reinterpret_cast<HTTPCURLRequest *>(userp); - MBGL_VERIFY_THREAD(baton->tid); + auto baton = reinterpret_cast<HTTPRequest *>(userp); if (!baton->response) { baton->response = std::make_unique<Response>(); @@ -462,7 +321,7 @@ size_t HTTPCURLRequest::headerCallback(char *const buffer, const size_t size, co baton->response->etag = std::string(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()); + baton->response->expires = http::CacheControl::parse(value.c_str()).toTimePoint(); } 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 = SystemClock::from_time_t(curl_getdate(value.c_str(), nullptr)); @@ -471,16 +330,7 @@ size_t HTTPCURLRequest::headerCallback(char *const buffer, const size_t size, co return length; } -void HTTPCURLRequest::handleResult(CURLcode code) { - MBGL_VERIFY_THREAD(tid); - - if (cancelled) { - // In this case, it doesn't make sense to even process the response even further since - // the request was canceled anyway. - delete this; - return; - } - +void HTTPRequest::handleResult(CURLcode code) { // Make sure a response object exists in case we haven't got any headers or content. if (!response) { response = std::make_unique<Response>(); @@ -533,16 +383,23 @@ void HTTPCURLRequest::handleResult(CURLcode code) { } } - // Actually return the response. - notify(*response); - delete this; + // Calling `callback` may result in deleting `this`. Copy data to temporaries first. + auto callback_ = callback; + auto response_ = *response; + callback_(response_); } -std::unique_ptr<HTTPContextBase> HTTPContextBase::createContext() { - return std::make_unique<HTTPCURLContext>(); +HTTPFileSource::HTTPFileSource() + : impl(std::make_unique<Impl>()) { +} + +HTTPFileSource::~HTTPFileSource() = default; + +std::unique_ptr<AsyncRequest> HTTPFileSource::request(const Resource& resource, Callback callback) { + return std::make_unique<HTTPRequest>(impl.get(), resource, callback); } -uint32_t HTTPContextBase::maximumConcurrentRequests() { +uint32_t HTTPFileSource::maximumConcurrentRequests() { return 20; } diff --git a/platform/default/online_file_source.cpp b/platform/default/online_file_source.cpp index 533046850a..6753b34f25 100644 --- a/platform/default/online_file_source.cpp +++ b/platform/default/online_file_source.cpp @@ -1,5 +1,5 @@ #include <mbgl/storage/online_file_source.hpp> -#include <mbgl/storage/http_context_base.hpp> +#include <mbgl/storage/http_file_source.hpp> #include <mbgl/storage/network_status.hpp> #include <mbgl/storage/response.hpp> @@ -35,7 +35,7 @@ public: OnlineFileSource::Impl& impl; Resource resource; - HTTPRequestBase* request = nullptr; + std::unique_ptr<AsyncRequest> request; util::Timer timer; Callback callback; @@ -82,7 +82,7 @@ public: assert(activeRequests.find(request) == activeRequests.end()); assert(!request->request); - if (activeRequests.size() >= HTTPContextBase::maximumConcurrentRequests()) { + if (activeRequests.size() >= HTTPFileSource::maximumConcurrentRequests()) { queueRequest(request); } else { activateRequest(request); @@ -96,10 +96,10 @@ public: void activateRequest(OnlineFileRequest* request) { activeRequests.insert(request); - request->request = httpContext->createRequest(request->resource, [=] (Response response) { + request->request = httpFileSource.request(request->resource, [=] (Response response) { activeRequests.erase(request); activatePendingRequest(); - request->request = nullptr; + request->request.reset(); request->completed(response); }); } @@ -140,7 +140,7 @@ private: std::unordered_map<OnlineFileRequest*, std::list<OnlineFileRequest*>::iterator> pendingRequestsMap; std::unordered_set<OnlineFileRequest*> activeRequests; - const std::unique_ptr<HTTPContextBase> httpContext { HTTPContextBase::createContext() }; + HTTPFileSource httpFileSource; util::AsyncTask reachability { std::bind(&Impl::networkIsReachableAgain, this) }; }; @@ -198,9 +198,6 @@ OnlineFileRequest::OnlineFileRequest(const Resource& resource_, Callback callbac OnlineFileRequest::~OnlineFileRequest() { impl.remove(this); - if (request) { - request->cancel(); - } } static Duration errorRetryTimeout(Response::Error::Reason failedRequestReason, uint32_t failedRequests) { diff --git a/platform/ios/platform.gyp b/platform/ios/platform.gyp index b4190d6fef..a38ab3869c 100644 --- a/platform/ios/platform.gyp +++ b/platform/ios/platform.gyp @@ -98,7 +98,7 @@ '../default/mbgl/storage/offline_download.cpp', '../default/sqlite3.hpp', '../default/sqlite3.cpp', - '../darwin/src/http_request_nsurl.mm', + '../darwin/src/http_file_source.mm', '../darwin/src/log_nslog.mm', '../darwin/src/string_nsstring.mm', '../darwin/src/image.mm', diff --git a/platform/linux/platform.gyp b/platform/linux/platform.gyp index 557587d6e9..4bef3e69cd 100644 --- a/platform/linux/platform.gyp +++ b/platform/linux/platform.gyp @@ -57,7 +57,7 @@ '../default/png_reader.cpp', '../default/jpeg_reader.cpp', '../default/asset_file_source.cpp', - '../default/http_request_curl.cpp', + '../default/http_file_source.cpp', '../default/default_file_source.cpp', '../default/online_file_source.cpp', '../default/mbgl/storage/offline.hpp', diff --git a/platform/osx/platform.gyp b/platform/osx/platform.gyp index 657aa85286..229c721845 100644 --- a/platform/osx/platform.gyp +++ b/platform/osx/platform.gyp @@ -59,7 +59,7 @@ '../default/mbgl/storage/offline_download.cpp', '../default/sqlite3.hpp', '../default/sqlite3.cpp', - '../darwin/src/http_request_nsurl.mm', + '../darwin/src/http_file_source.mm', '../darwin/src/log_nslog.mm', '../darwin/src/string_nsstring.mm', '../darwin/src/image.mm', |