Skip to content

Commit 6376ec9

Browse files
committed
Refactoring in server handlers
In preparation to move handleRequest method up. See gh-959
1 parent 432d982 commit 6376ec9

File tree

7 files changed

+251
-228
lines changed

7 files changed

+251
-228
lines changed

spring-graphql/src/main/java/org/springframework/graphql/execution/SubscriptionPublisherException.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,10 +17,13 @@
1717
package org.springframework.graphql.execution;
1818

1919
import java.util.List;
20+
import java.util.Map;
2021

22+
import graphql.ExecutionResult;
2123
import graphql.GraphQLError;
2224

2325
import org.springframework.core.NestedRuntimeException;
26+
import org.springframework.lang.Nullable;
2427

2528
/**
2629
* An exception raised after a GraphQL subscription
@@ -47,7 +50,7 @@ public final class SubscriptionPublisherException extends NestedRuntimeException
4750
* @param errors the list of resolved GraphQL errors
4851
* @param cause the original exception
4952
*/
50-
public SubscriptionPublisherException(List<GraphQLError> errors, Throwable cause) {
53+
public SubscriptionPublisherException(List<GraphQLError> errors, @Nullable Throwable cause) {
5154
super("GraphQL subscription ended with error(s): " + errors, cause);
5255
this.errors = errors;
5356
}
@@ -62,4 +65,12 @@ public List<GraphQLError> getErrors() {
6265
return this.errors;
6366
}
6467

68+
/**
69+
* Return an {@link ExecutionResult} specification map with the GraphQL errors.
70+
* @since 1.3.0
71+
*/
72+
public Map<String, Object> toMap() {
73+
return ExecutionResult.newExecutionResult().errors(this.errors).build().toSpecification();
74+
}
75+
6576
}

spring-graphql/src/main/java/org/springframework/graphql/server/webflux/GraphQlHttpHandler.java

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717
package org.springframework.graphql.server.webflux;
1818

19-
import java.util.Arrays;
2019
import java.util.List;
2120

2221
import org.apache.commons.logging.Log;
@@ -25,6 +24,7 @@
2524

