Skip to content
This repository was archived by the owner on Dec 19, 2023. It is now read-only.

Commit f4d9732

Browse files
authored
Merge pull request #345 from rbraeunlich/master
Add support for GraphiQL in a reactive context
2 parents f229b0d + cdb27fa commit f4d9732

File tree

7 files changed

+182
-20
lines changed

7 files changed

+182
-20
lines changed

graphiql-spring-boot-autoconfigure/build.gradle

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,12 @@ dependencies{
2323
compile "org.apache.commons:commons-text:1.1"
2424
compileOnly "org.springframework.boot:spring-boot-starter-web:$LIB_SPRING_BOOT_VER"
2525
compileOnly "org.springframework.boot:spring-boot-starter-security:$LIB_SPRING_BOOT_VER"
26+
compileOnly "io.projectreactor:reactor-core:3.3.0.RELEASE"
2627

2728
testCompile "org.springframework.boot:spring-boot-starter-web:$LIB_SPRING_BOOT_VER"
2829
testCompile "org.springframework.boot:spring-boot-starter-test:$LIB_SPRING_BOOT_VER"
30+
31+
testRuntime "org.springframework.boot:spring-boot-starter-webflux:$LIB_SPRING_BOOT_VER"
2932
}
3033

3134
compileJava.dependsOn(processResources)
Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,36 @@
11
package com.oembedler.moon.graphiql.boot;
22

33
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
4+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
45
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
56
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
67
import org.springframework.boot.context.properties.EnableConfigurationProperties;
78
import org.springframework.context.annotation.Bean;
89
import org.springframework.context.annotation.Configuration;
910
import org.springframework.web.servlet.DispatcherServlet;
1011

