|
4 | 4 | import graphql.kickstart.execution.GraphQLObjectMapper;
|
5 | 5 | import java.io.IOException;
|
6 | 6 | import java.nio.charset.StandardCharsets;
|
7 |
| -import java.util.Iterator; |
| 7 | +import java.util.ArrayList; |
8 | 8 | import java.util.List;
|
9 | 9 | import javax.servlet.http.HttpServletRequest;
|
10 | 10 | import javax.servlet.http.HttpServletResponse;
|
|
14 | 14 | @Slf4j
|
15 | 15 | @RequiredArgsConstructor
|
16 | 16 | class BatchedQueryResponseWriter implements QueryResponseWriter {
|
17 |
| - |
18 | 17 | private final List<ExecutionResult> results;
|
19 | 18 | private final GraphQLObjectMapper graphQLObjectMapper;
|
20 | 19 |
|
21 | 20 | @Override
|
22 | 21 | public void write(HttpServletRequest request, HttpServletResponse response) throws IOException {
|
| 22 | + response.setCharacterEncoding(StandardCharsets.UTF_8.name()); |
23 | 23 | response.setContentType(HttpRequestHandler.APPLICATION_JSON_UTF8);
|
24 | 24 | response.setStatus(HttpRequestHandler.STATUS_OK);
|
25 | 25 |
|
26 |
| - Iterator<ExecutionResult> executionInputIterator = results.iterator(); |
27 |
| - StringBuilder responseBuilder = new StringBuilder(); |
28 |
| - responseBuilder.append('['); |
29 |
| - while (executionInputIterator.hasNext()) { |
30 |
| - responseBuilder |
31 |
| - .append(graphQLObjectMapper.serializeResultAsJson(executionInputIterator.next())); |
32 |
| - if (executionInputIterator.hasNext()) { |
33 |
| - responseBuilder.append(','); |
| 26 | + // Use direct serialization to byte arrays and avoid any string concatenation to save multiple |
| 27 | + // GiB of memory allocation during large response processing. |
| 28 | + List<byte[]> serializedResults = new ArrayList<>(2 * results.size() + 1); |
| 29 | + |
| 30 | + if (results.size() > 0) { |
| 31 | + serializedResults.add("[".getBytes(StandardCharsets.UTF_8)); |
| 32 | + } else { |
| 33 | + serializedResults.add("[]".getBytes(StandardCharsets.UTF_8)); |
| 34 | + } |
| 35 | + long totalLength = serializedResults.get(0).length; |
| 36 | + |
| 37 | + // '[', ',' and ']' are all 1 byte in UTF-8. |
| 38 | + for (int i = 0; i < results.size(); i++) { |
| 39 | + byte[] currentResult = graphQLObjectMapper.serializeResultAsBytes(results.get(i)); |
| 40 | + serializedResults.add(currentResult); |
| 41 | + |
| 42 | + if (i != results.size() - 1) { |
| 43 | + serializedResults.add(",".getBytes(StandardCharsets.UTF_8)); |
| 44 | + } else { |
| 45 | + serializedResults.add("]".getBytes(StandardCharsets.UTF_8)); |
34 | 46 | }
|
| 47 | + totalLength += currentResult.length + 1; // result.length + ',' or ']' |
35 | 48 | }
|
36 |
| - responseBuilder.append(']'); |
37 | 49 |
|
38 |
| - String responseContent = responseBuilder.toString(); |
39 |
| - byte[] contentBytes = responseContent.getBytes(StandardCharsets.UTF_8); |
| 50 | + if (totalLength > Integer.MAX_VALUE) { |
| 51 | + throw new IllegalStateException( |
| 52 | + "Response size exceed 2GiB. Query will fail. Seen size: " + totalLength); |
| 53 | + } |
| 54 | + response.setContentLength((int) totalLength); |
40 | 55 |
|
41 |
| - response.setContentLength(contentBytes.length); |
42 |
| - response.getOutputStream().write(contentBytes); |
| 56 | + for (byte[] result : serializedResults) { |
| 57 | + response.getOutputStream().write(result); |
| 58 | + } |
43 | 59 | }
|
44 | 60 |
|
45 | 61 | }
|
0 commit comments