Skip to content

Commit ea2dec6

Browse files
authored
Merge pull request #439 from heowc/gh-417
Add listener for errors that occur in parsing `InvocationInput`
2 parents 6fc85df + 99ba5e7 commit ea2dec6

9 files changed

+88
-31
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package graphql.kickstart.servlet;
22

3-
import com.fasterxml.jackson.core.JsonProcessingException;
43
import graphql.GraphQLException;
54
import graphql.kickstart.execution.input.GraphQLInvocationInput;
65
import java.io.IOException;
@@ -15,7 +14,7 @@ class HttpRequestHandlerImpl implements HttpRequestHandler {
1514
private final GraphQLConfiguration configuration;
1615
private final HttpRequestInvoker requestInvoker;
1716

18-
public HttpRequestHandlerImpl(GraphQLConfiguration configuration) {
17+
HttpRequestHandlerImpl(GraphQLConfiguration configuration) {
1918
this(
2019
configuration,
2120
new HttpRequestInvokerImpl(
@@ -24,28 +23,30 @@ public HttpRequestHandlerImpl(GraphQLConfiguration configuration) {
2423
new QueryResponseWriterFactoryImpl()));
2524
}
2625

27-
public HttpRequestHandlerImpl(
26+
HttpRequestHandlerImpl(
2827
GraphQLConfiguration configuration, HttpRequestInvoker requestInvoker) {
2928
this.configuration = configuration;
3029
this.requestInvoker = requestInvoker;
3130
}
3231

3332
@Override
3433
public void handle(HttpServletRequest request, HttpServletResponse response) throws IOException {
34+
if (request.getCharacterEncoding() == null) {
35+
request.setCharacterEncoding(StandardCharsets.UTF_8.name());
36+
}
37+
38+
ListenerHandler listenerHandler =
39+
ListenerHandler.start(request, response, configuration.getListeners());
40+
3541
try {
36-
if (request.getCharacterEncoding() == null) {
37-
request.setCharacterEncoding(StandardCharsets.UTF_8.name());
38-
}
39-
GraphQLInvocationInputParser invocationInputParser =
40-
GraphQLInvocationInputParser.create(
41-
request,
42-
configuration.getInvocationInputFactory(),
43-
configuration.getObjectMapper(),
44-
configuration.getContextSetting());
45-
GraphQLInvocationInput invocationInput =
46-
invocationInputParser. getGraphQLInvocationInput(request, response);
47-
requestInvoker.execute(invocationInput, request, response);
48-
} catch (GraphQLException | JsonProcessingException e) {
42+
GraphQLInvocationInput invocationInput = parseInvocationInput(request, response);
43+
requestInvoker.execute(invocationInput, request, response, listenerHandler);
44+
} catch (InvocationInputParseException e) {
45+
response.setStatus(STATUS_BAD_REQUEST);
46+
log.info("Bad request: cannot parse http request", e);
47+
listenerHandler.onParseError(e);
48+
throw e;
49+
} catch (GraphQLException e) {
4950
response.setStatus(STATUS_BAD_REQUEST);
5051
log.info("Bad request: cannot handle http request", e);
5152
throw e;
@@ -55,4 +56,20 @@ public void handle(HttpServletRequest request, HttpServletResponse response) thr
5556
throw t;
5657
}
5758
}
59+
60+
private GraphQLInvocationInput parseInvocationInput(
61+
HttpServletRequest request,
62+
HttpServletResponse response) {
63+
try {
64+
GraphQLInvocationInputParser invocationInputParser =
65+
GraphQLInvocationInputParser.create(
66+
request,
67+
configuration.getInvocationInputFactory(),
68+
configuration.getObjectMapper(),
69+
configuration.getContextSetting());
70+
return invocationInputParser.getGraphQLInvocationInput(request, response);
71+
} catch (Exception e) {
72+
throw new InvocationInputParseException(e);
73+
}
74+
}
5875
}

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,6 @@ public interface HttpRequestInvoker {
99
void execute(
1010
GraphQLInvocationInput invocationInput,
1111
HttpServletRequest request,
12-
HttpServletResponse response);
12+
HttpServletResponse response,
13+
ListenerHandler listenerHandler);
1314
}

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

+2-3
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,8 @@ public class HttpRequestInvokerImpl implements HttpRequestInvoker {
3939
public void execute(
4040
GraphQLInvocationInput invocationInput,
4141
HttpServletRequest request,
42-
HttpServletResponse response) {
43-
ListenerHandler listenerHandler =
44-
ListenerHandler.start(request, response, configuration.getListeners());
42+
HttpServletResponse response,
43+
ListenerHandler listenerHandler) {
4544
if (request.isAsyncSupported()) {
4645
invokeAndHandleAsync(invocationInput, request, response, listenerHandler);
4746
} else {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package graphql.kickstart.servlet;
2+
3+
public class InvocationInputParseException extends RuntimeException {
4+
5+
public InvocationInputParseException(Throwable t) {
6+
super("Request parsing failed", t);
7+
}
8+
}

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

+5-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
@Slf4j
1818
@RequiredArgsConstructor
19-
class ListenerHandler {
19+
public class ListenerHandler {
2020

2121
private final List<RequestCallback> callbacks;
2222
private final HttpServletRequest request;
@@ -60,6 +60,10 @@ void runCallbacks(Consumer<RequestCallback> action) {
6060
});
6161
}
6262

63+
void onParseError(Throwable throwable) {
64+
runCallbacks(it -> it.onParseError(request, response, throwable));
65+
}
66+
6367
void beforeFlush() {
6468
runCallbacks(it -> it.beforeFlush(request, response));
6569
}

graphql-java-servlet/src/main/java/graphql/kickstart/servlet/cache/CachingHttpRequestInvoker.java

+4-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import graphql.kickstart.servlet.GraphQLConfiguration;
77
import graphql.kickstart.servlet.HttpRequestInvoker;
88
import graphql.kickstart.servlet.HttpRequestInvokerImpl;
9+
import graphql.kickstart.servlet.ListenerHandler;
910
import java.io.IOException;
1011
import javax.servlet.http.HttpServletRequest;
1112
import javax.servlet.http.HttpServletResponse;
@@ -36,11 +37,12 @@ public CachingHttpRequestInvoker(GraphQLConfiguration configuration) {
3637
public void execute(
3738
GraphQLInvocationInput invocationInput,
3839
HttpServletRequest request,
39-
HttpServletResponse response) {
40+
HttpServletResponse response,
41+
ListenerHandler listenerHandler) {
4042
try {
4143
if (!cacheReader.responseFromCache(
4244
invocationInput, request, response, configuration.getResponseCacheManager())) {
43-
requestInvoker.execute(invocationInput, request, response);
45+
requestInvoker.execute(invocationInput, request, response, listenerHandler);
4446
}
4547
} catch (IOException e) {
4648
response.setStatus(STATUS_BAD_REQUEST);

graphql-java-servlet/src/main/java/graphql/kickstart/servlet/core/GraphQLServletListener.java

+8
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,14 @@ default RequestCallback onRequest(HttpServletRequest request, HttpServletRespons
2121
*/
2222
interface RequestCallback {
2323

24+
/**
25+
* Called when failed to parse InvocationInput and the response was not written.
26+
* @param request http request
27+
* @param response http response
28+
*/
29+
default void onParseError(
30+
HttpServletRequest request, HttpServletResponse response, Throwable throwable) {}
31+
2432
/**
2533
* Called right before the response will be written and flushed. Can be used for applying some
2634
* changes to the response object, like adding response headers.

graphql-java-servlet/src/test/groovy/graphql/kickstart/servlet/AbstractGraphQLHttpServletSpec.groovy

+17-2
Original file line numberDiff line numberDiff line change
@@ -755,6 +755,22 @@ b
755755
getResponseContent().data.echoFiles == ["test", "test again"]
756756
}
757757

758+
def "errors while accessing file from the request"() {
759+
setup:
760+
request = Spy(MockHttpServletRequest)
761+
request.setMethod("POST")
762+
request.setContentType("multipart/form-data, boundary=test")
763+
// See https://github.com/apache/tomcat/blob/main/java/org/apache/catalina/connector/Request.java#L2775...L2791
764+
request.getParts() >> { throw new IllegalStateException() }
765+
766+
when:
767+
servlet.doPost(request, response)
768+
769+
then:
770+
response.getStatus() == STATUS_BAD_REQUEST
771+
response.getContentLength() == 0
772+
}
773+
758774
def "batched query over HTTP POST body returns data"() {
759775
setup:
760776
request.setContent('[{ "query": "query { echo(arg:\\"test\\") }" }, { "query": "query { echo(arg:\\"test\\") }" }]'.bytes)
@@ -1112,8 +1128,7 @@ b
11121128
servlet.doGet(request, response)
11131129

11141130
then:
1115-
noExceptionThrown()
1116-
response.getStatus() == STATUS_ERROR
1131+
response.getStatus() == STATUS_BAD_REQUEST
11171132
}
11181133

11191134
def "errors while data fetching are masked in the response"() {

graphql-java-servlet/src/test/groovy/graphql/kickstart/servlet/cache/CachingHttpRequestInvokerTest.groovy

+9-6
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import graphql.kickstart.execution.GraphQLQueryResult
88
import graphql.kickstart.execution.input.GraphQLSingleInvocationInput
99
import graphql.kickstart.servlet.GraphQLConfiguration
1010
import graphql.kickstart.servlet.HttpRequestInvoker
11+
import graphql.kickstart.servlet.ListenerHandler
1112
import spock.lang.Specification
1213

1314
import javax.servlet.ServletOutputStream
@@ -28,6 +29,7 @@ class CachingHttpRequestInvokerTest extends Specification {
2829
def configuration
2930
def graphqlObjectMapper
3031
def outputStreamMock
32+
def listenerHandlerMock
3133

3234
def setup() {
3335
cacheReaderMock = Mock(CacheReader)
@@ -42,6 +44,7 @@ class CachingHttpRequestInvokerTest extends Specification {
4244
outputStreamMock = Mock(ServletOutputStream)
4345
graphqlInvoker.execute(invocationInputMock) >> FutureExecutionResult.single(invocationInputMock, CompletableFuture.completedFuture(Mock(GraphQLQueryResult)))
4446
cachingInvoker = new CachingHttpRequestInvoker(configuration, httpRequestInvokerMock, cacheReaderMock)
47+
listenerHandlerMock = Mock(ListenerHandler)
4548

4649
configuration.getResponseCacheManager() >> responseCacheManagerMock
4750
configuration.getGraphQLInvoker() >> graphqlInvoker
@@ -57,29 +60,29 @@ class CachingHttpRequestInvokerTest extends Specification {
5760
cacheReaderMock.responseFromCache(invocationInputMock, requestMock, responseMock, responseCacheManagerMock) >> false
5861

5962
when:
60-
cachingInvoker.execute(invocationInputMock, requestMock, responseMock)
63+
cachingInvoker.execute(invocationInputMock, requestMock, responseMock, listenerHandlerMock)
6164

6265
then:
63-
1 * httpRequestInvokerMock.execute(invocationInputMock, requestMock, responseMock)
66+
1 * httpRequestInvokerMock.execute(invocationInputMock, requestMock, responseMock, listenerHandlerMock)
6467
}
6568

6669
def "should not execute regular invoker if cache exists"() {
6770
given:
6871
cacheReaderMock.responseFromCache(invocationInputMock, requestMock, responseMock, responseCacheManagerMock) >> true
6972

7073
when:
71-
cachingInvoker.execute(invocationInputMock, requestMock, responseMock)
74+
cachingInvoker.execute(invocationInputMock, requestMock, responseMock, listenerHandlerMock)
7275

7376
then:
74-
0 * httpRequestInvokerMock.execute(invocationInputMock, requestMock, responseMock)
77+
0 * httpRequestInvokerMock.execute(invocationInputMock, requestMock, responseMock, listenerHandlerMock)
7578
}
7679

7780
def "should return bad request response when ioexception"() {
7881
given:
7982
cacheReaderMock.responseFromCache(invocationInputMock, requestMock, responseMock, responseCacheManagerMock) >> { throw new IOException() }
8083

8184
when:
82-
cachingInvoker.execute(invocationInputMock, requestMock, responseMock)
85+
cachingInvoker.execute(invocationInputMock, requestMock, responseMock, listenerHandlerMock)
8386

8487
then:
8588
1 * responseMock.setStatus(400)
@@ -90,7 +93,7 @@ class CachingHttpRequestInvokerTest extends Specification {
9093
def invoker = new CachingHttpRequestInvoker(configuration)
9194

9295
when:
93-
invoker.execute(invocationInputMock, requestMock, responseMock)
96+
invoker.execute(invocationInputMock, requestMock, responseMock, listenerHandlerMock)
9497

9598
then:
9699
noExceptionThrown()

0 commit comments

Comments
 (0)