summaryrefslogtreecommitdiff
path: root/common/http_request_baton_cocoa.mm
diff options
context:
space:
mode:
Diffstat (limited to 'common/http_request_baton_cocoa.mm')
-rw-r--r--common/http_request_baton_cocoa.mm149
1 files changed, 149 insertions, 0 deletions
diff --git a/common/http_request_baton_cocoa.mm b/common/http_request_baton_cocoa.mm
new file mode 100644
index 0000000000..a9992fad8c
--- /dev/null
+++ b/common/http_request_baton_cocoa.mm
@@ -0,0 +1,149 @@
+#include <mbgl/storage/http_request_baton.hpp>
+#include <mbgl/util/std.hpp>
+#include <mbgl/util/parsedate.h>
+
+#include <uv.h>
+
+#include <mbgl/util/uv.hpp>
+
+#import <Foundation/Foundation.h>
+#include <ctime>
+#include <xlocale.h>
+
+namespace mbgl {
+
+dispatch_once_t request_initialize = 0;
+NSURLSession *session = nullptr;
+
+void HTTPRequestBaton::start(const util::ptr<HTTPRequestBaton> &ptr) {
+ assert(uv_thread_self() == ptr->thread_id);
+
+ // Starts the request.
+ util::ptr<HTTPRequestBaton> baton = ptr;
+
+ // Create a C locale
+ static locale_t locale = newlocale(LC_ALL_MASK, nullptr, nullptr);
+
+ dispatch_once(&request_initialize, ^{
+ NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
+ sessionConfig.timeoutIntervalForResource = 30;
+ sessionConfig.HTTPMaximumConnectionsPerHost = 8;
+ sessionConfig.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
+ sessionConfig.URLCache = nil;
+
+ session = [NSURLSession sessionWithConfiguration:sessionConfig];
+ });
+
+ 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 time_t modified = baton->response->modified;
+ struct tm *timeinfo = std::gmtime(&modified);
+ char buffer[32];
+ strftime_l(buffer, 32, "%a, %d %b %Y %H:%M:%S GMT", timeinfo, locale);
+ [request addValue:@(buffer) forHTTPHeaderField:@"If-Modified-Since"];
+ }
+ }
+
+ NSURLSessionDataTask *task = [session dataTaskWithRequest:request
+ 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 = std::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 = std::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 = std::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(uv_thread_self() == ptr->thread_id);
+ assert(ptr->ptr);
+
+ NSURLSessionDataTask *task = CFBridgingRelease(ptr->ptr);
+ ptr->ptr = nullptr;
+ [task cancel];
+}
+
+}