12+
import static org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type.SERVLET;
13+
import static org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type.REACTIVE;
14+
1115
/**
1216
* @author Andrew Potter
17+
* @author Ronny Bräunlich
1318
*/
1419
@Configuration
15-
@ConditionalOnWebApplication
16-
@ConditionalOnClass(DispatcherServlet.class)
20+
@ConditionalOnProperty(value = "graphiql.enabled", havingValue = "true", matchIfMissing = true)
1721
@EnableConfigurationProperties(GraphiQLProperties.class)
1822
public class GraphiQLAutoConfiguration {
1923

20-
@Bean
21-
@ConditionalOnProperty(value = "graphiql.enabled", havingValue = "true", matchIfMissing = true)
22-
GraphiQLController graphiQLController() {
23-
return new GraphiQLController();
24+
@Bean(name = "graphiQLController")
25+
@ConditionalOnWebApplication(type=SERVLET)
26+
ServletGraphiQLController servletGraphiQLController() {
27+
return new ServletGraphiQLController();
2428
}
2529

30+
@Bean(name = "graphiQLController")
31+
@ConditionalOnMissingBean(ServletGraphiQLController.class)
32+
@ConditionalOnWebApplication(type=REACTIVE)
33+
ReactiveGraphiQLController reactiveGraphiQLController() {
34+
return new ReactiveGraphiQLController();
35+
}
2636
}

graphiql-spring-boot-autoconfigure/src/main/java/com/oembedler/moon/graphiql/boot/GraphiQLController.java

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,7 @@
3030
* @author Andrew Potter
3131
*/
3232
@Slf4j
33-
@Controller
34-
public class GraphiQLController {
33+
public abstract class GraphiQLController {
3534

3635
private static final String CDNJS_CLOUDFLARE_COM_AJAX_LIBS = "//cdnjs.cloudflare.com/ajax/libs/";
3736
private static final String CDN_JSDELIVR_NET_NPM = "//cdn.jsdelivr.net/npm/";
@@ -48,7 +47,6 @@ public class GraphiQLController {
4847
private String props;
4948
private Properties headerProperties;
5049

51-
@PostConstruct
5250
public void onceConstructed() throws IOException {
5351
loadTemplate();
5452
loadProps();
@@ -78,23 +76,20 @@ private void addIfAbsent(Properties headerProperties, String header) {
7876
}
7977
}
8078

81-
@GetMapping(value = "${graphiql.mapping:/graphiql}")
82-
public void graphiql(HttpServletRequest request, HttpServletResponse response, @PathVariable Map<String, String> params) throws IOException {
83-
response.setContentType("text/html; charset=UTF-8");
84-
Object csrf = request.getAttribute("_csrf");
79+
public byte[] graphiql(String contextPath, @PathVariable Map<String, String> params, Object csrf) {
8580
if (csrf != null) {
8681
CsrfToken csrfToken = (CsrfToken) csrf;
8782
headerProperties.setProperty(csrfToken.getHeaderName(), csrfToken.getToken());
8883
}
8984

9085
Map<String, String> replacements = getReplacements(
91-
constructGraphQlEndpoint(request, params),
92-
request.getContextPath() + graphiQLProperties.getEndpoint().getSubscriptions(),
93-
request.getContextPath() + graphiQLProperties.getSTATIC().getBasePath()
86+
constructGraphQlEndpoint(contextPath, params),
87+
contextPath + graphiQLProperties.getEndpoint().getSubscriptions(),
88+
contextPath + graphiQLProperties.getSTATIC().getBasePath()
9489
);
9590

9691
String populatedTemplate = StrSubstitutor.replace(template, replacements);
97-
response.getOutputStream().write(populatedTemplate.getBytes(Charset.defaultCharset()));
92+
return populatedTemplate.getBytes(Charset.defaultCharset());
9893
}
9994

10095
private Map<String, String> getReplacements(
@@ -169,13 +164,13 @@ private String joinJsDelivrPath(String library, String cdnVersion, String cdnFil
169164
return CDN_JSDELIVR_NET_NPM + library + "@" + cdnVersion + "/" + cdnFileName;
170165
}
171166

172-
private String constructGraphQlEndpoint(HttpServletRequest request, @RequestParam Map<String, String> params) {
167+
private String constructGraphQlEndpoint(String contextPath, @RequestParam Map<String, String> params) {
173168
String endpoint = graphiQLProperties.getEndpoint().getGraphql();
174169
for (Map.Entry<String, String> param : params.entrySet()) {
175170
endpoint = endpoint.replaceAll("\\{" + param.getKey() + "}", param.getValue());
176171
}
177-
if (StringUtils.isNotBlank(request.getContextPath()) && !endpoint.startsWith(request.getContextPath())) {
178-
return request.getContextPath() + endpoint;
172+
if (StringUtils.isNotBlank(contextPath) && !endpoint.startsWith(contextPath)) {
173+
return contextPath + endpoint;
179174
}
180175
return endpoint;
181176
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package com.oembedler.moon.graphiql.boot;
2+
3+
import lombok.extern.slf4j.Slf4j;
4+
import org.apache.commons.lang3.StringUtils;
5+
import org.apache.commons.lang3.text.StrSubstitutor;
6+
import org.springframework.beans.factory.annotation.Value;
7+
import org.springframework.core.io.ClassPathResource;
8+
import org.springframework.core.io.buffer.DataBufferFactory;
9+
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
10+
import org.springframework.http.MediaType;
11+
import org.springframework.http.server.reactive.ServerHttpRequest;
12+
import org.springframework.http.server.reactive.ServerHttpResponse;
13+
import org.springframework.stereotype.Controller;
14+
import org.springframework.util.StreamUtils;
15+
import org.springframework.web.bind.annotation.PathVariable;
16+
import org.springframework.web.bind.annotation.RequestMapping;
17+
import org.springframework.web.bind.annotation.RequestParam;
18+
import reactor.core.publisher.Mono;
19+
20+
import javax.annotation.PostConstruct;
21+
import java.io.IOException;
22+
import java.io.InputStream;
23+
import java.nio.charset.Charset;
24+
import java.util.HashMap;
25+
import java.util.Map;
26+
27+
@Slf4j
28+
@Controller
29+
public class ReactiveGraphiQLController extends GraphiQLController {
30+
31+
private DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
32+
33+
@PostConstruct
34+
public void onceConstructed() throws IOException {
35+
super.onceConstructed();
36+
}
37+
38+
@RequestMapping(value = "${graphiql.mapping:/graphiql}")
39+
public Mono<Void> graphiql(ServerHttpRequest request, ServerHttpResponse response,
40+
@PathVariable Map<String, String> params) {
41+
response.getHeaders().setContentType(MediaType.TEXT_HTML);
42+
Object csrf = request.getQueryParams().getFirst("_csrf");
43+
return response.writeWith(Mono.just(request.getPath().contextPath().value())
44+
.map(contextPath -> super.graphiql(contextPath, params, csrf))
45+
.map(dataBufferFactory::wrap));
46+
}
47+
48+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package com.oembedler.moon.graphiql.boot;
2+
3+
import lombok.extern.slf4j.Slf4j;
4+
import org.springframework.stereotype.Controller;
5+
import org.springframework.web.bind.annotation.GetMapping;
6+
import org.springframework.web.bind.annotation.PathVariable;
7+
8+
import javax.annotation.PostConstruct;
9+
import javax.servlet.http.HttpServletRequest;
10+
import javax.servlet.http.HttpServletResponse;
11+
import java.io.IOException;
12+
import java.util.Map;
13+
14+
/**
15+
* @author Andrew Potter
16+
*/
17+
@Slf4j
18+
@Controller
19+
public class ServletGraphiQLController extends GraphiQLController{
20+
21+
@PostConstruct
22+
public void onceConstructed() throws IOException {
23+
super.onceConstructed();
24+
}
25+
26+
@GetMapping(value = "${graphiql.mapping:/graphiql}")
27+
public void graphiql(HttpServletRequest request, HttpServletResponse response,
28+
@PathVariable Map<String, String> params) throws IOException {
29+
response.setContentType("text/html; charset=UTF-8");
30+
Object csrf = request.getAttribute("_csrf");
31+
byte[] graphiqlBytes = super.graphiql(request.getContextPath(), params, csrf);
32+
response.getOutputStream().write(graphiqlBytes);
33+
}
34+
35+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package com.oembedler.moon.graphiql.boot;
2+
3+
import org.junit.Test;
4+
import org.junit.runner.RunWith;
5+
import org.springframework.beans.factory.annotation.Autowired;
6+
import org.springframework.boot.SpringBootConfiguration;
7+
import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest;
8+
import org.springframework.context.annotation.Import;
9+
import org.springframework.http.MediaType;
10+
import org.springframework.test.context.TestPropertySource;
11+
import org.springframework.test.context.junit4.SpringRunner;
12+
import org.springframework.test.web.reactive.server.WebTestClient;
13+
14+
@RunWith(SpringRunner.class)
15+
@WebFluxTest
16+
public class ReactiveGraphiQLControllerTest {
17+
18+
@Autowired
19+
private WebTestClient webTestClient;
20+
21+
@Test
22+
public void shouldBeAbleToAccessGraphiQL() {
23+
webTestClient.get()
24+
.uri("/graphiql")
25+
.exchange()
26+
.expectStatus().is2xxSuccessful()
27+
.expectHeader().contentType(MediaType.TEXT_HTML);
28+
}
29+
30+
@SpringBootConfiguration
31+
@TestPropertySource(properties = "graphiql.enabled=true")
32+
@Import(GraphiQLAutoConfiguration.class)
33+
public static class ReactiveTestApplication {
34+
}
35+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.oembedler.moon.graphiql.boot;
2+
3+
import org.junit.Test;
4+
import org.junit.runner.RunWith;
5+
import org.springframework.beans.factory.annotation.Autowired;
6+
import org.springframework.boot.SpringBootConfiguration;
7+
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
8+
import org.springframework.context.annotation.Import;
9+
import org.springframework.test.context.TestPropertySource;
10+
import org.springframework.test.context.junit4.SpringRunner;
11+
import org.springframework.test.web.servlet.MockMvc;
12+
13+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
14+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
15+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
16+
17+
@RunWith(SpringRunner.class)
18+
@WebMvcTest
19+
public class ServletGraphiQLControllerTest {
20+
21+
@Autowired
22+
private MockMvc mockMvc;
23+
24+
@Test
25+
public void shouldBeAbleToAccessGraphiQL() throws Exception {
26+
mockMvc.perform(get("/graphiql"))
27+
.andExpect(status().is2xxSuccessful())
28+
.andExpect(content().contentType("text/html; charset=UTF-8"));
29+
}
30+
31+
@SpringBootConfiguration
32+
@TestPropertySource(properties = "graphiql.enabled=true")
33+
@Import(GraphiQLAutoConfiguration.class)
34+
public static class ServletTestApplication {
35+
}
36+
}

0 commit comments

Comments
 (0)