Skip to content

Commit 088e5e5

Browse files
committed
Fix unit tests
1 parent 4380611 commit 088e5e5

10 files changed

+323
-346
lines changed
Lines changed: 6 additions & 282 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,40 @@
11
package 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;
123
import graphql.schema.GraphQLFieldDefinition;
134
import graphql.servlet.config.GraphQLConfiguration;
14-
import graphql.servlet.context.ContextSetting;
155
import graphql.servlet.core.GraphQLMBean;
166
import graphql.servlet.core.GraphQLObjectMapper;
177
import graphql.servlet.core.GraphQLQueryInvoker;
188
import graphql.servlet.core.GraphQLServletListener;
199
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;
2410
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;
3111
import java.util.ArrayList;
32-
import java.util.Arrays;
3312
import java.util.HashMap;
34-
import java.util.Iterator;
3513
import java.util.List;
36-
import java.util.Map;
3714
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;
4215
import java.util.function.Consumer;
4316
import java.util.function.Function;
4417
import java.util.stream.Collectors;
4518
import javax.servlet.AsyncContext;
46-
import javax.servlet.AsyncEvent;
47-
import javax.servlet.AsyncListener;
4819
import javax.servlet.Servlet;
4920
import javax.servlet.http.HttpServlet;
5021
import javax.servlet.http.HttpServletRequest;
5122
import javax.servlet.http.HttpServletResponse;
52-
import javax.servlet.http.Part;
5323
import 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
6229
public 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
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package graphql.servlet;
2+
3+
import graphql.servlet.context.ContextSetting;
4+
import graphql.servlet.core.GraphQLObjectMapper;
5+
import graphql.servlet.input.GraphQLInvocationInputFactory;
6+
import lombok.RequiredArgsConstructor;
7+
8+
@RequiredArgsConstructor
9+
abstract class AbstractGraphQLInvocationInputParser implements GraphQLInvocationInputParser {
10+
11+
final GraphQLInvocationInputFactory invocationInputFactory;
12+
final GraphQLObjectMapper graphQLObjectMapper;
13+
final ContextSetting contextSetting;
14+
15+
boolean isSingleQuery(String query) {
16+
return !isArrayStart(query);
17+
}
18+
19+
private boolean isArrayStart(String s) {
20+
return s != null && s.trim().startsWith("[");
21+
}
22+
23+
}

0 commit comments

Comments
 (0)