1 /*
2 * Copyright (c) 2022 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 "cache_proxy.h"
17
18 #include <atomic>
19 #include <condition_variable>
20 #include <mutex>
21 #include <thread>
22
23 #include "base64_utils.h"
24 #include "constant.h"
25 #include "casche_constant.h"
26 #include "lru_cache_disk_handler.h"
27 #include "netstack_common_utils.h"
28 #include "netstack_log.h"
29 #include "request_context.h"
30
31 static constexpr const char *CACHE_FILE = "/data/storage/el2/base/cache/cache.json";
32 static constexpr int32_t WRITE_INTERVAL = 60;
33
34 namespace OHOS::NetStack::Http {
35 std::mutex g_diskCacheMutex;
36 std::mutex g_cacheNeedRunMutex;
37 std::atomic_bool g_cacheNeedRun(false);
38 std::atomic_bool g_cacheIsRunning(false);
39 std::condition_variable g_cacheThreadCondition;
40 std::condition_variable g_cacheNeedRunCondition;
41 static LRUCacheDiskHandler DISK_LRU_CACHE(CACHE_FILE, 0); // NOLINT(cert-err58-cpp)
42
CacheProxy(HttpRequestOptions & requestOptions)43 CacheProxy::CacheProxy(HttpRequestOptions &requestOptions) : strategy_(requestOptions)
44 {
45 std::string str = requestOptions.GetUrl() + HttpConstant::HTTP_LINE_SEPARATOR +
46 CommonUtils::ToLower(requestOptions.GetMethod()) + HttpConstant::HTTP_LINE_SEPARATOR;
47 for (const auto &p : requestOptions.GetHeader()) {
48 if (p.first == IF_NONE_MATCH || p.first == IF_MODIFIED_SINCE) {
49 continue;
50 }
51 str += p.first + HttpConstant::HTTP_HEADER_SEPARATOR + p.second + HttpConstant::HTTP_LINE_SEPARATOR;
52 }
53 str += std::to_string(requestOptions.GetHttpVersion());
54 key_ = Base64::Encode(str);
55 }
56
ReadResponseFromCache(RequestContext * context)57 bool CacheProxy::ReadResponseFromCache(RequestContext *context)
58 {
59 if (!g_cacheIsRunning.load()) {
60 return false;
61 }
62
63 if (!strategy_.CouldUseCache()) {
64 NETSTACK_LOGI("only GET/HEAD method or header has [Range] can use cache");
65 return false;
66 }
67
68 auto responseFromCache = DISK_LRU_CACHE.Get(key_);
69 if (responseFromCache.empty()) {
70 NETSTACK_LOGI("no cache with this request");
71 return false;
72 }
73 HttpResponse cachedResponse;
74 cachedResponse.SetRawHeader(Base64::Decode(responseFromCache[HttpConstant::RESPONSE_KEY_HEADER]));
75 cachedResponse.SetResult(Base64::Decode(responseFromCache[HttpConstant::RESPONSE_KEY_RESULT]));
76 cachedResponse.SetCookies(Base64::Decode(responseFromCache[HttpConstant::RESPONSE_KEY_COOKIES]));
77 cachedResponse.SetResponseTime(Base64::Decode(responseFromCache[HttpConstant::RESPONSE_TIME]));
78 cachedResponse.SetRequestTime(Base64::Decode(responseFromCache[HttpConstant::REQUEST_TIME]));
79 cachedResponse.SetResponseCode(static_cast<uint32_t>(ResponseCode::OK));
80 cachedResponse.ParseHeaders();
81
82 CacheStatus status = strategy_.RunStrategy(cachedResponse);
83 if (status == CacheStatus::FRESH) {
84 context->response = cachedResponse;
85 NETSTACK_LOGI("cache is FRESH");
86 return true;
87 }
88 if (status == CacheStatus::STALE) {
89 NETSTACK_LOGI("cache is STATE, we try to talk to the server");
90 context->SetCacheResponse(cachedResponse);
91 return false;
92 }
93 NETSTACK_LOGD("cache should not be used");
94 return false;
95 }
96
WriteResponseToCache(const HttpResponse & response)97 void CacheProxy::WriteResponseToCache(const HttpResponse &response)
98 {
99 if (!g_cacheIsRunning.load()) {
100 return;
101 }
102
103 if (!strategy_.IsCacheable(response)) {
104 NETSTACK_LOGE("do not cache this response");
105 return;
106 }
107 std::unordered_map<std::string, std::string> cacheResponse;
108 cacheResponse[HttpConstant::RESPONSE_KEY_HEADER] = Base64::Encode(response.GetRawHeader());
109 cacheResponse[HttpConstant::RESPONSE_KEY_RESULT] = Base64::Encode(response.GetResult());
110 cacheResponse[HttpConstant::RESPONSE_KEY_COOKIES] = Base64::Encode(response.GetCookies());
111 cacheResponse[HttpConstant::RESPONSE_TIME] = Base64::Encode(response.GetResponseTime());
112 cacheResponse[HttpConstant::REQUEST_TIME] = Base64::Encode(response.GetRequestTime());
113
114 DISK_LRU_CACHE.Put(key_, cacheResponse);
115 }
116
RunCache()117 void CacheProxy::RunCache()
118 {
119 RunCacheWithSize(MAX_DISK_CACHE_SIZE);
120 }
121
RunCacheWithSize(size_t capacity)122 void CacheProxy::RunCacheWithSize(size_t capacity)
123 {
124 if (g_cacheIsRunning.load()) {
125 return;
126 }
127 DISK_LRU_CACHE.SetCapacity(capacity);
128
129 g_cacheNeedRun.store(true);
130
131 DISK_LRU_CACHE.ReadCacheFromJsonFile();
132
133 std::thread([]() {
134 g_cacheIsRunning.store(true);
135 while (g_cacheNeedRun.load()) {
136 std::unique_lock<std::mutex> lock(g_cacheNeedRunMutex);
137 g_cacheNeedRunCondition.wait_for(lock, std::chrono::seconds(WRITE_INTERVAL),
138 [] { return !g_cacheNeedRun.load(); });
139
140 DISK_LRU_CACHE.WriteCacheToJsonFile();
141 }
142
143 g_cacheIsRunning.store(false);
144 g_cacheThreadCondition.notify_all();
145 }).detach();
146 }
147
FlushCache()148 void CacheProxy::FlushCache()
149 {
150 if (!g_cacheIsRunning.load()) {
151 return;
152 }
153 DISK_LRU_CACHE.WriteCacheToJsonFile();
154 }
155
StopCacheAndDelete()156 void CacheProxy::StopCacheAndDelete()
157 {
158 if (!g_cacheIsRunning.load()) {
159 return;
160 }
161 g_cacheNeedRun.store(false);
162 g_cacheNeedRunCondition.notify_all();
163
164 std::unique_lock<std::mutex> lock(g_diskCacheMutex);
165 g_cacheThreadCondition.wait(lock, [] { return !g_cacheIsRunning.load(); });
166 DISK_LRU_CACHE.Delete();
167 }
168 } // namespace OHOS::NetStack::Http
169