|
17 | 17 | package org.springframework.boot.autoconfigure.graphql.servlet;
|
18 | 18 |
|
19 | 19 | import java.util.Collections;
|
| 20 | +import java.util.Map; |
20 | 21 | import java.util.stream.Collectors;
|
21 | 22 |
|
| 23 | +import javax.websocket.server.ServerContainer; |
| 24 | + |
22 | 25 | import graphql.GraphQL;
|
23 | 26 | import org.apache.commons.logging.Log;
|
24 | 27 | import org.apache.commons.logging.LogFactory;
|
|
29 | 32 | import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
30 | 33 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
31 | 34 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
| 35 | +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; |
32 | 36 | import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
33 | 37 | import org.springframework.boot.autoconfigure.graphql.GraphQlAutoConfiguration;
|
34 | 38 | import org.springframework.boot.autoconfigure.graphql.GraphQlCorsProperties;
|
35 | 39 | import org.springframework.boot.autoconfigure.graphql.GraphQlProperties;
|
| 40 | +import org.springframework.boot.autoconfigure.http.HttpMessageConverters; |
36 | 41 | import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
37 | 42 | import org.springframework.context.annotation.Bean;
|
38 | 43 | import org.springframework.context.annotation.Configuration;
|
|
43 | 48 | import org.springframework.graphql.web.WebGraphQlHandler;
|
44 | 49 | import org.springframework.graphql.web.WebInterceptor;
|
45 | 50 | import org.springframework.graphql.web.webmvc.GraphQlHttpHandler;
|
| 51 | +import org.springframework.graphql.web.webmvc.GraphQlWebSocketHandler; |
46 | 52 | import org.springframework.graphql.web.webmvc.GraphiQlHandler;
|
47 | 53 | import org.springframework.graphql.web.webmvc.SchemaHandler;
|
48 | 54 | import org.springframework.http.HttpMethod;
|
49 | 55 | import org.springframework.http.HttpStatus;
|
50 | 56 | import org.springframework.http.MediaType;
|
| 57 | +import org.springframework.http.converter.GenericHttpMessageConverter; |
51 | 58 | import org.springframework.web.cors.CorsConfiguration;
|
| 59 | +import org.springframework.web.servlet.HandlerMapping; |
52 | 60 | import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
53 | 61 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
54 | 62 | import org.springframework.web.servlet.function.RequestPredicates;
|
55 | 63 | import org.springframework.web.servlet.function.RouterFunction;
|
56 | 64 | import org.springframework.web.servlet.function.RouterFunctions;
|
57 | 65 | import org.springframework.web.servlet.function.ServerResponse;
|
| 66 | +import org.springframework.web.socket.WebSocketHandler; |
| 67 | +import org.springframework.web.socket.server.support.DefaultHandshakeHandler; |
| 68 | +import org.springframework.web.socket.server.support.WebSocketHandlerMapping; |
| 69 | +import org.springframework.web.socket.server.support.WebSocketHttpRequestHandler; |
58 | 70 |
|
59 | 71 | /**
|
60 | 72 | * {@link EnableAutoConfiguration Auto-configuration} for enabling Spring GraphQL over
|
@@ -140,4 +152,42 @@ public void addCorsMappings(CorsRegistry registry) {
|
140 | 152 |
|
141 | 153 | }
|
142 | 154 |
|
| 155 | + @Configuration(proxyBeanMethods = false) |
| 156 | + @ConditionalOnClass({ ServerContainer.class, WebSocketHandler.class }) |
| 157 | + @ConditionalOnProperty(prefix = "spring.graphql.websocket", name = "path") |
| 158 | + public static class WebSocketConfiguration { |
| 159 | + |
| 160 | + @Bean |
| 161 | + @ConditionalOnMissingBean |
| 162 | + public GraphQlWebSocketHandler graphQlWebSocketHandler(WebGraphQlHandler webGraphQlHandler, |
| 163 | + GraphQlProperties properties, HttpMessageConverters converters) { |
| 164 | + |
| 165 | + return new GraphQlWebSocketHandler(webGraphQlHandler, getJsonConverter(converters), |
| 166 | + properties.getWebsocket().getConnectionInitTimeout()); |
| 167 | + } |
| 168 | + |
| 169 | + @SuppressWarnings("unchecked") |
| 170 | + private static GenericHttpMessageConverter<Object> getJsonConverter(HttpMessageConverters converters) { |
| 171 | + return converters.getConverters().stream() |
| 172 | + .filter((candidate) -> candidate.canRead(Map.class, MediaType.APPLICATION_JSON)).findFirst() |
| 173 | + .map((converter) -> (GenericHttpMessageConverter<Object>) converter) |
| 174 | + .orElseThrow(() -> new IllegalStateException("No JSON converter")); |
| 175 | + } |
| 176 | + |
| 177 | + @Bean |
| 178 | + public HandlerMapping graphQlWebSocketMapping(GraphQlWebSocketHandler handler, GraphQlProperties properties) { |
| 179 | + String path = properties.getWebsocket().getPath(); |
| 180 | + if (logger.isInfoEnabled()) { |
| 181 | + logger.info("GraphQL endpoint WebSocket " + path); |
| 182 | + } |
| 183 | + WebSocketHandlerMapping mapping = new WebSocketHandlerMapping(); |
| 184 | + mapping.setWebSocketUpgradeMatch(true); |
| 185 | + mapping.setUrlMap(Collections.singletonMap(path, |
| 186 | + new WebSocketHttpRequestHandler(handler, new DefaultHandshakeHandler()))); |
| 187 | + mapping.setOrder(2); // Ahead of HTTP endpoint ("routerFunctionMapping" bean) |
| 188 | + return mapping; |
| 189 | + } |
| 190 | + |
| 191 | + } |
| 192 | + |
143 | 193 | }
|
0 commit comments