SOLR-6043: Add ability to set http headers in solr response

git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/trunk@1593423 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Ryan Ernst 2014-05-08 21:26:37 +00:00
parent 564c221c95
commit 050b0b8af9
5 changed files with 396 additions and 12 deletions

View File

@ -145,6 +145,11 @@ Bug Fixes
* SOLR-6039: fixed debug output when no results in response
(Tomás Fernández Löbbe, hossman)
New Features
----------------------
* SOLR-6043: Add ability to set http headers in solr response
(Tomás Fernández Löbbe via Ryan Ernst)
Other Changes
---------------------

View File

@ -17,13 +17,19 @@
package org.apache.solr.response;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import javax.servlet.http.HttpServletResponse;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.util.SimpleOrderedMap;
import org.apache.solr.search.ReturnFields;
import org.apache.solr.search.SolrReturnFields;
import java.util.*;
/**
* <code>SolrQueryResponse</code> is used by a query handler to return
* the response to a query request.
@ -77,6 +83,15 @@ public class SolrQueryResponse {
protected NamedList<Object> toLog = new SimpleOrderedMap<>();
protected ReturnFields returnFields;
/**
* Container for storing HTTP headers. Internal Solr components can add headers to
* this SolrQueryResponse through the methods: {@link #addHttpHeader(String, String)}
* and {@link #setHttpHeader(String, String)}, or remove existing ones through
* {@link #removeHttpHeader(String)} and {@link #removeHttpHeaders(String)}.
* All these headers are going to be added to the HTTP response.
*/
private final NamedList<String> headers = new SimpleOrderedMap<>();
// error if this is set...
protected Exception err;
@ -245,4 +260,110 @@ public class SolrQueryResponse {
public boolean isHttpCaching() {
return this.httpCaching;
}
/**
*
* Sets a response header with the given name and value. This header
* will be included in the HTTP response
* If the header had already been set, the new value overwrites the
* previous ones (all of them if there are multiple for the same name).
*
* @param name the name of the header
* @param value the header value If it contains octet string,
* it should be encoded according to RFC 2047
* (http://www.ietf.org/rfc/rfc2047.txt)
*
* @see #addHttpHeader
* @see HttpServletResponse#setHeader
*/
public void setHttpHeader(String name, String value) {
headers.removeAll(name);
headers.add(name, value);
}
/**
* Adds a response header with the given name and value. This header
* will be included in the HTTP response
* This method allows response headers to have multiple values.
*
* @param name the name of the header
* @param value the additional header value If it contains
* octet string, it should be encoded
* according to RFC 2047
* (http://www.ietf.org/rfc/rfc2047.txt)
*
* @see #setHttpHeader
* @see HttpServletResponse#addHeader
*/
public void addHttpHeader(String name, String value) {
headers.add(name, value);
}
/**
* Gets the value of the response header with the given name.
*
* <p>If a response header with the given name exists and contains
* multiple values, the value that was added first will be returned.</p>
*
* <p>NOTE: this runs in linear time (it scans starting at the
* beginning of the list until it finds the first pair with
* the specified name).</p>
*
* @param name the name of the response header whose value to return
* @return the value of the response header with the given name,
* or <tt>null</tt> if no header with the given name has been set
* on this response
*/
public String getHttpHeader(String name) {
return headers.get(name);
}
/**
* Gets the values of the response header with the given name.
*
* @param name the name of the response header whose values to return
*
* @return a (possibly empty) <code>Collection</code> of the values
* of the response header with the given name
*
*/
public Collection<String> getHttpHeaders(String name) {
return headers.getAll(name);
}
/**
* Removes a previously added header with the given name (only
* the first one if multiple are present for the same name)
*
* <p>NOTE: this runs in linear time (it scans starting at the
* beginning of the list until it finds the first pair with
* the specified name).</p>
*
* @param name the name of the response header to remove
* @return the value of the removed entry or <tt>null</tt> if no
* value is found for the given header name
*/
public String removeHttpHeader(String name) {
return headers.remove(name);
}
/**
* Removes all previously added headers with the given name.
*
* @param name the name of the response headers to remove
* @return a <code>Collection</code> with all the values
* of the removed entries. It returns <code>null</code> if no
* entries are found for the given name
*/
public Collection<String> removeHttpHeaders(String name) {
return headers.removeAll(name);
}
/**
* Returns a new iterator of response headers
* @return a new Iterator instance for the response headers
*/
public Iterator<Entry<String, String>> httpHeaders() {
return headers.iterator();
}
}

View File

