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 =

Reply via email to