cacheRules.add(new CacheRule() {
public void onResourceFinish(NetworkResource resource, double timestamp, int refRecord) {
if (!isCacheableResourceType(getResourceType(resource))) {
return;
}
HeaderMap responseHeaders = resource.getResponseHeaders();
if (hasCookie(responseHeaders) != null) {
return;
}
if (isExplicitlyNonCacheable(
responseHeaders, resource.getUrl(), resource.getStatusCode())) {
return;
}
if (!hasExplicitExpiration(responseHeaders)) {
addHint(getHintletName(), timestamp, formatMessage(resource), refRecord,
HintRecord.SEVERITY_CRITICAL);
}
}
private String formatMessage(NetworkResource resource) {
return "The following resources are missing a cache expiration."
+ " Resources that do not specify an expiration may not be cached by"
+ " browsers. Specify an expiration at least one month in the future"
+ " for resources that should be cached, and an expiration in the past"
+ " for resources that should not be cached: " + resource.getUrl();
}
});
/*
* Vary Rule Internet Explorer does not cache any resources that are served with the Vary header
* and any fields but Accept-Encoding and User-Agent. To ensure these resources are cached by
* IE, make sure to strip out any other fields from the Vary header, or remove the Vary header
* altogether if possible
*/
cacheRules.add(new CacheRule() {
public void onResourceFinish(NetworkResource resource, double timestamp, int refRecord) {
HeaderMap responseHeaders = resource.getResponseHeaders();
String varyHeader = responseHeaders.get("Vary");
if (varyHeader == null) {
return;
}
if (!isCacheableResourceType(getResourceType(resource))) {
return;
}
if (!freshnessLifetimeGreaterThan(responseHeaders, 0)) {
return;
}
// We strip Accept-Encoding and User-Agent from the header, as well
// as separator characters (comma and space), and trigger a hintlet
// if there is anything left in the header.
varyHeader = removeValidVary(varyHeader);
if (varyHeader.length() > 0) {
addHint(getHintletName(), timestamp, formatMessage(resource), refRecord,
HintRecord.SEVERITY_CRITICAL);
}
}
private native String removeValidVary(String header) /*-{
header = header.replace(/User-Agent/gi, '');
header = header.replace(/Accept-Encoding/gi, '');
var patt = new RegExp('[, ]*', 'g');
header = header.replace(patt, '');
return header;
}-*/;
private String formatMessage(NetworkResource resource) {
return "The following resources specify a 'Vary' header that"
+ " disables caching in most versions of Internet Explorer. Fix or remove"
+ " the 'Vary' header for the following resources: " + resource.getUrl();
}
});
/*
* Freshness Rule Set Expires to a minimum of one month for cacheable (static) resource,
* preferably up to one year. TODO (sarahgsmith) : Not more than a year as this violates RFC
* guidelines. (We prefer Expires over Cache-Control: max-age because it is is more widely
* supported.)
*/
cacheRules.add(new CacheRule() {
public void onResourceFinish(NetworkResource resource, double timestamp, int refRecord) {
// must have cacheable resource type or be a favicon
if (!isCacheableResourceType(getResourceType(resource))) {
return;
}
HeaderMap responseHeaders = resource.getResponseHeaders();
if (hasCookie(responseHeaders) != null) {
return;
}
// must have a freshness lifetime which is greater than 0
if (!freshnessLifetimeGreaterThan(responseHeaders, 0)) {
return;
}
// if the freshness is less than a month, fire the hint
if (!freshnessLifetimeGreaterThan(responseHeaders, MS_IN_A_MONTH)) {
addHint(getHintletName(), timestamp, formatLessThanMonthMessage(resource), refRecord,
HintRecord.SEVERITY_WARNING);
return;
}
// if the freshness is more than a month but less than a year, fire info hint
if (!freshnessLifetimeGreaterThan(responseHeaders, MS_IN_A_MONTH * 11)) {
addHint(getHintletName(), timestamp, formatLessThanYearMessage(resource), refRecord,
HintRecord.SEVERITY_INFO);
}
}
private String formatLessThanMonthMessage(NetworkResource resource) {
return "The following cacheable resources have a short"
+ " freshness lifetime. Specify an expiration at least one month in the"
+ " future for the following resources: " + resource.getUrl();
}
private String formatLessThanYearMessage(NetworkResource resource) {
return "To further improve cache hit rate, specify an expiration"
+ " one year in the future for the following cacheable resources: " + resource.getUrl();
}
});
/*
* Favicon Rule Favicons should have an expiration at least one month in the future this is a
* separate rule since they do not fit under the "cacheable". Additionally, we want to fire the
* hint even if no freshness is specified, which the previous freshness rule looks for.
*/
cacheRules.add(new CacheRule() {
public void onResourceFinish(NetworkResource resource, double timestamp, int refRecord) {
// for rule to fire:
// must be of type FAVICON
if (getResourceType(resource) != WebInspectorType.FAVICON) {
return;
}
HeaderMap responseHeaders = resource.getResponseHeaders();
// must not set cookie
if (hasCookie(responseHeaders) != null) {
return;
}
// if it doesn't have a long enough freshness liftime, fire hint
if (!freshnessLifetimeGreaterThan(responseHeaders, MS_IN_A_MONTH)) {
addHint(getHintletName(), timestamp, formatMessage(resource), refRecord,
HintRecord.SEVERITY_WARNING);
}
}
private String formatMessage(NetworkResource resource) {
return "Favicons should have an expiration at least one month in the future: "
+ resource.getUrl();
}
});
/*
* Query Rule Most proxies, most notably Squid up through version 3.0, do not cache resources
* with a "?" in their URL even if a Cache-control: public header is present in the response. To
* enable proxy caching for these resources, remove query strings from references to static
* resources, and instead encode the parameters into the file names themselves.
*/
cacheRules.add(new CacheRule() {
public void onResourceFinish(NetworkResource resource, double timestamp, int refRecord) {
if (resource.getUrl().indexOf('?') == -1) {
return;
}
HeaderMap responseHeaders = resource.getResponseHeaders();
if (hasCookie(responseHeaders) != null) {
return;
}
if (isPubliclyCacheable(responseHeaders, resource.getUrl(), resource.getStatusCode())) {
addHint(getHintletName(), timestamp, formatMessage(resource), refRecord,
HintRecord.SEVERITY_WARNING);
}
}
private String formatMessage(NetworkResource resource) {
return "Resources with a '?' in the URL are not cached by most"
+ " proxy caching servers. Remove the query string and encode the"
+ " parameters into the URL for the following resources: " + resource.getUrl();
}
});
/**
* Public Rule and Proxy Bug Rule Enabling public caching in the HTTP headers for static
* resources allows the browser to download resources from a nearby proxy server rather than
* from a remote origin server. You use the Cache-control: public header to indicate that a
* resource can be cached by public web proxies in addition to the browser that issued the
* request. With some exceptions (described below), you should configure your web server to set
* this header to public for cacheable resources.
*
* Some public proxies have bugs that do not detect the presence of the Content-Encoding
* response header. This can result in compressed versions being delivered to client browsers
* that cannot properly decompress the files. Since these files should always be gzipped by your
* server, to ensure that the client can correctly read the files, do either of the following:
* (see Bug Rule) - Set the the Cache-Control header to private. This disables proxy caching
* altogether for these resources. If your application is multi-homed around the globe and
* relies less on proxy caches for user locality, this might be an appropriate setting. - Set
* the Vary: Accept-Encoding response header. This instructs the proxies to cache two versions
* of the resource: one compressed, and one uncompressed. The correct version of the resource is
* delivered based on the client request header. This is a good choice for applications that are
* singly homed and depend on public proxies for user locality.
*/
cacheRules.add(new CacheRule() {
public void onResourceFinish(NetworkResource resource, double timestamp, int refRecord) {
HeaderMap responseHeaders = resource.getResponseHeaders();
if (!isCacheableResourceType(getResourceType(resource))) {
return;
}
if (isExplicitlyNonCacheable(
responseHeaders, resource.getUrl(), resource.getStatusCode())) {
return;
}
if (hasCookie(responseHeaders) != null) {
return;
}
// "Some public proxies have bugs..." (see comment for rule)
if (isCompressed(responseHeaders)
&& !headerContains(responseHeaders, "Vary", "Accept-Encoding")) {
if (isPubliclyCacheable(responseHeaders, resource.getUrl(), resource.getStatusCode())) {
addHint(getHintletName(), timestamp, formatMessageBug(resource), refRecord,
HintRecord.SEVERITY_WARNING);
}
return;
}
if (headerContains(responseHeaders, "Cache-Control", "public")) {
return;
}
// consider making it publicly cacheable
addHint(getHintletName(), timestamp, formatMessagePublic(resource), refRecord,
HintRecord.SEVERITY_INFO);
}
private String formatMessagePublic(NetworkResource resource) {
return "Consider adding a 'Cache-Control: public' header to the" + " following resource: "
+ resource.getUrl();
}
private String formatMessageBug(NetworkResource resource) {
return "Due to a bug, some proxy caching servers do not detect the presence"
+ " of the Content-Encoding response header. This can result in compressed"
+ " versions being delivered to client browsers that cannot properly"
+ " decompress the files. Therefore, use either 'Cache-Control: private'"
+ " or 'Vary: Accept-Encoding' for the following resource: " + resource.getUrl();
}
});
/*
* Cookie Rule Files with Cookie headers should never be publicly cached. Don't enable proxy
* caching for resources that set cookies. Setting the header to public effectively shares
* resources among multiple users, which means that any cookies set for those resources are
* shared as well. While many proxies won't actually cache any resources with cookie headers
* set, it's better to avoid the risk altogether. Either set the Cache-Control header to private
* or serve these resources from a cookieless domain.
*/
cacheRules.add(new CacheRule() {
public void onResourceFinish(NetworkResource resource, double timestamp, int refRecord) {
HeaderMap responseHeaders = resource.getResponseHeaders();
if (hasCookie(responseHeaders) != null
&& isPubliclyCacheable(responseHeaders, resource.getUrl(), resource.getStatusCode())) {
addHint(getHintletName(), timestamp, formatMessage(resource), refRecord,
HintRecord.SEVERITY_CRITICAL);
}