You are viewing a plain text version of this content. The canonical link for it is here.
Posted to solr-commits@lucene.apache.org by ho...@apache.org on 2008/02/21 23:44:20 UTC
svn commit: r630037 - in /lucene/solr/trunk: ./
client/java/solrj/src/org/apache/solr/client/solrj/embedded/
example/solr/conf/ src/java/org/apache/solr/core/
src/java/org/apache/solr/search/ src/test/org/apache/solr/servlet/
src/test/test-files/solr/c...
Author: hossman
Date: Thu Feb 21 14:44:19 2008
New Revision: 630037
URL: http://svn.apache.org/viewvc?rev=630037&view=rev
Log:
SOLR-127: HTTP Caching awareness
Added:
lucene/solr/trunk/src/test/org/apache/solr/servlet/CacheHeaderTest.java (with props)
lucene/solr/trunk/src/test/org/apache/solr/servlet/CacheHeaderTestBase.java (with props)
lucene/solr/trunk/src/test/org/apache/solr/servlet/NoCacheHeaderTest.java (with props)
lucene/solr/trunk/src/test/test-files/solr/conf/solrconfig-nocache.xml (with props)
lucene/solr/trunk/src/webapp/src/org/apache/solr/servlet/cache/
lucene/solr/trunk/src/webapp/src/org/apache/solr/servlet/cache/HttpCacheHeaderUtil.java (with props)
lucene/solr/trunk/src/webapp/src/org/apache/solr/servlet/cache/Method.java (with props)
Modified:
lucene/solr/trunk/CHANGES.txt
lucene/solr/trunk/client/java/solrj/src/org/apache/solr/client/solrj/embedded/JettySolrRunner.java
lucene/solr/trunk/example/solr/conf/solrconfig.xml
lucene/solr/trunk/src/java/org/apache/solr/core/SolrConfig.java
lucene/solr/trunk/src/java/org/apache/solr/search/SolrIndexSearcher.java
lucene/solr/trunk/src/test/test-files/solr/conf/solrconfig.xml
lucene/solr/trunk/src/webapp/src/org/apache/solr/servlet/SolrDispatchFilter.java
lucene/solr/trunk/src/webapp/src/org/apache/solr/servlet/SolrRequestParsers.java
Modified: lucene/solr/trunk/CHANGES.txt
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/CHANGES.txt?rev=630037&r1=630036&r2=630037&view=diff
==============================================================================
--- lucene/solr/trunk/CHANGES.txt (original)
+++ lucene/solr/trunk/CHANGES.txt Thu Feb 21 14:44:19 2008
@@ -191,6 +191,12 @@
(ryan)
38. SOLR-478: Added ability to get back unique key information from the LukeRequestHandler. (gsingers)
+
+39. SOLR-127: HTTP Caching awareness. Solr now recognizes HTTP Request
+ headers related to HTTP Caching (see RFC 2616 sec13) and will respond
+ with "304 Not Modified" when appropriate. New options have been added
+ to solrconfig.xml to influence this behavior.
+ (Thomas Peuss via hossman)
Changes in runtime behavior
Modified: lucene/solr/trunk/client/java/solrj/src/org/apache/solr/client/solrj/embedded/JettySolrRunner.java
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/client/java/solrj/src/org/apache/solr/client/solrj/embedded/JettySolrRunner.java?rev=630037&r1=630036&r2=630037&view=diff
==============================================================================
--- lucene/solr/trunk/client/java/solrj/src/org/apache/solr/client/solrj/embedded/JettySolrRunner.java (original)
+++ lucene/solr/trunk/client/java/solrj/src/org/apache/solr/client/solrj/embedded/JettySolrRunner.java Thu Feb 21 14:44:19 2008
@@ -45,6 +45,12 @@
{
this.init( context, port );
}
+
+ public JettySolrRunner( String context, int port, String solrConfigFilename )
+ {
+ this.init( context, port );
+ dispatchFilter.setInitParameter("solrconfig-filename", solrConfigFilename);
+ }
// public JettySolrRunner( String context, String home, String dataDir, int port, boolean log )
// {
@@ -88,6 +94,7 @@
{
if( server.isRunning() ) {
server.stop();
+ server.join();
}
}
Modified: lucene/solr/trunk/example/solr/conf/solrconfig.xml
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/example/solr/conf/solrconfig.xml?rev=630037&r1=630036&r2=630037&view=diff
==============================================================================
--- lucene/solr/trunk/example/solr/conf/solrconfig.xml (original)
+++ lucene/solr/trunk/example/solr/conf/solrconfig.xml Thu Feb 21 14:44:19 2008
@@ -264,6 +264,42 @@
<requestDispatcher handleSelect="true" >
<!--Make sure your system has some authentication before enabling remote streaming! -->
<requestParsers enableRemoteStreaming="false" multipartUploadLimitInKB="2048" />
+
+ <!-- Set HTTP caching related parameters (for proxy caches and clients).
+
+ To get the behaviour of Solr 1.2 (ie: no caching related headers)
+ use the never304="true" option and do not specify a value for
+ <cacheControl>
+ -->
+ <!-- <httpCaching never304="true"> -->
+ <httpCaching lastModifiedFrom="openTime"
+ etagSeed="Solr">
+ <!-- lastModFrom="openTime" is the default, the Last-Modified value
+ (and validation against If-Modified-Since requests) will all be
+ relative to when the current Searcher was opened.
+ You can change it to lastModFrom="dirLastMod" if you want the
+ value to exactly corrispond to when the physical index was last
+ modified.
+
+ etagSeed="..." is an option you can change to force the ETag
+ header (and validation against If-None-Match requests) to be
+ differnet even if the index has not changed (ie: when making
+ significant changes to your config file)
+
+ lastModifiedFrom and etagSeed are both ignored if you use the
+ never304="true" option.
+ -->
+ <!-- If you include a <cacheControl> directive, it will be used to
+ generate a Cache-Control header, as well as an Expires header
+ if the value contains "max-age="
+
+ By default, no Cache-Control header is generated.
+
+ You can use the <cacheControl> option even if you have set
+ never304="true"
+ -->
+ <!-- <cacheControl>max-age=30, public</cacheControl> -->
+ </httpCaching>
</requestDispatcher>
Modified: lucene/solr/trunk/src/java/org/apache/solr/core/SolrConfig.java
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/java/org/apache/solr/core/SolrConfig.java?rev=630037&r1=630036&r2=630037&view=diff
==============================================================================
--- lucene/solr/trunk/src/java/org/apache/solr/core/SolrConfig.java (original)
+++ lucene/solr/trunk/src/java/org/apache/solr/core/SolrConfig.java Thu Feb 21 14:44:19 2008
@@ -33,6 +33,10 @@
import java.util.Collection;
import java.util.HashSet;
import java.util.StringTokenizer;
+import java.util.logging.Logger;
+import java.util.logging.Level;
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
import java.io.IOException;
import java.io.InputStream;
@@ -117,6 +121,8 @@
hashDocSetMaxSize= getInt("//HashDocSet/@maxSize",3000);
pingQueryParams = readPingQueryParams(this);
+
+ httpCachingConfig = new HttpCachingConfig(this);
Config.log.info("Loaded SolrConfig: " + file);
// TODO -- at solr 2.0. this should go away
@@ -146,6 +152,11 @@
public final SolrIndexConfig defaultIndexConfig;
public final SolrIndexConfig mainIndexConfig;
+ private final HttpCachingConfig httpCachingConfig;
+ public HttpCachingConfig getHttpCachingConfig() {
+ return httpCachingConfig;
+ }
+
// ping query request parameters
@Deprecated
private final NamedList pingQueryParams;
@@ -174,5 +185,77 @@
@Deprecated
public SolrQueryRequest getPingQueryRequest(SolrCore core) {
return new LocalSolrQueryRequest(core, pingQueryParams);
+ }
+
+
+ public static class HttpCachingConfig {
+
+ /** config xpath prefix for getting HTTP Caching options */
+ private final static String CACHE_PRE
+ = "requestDispatcher/httpCaching/";
+
+ /** For extracting Expires "ttl" from <cacheControl> config */
+ private final static Pattern MAX_AGE
+ = Pattern.compile("\\bmax-age=(\\d+)");
+
+ public static enum LastModFrom {
+ OPENTIME, DIRLASTMOD, BOGUS;
+
+ /** Input must not be null */
+ public static LastModFrom parse(final String s) {
+ try {
+ return valueOf(s.toUpperCase());
+ } catch (Exception e) {
+ log.log(Level.WARNING,
+ "Unrecognized value for lastModFrom: " + s, e);
+ return BOGUS;
+ }
+ }
+ }
+
+ private final boolean never304;
+ private final String etagSeed;
+ private final String cacheControlHeader;
+ private final Integer maxAge;
+ private final LastModFrom lastModFrom;
+
+ private HttpCachingConfig(SolrConfig conf) {
+
+ never304 = conf.getBool(CACHE_PRE+"@never304", false);
+
+ etagSeed = conf.get(CACHE_PRE+"@etagSeed", "Solr");
+
+
+ lastModFrom = LastModFrom.parse(conf.get(CACHE_PRE+"@lastModFrom",
+ "openTime"));
+
+ cacheControlHeader = conf.get(CACHE_PRE+"cacheControl",null);
+
+ Integer tmp = null; // maxAge
+ if (null != cacheControlHeader) {
+ try {
+ final Matcher ttlMatcher = MAX_AGE.matcher(cacheControlHeader);
+ final String ttlStr = ttlMatcher.find() ? ttlMatcher.group(1) : null;
+ tmp = (null != ttlStr && !"".equals(ttlStr))
+ ? Integer.valueOf(ttlStr)
+ : null;
+ } catch (Exception e) {
+ log.log(Level.WARNING,
+ "Ignoring exception while attempting to " +
+ "extract max-age from cacheControl config: " +
+ cacheControlHeader, e);
+ }
+ }
+ maxAge = tmp;
+
+ }
+
+ public boolean isNever304() { return never304; }
+ public String getEtagSeed() { return etagSeed; }
+ /** null if no Cache-Control header */
+ public String getCacheControlHeader() { return cacheControlHeader; }
+ /** null if no max age limitation */
+ public Integer getMaxAge() { return maxAge; }
+ public LastModFrom getLastModFrom() { return lastModFrom; }
}
}
Modified: lucene/solr/trunk/src/java/org/apache/solr/search/SolrIndexSearcher.java
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/java/org/apache/solr/search/SolrIndexSearcher.java?rev=630037&r1=630036&r2=630037&view=diff
==============================================================================
--- lucene/solr/trunk/src/java/org/apache/solr/search/SolrIndexSearcher.java (original)
+++ lucene/solr/trunk/src/java/org/apache/solr/search/SolrIndexSearcher.java Thu Feb 21 14:44:19 2008
@@ -1395,7 +1395,6 @@
}
}
-
/**
* return the named generic cache
*/
@@ -1419,6 +1418,9 @@
return cache==null ? null : cache.put(key,val);
}
+ public long getOpenTime() {
+ return openTime;
+ }
/////////////////////////////////////////////////////////////////////
// SolrInfoMBean stuff: Statistics and Module Info
Added: lucene/solr/trunk/src/test/org/apache/solr/servlet/CacheHeaderTest.java
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/test/org/apache/solr/servlet/CacheHeaderTest.java?rev=630037&view=auto
==============================================================================
--- lucene/solr/trunk/src/test/org/apache/solr/servlet/CacheHeaderTest.java (added)
+++ lucene/solr/trunk/src/test/org/apache/solr/servlet/CacheHeaderTest.java Thu Feb 21 14:44:19 2008
@@ -0,0 +1,180 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.servlet;
+
+import java.util.Date;
+
+import org.apache.commons.httpclient.Header;
+import org.apache.commons.httpclient.HttpMethodBase;
+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"; }
+
+ 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
+ HttpMethodBase get = getSelectMethod(method);
+ getClient().executeMethod(get);
+ checkResponseBody(method, get);
+
+ assertEquals("Got no response code 200 in initial request", 200, get
+ .getStatusCode());
+
+ Header head = get.getResponseHeader("Last-Modified");
+ assertNotNull("We got no Last-Modified header", head);
+
+ Date lastModified = DateUtil.parseDate(head.getValue());
+
+ // If-Modified-Since tests
+ get = getSelectMethod(method);
+ get.addRequestHeader("If-Modified-Since", DateUtil.formatDate(new Date()));
+
+ getClient().executeMethod(get);
+ checkResponseBody(method, get);
+ assertEquals("Expected 304 NotModified response with current date", 304,
+ get.getStatusCode());
+
+ get = getSelectMethod(method);
+ get.addRequestHeader("If-Modified-Since", DateUtil.formatDate(new Date(
+ lastModified.getTime() - 10000)));
+ getClient().executeMethod(get);
+ checkResponseBody(method, get);
+ assertEquals("Expected 200 OK response with If-Modified-Since in the past",
+ 200, get.getStatusCode());
+
+ // If-Unmodified-Since tests
+ get = getSelectMethod(method);
+ get.addRequestHeader("If-Unmodified-Since", DateUtil.formatDate(new Date(
+ lastModified.getTime() - 10000)));
+
+ getClient().executeMethod(get);
+ checkResponseBody(method, get);
+ assertEquals(
+ "Expected 412 Precondition failed with If-Unmodified-Since in the past",
+ 412, get.getStatusCode());
+
+ get = getSelectMethod(method);
+ get
+ .addRequestHeader("If-Unmodified-Since", DateUtil
+ .formatDate(new Date()));
+ getClient().executeMethod(get);
+ checkResponseBody(method, get);
+ assertEquals(
+ "Expected 200 OK response with If-Unmodified-Since and current date",
+ 200, get.getStatusCode());
+ }
+
+ // test ETag
+ protected void doETag(String method) throws Exception {
+ HttpMethodBase get = getSelectMethod(method);
+ getClient().executeMethod(get);
+ checkResponseBody(method, get);
+
+ assertEquals("Got no response code 200 in initial request", 200, get
+ .getStatusCode());
+
+ Header head = get.getResponseHeader("ETag");
+ assertNotNull("We got no ETag in the response", head);
+ assertTrue("Not a valid ETag", head.getValue().startsWith("\"")
+ && head.getValue().endsWith("\""));
+
+ String etag = head.getValue();
+
+ // If-None-Match tests
+ // we set a non matching ETag
+ get = getSelectMethod(method);
+ get.addRequestHeader("If-None-Match", "\"xyz123456\"");
+ getClient().executeMethod(get);
+ checkResponseBody(method, get);
+ assertEquals(
+ "If-None-Match: Got no response code 200 in response to non matching ETag",
+ 200, get.getStatusCode());
+
+ // now we set matching ETags
+ get = getSelectMethod(method);
+ get.addRequestHeader("If-None-Match", "\"xyz1223\"");
+ get.addRequestHeader("If-None-Match", "\"1231323423\", \"1211211\", "
+ + etag);
+ getClient().executeMethod(get);
+ checkResponseBody(method, get);
+ assertEquals("If-None-Match: Got no response 304 to matching ETag", 304,
+ get.getStatusCode());
+
+ // we now set the special star ETag
+ get = getSelectMethod(method);
+ get.addRequestHeader("If-None-Match", "*");
+ getClient().executeMethod(get);
+ checkResponseBody(method, get);
+ assertEquals("If-None-Match: Got no response 304 for star ETag", 304, get
+ .getStatusCode());
+
+ // If-Match tests
+ // we set a non matching ETag
+ get = getSelectMethod(method);
+ get.addRequestHeader("If-Match", "\"xyz123456\"");
+ getClient().executeMethod(get);
+ checkResponseBody(method, get);
+ assertEquals(
+ "If-Match: Got no response code 412 in response to non matching ETag",
+ 412, get.getStatusCode());
+
+ // now we set matching ETags
+ get = getSelectMethod(method);
+ get.addRequestHeader("If-Match", "\"xyz1223\"");
+ get.addRequestHeader("If-Match", "\"1231323423\", \"1211211\", " + etag);
+ getClient().executeMethod(get);
+ checkResponseBody(method, get);
+ assertEquals("If-Match: Got no response 200 to matching ETag", 200, get
+ .getStatusCode());
+
+ // now we set the special star ETag
+ get = getSelectMethod(method);
+ get.addRequestHeader("If-Match", "*");
+ getClient().executeMethod(get);
+ checkResponseBody(method, get);
+ assertEquals("If-Match: Got no response 200 to star ETag", 200, get
+ .getStatusCode());
+ }
+
+ protected void doCacheControl(String method) throws Exception {
+ if ("POST".equals(method)) {
+ HttpMethodBase m = getSelectMethod(method);
+ getClient().executeMethod(m);
+ checkResponseBody(method, m);
+
+ Header head = m.getResponseHeader("Cache-Control");
+ assertNull("We got a cache-control header in response to POST", head);
+
+ head=m.getResponseHeader("Expires");
+ assertNull("We got an Expires header in response to POST", head);
+ } else {
+ HttpMethodBase m = getSelectMethod(method);
+ getClient().executeMethod(m);
+ checkResponseBody(method, m);
+
+ 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);
+ }
+ }
+}
Propchange: lucene/solr/trunk/src/test/org/apache/solr/servlet/CacheHeaderTest.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: lucene/solr/trunk/src/test/org/apache/solr/servlet/CacheHeaderTest.java
------------------------------------------------------------------------------
svn:keywords = Date Author Id Revision HeadURL
Added: lucene/solr/trunk/src/test/org/apache/solr/servlet/CacheHeaderTestBase.java
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/test/org/apache/solr/servlet/CacheHeaderTestBase.java?rev=630037&view=auto
==============================================================================
--- lucene/solr/trunk/src/test/org/apache/solr/servlet/CacheHeaderTestBase.java (added)
+++ lucene/solr/trunk/src/test/org/apache/solr/servlet/CacheHeaderTestBase.java Thu Feb 21 14:44:19 2008
@@ -0,0 +1,148 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.servlet;
+
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.commons.httpclient.HttpMethodBase;
+import org.apache.commons.httpclient.NameValuePair;
+import org.apache.commons.httpclient.methods.GetMethod;
+import org.apache.commons.httpclient.methods.HeadMethod;
+import org.apache.commons.httpclient.methods.PostMethod;
+import org.apache.solr.client.solrj.SolrExampleTestBase;
+import org.apache.solr.client.solrj.SolrServer;
+import org.apache.solr.client.solrj.embedded.JettySolrRunner;
+import org.apache.solr.client.solrj.impl.CommonsHttpSolrServer;
+
+public abstract class CacheHeaderTestBase extends SolrExampleTestBase {
+ @Override public String getSolrHome() { return "solr/"; }
+
+ abstract public String getSolrConfigFilename();
+
+ public String getSolrConfigFile() { return getSolrHome()+"conf/"+getSolrConfigFilename(); }
+
+ CommonsHttpSolrServer server;
+
+ JettySolrRunner jetty;
+
+ static final int port = 8985; // not 8983
+
+ static final String context = "/example";
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ jetty = new JettySolrRunner(context, port, getSolrConfigFilename());
+ jetty.start();
+
+ server = this.createNewSolrServer();
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ super.tearDown();
+ jetty.stop(); // stop the server
+ }
+
+ @Override
+ protected SolrServer getSolrServer() {
+ return server;
+ }
+
+ @Override
+ protected CommonsHttpSolrServer createNewSolrServer() {
+ try {
+ // setup the server...
+ String url = "http://localhost:" + port + context;
+ CommonsHttpSolrServer s = new CommonsHttpSolrServer(url);
+ s.setConnectionTimeout(5);
+ s.setDefaultMaxConnectionsPerHost(100);
+ s.setMaxTotalConnections(100);
+ return s;
+ } catch (Exception ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ protected HttpMethodBase getSelectMethod(String method) {
+ HttpMethodBase m = null;
+ if ("GET".equals(method)) {
+ m = new GetMethod(server.getBaseURL() + "/select");
+ } else if ("HEAD".equals(method)) {
+ m = new HeadMethod(server.getBaseURL() + "/select");
+ } else if ("POST".equals(method)) {
+ m = new PostMethod(server.getBaseURL() + "/select");
+ }
+ m.setQueryString(new NameValuePair[] { new NameValuePair("q", "solr"),
+ new NameValuePair("qt", "standard") });
+ return m;
+ }
+
+ protected HttpClient getClient() {
+ return server.getHttpClient();
+ }
+
+ protected void checkResponseBody(String method, HttpMethodBase resp)
+ throws Exception {
+ String responseBody = resp.getResponseBodyAsString();
+ if ("GET".equals(method)) {
+ switch (resp.getStatusCode()) {
+ case 200:
+ assertTrue("Response body was empty for method " + method,
+ responseBody != null && responseBody.length() > 0);
+ break;
+ case 304:
+ assertTrue("Response body was not empty for method " + method,
+ responseBody == null || responseBody.length() == 0);
+ break;
+ case 412:
+ assertTrue("Response body was not empty for method " + method,
+ responseBody == null || responseBody.length() == 0);
+ break;
+ default:
+ System.err.println(responseBody);
+ assertEquals("Unknown request response", 0, resp.getStatusCode());
+ }
+ }
+ if ("HEAD".equals(method)) {
+ assertTrue("Response body was not empty for method " + method,
+ responseBody == null || responseBody.length() == 0);
+ }
+ }
+
+ // The tests
+ public void testLastModified() throws Exception {
+ doLastModified("GET");
+ doLastModified("HEAD");
+ }
+
+ public void testEtag() throws Exception {
+ doETag("GET");
+ doETag("HEAD");
+ }
+
+ public void testCacheControl() throws Exception {
+ doCacheControl("GET");
+ doCacheControl("HEAD");
+ doCacheControl("POST");
+ }
+
+ protected abstract void doCacheControl(String method) throws Exception;
+ protected abstract void doETag(String method) throws Exception;
+ protected abstract void doLastModified(String method) throws Exception;
+
+}
Propchange: lucene/solr/trunk/src/test/org/apache/solr/servlet/CacheHeaderTestBase.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: lucene/solr/trunk/src/test/org/apache/solr/servlet/CacheHeaderTestBase.java
------------------------------------------------------------------------------
svn:keywords = Date Author Id Revision HeadURL
Added: lucene/solr/trunk/src/test/org/apache/solr/servlet/NoCacheHeaderTest.java
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/test/org/apache/solr/servlet/NoCacheHeaderTest.java?rev=630037&view=auto
==============================================================================
--- lucene/solr/trunk/src/test/org/apache/solr/servlet/NoCacheHeaderTest.java (added)
+++ lucene/solr/trunk/src/test/org/apache/solr/servlet/NoCacheHeaderTest.java Thu Feb 21 14:44:19 2008
@@ -0,0 +1,158 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.solr.servlet;
+
+import java.util.Date;
+
+import org.apache.commons.httpclient.Header;
+import org.apache.commons.httpclient.HttpMethodBase;
+import org.apache.commons.httpclient.util.DateUtil;
+
+/**
+ * A test case for the several HTTP cache headers emitted by Solr
+ */
+public class NoCacheHeaderTest extends CacheHeaderTestBase {
+ @Override public String getSolrConfigFilename() { return "solrconfig-nocache.xml"; }
+
+ // The tests
+ public void testLastModified() throws Exception {
+ doLastModified("GET");
+ doLastModified("HEAD");
+ }
+
+ public void testEtag() throws Exception {
+ doETag("GET");
+ doETag("HEAD");
+ }
+
+ public void testCacheControl() throws Exception {
+ doCacheControl("GET");
+ doCacheControl("HEAD");
+ doCacheControl("POST");
+ }
+
+ 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
+ HttpMethodBase get = getSelectMethod(method);
+ getClient().executeMethod(get);
+ checkResponseBody(method, get);
+
+ assertEquals("Got no response code 200 in initial request", 200, get
+ .getStatusCode());
+
+ Header head = get.getResponseHeader("Last-Modified");
+ assertNull("We got a Last-Modified header", head);
+
+ // If-Modified-Since tests
+ get = getSelectMethod(method);
+ get.addRequestHeader("If-Modified-Since", DateUtil.formatDate(new Date()));
+
+ getClient().executeMethod(get);
+ checkResponseBody(method, get);
+ assertEquals("Expected 200 with If-Modified-Since header. We should never get a 304 here", 200,
+ get.getStatusCode());
+
+ get = getSelectMethod(method);
+ get.addRequestHeader("If-Modified-Since", DateUtil.formatDate(new Date(System.currentTimeMillis()-10000)));
+ getClient().executeMethod(get);
+ checkResponseBody(method, get);
+ assertEquals("Expected 200 with If-Modified-Since header. We should never get a 304 here",
+ 200, get.getStatusCode());
+
+ // If-Unmodified-Since tests
+ get = getSelectMethod(method);
+ get.addRequestHeader("If-Unmodified-Since", DateUtil.formatDate(new Date(System.currentTimeMillis()-10000)));
+
+ getClient().executeMethod(get);
+ checkResponseBody(method, get);
+ assertEquals(
+ "Expected 200 with If-Unmodified-Since header. We should never get a 304 here",
+ 200, get.getStatusCode());
+
+ get = getSelectMethod(method);
+ get
+ .addRequestHeader("If-Unmodified-Since", DateUtil
+ .formatDate(new Date()));
+ getClient().executeMethod(get);
+ checkResponseBody(method, get);
+ assertEquals(
+ "Expected 200 with If-Unmodified-Since header. We should never get a 304 here",
+ 200, get.getStatusCode());
+ }
+
+ // test ETag
+ protected void doETag(String method) throws Exception {
+ HttpMethodBase get = getSelectMethod(method);
+ getClient().executeMethod(get);
+ checkResponseBody(method, get);
+
+ assertEquals("Got no response code 200 in initial request", 200, get
+ .getStatusCode());
+
+ Header head = get.getResponseHeader("ETag");
+ assertNull("We got an ETag in the response", head);
+
+ // If-None-Match tests
+ // we set a non matching ETag
+ get = getSelectMethod(method);
+ get.addRequestHeader("If-None-Match", "\"xyz123456\"");
+ getClient().executeMethod(get);
+ checkResponseBody(method, get);
+ assertEquals(
+ "If-None-Match: Got no response code 200 in response to non matching ETag",
+ 200, get.getStatusCode());
+
+ // we now set the special star ETag
+ get = getSelectMethod(method);
+ get.addRequestHeader("If-None-Match", "*");
+ getClient().executeMethod(get);
+ checkResponseBody(method, get);
+ assertEquals("If-None-Match: Got no response 200 for star ETag", 200, get
+ .getStatusCode());
+
+ // If-Match tests
+ // we set a non matching ETag
+ get = getSelectMethod(method);
+ get.addRequestHeader("If-Match", "\"xyz123456\"");
+ getClient().executeMethod(get);
+ checkResponseBody(method, get);
+ assertEquals(
+ "If-Match: Got no response code 200 in response to non matching ETag",
+ 200, get.getStatusCode());
+
+ // now we set the special star ETag
+ get = getSelectMethod(method);
+ get.addRequestHeader("If-Match", "*");
+ getClient().executeMethod(get);
+ checkResponseBody(method, get);
+ assertEquals("If-Match: Got no response 200 to star ETag", 200, get
+ .getStatusCode());
+ }
+
+ protected void doCacheControl(String method) throws Exception {
+ HttpMethodBase m = getSelectMethod(method);
+ getClient().executeMethod(m);
+ checkResponseBody(method, m);
+
+ Header head = m.getResponseHeader("Cache-Control");
+ assertNull("We got a cache-control header in response", head);
+
+ head = m.getResponseHeader("Expires");
+ assertNull("We got an Expires header in response", head);
+ }
+}
\ No newline at end of file
Propchange: lucene/solr/trunk/src/test/org/apache/solr/servlet/NoCacheHeaderTest.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: lucene/solr/trunk/src/test/org/apache/solr/servlet/NoCacheHeaderTest.java
------------------------------------------------------------------------------
svn:keywords = Date Author Id Revision HeadURL
Added: lucene/solr/trunk/src/test/test-files/solr/conf/solrconfig-nocache.xml
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/test/test-files/solr/conf/solrconfig-nocache.xml?rev=630037&view=auto
==============================================================================
--- lucene/solr/trunk/src/test/test-files/solr/conf/solrconfig-nocache.xml (added)
+++ lucene/solr/trunk/src/test/test-files/solr/conf/solrconfig-nocache.xml Thu Feb 21 14:44:19 2008
@@ -0,0 +1,324 @@
+<?xml version="1.0" ?>
+
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- $Id$
+ $Source$
+ $Name$
+ -->
+
+<config>
+
+ <!-- Used to specify an alternate directory to hold all index data.
+ It defaults to "index" if not present, and should probably
+ not be changed if replication is in use. -->
+ <!--
+ <indexDir>index</indexDir>
+ -->
+
+ <indexDefaults>
+ <!-- Values here affect all index writers and act as a default
+ unless overridden. -->
+ <useCompoundFile>false</useCompoundFile>
+ <mergeFactor>10</mergeFactor>
+ <maxBufferedDocs>1000</maxBufferedDocs>
+ <maxMergeDocs>2147483647</maxMergeDocs>
+ <maxFieldLength>10000</maxFieldLength>
+
+ <!-- these are global... can't currently override per index -->
+ <writeLockTimeout>1000</writeLockTimeout>
+ <commitLockTimeout>10000</commitLockTimeout>
+
+ <lockType>single</lockType>
+ </indexDefaults>
+
+ <mainIndex>
+ <!-- lucene options specific to the main on-disk lucene index -->
+ <useCompoundFile>false</useCompoundFile>
+ <mergeFactor>10</mergeFactor>
+ <maxBufferedDocs>1000</maxBufferedDocs>
+ <maxMergeDocs>2147483647</maxMergeDocs>
+ <maxFieldLength>10000</maxFieldLength>
+
+ <unlockOnStartup>true</unlockOnStartup>
+ </mainIndex>
+
+ <updateHandler class="solr.DirectUpdateHandler2">
+
+ <!-- autocommit pending docs if certain criteria are met
+ <autoCommit>
+ <maxDocs>10000</maxDocs>
+ <maxTime>3600000</maxTime>
+ </autoCommit>
+ -->
+ <!-- represents a lower bound on the frequency that commits may
+ occur (in seconds). NOTE: not yet implemented
+
+ <commitIntervalLowerBound>0</commitIntervalLowerBound>
+ -->
+
+ <!-- The RunExecutableListener executes an external command.
+ exe - the name of the executable to run
+ dir - dir to use as the current working directory. default="."
+ wait - the calling thread waits until the executable returns. default="true"
+ args - the arguments to pass to the program. default=nothing
+ env - environment variables to set. default=nothing
+ -->
+ <!-- A postCommit event is fired after every commit
+ <listener event="postCommit" class="solr.RunExecutableListener">
+ <str name="exe">/var/opt/resin3/__PORT__/scripts/solr/snapshooter</str>
+ <str name="dir">/var/opt/resin3/__PORT__</str>
+ <bool name="wait">true</bool>
+ <arr name="args"> <str>arg1</str> <str>arg2</str> </arr>
+ <arr name="env"> <str>MYVAR=val1</str> </arr>
+ </listener>
+ -->
+
+
+ </updateHandler>
+
+
+ <query>
+ <!-- Maximum number of clauses in a boolean query... can affect
+ range or wildcard queries that expand to big boolean
+ queries. An exception is thrown if exceeded.
+ -->
+ <maxBooleanClauses>1024</maxBooleanClauses>
+
+
+ <!-- Cache specification for Filters or DocSets - unordered set of *all* documents
+ that match a particular query.
+ -->
+ <filterCache
+ class="solr.search.LRUCache"
+ size="512"
+ initialSize="512"
+ autowarmCount="256"/>
+
+ <queryResultCache
+ class="solr.search.LRUCache"
+ size="512"
+ initialSize="512"
+ autowarmCount="1024"/>
+
+ <documentCache
+ class="solr.search.LRUCache"
+ size="512"
+ initialSize="512"
+ autowarmCount="0"/>
+
+ <!-- If true, stored fields that are not requested will be loaded lazily.
+ -->
+ <enableLazyFieldLoading>true</enableLazyFieldLoading>
+
+ <!--
+
+ <cache name="myUserCache"
+ class="solr.search.LRUCache"
+ size="4096"
+ initialSize="1024"
+ autowarmCount="1024"
+ regenerator="MyRegenerator"
+ />
+ -->
+
+
+ <useFilterForSortedQuery>true</useFilterForSortedQuery>
+
+ <queryResultWindowSize>10</queryResultWindowSize>
+
+ <!-- set maxSize artificially low to exercise both types of sets -->
+ <HashDocSet maxSize="3" loadFactor="0.75"/>
+
+
+ <!-- boolToFilterOptimizer converts boolean clauses with zero boost
+ into cached filters if the number of docs selected by the clause exceeds
+ the threshold (represented as a fraction of the total index)
+ -->
+ <boolTofilterOptimizer enabled="false" cacheSize="32" threshold=".05"/>
+
+
+ <!-- a newSearcher event is fired whenever a new searcher is being prepared
+ and there is a current searcher handling requests (aka registered). -->
+ <!-- QuerySenderListener takes an array of NamedList and executes a
+ local query request for each NamedList in sequence. -->
+ <!--
+ <listener event="newSearcher" class="solr.QuerySenderListener">
+ <arr name="queries">
+ <lst> <str name="q">solr</str> <str name="start">0</str> <str name="rows">10</str> </lst>
+ <lst> <str name="q">rocks</str> <str name="start">0</str> <str name="rows">10</str> </lst>
+ </arr>
+ </listener>
+ -->
+
+ <!-- a firstSearcher event is fired whenever a new searcher is being
+ prepared but there is no current registered searcher to handle
+ requests or to gain prewarming data from. -->
+ <!--
+ <listener event="firstSearcher" class="solr.QuerySenderListener">
+ <arr name="queries">
+ <lst> <str name="q">fast_warm</str> <str name="start">0</str> <str name="rows">10</str> </lst>
+ </arr>
+ </listener>
+ -->
+
+
+ </query>
+
+
+ <!-- An alternate set representation that uses an integer hash to store filters (sets of docids).
+ If the set cardinality <= maxSize elements, then HashDocSet will be used instead of the bitset
+ based HashBitset. -->
+
+ <!-- requestHandler plugins... incoming queries will be dispatched to the
+ correct handler based on the qt (query type) param matching the
+ name of registered handlers.
+ The "standard" request handler is the default and will be used if qt
+ is not specified in the request.
+ -->
+ <requestHandler name="standard" class="solr.StandardRequestHandler"/>
+ <requestHandler name="dismaxOldStyleDefaults"
+ class="solr.DisMaxRequestHandler" >
+ <!-- for historic reasons, DisMaxRequestHandler will use all of
+ it's init params as "defaults" if there is no "defaults" list
+ specified
+ -->
+ <float name="tie">0.01</float>
+ <str name="qf">
+ text^0.5 features_t^1.0 subject^1.4 title_stemmed^2.0
+ </str>
+ <str name="pf">
+ text^0.2 features_t^1.1 subject^1.4 title_stemmed^2.0 title^1.5
+ </str>
+ <str name="bf">
+ ord(weight)^0.5 recip(rord(iind),1,1000,1000)^0.3
+ </str>
+ <str name="mm">
+ 3<-1 5<-2 6<90%
+ </str>
+ <int name="ps">100</int>
+ </requestHandler>
+ <requestHandler name="dismax" class="solr.DisMaxRequestHandler" >
+ <lst name="defaults">
+ <str name="q.alt">*:*</str>
+ <float name="tie">0.01</float>
+ <str name="qf">
+ text^0.5 features_t^1.0 subject^1.4 title_stemmed^2.0
+ </str>
+ <str name="pf">
+ text^0.2 features_t^1.1 subject^1.4 title_stemmed^2.0 title^1.5
+ </str>
+ <str name="bf">
+ ord(weight)^0.5 recip(rord(iind),1,1000,1000)^0.3
+ </str>
+ <str name="mm">
+ 3<-1 5<-2 6<90%
+ </str>
+ <int name="ps">100</int>
+ </lst>
+ </requestHandler>
+ <requestHandler name="old" class="solr.tst.OldRequestHandler" >
+ <int name="myparam">1000</int>
+ <float name="ratio">1.4142135</float>
+ <arr name="myarr"><int>1</int><int>2</int></arr>
+ <str>foo</str>
+ </requestHandler>
+ <requestHandler name="oldagain" class="solr.tst.OldRequestHandler" >
+ <lst name="lst1"> <str name="op">sqrt</str> <int name="val">2</int> </lst>
+ <lst name="lst2"> <str name="op">log</str> <float name="val">10</float> </lst>
+ </requestHandler>
+
+ <requestHandler name="test" class="solr.tst.TestRequestHandler" />
+
+ <!-- test query parameter defaults -->
+ <requestHandler name="defaults" class="solr.StandardRequestHandler">
+ <lst name="defaults">
+ <int name="rows">4</int>
+ <bool name="hl">true</bool>
+ <str name="hl.fl">text,name,subject,title,whitetok</str>
+ </lst>
+ </requestHandler>
+
+ <!-- test query parameter defaults -->
+ <requestHandler name="lazy" class="solr.StandardRequestHandler" startup="lazy">
+ <lst name="defaults">
+ <int name="rows">4</int>
+ <bool name="hl">true</bool>
+ <str name="hl.fl">text,name,subject,title,whitetok</str>
+ </lst>
+ </requestHandler>
+
+ <requestHandler name="/update" class="solr.XmlUpdateRequestHandler" />
+ <requestHandler name="/update/csv" class="solr.CSVRequestHandler" startup="lazy" />
+
+ <!-- test elevation -->
+ <searchComponent name="elevate" class="org.apache.solr.handler.component.QueryElevationComponent" >
+ <str name="queryFieldType">string</str>
+ <str name="config-file">elevate.xml</str>
+ </searchComponent>
+
+ <requestHandler name="/elevate" class="org.apache.solr.handler.component.SearchHandler">
+ <lst name="defaults">
+ <str name="echoParams">explicit</str>
+ </lst>
+ <arr name="last-components">
+ <str>elevate</str>
+ </arr>
+ </requestHandler>
+
+
+ <highlighting>
+ <!-- Configure the standard fragmenter -->
+ <fragmenter name="gap" class="org.apache.solr.highlight.GapFragmenter" default="true">
+ <lst name="defaults">
+ <int name="hl.fragsize">100</int>
+ </lst>
+ </fragmenter>
+
+ <fragmenter name="regex" class="org.apache.solr.highlight.RegexFragmenter">
+ <lst name="defaults">
+ <int name="hl.fragsize">70</int>
+ </lst>
+ </fragmenter>
+
+ <!-- Configure the standard formatter -->
+ <formatter name="html" class="org.apache.solr.highlight.HtmlFormatter" default="true">
+ <lst name="defaults">
+ <str name="hl.simple.pre"><![CDATA[<em>]]></str>
+ <str name="hl.simple.post"><![CDATA[</em>]]></str>
+ </lst>
+ </formatter>
+ </highlighting>
+
+
+ <!-- enable streaming for testing... -->
+ <requestDispatcher handleSelect="true" >
+ <requestParsers enableRemoteStreaming="true" multipartUploadLimitInKB="2048" />
+ <httpCaching never304="true" />
+ </requestDispatcher>
+
+ <admin>
+ <defaultQuery>solr</defaultQuery>
+ <gettableFiles>solrconfig.xml scheam.xml admin-extra.html</gettableFiles>
+ </admin>
+
+ <!-- test getting system property -->
+ <propTest attr1="${solr.test.sys.prop1}-$${literal}"
+ attr2="${non.existent.sys.prop:default-from-config}">prefix-${solr.test.sys.prop2}-suffix</propTest>
+
+</config>
Propchange: lucene/solr/trunk/src/test/test-files/solr/conf/solrconfig-nocache.xml
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: lucene/solr/trunk/src/test/test-files/solr/conf/solrconfig-nocache.xml
------------------------------------------------------------------------------
svn:keywords = Date Author Id Revision HeadURL
Modified: lucene/solr/trunk/src/test/test-files/solr/conf/solrconfig.xml
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/test/test-files/solr/conf/solrconfig.xml?rev=630037&r1=630036&r2=630037&view=diff
==============================================================================
--- lucene/solr/trunk/src/test/test-files/solr/conf/solrconfig.xml (original)
+++ lucene/solr/trunk/src/test/test-files/solr/conf/solrconfig.xml Thu Feb 21 14:44:19 2008
@@ -309,6 +309,9 @@
<!-- enable streaming for testing... -->
<requestDispatcher handleSelect="true" >
<requestParsers enableRemoteStreaming="true" multipartUploadLimitInKB="2048" />
+ <httpCaching lastModifiedFrom="openTime" etagSeed="Solr" never304="false">
+ <cacheControl>max-age=30, public</cacheControl>
+ </httpCaching>
</requestDispatcher>
<admin>
Modified: lucene/solr/trunk/src/webapp/src/org/apache/solr/servlet/SolrDispatchFilter.java
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/webapp/src/org/apache/solr/servlet/SolrDispatchFilter.java?rev=630037&r1=630036&r2=630037&view=diff
==============================================================================
--- lucene/solr/trunk/src/webapp/src/org/apache/solr/servlet/SolrDispatchFilter.java (original)
+++ lucene/solr/trunk/src/webapp/src/org/apache/solr/servlet/SolrDispatchFilter.java Thu Feb 21 14:44:19 2008
@@ -44,6 +44,8 @@
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.request.SolrQueryResponse;
import org.apache.solr.request.SolrRequestHandler;
+import org.apache.solr.servlet.cache.HttpCacheHeaderUtil;
+import org.apache.solr.servlet.cache.Method;
/**
* This filter looks at the incoming URL maps them to handlers defined in solrconfig.xml
@@ -51,13 +53,14 @@
public class SolrDispatchFilter implements Filter
{
final Logger log = Logger.getLogger(SolrDispatchFilter.class.getName());
-
+
protected SolrCore singlecore;
protected MultiCore multicore;
protected SolrRequestParsers parsers;
protected boolean handleSelect = false;
protected String pathPrefix = null; // strip this from the beginning of a path
protected String abortErrorMessage = null;
+ protected String solrConfigFilename = null;
public void init(FilterConfig config) throws ServletException
{
@@ -67,6 +70,7 @@
try {
// web.xml configuration
this.pathPrefix = config.getInitParameter( "path-prefix" );
+ this.solrConfigFilename = config.getInitParameter("solrconfig-filename");
// Find a valid solr core
SolrCore core = null;
@@ -87,7 +91,11 @@
core = multicore.getDefaultCore();
}
else {
- singlecore = new SolrCore( null, null, new SolrConfig(), null );
+ if (this.solrConfigFilename==null) {
+ singlecore = new SolrCore( null, null, new SolrConfig(), null );
+ } else {
+ singlecore = new SolrCore( null, null, new SolrConfig(this.solrConfigFilename), null);
+ }
core = singlecore;
}
@@ -168,6 +176,7 @@
if( request instanceof HttpServletRequest) {
SolrQueryRequest solrReq = null;
HttpServletRequest req = (HttpServletRequest)request;
+ HttpServletResponse resp = (HttpServletResponse)response;
try {
String path = req.getServletPath();
if( req.getPathInfo() != null ) {
@@ -233,7 +242,29 @@
if( solrReq == null ) {
solrReq = parsers.parse( core, path, req );
}
+
+ final SolrConfig conf = core.getSolrConfig();
+ final Method reqMethod = Method.getMethod(req.getMethod());
+
+ if (Method.POST != reqMethod) {
+ HttpCacheHeaderUtil.setCacheControlHeader(conf, resp);
+ }
+
+ // unless we have been explicitly told not to, do cache validation
+ if (!conf.getHttpCachingConfig().isNever304()) {
+ // if we've confirmed cache validation, return immediately
+ if (HttpCacheHeaderUtil.doCacheHeaderValidation(solrReq,
+ req,resp)) {
+ return;
+ }
+ }
+
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
+ * QueryResponseWriter is selectedand we get the correct
+ * Content-Type)
+ */
this.execute( req, handler, solrReq, solrRsp );
if( solrRsp.getException() != null ) {
sendError( (HttpServletResponse)response, solrRsp.getException() );
@@ -243,6 +274,11 @@
// Now write it out
QueryResponseWriter responseWriter = core.getQueryResponseWriter(solrReq);
response.setContentType(responseWriter.getContentType(solrReq, solrRsp));
+ if (Method.HEAD == Method.getMethod(req.getMethod())) {
+ // nothing to write out, waited this long just to get ContentType
+ return;
+ }
+
PrintWriter out = response.getWriter();
responseWriter.write(out, solrReq, solrRsp);
return;
@@ -303,7 +339,7 @@
}
}
res.sendError( code, ex.getMessage() + trace );
- }
+ }
//---------------------------------------------------------------------
//---------------------------------------------------------------------
Modified: lucene/solr/trunk/src/webapp/src/org/apache/solr/servlet/SolrRequestParsers.java
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/webapp/src/org/apache/solr/servlet/SolrRequestParsers.java?rev=630037&r1=630036&r2=630037&view=diff
==============================================================================
--- lucene/solr/trunk/src/webapp/src/org/apache/solr/servlet/SolrRequestParsers.java (original)
+++ lucene/solr/trunk/src/webapp/src/org/apache/solr/servlet/SolrRequestParsers.java Thu Feb 21 14:44:19 2008
@@ -348,7 +348,7 @@
final HttpServletRequest req, ArrayList<ContentStream> streams ) throws Exception
{
String method = req.getMethod().toUpperCase();
- if( "GET".equals( method ) ) {
+ if( "GET".equals( method ) || "HEAD".equals( method )) {
return new ServletSolrParams(req);
}
if( "POST".equals( method ) ) {
@@ -367,7 +367,7 @@
}
return raw.parseParamsAndFillStreams(req, streams);
}
- throw new SolrException( SolrException.ErrorCode.BAD_REQUEST, "Unsuported method: "+method );
+ throw new SolrException( SolrException.ErrorCode.BAD_REQUEST, "Unsupported method: "+method );
}
}
Added: lucene/solr/trunk/src/webapp/src/org/apache/solr/servlet/cache/HttpCacheHeaderUtil.java
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/webapp/src/org/apache/solr/servlet/cache/HttpCacheHeaderUtil.java?rev=630037&view=auto
==============================================================================
--- lucene/solr/trunk/src/webapp/src/org/apache/solr/servlet/cache/HttpCacheHeaderUtil.java (added)
+++ lucene/solr/trunk/src/webapp/src/org/apache/solr/servlet/cache/HttpCacheHeaderUtil.java Thu Feb 21 14:44:19 2008
@@ -0,0 +1,298 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.servlet.cache;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Map;
+import java.util.WeakHashMap;
+import java.util.List;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.lucene.index.IndexReader;
+
+import org.apache.solr.core.SolrCore;
+import org.apache.solr.core.SolrConfig;
+import org.apache.solr.core.SolrConfig.HttpCachingConfig.LastModFrom;
+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.SolrRequestHandler;
+
+import org.apache.commons.codec.binary.Base64;
+
+public final class HttpCacheHeaderUtil {
+
+ public static void sendNotModified(HttpServletResponse res)
+ throws IOException {
+ res.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+ }
+
+ public static void sendPreconditionFailed(HttpServletResponse res)
+ throws IOException {
+ res.setStatus(HttpServletResponse.SC_PRECONDITION_FAILED);
+ }
+
+ /**
+ * Weak Ref based cache for keeping track of core specific etagSeed
+ * and the last computed etag.
+ *
+ * @see #calcEtag
+ */
+ private static Map<SolrCore, EtagCacheVal> etagCoreCache
+ = new WeakHashMap<SolrCore, EtagCacheVal>();
+
+ /** @see #etagCoreCache */
+ private static class EtagCacheVal {
+ private final String etagSeed;
+
+ private String etagCache = null;
+ private long indexVersionCache=-1;
+
+ public EtagCacheVal(final String etagSeed) {
+ this.etagSeed = etagSeed;
+ }
+
+ public String calcEtag(final long currentIndexVersion) {
+ if (currentIndexVersion != indexVersionCache) {
+ indexVersionCache=currentIndexVersion;
+
+ etagCache = "\""
+ + new String(Base64.encodeBase64((Long.toHexString
+ (Long.reverse(indexVersionCache))
+ + etagSeed).getBytes()))
+ + "\"";
+ }
+
+ return etagCache;
+ }
+ }
+
+ /**
+ * Calculates a tag for the ETag header.
+ *
+ * @param solrReq
+ * @return a tag
+ */
+ public static String calcEtag(final SolrQueryRequest solrReq) {
+ final SolrCore core = solrReq.getCore();
+ final long currentIndexVersion
+ = solrReq.getSearcher().getReader().getVersion();
+
+ EtagCacheVal etagCache = etagCoreCache.get(core);
+ if (null == etagCache) {
+ final String etagSeed
+ = core.getSolrConfig().getHttpCachingConfig().getEtagSeed();
+ etagCache = new EtagCacheVal(etagSeed);
+ etagCoreCache.put(core, etagCache);
+ }
+
+ return etagCache.calcEtag(currentIndexVersion);
+
+ }
+
+ /**
+ * Checks if one of the tags in the list equals the given etag.
+ *
+ * @param headerList
+ * the ETag header related header elements
+ * @param etag
+ * the ETag to compare with
+ * @return true if the etag is found in one of the header elements - false
+ * otherwise
+ */
+ public static boolean isMatchingEtag(final List<String> headerList,
+ final String etag) {
+ for (String header : headerList) {
+ final String[] headerEtags = header.split(",");
+ for (String s : headerEtags) {
+ s = s.trim();
+ if (s.equals(etag) || "*".equals(s)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Calculate the appropriate last-modified time for Solr relative the current request.
+ *
+ * @param solrReq
+ * @return the timestamp to use as a last modified time.
+ */
+ public static long calcLastModified(final SolrQueryRequest solrReq) {
+ final SolrCore core = solrReq.getCore();
+ final SolrIndexSearcher searcher = solrReq.getSearcher();
+
+ final LastModFrom lastModFrom
+ = core.getSolrConfig().getHttpCachingConfig().getLastModFrom();
+
+ long lastMod;
+ try {
+ // assume default, change if needed (getOpenTime() should be fast)
+ lastMod =
+ LastModFrom.DIRLASTMOD == lastModFrom
+ ? IndexReader.lastModified(searcher.getReader().directory())
+ : searcher.getOpenTime();
+ } catch (IOException e) {
+ // we're pretty freaking screwed if this happens
+ throw new SolrException(ErrorCode.SERVER_ERROR, e);
+ }
+ // Get the time where the searcher has been opened
+ // We get rid of the milliseconds because the HTTP header has only
+ // second granularity
+ return lastMod - (lastMod % 1000L);
+ }
+
+ /**
+ * Set the Cache-Control HTTP header (and Expires if needed)
+ * based on the SolrConfig.
+ */
+ public static void setCacheControlHeader(final SolrConfig conf,
+ final HttpServletResponse resp) {
+
+ final String cc = conf.getHttpCachingConfig().getCacheControlHeader();
+ if (null != cc) {
+ resp.setHeader("Cache-Control", cc);
+ }
+ Integer maxAge = conf.getHttpCachingConfig().getMaxAge();
+ if (null != maxAge) {
+ resp.setDateHeader("Expires", System.currentTimeMillis()
+ + (maxAge * 1000));
+ }
+
+ return;
+ }
+
+ /**
+ * Sets HTTP Response cache validator headers appropriately and
+ * validates the HTTP Request against these using any conditional
+ * request headers.
+ *
+ * If the request contains conditional headers, and those headers
+ * indicate a match with the current known state of the system, this
+ * method will return "true" indicating that a 304 Status code can be
+ * returned, and no further processing is needed.
+ *
+ *
+ * @return true if the request contains conditional headers, and those
+ * headers indicate a match with the current known state of the
+ * system -- indicating that a 304 Status code can be returned to
+ * the client, and no further request processing is needed.
+ */
+ public static boolean doCacheHeaderValidation(final SolrQueryRequest solrReq,
+ final HttpServletRequest req,
+ final HttpServletResponse resp)
+ throws IOException {
+
+ final Method reqMethod=Method.getMethod(req.getMethod());
+
+ final long lastMod = HttpCacheHeaderUtil.calcLastModified(solrReq);
+ final String etag = HttpCacheHeaderUtil.calcEtag(solrReq);
+
+ resp.setDateHeader("Last-Modified", lastMod);
+ resp.setHeader("ETag", etag);
+
+ if (checkETagValidators(req, resp, reqMethod, etag)) {
+ return true;
+ }
+
+ if (checkLastModValidators(req, resp, lastMod)) {
+ return true;
+ }
+
+ return false;
+ }
+
+
+ /**
+ * Check for etag related conditional headers and set status
+ *
+ * @return true if no request processing is necessary and HTTP response status has been set, false otherwise.
+ * @throws IOException
+ */
+ @SuppressWarnings("unchecked")
+ public static boolean checkETagValidators(final HttpServletRequest req,
+ final HttpServletResponse resp,
+ final Method reqMethod,
+ final String etag)
+ throws IOException {
+
+ // First check If-None-Match because this is the common used header
+ // element by HTTP clients
+ final List<String> ifNoneMatchList = Collections.list(req
+ .getHeaders("If-None-Match"));
+ if (ifNoneMatchList.size() > 0 && isMatchingEtag(ifNoneMatchList, etag)) {
+ if (reqMethod == Method.GET || reqMethod == Method.HEAD) {
+ sendNotModified(resp);
+ } else {
+ sendPreconditionFailed(resp);
+ }
+ return true;
+ }
+
+ // Check for If-Match headers
+ final List<String> ifMatchList = Collections.list(req
+ .getHeaders("If-Match"));
+ if (ifMatchList.size() > 0 && !isMatchingEtag(ifMatchList, etag)) {
+ sendPreconditionFailed(resp);
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Check for modify time related conditional headers and set status
+ *
+ * @return true if no request processing is necessary and HTTP response status has been set, false otherwise.
+ * @throws IOException
+ */
+ public static boolean checkLastModValidators(final HttpServletRequest req,
+ final HttpServletResponse resp,
+ final long lastMod)
+ throws IOException {
+
+ try {
+ // First check for If-Modified-Since because this is the common
+ // used header by HTTP clients
+ final long modifiedSince = req.getDateHeader("If-Modified-Since");
+ if (modifiedSince != -1L && lastMod <= modifiedSince) {
+ // Send a "not-modified"
+ sendNotModified(resp);
+ return true;
+ }
+
+ final long unmodifiedSince = req.getDateHeader("If-Unmodified-Since");
+ if (unmodifiedSince != -1L && lastMod > unmodifiedSince) {
+ // Send a "precondition failed"
+ sendPreconditionFailed(resp);
+ return true;
+ }
+ } catch (IllegalArgumentException iae) {
+ // one of our date headers was not formated properly, ignore it
+ /* NOOP */
+ }
+ return false;
+ }
+}
Propchange: lucene/solr/trunk/src/webapp/src/org/apache/solr/servlet/cache/HttpCacheHeaderUtil.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: lucene/solr/trunk/src/webapp/src/org/apache/solr/servlet/cache/HttpCacheHeaderUtil.java
------------------------------------------------------------------------------
svn:keywords = Date Author Id Revision HeadURL
Added: lucene/solr/trunk/src/webapp/src/org/apache/solr/servlet/cache/Method.java
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/webapp/src/org/apache/solr/servlet/cache/Method.java?rev=630037&view=auto
==============================================================================
--- lucene/solr/trunk/src/webapp/src/org/apache/solr/servlet/cache/Method.java (added)
+++ lucene/solr/trunk/src/webapp/src/org/apache/solr/servlet/cache/Method.java Thu Feb 21 14:44:19 2008
@@ -0,0 +1,41 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+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();
+ }
+
+ 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;
+ }
+ }
+
+ return OTHER;
+ }
+}
Propchange: lucene/solr/trunk/src/webapp/src/org/apache/solr/servlet/cache/Method.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: lucene/solr/trunk/src/webapp/src/org/apache/solr/servlet/cache/Method.java
------------------------------------------------------------------------------
svn:keywords = Date Author Id Revision HeadURL