summaryrefslogtreecommitdiff
path: root/src/storage/http_request.cpp
blob: 56eef9268cc68a81b94c1489e9eab654e27f1151 (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
#include <mbgl/storage/http_request.hpp>
#include <mbgl/storage/request.hpp>
#include <mbgl/storage/sqlite_store.hpp>
#include <mbgl/storage/http_request_baton.hpp>

#include <uv.h>

#include <cassert>
#include <chrono>

namespace mbgl {

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdisabled-macro-expansion"
#pragma clang diagnostic ignored "-Wexit-time-destructors"
#pragma clang diagnostic ignored "-Wglobal-constructors"

struct CacheRequestBaton {
    HTTPRequest *request = nullptr;
    std::string path;
    uv_loop_t *loop;
};

HTTPRequest::HTTPRequest(ResourceType type_, const std::string &path, uv_loop_t *loop, std::shared_ptr<SQLiteStore> store_)
    : BaseRequest(), thread_id(uv_thread_self()), store(store_), type(type_) {
    cache_baton = new CacheRequestBaton;
    cache_baton->request = this;
    cache_baton->path = path;
    cache_baton->loop = loop;
    store->get(path, [](std::unique_ptr<Response> &&response, void *ptr) {
        // Wrap in a unique_ptr, so it'll always get auto-destructed.
        std::unique_ptr<CacheRequestBaton> baton((CacheRequestBaton *)ptr);
        if (baton->request) {
            assert(uv_thread_self() == baton->request->thread_id);
            baton->request->loadedCacheEntry(std::move(response));
            if (baton->request) {
                // If we called notify(), the request object may already have ceased to exist.
                baton->request->cache_baton = nullptr;
            }
        }
    }, cache_baton);
}

void HTTPRequest::loadedCacheEntry(std::unique_ptr<Response> &&response) {
    if (response) {
        // This entry was stored in the cache. Now determine if we need to revalidate.
        const int64_t now = std::chrono::duration_cast<std::chrono::seconds>(
                                std::chrono::system_clock::now().time_since_epoch()).count();
        if (response->expires > now) {
            if (cache_baton->request) {
                cache_baton->request->response = std::move(response);
                cache_baton->request->notify();
                // Note: after calling notify(), the request object may cease to exist.
            }
            // This HTTPRequest is completed.
            return;
        } else {
            // TODO: notify with preliminary results.
        }
    }

    // Either, the cache entry is expired, or there is no cached version. In both cases,
    // we need to do a new request. The requesting code will issue a conditional request in case
    // we already have a modified date on the response object.
    http_baton = new HTTPRequestBaton;
    http_baton->request = this;
    http_baton->path = std::move(cache_baton->path);
    http_baton->async = new uv_async_t;
    http_baton->response = std::move(response);
    http_baton->async->data = http_baton;
    uv_async_init(cache_baton->loop, http_baton->async, [](uv_async_t *async) {
        std::unique_ptr<HTTPRequestBaton> baton((HTTPRequestBaton *)async->data);
        if (baton->request) {
            assert(uv_thread_self() == baton->request->thread_id);
            baton->request->response = std::move(baton->response);
            if (baton->not_modified) {
                baton->request->store->updateExpiration(baton->path, baton->request->response->expires);
            } else {
                baton->request->store->put(baton->path, baton->request->type, *baton->request->response);
            }
            baton->request->notify();
            // Note: after calling notify(), the baton object may cease to exist.
        }

        uv_close((uv_handle_t *)async, [](uv_handle_t *handle) {
            delete (uv_async_t *)handle;
        });
    });
    http_baton->start();
}

HTTPRequest::~HTTPRequest() {
    assert(uv_thread_self() == thread_id);

    if (cache_baton) {
        // Make sure that this object doesn't accidentally get accessed when it is destructed before
        // the callback returned. They are being run in the same thread, so just setting it to
        // null is sufficient.
        // Note: We don't manually delete the CacheRequestBaton since it'll be deleted by the
        // callback.
        cache_baton->request = nullptr;
    }

    if (http_baton) {
        http_baton->request = nullptr;
        http_baton->cancel();
    }
}

#pragma clang diagnostic pop

}