1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package org.esigate.cache;
16
17 import java.io.IOException;
18 import java.util.Arrays;
19 import java.util.Date;
20 import java.util.Properties;
21
22 import org.apache.http.HttpException;
23 import org.apache.http.HttpStatus;
24 import org.apache.http.client.cache.CacheResponseStatus;
25 import org.apache.http.client.cache.HttpCacheContext;
26 import org.apache.http.client.methods.CloseableHttpResponse;
27 import org.apache.http.client.methods.HttpExecutionAware;
28 import org.apache.http.client.methods.HttpRequestWrapper;
29 import org.apache.http.client.protocol.HttpClientContext;
30 import org.apache.http.conn.routing.HttpRoute;
31 import org.apache.http.impl.execchain.ClientExecChain;
32 import org.esigate.ConfigurationException;
33 import org.esigate.Parameters;
34 import org.esigate.http.DateUtils;
35 import org.esigate.http.OutgoingRequestContext;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
38
39
40
41
42
43
44
45 public class CacheAdapter {
46 private static final Logger LOG = LoggerFactory.getLogger(CacheAdapter.class);
47 private int staleIfError;
48 private int staleWhileRevalidate;
49 private int ttl;
50 private boolean xCacheHeader;
51 private boolean viaHeader;
52
53
54
55
56
57
58
59 public void init(Properties properties) {
60 staleIfError = Parameters.STALE_IF_ERROR.getValue(properties);
61 staleWhileRevalidate = Parameters.STALE_WHILE_REVALIDATE.getValue(properties);
62 int maxAsynchronousWorkers = Parameters.MAX_ASYNCHRONOUS_WORKERS.getValue(properties);
63 if (staleWhileRevalidate > 0 && maxAsynchronousWorkers == 0) {
64 throw new ConfigurationException("You must set a positive value for maxAsynchronousWorkers "
65 + "in order to enable background revalidation (staleWhileRevalidate)");
66 }
67 ttl = Parameters.TTL.getValue(properties);
68 xCacheHeader = Parameters.X_CACHE_HEADER.getValue(properties);
69 viaHeader = Parameters.VIA_HEADER.getValue(properties);
70 LOG.info("Initializing cache for provider " + Arrays.toString(Parameters.REMOTE_URL_BASE.getValue(properties))
71 + " staleIfError=" + staleIfError + " staleWhileRevalidate=" + staleWhileRevalidate + " ttl=" + ttl
72 + " xCacheHeader=" + xCacheHeader + " viaHeader=" + viaHeader);
73 }
74
75 public ClientExecChain wrapCachingHttpClient(final ClientExecChain wrapped) {
76 return new ClientExecChain() {
77
78
79
80
81
82
83 @Override
84 public CloseableHttpResponse execute(HttpRoute route, HttpRequestWrapper request,
85 HttpClientContext httpClientContext, HttpExecutionAware execAware) throws IOException,
86 HttpException {
87 OutgoingRequestContext context = OutgoingRequestContext.adapt(httpClientContext);
88
89
90 CloseableHttpResponse response = wrapped.execute(route, request, context, execAware);
91
92
93 if (request.getRequestLine().getMethod().equalsIgnoreCase("GET")
94 && (staleWhileRevalidate > 0 || staleIfError > 0)) {
95 response.removeHeader(response.getLastHeader("Cache-control"));
96 }
97
98 if (xCacheHeader) {
99 if (context != null) {
100 CacheResponseStatus cacheResponseStatus =
101 (CacheResponseStatus) context.getAttribute(HttpCacheContext.CACHE_RESPONSE_STATUS);
102 String xCacheString;
103 if (cacheResponseStatus.equals(CacheResponseStatus.CACHE_HIT)) {
104 xCacheString = "HIT";
105 } else if (cacheResponseStatus.equals(CacheResponseStatus.VALIDATED)) {
106 xCacheString = "VALIDATED";
107 } else {
108 xCacheString = "MISS";
109 }
110 xCacheString += " from " + route.getTargetHost().toHostString();
111 xCacheString +=
112 " (" + request.getRequestLine().getMethod() + " " + request.getRequestLine().getUri()
113 + ")";
114 response.addHeader("X-Cache", xCacheString);
115 }
116 }
117
118
119 if (!viaHeader && response.containsHeader("Via")) {
120 response.removeHeaders("Via");
121 }
122 return response;
123 }
124 };
125 }
126
127 public ClientExecChain wrapBackendHttpClient(final ClientExecChain wrapped) {
128 return new ClientExecChain() {
129
130 private boolean isCacheableStatus(int statusCode) {
131 return (statusCode == HttpStatus.SC_OK || statusCode == HttpStatus.SC_MOVED_PERMANENTLY
132 || statusCode == HttpStatus.SC_MOVED_TEMPORARILY || statusCode == HttpStatus.SC_NOT_FOUND
133 || statusCode == HttpStatus.SC_INTERNAL_SERVER_ERROR
134 || statusCode == HttpStatus.SC_SERVICE_UNAVAILABLE || statusCode == HttpStatus.SC_NOT_MODIFIED || statusCode == HttpStatus.SC_GATEWAY_TIMEOUT);
135 }
136
137
138
139
140
141
142
143
144
145
146 @Override
147 public CloseableHttpResponse execute(HttpRoute route, HttpRequestWrapper request,
148 HttpClientContext httpClientContext, HttpExecutionAware execAware) throws IOException,
149 HttpException {
150 OutgoingRequestContext context = OutgoingRequestContext.adapt(httpClientContext);
151
152 CloseableHttpResponse response = wrapped.execute(route, request, context, execAware);
153
154 String method = request.getRequestLine().getMethod();
155 int statusCode = response.getStatusLine().getStatusCode();
156
157
158 if (ttl > 0 && method.equalsIgnoreCase("GET") && isCacheableStatus(statusCode)) {
159 response.removeHeaders("Date");
160 response.removeHeaders("Cache-control");
161 response.removeHeaders("Expires");
162 response.setHeader("Date", DateUtils.formatDate(new Date(System.currentTimeMillis())));
163 response.setHeader("Cache-control", "public, max-age=" + ttl);
164 response.setHeader("Expires",
165 DateUtils.formatDate(new Date(System.currentTimeMillis() + ((long) ttl) * 1000)));
166 }
167 if (request.getRequestLine().getMethod().equalsIgnoreCase("GET")) {
168 String cacheControlHeader = "";
169 if (staleWhileRevalidate > 0) {
170 cacheControlHeader += "stale-while-revalidate=" + staleWhileRevalidate;
171 }
172 if (staleIfError > 0) {
173 if (cacheControlHeader.length() > 0) {
174 cacheControlHeader += ",";
175 }
176 cacheControlHeader += "stale-if-error=" + staleIfError;
177 }
178 if (cacheControlHeader.length() > 0) {
179 response.addHeader("Cache-control", cacheControlHeader);
180 }
181 }
182
183 return response;
184 }
185
186 };
187 }
188 }