@ -419,16 +419,11 @@ public class SolrDispatchFilter extends BaseSolrFilter {
SolrRequestInfo.setRequestInfo(new SolrRequestInfo(solrReq, solrRsp));
this.execute( req, handler, solrReq, solrRsp );
HttpCacheHeaderUtil.checkHttpCachingVeto(solrRsp, resp, reqMethod);
// add info to http headers
//TODO: See SOLR-232 and SOLR-267.
/*try {
NamedList solrRspHeader = solrRsp.getResponseHeader();
for (int i=0; i<solrRspHeader.size(); i++) {
((javax.servlet.http.HttpServletResponse) response).addHeader(("Solr-" + solrRspHeader.getName(i)), String.valueOf(solrRspHeader.getVal(i)));
}
} catch (ClassCastException cce) {
log.log(Level.WARNING, "exception adding response header log information", cce);
}*/
Iterator<Entry<String, String>> headers = solrRsp.httpHeaders();
while (headers.hasNext()) {
Entry<String, String> entry = headers.next();
resp.addHeader(entry.getKey(), entry.getValue());
}
QueryResponseWriter responseWriter = core.getQueryResponseWriter(solrReq);
writeResponse(solrRsp, response, responseWriter, solrReq, reqMethod);
}

View File

@ -0,0 +1,32 @@
<?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.
-->
<config>
<luceneMatchVersion>${tests.luceneMatchVersion:LUCENE_CURRENT}</luceneMatchVersion>
<dataDir>${solr.data.dir:}</dataDir>
<xi:include href="solrconfig.snippet.randomindexconfig.xml" xmlns:xi="http://www.w3.org/2001/XInclude"/>
<directoryFactory name="DirectoryFactory" class="${solr.directoryFactory:solr.RAMDirectoryFactory}"/>
<searchComponent name="componentThatAddsHeader" class="org.apache.solr.servlet.ResponseHeaderTest$ComponentThatAddsHeader"/>
<requestHandler name="/withHeaders" class="solr.StandardRequestHandler">
<arr name="first-components">
<str>componentThatAddsHeader</str>
</arr>
</requestHandler>
<requestHandler name="/update" class="solr.UpdateRequestHandler" />
</config>

View File

@ -0,0 +1,231 @@
package org.apache.solr.servlet;
/*
* 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.
*/
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Map.Entry;
import org.apache.commons.io.FileUtils;
import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.solr.SolrJettyTestBase;
import org.apache.solr.SolrTestCaseJ4;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.HttpSolrServer;
import org.apache.solr.handler.component.ResponseBuilder;
import org.apache.solr.handler.component.SearchComponent;
import org.apache.solr.response.SolrQueryResponse;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
public class ResponseHeaderTest extends SolrJettyTestBase {
private static File solrHomeDirectory;
@BeforeClass
public static void beforeTest() throws Exception {
solrHomeDirectory = createTempDir();
setupJettyTestHome(solrHomeDirectory, "collection1");
String top = SolrTestCaseJ4.TEST_HOME() + "/collection1/conf";
FileUtils.copyFile(new File(top, "solrconfig-headers.xml"), new File(solrHomeDirectory + "/collection1/conf", "solrconfig.xml"));
createJetty(solrHomeDirectory.getAbsolutePath(), null, null);
}
@AfterClass
public static void afterTest() throws Exception {
cleanUpJettyHome(solrHomeDirectory);
}
@Test
public void testHttpResponse() throws SolrServerException, IOException {
HttpSolrServer client = (HttpSolrServer)getSolrServer();
HttpClient httpClient = client.getHttpClient();
URI uri = URI.create(client.getBaseURL() + "/withHeaders?q=*:*");
HttpGet httpGet = new HttpGet(uri);
HttpResponse response = httpClient.execute(httpGet);
Header[] headers = response.getAllHeaders();
boolean containsWarningHeader = false;
for (Header header:headers) {
if ("Warning".equals(header.getName())) {
containsWarningHeader = true;
assertEquals("This is a test warning", header.getValue());
break;
}
}
assertTrue("Expected header not found", containsWarningHeader);
}
@Test
public void testAddHttpHeader() {
SolrQueryResponse response = new SolrQueryResponse();
Iterator<Entry<String, String>> it = response.httpHeaders();
assertFalse(it.hasNext());
response.addHttpHeader("key1", "value1");
it = response.httpHeaders();
assertTrue(it.hasNext());
Entry<String, String> entry = it.next();
assertEquals("key1", entry.getKey());
assertEquals("value1", entry.getValue());
assertFalse(it.hasNext());
response.addHttpHeader("key1", "value2");
it = response.httpHeaders();
assertTrue(it.hasNext());
entry = it.next();
assertEquals("key1", entry.getKey());
assertEquals("value1", entry.getValue());
assertTrue(it.hasNext());
entry = it.next();
assertEquals("key1", entry.getKey());
assertEquals("value2", entry.getValue());
assertFalse(it.hasNext());
response.addHttpHeader("key2", "value2");
it = response.httpHeaders();
assertTrue(it.hasNext());
entry = it.next();
assertEquals("key1", entry.getKey());
assertEquals("value1", entry.getValue());
assertTrue(it.hasNext());
entry = it.next();
assertEquals("key1", entry.getKey());
assertEquals("value2", entry.getValue());
assertTrue(it.hasNext());
entry = it.next();
assertEquals("key2", entry.getKey());
assertEquals("value2", entry.getValue());
assertFalse(it.hasNext());
}
@Test
public void testSetHttpHeader() {
SolrQueryResponse response = new SolrQueryResponse();
Iterator<Entry<String, String>> it = response.httpHeaders();
assertFalse(it.hasNext());
response.setHttpHeader("key1", "value1");
it = response.httpHeaders();
assertTrue(it.hasNext());
Entry<String, String> entry = it.next();
assertEquals("key1", entry.getKey());
assertEquals("value1", entry.getValue());
assertFalse(it.hasNext());
response.setHttpHeader("key1", "value2");
it = response.httpHeaders();
assertTrue(it.hasNext());
entry = it.next();
assertEquals("key1", entry.getKey());
assertEquals("value2", entry.getValue());
assertFalse(it.hasNext());
response.addHttpHeader("key1", "value3");
response.setHttpHeader("key1", "value4");
it = response.httpHeaders();
assertTrue(it.hasNext());
entry = it.next();
assertEquals("key1", entry.getKey());
assertEquals("value4", entry.getValue());
assertFalse(it.hasNext());
response.setHttpHeader("key2", "value5");
it = response.httpHeaders();
assertTrue(it.hasNext());
entry = it.next();
assertEquals("key1", entry.getKey());
assertEquals("value4", entry.getValue());
assertTrue(it.hasNext());
entry = it.next();
assertEquals("key2", entry.getKey());
assertEquals("value5", entry.getValue());
assertFalse(it.hasNext());
}
@Test
public void testRemoveHttpHeader() {
SolrQueryResponse response = new SolrQueryResponse();
Iterator<Entry<String, String>> it = response.httpHeaders();
assertFalse(it.hasNext());
response.addHttpHeader("key1", "value1");
assertTrue(response.httpHeaders().hasNext());
assertEquals("value1", response.removeHttpHeader("key1"));
assertFalse(response.httpHeaders().hasNext());
response.addHttpHeader("key1", "value2");
response.addHttpHeader("key1", "value3");
response.addHttpHeader("key2", "value4");
assertTrue(response.httpHeaders().hasNext());
assertEquals("value2", response.removeHttpHeader("key1"));
assertEquals("value3", response.httpHeaders().next().getValue());
assertEquals("value3", response.removeHttpHeader("key1"));
assertNull(response.removeHttpHeader("key1"));
assertEquals("key2", response.httpHeaders().next().getKey());
}
@Test
public void testRemoveHttpHeaders() {
SolrQueryResponse response = new SolrQueryResponse();
Iterator<Entry<String, String>> it = response.httpHeaders();
assertFalse(it.hasNext());
response.addHttpHeader("key1", "value1");
assertTrue(response.httpHeaders().hasNext());
assertEquals(Arrays.asList("value1"), response.removeHttpHeaders("key1"));
assertFalse(response.httpHeaders().hasNext());
response.addHttpHeader("key1", "value2");
response.addHttpHeader("key1", "value3");
response.addHttpHeader("key2", "value4");
assertTrue(response.httpHeaders().hasNext());
assertEquals(Arrays.asList(new String[]{"value2", "value3"}), response.removeHttpHeaders("key1"));
assertNull(response.removeHttpHeaders("key1"));
assertEquals("key2", response.httpHeaders().next().getKey());
}
public static class ComponentThatAddsHeader extends SearchComponent {
@Override
public void prepare(ResponseBuilder rb) throws IOException {
rb.rsp.addHttpHeader("Warning", "This is a test warning");
}
@Override
public void process(ResponseBuilder rb) throws IOException {}
@Override
public String getDescription() {
return null;
}
@Override
public String getSource() {
return null;
}
}
}