diff options
Diffstat (limited to 'platform/darwin/src/http_request_nsurl.mm')
-rw-r--r-- | platform/darwin/src/http_request_nsurl.mm | 267 |
1 files changed, 267 insertions, 0 deletions
diff --git a/platform/darwin/src/http_request_nsurl.mm b/platform/darwin/src/http_request_nsurl.mm new file mode 100644 index 0000000000..e58441e4d2 --- /dev/null +++ b/platform/darwin/src/http_request_nsurl.mm @@ -0,0 +1,267 @@ +#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); + ~HTTPNSURLRequest(); + + 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(); + ~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]; + [session retain]; + + // Write user agent string + userAgent = @"MapboxGL"; + + accountType = [[NSUserDefaults standardUserDefaults] integerForKey:@"MGLMapboxAccountType"]; + } +} + +HTTPNSURLContext::~HTTPNSURLContext() { + [session release]; + session = nullptr; + + [userAgent release]; + userAgent = nullptr; +} + +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 retain]; + [task resume]; + } +} + +HTTPNSURLRequest::~HTTPNSURLRequest() { + assert(!task); +} + +void HTTPNSURLRequest::handleResponse() { + if (task) { + [task release]; + task = nullptr; + } + + assert(response); + notify(*response); + + delete this; +} + +void HTTPNSURLRequest::cancel() { + if (task) { + [task cancel]; + [task release]; + task = nullptr; + } + + { + 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; +} + +} |