22
33import static graphql .kickstart .servlet .HttpRequestHandler .STATUS_BAD_REQUEST ;
44
5+ import graphql .ExecutionResultImpl ;
56import graphql .kickstart .execution .GraphQLInvoker ;
67import graphql .kickstart .execution .GraphQLQueryResult ;
8+ import graphql .kickstart .execution .error .GenericGraphQLError ;
79import graphql .kickstart .execution .input .GraphQLBatchedInvocationInput ;
810import graphql .kickstart .execution .input .GraphQLInvocationInput ;
911import graphql .kickstart .execution .input .GraphQLSingleInvocationInput ;
1012import graphql .kickstart .servlet .input .BatchInputPreProcessResult ;
1113import graphql .kickstart .servlet .input .BatchInputPreProcessor ;
1214import java .io .IOException ;
1315import java .io .UncheckedIOException ;
16+ import java .util .Optional ;
1417import java .util .concurrent .CompletableFuture ;
18+ import java .util .concurrent .atomic .AtomicReference ;
1519import javax .servlet .AsyncContext ;
1620import javax .servlet .http .HttpServletRequest ;
1721import javax .servlet .http .HttpServletResponse ;
@@ -31,43 +35,65 @@ public void execute(
3135 GraphQLInvocationInput invocationInput ,
3236 HttpServletRequest request ,
3337 HttpServletResponse response ) {
38+ ListenerHandler listenerHandler =
39+ ListenerHandler .start (request , response , configuration .getListeners ());
3440 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 );
4242 } else {
43- invokeAndHandle (invocationInput , request , response ).join ();
43+ invokeAndHandle (invocationInput , request , response , listenerHandler ).join ();
4444 }
4545 }
4646
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+
4777 private CompletableFuture <Void > invokeAndHandle (
4878 GraphQLInvocationInput invocationInput ,
4979 HttpServletRequest request ,
50- HttpServletResponse response ) {
51- ListenerHandler listenerHandler =
52- ListenerHandler .start (request , response , configuration .getListeners ());
80+ HttpServletResponse response ,
81+ ListenerHandler listenerHandler ) {
5382 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 ());
5987 }
6088
6189 private void writeResultResponse (
6290 GraphQLInvocationInput invocationInput ,
6391 GraphQLQueryResult queryResult ,
6492 HttpServletRequest request ,
65- HttpServletResponse response ,
66- ListenerHandler listenerHandler ) {
93+ HttpServletResponse response ) {
6794 QueryResponseWriter queryResponseWriter = createWriter (invocationInput , queryResult );
6895 try {
6996 queryResponseWriter .write (request , response );
70- listenerHandler .onSuccess ();
7197 } catch (IOException e ) {
7298 throw new UncheckedIOException (e );
7399 }
@@ -78,12 +104,18 @@ protected QueryResponseWriter createWriter(
78104 return queryResponseWriterFactory .createWriter (invocationInput , queryResult , configuration );
79105 }
80106
81- private Void writeErrorResponse (
107+ private Void writeBadRequestError (
82108 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+ }
87119 return null ;
88120 }
89121
0 commit comments