Simplify access to WebSocketInterceptor
WebGraphQlHandler is no longer aware of individual callbacks on WebSocketInterceptor and instead provides a method to return the interceptor. This allows the WebSocket handlers for WebFlux and WebMvc to call the interceptor directly. See gh-276
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2022 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.
|
||||
@@ -18,10 +18,7 @@ package org.springframework.graphql.web;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@@ -32,6 +29,7 @@ import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
|
||||
/**
|
||||
* Default implementation of {@link WebGraphQlHandler.Builder}.
|
||||
*
|
||||
@@ -41,17 +39,21 @@ class DefaultWebGraphQlHandlerBuilder implements WebGraphQlHandler.Builder {
|
||||
|
||||
private final GraphQlService service;
|
||||
|
||||
private final List<WebInterceptor> interceptors = new ArrayList<>();
|
||||
|
||||
@Nullable
|
||||
private List<WebInterceptor> interceptors;
|
||||
private WebSocketInterceptor webSocketInterceptor;
|
||||
|
||||
@Nullable
|
||||
private List<ThreadLocalAccessor> accessors;
|
||||
|
||||
|
||||
DefaultWebGraphQlHandlerBuilder(GraphQlService service) {
|
||||
Assert.notNull(service, "GraphQlService is required");
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public WebGraphQlHandler.Builder interceptor(WebInterceptor... interceptors) {
|
||||
return interceptors(Arrays.asList(interceptors));
|
||||
@@ -59,10 +61,13 @@ class DefaultWebGraphQlHandlerBuilder implements WebGraphQlHandler.Builder {
|
||||
|
||||
@Override
|
||||
public WebGraphQlHandler.Builder interceptors(List<WebInterceptor> interceptors) {
|
||||
if (!CollectionUtils.isEmpty(interceptors)) {
|
||||
this.interceptors = (this.interceptors != null) ? this.interceptors : new ArrayList<>();
|
||||
this.interceptors.addAll(interceptors);
|
||||
}
|
||||
this.interceptors.addAll(interceptors);
|
||||
interceptors.forEach(interceptor -> {
|
||||
if (interceptor instanceof WebSocketInterceptor) {
|
||||
Assert.isNull(this.webSocketInterceptor, "There can be at most 1 WebSocketInterceptor");
|
||||
this.webSocketInterceptor = (WebSocketInterceptor) interceptor;
|
||||
}
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -83,98 +88,34 @@ class DefaultWebGraphQlHandlerBuilder implements WebGraphQlHandler.Builder {
|
||||
@Override
|
||||
public WebGraphQlHandler build() {
|
||||
|
||||
List<WebInterceptor> interceptorsToUse =
|
||||
(this.interceptors != null) ? this.interceptors : Collections.emptyList();
|
||||
WebInterceptorChain endOfChain =
|
||||
webInput -> this.service.execute(webInput).map(WebOutput::new);
|
||||
|
||||
WebInterceptorChain interceptorChain = initWebInterceptorChain(interceptorsToUse);
|
||||
WebSocketInterceptor webSocketInterceptor = initWebSocketInterceptor(interceptorsToUse);
|
||||
|
||||
WebGraphQlHandler graphQlHandler = new WebGraphQlHandler() {
|
||||
|
||||
@Override
|
||||
public Mono<WebOutput> handleRequest(WebInput input) {
|
||||
return interceptorChain.next(input);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Object> handleWebSocketInitialization(Map<String, Object> payload) {
|
||||
return (webSocketInterceptor != null ?
|
||||
webSocketInterceptor.handleConnectionInitialization(payload) : Mono.empty());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> handleWebSocketCompletion() {
|
||||
return (webSocketInterceptor != null ?
|
||||
webSocketInterceptor.handleConnectionCompletion() : Mono.empty());
|
||||
}
|
||||
};
|
||||
|
||||
if (!CollectionUtils.isEmpty(this.accessors)) {
|
||||
graphQlHandler = new ThreadLocalExtractingHandler(
|
||||
graphQlHandler, ThreadLocalAccessor.composite(this.accessors));
|
||||
}
|
||||
|
||||
return graphQlHandler;
|
||||
}
|
||||
|
||||
private WebInterceptorChain initWebInterceptorChain(List<WebInterceptor> interceptors) {
|
||||
|
||||
WebInterceptorChain endOfChain = webInput -> this.service.execute(webInput).map(WebOutput::new);
|
||||
|
||||
return interceptors.stream()
|
||||
WebInterceptorChain chain = this.interceptors.stream()
|
||||
.reduce(WebInterceptor::andThen)
|
||||
.map(interceptor -> (WebInterceptorChain) (input) -> interceptor.intercept(input, endOfChain))
|
||||
.orElse(endOfChain);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private WebSocketInterceptor initWebSocketInterceptor(List<WebInterceptor> interceptors) {
|
||||
return new WebGraphQlHandler() {
|
||||
|
||||
List<WebSocketInterceptor> filtered = interceptors.stream()
|
||||
.filter(current -> current instanceof WebSocketInterceptor)
|
||||
.map(current -> (WebSocketInterceptor) current)
|
||||
.collect(Collectors.toList());
|
||||
@Override
|
||||
public Mono<WebOutput> handleRequest(WebInput input) {
|
||||
return chain.next(input)
|
||||
.contextWrite(context -> {
|
||||
if (!CollectionUtils.isEmpty(accessors)) {
|
||||
ThreadLocalAccessor accessor = ThreadLocalAccessor.composite(accessors);
|
||||
return ReactorContextManager.extractThreadLocalValues(accessor, context);
|
||||
}
|
||||
return context;
|
||||
});
|
||||
}
|
||||
|
||||
if (filtered.size() > 1) {
|
||||
throw new IllegalArgumentException(
|
||||
"There can be at most 1 WebSocketInterceptor. Found " + filtered.size() + ".");
|
||||
}
|
||||
|
||||
return (!filtered.isEmpty() ? filtered.get(0) : null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@link WebGraphQlHandler} that extracts ThreadLocal values and saves them in the
|
||||
* Reactor context for subsequent use for DataFetcher's.
|
||||
*/
|
||||
private static class ThreadLocalExtractingHandler implements WebGraphQlHandler {
|
||||
|
||||
private final WebGraphQlHandler delegate;
|
||||
|
||||
private final ThreadLocalAccessor accessor;
|
||||
|
||||
ThreadLocalExtractingHandler(WebGraphQlHandler delegate, ThreadLocalAccessor accessor) {
|
||||
this.delegate = delegate;
|
||||
this.accessor = accessor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<WebOutput> handleRequest(WebInput input) {
|
||||
return this.delegate.handleRequest(input).contextWrite((context) ->
|
||||
ReactorContextManager.extractThreadLocalValues(this.accessor, context));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Object> handleWebSocketInitialization(Map<String, Object> payload) {
|
||||
return this.delegate.handleWebSocketInitialization(payload);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> handleWebSocketCompletion() {
|
||||
return this.delegate.handleWebSocketCompletion();
|
||||
}
|
||||
@Override
|
||||
public WebSocketInterceptor webSocketInterceptor() {
|
||||
return (webSocketInterceptor != null ? webSocketInterceptor : new WebSocketInterceptor() {});
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2022 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.
|
||||
@@ -17,13 +17,13 @@
|
||||
package org.springframework.graphql.web;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.graphql.GraphQlService;
|
||||
import org.springframework.graphql.execution.ThreadLocalAccessor;
|
||||
|
||||
|
||||
/**
|
||||
* Contract for common handling of a GraphQL request over HTTP or WebSocket,
|
||||
* for use with Spring MVC or Spring WebFlux.
|
||||
@@ -33,6 +33,7 @@ import org.springframework.graphql.execution.ThreadLocalAccessor;
|
||||
*/
|
||||
public interface WebGraphQlHandler {
|
||||
|
||||
|
||||
/**
|
||||
* Execute the given request and return the resulting output.
|
||||
* @param input the GraphQL request input container
|
||||
@@ -41,24 +42,10 @@ public interface WebGraphQlHandler {
|
||||
Mono<WebOutput> handleRequest(WebInput input);
|
||||
|
||||
/**
|
||||
* Handle the payload from the connection initialization message that a
|
||||
* GraphQL over WebSocket client must send after the WebSocket session is
|
||||
* established and before sending any requests.
|
||||
* @param payload the payload from the {@code ConnectionInit} message
|
||||
* @return an optional payload for the {@code ConnectionAck} message
|
||||
* Return the single interceptor of type {@link WebSocketInterceptor} among
|
||||
* all the configured interceptors.
|
||||
*/
|
||||
default Mono<Object> handleWebSocketInitialization(Map<String, Object> payload) {
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the completion message that a GraphQL over WebSocket clients sends
|
||||
* before closing the WebSocket connection.
|
||||
* @return signals the end of completion handling
|
||||
*/
|
||||
default Mono<Void> handleWebSocketCompletion() {
|
||||
return Mono.empty();
|
||||
}
|
||||
WebSocketInterceptor webSocketInterceptor();
|
||||
|
||||
|
||||
/**
|
||||
|
||||
@@ -35,6 +35,7 @@ import reactor.core.publisher.Mono;
|
||||
import org.springframework.graphql.web.WebGraphQlHandler;
|
||||
import org.springframework.graphql.web.WebInput;
|
||||
import org.springframework.graphql.web.WebOutput;
|
||||
import org.springframework.graphql.web.WebSocketInterceptor;
|
||||
import org.springframework.http.codec.CodecConfigurer;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
@@ -61,6 +62,8 @@ public class GraphQlWebSocketHandler implements WebSocketHandler {
|
||||
|
||||
private final WebGraphQlHandler graphQlHandler;
|
||||
|
||||
private final WebSocketInterceptor webSocketInterceptor;
|
||||
|
||||
private final CodecDelegate codecDelegate;
|
||||
|
||||
private final Duration initTimeoutDuration;
|
||||
@@ -77,7 +80,9 @@ public class GraphQlWebSocketHandler implements WebSocketHandler {
|
||||
WebGraphQlHandler graphQlHandler, CodecConfigurer codecConfigurer, Duration connectionInitTimeout) {
|
||||
|
||||
Assert.notNull(graphQlHandler, "WebGraphQlHandler is required");
|
||||
|
||||
this.graphQlHandler = graphQlHandler;
|
||||
this.webSocketInterceptor = this.graphQlHandler.webSocketInterceptor();
|
||||
this.codecDelegate = new CodecDelegate(codecConfigurer);
|
||||
this.initTimeoutDuration = connectionInitTimeout;
|
||||
}
|
||||
@@ -137,12 +142,12 @@ public class GraphQlWebSocketHandler implements WebSocketHandler {
|
||||
subscription.cancel();
|
||||
}
|
||||
}
|
||||
return this.graphQlHandler.handleWebSocketCompletion().thenMany(Flux.empty());
|
||||
return this.webSocketInterceptor.handleConnectionCompletion().thenMany(Flux.empty());
|
||||
case "connection_init":
|
||||
if (!connectionInitProcessed.compareAndSet(false, true)) {
|
||||
return GraphQlStatus.close(session, GraphQlStatus.TOO_MANY_INIT_REQUESTS_STATUS);
|
||||
}
|
||||
return this.graphQlHandler.handleWebSocketInitialization(payload)
|
||||
return this.webSocketInterceptor.handleConnectionInitialization(payload)
|
||||
.defaultIfEmpty(Collections.emptyMap())
|
||||
.map(ackPayload -> this.codecDelegate.encodeConnectionAck(session, ackPayload))
|
||||
.flux()
|
||||
|
||||
@@ -45,6 +45,7 @@ import reactor.core.scheduler.Schedulers;
|
||||
import org.springframework.graphql.web.WebGraphQlHandler;
|
||||
import org.springframework.graphql.web.WebInput;
|
||||
import org.springframework.graphql.web.WebOutput;
|
||||
import org.springframework.graphql.web.WebSocketInterceptor;
|
||||
import org.springframework.graphql.web.webflux.GraphQlWebSocketMessage;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpInputMessage;
|
||||
@@ -78,6 +79,8 @@ public class GraphQlWebSocketHandler extends TextWebSocketHandler implements Sub
|
||||
|
||||
private final WebGraphQlHandler graphQlHandler;
|
||||
|
||||
private final WebSocketInterceptor webSocketInterceptor;
|
||||
|
||||
private final Duration initTimeoutDuration;
|
||||
|
||||
private final HttpMessageConverter<?> converter;
|
||||
@@ -92,12 +95,13 @@ public class GraphQlWebSocketHandler extends TextWebSocketHandler implements Sub
|
||||
* message must be received.
|
||||
*/
|
||||
public GraphQlWebSocketHandler(
|
||||
WebGraphQlHandler graphQlHandler, HttpMessageConverter<?> converter,
|
||||
Duration connectionInitTimeout) {
|
||||
WebGraphQlHandler graphQlHandler, HttpMessageConverter<?> converter, Duration connectionInitTimeout) {
|
||||
|
||||
Assert.notNull(graphQlHandler, "WebGraphQlHandler is required");
|
||||
Assert.notNull(converter, "HttpMessageConverter for JSON is required");
|
||||
|
||||
this.graphQlHandler = graphQlHandler;
|
||||
this.webSocketInterceptor = this.graphQlHandler.webSocketInterceptor();
|
||||
this.initTimeoutDuration = connectionInitTimeout;
|
||||
this.converter = converter;
|
||||
}
|
||||
@@ -166,14 +170,14 @@ public class GraphQlWebSocketHandler extends TextWebSocketHandler implements Sub
|
||||
subscription.cancel();
|
||||
}
|
||||
}
|
||||
this.graphQlHandler.handleWebSocketCompletion().block(Duration.ofSeconds(10));
|
||||
this.webSocketInterceptor.handleConnectionCompletion().block(Duration.ofSeconds(10));
|
||||
return;
|
||||
case "connection_init":
|
||||
if (sessionState.setConnectionInitProcessed()) {
|
||||
GraphQlStatus.closeSession(session, GraphQlStatus.TOO_MANY_INIT_REQUESTS_STATUS);
|
||||
return;
|
||||
}
|
||||
this.graphQlHandler.handleWebSocketInitialization(payload)
|
||||
this.webSocketInterceptor.handleConnectionInitialization(payload)
|
||||
.defaultIfEmpty(Collections.emptyMap())
|
||||
.publishOn(sessionState.getScheduler()) // Serial blocking send via single thread
|
||||
.doOnNext(ackPayload -> {
|
||||
|
||||
@@ -140,8 +140,8 @@ public class GraphQlWebSocketHandlerTests extends WebSocketHandlerTestSupport {
|
||||
new WebSocketInterceptor() {
|
||||
|
||||
@Override
|
||||
public Mono<Object> handleConnectionInitialization(Map<String, Object> payload) {
|
||||
Object value = payload.get("key");
|
||||
public Mono<Object> handleConnectionInitialization(Map<String, Object> connectionInitPayload) {
|
||||
Object value = connectionInitPayload.get("key");
|
||||
return Mono.just(Collections.singletonMap("key", value + " acknowledged"));
|
||||
}
|
||||
});
|
||||
@@ -162,7 +162,7 @@ public class GraphQlWebSocketHandlerTests extends WebSocketHandlerTestSupport {
|
||||
new WebSocketInterceptor() {
|
||||
|
||||
@Override
|
||||
public Mono<Object> handleConnectionInitialization(Map<String, Object> payload) {
|
||||
public Mono<Object> handleConnectionInitialization(Map<String, Object> connectionInitPayload) {
|
||||
return Mono.error(new IllegalStateException());
|
||||
}
|
||||
});
|
||||
|
||||
@@ -141,8 +141,8 @@ public class GraphQlWebSocketHandlerTests extends WebSocketHandlerTestSupport {
|
||||
WebSocketInterceptor interceptor = new WebSocketInterceptor() {
|
||||
|
||||
@Override
|
||||
public Mono<Object> handleConnectionInitialization(Map<String, Object> payload) {
|
||||
Object value = payload.get("key");
|
||||
public Mono<Object> handleConnectionInitialization(Map<String, Object> connectionInitPayload) {
|
||||
Object value = connectionInitPayload.get("key");
|
||||
return Mono.just(Collections.singletonMap("key", value + " acknowledged"));
|
||||
}
|
||||
};
|
||||
@@ -166,7 +166,7 @@ public class GraphQlWebSocketHandlerTests extends WebSocketHandlerTestSupport {
|
||||
WebSocketInterceptor interceptor = new WebSocketInterceptor() {
|
||||
|
||||
@Override
|
||||
public Mono<Object> handleConnectionInitialization(Map<String, Object> payload) {
|
||||
public Mono<Object> handleConnectionInitialization(Map<String, Object> connectionInitPayload) {
|
||||
return Mono.error(new IllegalStateException());
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user