2
2
3
3
import static graphql .kickstart .servlet .HttpRequestHandler .STATUS_BAD_REQUEST ;
4
4
5
+ import graphql .ExecutionResultImpl ;
5
6
import graphql .kickstart .execution .GraphQLInvoker ;
6
7
import graphql .kickstart .execution .GraphQLQueryResult ;
8
+ import graphql .kickstart .execution .error .GenericGraphQLError ;
7
9
import graphql .kickstart .execution .input .GraphQLBatchedInvocationInput ;
8
10
import graphql .kickstart .execution .input .GraphQLInvocationInput ;
9
11
import graphql .kickstart .execution .input .GraphQLSingleInvocationInput ;
10
12
import graphql .kickstart .servlet .input .BatchInputPreProcessResult ;
11
13
import graphql .kickstart .servlet .input .BatchInputPreProcessor ;
12
14
import java .io .IOException ;
13
15
import java .io .UncheckedIOException ;
16
+ import java .util .Optional ;
14
17
import java .util .concurrent .CompletableFuture ;
18
+ import java .util .concurrent .atomic .AtomicReference ;
15
19
import javax .servlet .AsyncContext ;
16
20
import javax .servlet .http .HttpServletRequest ;
17
21
import javax .servlet .http .HttpServletResponse ;
@@ -31,43 +35,65 @@ public void execute(
31
35
GraphQLInvocationInput invocationInput ,
32
36
HttpServletRequest request ,
33
37
HttpServletResponse response ) {
38
+ ListenerHandler listenerHandler =
39
+ ListenerHandler .start (request , response , configuration .getListeners ());
34
40
if (request .isAsyncSupported ()) {
35
- AsyncContext asyncContext =
36
- request .isAsyncStarted ()
37
- ? request .getAsyncContext ()
38
- : request .startAsync (request , response );
39
- asyncContext .setTimeout (configuration .getAsyncTimeout ());
40
- invokeAndHandle (invocationInput , request , response )
41
- .thenAccept (aVoid -> asyncContext .complete ());
41
+ invokeAndHandleAsync (invocationInput , request , response , listenerHandler );
42
42
} else {
43
- invokeAndHandle (invocationInput , request , response ).join ();
43
+ invokeAndHandle (invocationInput , request , response , listenerHandler ).join ();
44
44
}
45
45
}
46
46
47
+ private void invokeAndHandleAsync (
48
+ GraphQLInvocationInput invocationInput ,
49
+ HttpServletRequest request ,
50
+ HttpServletResponse response ,
51
+ ListenerHandler listenerHandler ) {
52
+ AsyncContext asyncContext =
53
+ request .isAsyncStarted ()
54
+ ? request .getAsyncContext ()
55
+ : request .startAsync (request , response );
56
+ asyncContext .setTimeout (configuration .getAsyncTimeout ());
57
+ AtomicReference <CompletableFuture <Void >> futureHolder = new AtomicReference <>();
58
+ AsyncTimeoutListener timeoutListener =
59
+ event -> {
60
+ Optional .ofNullable (futureHolder .get ()).ifPresent (it -> it .cancel (true ));
61
+ writeResultResponse (
62
+ invocationInput ,
63
+ GraphQLQueryResult .create (
64
+ new ExecutionResultImpl (new GenericGraphQLError ("Timeout" ))),
65
+ (HttpServletRequest ) event .getAsyncContext ().getRequest (),
66
+ (HttpServletResponse ) event .getAsyncContext ().getResponse ());
67
+ listenerHandler .onError (event .getThrowable ());
68
+ };
69
+ asyncContext .addListener (timeoutListener );
70
+ asyncContext .start (
71
+ () ->
72
+ futureHolder .set (
73
+ invokeAndHandle (invocationInput , request , response , listenerHandler )
74
+ .thenAccept (aVoid -> asyncContext .complete ())));
75
+ }
76
+
47
77
private CompletableFuture <Void > invokeAndHandle (
48
78
GraphQLInvocationInput invocationInput ,
49
79
HttpServletRequest request ,
50
- HttpServletResponse response ) {
51
- ListenerHandler listenerHandler =
52
- ListenerHandler .start (request , response , configuration .getListeners ());
80
+ HttpServletResponse response ,
81
+ ListenerHandler listenerHandler ) {
53
82
return invoke (invocationInput , request , response )
54
- .thenAccept (
55
- result ->
56
- writeResultResponse (invocationInput , result , request , response , listenerHandler ))
57
- .exceptionally (t -> writeErrorResponse (t , response , listenerHandler ))
58
- .thenAccept (aVoid -> listenerHandler .onFinally ());
83
+ .thenAccept (it -> writeResultResponse (invocationInput , it , request , response ))
84
+ .thenAccept (it -> listenerHandler .onSuccess ())
85
+ .exceptionally (t -> writeBadRequestError (t , response , listenerHandler ))
86
+ .thenAccept (it -> listenerHandler .onFinally ());
59
87
}
60
88
61
89
private void writeResultResponse (
62
90
GraphQLInvocationInput invocationInput ,
63
91
GraphQLQueryResult queryResult ,
64
92
HttpServletRequest request ,
65
- HttpServletResponse response ,
66
- ListenerHandler listenerHandler ) {
93
+ HttpServletResponse response ) {
67
94
QueryResponseWriter queryResponseWriter = createWriter (invocationInput , queryResult );
68
95
try {
69
96
queryResponseWriter .write (request , response );
70
- listenerHandler .onSuccess ();
71
97
} catch (IOException e ) {
72
98
throw new UncheckedIOException (e );
73
99
}
@@ -78,12 +104,18 @@ protected QueryResponseWriter createWriter(
78
104
return queryResponseWriterFactory .createWriter (invocationInput , queryResult , configuration );
79
105
}
80
106
81
- private Void writeErrorResponse (
107
+ private Void writeBadRequestError (
82
108
Throwable t , HttpServletResponse response , ListenerHandler listenerHandler ) {
83
- response .setStatus (STATUS_BAD_REQUEST );
84
- log .info (
85
- "Bad request: path was not \" /schema.json\" or no query variable named \" query\" given" , t );
86
- listenerHandler .onError (t );
109
+ if (!response .isCommitted ()) {
110
+ response .setStatus (STATUS_BAD_REQUEST );
111
+ log .info (
112
+ "Bad request: path was not \" /schema.json\" or no query variable named \" query\" given" ,
113
+ t );
114
+ listenerHandler .onError (t );
115
+ } else {
116
+ log .warn (
117
+ "Cannot write GraphQL response, because the HTTP response is already committed. It most likely timed out." );
118
+ }
87
119
return null ;
88
120
}
89
121
0 commit comments