1
1
package graphql .servlet ;
2
2
3
- import static graphql .servlet .HttpRequestHandler .APPLICATION_GRAPHQL ;
4
- import static graphql .servlet .HttpRequestHandler .STATUS_BAD_REQUEST ;
5
-
6
- import com .google .common .io .ByteStreams ;
7
- import com .google .common .io .CharStreams ;
8
- import graphql .ExecutionResult ;
9
- import graphql .GraphQL ;
10
- import graphql .execution .reactive .SingleSubscriberPublisher ;
11
- import graphql .introspection .IntrospectionQuery ;
12
3
import graphql .schema .GraphQLFieldDefinition ;
13
4
import graphql .servlet .config .GraphQLConfiguration ;
14
- import graphql .servlet .context .ContextSetting ;
15
5
import graphql .servlet .core .GraphQLMBean ;
16
6
import graphql .servlet .core .GraphQLObjectMapper ;
17
7
import graphql .servlet .core .GraphQLQueryInvoker ;
18
8
import graphql .servlet .core .GraphQLServletListener ;
19
9
import graphql .servlet .core .internal .GraphQLRequest ;
20
- import graphql .servlet .core .internal .VariableMapper ;
21
- import graphql .servlet .input .BatchInputPreProcessResult ;
22
- import graphql .servlet .input .BatchInputPreProcessor ;
23
- import graphql .servlet .input .GraphQLBatchedInvocationInput ;
24
10
import graphql .servlet .input .GraphQLInvocationInputFactory ;
25
- import graphql .servlet .input .GraphQLSingleInvocationInput ;
26
- import java .io .BufferedInputStream ;
27
- import java .io .ByteArrayOutputStream ;
28
- import java .io .IOException ;
29
- import java .io .InputStream ;
30
- import java .io .Writer ;
31
11
import java .util .ArrayList ;
32
- import java .util .Arrays ;
33
12
import java .util .HashMap ;
34
- import java .util .Iterator ;
35
13
import java .util .List ;
36
- import java .util .Map ;
37
14
import java .util .Objects ;
38
- import java .util .Optional ;
39
- import java .util .concurrent .CountDownLatch ;
40
- import java .util .concurrent .atomic .AtomicReference ;
41
- import java .util .function .BiConsumer ;
42
15
import java .util .function .Consumer ;
43
16
import java .util .function .Function ;
44
17
import java .util .stream .Collectors ;
45
18
import javax .servlet .AsyncContext ;
46
- import javax .servlet .AsyncEvent ;
47
- import javax .servlet .AsyncListener ;
48
19
import javax .servlet .Servlet ;
49
20
import javax .servlet .http .HttpServlet ;
50
21
import javax .servlet .http .HttpServletRequest ;
51
22
import javax .servlet .http .HttpServletResponse ;
52
- import javax .servlet .http .Part ;
53
23
import lombok .extern .slf4j .Slf4j ;
54
- import org .reactivestreams .Publisher ;
55
- import org .reactivestreams .Subscriber ;
56
- import org .reactivestreams .Subscription ;
57
24
58
25
/**
59
26
* @author Andrew Potter
60
27
*/
61
28
@ Slf4j
62
29
public abstract class AbstractGraphQLHttpServlet extends HttpServlet implements Servlet , GraphQLMBean {
63
30
64
- private static final String [] MULTIPART_KEYS = new String []{"operations" , "graphql" , "query" };
65
31
/**
66
32
* @deprecated use {@link #getConfiguration()} instead
67
33
*/
68
34
@ Deprecated
69
35
private final List <GraphQLServletListener > listeners ;
70
36
private GraphQLConfiguration configuration ;
71
- private HttpRequestHandler getHandler ;
72
- private HttpRequestHandler postHandler ;
37
+ private HttpRequestHandler requestHandler ;
73
38
74
39
public AbstractGraphQLHttpServlet () {
75
40
this (null );
@@ -114,138 +79,10 @@ protected GraphQLConfiguration getConfiguration() {
114
79
115
80
@ Override
116
81
public void init () {
117
- if (configuration != null ) {
118
- return ;
82
+ if (configuration == null ) {
83
+ this .configuration = getConfiguration ();
84
+ this .requestHandler = new HttpRequestHandlerImpl (configuration );
119
85
}
120
- this .configuration = getConfiguration ();
121
- this .getHandler = new HttpGetRequestHandler (configuration );
122
-
123
- this .postHandler = (request , response ) -> {
124
- GraphQLInvocationInputFactory invocationInputFactory = configuration .getInvocationInputFactory ();
125
- GraphQLObjectMapper graphQLObjectMapper = configuration .getObjectMapper ();
126
- GraphQLQueryInvoker queryInvoker = configuration .getQueryInvoker ();
127
-
128
- try {
129
- if (APPLICATION_GRAPHQL .equals (request .getContentType ())) {
130
- String query = CharStreams .toString (request .getReader ());
131
- query (queryInvoker , graphQLObjectMapper ,
132
- invocationInputFactory .create (new GraphQLRequest (query , null , null ), request , response ),
133
- request , response );
134
- } else if (request .getContentType () != null && request .getContentType ().startsWith ("multipart/form-data" )
135
- && !request .getParts ().isEmpty ()) {
136
- final Map <String , List <Part >> fileItems = request .getParts ()
137
- .stream ()
138
- .collect (Collectors .groupingBy (Part ::getName ));
139
-
140
- for (String key : MULTIPART_KEYS ) {
141
- // Check to see if there is a part under the key we seek
142
- if (!fileItems .containsKey (key )) {
143
- continue ;
144
- }
145
-
146
- final Optional <Part > queryItem = getFileItem (fileItems , key );
147
- if (!queryItem .isPresent ()) {
148
- // If there is a part, but we don't see an item, then break and return BAD_REQUEST
149
- break ;
150
- }
151
-
152
- InputStream inputStream = asMarkableInputStream (queryItem .get ().getInputStream ());
153
-
154
- final Optional <Map <String , List <String >>> variablesMap =
155
- getFileItem (fileItems , "map" ).map (graphQLObjectMapper ::deserializeMultipartMap );
156
-
157
- if (isBatchedQuery (inputStream )) {
158
- List <GraphQLRequest > graphQLRequests =
159
- graphQLObjectMapper .readBatchedGraphQLRequest (inputStream );
160
- variablesMap .ifPresent (map -> graphQLRequests .forEach (r -> mapMultipartVariables (r , map , fileItems )));
161
- GraphQLBatchedInvocationInput batchedInvocationInput = invocationInputFactory
162
- .create (configuration .getContextSetting (),
163
- graphQLRequests , request , response );
164
- queryBatched (queryInvoker , batchedInvocationInput , request , response , configuration );
165
- return ;
166
- } else {
167
- GraphQLRequest graphQLRequest ;
168
- if ("query" .equals (key )) {
169
- graphQLRequest = buildRequestFromQuery (inputStream , graphQLObjectMapper , fileItems );
170
- } else {
171
- graphQLRequest = graphQLObjectMapper .readGraphQLRequest (inputStream );
172
- }
173
-
174
- variablesMap .ifPresent (m -> mapMultipartVariables (graphQLRequest , m , fileItems ));
175
- GraphQLSingleInvocationInput invocationInput =
176
- invocationInputFactory .create (graphQLRequest , request , response );
177
- query (queryInvoker , graphQLObjectMapper , invocationInput , request , response );
178
- return ;
179
- }
180
- }
181
-
182
- response .setStatus (STATUS_BAD_REQUEST );
183
- log .info ("Bad POST multipart request: no part named " + Arrays .toString (MULTIPART_KEYS ));
184
- } else {
185
- // this is not a multipart request
186
- InputStream inputStream = asMarkableInputStream (request .getInputStream ());
187
-
188
- if (isBatchedQuery (inputStream )) {
189
- List <GraphQLRequest > requests = graphQLObjectMapper .readBatchedGraphQLRequest (inputStream );
190
- GraphQLBatchedInvocationInput batchedInvocationInput =
191
- invocationInputFactory .create (configuration .getContextSetting (), requests , request , response );
192
- queryBatched (queryInvoker , batchedInvocationInput , request , response , configuration );
193
- } else {
194
- query (queryInvoker , graphQLObjectMapper ,
195
- invocationInputFactory .create (graphQLObjectMapper .readGraphQLRequest (inputStream ), request , response ),
196
- request , response );
197
- }
198
- }
199
- } catch (Exception e ) {
200
- log .info ("Bad POST request: parsing failed" , e );
201
- response .setStatus (STATUS_BAD_REQUEST );
202
- }
203
- };
204
- }
205
-
206
- private InputStream asMarkableInputStream (InputStream inputStream ) {
207
- if (!inputStream .markSupported ()) {
208
- return new BufferedInputStream (inputStream );
209
- }
210
- return inputStream ;
211
- }
212
-
213
- private GraphQLRequest buildRequestFromQuery (InputStream inputStream ,
214
- GraphQLObjectMapper graphQLObjectMapper ,
215
- Map <String , List <Part >> fileItems ) throws IOException {
216
- GraphQLRequest graphQLRequest ;
217
- String query = new String (ByteStreams .toByteArray (inputStream ));
218
-
219
- Map <String , Object > variables = null ;
220
- final Optional <Part > variablesItem = getFileItem (fileItems , "variables" );
221
- if (variablesItem .isPresent ()) {
222
- variables = graphQLObjectMapper
223
- .deserializeVariables (new String (ByteStreams .toByteArray (variablesItem .get ().getInputStream ())));
224
- }
225
-
226
- String operationName = null ;
227
- final Optional <Part > operationNameItem = getFileItem (fileItems , "operationName" );
228
- if (operationNameItem .isPresent ()) {
229
- operationName = new String (ByteStreams .toByteArray (operationNameItem .get ().getInputStream ())).trim ();
230
- }
231
-
232
- graphQLRequest = new GraphQLRequest (query , variables , operationName );
233
- return graphQLRequest ;
234
- }
235
-
236
- private void mapMultipartVariables (GraphQLRequest request ,
237
- Map <String , List <String >> variablesMap ,
238
- Map <String , List <Part >> fileItems ) {
239
- Map <String , Object > variables = request .getVariables ();
240
-
241
- variablesMap .forEach ((partName , objectPaths ) -> {
242
- Part part = getFileItem (fileItems , partName )
243
- .orElseThrow (() -> new RuntimeException ("unable to find part name " +
244
- partName +
245
- " as referenced in the variables map" ));
246
-
247
- objectPaths .forEach (objectPath -> VariableMapper .mapVariable (objectPath , variables , part ));
248
- });
249
86
}
250
87
251
88
public void addListener (GraphQLServletListener servletListener ) {
@@ -320,95 +157,13 @@ private void doRequest(HttpServletRequest request, HttpServletResponse response,
320
157
@ Override
321
158
protected void doGet (HttpServletRequest req , HttpServletResponse resp ) {
322
159
init ();
323
- doRequestAsync (req , resp , getHandler );
160
+ doRequestAsync (req , resp , requestHandler );
324
161
}
325
162
326
163
@ Override
327
164
protected void doPost (HttpServletRequest req , HttpServletResponse resp ) {
328
165
init ();
329
- doRequestAsync (req , resp , postHandler );
330
- }
331
-
332
- private Optional <Part > getFileItem (Map <String , List <Part >> fileItems , String name ) {
333
- return Optional .ofNullable (fileItems .get (name )).filter (list -> !list .isEmpty ()).map (list -> list .get (0 ));
334
- }
335
-
336
- private void query (GraphQLQueryInvoker queryInvoker , GraphQLObjectMapper graphQLObjectMapper ,
337
- GraphQLSingleInvocationInput invocationInput ,
338
- HttpServletRequest req , HttpServletResponse resp ) throws IOException {
339
- ExecutionResult result = queryInvoker .query (invocationInput );
340
-
341
- boolean isDeferred =
342
- Objects .nonNull (result .getExtensions ()) && result .getExtensions ().containsKey (GraphQL .DEFERRED_RESULTS );
343
-
344
- if (!(result .getData () instanceof Publisher || isDeferred )) {
345
- resp .setContentType (APPLICATION_JSON_UTF8 );
346
- resp .setStatus (STATUS_OK );
347
- graphQLObjectMapper .serializeResultAsJson (resp .getWriter (), result );
348
- } else {
349
- if (req == null ) {
350
- throw new IllegalStateException ("Http servlet request can not be null" );
351
- }
352
- resp .setContentType (APPLICATION_EVENT_STREAM_UTF8 );
353
- resp .setStatus (STATUS_OK );
354
-
355
- boolean isInAsyncThread = req .isAsyncStarted ();
356
- AsyncContext asyncContext = isInAsyncThread ? req .getAsyncContext () : req .startAsync (req , resp );
357
- asyncContext .setTimeout (configuration .getSubscriptionTimeout ());
358
- AtomicReference <Subscription > subscriptionRef = new AtomicReference <>();
359
- asyncContext .addListener (new SubscriptionAsyncListener (subscriptionRef ));
360
- ExecutionResultSubscriber subscriber = new ExecutionResultSubscriber (subscriptionRef , asyncContext ,
361
- graphQLObjectMapper );
362
- List <Publisher <ExecutionResult >> publishers = new ArrayList <>();
363
- if (result .getData () instanceof Publisher ) {
364
- publishers .add (result .getData ());
365
- } else {
366
- publishers .add (new StaticDataPublisher <>(result ));
367
- final Publisher <ExecutionResult > deferredResultsPublisher = (Publisher <ExecutionResult >) result .getExtensions ()
368
- .get (GraphQL .DEFERRED_RESULTS );
369
- publishers .add (deferredResultsPublisher );
370
- }
371
- publishers .forEach (it -> it .subscribe (subscriber ));
372
-
373
- if (isInAsyncThread ) {
374
- // We need to delay the completion of async context until after the subscription has terminated, otherwise the AsyncContext is prematurely closed.
375
- try {
376
- subscriber .await ();
377
- } catch (InterruptedException e ) {
378
- Thread .currentThread ().interrupt ();
379
- }
380
- }
381
- }
382
- }
383
-
384
- private void queryBatched (GraphQLQueryInvoker queryInvoker , GraphQLBatchedInvocationInput batchedInvocationInput ,
385
- HttpServletRequest request ,
386
- HttpServletResponse response , GraphQLConfiguration configuration ) throws IOException {
387
- BatchInputPreProcessor batchInputPreProcessor = configuration .getBatchInputPreProcessor ();
388
- ContextSetting contextSetting = configuration .getContextSetting ();
389
- BatchInputPreProcessResult batchInputPreProcessResult = batchInputPreProcessor
390
- .preProcessBatch (batchedInvocationInput , request , response );
391
- if (batchInputPreProcessResult .isExecutable ()) {
392
- List <ExecutionResult > results = queryInvoker
393
- .query (batchInputPreProcessResult .getBatchedInvocationInput ().getExecutionInputs (),
394
- contextSetting );
395
- response .setContentType (AbstractGraphQLHttpServlet .APPLICATION_JSON_UTF8 );
396
- response .setStatus (AbstractGraphQLHttpServlet .STATUS_OK );
397
- Writer writer = response .getWriter ();
398
- Iterator <ExecutionResult > executionInputIterator = results .iterator ();
399
- writer .write ("[" );
400
- GraphQLObjectMapper graphQLObjectMapper = configuration .getObjectMapper ();
401
- while (executionInputIterator .hasNext ()) {
402
- String result = graphQLObjectMapper .serializeResultAsJson (executionInputIterator .next ());
403
- writer .write (result );
404
- if (executionInputIterator .hasNext ()) {
405
- writer .write ("," );
406
- }
407
- }
408
- writer .write ("]" );
409
- } else {
410
- response .sendError (batchInputPreProcessResult .getStatusCode (), batchInputPreProcessResult .getStatusMessage ());
411
- }
166
+ doRequestAsync (req , resp , requestHandler );
412
167
}
413
168
414
169
private <R > List <R > runListeners (Function <? super GraphQLServletListener , R > action ) {
@@ -435,35 +190,4 @@ private <T> void runCallbacks(List<T> callbacks, Consumer<T> action) {
435
190
});
436
191
}
437
192
438
- private boolean isBatchedQuery (InputStream inputStream ) throws IOException {
439
- if (inputStream == null ) {
440
- return false ;
441
- }
442
-
443
- final int BUFFER_SIZE = 128 ;
444
- ByteArrayOutputStream result = new ByteArrayOutputStream ();
445
- byte [] buffer = new byte [BUFFER_SIZE ];
446
- int length ;
447
-
448
- inputStream .mark (BUFFER_SIZE );
449
- while ((length = inputStream .read (buffer )) != -1 ) {
450
- result .write (buffer , 0 , length );
451
- if (isArrayStart (result .toString ())) {
452
- inputStream .reset ();
453
- return true ;
454
- }
455
- }
456
-
457
- inputStream .reset ();
458
- return false ;
459
- }
460
-
461
- private boolean isBatchedQuery (String query ) {
462
- return isArrayStart (query );
463
- }
464
-
465
- private boolean isArrayStart (String s ) {
466
- return s != null && s .trim ().startsWith ("[" );
467
- }
468
-
469
193
}
0 commit comments