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 "http_cache_strategy.h"
17
18 #include <algorithm>
19 #include <cstring>
20
21 #include "casche_constant.h"
22 #include "constant.h"
23 #include "http_cache_request.h"
24 #include "http_cache_response.h"
25 #include "http_time.h"
26 #include "netstack_log.h"
27
28 static constexpr int64_t ONE_DAY_MILLISECONDS = 24 * 60 * 60 * 1000L;
29 static constexpr int64_t CONVERT_TO_MILLISECONDS = 1000;
30 static constexpr const char *KEY_RANGE = "range";
31
32 // RFC 7234
33
34 namespace OHOS::NetStack::Http {
HttpCacheStrategy(HttpRequestOptions & requestOptions)35 HttpCacheStrategy::HttpCacheStrategy(HttpRequestOptions &requestOptions) : requestOptions_(requestOptions)
36 {
37 cacheRequest_.ParseRequestHeader(requestOptions_.GetHeader());
38 cacheRequest_.SetRequestTime(requestOptions_.GetRequestTime());
39 }
40
CouldUseCache()41 bool HttpCacheStrategy::CouldUseCache()
42 {
43 return requestOptions_.GetMethod() == HttpConstant::HTTP_METHOD_GET ||
44 requestOptions_.GetMethod() == HttpConstant::HTTP_METHOD_HEAD ||
45 requestOptions_.GetHeader().find(KEY_RANGE) != requestOptions_.GetHeader().end();
46 }
47
RunStrategy(HttpResponse & response)48 CacheStatus HttpCacheStrategy::RunStrategy(HttpResponse &response)
49 {
50 cacheResponse_.ParseCacheResponseHeader(response.GetHeader());
51 cacheResponse_.SetRespCode(static_cast<ResponseCode>(response.GetResponseCode()));
52 cacheResponse_.SetResponseTime(response.GetResponseTime());
53 cacheResponse_.SetRequestTime(response.GetRequestTime());
54 return RunStrategyInternal(response);
55 }
56
IsCacheable(const HttpResponse & response)57 bool HttpCacheStrategy::IsCacheable(const HttpResponse &response)
58 {
59 if (!CouldUseCache()) {
60 return false;
61 }
62 HttpCacheResponse tempCacheResponse;
63 tempCacheResponse.ParseCacheResponseHeader(response.GetHeader());
64 tempCacheResponse.SetRespCode(static_cast<ResponseCode>(response.GetResponseCode()));
65 return IsCacheable(tempCacheResponse);
66 }
67
CacheResponseAgeMillis()68 int64_t HttpCacheStrategy::CacheResponseAgeMillis()
69 {
70 // 4.2.3. Calculating Age
71
72 /* The following data is used for the age calculation:
73
74 age_value
75 The term "age_value" denotes the value of the Age header field (Section 5.1), in a form appropriate for arithmetic
76 operation; or 0, if not available.
77
78 date_value
79 The term "date_value" denotes the value of the Date header field, in a form appropriate for arithmetic operations.
80 See Section 7.1.1.2 of [RFC7231] for the definition of the Date header field, and for requirements regarding
81 responses without it.
82
83 now
84 The term "now" means "the current value of the clock at the host performing the calculation". A host ought to use
85 NTP ([RFC5905]) or some similar protocol to synchronize its clocks to Coordinated Universal Time.
86
87 request_time
88 The current value of the clock at the host at the time the request resulting in the stored response was made.
89
90 response_time
91 The current value of the clock at the host at the time the response was received.
92 A response's age can be calculated in two entirely independent ways:
93
94 the "apparent_age": response_time minus date_value, if the local clock is reasonably well synchronized to the origin
95 server's clock. If the result is negative, the result is replaced by zero. the "corrected_age_value", if all of the
96 caches along the response path implement HTTP/1.1. A cache MUST interpret this value relative to the time the
97 request was initiated, not the time that the response was received. apparent_age = max(0, response_time -
98 date_value);
99
100 response_delay = response_time - request_time;
101 corrected_age_value = age_value + response_delay;
102 These are combined as
103
104 corrected_initial_age = max(apparent_age, corrected_age_value);
105 unless the cache is confident in the value of the Age header field (e.g., because there are no HTTP/1.0 hops in the
106 Via header field), in which case the corrected_age_value MAY be used as the corrected_initial_age.
107
108 The current_age of a stored response can then be calculated by adding the amount of time (in seconds) since the
109 stored response was last validated by the origin server to the corrected_initial_age.
110
111 resident_time = now - response_time;
112 current_age = corrected_initial_age + resident_time; */
113
114 int64_t age = std::max<int64_t>(0, cacheResponse_.GetAgeSeconds());
115 int64_t dateTime = std::max<int64_t>(0, cacheResponse_.GetDate());
116 int64_t nowTime = std::max<int64_t>(0, HttpTime::GetNowTimeSeconds());
117 int64_t requestTime = std::max<int64_t>(0, cacheResponse_.GetRequestTime());
118 int64_t responseTime = std::max<int64_t>(0, cacheResponse_.GetResponseTime());
119
120 NETSTACK_LOGI("CacheResponseAgeMillis: %{public}lld, %{public}lld, %{public}lld, %{public}lld, %{public}lld",
121 static_cast<long long>(age), static_cast<long long>(dateTime), static_cast<long long>(nowTime),
122 static_cast<long long>(requestTime), static_cast<long long>(responseTime));
123
124 int64_t apparentAge = std::max<int64_t>(0, responseTime - dateTime);
125 int64_t responseDelay = std::max<int64_t>(0, responseTime - requestTime);
126 int64_t correctedAgeValue = age + responseDelay;
127 int64_t correctedInitialAge = std::max<int64_t>(apparentAge, correctedAgeValue);
128
129 int64_t residentTime = std::max<int64_t>(0, nowTime - responseTime);
130
131 return (correctedInitialAge + residentTime) * CONVERT_TO_MILLISECONDS;
132 }
133
ComputeFreshnessLifetimeSecondsInternal()134 int64_t HttpCacheStrategy::ComputeFreshnessLifetimeSecondsInternal()
135 {
136 int64_t sMaxAge = cacheResponse_.GetSMaxAgeSeconds();
137 if (sMaxAge != INVALID_TIME) {
138 // If the cache is shared and the s-maxage response directive (Section 5.2.2.9) is present, use its value
139 return sMaxAge;
140 }
141
142 int64_t maxAge = cacheResponse_.GetMaxAgeSeconds();
143 if (maxAge != INVALID_TIME) {
144 // If the max-age response directive (Section 5.2.2.8) is present, use its value
145 return maxAge;
146 }
147
148 if (cacheResponse_.GetExpires() != INVALID_TIME) {
149 // If the Expires response header field (Section 5.3) is present, use its value minus the value of the Date
150 // response header field
151 int64_t responseTime = cacheResponse_.GetResponseTime();
152 if (cacheResponse_.GetDate() != INVALID_TIME) {
153 responseTime = cacheResponse_.GetDate();
154 }
155 int64_t delta = cacheResponse_.GetExpires() - responseTime;
156 return std::max<int64_t>(delta, 0);
157 }
158
159 if (cacheResponse_.GetLastModified() != INVALID_TIME) {
160 // 4.2.2. Calculating Heuristic Freshness
161 int64_t requestTime = cacheRequest_.GetRequestTime();
162 if (cacheResponse_.GetDate() != INVALID_TIME) {
163 requestTime = cacheResponse_.GetDate();
164 }
165 int64_t delta = requestTime - cacheResponse_.GetLastModified();
166 return std::max<int64_t>(delta / DECIMAL, 0);
167 }
168
169 return 0;
170 }
171
ComputeFreshnessLifetimeMillis()172 int64_t HttpCacheStrategy::ComputeFreshnessLifetimeMillis()
173 {
174 int64_t lifeTime = ComputeFreshnessLifetimeSecondsInternal();
175
176 int64_t reqMaxAge = cacheRequest_.GetMaxAgeSeconds();
177 if (reqMaxAge != INVALID_TIME) {
178 lifeTime = std::min(lifeTime, reqMaxAge);
179 }
180
181 NETSTACK_LOGI("lifeTime=%{public}lld", static_cast<long long>(lifeTime));
182 return lifeTime * CONVERT_TO_MILLISECONDS;
183 }
184
UpdateRequestHeader(const std::string & etag,const std::string & lastModified,const std::string & date)185 void HttpCacheStrategy::UpdateRequestHeader(const std::string &etag,
186 const std::string &lastModified,
187 const std::string &date)
188 {
189 if (!etag.empty()) {
190 requestOptions_.SetHeader(IF_NONE_MATCH, etag);
191 } else if (!lastModified.empty()) {
192 requestOptions_.SetHeader(IF_MODIFIED_SINCE, lastModified);
193 } else if (!date.empty()) {
194 requestOptions_.SetHeader(IF_MODIFIED_SINCE, date);
195 }
196 }
197
IsCacheable(const HttpCacheResponse & cacheResponse)198 bool HttpCacheStrategy::IsCacheable(const HttpCacheResponse &cacheResponse)
199 {
200 switch (cacheResponse.GetRespCode()) {
201 case ResponseCode::OK:
202 case ResponseCode::NOT_AUTHORITATIVE:
203 case ResponseCode::NO_CONTENT:
204 case ResponseCode::MULT_CHOICE:
205 case ResponseCode::MOVED_PERM:
206 case ResponseCode::NOT_FOUND:
207 case ResponseCode::BAD_METHOD:
208 case ResponseCode::GONE:
209 case ResponseCode::REQ_TOO_LONG:
210 case ResponseCode::NOT_IMPLEMENTED:
211 // These codes can be cached unless headers forbid it.
212 break;
213
214 case ResponseCode::MOVED_TEMP:
215 if (cacheResponse.GetExpires() != INVALID_TIME || cacheResponse.GetMaxAgeSeconds() != INVALID_TIME ||
216 cacheResponse.IsPublicCache() || cacheResponse.IsPrivateCache()) {
217 break;
218 }
219 return false;
220
221 default:
222 return false;
223 }
224
225 return !cacheResponse.IsNoStore() && !cacheRequest_.IsNoStore();
226 }
227
GetFreshness()228 std::tuple<int64_t, int64_t, int64_t, int64_t> HttpCacheStrategy::GetFreshness()
229 {
230 int64_t ageMillis = CacheResponseAgeMillis();
231
232 int64_t lifeTime = ComputeFreshnessLifetimeMillis();
233
234 int64_t minFreshMillis = std::max<int64_t>(0, cacheRequest_.GetMinFreshSeconds() * CONVERT_TO_MILLISECONDS);
235
236 int64_t maxStaleMillis = 0;
237 if (!cacheResponse_.IsMustRevalidate()) {
238 maxStaleMillis = std::max<int64_t>(0, cacheRequest_.GetMaxStaleSeconds() * CONVERT_TO_MILLISECONDS);
239 }
240
241 NETSTACK_LOGI("GetFreshness: %{public}lld, %{public}lld, %{public}lld, %{public}lld",
242 static_cast<long long>(ageMillis), static_cast<long long>(minFreshMillis),
243 static_cast<long long>(lifeTime), static_cast<long long>(maxStaleMillis));
244 return {ageMillis, minFreshMillis, lifeTime, maxStaleMillis};
245 }
246
RunStrategyInternal(HttpResponse & response)247 CacheStatus HttpCacheStrategy::RunStrategyInternal(HttpResponse &response)
248 {
249 if (cacheRequest_.IsNoCache()) {
250 NETSTACK_LOGI("return DENY");
251 return DENY;
252 }
253
254 if (cacheResponse_.IsNoCache()) {
255 NETSTACK_LOGI("return STALE");
256 return STALE;
257 }
258
259 if (cacheRequest_.IsOnlyIfCached()) {
260 NETSTACK_LOGI("return FRESH");
261 return FRESH;
262 }
263
264 // T.B.D https and TLS handshake
265 if (!IsCacheable(cacheResponse_)) {
266 NETSTACK_LOGI("return DENY");
267 return DENY;
268 }
269
270 if (cacheRequest_.GetIfModifiedSince() != INVALID_TIME || !cacheRequest_.GetIfNoneMatch().empty()) {
271 NETSTACK_LOGI("return DENY");
272 return DENY;
273 }
274
275 auto [ageMillis, minFreshMillis, lifeTime, maxStaleMillis] = GetFreshness();
276
277 if (ageMillis + minFreshMillis < lifeTime + maxStaleMillis) {
278 if (ageMillis + minFreshMillis >= lifeTime) {
279 response.SetWarning("110 \"Response is STALE\"");
280 }
281
282 if (ageMillis > ONE_DAY_MILLISECONDS && cacheRequest_.GetMaxAgeSeconds() == INVALID_TIME &&
283 cacheResponse_.GetExpires() == INVALID_TIME) {
284 response.SetWarning("113 \"Heuristic expiration\"");
285 }
286
287 NETSTACK_LOGI("return FRESH");
288 return FRESH;
289 }
290
291 // The cache has expired and the request needs to be re-initialized
292 UpdateRequestHeader(cacheResponse_.GetEtag(), cacheResponse_.GetLastModifiedStr(), cacheResponse_.GetDateStr());
293
294 NETSTACK_LOGI("return STALE");
295 return STALE;
296 }
297 } // namespace OHOS::NetStack::Http
298