2625
import org.springframework.graphql.server.WebGraphQlHandler;
2726
import org.springframework.graphql.server.WebGraphQlRequest;
27+
import org.springframework.graphql.server.WebGraphQlResponse;
2828
import org.springframework.http.MediaType;
2929
import org.springframework.http.codec.CodecConfigurer;
3030
import org.springframework.web.reactive.function.server.ServerRequest;
@@ -42,8 +42,8 @@ public class GraphQlHttpHandler extends AbstractGraphQlHttpHandler {
4242
private static final Log logger = LogFactory.getLog(GraphQlHttpHandler.class);
4343

4444
@SuppressWarnings("removal")
45-
private static final List<MediaType> SUPPORTED_MEDIA_TYPES =
46-
Arrays.asList(MediaType.APPLICATION_GRAPHQL_RESPONSE, MediaType.APPLICATION_JSON, MediaType.APPLICATION_GRAPHQL);
45+
private static final List<MediaType> SUPPORTED_MEDIA_TYPES = List.of(
46+
MediaType.APPLICATION_GRAPHQL_RESPONSE, MediaType.APPLICATION_JSON, MediaType.APPLICATION_GRAPHQL);
4747

4848

4949
/**
@@ -66,39 +66,39 @@ public GraphQlHttpHandler(WebGraphQlHandler graphQlHandler, CodecConfigurer code
6666

6767
/**
6868
* Handle GraphQL requests over HTTP.
69-
* @param serverRequest the incoming HTTP request
69+
* @param request the incoming HTTP request
7070
* @return the HTTP response
7171
*/
72-
public Mono<ServerResponse> handleRequest(ServerRequest serverRequest) {
73-
return readRequest(serverRequest)
72+
public Mono<ServerResponse> handleRequest(ServerRequest request) {
73+
return readRequest(request)
7474
.flatMap((body) -> {
7575
WebGraphQlRequest graphQlRequest = new WebGraphQlRequest(
76-
serverRequest.uri(), serverRequest.headers().asHttpHeaders(),
77-
serverRequest.cookies(), serverRequest.remoteAddress().orElse(null),
78-
serverRequest.attributes(), body,
79-
serverRequest.exchange().getRequest().getId(),
80-
serverRequest.exchange().getLocaleContext().getLocale());
76+
request.uri(), request.headers().asHttpHeaders(),
77+
request.cookies(), request.remoteAddress().orElse(null),
78+
request.attributes(), body,
79+
request.exchange().getRequest().getId(),
80+
request.exchange().getLocaleContext().getLocale());
8181
if (logger.isDebugEnabled()) {
8282
logger.debug("Executing: " + graphQlRequest);
8383
}
8484
return this.graphQlHandler.handleRequest(graphQlRequest);
8585
})
8686
.flatMap((response) -> {
8787
if (logger.isDebugEnabled()) {
88-
logger.debug("Execution complete");
89-
}
90-
ServerResponse.BodyBuilder builder = ServerResponse.ok();
91-
builder.headers((headers) -> headers.putAll(response.getResponseHeaders()));
92-
builder.contentType(selectResponseMediaType(serverRequest));
93-
if (this.codecDelegate != null) {
94-
return builder.bodyValue(this.codecDelegate.encode(response));
95-
}
96-
else {
97-
return builder.bodyValue(response.toMap());
88+
logger.debug("Execution result ready");
9889
}
90+
return prepareResponse(request, response);
9991
});
10092
}
10193

94+
protected Mono<ServerResponse> prepareResponse(ServerRequest serverRequest, WebGraphQlResponse response) {
95+
ServerResponse.BodyBuilder builder = ServerResponse.ok();
96+
builder.headers((headers) -> headers.putAll(response.getResponseHeaders()));
97+
builder.contentType(selectResponseMediaType(serverRequest));
98+
return builder.bodyValue((this.codecDelegate != null) ?
99+
this.codecDelegate.encode(response) : response.toMap());
100+
}
101+
102102
private static MediaType selectResponseMediaType(ServerRequest serverRequest) {
103103
for (MediaType accepted : serverRequest.headers().accept()) {
104104
if (SUPPORTED_MEDIA_TYPES.contains(accepted)) {

spring-graphql/src/main/java/org/springframework/graphql/server/webflux/GraphQlSseHandler.java

Lines changed: 33 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919

2020
import java.util.Collections;
21+
import java.util.List;
2122
import java.util.Map;
2223

2324
import graphql.ErrorType;
@@ -29,6 +30,7 @@
2930
import reactor.core.publisher.Flux;
3031
import reactor.core.publisher.Mono;
3132

33+
import org.springframework.graphql.ResponseError;
3234
import org.springframework.graphql.execution.SubscriptionPublisherException;
3335
import org.springframework.graphql.server.WebGraphQlHandler;
3436
import org.springframework.graphql.server.WebGraphQlRequest;
@@ -46,13 +48,15 @@
4648
* {@link org.springframework.web.reactive.function.server.RouterFunctions}.
4749
*
4850
* @author Brian Clozel
51+
* @author Rossen Stoyanchev
4952
* @since 1.3.0
5053
*/
5154
public class GraphQlSseHandler extends AbstractGraphQlHttpHandler {
5255

5356
private static final Log logger = LogFactory.getLog(GraphQlSseHandler.class);
5457

55-
private static final Mono<ServerSentEvent<Map<String, Object>>> COMPLETE_EVENT = Mono.just(ServerSentEvent.<Map<String, Object>>builder(Collections.emptyMap()).event("complete").build());
58+
private static final Mono<ServerSentEvent<Map<String, Object>>> COMPLETE_EVENT = Mono.just(
59+
ServerSentEvent.<Map<String, Object>>builder(Collections.emptyMap()).event("complete").build());
5660

5761

5862
public GraphQlSseHandler(WebGraphQlHandler graphQlHandler) {
@@ -66,7 +70,7 @@ public GraphQlSseHandler(WebGraphQlHandler graphQlHandler) {
6670
*/
6771
@SuppressWarnings("unchecked")
6872
public Mono<ServerResponse> handleRequest(ServerRequest serverRequest) {
69-
Flux<ServerSentEvent<Map<String, Object>>> data = readRequest(serverRequest)
73+
return readRequest(serverRequest)
7074
.flatMap((body) -> {
7175
WebGraphQlRequest graphQlRequest = new WebGraphQlRequest(
7276
serverRequest.uri(), serverRequest.headers().asHttpHeaders(),
@@ -79,35 +83,39 @@ public Mono<ServerResponse> handleRequest(ServerRequest serverRequest) {
7983
}
8084
return this.graphQlHandler.handleRequest(graphQlRequest);
8185
})
82-
.flatMapMany((response) -> {
86+
.flatMap((response) -> {
8387
if (logger.isDebugEnabled()) {
84-
logger.debug("Execution result ready"
85-
+ (!CollectionUtils.isEmpty(response.getErrors()) ? " with errors: " + response.getErrors() : "")
86-
+ ".");
88+
List<ResponseError> errors = response.getErrors();
89+
logger.debug("Execution result " +
90+
(!CollectionUtils.isEmpty(errors) ? "has errors: " + errors : "is ready") + ".");
8791
}
92+
Flux<Map<String, Object>> resultFlux;
8893
if (response.getData() instanceof Publisher) {
89-
// Subscription
90-
return Flux.from((Publisher<ExecutionResult>) response.getData()).map(ExecutionResult::toSpecification);
94+
resultFlux = Flux.from((Publisher<ExecutionResult>) response.getData())
95+
.map(ExecutionResult::toSpecification)
96+
.onErrorResume(SubscriptionPublisherException.class, (ex) -> Mono.just(ex.toMap()));
9197
}
92-
if (logger.isDebugEnabled()) {
93-
logger.debug("Only subscriptions are supported, DataFetcher must return a Publisher type");
98+
else {
99+
if (logger.isDebugEnabled()) {
100+
logger.debug("A subscription DataFetcher must return a Publisher: " + response.getData());
101+
}
102+
resultFlux = Flux.just(ExecutionResult.newExecutionResult()
103+
.addError(GraphQLError.newError()
104+
.errorType(ErrorType.OperationNotSupported)
105+
.message("SSE handler supports only subscriptions")
106+
.build())
107+
.build()
108+
.toSpecification());
94109
}
95-
// Single response (query or mutation) are not supported
96-
String errorMessage = "SSE transport only supports Subscription operations";
97-
GraphQLError unsupportedOperationError = GraphQLError.newError().errorType(ErrorType.OperationNotSupported)
98-
.message(errorMessage).build();
99-
return Flux.error(new SubscriptionPublisherException(Collections.singletonList(unsupportedOperationError),
100-
new IllegalArgumentException(errorMessage)));
101-
})
102-
.onErrorResume(SubscriptionPublisherException.class, (exc) -> {
103-
ExecutionResult errorResult = ExecutionResult.newExecutionResult().errors(exc.getErrors()).build();
104-
return Flux.just(errorResult.toSpecification());
105-
})
106-
.map((event) -> ServerSentEvent.builder(event).event("next").build());
107110

108-
Flux<ServerSentEvent<Map<String, Object>>> body = data.concatWith(COMPLETE_EVENT);
109-
return ServerResponse.ok().contentType(MediaType.TEXT_EVENT_STREAM).body(BodyInserters.fromServerSentEvents(body))
110-
.onErrorResume(Throwable.class, (exc) -> ServerResponse.badRequest().build());
111+
Flux<ServerSentEvent<Map<String, Object>>> sseFlux =
112+
resultFlux.map((event) -> ServerSentEvent.builder(event).event("next").build());
113+
114+
return ServerResponse.ok()
115+
.contentType(MediaType.TEXT_EVENT_STREAM)
116+
.body(BodyInserters.fromServerSentEvents(sseFlux.concatWith(COMPLETE_EVENT)))
117+
.onErrorResume(Throwable.class, (ex) -> ServerResponse.badRequest().build());
118+
});
111119
}
112120

113121
}

spring-graphql/src/main/java/org/springframework/graphql/server/webmvc/GraphQlHttpHandler.java

Lines changed: 21 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,16 @@
1616

1717
package org.springframework.graphql.server.webmvc;
1818

19-
import java.util.Arrays;
2019
import java.util.List;
20+
import java.util.Map;
2121
import java.util.concurrent.CompletableFuture;
2222
import java.util.concurrent.ExecutionException;
2323

2424
import jakarta.servlet.ServletException;
2525

2626
import org.springframework.context.i18n.LocaleContextHolder;
27-
import org.springframework.graphql.GraphQlResponse;
2827
import org.springframework.graphql.server.WebGraphQlHandler;
2928
import org.springframework.graphql.server.WebGraphQlRequest;
30-
import org.springframework.http.HttpMethod;
3129
import org.springframework.http.MediaType;
3230
import org.springframework.http.converter.HttpMessageConverter;
3331
import org.springframework.http.server.ServletServerHttpResponse;
@@ -47,8 +45,8 @@
4745
public class GraphQlHttpHandler extends AbstractGraphQlHttpHandler {
4846

4947
@SuppressWarnings("removal")
50-
private static final List<MediaType> SUPPORTED_MEDIA_TYPES =
51-
Arrays.asList(MediaType.APPLICATION_GRAPHQL_RESPONSE, MediaType.APPLICATION_JSON, MediaType.APPLICATION_GRAPHQL);
48+
private static final List<MediaType> SUPPORTED_MEDIA_TYPES = List.of(
49+
MediaType.APPLICATION_GRAPHQL_RESPONSE, MediaType.APPLICATION_JSON, MediaType.APPLICATION_GRAPHQL);
5250

5351

5452
/**
@@ -62,27 +60,28 @@ public GraphQlHttpHandler(WebGraphQlHandler graphQlHandler) {
6260
/**
6361
* Create a new instance with a custom message converter.
6462
* <p>If no converter is provided, this will use
65-
* {@link org.springframework.web.servlet.config.annotation.WebMvcConfigurer#configureMessageConverters(List) the one configured in the web framework}.
63+
* {@link org.springframework.web.servlet.config.annotation.WebMvcConfigurer#configureMessageConverters(List)
64+
* the one configured in the web framework}.
6665
* @param graphQlHandler common handler for GraphQL over HTTP requests
67-
* @param messageConverter custom {@link HttpMessageConverter} to be used for encoding and decoding GraphQL payloads
66+
* @param converter custom {@link HttpMessageConverter} to read and write GraphQL payloads
6867
*/
69-
public GraphQlHttpHandler(WebGraphQlHandler graphQlHandler, @Nullable HttpMessageConverter<?> messageConverter) {
70-
super(graphQlHandler, messageConverter);
68+
public GraphQlHttpHandler(WebGraphQlHandler graphQlHandler, @Nullable HttpMessageConverter<?> converter) {
69+
super(graphQlHandler, converter);
7170
}
7271

7372
/**
7473
* Handle GraphQL requests over HTTP.
75-
* @param serverRequest the incoming HTTP request
74+
* @param request the incoming HTTP request
7675
* @return the HTTP response
7776
* @throws ServletException may be raised when reading the request body, e.g.
7877
* {@link HttpMediaTypeNotSupportedException}.
7978
*/
80-
public ServerResponse handleRequest(ServerRequest serverRequest) throws ServletException {
79+
public ServerResponse handleRequest(ServerRequest request) throws ServletException {
8180

8281
WebGraphQlRequest graphQlRequest = new WebGraphQlRequest(
83-
serverRequest.uri(), serverRequest.headers().asHttpHeaders(), initCookies(serverRequest),
84-
serverRequest.remoteAddress().orElse(null),
85-
serverRequest.attributes(), readBody(serverRequest), this.idGenerator.generateId().toString(),
82+
request.uri(), request.headers().asHttpHeaders(), initCookies(request),
83+
request.remoteAddress().orElse(null),
84+
request.attributes(), readBody(request), this.idGenerator.generateId().toString(),
8685
LocaleContextHolder.getLocale());
8786

8887
if (logger.isDebugEnabled()) {
@@ -94,13 +93,13 @@ public ServerResponse handleRequest(ServerRequest serverRequest) throws ServletE
9493
if (logger.isDebugEnabled()) {
9594
logger.debug("Execution complete");
9695
}
97-
MediaType contentType = selectResponseMediaType(serverRequest);
96+
MediaType contentType = selectResponseMediaType(request);
9897
ServerResponse.BodyBuilder builder = ServerResponse.ok();
9998
builder.headers((headers) -> headers.putAll(response.getResponseHeaders()));
10099
builder.contentType(contentType);
101100

102101
if (this.messageConverter != null) {
103-
return builder.build(writeFunction(contentType, response));
102+
return builder.build(writeFunction(this.messageConverter, contentType, response.toMap()));
104103
}
105104
else {
106105
return builder.body(response.toMap());
@@ -132,16 +131,13 @@ private static MediaType selectResponseMediaType(ServerRequest serverRequest) {
132131
return MediaType.APPLICATION_JSON;
133132
}
134133

135-
private ServerResponse.HeadersBuilder.WriteFunction writeFunction(MediaType contentType, GraphQlResponse response) {
134+
private static ServerResponse.HeadersBuilder.WriteFunction writeFunction(
135+
HttpMessageConverter<Object> converter, MediaType contentType, Map<String, Object> resultMap) {
136+
136137
return (servletRequest, servletResponse) -> {
137-
if (messageConverter != null) {
138-
ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(servletResponse);
139-
messageConverter.write(response.toMap(), contentType, httpResponse);
140-
return null;
141-
}
142-
else {
143-
throw new HttpMediaTypeNotSupportedException(contentType, SUPPORTED_MEDIA_TYPES, HttpMethod.POST);
144-
}
138+
ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(servletResponse);
139+
converter.write(resultMap, contentType, httpResponse);
140+
return null;
145141
};
146142
}
147143

0 commit comments

Comments
 (0)