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:
Rossen Stoyanchev
2021-04-30 21:26:26 +01:00
parent e5a92a310d
commit f93c46eee3
29 changed files with 402 additions and 407 deletions

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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

View File

@@ -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")));
}
}

View File

@@ -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")));
}
}