This is an automated email from the ASF dual-hosted git repository.

crallen pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-druid.git


The following commit(s) were added to refs/heads/master by this push:
     new 6567fff  Query Response format to be based on http 'accept' header & 
Query Payload content type to be based on 'content-type' header (#4033)
6567fff is described below

commit 6567fff9e7024883ac548abc920f051bdeddaefb
Author: vishnu rao <[email protected]>
AuthorDate: Sat Oct 13 05:29:14 2018 +0800

    Query Response format to be based on http 'accept' header & Query Payload 
content type to be based on 'content-type' header (#4033)
    
    * o- Query Response format to be based on http 'accept' header & Query 
Payload contenty type to be based on 'content-type' header
    
    * o- Query Response format to be based on http 'accept' header & Query 
Payload contenty type to be based on 'content-type' header
    o- if Accept header is absent, it defaults to Content-Type header
    
    * Feature: Query Response format to be based on http 'accept' header & 
Query Payload content type to be based on 'content-type'  PR #4033
    Minor change to a comment - restoring to previous wording
    
    * Feature: Query Response format to be based on http 'accept' header & 
Query Payload content type to be based on 'content-type'  PR #4033
    o- minor change to check for empty string
---
 docs/content/querying/querying.md                  |  10 +-
 .../org/apache/druid/server/QueryResource.java     |  23 +++-
 .../org/apache/druid/server/QueryResourceTest.java | 121 ++++++++++++++++++++-
 3 files changed, 145 insertions(+), 9 deletions(-)

diff --git a/docs/content/querying/querying.md 
b/docs/content/querying/querying.md
index 549493e..a73939c 100644
--- a/docs/content/querying/querying.md
+++ b/docs/content/querying/querying.md
@@ -11,12 +11,20 @@ REST query interface. For normal Druid operations, queries 
should be issued to t
 to the queryable nodes like this -
 
  ```bash
- curl -X POST '<queryable_host>:<port>/druid/v2/?pretty' -H 
'Content-Type:application/json' -d @<query_json_file>
+ curl -X POST '<queryable_host>:<port>/druid/v2/?pretty' -H 
'Content-Type:application/json' -H 'Accept:application/json' -d 
@<query_json_file>
  ```
  
 Druid's native query language is JSON over HTTP, although many members of the 
community have contributed different 
 [client libraries](../development/libraries.html) in other languages to query 
Druid. 
 
+The Content-Type/Accept Headers can also take 'application/x-jackson-smile'.
+
+ ```bash
+ curl -X POST '<queryable_host>:<port>/druid/v2/?pretty' -H 
'Content-Type:application/json' -H 'Accept:x-jackson-smile' -d 
@<query_json_file>
+ ```
+
+Note: If Accept header is not provided, it defaults to value of 'Content-Type' 
header.
+
 Druid's native query is relatively low level, mapping closely to how 
computations are performed internally. Druid queries 
 are designed to be lightweight and complete very quickly. This means that for 
more complex analysis, or to build 
 more complex visualizations, multiple Druid queries may be required.
diff --git a/server/src/main/java/org/apache/druid/server/QueryResource.java 
b/server/src/main/java/org/apache/druid/server/QueryResource.java
index 70fabd2..12416e4 100644
--- a/server/src/main/java/org/apache/druid/server/QueryResource.java
+++ b/server/src/main/java/org/apache/druid/server/QueryResource.java
@@ -24,6 +24,8 @@ import com.fasterxml.jackson.databind.ObjectWriter;
 import com.fasterxml.jackson.databind.module.SimpleModule;
 import com.fasterxml.jackson.datatype.joda.ser.DateTimeSerializer;
 import com.fasterxml.jackson.jaxrs.smile.SmileMediaTypes;
+
+import com.google.common.base.Strings;
 import com.google.common.base.Throwables;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterables;
@@ -156,13 +158,19 @@ public class QueryResource implements 
QueryCountStatsProvider
   public Response doPost(
       final InputStream in,
       @QueryParam("pretty") final String pretty,
-      @Context final HttpServletRequest req // used to get request 
content-type, remote address and auth-related headers
+      @Context final HttpServletRequest req // used to get request 
content-type,Accept header, remote address and auth-related headers
   ) throws IOException
   {
     final QueryLifecycle queryLifecycle = queryLifecycleFactory.factorize();
     Query<?> query = null;
 
-    final ResponseContext context = createContext(req.getContentType(), pretty 
!= null);
+    String acceptHeader = req.getHeader("Accept");
+    if (Strings.isNullOrEmpty(acceptHeader)) {
+      //default to content-type
+      acceptHeader = req.getContentType();
+    }
+
+    final ResponseContext context = createContext(acceptHeader, pretty != 
null);
 
     final String currThreadName = Thread.currentThread().getName();
     try {
@@ -294,13 +302,13 @@ public class QueryResource implements 
QueryCountStatsProvider
     }
   }
 
-  private static Query<?> readQuery(
+  private Query<?> readQuery(
       final HttpServletRequest req,
       final InputStream in,
       final ResponseContext context
   ) throws IOException
   {
-    Query baseQuery = context.getObjectMapper().readValue(in, Query.class);
+    Query baseQuery = getMapperForRequest(req.getContentType()).readValue(in, 
Query.class);
     String prevEtag = getPreviousEtag(req);
 
     if (prevEtag != null) {
@@ -317,6 +325,13 @@ public class QueryResource implements 
QueryCountStatsProvider
     return req.getHeader(HEADER_IF_NONE_MATCH);
   }
 
+  protected ObjectMapper getMapperForRequest(String requestContentType)
+  {
+    boolean isSmile = 
SmileMediaTypes.APPLICATION_JACKSON_SMILE.equals(requestContentType) ||
+        APPLICATION_SMILE.equals(requestContentType);
+    return isSmile ? smileMapper : jsonMapper;
+  }
+
   protected ObjectMapper serializeDataTimeAsLong(ObjectMapper mapper)
   {
     return mapper.copy().registerModule(new 
SimpleModule().addSerializer(DateTime.class, new DateTimeSerializer()));
diff --git 
a/server/src/test/java/org/apache/druid/server/QueryResourceTest.java 
b/server/src/test/java/org/apache/druid/server/QueryResourceTest.java
index 9e48793..bcf557b 100644
--- a/server/src/test/java/org/apache/druid/server/QueryResourceTest.java
+++ b/server/src/test/java/org/apache/druid/server/QueryResourceTest.java
@@ -19,6 +19,7 @@
 
 package org.apache.druid.server;
 
+import com.fasterxml.jackson.jaxrs.smile.SmileMediaTypes;
 import com.fasterxml.jackson.core.type.TypeReference;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.google.common.base.Throwables;
@@ -52,6 +53,7 @@ import org.apache.druid.server.security.Authorizer;
 import org.apache.druid.server.security.AuthorizerMapper;
 import org.apache.druid.server.security.ForbiddenException;
 import org.apache.druid.server.security.Resource;
+import org.apache.http.HttpStatus;
 import org.easymock.EasyMock;
 import org.joda.time.Interval;
 import org.junit.After;
@@ -125,6 +127,7 @@ public class QueryResourceTest
   public void setup()
   {
     
EasyMock.expect(testServletRequest.getContentType()).andReturn(MediaType.APPLICATION_JSON).anyTimes();
+    
EasyMock.expect(testServletRequest.getHeader("Accept")).andReturn(MediaType.APPLICATION_JSON).anyTimes();
     
EasyMock.expect(testServletRequest.getHeader(QueryResource.HEADER_IF_NONE_MATCH)).andReturn(null).anyTimes();
     
EasyMock.expect(testServletRequest.getRemoteAddr()).andReturn("localhost").anyTimes();
     queryManager = new QueryManager();
@@ -188,6 +191,116 @@ public class QueryResourceTest
   }
 
   @Test
+  public void testGoodQueryWithNullAcceptHeader() throws IOException
+  {
+    final String acceptHeader = null;
+    final String contentTypeHeader = MediaType.APPLICATION_JSON;
+    EasyMock.reset(testServletRequest);
+
+    
EasyMock.expect(testServletRequest.getAttribute(AuthConfig.DRUID_AUTHORIZATION_CHECKED))
+        .andReturn(null)
+        .anyTimes();
+    
EasyMock.expect(testServletRequest.getAttribute(AuthConfig.DRUID_ALLOW_UNSECURED_PATH)).andReturn(null).anyTimes();
+
+    
EasyMock.expect(testServletRequest.getAttribute(AuthConfig.DRUID_AUTHENTICATION_RESULT))
+        .andReturn(authenticationResult)
+        .anyTimes();
+
+    testServletRequest.setAttribute(AuthConfig.DRUID_AUTHORIZATION_CHECKED, 
true);
+
+    
EasyMock.expect(testServletRequest.getHeader("Accept")).andReturn(acceptHeader).anyTimes();
+    
EasyMock.expect(testServletRequest.getContentType()).andReturn(contentTypeHeader).anyTimes();
+    
EasyMock.expect(testServletRequest.getHeader(QueryResource.HEADER_IF_NONE_MATCH)).andReturn(null).anyTimes();
+    
EasyMock.expect(testServletRequest.getRemoteAddr()).andReturn("localhost").anyTimes();
+
+    EasyMock.replay(testServletRequest);
+    Response response = queryResource.doPost(
+        new ByteArrayInputStream(simpleTimeSeriesQuery.getBytes("UTF-8")),
+        null /*pretty*/,
+        testServletRequest
+    );
+    Assert.assertEquals(HttpStatus.SC_OK, response.getStatus());
+    //since accept header is null, the response content type should be same as 
the value of 'Content-Type' header
+    Assert.assertEquals(contentTypeHeader, 
(response.getMetadata().get("Content-Type").get(0)).toString());
+    Assert.assertNotNull(response);
+  }
+
+  @Test
+  public void testGoodQueryWithEmptyAcceptHeader() throws IOException
+  {
+    final String acceptHeader = "";
+    final String contentTypeHeader = MediaType.APPLICATION_JSON;
+    EasyMock.reset(testServletRequest);
+
+    
EasyMock.expect(testServletRequest.getAttribute(AuthConfig.DRUID_AUTHORIZATION_CHECKED))
+        .andReturn(null)
+        .anyTimes();
+    
EasyMock.expect(testServletRequest.getAttribute(AuthConfig.DRUID_ALLOW_UNSECURED_PATH)).andReturn(null).anyTimes();
+
+    
EasyMock.expect(testServletRequest.getAttribute(AuthConfig.DRUID_AUTHENTICATION_RESULT))
+        .andReturn(authenticationResult)
+        .anyTimes();
+
+    testServletRequest.setAttribute(AuthConfig.DRUID_AUTHORIZATION_CHECKED, 
true);
+
+    
EasyMock.expect(testServletRequest.getHeader("Accept")).andReturn(acceptHeader).anyTimes();
+    
EasyMock.expect(testServletRequest.getContentType()).andReturn(contentTypeHeader).anyTimes();
+    
EasyMock.expect(testServletRequest.getHeader(QueryResource.HEADER_IF_NONE_MATCH)).andReturn(null).anyTimes();
+    
EasyMock.expect(testServletRequest.getRemoteAddr()).andReturn("localhost").anyTimes();
+
+    EasyMock.replay(testServletRequest);
+    Response response = queryResource.doPost(
+        new ByteArrayInputStream(simpleTimeSeriesQuery.getBytes("UTF-8")),
+        null /*pretty*/,
+        testServletRequest
+    );
+    Assert.assertEquals(HttpStatus.SC_OK, response.getStatus());
+    //since accept header is empty, the response content type should be same 
as the value of 'Content-Type' header
+    Assert.assertEquals(contentTypeHeader, 
(response.getMetadata().get("Content-Type").get(0)).toString());
+    Assert.assertNotNull(response);
+  }
+
+  @Test
+  public void testGoodQueryWithSmileAcceptHeader() throws IOException
+  {
+    //Doing a replay of testServletRequest for teardown to succeed.
+    //We dont use testServletRequest in this testcase
+    EasyMock.replay(testServletRequest);
+
+    //Creating our own Smile Servlet request, as to not disturb the remaining 
tests.
+    // else refactoring required for this class. i know this kinda makes the 
class somewhat Dirty.
+    final HttpServletRequest smileRequest = 
EasyMock.createMock(HttpServletRequest.class);
+    
EasyMock.expect(smileRequest.getContentType()).andReturn(MediaType.APPLICATION_JSON).anyTimes();
+
+    
EasyMock.expect(smileRequest.getAttribute(AuthConfig.DRUID_AUTHORIZATION_CHECKED))
+        .andReturn(null)
+        .anyTimes();
+    
EasyMock.expect(smileRequest.getAttribute(AuthConfig.DRUID_ALLOW_UNSECURED_PATH)).andReturn(null).anyTimes();
+
+    
EasyMock.expect(smileRequest.getAttribute(AuthConfig.DRUID_AUTHENTICATION_RESULT))
+        .andReturn(authenticationResult)
+        .anyTimes();
+
+    smileRequest.setAttribute(AuthConfig.DRUID_AUTHORIZATION_CHECKED, true);
+
+    
EasyMock.expect(smileRequest.getHeader("Accept")).andReturn(SmileMediaTypes.APPLICATION_JACKSON_SMILE).anyTimes();
+    
EasyMock.expect(smileRequest.getHeader(QueryResource.HEADER_IF_NONE_MATCH)).andReturn(null).anyTimes();
+    
EasyMock.expect(smileRequest.getRemoteAddr()).andReturn("localhost").anyTimes();
+
+    EasyMock.replay(smileRequest);
+    Response response = queryResource.doPost(
+        new ByteArrayInputStream(simpleTimeSeriesQuery.getBytes("UTF-8")),
+        null /*pretty*/,
+        smileRequest
+    );
+    Assert.assertEquals(HttpStatus.SC_OK, response.getStatus());
+    Assert.assertEquals(SmileMediaTypes.APPLICATION_JACKSON_SMILE, 
(response.getMetadata().get("Content-Type").get(0)).toString());
+    Assert.assertNotNull(response);
+    EasyMock.verify(smileRequest);
+  }
+
+
+  @Test
   public void testBadQuery() throws IOException
   {
     EasyMock.replay(testServletRequest);
@@ -287,7 +400,8 @@ public class QueryResourceTest
     Assert.assertEquals(Response.Status.OK.getStatusCode(), 
response.getStatus());
     Assert.assertEquals(0, responses.size());
     Assert.assertEquals(1, testRequestLogger.getLogs().size());
-    Assert.assertEquals(true, 
testRequestLogger.getLogs().get(0).getQueryStats().getStats().get("success"));
+    Assert.assertEquals(true,
+        
testRequestLogger.getLogs().get(0).getQueryStats().getStats().get("success"));
     Assert.assertEquals("druid", 
testRequestLogger.getLogs().get(0).getQueryStats().getStats().get("identity"));
   }
 
@@ -401,8 +515,7 @@ public class QueryResourceTest
     startAwaitLatch.await();
 
     Executors.newSingleThreadExecutor().submit(
-        new Runnable()
-        {
+        new Runnable() {
           @Override
           public void run()
           {
@@ -431,7 +544,7 @@ public class QueryResourceTest
     
EasyMock.expect(testServletRequest.getAttribute(AuthConfig.DRUID_ALLOW_UNSECURED_PATH)).andReturn(null).anyTimes();
 
     
EasyMock.expect(testServletRequest.getAttribute(AuthConfig.DRUID_AUTHENTICATION_RESULT))
-            .andReturn(authenticationResult)
+        .andReturn(authenticationResult)
             .anyTimes();
 
     testServletRequest.setAttribute(AuthConfig.DRUID_AUTHORIZATION_CHECKED, 
true);


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to