org.aoju.bus.http.cache.CacheStrategy Maven / Gradle / Ivy
/*
* The MIT License
*
* Copyright (c) 2015-2020 aoju.org All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.aoju.bus.http.cache;
import org.aoju.bus.core.lang.Header;
import org.aoju.bus.core.lang.Http;
import org.aoju.bus.http.Builder;
import org.aoju.bus.http.Headers;
import org.aoju.bus.http.Request;
import org.aoju.bus.http.Response;
import org.aoju.bus.http.metric.http.HttpHeaders;
import java.util.Date;
import java.util.concurrent.TimeUnit;
/**
* 给定一个请求和缓存的响应,这将确定是使用网络、缓存还是两者都使用
* 选择缓存策略可能会向请求添加条件(比如条件get的“if - modified - since”报头)
* 或向缓存的响应添加警告(如果缓存的数据可能过时)
*
* @author Kimi Liu
* @version 5.6.5
* @since JDK 1.8+
*/
public final class CacheStrategy {
/**
* 请求在网络上发送,如果调用不使用网络则为空
*/
public final Request networkRequest;
/**
* 缓存响应以返回或验证;如果这个调用不使用缓存,则为null
*/
public final Response cacheResponse;
CacheStrategy(Request networkRequest, Response cacheResponse) {
this.networkRequest = networkRequest;
this.cacheResponse = cacheResponse;
}
/**
* 如果{@code response}可以存储为以后服务另一个请求,则返回true
*
* @param response 相应信息
* @param request 请求信息
* @return the true/false
*/
public static boolean isCacheable(Response response, Request request) {
// 总是去网络获取非缓存的响应代码(RFC 7231 section 6.1),这个实现不支持缓存部分内容
switch (response.code()) {
case Http.HTTP_OK:
case Http.HTTP_NOT_AUTHORITATIVE:
case Http.HTTP_NO_CONTENT:
case Http.HTTP_MULT_CHOICE:
case Http.HTTP_MOVED_PERM:
case Http.HTTP_NOT_FOUND:
case Http.HTTP_BAD_METHOD:
case Http.HTTP_GONE:
case Http.HTTP_REQ_TOO_LONG:
case Http.HTTP_NOT_IMPLEMENTED:
case Http.HTTP_PERM_REDIRECT:
// 这些代码可以被缓存,除非标头禁止
break;
case Http.HTTP_MOVED_TEMP:
case Http.HTTP_TEMP_REDIRECT:
if (response.header(Header.EXPIRES) != null
|| response.cacheControl().maxAgeSeconds() != -1
|| response.cacheControl().isPublic()
|| response.cacheControl().isPrivate()) {
break;
}
default:
// 不能缓存所有其他代码
return false;
}
// 针对请求或响应的'no-store'指令会阻止缓存响应。
return !response.cacheControl().noStore() && !request.cacheControl().noStore();
}
public static class Factory {
final long nowMillis;
final Request request;
final Response cacheResponse;
/**
* 服务器提供缓存的响应的时间。
*/
private Date servedDate;
private String servedDateString;
/**
* 缓存响应的最后修改日期
*/
private Date lastModified;
private String lastModifiedString;
/**
* 缓存的响应的过期日期。如果设置了该字段和最大值,则首选最大值
*/
private Date expires;
/**
* 扩展头由Httpd设置,指定缓存的HTTP请求首次启动时的时间戳
*/
private long sentRequestMillis;
/**
* 由Httpd设置的扩展头,指定首次接收缓存的HTTP响应时的时间戳
*/
private long receivedResponseMillis;
/**
* 缓存响应的Etag
*/
private String etag;
/**
* 缓存响应的时间
*/
private int ageSeconds = -1;
public Factory(long nowMillis, Request request, Response cacheResponse) {
this.nowMillis = nowMillis;
this.request = request;
this.cacheResponse = cacheResponse;
if (cacheResponse != null) {
this.sentRequestMillis = cacheResponse.sentRequestAtMillis();
this.receivedResponseMillis = cacheResponse.receivedResponseAtMillis();
Headers headers = cacheResponse.headers();
for (int i = 0, size = headers.size(); i < size; i++) {
String fieldName = headers.name(i);
String value = headers.value(i);
if ("Date".equalsIgnoreCase(fieldName)) {
servedDate = org.aoju.bus.http.Builder.parse(value);
servedDateString = value;
} else if ("Expires".equalsIgnoreCase(fieldName)) {
expires = org.aoju.bus.http.Builder.parse(value);
} else if ("Last-Modified".equalsIgnoreCase(fieldName)) {
lastModified = org.aoju.bus.http.Builder.parse(value);
lastModifiedString = value;
} else if ("ETag".equalsIgnoreCase(fieldName)) {
etag = value;
} else if ("Age".equalsIgnoreCase(fieldName)) {
ageSeconds = HttpHeaders.parseSeconds(value, -1);
}
}
}
}
/**
* 如果请求包含保存服务器不发送客户机本地响应的条件,则返回true
* 当请求按照自己的条件加入队列时,将不使用内置的响应缓存。
*
* @param request 氢气信息
* @return the true/false
*/
private static boolean hasConditions(Request request) {
return request.header(Header.IF_MODIFIED_SINCE) != null || request.header(Header.IF_NONE_MATCH) != null;
}
/**
* @return 使用缓存的响应{@code response}返回满足{@code request}的策略。
*/
public CacheStrategy get() {
CacheStrategy candidate = getCandidate();
if (candidate.networkRequest != null && request.cacheControl().onlyIfCached()) {
// 被禁止使用网络和缓存是不够的
return new CacheStrategy(null, null);
}
return candidate;
}
/**
* @return 如果请求可以使用网络,则返回要使用的策略
*/
private CacheStrategy getCandidate() {
//没有缓存的响应.
if (cacheResponse == null) {
return new CacheStrategy(request, null);
}
// 如果缺少必要的握手,则删除缓存的响应。
if (request.isHttps() && cacheResponse.handshake() == null) {
return new CacheStrategy(request, null);
}
// 如果不应该存储此响应,则不应该将其用作响应源。
// 只要持久性存储表现良好且规则不变,此检查就应该是多余的
if (!isCacheable(cacheResponse, request)) {
return new CacheStrategy(request, null);
}
CacheControl requestCaching = request.cacheControl();
if (requestCaching.noCache() || hasConditions(request)) {
return new CacheStrategy(request, null);
}
CacheControl responseCaching = cacheResponse.cacheControl();
long ageMillis = cacheResponseAge();
long freshMillis = computeFreshnessLifetime();
if (requestCaching.maxAgeSeconds() != -1) {
freshMillis = Math.min(freshMillis, TimeUnit.SECONDS.toMillis(requestCaching.maxAgeSeconds()));
}
long minFreshMillis = 0;
if (requestCaching.minFreshSeconds() != -1) {
minFreshMillis = TimeUnit.SECONDS.toMillis(requestCaching.minFreshSeconds());
}
long maxStaleMillis = 0;
if (!responseCaching.mustRevalidate() && requestCaching.maxStaleSeconds() != -1) {
maxStaleMillis = TimeUnit.SECONDS.toMillis(requestCaching.maxStaleSeconds());
}
if (!responseCaching.noCache() && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
Response.Builder builder = cacheResponse.newBuilder();
if (ageMillis + minFreshMillis >= freshMillis) {
builder.addHeader("Warning", "110 HttpURLConnection \"Response is stale\"");
}
long oneDayMillis = 24 * 60 * 60 * 1000L;
if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {
builder.addHeader("Warning", "113 HttpURLConnection \"Heuristic expiration\"");
}
return new CacheStrategy(null, builder.build());
}
// 查找要添加到请求的条件。如果条件满足,则不会传输响应体。
String conditionName;
String conditionValue;
if (etag != null) {
conditionName = "If-None-Match";
conditionValue = etag;
} else if (lastModified != null) {
conditionName = "If-Modified-Since";
conditionValue = lastModifiedString;
} else if (servedDate != null) {
conditionName = "If-Modified-Since";
conditionValue = servedDateString;
} else {
return new CacheStrategy(request, null);
}
Headers.Builder conditionalRequestHeaders = request.headers().newBuilder();
Builder.instance.addLenient(conditionalRequestHeaders, conditionName, conditionValue);
Request conditionalRequest = request.newBuilder()
.headers(conditionalRequestHeaders.build())
.build();
return new CacheStrategy(conditionalRequest, cacheResponse);
}
/**
* @return 响应刷新的毫秒数,从服务日期开始
*/
private long computeFreshnessLifetime() {
CacheControl responseCaching = cacheResponse.cacheControl();
if (responseCaching.maxAgeSeconds() != -1) {
return TimeUnit.SECONDS.toMillis(responseCaching.maxAgeSeconds());
} else if (expires != null) {
long servedMillis = servedDate != null
? servedDate.getTime()
: receivedResponseMillis;
long delta = expires.getTime() - servedMillis;
return delta > 0 ? delta : 0;
} else if (lastModified != null
&& cacheResponse.request().url().query() == null) {
// 根据HTTP RFC的建议并在Firefox中实现,
// 文档的最大值应该默认为其被提供时文档值的10%。
// 默认过期日期不用于包含查询的uri
long servedMillis = servedDate != null
? servedDate.getTime()
: sentRequestMillis;
long delta = servedMillis - lastModified.getTime();
return delta > 0 ? (delta / 10) : 0;
}
return 0;
}
/**
* @return 返回响应的当前值(以毫秒为单位)。计算按RFC 7234规定,4.2.3计算值
*/
private long cacheResponseAge() {
long apparentReceivedAge = servedDate != null
? Math.max(0, receivedResponseMillis - servedDate.getTime())
: 0;
long receivedAge = ageSeconds != -1
? Math.max(apparentReceivedAge, TimeUnit.SECONDS.toMillis(ageSeconds))
: apparentReceivedAge;
long responseDuration = receivedResponseMillis - sentRequestMillis;
long residentDuration = nowMillis - receivedResponseMillis;
return receivedAge + responseDuration + residentDuration;
}
/**
* @return 如果computeFreshnessLifetime使用了启发式,则返回true
* 如果我们使用启发式来服务大于24小时的缓存响应,则需要附加一个警告
*/
private boolean isFreshnessLifetimeHeuristic() {
return cacheResponse.cacheControl().maxAgeSeconds() == -1 && expires == null;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy