This is an automated email from the ASF dual-hosted git repository.
gerlowskija pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/solr.git
The following commit(s) were added to refs/heads/main by this push:
new e6d9dc52033 SOLR-10998: Obey 'Accept' header in v2 APIs (#3262)
e6d9dc52033 is described below
commit e6d9dc520331697684a1051ddbe18e2d3174cd98
Author: Jason Gerlowski <[email protected]>
AuthorDate: Fri Mar 21 09:20:27 2025 -0400
SOLR-10998: Obey 'Accept' header in v2 APIs (#3262)
'wt' takes precedence if specified for now, if not provided Solr
(through our use of Jersey) will pick a response format in keeping with
the specified 'Accept' header.
JSON responses remains the "default" if neither 'wt' nor an "Accept"
header are specified.
---
solr/CHANGES.txt | 3 ++
.../org/apache/solr/jersey/JerseyApplications.java | 12 ++---
.../solr/jersey/MediaTypeOverridingFilter.java | 22 ++++++++--
.../apache/solr/handler/V2ApiIntegrationTest.java | 51 ++++++++++++++++++++++
4 files changed, 78 insertions(+), 10 deletions(-)
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index 66adcaf6291..812ab8243a7 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -195,6 +195,9 @@ Improvements
* SOLR-17607: SolrJ CloudSolrClient configured with HTTP URLs will no longer
eagerly connect to
anything. (David Smiley)
+* SOLR-10998: v2 APIs now obey the "Accept" request header for
content-negotiation if 'wt' is unspecified. JSON is still used as a default
when
+ neither 'Accept' or 'wt' are specified. (Jason Gerlowski)
+
Optimizations
---------------------
* SOLR-17578: Remove ZkController internal core supplier, for slightly faster
reconnection after Zookeeper session loss. (Pierre Salagnac)
diff --git a/solr/core/src/java/org/apache/solr/jersey/JerseyApplications.java
b/solr/core/src/java/org/apache/solr/jersey/JerseyApplications.java
index 6bb4b07c439..3ac1ef79600 100644
--- a/solr/core/src/java/org/apache/solr/jersey/JerseyApplications.java
+++ b/solr/core/src/java/org/apache/solr/jersey/JerseyApplications.java
@@ -43,12 +43,12 @@ public class JerseyApplications {
// Request and response serialization/deserialization
// TODO: could these be singletons to save per-request object creations?
- register(MessageBodyWriters.JavabinMessageBodyWriter.class);
- register(MessageBodyWriters.XmlMessageBodyWriter.class);
- register(MessageBodyWriters.CsvMessageBodyWriter.class);
- register(MessageBodyWriters.RawMessageBodyWriter.class);
- register(JacksonJsonProvider.class, 5);
- register(MessageBodyReaders.CachingJsonMessageBodyReader.class, 10);
+ register(JacksonJsonProvider.class, 1);
+ register(MessageBodyWriters.JavabinMessageBodyWriter.class, 5);
+ register(MessageBodyWriters.XmlMessageBodyWriter.class, 5);
+ register(MessageBodyWriters.CsvMessageBodyWriter.class, 5);
+ register(MessageBodyWriters.RawMessageBodyWriter.class, 5);
+ register(MessageBodyReaders.CachingJsonMessageBodyReader.class, 2);
register(SolrJacksonMapper.class);
// Request lifecycle logic
diff --git
a/solr/core/src/java/org/apache/solr/jersey/MediaTypeOverridingFilter.java
b/solr/core/src/java/org/apache/solr/jersey/MediaTypeOverridingFilter.java
index 44c08bff03e..cc5c4f6ca80 100644
--- a/solr/core/src/java/org/apache/solr/jersey/MediaTypeOverridingFilter.java
+++ b/solr/core/src/java/org/apache/solr/jersey/MediaTypeOverridingFilter.java
@@ -17,7 +17,10 @@
package org.apache.solr.jersey;
+import static jakarta.ws.rs.core.HttpHeaders.ACCEPT;
import static jakarta.ws.rs.core.HttpHeaders.CONTENT_TYPE;
+import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON;
+import static org.apache.solr.common.params.CommonParams.WT;
import static org.apache.solr.jersey.RequestContextKeys.SOLR_QUERY_REQUEST;
import jakarta.ws.rs.container.ContainerRequestContext;
@@ -25,19 +28,23 @@ import jakarta.ws.rs.container.ContainerResponseContext;
import jakarta.ws.rs.container.ContainerResponseFilter;
import jakarta.ws.rs.container.ResourceInfo;
import jakarta.ws.rs.core.Context;
-import jakarta.ws.rs.core.MediaType;
import java.io.IOException;
+import java.lang.invoke.MethodHandles;
import java.util.List;
import org.apache.solr.api.JerseyResource;
import org.apache.solr.handler.admin.ZookeeperRead;
import org.apache.solr.handler.api.V2ApiUtils;
import org.apache.solr.request.SolrQueryRequest;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
// TODO Deprecate or remove support for the 'wt' parameter in the v2 APIs in
favor of the more
// HTTP-compliant 'Accept' header
/** Overrides the content-type of the response based on an optional
user-provided 'wt' parameter */
public class MediaTypeOverridingFilter implements ContainerResponseFilter {
+ private static final Logger log =
LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
private static final List<Class<? extends JerseyResource>>
EXEMPTED_RESOURCES =
List.of(ZookeeperRead.class);
@@ -65,9 +72,16 @@ public class MediaTypeOverridingFilter implements
ContainerResponseFilter {
(SolrQueryRequest) requestContext.getProperty(SOLR_QUERY_REQUEST);
// TODO Is it valid for SQRequest to be null?
final var params = (solrQueryRequest != null) ?
solrQueryRequest.getParams() : null;
- final String mediaType = V2ApiUtils.getMediaTypeFromWtParam(params,
MediaType.APPLICATION_JSON);
- if (mediaType != null) {
- responseContext.getHeaders().putSingle(CONTENT_TYPE, mediaType);
+ if (params != null && params.get(WT) != null) { // Override for 'wt'
+ final String mediaType = V2ApiUtils.getMediaTypeFromWtParam(params,
null);
+ if (mediaType != null) {
+ responseContext.getHeaders().putSingle(CONTENT_TYPE, mediaType);
+ }
+ } else if (!requestContext.getHeaders().containsKey(ACCEPT)
+ || "*/*"
+ .equals(requestContext.getHeaderString(ACCEPT))) { // Override
default response to json
+ responseContext.getHeaders().putSingle(CONTENT_TYPE, APPLICATION_JSON);
}
+ // Else, obey the user-provided 'Accept' header
}
}
diff --git
a/solr/core/src/test/org/apache/solr/handler/V2ApiIntegrationTest.java
b/solr/core/src/test/org/apache/solr/handler/V2ApiIntegrationTest.java
index a7f4606e160..70ac4e89d8c 100644
--- a/solr/core/src/test/org/apache/solr/handler/V2ApiIntegrationTest.java
+++ b/solr/core/src/test/org/apache/solr/handler/V2ApiIntegrationTest.java
@@ -22,11 +22,16 @@ import java.nio.file.Paths;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpRequestBase;
+import org.apache.http.client.utils.URIBuilder;
import org.apache.solr.client.solrj.ResponseParser;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrRequest;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.BinaryResponseParser;
+import org.apache.solr.client.solrj.impl.CloudLegacySolrClient;
import org.apache.solr.client.solrj.impl.CloudSolrClient;
import org.apache.solr.client.solrj.impl.JsonMapResponseParser;
import org.apache.solr.client.solrj.impl.NoOpResponseParser;
@@ -139,6 +144,52 @@ public class V2ApiIntegrationTest extends
SolrCloudTestCase {
assertEquals(respString, 0, Utils.getObjectByPath(resp, true,
"/responseHeader/status"));
}
+ @Test
+ public void testObeysWtParameterWhenProvided() throws Exception {
+ final var httpClient = getRawClient();
+ final var listCollRequest = getListCollectionsRequest();
+ listCollRequest.setURI(
+ new URIBuilder(listCollRequest.getURI()).addParameter("wt",
"xml").build());
+
+ final var response = httpClient.execute(listCollRequest);
+
+ assertEquals(200, response.getStatusLine().getStatusCode());
+ assertEquals("application/xml",
response.getFirstHeader("Content-type").getValue());
+ }
+
+ @Test
+ public void testObeysAcceptHeaderWhenWtParamNotProvided() throws Exception {
+ final var httpClient = getRawClient();
+ final var listCollRequest = getListCollectionsRequest();
+ listCollRequest.addHeader("Accept", "application/xml");
+
+ final var response = httpClient.execute(listCollRequest);
+
+ assertEquals(200, response.getStatusLine().getStatusCode());
+ assertEquals("application/xml",
response.getFirstHeader("Content-type").getValue());
+ }
+
+ @Test
+ public void testRespondsWithJsonWhenWtAndAcceptAreMissing() throws Exception
{
+ final var httpClient = getRawClient();
+ final var listCollRequest = getListCollectionsRequest();
+
+ final var response = httpClient.execute(listCollRequest);
+
+ assertEquals(200, response.getStatusLine().getStatusCode());
+ assertEquals("application/json",
response.getFirstHeader("Content-type").getValue());
+ }
+
+ private HttpClient getRawClient() {
+ return ((CloudLegacySolrClient) cluster.getSolrClient()).getHttpClient();
+ }
+
+ private HttpRequestBase getListCollectionsRequest() {
+ final var v2BaseUrl =
cluster.getJettySolrRunner(0).getBaseURLV2().toString();
+ final var listCollUrl = v2BaseUrl + "/collections";
+ return new HttpGet(listCollUrl);
+ }
+
@Test
public void testSingleWarning() throws Exception {
NamedList<?> resp =