1 /*
2  * Copyright (c) 2024 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include "net_http_cache_proxy.h"
17 
18 #include <atomic>
19 #include <condition_variable>
20 #include <mutex>
21 #include <thread>
22 #include <array>
23 
24 #include "netstack_log.h"
25 #include "netstack_common_utils.h"
26 
27 static constexpr const char *CACHE_FILE = "/data/storage/el2/base/cache/cache.json";
28 static constexpr int32_t WRITE_INTERVAL = 60;
29 
30 namespace OHOS::NetStack::Http {
31 std::mutex cj_diskCacheMutex;
32 std::mutex cj_cacheNeedRunMutex;
33 std::atomic_bool cj_cacheNeedRun(false);
34 std::atomic_bool cj_cacheIsRunning(false);
35 std::condition_variable cj_cacheThreadCondition;
36 std::condition_variable cj_cacheNeedRunCondition;
37 static LRUCacheDiskHandler g_cjDiskLruCache(CACHE_FILE, 0); // NOLINT(cert-err58-cpp)
38 
CacheProxy(HttpRequest & requestOptions)39 CacheProxy::CacheProxy(HttpRequest &requestOptions) : strategy_(requestOptions)
40 {
41     std::string str = requestOptions.GetUrl() + HTTP_LINE_SEPARATOR +
42                       CommonUtils::ToLower(requestOptions.GetMethod()) + HTTP_LINE_SEPARATOR;
43     for (const auto &p : requestOptions.GetHeader()) {
44         str += p.first + HTTP_HEADER_SEPARATOR + p.second + HTTP_LINE_SEPARATOR;
45     }
46     str += std::to_string(requestOptions.GetHttpVersion());
47     key_ = Encode(str);
48 }
49 
ReadResponseFromCache(RequestContext * context)50 bool CacheProxy::ReadResponseFromCache(RequestContext *context)
51 {
52     if (!cj_cacheIsRunning.load()) {
53         return false;
54     }
55 
56     if (!strategy_.CouldUseCache()) {
57         NETSTACK_LOGI("only GET/HEAD method or header has [Range] can use cache");
58         return false;
59     }
60 
61     auto responseFromCache = g_cjDiskLruCache.Get(key_);
62     if (responseFromCache.empty()) {
63         NETSTACK_LOGI("no cache with this request");
64         return false;
65     }
66     HttpResponse cachedResponse;
67     cachedResponse.SetRawHeader(Decode(responseFromCache[RESPONSE_KEY_HEADER]));
68     cachedResponse.SetResult(Decode(responseFromCache[RESPONSE_KEY_RESULT]));
69     cachedResponse.SetCookies(Decode(responseFromCache[RESPONSE_KEY_COOKIES]));
70     cachedResponse.SetResponseTime(Decode(responseFromCache[RESPONSE_TIME]));
71     cachedResponse.SetRequestTime(Decode(responseFromCache[REQUEST_TIME]));
72     cachedResponse.SetResponseCode(static_cast<uint32_t>(ResponseCode::OK));
73     cachedResponse.ParseHeaders();
74 
75     CacheStatus status = strategy_.RunStrategy(cachedResponse);
76     if (status == CacheStatus::FRESH) {
77         context->response = cachedResponse;
78         NETSTACK_LOGI("cache is FRESH");
79         return true;
80     }
81     if (status == CacheStatus::STALE) {
82         NETSTACK_LOGI("cache is STATE, we try to talk to the server");
83         context->SetCacheResponse(cachedResponse);
84         return false;
85     }
86     NETSTACK_LOGI("cache should not be used");
87     return false;
88 }
89 
WriteResponseToCache(const HttpResponse & response)90 void CacheProxy::WriteResponseToCache(const HttpResponse &response)
91 {
92     if (!cj_cacheIsRunning.load()) {
93         return;
94     }
95 
96     if (!strategy_.IsCacheable(response)) {
97         NETSTACK_LOGE("do not cache this response");
98         return;
99     }
100     std::unordered_map<std::string, std::string> cacheResponse;
101     cacheResponse[RESPONSE_KEY_HEADER] = Encode(response.GetRawHeader());
102     cacheResponse[RESPONSE_KEY_RESULT] = Encode(response.GetResult());
103     cacheResponse[RESPONSE_KEY_COOKIES] = Encode(response.GetCookies());
104     cacheResponse[RESPONSE_TIME] = Encode(response.GetResponseTime());
105     cacheResponse[REQUEST_TIME] = Encode(response.GetRequestTime());
106 
107     g_cjDiskLruCache.Put(key_, cacheResponse);
108 }
109 
RunCache()110 void CacheProxy::RunCache()
111 {
112     RunCacheWithSize(MAX_DISK_CACHE_SIZE);
113 }
114 
RunCacheWithSize(size_t capacity)115 void CacheProxy::RunCacheWithSize(size_t capacity)
116 {
117     if (cj_cacheIsRunning.load()) {
118         return;
119     }
120     g_cjDiskLruCache.SetCapacity(capacity);
121 
122     cj_cacheNeedRun.store(true);
123 
124     g_cjDiskLruCache.ReadCacheFromJsonFile();
125 
126     std::thread([]() {
127         cj_cacheIsRunning.store(true);
128         while (cj_cacheNeedRun.load()) {
129             std::unique_lock<std::mutex> lock(cj_cacheNeedRunMutex);
130             cj_cacheNeedRunCondition.wait_for(lock, std::chrono::seconds(WRITE_INTERVAL),
131                 [] { return !cj_cacheNeedRun.load(); });
132 
133             g_cjDiskLruCache.WriteCacheToJsonFile();
134         }
135 
136         cj_cacheIsRunning.store(false);
137         cj_cacheThreadCondition.notify_all();
138     }).detach();
139 }
140 
FlushCache()141 void CacheProxy::FlushCache()
142 {
143     if (!cj_cacheIsRunning.load()) {
144         return;
145     }
146     g_cjDiskLruCache.WriteCacheToJsonFile();
147 }
148 
StopCacheAndDelete()149 void CacheProxy::StopCacheAndDelete()
150 {
151     if (!cj_cacheIsRunning.load()) {
152         return;
153     }
154     cj_cacheNeedRun.store(false);
155     cj_cacheNeedRunCondition.notify_all();
156 
157     std::unique_lock<std::mutex> lock(cj_diskCacheMutex);
158     cj_cacheThreadCondition.wait(lock, [] { return !cj_cacheIsRunning.load(); });
159     g_cjDiskLruCache.Delete();
160 }
161 } // namespace OHOS::NetStack::Http
162