Skip to content

Commit 4d4020d

Browse files
committed
Added ability to cache the whole graphql response.
1 parent cba36c6 commit 4d4020d

11 files changed

+244
-23
lines changed

graphql-java-servlet/src/main/java/graphql/kickstart/servlet/BatchedQueryResponseWriter.java

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,29 @@
22

33
import graphql.ExecutionResult;
44
import graphql.kickstart.execution.GraphQLObjectMapper;
5+
import graphql.kickstart.execution.input.GraphQLInvocationInput;
6+
import graphql.kickstart.servlet.cache.CachedResponse;
7+
import graphql.kickstart.servlet.cache.GraphQLResponseCache;
8+
import lombok.RequiredArgsConstructor;
9+
import lombok.extern.slf4j.Slf4j;
10+
11+
import javax.servlet.http.HttpServletRequest;
12+
import javax.servlet.http.HttpServletResponse;
513
import java.io.IOException;
614
import java.nio.charset.StandardCharsets;
715
import java.util.Iterator;
816
import java.util.List;
9-
import javax.servlet.http.HttpServletRequest;
10-
import javax.servlet.http.HttpServletResponse;
11-
import lombok.RequiredArgsConstructor;
1217

18+
@Slf4j
1319
@RequiredArgsConstructor
1420
class BatchedQueryResponseWriter implements QueryResponseWriter {
1521

1622
private final List<ExecutionResult> results;
1723
private final GraphQLObjectMapper graphQLObjectMapper;
24+
private final GraphQLInvocationInput invocationInput;
1825

1926
@Override
20-
public void write(HttpServletRequest request, HttpServletResponse response) throws IOException {
27+
public void write(HttpServletRequest request, HttpServletResponse response, GraphQLResponseCache responseCache) throws IOException {
2128
response.setContentType(HttpRequestHandler.APPLICATION_JSON_UTF8);
2229
response.setStatus(HttpRequestHandler.STATUS_OK);
2330

@@ -34,6 +41,16 @@ public void write(HttpServletRequest request, HttpServletResponse response) thro
3441

3542
String responseContent = responseBuilder.toString();
3643
byte[] contentBytes = responseContent.getBytes(StandardCharsets.UTF_8);
44+
45+
if (responseCache != null) {
46+
try {
47+
responseCache.cacheResponse(invocationInput, CachedResponse.ofContent(contentBytes));
48+
} catch (Throwable t) {
49+
log.warn(t.getMessage(), t);
50+
log.warn("Ignore read from cache, unexpected error happened");
51+
}
52+
}
53+
3754
response.setContentLength(contentBytes.length);
3855
response.getOutputStream().write(contentBytes);
3956
}

graphql-java-servlet/src/main/java/graphql/kickstart/servlet/ErrorQueryResponseWriter.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,31 @@
33
import java.io.IOException;
44
import javax.servlet.http.HttpServletRequest;
55
import javax.servlet.http.HttpServletResponse;
6+
7+
import graphql.kickstart.execution.input.GraphQLInvocationInput;
8+
import graphql.kickstart.servlet.cache.CachedResponse;
9+
import graphql.kickstart.servlet.cache.GraphQLResponseCache;
610
import lombok.RequiredArgsConstructor;
11+
import lombok.extern.slf4j.Slf4j;
712

13+
@Slf4j
814
@RequiredArgsConstructor
915
class ErrorQueryResponseWriter implements QueryResponseWriter {
1016

1117
private final int statusCode;
1218
private final String message;
19+
private final GraphQLInvocationInput invocationInput;
1320

1421
@Override
15-
public void write(HttpServletRequest request, HttpServletResponse response) throws IOException {
22+
public void write(HttpServletRequest request, HttpServletResponse response, GraphQLResponseCache responseCache) throws IOException {
23+
if (responseCache != null) {
24+
try {
25+
responseCache.cacheResponse(invocationInput, CachedResponse.ofError(statusCode, message));
26+
} catch (Throwable t) {
27+
log.warn(t.getMessage(), t);
28+
log.warn("Ignore read from cache, unexpected error happened");
29+
}
30+
}
1631
response.sendError(statusCode, message);
1732
}
1833

graphql-java-servlet/src/main/java/graphql/kickstart/servlet/GraphQLConfiguration.java

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import graphql.kickstart.execution.GraphQLObjectMapper;
55
import graphql.kickstart.execution.GraphQLQueryInvoker;
66
import graphql.kickstart.execution.context.ContextSetting;
7+
import graphql.kickstart.servlet.cache.GraphQLResponseCache;
78
import graphql.kickstart.servlet.config.DefaultGraphQLSchemaServletProvider;
89
import graphql.kickstart.servlet.config.GraphQLSchemaServletProvider;
910
import graphql.kickstart.servlet.context.GraphQLServletContextBuilder;
@@ -33,12 +34,13 @@ public class GraphQLConfiguration {
3334
private final Executor asyncExecutor;
3435
private final long subscriptionTimeout;
3536
private final ContextSetting contextSetting;
37+
private final GraphQLResponseCache responseCache;
3638

3739
private GraphQLConfiguration(GraphQLInvocationInputFactory invocationInputFactory,
3840
GraphQLQueryInvoker queryInvoker,
3941
GraphQLObjectMapper objectMapper, List<GraphQLServletListener> listeners, boolean asyncServletModeEnabled,
4042
Executor asyncExecutor, long subscriptionTimeout, ContextSetting contextSetting,
41-
Supplier<BatchInputPreProcessor> batchInputPreProcessor) {
43+
Supplier<BatchInputPreProcessor> batchInputPreProcessor, GraphQLResponseCache responseCache) {
4244
this.invocationInputFactory = invocationInputFactory;
4345
this.queryInvoker = queryInvoker;
4446
this.graphQLInvoker = queryInvoker.toGraphQLInvoker();
@@ -49,6 +51,7 @@ private GraphQLConfiguration(GraphQLInvocationInputFactory invocationInputFactor
4951
this.subscriptionTimeout = subscriptionTimeout;
5052
this.contextSetting = contextSetting;
5153
this.batchInputPreProcessor = batchInputPreProcessor;
54+
this.responseCache = responseCache;
5255
}
5356

5457
public static GraphQLConfiguration.Builder with(GraphQLSchema schema) {
@@ -109,6 +112,10 @@ public BatchInputPreProcessor getBatchInputPreProcessor() {
109112
return batchInputPreProcessor.get();
110113
}
111114

115+
public GraphQLResponseCache getResponseCache() {
116+
return responseCache;
117+
}
118+
112119
public static class Builder {
113120

114121
private GraphQLInvocationInputFactory.Builder invocationInputFactoryBuilder;
@@ -121,6 +128,7 @@ public static class Builder {
121128
private long subscriptionTimeout = 0;
122129
private ContextSetting contextSetting = ContextSetting.PER_QUERY_WITH_INSTRUMENTATION;
123130
private Supplier<BatchInputPreProcessor> batchInputPreProcessorSupplier = () -> new NoOpBatchInputPreProcessor();
131+
private GraphQLResponseCache responseCache;
124132

125133
private Builder(GraphQLInvocationInputFactory.Builder invocationInputFactoryBuilder) {
126134
this.invocationInputFactoryBuilder = invocationInputFactoryBuilder;
@@ -199,6 +207,11 @@ public Builder with(Supplier<BatchInputPreProcessor> batchInputPreProcessor) {
199207
return this;
200208
}
201209

210+
public Builder with(GraphQLResponseCache responseCache) {
211+
this.responseCache = responseCache;
212+
return this;
213+
}
214+
202215
public GraphQLConfiguration build() {
203216
return new GraphQLConfiguration(
204217
this.invocationInputFactory != null ? this.invocationInputFactory : invocationInputFactoryBuilder.build(),
@@ -209,7 +222,8 @@ public GraphQLConfiguration build() {
209222
asyncExecutor,
210223
subscriptionTimeout,
211224
contextSetting,
212-
batchInputPreProcessorSupplier
225+
batchInputPreProcessorSupplier,
226+
responseCache
213227
);
214228
}
215229

graphql-java-servlet/src/main/java/graphql/kickstart/servlet/HttpRequestHandler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import javax.servlet.http.HttpServletRequest;
44
import javax.servlet.http.HttpServletResponse;
55

6-
interface HttpRequestHandler {
6+
public interface HttpRequestHandler {
77

88
String APPLICATION_JSON_UTF8 = "application/json;charset=UTF-8";
99
String APPLICATION_EVENT_STREAM_UTF8 = "text/event-stream;charset=UTF-8";

graphql-java-servlet/src/main/java/graphql/kickstart/servlet/HttpRequestHandlerImpl.java

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,18 @@
33
import graphql.GraphQLException;
44
import graphql.kickstart.execution.GraphQLInvoker;
55
import graphql.kickstart.execution.GraphQLQueryResult;
6-
import graphql.kickstart.servlet.input.BatchInputPreProcessResult;
7-
import graphql.kickstart.servlet.input.BatchInputPreProcessor;
86
import graphql.kickstart.execution.input.GraphQLBatchedInvocationInput;
97
import graphql.kickstart.execution.input.GraphQLInvocationInput;
108
import graphql.kickstart.execution.input.GraphQLSingleInvocationInput;
11-
import java.io.IOException;
9+
import graphql.kickstart.servlet.cache.CacheResponseWriter;
10+
import graphql.kickstart.servlet.cache.CachedResponse;
11+
import graphql.kickstart.servlet.input.BatchInputPreProcessResult;
12+
import graphql.kickstart.servlet.input.BatchInputPreProcessor;
13+
import lombok.extern.slf4j.Slf4j;
14+
1215
import javax.servlet.http.HttpServletRequest;
1316
import javax.servlet.http.HttpServletResponse;
14-
import lombok.extern.slf4j.Slf4j;
17+
import java.io.IOException;
1518

1619
@Slf4j
1720
class HttpRequestHandlerImpl implements HttpRequestHandler {
@@ -49,11 +52,31 @@ public void handle(HttpServletRequest request, HttpServletResponse response) thr
4952
private void execute(GraphQLInvocationInput invocationInput, HttpServletRequest request,
5053
HttpServletResponse response) {
5154
try {
55+
if (configuration.getResponseCache() != null) {
56+
CachedResponse cachedResponse = null;
57+
try {
58+
cachedResponse = configuration.getResponseCache().getCachedResponse(invocationInput);
59+
} catch (Throwable t) {
60+
log.warn(t.getMessage(), t);
61+
log.warn("Ignore read from cache, unexpected error happened");
62+
}
63+
64+
if (cachedResponse != null) {
65+
CacheResponseWriter cacheResponseWriter = new CacheResponseWriter();
66+
cacheResponseWriter.write(request, response, cachedResponse);
67+
return;
68+
}
69+
}
70+
5271
GraphQLQueryResult queryResult = invoke(invocationInput, request, response);
5372

54-
QueryResponseWriter queryResponseWriter = QueryResponseWriter.createWriter(queryResult, configuration.getObjectMapper(),
55-
configuration.getSubscriptionTimeout());
56-
queryResponseWriter.write(request, response);
73+
QueryResponseWriter queryResponseWriter = QueryResponseWriter.createWriter(
74+
queryResult,
75+
configuration.getObjectMapper(),
76+
configuration.getSubscriptionTimeout(),
77+
invocationInput
78+
);
79+
queryResponseWriter.write(request, response, configuration.getResponseCache());
5780
} catch (Throwable t) {
5881
response.setStatus(STATUS_BAD_REQUEST);
5982
log.info("Bad GET request: path was not \"/schema.json\" or no query variable named \"query\" given");

graphql-java-servlet/src/main/java/graphql/kickstart/servlet/QueryResponseWriter.java

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
import graphql.kickstart.execution.GraphQLQueryResult;
44
import graphql.kickstart.execution.GraphQLObjectMapper;
5+
import graphql.kickstart.execution.input.GraphQLInvocationInput;
6+
import graphql.kickstart.servlet.cache.GraphQLResponseCache;
7+
58
import java.io.IOException;
69
import java.util.Objects;
710
import javax.servlet.http.HttpServletRequest;
@@ -12,20 +15,21 @@ interface QueryResponseWriter {
1215
static QueryResponseWriter createWriter(
1316
GraphQLQueryResult result,
1417
GraphQLObjectMapper graphQLObjectMapper,
15-
long subscriptionTimeout
18+
long subscriptionTimeout,
19+
GraphQLInvocationInput invocationInput
1620
) {
1721
Objects.requireNonNull(result, "GraphQL query result cannot be null");
1822

1923
if (result.isBatched()) {
20-
return new BatchedQueryResponseWriter(result.getResults(), graphQLObjectMapper);
24+
return new BatchedQueryResponseWriter(result.getResults(), graphQLObjectMapper, invocationInput);
2125
} else if (result.isAsynchronous()) {
22-
return new SingleAsynchronousQueryResponseWriter(result.getResult(), graphQLObjectMapper, subscriptionTimeout);
26+
return new SingleAsynchronousQueryResponseWriter(result.getResult(), graphQLObjectMapper, subscriptionTimeout, invocationInput);
2327
} else if (result.isError()) {
24-
return new ErrorQueryResponseWriter(result.getStatusCode(), result.getMessage());
28+
return new ErrorQueryResponseWriter(result.getStatusCode(), result.getMessage(), invocationInput);
2529
}
26-
return new SingleQueryResponseWriter(result.getResult(), graphQLObjectMapper);
30+
return new SingleQueryResponseWriter(result.getResult(), graphQLObjectMapper, invocationInput);
2731
}
2832

29-
void write(HttpServletRequest request, HttpServletResponse response) throws IOException;
33+
void write(HttpServletRequest request, HttpServletResponse response, GraphQLResponseCache responseCache) throws IOException;
3034

3135
}

graphql-java-servlet/src/main/java/graphql/kickstart/servlet/SingleAsynchronousQueryResponseWriter.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,31 @@
1010
import javax.servlet.AsyncContext;
1111
import javax.servlet.http.HttpServletRequest;
1212
import javax.servlet.http.HttpServletResponse;
13+
14+
import graphql.kickstart.execution.input.GraphQLInvocationInput;
15+
import graphql.kickstart.servlet.cache.GraphQLResponseCache;
1316
import lombok.Getter;
1417
import lombok.RequiredArgsConstructor;
18+
import lombok.extern.slf4j.Slf4j;
1519
import org.reactivestreams.Publisher;
1620
import org.reactivestreams.Subscription;
1721

22+
@Slf4j
1823
@RequiredArgsConstructor
1924
class SingleAsynchronousQueryResponseWriter implements QueryResponseWriter {
2025

2126
@Getter
2227
private final ExecutionResult result;
2328
private final GraphQLObjectMapper graphQLObjectMapper;
2429
private final long subscriptionTimeout;
30+
private final GraphQLInvocationInput invocationInput;
2531

2632
@Override
27-
public void write(HttpServletRequest request, HttpServletResponse response) {
33+
public void write(HttpServletRequest request, HttpServletResponse response, GraphQLResponseCache responseCache) {
34+
if (responseCache != null) {
35+
log.warn("Response cache for asynchronous query are not implemented yet");
36+
}
37+
2838
Objects.requireNonNull(request, "Http servlet request cannot be null");
2939
response.setContentType(HttpRequestHandler.APPLICATION_EVENT_STREAM_UTF8);
3040
response.setStatus(HttpRequestHandler.STATUS_OK);

graphql-java-servlet/src/main/java/graphql/kickstart/servlet/SingleQueryResponseWriter.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,42 @@
22

33
import graphql.ExecutionResult;
44
import graphql.kickstart.execution.GraphQLObjectMapper;
5+
import graphql.kickstart.execution.input.GraphQLInvocationInput;
6+
import graphql.kickstart.servlet.cache.CachedResponse;
7+
import graphql.kickstart.servlet.cache.GraphQLResponseCache;
58
import lombok.RequiredArgsConstructor;
9+
import lombok.extern.slf4j.Slf4j;
610

711
import java.io.IOException;
812
import java.nio.charset.StandardCharsets;
913
import javax.servlet.http.HttpServletRequest;
1014
import javax.servlet.http.HttpServletResponse;
1115

16+
@Slf4j
1217
@RequiredArgsConstructor
1318
class SingleQueryResponseWriter implements QueryResponseWriter {
1419

1520
private final ExecutionResult result;
1621
private final GraphQLObjectMapper graphQLObjectMapper;
22+
private final GraphQLInvocationInput invocationInput;
1723

1824
@Override
19-
public void write(HttpServletRequest request, HttpServletResponse response) throws IOException {
25+
public void write(HttpServletRequest request, HttpServletResponse response, GraphQLResponseCache responseCache) throws IOException {
2026
response.setContentType(HttpRequestHandler.APPLICATION_JSON_UTF8);
2127
response.setStatus(HttpRequestHandler.STATUS_OK);
2228
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
2329
String responseContent = graphQLObjectMapper.serializeResultAsJson(result);
2430
byte[] contentBytes = responseContent.getBytes(StandardCharsets.UTF_8);
31+
32+
if (responseCache != null) {
33+
try {
34+
responseCache.cacheResponse(invocationInput, CachedResponse.ofContent(contentBytes));
35+
} catch (Throwable t) {
36+
log.warn(t.getMessage(), t);
37+
log.warn("Ignore read from cache, unexpected error happened");
38+
}
39+
}
40+
2541
response.setContentLength(contentBytes.length);
2642
response.getOutputStream().write(contentBytes);
2743
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package graphql.kickstart.servlet.cache;
2+
3+
import graphql.kickstart.servlet.HttpRequestHandler;
4+
5+
import javax.servlet.http.HttpServletRequest;
6+
import javax.servlet.http.HttpServletResponse;
7+
import java.io.IOException;
8+
import java.nio.charset.StandardCharsets;
9+
10+
public class CacheResponseWriter {
11+
12+
public void write(HttpServletRequest request, HttpServletResponse response, CachedResponse cachedResponse)
13+
throws IOException {
14+
if (cachedResponse.isError()) {
15+
response.sendError(cachedResponse.getErrorStatusCode(), cachedResponse.getErrorMessage());
16+
} else {
17+
response.setContentType(HttpRequestHandler.APPLICATION_JSON_UTF8);
18+
response.setStatus(HttpRequestHandler.STATUS_OK);
19+
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
20+
response.setContentLength(cachedResponse.getContentBytes().length);
21+
response.getOutputStream().write(cachedResponse.getContentBytes());
22+
}
23+
}
24+
25+
}

0 commit comments

Comments
 (0)