001: // SimpleCacheValidator.java
002: // $Id: SimpleCacheValidator.java,v 1.23 2003/02/20 17:32:18 ylafon Exp $
003: // (c) COPYRIGHT MIT, INRIA and Keio, 1999.
004: // Please first read the full copyright statement in file COPYRIGHT.htm
005:
006: package org.w3c.www.protocol.http.cache;
007:
008: import java.net.URL;
009:
010: import org.w3c.util.ArrayDictionary;
011: import org.w3c.www.http.HTTP;
012: import org.w3c.www.http.HttpEntityTag;
013: import org.w3c.www.protocol.http.Reply;
014: import org.w3c.www.protocol.http.Request;
015:
016: public class SimpleCacheValidator extends CacheValidator {
017: private static final boolean debug = false;
018:
019: /**
020: * Check if the request is stale or not
021: * @return a boolean, false if the resource is still valid
022: * true it if needs a revalidation.
023: */
024: public boolean checkStaleness(CachedResource cr) {
025: return (cr.getCurrentAge() >= cr.getFreshnessLifetime());
026: }
027:
028: /**
029: * Is the currently cached version usable to answer the given request ?
030: * @return A boolean, <strong>true</strong> if we are able to generate
031: * a valid answer to this request by the <code>perform</code> method,
032: * <strong>false</strong> otherwise (the resource needs to be refreshed).
033: */
034: public boolean isValid(CachedResource cr, Request request) {
035: EntityCachedResource rcr;
036: rcr = (EntityCachedResource) cr.lookupResource(request);
037: // no real resource, or it has been marked as invalid already
038: if ((rcr == null) || rcr.getWillRevalidate()) {
039: return false;
040: }
041: // RFC2616: 14.9.2 revalidation - end-to-end revalidation
042: if (request.getMaxAge() == 0) {
043: rcr.setWillRevalidate(true);
044: return false;
045: }
046: // RFC2616: 13.6 Check Vary header
047: String vary[] = cr.getVary();
048: if (vary != null) {
049: for (int i = 0; i < vary.length; i++) {
050: // always revalidate on *
051: if (vary[i].equals("*")) {
052: return false;
053: }
054: ArrayDictionary a = cr.getConnegHeaders();
055: String rh = null;
056: String crh = null;
057: String lowh = null;
058: // we have a header checked, but nothing in the conneg
059: // it must not happen, but it's not a good sign for validity
060: if (a == null) {
061: return false;
062: }
063: lowh = vary[i].toLowerCase();
064: crh = (String) a.get(lowh);
065: rh = request.getValue(vary[i]);
066: if (crh == null) {
067: if (rh == null) {
068: continue;
069: }
070: return false;
071: } else if (rh == null) {
072: return false;
073: }
074: if (!rh.equals(crh)) {
075: return false;
076: }
077: }
078: }
079: // RFC2616:14.9.3 Modification of basic expiration
080: int maxage = request.getMaxAge();
081: int minfresh = request.getMinFresh();
082: int maxstale = request.getMaxStale();
083: int currage = rcr.getCurrentAge();
084: int freshtime = rcr.getFreshnessLifetime();
085:
086: if (debug) {
087: System.out.println("* Maxage :" + maxage);
088: System.out.println("* MinFresh :" + minfresh);
089: System.out.println("* MaxStale :" + maxstale);
090: System.out.println("* CurrAge :" + currage);
091: System.out.println("* Freshtime :" + freshtime);
092: }
093: // RFC2616:14.9.3 maxage set, it overrides the freshness lifetime
094: if (maxage == -1) {
095: maxage = freshtime;
096: }
097: if (currage < maxage) {
098: // yeah! hum, not yet ;)
099: if (minfresh != -1) {
100: if (currage + minfresh > freshtime) {
101: // FIXME should we revalidate? avoid the cache?
102: return false;
103: }
104: }
105: // yes!
106: return true;
107: }
108: // if we have max-stale and min-fresh, we should kill someone ;)
109: if ((maxstale != -1) && (minfresh == -1)) {
110: if (currage < maxage + maxstale) {
111: // FIXME add a Warning 110 to the request there!
112: return true;
113: }
114: }
115: // we have max age, the resource is stale according to that
116: // so... revalidate!
117: return false;
118: }
119:
120: /**
121: * Update the expiration information on a cached resource, even if it was
122: * not used. Note that it is the right place to update also information
123: * for other cache behaviour used by the sweeper.
124: * @param cr, the CachedResource we are upgrading.
125: * @param request, the Request
126: * @param reply, the Reply
127: */
128: public void updateExpirationInfo(CachedResource cr,
129: Request request, Reply reply) {
130: // we update only "real" resources
131: if (!(cr instanceof EntityCachedResource))
132: return;
133: EntityCachedResource ecr = (EntityCachedResource) cr;
134:
135: // First, compute the initial age, the date and the response time.
136: int age_value; // s
137: long date_value; // ms
138: long request_time; // ms
139: long response_time; // ms
140: long now = System.currentTimeMillis(); // ms
141:
142: int apparent_age; // s
143: int corrected_received_age; // s
144: int response_delay; // s
145: int corrected_initial_age; // s
146:
147: // RFC2616: 13.2.3: Age Calculation
148: age_value = reply.getAge();
149: date_value = reply.getDate();
150: // update the cached resource date
151: ecr.setDate(date_value);
152: request_time = request.getEmitDate();
153: // small hack, as we cache directly, the time is the same
154: // If we were to use an external database or other things,
155: // we may implement it another way
156: response_time = now;
157: if (date_value == -1) {
158: // RFC2616: 14.18 the recipient MUST assign a date if not present
159: // We assume that the reply was generated instantly, it is
160: // then response_time - request_time, in our case... now
161: date_value = now / 2 + request_time / 2;
162: }
163:
164: apparent_age = (int) Math.max(0,
165: (response_time - date_value) / 1000);
166: corrected_received_age = Math.max(apparent_age, age_value);
167: response_delay = (int) ((response_time - request_time) / 1000);
168: corrected_initial_age = corrected_received_age + response_delay;
169:
170: ecr.setInitialAge(corrected_initial_age);
171: ecr.setResponseTime(response_time);
172: // Now check in the response all the cacheability information
173: // and uptade some flags directly
174:
175: // first calculate the freshness-lifetime
176: int freshness_lifetime = -1;
177: // if it is a shared cache and s-maxage is there it overrides
178: // everything
179: int s_maxage = reply.getSMaxAge();
180: if (filter.isShared() && (s_maxage != -1)) {
181: freshness_lifetime = s_maxage;
182: // RFC2616: 14.9.3: s-maxage implies proxy-revalidate
183: ecr.setRevalidate(true);
184: } else {
185: int maxage = reply.getMaxAge();
186: // no maxage, check the expire
187: if (maxage < 0) {
188: // get from the Expire now...
189: long expires = reply.getExpires();
190: if (expires > 0) {
191: freshness_lifetime = (int) Math.max(0,
192: (expires - date_value) / 1000);
193: if ((freshness_lifetime > 31536000)
194: && (reply.getMinorVersion() == 1)
195: && (reply.getMajorVersion() == 1)) {
196: // a HTTP/1.1 Cache sent an expire date more than one
197: // year in the future, it is invalid, as of
198: // RFC2616: 14.23 *sigh*
199: freshness_lifetime = 31536000;
200: }
201: } else if (reply.hasHeader(reply.H_EXPIRES)) {
202: // a bad Expires: header!
203: // RFC2616: 14.21, invalid expires, set it to 0!
204: freshness_lifetime = 0;
205: } else {
206: // no expires, no maxage, figure out a default value
207: if (reply.hasHeader(reply.H_LAST_MODIFIED)) {
208: // if we have a last_modified, try 10%
209: long last_mod = reply.getLastModified();
210: int difftime = (int) Math.max(0,
211: (now - last_mod) / 1000);
212: // ...but no more than one day is a good rule
213: freshness_lifetime = Math.min(86400,
214: difftime / 10);
215: } else {
216: // no last modified, no expires, no cache control...
217: // let's use a low default setting that allow people
218: // to share resources without caching too much, as
219: // there are many badly configured dynamic servers.
220: int cr_lifetime = ecr.getFreshnessLifetime();
221: if (cr_lifetime == -1) {
222: URL requrl = request.getURL();
223: if (requrl != null) {
224: String surl = requrl.toExternalForm();
225: if ((surl.indexOf('?') == -1)
226: && (surl.indexOf("cgi") == -1)) {
227: freshness_lifetime = 300;
228: } else {
229: // for cgi let's be safe and use 0
230: freshness_lifetime = 0;
231: }
232: }
233: } else {
234: freshness_lifetime = cr_lifetime;
235: }
236: }
237: }
238: } else {
239: freshness_lifetime = maxage;
240: }
241: }
242: // Yeah! we got the maxtime!
243: if (freshness_lifetime != -1) {
244: ecr.setFreshnessLifetime(freshness_lifetime);
245: }
246: // check the must/proxy-revalidate flags RCC2616: 14.9.4
247: if (reply.checkMustRevalidate()) {
248: ecr.setRevalidate(true);
249: } else if (filter.isShared() && reply.checkProxyRevalidate()) {
250: ecr.setRevalidate(true);
251: }
252: }
253:
254: private void checkConsistency(CachedResource cr, Request request,
255: Reply reply) {
256: // taken from Alex Rousskov HTTP compliance tests
257: // available at http://coad.measurement-factory.com/
258:
259: // we update only "real" resources
260: // if (!(cr instanceof EntityCachedResource))
261: // return;
262: // EntityCachedResource ecr = (EntityCachedResource) cr;
263: // test MD5, check only if MD5 is present in the reply
264: // or should it be done if !=, taking the first option for now
265: String cmd5 = cr.getContentMD5();
266: String rmd5 = reply.getContentMD5();
267: if (cmd5 == null) {
268: if (rmd5 != null) {
269: cr.setWillRevalidate(true);
270: }
271: } else {
272: if ((rmd5 != null) && (!rmd5.equals(cmd5))) {
273: cr.setWillRevalidate(true);
274: }
275: }
276: // check Content Length
277: int ccl = cr.getContentLength();
278: int rcl = reply.getContentLength();
279: if (rcl >= 0) {
280: if (ccl != rcl) {
281: cr.setWillRevalidate(true);
282: }
283: }
284: // check ETag
285: HttpEntityTag rtag = reply.getETag();
286: if (rtag != null) {
287: String retag = rtag.toString();
288: String cetag = cr.getETag();
289: if ((cetag == null) || !retag.equals(cetag)) {
290: cr.setWillRevalidate(true);
291: }
292: }
293: // check last modified
294: long rlmt = reply.getLastModified();
295: if (rlmt >= 0) {
296: long clmt = cr.getLastModified();
297: if (clmt != rlmt) {
298: cr.setWillRevalidate(true);
299: }
300: }
301: }
302:
303: /**
304: * reset all the ages after a revalidation
305: * @param cr, the CachedResource we are upgrading.
306: * @param request, the Request
307: * @param reply, the Reply
308: */
309: public void revalidateResource(CachedResource cr, Request request,
310: Reply reply) {
311:
312: cr.setWillRevalidate(false);
313: if (reply.getStatus() == HTTP.NOT_MODIFIED) {
314: updateExpirationInfo(cr, request, reply);
315: } else {
316: // do something good
317: }
318: checkConsistency(cr, request, reply);
319: }
320: }
|