SOLR-505 Give RequestHandlers the possiblity to suppress the generation of HTTP caching headers

git-svn-id: https://svn.apache.org/repos/asf/lucene/solr/trunk@659657 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Otis Gospodnetic 2008-05-23 20:55:48 +00:00
parent a0de6f7b9f
commit 5ba95af1bb
9 changed files with 173 additions and 32 deletions

View File

@ -79,6 +79,7 @@ public class MoreLikeThisHandler extends RequestHandlerBase
public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception
{
RequestHandlerUtils.addExperimentalFormatWarning( rsp );
rsp.setHttpCaching(true);
SolrParams params = req.getParams();
SolrIndexSearcher searcher = req.getSearcher();

View File

@ -263,6 +263,7 @@ public class SpellCheckerRequestHandler extends RequestHandlerBase implements So
@Override
public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp)
throws Exception {
rsp.setHttpCaching(true);
SolrParams p = req.getParams();
String words = p.get("q");
String cmd = p.get("cmd");

View File

@ -126,6 +126,8 @@ public class SearchHandler extends RequestHandlerBase implements SolrCoreAware
final RTimer timer = rb.isDebug() ? new RTimer() : null;
rsp.setHttpCaching(true);
if (timer == null) {
// non-debugging prepare phase
for( SearchComponent c : components ) {

View File

@ -72,6 +72,11 @@ public class SolrQueryResponse {
// error if this is set...
protected Exception err;
/**
* Should this response be tagged with HTTP caching headers?
*/
protected boolean httpCaching=false;
/***
// another way of returning an error
int errCode;
@ -199,5 +204,20 @@ public class SolrQueryResponse {
public NamedList getToLog() {
return toLog;
}
/**
* Enables or disables the emission of HTTP caching headers for this response.
* @param httpCaching true=emit caching headers, false otherwise
*/
public void setHttpCaching(boolean httpCaching) {
this.httpCaching=httpCaching;
}
/**
* Should this response emit HTTP caching headers?
* @return true=yes emit headers, false otherwise
*/
public boolean isHttpCaching() {
return this.httpCaching;
}
}

View File

@ -16,18 +16,68 @@
*/
package org.apache.solr.servlet;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.Date;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpMethodBase;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.httpclient.util.DateUtil;
/**
* A test case for the several HTTP cache headers emitted by Solr
*/
public class CacheHeaderTest extends CacheHeaderTestBase {
@Override public String getSolrConfigFilename() { return "solrconfig.xml"; }
@Override
public String getSolrConfigFilename() {
return "solrconfig.xml";
}
protected static final String FILENAME = "cacheheadertest.csv";
protected static final String CHARSET = "UTF-8";
protected static final String CONTENTS = "id\n100\n101\n102";
public void testCacheVetoHandler() throws Exception {
File f=makeFile(CONTENTS);
HttpMethodBase m=getUpdateMethod("GET");
m.setQueryString(new NameValuePair[] { new NameValuePair("stream.file",f.getCanonicalPath())});
getClient().executeMethod(m);
assertEquals(200, m.getStatusCode());
checkVetoHeaders(m);
}
public void testCacheVetoException() throws Exception {
HttpMethodBase m = getSelectMethod("GET");
// We force an exception from Solr. This should emit "no-cache" HTTP headers
m.setQueryString(new NameValuePair[] { new NameValuePair("q", "xyz:solr"),
new NameValuePair("qt", "standard") });
getClient().executeMethod(m);
assertFalse(m.getStatusCode() == 200);
checkVetoHeaders(m);
}
protected void checkVetoHeaders(HttpMethodBase m) throws Exception {
Header head = m.getResponseHeader("Cache-Control");
assertNotNull("We got no Cache-Control header", head);
assertEquals("no-cache, no-store", head.getValue());
head = m.getResponseHeader("Pragma");
assertNotNull("We got no Pragma header", head);
assertEquals("no-cache", head.getValue());
head = m.getResponseHeader("Expires");
assertNotNull("We got no Expires header", head);
Date d = DateUtil.parseDate(head.getValue());
assertTrue("We got no Expires header far in the past", System
.currentTimeMillis()
- d.getTime() > 100000);
}
protected void doLastModified(String method) throws Exception {
// We do a first request to get the last modified
// This must result in a 200 OK response
@ -162,8 +212,8 @@ public class CacheHeaderTest extends CacheHeaderTestBase {
Header head = m.getResponseHeader("Cache-Control");
assertNull("We got a cache-control header in response to POST", head);
head=m.getResponseHeader("Expires");
head = m.getResponseHeader("Expires");
assertNull("We got an Expires header in response to POST", head);
} else {
HttpMethodBase m = getSelectMethod(method);
@ -172,9 +222,26 @@ public class CacheHeaderTest extends CacheHeaderTestBase {
Header head = m.getResponseHeader("Cache-Control");
assertNotNull("We got no cache-control header", head);
head=m.getResponseHeader("Expires");
assertNotNull("We got no Expires header in response",head);
head = m.getResponseHeader("Expires");
assertNotNull("We got no Expires header in response", head);
}
}
protected File makeFile(String contents) {
return makeFile(contents, CHARSET);
}
protected File makeFile(String contents, String charset) {
try {
File f=new File(FILENAME);
Writer out = new OutputStreamWriter(new FileOutputStream(f),
charset);
out.write(contents);
out.close();
return f;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

View File

@ -93,6 +93,20 @@ public abstract class CacheHeaderTestBase extends SolrExampleTestBase {
return m;
}
protected HttpMethodBase getUpdateMethod(String method) {
HttpMethodBase m = null;
if ("GET".equals(method)) {
m=new GetMethod(server.getBaseURL()+"/update/csv");
} else if ("POST".equals(method)) {
m=new PostMethod(server.getBaseURL()+"/update/csv");
} else if ("HEAD".equals(method)) {
m=new HeadMethod(server.getBaseURL()+"/update/csv");
}
return m;
}
protected HttpClient getClient() {
return server.getHttpClient();
}

View File

@ -258,13 +258,11 @@ public class SolrDispatchFilter implements Filter
}
final Method reqMethod = Method.getMethod(req.getMethod());
if (Method.POST != reqMethod) {
HttpCacheHeaderUtil.setCacheControlHeader(config, resp);
}
HttpCacheHeaderUtil.setCacheControlHeader(config, resp, reqMethod);
// unless we have been explicitly told not to, do cache validation
// if we fail cache validation, execute the query
if (config.getHttpCachingConfig().isNever304() ||
!HttpCacheHeaderUtil.doCacheHeaderValidation(solrReq, req, resp)) {
!HttpCacheHeaderUtil.doCacheHeaderValidation(solrReq, req, reqMethod, resp)) {
SolrQueryResponse solrRsp = new SolrQueryResponse();
/* even for HEAD requests, we need to execute the handler to
* ensure we don't get an error (and to make sure the correct
@ -272,6 +270,7 @@ public class SolrDispatchFilter implements Filter
* Content-Type)
*/
this.execute( req, handler, solrReq, solrRsp );
HttpCacheHeaderUtil.checkHttpCachingVeto(solrRsp, resp, reqMethod);
// add info to http headers
//TODO: See SOLR-232 and SOLR-267.
/*try {
@ -289,7 +288,7 @@ public class SolrDispatchFilter implements Filter
// Now write it out
QueryResponseWriter responseWriter = core.getQueryResponseWriter(solrReq);
response.setContentType(responseWriter.getContentType(solrReq, solrRsp));
if (Method.HEAD != Method.getMethod(req.getMethod())) {
if (Method.HEAD != reqMethod) {
if (responseWriter instanceof BinaryQueryResponseWriter) {
BinaryQueryResponseWriter binWriter = (BinaryQueryResponseWriter) responseWriter;
binWriter.write(response.getOutputStream(), solrReq, solrRsp);

View File

@ -35,6 +35,7 @@ import org.apache.solr.common.SolrException;
import org.apache.solr.common.SolrException.ErrorCode;
import org.apache.solr.search.SolrIndexSearcher;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.request.SolrQueryResponse;
import org.apache.solr.request.SolrRequestHandler;
import org.apache.commons.codec.binary.Base64;
@ -167,10 +168,16 @@ public final class HttpCacheHeaderUtil {
/**
* Set the Cache-Control HTTP header (and Expires if needed)
* based on the SolrConfig.
* @param conf The config of the SolrCore handling this request
* @param resp The servlet response object to modify
* @param method The request method (GET, POST, ...) used by this request
*/
public static void setCacheControlHeader(final SolrConfig conf,
final HttpServletResponse resp) {
final HttpServletResponse resp, final Method method) {
// We do not emit HTTP header for POST and OTHER request types
if (Method.POST==method || Method.OTHER==method) {
return;
}
final String cc = conf.getHttpCachingConfig().getCacheControlHeader();
if (null != cc) {
resp.setHeader("Cache-Control", cc);
@ -202,10 +209,13 @@ public final class HttpCacheHeaderUtil {
*/
public static boolean doCacheHeaderValidation(final SolrQueryRequest solrReq,
final HttpServletRequest req,
final Method reqMethod,
final HttpServletResponse resp)
throws IOException {
final Method reqMethod=Method.getMethod(req.getMethod());
if (Method.POST==reqMethod || Method.OTHER==reqMethod) {
return false;
}
final long lastMod = HttpCacheHeaderUtil.calcLastModified(solrReq);
final String etag = HttpCacheHeaderUtil.calcEtag(solrReq);
@ -295,4 +305,42 @@ public final class HttpCacheHeaderUtil {
}
return false;
}
/**
* Checks if the downstream request handler wants to avoid HTTP caching of
* the response.
*
* @param solrRsp The Solr response object
* @param resp The HTTP servlet response object
* @param reqMethod The HTTP request type
*/
public static void checkHttpCachingVeto(final SolrQueryResponse solrRsp,
HttpServletResponse resp, final Method reqMethod) {
// For POST we do nothing. They never get cached
if (Method.POST == reqMethod || Method.OTHER == reqMethod) {
return;
}
// If the request handler has not vetoed and there is no
// exception silently return
if (solrRsp.isHttpCaching() && solrRsp.getException() == null) {
return;
}
// Otherwise we tell the caches that we don't want to cache the response
resp.setHeader("Cache-Control", "no-cache, no-store");
// For HTTP/1.0 proxy caches
resp.setHeader("Pragma", "no-cache");
// This sets the expiry date to a date in the past
// As long as no time machines get invented this is safe
resp.setHeader("Expires", "Sat, 01 Jan 2000 01:00:00 GMT");
// We signal "just modified" just in case some broken
// proxy cache does not follow the above headers
resp.setDateHeader("Last-Modified", System.currentTimeMillis());
// We override the ETag with something different
resp.setHeader("ETag", '"'+Long.toHexString(System.currentTimeMillis())+'"');
}
}

View File

@ -18,24 +18,13 @@
package org.apache.solr.servlet.cache;
public enum Method {
GET("GET"), POST("POST"), HEAD("HEAD"), OTHER("");
private final String method;
Method(String method) {
this.method = method.intern();
}
GET, POST, HEAD, OTHER;
public static Method getMethod(String method) {
method = method.toUpperCase().intern();
for (Method m : Method.values()) {
// we can use == because we interned the String objects
if (m.method==method) {
return m;
}
try {
return Method.valueOf(method.toUpperCase());
} catch (Exception e) {
return OTHER;
}
return OTHER;
}
}