WebInterceptor contract revision
WebInterceptor now uses delegation, forming a chain of interceptors followed by a GraphQLService at the end to invoke graphql.GraphQL. Closes gh-49
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.graphql.boot;
|
||||
|
||||
import graphql.GraphQL;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.graphql.GraphQLService;
|
||||
import org.springframework.graphql.support.ExecutionGraphQLService;
|
||||
import org.springframework.graphql.support.GraphQLSource;
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnClass(GraphQL.class)
|
||||
@ConditionalOnMissingBean(GraphQLService.class)
|
||||
@AutoConfigureAfter(GraphQLAutoConfiguration.class)
|
||||
public class GraphQLServiceAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public GraphQLService graphQLService(GraphQLSource graphQLSource) {
|
||||
return new ExecutionGraphQLService(graphQLSource);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -34,9 +34,9 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.graphql.GraphQLService;
|
||||
import org.springframework.graphql.support.GraphQLSource;
|
||||
import org.springframework.graphql.web.DefaultWebGraphQLService;
|
||||
import org.springframework.graphql.web.WebGraphQLService;
|
||||
import org.springframework.graphql.web.WebGraphQLHandler;
|
||||
import org.springframework.graphql.web.WebInterceptor;
|
||||
import org.springframework.graphql.web.webflux.GraphQLHttpHandler;
|
||||
import org.springframework.graphql.web.webflux.GraphQLWebSocketHandler;
|
||||
@@ -64,16 +64,14 @@ public class WebFluxGraphQLAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public WebGraphQLService webGraphQLService(GraphQLSource graphQLSource, ObjectProvider<WebInterceptor> interceptors) {
|
||||
DefaultWebGraphQLService handler = new DefaultWebGraphQLService(graphQLSource);
|
||||
handler.setInterceptors(interceptors.orderedStream().collect(Collectors.toList()));
|
||||
return handler;
|
||||
public WebGraphQLHandler webGraphQLHandler(ObjectProvider<WebInterceptor> interceptors, GraphQLService service) {
|
||||
return WebInterceptor.createHandler(interceptors.orderedStream().collect(Collectors.toList()), service);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public GraphQLHttpHandler graphQLHandler(WebGraphQLService service) {
|
||||
return new GraphQLHttpHandler(service);
|
||||
public GraphQLHttpHandler graphQLHttpHandler(WebGraphQLHandler webGraphQLHandler) {
|
||||
return new GraphQLHttpHandler(webGraphQLHandler);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -99,22 +97,22 @@ public class WebFluxGraphQLAutoConfiguration {
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public GraphQLWebSocketHandler graphQLWebSocketHandler(
|
||||
WebGraphQLService service, GraphQLProperties properties, ServerCodecConfigurer configurer) {
|
||||
WebGraphQLHandler webGraphQLHandler, GraphQLProperties properties, ServerCodecConfigurer configurer) {
|
||||
|
||||
return new GraphQLWebSocketHandler(
|
||||
service, configurer, properties.getWebsocket().getConnectionInitTimeout());
|
||||
webGraphQLHandler, configurer, properties.getWebsocket().getConnectionInitTimeout());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public HandlerMapping graphQLWebSocketEndpoint(
|
||||
GraphQLWebSocketHandler handler, GraphQLProperties properties) {
|
||||
GraphQLWebSocketHandler graphQLWebSocketHandler, GraphQLProperties properties) {
|
||||
|
||||
String path = properties.getWebsocket().getPath();
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("GraphQL endpoint WebSocket " + path);
|
||||
}
|
||||
WebSocketHandlerMapping handlerMapping = new WebSocketHandlerMapping();
|
||||
handlerMapping.setUrlMap(Collections.singletonMap(path, handler));
|
||||
handlerMapping.setUrlMap(Collections.singletonMap(path, graphQLWebSocketHandler));
|
||||
handlerMapping.setOrder(-2); // Ahead of HTTP endpoint ("routerFunctionMapping" bean)
|
||||
return handlerMapping;
|
||||
}
|
||||
|
||||
@@ -38,9 +38,9 @@ import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.graphql.GraphQLService;
|
||||
import org.springframework.graphql.support.GraphQLSource;
|
||||
import org.springframework.graphql.web.DefaultWebGraphQLService;
|
||||
import org.springframework.graphql.web.WebGraphQLService;
|
||||
import org.springframework.graphql.web.WebGraphQLHandler;
|
||||
import org.springframework.graphql.web.WebInterceptor;
|
||||
import org.springframework.graphql.web.webmvc.GraphQLHttpHandler;
|
||||
import org.springframework.graphql.web.webmvc.GraphQLWebSocketHandler;
|
||||
@@ -71,16 +71,14 @@ public class WebMvcGraphQLAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public WebGraphQLService webGraphQLService(GraphQLSource graphQLSource, ObjectProvider<WebInterceptor> interceptors) {
|
||||
DefaultWebGraphQLService handler = new DefaultWebGraphQLService(graphQLSource);
|
||||
handler.setInterceptors(interceptors.orderedStream().collect(Collectors.toList()));
|
||||
return handler;
|
||||
public WebGraphQLHandler webGraphQLHandler(ObjectProvider<WebInterceptor> interceptors, GraphQLService service) {
|
||||
return WebInterceptor.createHandler(interceptors.orderedStream().collect(Collectors.toList()), service);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public GraphQLHttpHandler graphQLHandler(WebGraphQLService service) {
|
||||
return new GraphQLHttpHandler(service);
|
||||
public GraphQLHttpHandler graphQLHttpHandler(WebGraphQLHandler webGraphQLHandler) {
|
||||
return new GraphQLHttpHandler(webGraphQLHandler);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -108,7 +106,7 @@ public class WebMvcGraphQLAutoConfiguration {
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public GraphQLWebSocketHandler graphQLWebSocketHandler(
|
||||
WebGraphQLService service, GraphQLProperties properties, HttpMessageConverters converters) {
|
||||
WebGraphQLHandler webGraphQLHandler, GraphQLProperties properties, HttpMessageConverters converters) {
|
||||
|
||||
HttpMessageConverter<?> converter = converters.getConverters().stream()
|
||||
.filter(candidate -> candidate.canRead(Map.class, MediaType.APPLICATION_JSON))
|
||||
@@ -116,7 +114,7 @@ public class WebMvcGraphQLAutoConfiguration {
|
||||
.orElseThrow(() -> new IllegalStateException("No JSON converter"));
|
||||
|
||||
return new GraphQLWebSocketHandler(
|
||||
service, converter, properties.getWebsocket().getConnectionInitTimeout());
|
||||
webGraphQLHandler, converter, properties.getWebsocket().getConnectionInitTimeout());
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
org.springframework.graphql.boot.actuate.metrics.GraphQLMetricsAutoConfiguration,\
|
||||
org.springframework.graphql.boot.GraphQLAutoConfiguration,\
|
||||
org.springframework.graphql.boot.GraphQLServiceAutoConfiguration,\
|
||||
org.springframework.graphql.boot.WebFluxGraphQLAutoConfiguration,\
|
||||
org.springframework.graphql.boot.WebMvcGraphQLAutoConfiguration
|
||||
|
||||
@@ -19,7 +19,6 @@ import java.util.Collections;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration;
|
||||
@@ -32,7 +31,6 @@ import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.graphql.web.WebInterceptor;
|
||||
import org.springframework.graphql.web.WebOutput;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
|
||||
@@ -43,7 +41,8 @@ class WebFluxApplicationContextTests {
|
||||
private static final AutoConfigurations AUTO_CONFIGURATIONS = AutoConfigurations.of(
|
||||
HttpHandlerAutoConfiguration.class, WebFluxAutoConfiguration.class,
|
||||
CodecsAutoConfiguration.class, JacksonAutoConfiguration.class,
|
||||
GraphQLAutoConfiguration.class, WebFluxGraphQLAutoConfiguration.class);
|
||||
GraphQLAutoConfiguration.class, GraphQLServiceAutoConfiguration.class,
|
||||
WebFluxGraphQLAutoConfiguration.class);
|
||||
|
||||
private static final String BASE_URL = "https://spring.example.org/graphql";
|
||||
|
||||
@@ -139,12 +138,8 @@ class WebFluxApplicationContextTests {
|
||||
|
||||
@Bean
|
||||
public WebInterceptor customWebInterceptor() {
|
||||
return new WebInterceptor() {
|
||||
@Override
|
||||
public Mono<WebOutput> postHandle(WebOutput output) {
|
||||
return Mono.just(output.transform(builder -> builder.responseHeader("X-Custom-Header", "42")));
|
||||
}
|
||||
};
|
||||
return (input, next) -> next.handle(input).map(output ->
|
||||
output.transform(builder -> builder.responseHeader("X-Custom-Header", "42")));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ package org.springframework.graphql.boot;
|
||||
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
|
||||
@@ -29,7 +28,6 @@ import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.graphql.web.WebInterceptor;
|
||||
import org.springframework.graphql.web.WebOutput;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
@@ -39,7 +37,8 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
import static graphql.schema.idl.TypeRuntimeWiring.newTypeWiring;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
@@ -48,7 +47,8 @@ class WebMvcApplicationContextTests {
|
||||
public static final AutoConfigurations AUTO_CONFIGURATIONS = AutoConfigurations.of(
|
||||
DispatcherServletAutoConfiguration.class, WebMvcAutoConfiguration.class,
|
||||
HttpMessageConvertersAutoConfiguration.class, JacksonAutoConfiguration.class,
|
||||
GraphQLAutoConfiguration.class, WebMvcGraphQLAutoConfiguration.class);
|
||||
GraphQLAutoConfiguration.class, GraphQLServiceAutoConfiguration.class,
|
||||
WebMvcGraphQLAutoConfiguration.class);
|
||||
|
||||
@Test
|
||||
void endpointHandlesGraphQLQuery() {
|
||||
@@ -135,13 +135,8 @@ class WebMvcApplicationContextTests {
|
||||
|
||||
@Bean
|
||||
public WebInterceptor customWebInterceptor() {
|
||||
return new WebInterceptor() {
|
||||
@Override
|
||||
public Mono<WebOutput> postHandle(WebOutput output) {
|
||||
return Mono.just(output.transform(builder ->
|
||||
builder.responseHeader("X-Custom-Header", "42")));
|
||||
}
|
||||
};
|
||||
return (input, next) -> next.handle(input).map(output ->
|
||||
output.transform(builder -> builder.responseHeader("X-Custom-Header", "42")));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.graphql.web.WebGraphQLService;
|
||||
import org.springframework.graphql.web.WebGraphQLHandler;
|
||||
import org.springframework.graphql.test.tester.GraphQLTester;
|
||||
|
||||
/**
|
||||
@@ -34,9 +34,9 @@ public class QueryTests {
|
||||
|
||||
|
||||
@BeforeEach
|
||||
public void setUp(@Autowired WebGraphQLService service) {
|
||||
public void setUp(@Autowired WebGraphQLHandler handler) {
|
||||
this.graphQLTester = GraphQLTester.create(webInput ->
|
||||
service.execute(webInput).contextWrite(context -> context.put("name", "James")));
|
||||
handler.handle(webInput).contextWrite(context -> context.put("name", "James")));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ import reactor.test.StepVerifier;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.graphql.web.WebGraphQLService;
|
||||
import org.springframework.graphql.web.WebGraphQLHandler;
|
||||
import org.springframework.graphql.test.tester.GraphQLTester;
|
||||
|
||||
/**
|
||||
@@ -36,9 +36,9 @@ public class SubscriptionTests {
|
||||
|
||||
|
||||
@BeforeEach
|
||||
public void setUp(@Autowired WebGraphQLService service) {
|
||||
public void setUp(@Autowired WebGraphQLHandler handler) {
|
||||
this.graphQLTester = GraphQLTester.create(webInput ->
|
||||
service.execute(webInput).contextWrite(context -> context.put("name", "James")));
|
||||
handler.handle(webInput).contextWrite(context -> context.put("name", "James")));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.graphql.RequestInput;
|
||||
import org.springframework.graphql.web.WebGraphQLService;
|
||||
import org.springframework.graphql.web.WebGraphQLHandler;
|
||||
import org.springframework.graphql.web.WebInput;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
@@ -83,9 +83,9 @@ class DefaultGraphQLTester implements GraphQLTester {
|
||||
this.requestStrategy = new WebTestClientRequestStrategy(client, this.jsonPathConfig);
|
||||
}
|
||||
|
||||
DefaultGraphQLTester(WebGraphQLService service) {
|
||||
DefaultGraphQLTester(WebGraphQLHandler handler) {
|
||||
this.jsonPathConfig = initJsonPathConfig();
|
||||
this.requestStrategy = new DirectRequestStrategy(service, this.jsonPathConfig);
|
||||
this.requestStrategy = new DirectRequestStrategy(handler, this.jsonPathConfig);
|
||||
}
|
||||
|
||||
private Configuration initJsonPathConfig() {
|
||||
@@ -182,12 +182,12 @@ class DefaultGraphQLTester implements GraphQLTester {
|
||||
private static final Duration DEFAULT_TIMEOUT = Duration.ofSeconds(5);
|
||||
|
||||
|
||||
private final WebGraphQLService graphQLService;
|
||||
private final WebGraphQLHandler graphQLHandler;
|
||||
|
||||
private final Configuration jsonPathConfig;
|
||||
|
||||
public DirectRequestStrategy(WebGraphQLService service, Configuration jsonPathConfig) {
|
||||
this.graphQLService = service;
|
||||
public DirectRequestStrategy(WebGraphQLHandler handler, Configuration jsonPathConfig) {
|
||||
this.graphQLHandler = handler;
|
||||
this.jsonPathConfig = jsonPathConfig;
|
||||
}
|
||||
|
||||
@@ -213,7 +213,7 @@ class DefaultGraphQLTester implements GraphQLTester {
|
||||
|
||||
private ExecutionResult executeInternal(RequestInput input) {
|
||||
WebInput webInput = new WebInput(DEFAULT_URL, DEFAULT_HEADERS, input.toMap(), null);
|
||||
ExecutionResult result = this.graphQLService.execute(webInput).block(DEFAULT_TIMEOUT);
|
||||
ExecutionResult result = this.graphQLHandler.handle(webInput).block(DEFAULT_TIMEOUT);
|
||||
Assert.notNull(result, "Expected ExecutionResult");
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -24,13 +24,14 @@ import graphql.GraphQLError;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.graphql.web.WebGraphQLService;
|
||||
import org.springframework.graphql.web.WebGraphQLHandler;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
|
||||
/**
|
||||
* Main entry point for testing GraphQL with requests performed via
|
||||
* {@link WebTestClient} as an HTTP client or via any {@link WebGraphQLService}.
|
||||
* Main entry point for testing GraphQL with requests performed either as an
|
||||
* HTTP client via {@link WebTestClient} or directly via a
|
||||
* {@link WebGraphQLHandler}.
|
||||
*
|
||||
*
|
||||
* <p>GraphQL requests to Spring MVC without an HTTP server:
|
||||
@@ -75,7 +76,7 @@ import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* <p>GraphQL requests to any {@link WebGraphQLService}:
|
||||
* <p>GraphQL requests to any {@link WebGraphQLHandler}:
|
||||
* <pre class="code">
|
||||
* @SpringBootTest
|
||||
* public class MyTests {
|
||||
@@ -83,8 +84,8 @@ import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
* private GraphQLTester graphQLTester;
|
||||
*
|
||||
* @BeforeEach
|
||||
* public void setUp(@Autowired WebGraphQLService service) {
|
||||
* this.graphQLTester = GraphQLTester.create(service);
|
||||
* public void setUp(@Autowired WebGraphQLHandler handler) {
|
||||
* this.graphQLTester = GraphQLTester.create(handler);
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
@@ -112,13 +113,13 @@ public interface GraphQLTester {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@code GraphQLTester} that performs GraphQL requests through the
|
||||
* given {@link WebGraphQLService}.
|
||||
* @param service the handler to execute requests with
|
||||
* Create a {@code GraphQLTester} that performs GraphQL requests through
|
||||
* the given {@link WebGraphQLHandler}.
|
||||
* @param handler the handler to execute requests with
|
||||
* @return the created {@code GraphQLTester} instance
|
||||
*/
|
||||
static GraphQLTester create(WebGraphQLService service) {
|
||||
return new DefaultGraphQLTester(service);
|
||||
static GraphQLTester create(WebGraphQLHandler handler) {
|
||||
return new DefaultGraphQLTester(handler);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ import org.mockito.ArgumentCaptor;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.graphql.web.WebGraphQLService;
|
||||
import org.springframework.graphql.web.WebGraphQLHandler;
|
||||
import org.springframework.graphql.web.WebInput;
|
||||
import org.springframework.graphql.web.WebOutput;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
@@ -58,7 +58,7 @@ import static org.mockito.Mockito.when;
|
||||
* Tests for {@link GraphQLTester} parameterized to:
|
||||
* <ul>
|
||||
* <li>Connect to {@link MockWebServer} and return a preset HTTP response.
|
||||
* <li>Use mock {@link WebGraphQLService} to return a preset {@link ExecutionResult}.
|
||||
* <li>Use mock {@link WebGraphQLHandler} to return a preset {@link ExecutionResult}.
|
||||
* </ul>
|
||||
*
|
||||
* <p>There is no actual handling via {@link graphql.GraphQL} in either scenario.
|
||||
@@ -71,7 +71,7 @@ public class GraphQLTesterTests {
|
||||
|
||||
|
||||
public static Stream<GraphQLTesterSetup> argumentSource() {
|
||||
return Stream.of(new MockWebServerSetup(), new MockWebGraphQLServiceSetup());
|
||||
return Stream.of(new MockWebServerSetup(), new MockWebGraphQLHandlerSetup());
|
||||
}
|
||||
|
||||
|
||||
@@ -393,16 +393,16 @@ public class GraphQLTesterTests {
|
||||
}
|
||||
|
||||
|
||||
private static class MockWebGraphQLServiceSetup implements GraphQLTesterSetup {
|
||||
private static class MockWebGraphQLHandlerSetup implements GraphQLTesterSetup {
|
||||
|
||||
private final WebGraphQLService service = mock(WebGraphQLService.class);
|
||||
private final WebGraphQLHandler handler = mock(WebGraphQLHandler.class);
|
||||
|
||||
private final ArgumentCaptor<WebInput> bodyCaptor = ArgumentCaptor.forClass(WebInput.class);
|
||||
|
||||
private final GraphQLTester graphQLTester;
|
||||
|
||||
public MockWebGraphQLServiceSetup() {
|
||||
this.graphQLTester = GraphQLTester.create(this.service);
|
||||
public MockWebGraphQLHandlerSetup() {
|
||||
this.graphQLTester = GraphQLTester.create(this.handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -421,7 +421,7 @@ public class GraphQLTesterTests {
|
||||
}
|
||||
ExecutionResult result = builder.build();
|
||||
WebOutput output = new WebOutput(mock(WebInput.class), result);
|
||||
when(this.service.execute(this.bodyCaptor.capture())).thenReturn(Mono.just(output));
|
||||
when(this.handler.handle(this.bodyCaptor.capture())).thenReturn(Mono.just(output));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -43,6 +43,9 @@ dependencies {
|
||||
testImplementation 'org.springframework:spring-test'
|
||||
testImplementation 'javax.servlet:javax.servlet-api:4.0.1'
|
||||
testImplementation 'com.fasterxml.jackson.core:jackson-databind'
|
||||
|
||||
testRuntime 'org.apache.logging.log4j:log4j-core:2.14.1'
|
||||
testRuntime 'org.apache.logging.log4j:log4j-slf4j-impl:2.14.1'
|
||||
}
|
||||
|
||||
test {
|
||||
|
||||
@@ -15,23 +15,21 @@
|
||||
*/
|
||||
package org.springframework.graphql;
|
||||
|
||||
import graphql.ExecutionInput;
|
||||
import graphql.ExecutionResult;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* Contract to execute a GraphQL request.
|
||||
*
|
||||
* @param <IN> container for the GraphQL query along additional transport
|
||||
* related context such as the HTTP url or headers for web.
|
||||
* @param <OUT> the query execution result
|
||||
* Strategy to perform GraphQL query execution with input for and output from
|
||||
* the invocation of {@link graphql.GraphQL}.
|
||||
*/
|
||||
public interface GraphQLService<IN extends RequestInput, OUT extends ExecutionResult> {
|
||||
public interface GraphQLService {
|
||||
|
||||
/**
|
||||
* Perform the request and return the result.
|
||||
* @param input the GraphQL query container
|
||||
* Perform the query and return the result.
|
||||
* @param input the input for query execution via {@link graphql.GraphQL}
|
||||
* @return the execution result
|
||||
*/
|
||||
Mono<OUT> execute(IN input);
|
||||
Mono<ExecutionResult> execute(ExecutionInput input);
|
||||
|
||||
}
|
||||
|
||||
@@ -15,9 +15,12 @@
|
||||
*/
|
||||
package org.springframework.graphql;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import graphql.ExecutionInput;
|
||||
|
||||
@@ -37,6 +40,8 @@ public class RequestInput {
|
||||
|
||||
private final Map<String, Object> variables;
|
||||
|
||||
private final List<BiFunction<ExecutionInput, ExecutionInput.Builder, ExecutionInput>> executionInputConfigurers = new ArrayList<>();
|
||||
|
||||
|
||||
public RequestInput(String query, @Nullable String operationName, @Nullable Map<String, Object> vars) {
|
||||
Assert.notNull(query, "'query' is required");
|
||||
@@ -80,24 +85,44 @@ public class RequestInput {
|
||||
return this.variables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a consumer to configure the {@link ExecutionInput} used for input
|
||||
* to {@link graphql.GraphQL#executeAsync(ExecutionInput)}.
|
||||
* The builder is initially populated with the values from
|
||||
* {@link #getQuery()}, {@link #getOperationName()}, and {@link #getVariables()}.
|
||||
* @param configurer a {@code BiFunction} with the current
|
||||
* {@code ExecutionInput} and a builder to modify it.
|
||||
*/
|
||||
public void configureExecutionInput(BiFunction<ExecutionInput, ExecutionInput.Builder, ExecutionInput> configurer) {
|
||||
this.executionInputConfigurers.add(configurer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an {@link ExecutionInput} initialized with the {@link #getQuery()},
|
||||
* {@link #getOperationName()}, and {@link #getVariables()}.
|
||||
* Create the {@link ExecutionInput} for query execution. This is initially
|
||||
* populated from {@link #getQuery()}, {@link #getOperationName()}, and
|
||||
* {@link #getVariables()}, and is then further customized through
|
||||
* {@link #configureExecutionInput(BiFunction)}.
|
||||
*/
|
||||
public ExecutionInput toExecutionInput() {
|
||||
return ExecutionInput.newExecutionInput()
|
||||
.query(getQuery())
|
||||
.operationName(getOperationName())
|
||||
.variables(getVariables())
|
||||
ExecutionInput executionInput = ExecutionInput.newExecutionInput()
|
||||
.query(this.query)
|
||||
.operationName(this.operationName)
|
||||
.variables(this.variables)
|
||||
.build();
|
||||
|
||||
for (BiFunction<ExecutionInput, ExecutionInput.Builder, ExecutionInput> configurer : this.executionInputConfigurers) {
|
||||
ExecutionInput current = executionInput;
|
||||
executionInput = executionInput.transform(builder -> configurer.apply(current, builder));
|
||||
}
|
||||
|
||||
return executionInput;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a Map representation of the request input.
|
||||
*/
|
||||
public Map<String, Object> toMap() {
|
||||
Map<String, Object> map = new LinkedHashMap<>();
|
||||
Map<String, Object> map = new LinkedHashMap<>(3);
|
||||
map.put("query", getQuery());
|
||||
if (getOperationName() != null) {
|
||||
map.put("operationName", getOperationName());
|
||||
|
||||
@@ -13,33 +13,36 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.graphql.web;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
package org.springframework.graphql.support;
|
||||
|
||||
import graphql.ExecutionInput;
|
||||
import graphql.ExecutionResult;
|
||||
import graphql.GraphQL;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.graphql.support.GraphQLSource;
|
||||
import org.springframework.graphql.GraphQLService;
|
||||
|
||||
/**
|
||||
* Extension of {@link AbstractWebGraphQLService} that executes GraphQL queries
|
||||
* through the {@link GraphQL} instance it is configured with.
|
||||
* Implementation of {@link GraphQLService} that performs GraphQL query execution
|
||||
* through {@link GraphQL#executeAsync(ExecutionInput)}.
|
||||
*/
|
||||
public class DefaultWebGraphQLService extends AbstractWebGraphQLService {
|
||||
public class ExecutionGraphQLService implements GraphQLService {
|
||||
|
||||
private final GraphQLSource graphQLSource;
|
||||
|
||||
|
||||
public DefaultWebGraphQLService(GraphQLSource graphQLSource) {
|
||||
public ExecutionGraphQLService(GraphQLSource graphQLSource) {
|
||||
this.graphQLSource = graphQLSource;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected CompletableFuture<ExecutionResult> executeInternal(ExecutionInput input) {
|
||||
return this.graphQLSource.graphQL().executeAsync(input);
|
||||
public Mono<ExecutionResult> execute(ExecutionInput input) {
|
||||
GraphQL graphQL = this.graphQLSource.graphQL();
|
||||
return Mono.deferContextual(contextView -> {
|
||||
ReactorDataFetcherAdapter.addReactorContext(input, contextView);
|
||||
return Mono.fromFuture(graphQL.executeAsync(input));
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -39,13 +39,12 @@ import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Adapter to wrap a registered {@link DataFetcher} and enable it to return
|
||||
* Adapter that can wrap a registered {@link DataFetcher} and enable it to return
|
||||
* {@link Flux} or {@link Mono}, also adding Reactor Context passed through
|
||||
* the {@link ExecutionInput} via {@link #addReactorContext(ExecutionInput, ContextView)}.
|
||||
* Use {@link #TYPE_VISITOR} to transform the
|
||||
* {@link graphql.schema.GraphQLSchema} and apply the adapter.
|
||||
* the {@link ExecutionInput}. Also exposes a {@link #TYPE_VISITOR} to apply
|
||||
* the adapter.
|
||||
*/
|
||||
public class ReactorDataFetcherAdapter implements DataFetcher<Object> {
|
||||
class ReactorDataFetcherAdapter implements DataFetcher<Object> {
|
||||
|
||||
private static final String REACTOR_CONTEXT_KEY =
|
||||
ReactorDataFetcherAdapter.class.getName() + ".REACTOR_CONTEXT";
|
||||
@@ -108,7 +107,7 @@ public class ReactorDataFetcherAdapter implements DataFetcher<Object> {
|
||||
* {@link GraphQLTypeVisitor} that wraps non-GraphQL data fetchers and
|
||||
* adapts them if they return {@link Flux} or {@link Mono}.
|
||||
*/
|
||||
public static GraphQLTypeVisitor TYPE_VISITOR = new GraphQLTypeVisitorStub() {
|
||||
static GraphQLTypeVisitor TYPE_VISITOR = new GraphQLTypeVisitorStub() {
|
||||
|
||||
@Override
|
||||
public TraversalControl visitGraphQLFieldDefinition(
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.graphql.web;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import graphql.ExecutionInput;
|
||||
import graphql.ExecutionResult;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.graphql.support.ReactorDataFetcherAdapter;
|
||||
|
||||
/**
|
||||
* Base class for {@link WebGraphQLService} implementations, providing support
|
||||
* for customizations of the request through a {@link WebInterceptor} chain.
|
||||
* Sub-classes must implement {@link #executeInternal(ExecutionInput)} to
|
||||
* actually perform the GraphQL query.
|
||||
*/
|
||||
public abstract class AbstractWebGraphQLService implements WebGraphQLService {
|
||||
|
||||
private final List<WebInterceptor> interceptors = new ArrayList<>();
|
||||
|
||||
|
||||
/**
|
||||
* Set the interceptors to invoke to handle request.
|
||||
* @param interceptors the interceptors to use
|
||||
*/
|
||||
public void setInterceptors(List<WebInterceptor> interceptors) {
|
||||
this.interceptors.clear();
|
||||
this.interceptors.addAll(interceptors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link #setInterceptors(List) configured} interceptors.
|
||||
*/
|
||||
public List<WebInterceptor> getInterceptors() {
|
||||
return this.interceptors;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public final Mono<WebOutput> execute(WebInput input) {
|
||||
return preHandle(input)
|
||||
.flatMap(executionInput -> Mono.fromFuture(executeInternal(executionInput)))
|
||||
.flatMap(executionResult -> postHandle(new WebOutput(input, executionResult)));
|
||||
}
|
||||
|
||||
private Mono<ExecutionInput> preHandle(WebInput input) {
|
||||
Mono<ExecutionInput> resultMono = Mono.deferContextual(contextView -> {
|
||||
ExecutionInput executionInput = input.toExecutionInput();
|
||||
ReactorDataFetcherAdapter.addReactorContext(executionInput, contextView);
|
||||
return Mono.just(executionInput);
|
||||
});
|
||||
for (WebInterceptor interceptor : this.interceptors) {
|
||||
resultMono = resultMono.flatMap(executionInput -> interceptor.preHandle(executionInput, input));
|
||||
}
|
||||
return resultMono;
|
||||
}
|
||||
|
||||
private Mono<WebOutput> postHandle(WebOutput output) {
|
||||
Mono<WebOutput> outputMono = Mono.just(output);
|
||||
for (int i = this.interceptors.size() - 1 ; i >= 0; i--) {
|
||||
WebInterceptor interceptor = this.interceptors.get(i);
|
||||
outputMono = outputMono.flatMap(interceptor::postHandle);
|
||||
}
|
||||
return outputMono;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sub-classes must implement this method to actually handle the request.
|
||||
* @param input the input to invoke {@link graphql.GraphQL} with
|
||||
* @return the result from handling
|
||||
*/
|
||||
protected abstract CompletableFuture<ExecutionResult> executeInternal(ExecutionInput input);
|
||||
|
||||
}
|
||||
@@ -15,12 +15,26 @@
|
||||
*/
|
||||
package org.springframework.graphql.web;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.graphql.GraphQLService;
|
||||
|
||||
/**
|
||||
* {@link GraphQLService} for executing GraphQL requests in a Web environment,
|
||||
* over HTTP or WebSocket.
|
||||
* Contract to handle a GraphQL over HTTP or WebSocket request that forms the
|
||||
* basis of a {@link WebInterceptor} delegation chain.
|
||||
*
|
||||
* @see WebInterceptor#createHandler(List, GraphQLService)
|
||||
*/
|
||||
public interface WebGraphQLService extends GraphQLService<WebInput, WebOutput> {
|
||||
public interface WebGraphQLHandler {
|
||||
|
||||
/**
|
||||
* Perform query execution for the given request and return the result.
|
||||
*
|
||||
* @param input the GraphQL query container
|
||||
* @return the execution result
|
||||
*/
|
||||
Mono<WebOutput> handle(WebInput input);
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2020 the original author or authors.
|
||||
* Copyright 2020-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -15,56 +15,81 @@
|
||||
*/
|
||||
package org.springframework.graphql.web;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import java.util.List;
|
||||
|
||||
import graphql.ExecutionInput;
|
||||
import graphql.ExecutionResult;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.graphql.web.webmvc.GraphQLHttpHandler;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.graphql.GraphQLService;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Web interceptor for GraphQL queries over HTTP. The interceptor allows
|
||||
* customization of the {@link ExecutionInput} for the query as well as the
|
||||
* {@link ExecutionResult} of the query and is supported for both Spring MVC and
|
||||
* Spring WebFlux.
|
||||
* Interceptor for intercepting GraphQL over HTTP or WebSocket requests.
|
||||
* Provides information about the HTTP request or WebSocket handshake, allows
|
||||
* customization of the {@link ExecutionInput} and of the {@link ExecutionResult}
|
||||
* from query execution.
|
||||
*
|
||||
* <p>A list of interceptors may be provided to {@link GraphQLHttpHandler} or
|
||||
* to {@link org.springframework.graphql.web.webflux.GraphQLHttpHandler}. Interceptors are executed in that provided
|
||||
* order where each interceptor sees the {@code ExecutionInput} or the
|
||||
* {@code ExecutionResult} that was customized by the previous interceptor.
|
||||
* <p>Interceptors may be declared as beans in Spring configuration and ordered
|
||||
* as defined in {@link ObjectProvider#orderedStream()}.
|
||||
*
|
||||
* <p>Supported for Spring MVC and WebFlux.
|
||||
*/
|
||||
public interface WebInterceptor {
|
||||
|
||||
/**
|
||||
* Intercept a GraphQL over HTTP request before the query is executed.
|
||||
* Intercept a request and delegate for further handling and query execution
|
||||
* via {@link WebGraphQLHandler#handle(WebInput)}.
|
||||
*
|
||||
* <p>{@code ExecutionInput} is initially populated with the input from the
|
||||
* request body via {@link WebInput#toExecutionInput()} where the
|
||||
* {@link WebInput#getQuery() query} is guaranteed to be a non-empty String.
|
||||
* Interceptors are then executed in order to further customize the input
|
||||
* and or perform other actions or checks.
|
||||
*
|
||||
* @param executionInput the input to use, initialized from {@code WebInput}
|
||||
* @param webInput the input from the HTTP request
|
||||
* @return the same instance or a new one via {@link ExecutionInput#transform(Consumer)}
|
||||
* @param webInput container with HTTP request information and options to
|
||||
* customize the {@link ExecutionInput}.
|
||||
* @param next the handler to delegate to for query execution
|
||||
* @return a {@link Mono} with the result
|
||||
*/
|
||||
default Mono<ExecutionInput> preHandle(ExecutionInput executionInput, WebInput webInput) {
|
||||
return Mono.just(executionInput);
|
||||
Mono<WebOutput> intercept(WebInput webInput, WebGraphQLHandler next);
|
||||
|
||||
/**
|
||||
* Return a composed {@link WebInterceptor} that invokes the current
|
||||
* interceptor first one and then the one one passed in.
|
||||
*/
|
||||
default WebInterceptor andThen(WebInterceptor interceptor) {
|
||||
Assert.notNull(interceptor, "WebInterceptor must not be null");
|
||||
return (currentInput, next) -> intercept(currentInput, nextInput -> interceptor.intercept(nextInput, next));
|
||||
}
|
||||
|
||||
/**
|
||||
* Intercept a GraphQL over HTTP request after the query is executed.
|
||||
*
|
||||
* <p>{@code WebOutput} initially wraps the {@link ExecutionResult} returned
|
||||
* from the execution of the query. Interceptors are then executed in order
|
||||
* to further customize it and/or perform other actions.
|
||||
*
|
||||
* @param webOutput the execution result
|
||||
* @return the same instance or a new one via {@link WebOutput#transform(Consumer)}
|
||||
* Return {@link WebGraphQLHandler} that invokes the current interceptor
|
||||
* first and then the given {@link GraphQLService} for actual execution of
|
||||
* the GraphQL query.
|
||||
*/
|
||||
default Mono<WebOutput> postHandle(WebOutput webOutput) {
|
||||
return Mono.just(webOutput);
|
||||
default WebGraphQLHandler apply(GraphQLService service) {
|
||||
Assert.notNull(service, "GraphQLService must not be null");
|
||||
return currentInput -> intercept(currentInput, createHandler(service));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Factory method for a {@link WebGraphQLHandler} with a chain of
|
||||
* interceptors followed by a {@link GraphQLService} at the end.
|
||||
*/
|
||||
static WebGraphQLHandler createHandler(List<WebInterceptor> interceptors, GraphQLService service) {
|
||||
return interceptors.stream()
|
||||
.reduce(WebInterceptor::andThen)
|
||||
.map(interceptor -> interceptor.apply(service))
|
||||
.orElse(createHandler(service));
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method for a {@link WebGraphQLHandler} that simple invokes the
|
||||
* given {@link GraphQLService} adapting to its input and output.
|
||||
*/
|
||||
static WebGraphQLHandler createHandler(GraphQLService graphQLService) {
|
||||
Assert.notNull(graphQLService, "GraphQLService must not be null");
|
||||
return webInput -> {
|
||||
ExecutionInput executionInput = webInput.toExecutionInput();
|
||||
return graphQLService.execute(executionInput).map(result -> new WebOutput(webInput, result));
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -22,7 +22,7 @@ import org.apache.commons.logging.LogFactory;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.graphql.web.WebGraphQLService;
|
||||
import org.springframework.graphql.web.WebGraphQLHandler;
|
||||
import org.springframework.graphql.web.WebInput;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
@@ -39,16 +39,16 @@ public class GraphQLHttpHandler {
|
||||
new ParameterizedTypeReference<Map<String, Object>>() {};
|
||||
|
||||
|
||||
private final WebGraphQLService graphQLService;
|
||||
private final WebGraphQLHandler graphQLHandler;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new instance.
|
||||
* @param service for GraphQL query execution
|
||||
* @param graphQLHandler common handler for GraphQL over HTTP requests
|
||||
*/
|
||||
public GraphQLHttpHandler(WebGraphQLService service) {
|
||||
Assert.notNull(service, "WebGraphQLService is required");
|
||||
this.graphQLService = service;
|
||||
public GraphQLHttpHandler(WebGraphQLHandler graphQLHandler) {
|
||||
Assert.notNull(graphQLHandler, "WebGraphQLHandler is required");
|
||||
this.graphQLHandler = graphQLHandler;
|
||||
}
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ public class GraphQLHttpHandler {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Executing: " + input);
|
||||
}
|
||||
return this.graphQLService.execute(input);
|
||||
return this.graphQLHandler.handle(input);
|
||||
})
|
||||
.flatMap(output -> {
|
||||
Map<String, Object> spec = output.toSpecification();
|
||||
|
||||
@@ -39,7 +39,7 @@ import org.springframework.core.codec.Decoder;
|
||||
import org.springframework.core.codec.Encoder;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.io.buffer.DataBufferUtils;
|
||||
import org.springframework.graphql.web.WebGraphQLService;
|
||||
import org.springframework.graphql.web.WebGraphQLHandler;
|
||||
import org.springframework.graphql.web.WebInput;
|
||||
import org.springframework.graphql.web.WebOutput;
|
||||
import org.springframework.http.MediaType;
|
||||
@@ -72,7 +72,7 @@ public class GraphQLWebSocketHandler implements WebSocketHandler {
|
||||
ResolvableType.forType(new ParameterizedTypeReference<Map<String, Object>>() {});
|
||||
|
||||
|
||||
private final WebGraphQLService graphQLService;
|
||||
private final WebGraphQLHandler graphQLHandler;
|
||||
|
||||
private final Decoder<?> decoder;
|
||||
|
||||
@@ -83,16 +83,16 @@ public class GraphQLWebSocketHandler implements WebSocketHandler {
|
||||
|
||||
/**
|
||||
* Create a new instance.
|
||||
* @param service for GraphQL query execution
|
||||
* @param graphQLHandler common handler for GraphQL over HTTP requests
|
||||
* @param configurer codec configurer for JSON encoding and decoding
|
||||
* @param connectionInitTimeout the time within which the {@code CONNECTION_INIT}
|
||||
* type message must be received.
|
||||
*/
|
||||
public GraphQLWebSocketHandler(
|
||||
WebGraphQLService service, ServerCodecConfigurer configurer, Duration connectionInitTimeout) {
|
||||
WebGraphQLHandler graphQLHandler, ServerCodecConfigurer configurer, Duration connectionInitTimeout) {
|
||||
|
||||
Assert.notNull(service, "WebGraphQLService is required");
|
||||
this.graphQLService = service;
|
||||
Assert.notNull(graphQLHandler, "WebGraphQLHandler is required");
|
||||
this.graphQLHandler = graphQLHandler;
|
||||
this.decoder = initDecoder(configurer);
|
||||
this.encoder = initEncoder(configurer);
|
||||
this.initTimeoutDuration = connectionInitTimeout;
|
||||
@@ -164,7 +164,7 @@ public class GraphQLWebSocketHandler implements WebSocketHandler {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Executing: " + input);
|
||||
}
|
||||
return this.graphQLService.execute(input)
|
||||
return this.graphQLHandler.handle(input)
|
||||
.flatMapMany(output -> handleWebOutput(session, id, subscriptions, output))
|
||||
.doOnTerminate(() -> subscriptions.remove(id));
|
||||
case COMPLETE:
|
||||
|
||||
@@ -25,7 +25,7 @@ import org.apache.commons.logging.LogFactory;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.graphql.web.WebGraphQLService;
|
||||
import org.springframework.graphql.web.WebGraphQLHandler;
|
||||
import org.springframework.graphql.web.WebInput;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.HttpMediaTypeNotSupportedException;
|
||||
@@ -45,16 +45,16 @@ public class GraphQLHttpHandler {
|
||||
new ParameterizedTypeReference<Map<String, Object>>() {};
|
||||
|
||||
|
||||
private final WebGraphQLService graphQLService;
|
||||
private final WebGraphQLHandler graphQLHandler;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new instance.
|
||||
* @param service for GraphQL query execution
|
||||
* @param graphQLHandler common handler for GraphQL over HTTP requests
|
||||
*/
|
||||
public GraphQLHttpHandler(WebGraphQLService service) {
|
||||
Assert.notNull(service, "WebGraphQLService is required");
|
||||
this.graphQLService = service;
|
||||
public GraphQLHttpHandler(WebGraphQLHandler graphQLHandler) {
|
||||
Assert.notNull(graphQLHandler, "WebGraphQLHandler is required");
|
||||
this.graphQLHandler = graphQLHandler;
|
||||
}
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ public class GraphQLHttpHandler {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Executing: " + input);
|
||||
}
|
||||
Mono<ServerResponse> responseMono = this.graphQLService.execute(input)
|
||||
Mono<ServerResponse> responseMono = this.graphQLHandler.handle(input)
|
||||
.map(output -> {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Execution complete");
|
||||
|
||||
@@ -41,7 +41,7 @@ import reactor.core.publisher.Mono;
|
||||
import reactor.core.scheduler.Scheduler;
|
||||
import reactor.core.scheduler.Schedulers;
|
||||
|
||||
import org.springframework.graphql.web.WebGraphQLService;
|
||||
import org.springframework.graphql.web.WebGraphQLHandler;
|
||||
import org.springframework.graphql.web.WebInput;
|
||||
import org.springframework.graphql.web.WebOutput;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
@@ -71,7 +71,7 @@ public class GraphQLWebSocketHandler extends TextWebSocketHandler implements Sub
|
||||
Arrays.asList("graphql-transport-ws", "subscriptions-transport-ws");
|
||||
|
||||
|
||||
private final WebGraphQLService service;
|
||||
private final WebGraphQLHandler graphQLHandler;
|
||||
|
||||
private final Duration initTimeoutDuration;
|
||||
|
||||
@@ -82,17 +82,17 @@ public class GraphQLWebSocketHandler extends TextWebSocketHandler implements Sub
|
||||
|
||||
/**
|
||||
* Create a new instance.
|
||||
* @param service for GraphQL query execution
|
||||
* @param graphQLHandler common handler for GraphQL over HTTP requests
|
||||
* @param converter for JSON encoding and decoding
|
||||
* @param connectionInitTimeout the time within which the {@code CONNECTION_INIT}
|
||||
* type message must be received.
|
||||
*/
|
||||
public GraphQLWebSocketHandler(
|
||||
WebGraphQLService service, HttpMessageConverter<?> converter, Duration connectionInitTimeout) {
|
||||
WebGraphQLHandler graphQLHandler, HttpMessageConverter<?> converter, Duration connectionInitTimeout) {
|
||||
|
||||
Assert.notNull(service, "WebGraphQLService is required");
|
||||
Assert.notNull(graphQLHandler, "WebGraphQLHandler is required");
|
||||
Assert.notNull(converter, "HttpMessageConverter for JSON is required");
|
||||
this.service = service;
|
||||
this.graphQLHandler = graphQLHandler;
|
||||
this.initTimeoutDuration = connectionInitTimeout;
|
||||
this.converter = converter;
|
||||
}
|
||||
@@ -155,7 +155,7 @@ public class GraphQLWebSocketHandler extends TextWebSocketHandler implements Sub
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Executing: " + input);
|
||||
}
|
||||
this.service.execute(input)
|
||||
this.graphQLHandler.handle(input)
|
||||
.flatMapMany(output -> handleWebOutput(session, input.getId(), output))
|
||||
.publishOn(sessionState.getScheduler()) // Serial blocking send via single thread
|
||||
.subscribe(new SendMessageSubscriber(id, session, sessionState));
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
*/
|
||||
package org.springframework.graphql.support;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
@@ -25,12 +25,7 @@ import java.util.function.Consumer;
|
||||
import graphql.ExecutionInput;
|
||||
import graphql.ExecutionResult;
|
||||
import graphql.GraphQL;
|
||||
import graphql.schema.GraphQLSchema;
|
||||
import graphql.schema.SchemaTransformer;
|
||||
import graphql.schema.idl.RuntimeWiring;
|
||||
import graphql.schema.idl.SchemaGenerator;
|
||||
import graphql.schema.idl.SchemaParser;
|
||||
import graphql.schema.idl.TypeDefinitionRegistry;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.reactivestreams.Publisher;
|
||||
import reactor.core.publisher.Flux;
|
||||
@@ -38,7 +33,6 @@ import reactor.core.publisher.Mono;
|
||||
import reactor.util.context.Context;
|
||||
|
||||
import org.springframework.core.io.ByteArrayResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@@ -109,16 +103,16 @@ public class ReactorDataFetcherAdapterTests {
|
||||
}
|
||||
|
||||
|
||||
private GraphQL initGraphQL(String schemaValue, Consumer<RuntimeWiring.Builder> consumer) throws IOException {
|
||||
Resource schemaResource = new ByteArrayResource(schemaValue.getBytes(StandardCharsets.UTF_8));
|
||||
TypeDefinitionRegistry typeRegistry = new SchemaParser().parse(schemaResource.getInputStream());
|
||||
|
||||
private GraphQL initGraphQL(String schemaValue, Consumer<RuntimeWiring.Builder> consumer) {
|
||||
RuntimeWiring.Builder wiringBuilder = RuntimeWiring.newRuntimeWiring();
|
||||
consumer.accept(wiringBuilder);
|
||||
|
||||
GraphQLSchema schema = new SchemaGenerator().makeExecutableSchema(typeRegistry, wiringBuilder.build());
|
||||
schema = SchemaTransformer.transformSchema(schema, ReactorDataFetcherAdapter.TYPE_VISITOR);
|
||||
return GraphQL.newGraphQL(schema).build();
|
||||
return GraphQLSource.builder()
|
||||
.schemaResource(new ByteArrayResource(schemaValue.getBytes(StandardCharsets.UTF_8)))
|
||||
.runtimeWiring(wiringBuilder.build())
|
||||
.typeVisitors(Collections.singletonList(ReactorDataFetcherAdapter.TYPE_VISITOR))
|
||||
.build()
|
||||
.graphQL();
|
||||
}
|
||||
|
||||
private ExecutionInput initExecutionInput(String query, Context reactorContext) {
|
||||
|
||||
@@ -19,19 +19,17 @@ import org.reactivestreams.Publisher;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.graphql.web.WebInterceptor;
|
||||
import org.springframework.graphql.web.WebOutput;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class ConsumeOneAndNeverCompleteInterceptor implements WebInterceptor {
|
||||
|
||||
@Override
|
||||
public Mono<WebOutput> postHandle(WebOutput output) {
|
||||
return Mono.just(output.transform(builder -> {
|
||||
Publisher<?> publisher = output.getData();
|
||||
assertThat(publisher).isNotNull();
|
||||
builder.data(Flux.from(publisher).take(1).concatWith(Flux.never()));
|
||||
}));
|
||||
public Mono<WebOutput> intercept(WebInput webInput, WebGraphQLHandler next) {
|
||||
return next.handle(webInput).map(output ->
|
||||
output.transform(builder -> {
|
||||
Publisher<?> publisher = output.getData();
|
||||
assertThat(publisher).isNotNull();
|
||||
builder.data(Flux.from(publisher).take(1).concatWith(Flux.never()));
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,111 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.graphql.web;
|
||||
|
||||
import java.net.URI;
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import graphql.ExecutionInput;
|
||||
import graphql.schema.idl.RuntimeWiring;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.graphql.support.GraphQLSource;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
|
||||
import static graphql.schema.idl.TypeRuntimeWiring.newTypeWiring;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link DefaultWebGraphQLService}.
|
||||
*/
|
||||
public class DefaultWebGraphQLServiceTests {
|
||||
|
||||
@Test
|
||||
void testInterceptorInvocation() throws Exception {
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
List<WebInterceptor> interceptors = Arrays.asList(
|
||||
new TestWebInterceptor(sb, 1), new TestWebInterceptor(sb, 2), new TestWebInterceptor(sb, 3));
|
||||
|
||||
String query = "{" +
|
||||
" bookById(id: \\\"book-1\\\"){ " +
|
||||
" id" +
|
||||
" name" +
|
||||
" pageCount" +
|
||||
" author" +
|
||||
" }" +
|
||||
"}";
|
||||
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
Map body = mapper.reader().readValue("{\"query\": \"" + query + "\"}", Map.class);
|
||||
WebInput webInput = new WebInput(URI.create("/graphql"), new HttpHeaders(), body, "1");
|
||||
|
||||
DefaultWebGraphQLService requestHandler = new DefaultWebGraphQLService(createGraphQLSource());
|
||||
requestHandler.setInterceptors(interceptors);
|
||||
|
||||
WebOutput webOutput = requestHandler.execute(webInput).block();
|
||||
|
||||
assertThat(sb.toString()).isEqualTo(":pre1:pre2:pre3:post3:post2:post1");
|
||||
assertThat(webOutput.isDataPresent()).isTrue();
|
||||
assertThat(webOutput.getResponseHeaders().get("MyHeader")).containsExactly("MyValue3", "MyValue2", "MyValue1");
|
||||
}
|
||||
|
||||
|
||||
private static GraphQLSource createGraphQLSource() {
|
||||
RuntimeWiring runtimeWiring = RuntimeWiring.newRuntimeWiring()
|
||||
.type(newTypeWiring("Query").dataFetcher("bookById", GraphQLDataFetchers.getBookByIdDataFetcher()))
|
||||
.build();
|
||||
|
||||
return GraphQLSource.builder()
|
||||
.schemaResource(new ClassPathResource("books/schema.graphqls"))
|
||||
.runtimeWiring(runtimeWiring)
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
private static class TestWebInterceptor implements WebInterceptor {
|
||||
|
||||
private final StringBuilder output;
|
||||
|
||||
private final int index;
|
||||
|
||||
public TestWebInterceptor(StringBuilder output, int index) {
|
||||
this.output = output;
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<ExecutionInput> preHandle(ExecutionInput executionInput, WebInput webInput) {
|
||||
this.output.append(":pre").append(this.index);
|
||||
return Mono.delay(Duration.ofMillis(50)).map(aLong -> executionInput);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<WebOutput> postHandle(WebOutput output) {
|
||||
this.output.append(":post").append(this.index);
|
||||
return Mono.delay(Duration.ofMillis(50))
|
||||
.map(aLong -> output.transform(builder ->
|
||||
builder.responseHeader("myHeader", "MyValue" + this.index)));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.graphql.web;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import graphql.ExecutionInput;
|
||||
import graphql.ExecutionResult;
|
||||
import graphql.ExecutionResultImpl;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.scheduler.Schedulers;
|
||||
|
||||
import org.springframework.graphql.GraphQLService;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Unit tests for a {@link WebInterceptor} chain.
|
||||
*/
|
||||
public class WebInterceptorTests {
|
||||
|
||||
@Test
|
||||
void interceptorChain() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
List<WebInterceptor> interceptors = Arrays.asList(
|
||||
new TestWebInterceptor(sb, 1), new TestWebInterceptor(sb, 2), new TestWebInterceptor(sb, 3));
|
||||
|
||||
TestGraphQLService service = new TestGraphQLService();
|
||||
|
||||
WebInput input = new WebInput(
|
||||
URI.create("/"), new HttpHeaders(), Collections.singletonMap("query", "any"), "1");
|
||||
|
||||
WebOutput output = WebInterceptor.createHandler(interceptors, service).handle(input).block();
|
||||
|
||||
assertThat(sb.toString()).isEqualTo(":pre1:pre2:pre3:post3:post2:post1");
|
||||
assertThat(output.getResponseHeaders().get("name")).containsExactly("value3", "value2", "value1");
|
||||
assertThat(service.getSavedInput().getExtensions()).containsOnlyKeys("eKey1", "eKey2", "eKey3");
|
||||
}
|
||||
|
||||
|
||||
private static class TestGraphQLService implements GraphQLService {
|
||||
|
||||
private ExecutionInput savedInput;
|
||||
|
||||
public ExecutionInput getSavedInput() {
|
||||
return this.savedInput;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<ExecutionResult> execute(ExecutionInput input) {
|
||||
this.savedInput = input;
|
||||
return Mono.just(ExecutionResultImpl.newExecutionResult().build());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class TestWebInterceptor implements WebInterceptor {
|
||||
|
||||
private final StringBuilder output;
|
||||
|
||||
private final int index;
|
||||
|
||||
public TestWebInterceptor(StringBuilder output, int index) {
|
||||
this.output = output;
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<WebOutput> intercept(WebInput webInput, WebGraphQLHandler next) {
|
||||
|
||||
this.output.append(":pre").append(this.index);
|
||||
|
||||
webInput.configureExecutionInput((executionInput, builder) -> {
|
||||
Map<String, Object> extensions = new HashMap<>(executionInput.getExtensions());
|
||||
extensions.put("eKey" + this.index, "eValue" + this.index);
|
||||
return builder.extensions(extensions).build();
|
||||
});
|
||||
|
||||
return next.handle(webInput)
|
||||
.map(output -> {
|
||||
this.output.append(":post").append(this.index);
|
||||
return output.transform(builder -> builder.responseHeader("name", "value" + this.index));
|
||||
})
|
||||
.subscribeOn(Schedulers.boundedElastic());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -36,10 +36,11 @@ import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.io.buffer.DataBufferUtils;
|
||||
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
|
||||
import org.springframework.graphql.support.ExecutionGraphQLService;
|
||||
import org.springframework.graphql.support.GraphQLSource;
|
||||
import org.springframework.graphql.web.ConsumeOneAndNeverCompleteInterceptor;
|
||||
import org.springframework.graphql.web.DefaultWebGraphQLService;
|
||||
import org.springframework.graphql.web.GraphQLDataFetchers;
|
||||
import org.springframework.graphql.web.WebGraphQLHandler;
|
||||
import org.springframework.graphql.web.WebInterceptor;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.codec.ServerCodecConfigurer;
|
||||
@@ -273,12 +274,11 @@ public class GraphQLWebSocketHandlerTests {
|
||||
private GraphQLWebSocketHandler initWebSocketHandler(
|
||||
@Nullable List<WebInterceptor> interceptors, @Nullable Duration initTimeoutDuration) throws Exception {
|
||||
|
||||
DefaultWebGraphQLService requestHandler = new DefaultWebGraphQLService(initGraphQLSource());
|
||||
if (interceptors != null) {
|
||||
requestHandler.setInterceptors(interceptors);
|
||||
}
|
||||
WebGraphQLHandler graphQLHandler = WebInterceptor.createHandler(
|
||||
(interceptors != null ? interceptors : Collections.emptyList()),
|
||||
new ExecutionGraphQLService(initGraphQLSource()));
|
||||
|
||||
return new GraphQLWebSocketHandler(requestHandler,
|
||||
return new GraphQLWebSocketHandler(graphQLHandler,
|
||||
ServerCodecConfigurer.create(),
|
||||
(initTimeoutDuration != null ? initTimeoutDuration : Duration.ofSeconds(60)));
|
||||
}
|
||||
|
||||
@@ -31,10 +31,11 @@ import org.junit.jupiter.api.Test;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.graphql.support.ExecutionGraphQLService;
|
||||
import org.springframework.graphql.support.GraphQLSource;
|
||||
import org.springframework.graphql.web.ConsumeOneAndNeverCompleteInterceptor;
|
||||
import org.springframework.graphql.web.DefaultWebGraphQLService;
|
||||
import org.springframework.graphql.web.GraphQLDataFetchers;
|
||||
import org.springframework.graphql.web.WebGraphQLHandler;
|
||||
import org.springframework.graphql.web.WebInterceptor;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpInputMessage;
|
||||
@@ -257,12 +258,11 @@ public class GraphQLWebSocketHandlerTests {
|
||||
@Nullable List<WebInterceptor> interceptors, @Nullable Duration initTimeoutDuration) {
|
||||
|
||||
try {
|
||||
DefaultWebGraphQLService requestHandler = new DefaultWebGraphQLService(initGraphQLSource());
|
||||
if (interceptors != null) {
|
||||
requestHandler.setInterceptors(interceptors);
|
||||
}
|
||||
WebGraphQLHandler graphQLHandler = WebInterceptor.createHandler(
|
||||
(interceptors != null ? interceptors : Collections.emptyList()),
|
||||
new ExecutionGraphQLService(initGraphQLSource()));
|
||||
|
||||
return new GraphQLWebSocketHandler(requestHandler, converter,
|
||||
return new GraphQLWebSocketHandler(graphQLHandler, converter,
|
||||
(initTimeoutDuration != null ? initTimeoutDuration : Duration.ofSeconds(60)));
|
||||
}
|
||||
catch (Exception ex) {
|
||||
|
||||
Reference in New Issue
Block a user