11package graphql .servlet ;
22
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 ;
123import graphql .schema .GraphQLFieldDefinition ;
134import graphql .servlet .config .GraphQLConfiguration ;
14- import graphql .servlet .context .ContextSetting ;
155import graphql .servlet .core .GraphQLMBean ;
166import graphql .servlet .core .GraphQLObjectMapper ;
177import graphql .servlet .core .GraphQLQueryInvoker ;
188import graphql .servlet .core .GraphQLServletListener ;
199import 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 ;
2410import 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 ;
3111import java .util .ArrayList ;
32- import java .util .Arrays ;
3312import java .util .HashMap ;
34- import java .util .Iterator ;
3513import java .util .List ;
36- import java .util .Map ;
3714import 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 ;
4215import java .util .function .Consumer ;
4316import java .util .function .Function ;
4417import java .util .stream .Collectors ;
4518import javax .servlet .AsyncContext ;
46- import javax .servlet .AsyncEvent ;
47- import javax .servlet .AsyncListener ;
4819import javax .servlet .Servlet ;
4920import javax .servlet .http .HttpServlet ;
5021import javax .servlet .http .HttpServletRequest ;
5122import javax .servlet .http .HttpServletResponse ;
52- import javax .servlet .http .Part ;
5323import lombok .extern .slf4j .Slf4j ;
54- import org .reactivestreams .Publisher ;
55- import org .reactivestreams .Subscriber ;
56- import org .reactivestreams .Subscription ;
5724
5825/**
5926 * @author Andrew Potter
6027 */
6128@ Slf4j
6229public abstract class AbstractGraphQLHttpServlet extends HttpServlet implements Servlet , GraphQLMBean {
6330
64- private static final String [] MULTIPART_KEYS = new String []{"operations" , "graphql" , "query" };
6531 /**
6632 * @deprecated use {@link #getConfiguration()} instead
6733 */
6834 @ Deprecated
6935 private final List <GraphQLServletListener > listeners ;
7036 private GraphQLConfiguration configuration ;
71- private HttpRequestHandler getHandler ;
72- private HttpRequestHandler postHandler ;
37+ private HttpRequestHandler requestHandler ;
7338
7439 public AbstractGraphQLHttpServlet () {
7540 this (null );
@@ -114,138 +79,10 @@ protected GraphQLConfiguration getConfiguration() {
11479
11580 @ Override
11681 public void init () {
117- if (configuration != null ) {
118- return ;
82+ if (configuration == null ) {
83+ this .configuration = getConfiguration ();
84+ this .requestHandler = new HttpRequestHandlerImpl (configuration );
11985 }
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- });
24986 }
25087
25188 public void addListener (GraphQLServletListener servletListener ) {
@@ -320,95 +157,13 @@ private void doRequest(HttpServletRequest request, HttpServletResponse response,
320157 @ Override
321158 protected void doGet (HttpServletRequest req , HttpServletResponse resp ) {
322159 init ();
323- doRequestAsync (req , resp , getHandler );
160+ doRequestAsync (req , resp , requestHandler );
324161 }
325162
326163 @ Override
327164 protected void doPost (HttpServletRequest req , HttpServletResponse resp ) {
328165 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 );
412167 }
413168
414169 private <R > List <R > runListeners (Function <? super GraphQLServletListener , R > action ) {
@@ -435,35 +190,4 @@ private <T> void runCallbacks(List<T> callbacks, Consumer<T> action) {
435190 });
436191 }
437192
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-
469193}
0 commit comments