summaryrefslogtreecommitdiff
path: root/common/http_request_baton_cocoa.mm
blob: a9992fad8c2df18366428e599e6053d8cf390c62 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
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];
}

}