summaryrefslogtreecommitdiff
path: root/src/storage/http_request_baton_darwin.mm
blob: b1e47e35080441f058c0cf3e6f0edbf545dabfed (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
#include <mbgl/storage/http_request_baton.hpp>
#include <mbgl/util/parsedate.h>

#include <uv.h>

#import <Foundation/Foundation.h>
#include <ctime>
#include <xlocale.h>

namespace mbgl {

dispatch_once_t request_initialize = 0;
NSURLSession *session = nullptr;

void HTTPRequestBaton::start() {
    // Starts the request.

    // 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;

        session = [NSURLSession sessionWithConfiguration:sessionConfig];
        // TODO: add a delegate to the session that prohibits caching, since we handle this ourselves.
    });


    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:@(path.c_str())]];
    if (response && response->modified) {
        struct tm *timeinfo;
        char buffer[32];
        const time_t modified = response->modified;
        timeinfo = std::gmtime (&modified);
        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) {
                // TODO: Use different codes for host not found, timeout, invalid URL etc.
                // These can be categorized in temporary and permanent errors.
                response = std::unique_ptr<Response>(new Response);
                response->code = -1;
                response->message = [[error localizedDescription] UTF8String];
            } else {
                // This request was canceled.
                // The response code remains at 0 to indicate cancelation.
            }
        } else if ([res isKindOfClass:[NSHTTPURLResponse class]]) {
            const long code = [(NSHTTPURLResponse *)res statusCode];
            if (code == 304) {
                // Assume a Response object already exists.
                assert(response);
                not_modified = true;
            } else {
                response = std::unique_ptr<Response>(new Response);
                response->code = code;
                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 = Response::parseCacheControl([cache_control UTF8String]);
            }

            NSString *last_modified = [headers objectForKey:@"Last-Modified"];
            if (last_modified) {
                response->modified = parse_date([last_modified UTF8String]);
            }
        } else {
            // This should never happen.
            response = std::unique_ptr<Response>(new Response);
            response->code = -1;
            response->message = "response class is not NSHTTPURLResponse";
            // TODO: If we get a failure, we should not overwrite a potentially existing response.
        }

        uv_async_send(async);
    }];

    ptr = const_cast<void *>(CFBridgingRetain(task));

    [task resume];
}

void HTTPRequestBaton::cancel() {
    // After this function returns, the HTTPRequestBaton object may cease to exist at any time.
    // try to stop the request
    if (ptr) {
        [(NSURLSessionDataTask *)CFBridgingRelease(ptr) cancel];
        ptr = nullptr;
    }
}

}