Add property for GraphiQL UI path

This commit adds a new `spring.graphql.graphiql.path` configuration
property for setting the path to the GraphiQL UI page.

This change requires updates in the static HTML page to link the
JavaScript fetcher back to the actual GraphQL API endpoint.

Closes gh-70
This commit is contained in:
Brian Clozel
2021-06-29 13:48:48 +02:00
parent 9a4da366aa
commit eef61843d3
7 changed files with 160 additions and 18 deletions

View File

@@ -125,6 +125,9 @@ spring.graphql.schema.location=classpath:graphql/schema.graphqls
# endpoint path is concatenated with the main path, so "/graphql/schema" by default
spring.graphql.schema.printer.enabled=false
spring.graphql.schema.printer.path=/schema
# GraphiQL UI configuration
spring.graphql.graphiql.enabled=true
spring.graphql.graphiql.path=/graphiql
# whether micrometer metrics should be collected for graphql queries
management.metrics.graphql.autotime.enabled=true
````

View File

@@ -36,6 +36,8 @@ public class GraphQlProperties {
private final Schema schema = new Schema();
private final GraphiQL graphiql = new GraphiQL();
private final WebSocket websocket = new WebSocket();
public String getPath() {
@@ -50,6 +52,10 @@ public class GraphQlProperties {
return this.schema;
}
public GraphiQL getGraphiql() {
return this.graphiql;
}
public WebSocket getWebsocket() {
return this.websocket;
}
@@ -107,6 +113,35 @@ public class GraphQlProperties {
}
public static class GraphiQL {
/**
* Path to the GraphiQL UI endpoint.
*/
private String path = "/graphiql";
/**
* Whether the default GraphiQL UI is enabled.
*/
private boolean enabled = true;
public String getPath() {
return this.path;
}
public void setPath(String path) {
this.path = path;
}
public boolean isEnabled() {
return this.enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}
public static class WebSocket {
/**

View File

@@ -87,18 +87,21 @@ public class WebFluxGraphQlAutoConfiguration {
public RouterFunction<ServerResponse> graphQlEndpoint(GraphQlHttpHandler handler, GraphQlSource graphQlSource,
GraphQlProperties properties, ResourceLoader resourceLoader) {
String path = properties.getPath();
Resource resource = resourceLoader.getResource("classpath:graphiql/index.html");
String graphQLPath = properties.getPath();
if (logger.isInfoEnabled()) {
logger.info("GraphQL endpoint HTTP POST " + path);
logger.info("GraphQL endpoint HTTP POST " + graphQLPath);
}
// @formatter:off
RouterFunctions.Builder builder = RouterFunctions.route()
.GET(path, (req) -> ServerResponse.ok().bodyValue(resource))
.POST(path, accept(MediaType.APPLICATION_JSON).and(contentType(MediaType.APPLICATION_JSON)), handler::handleRequest);
.POST(graphQLPath, accept(MediaType.APPLICATION_JSON).and(contentType(MediaType.APPLICATION_JSON)), handler::handleRequest);
if (properties.getGraphiql().isEnabled()) {
Resource resource = resourceLoader.getResource("classpath:graphiql/index.html");
WebFluxGraphiQlHandler graphiQlHandler = new WebFluxGraphiQlHandler(graphQLPath, resource);
builder = builder.GET(properties.getGraphiql().getPath(), graphiQlHandler::showGraphiQlPage);
}
if (properties.getSchema().getPrinter().isEnabled()) {
SchemaPrinter printer = new SchemaPrinter();
builder = builder.GET(path + properties.getSchema().getPrinter().getPath(),
builder = builder.GET(graphQLPath + properties.getSchema().getPrinter().getPath(),
(req) -> ServerResponse.ok()
.contentType(MediaType.TEXT_PLAIN)
.bodyValue(printer.print(graphQlSource.schema())));

View File

@@ -0,0 +1,49 @@
/*
* 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.boot;
import reactor.core.publisher.Mono;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
/**
* WebFlux functional handler for the GraphiQl UI.
* @author Brian Clozel
*/
class WebFluxGraphiQlHandler {
private final String graphQlPath;
private final Resource graphiQlResource;
public WebFluxGraphiQlHandler(String graphQlPath, Resource graphiQlResource) {
this.graphQlPath = graphQlPath;
this.graphiQlResource = graphiQlResource;
}
public Mono<ServerResponse> showGraphiQlPage(ServerRequest request) {
if (request.queryParam("path").isPresent()) {
return ServerResponse.ok().contentType(MediaType.TEXT_HTML).bodyValue(this.graphiQlResource);
}
else {
return ServerResponse.temporaryRedirect(request.uriBuilder().queryParam("path", this.graphQlPath).build()).build();
}
}
}

View File

@@ -97,18 +97,21 @@ public class WebMvcGraphQlAutoConfiguration {
public RouterFunction<ServerResponse> graphQlRouterFunction(GraphQlHttpHandler handler, GraphQlSource graphQlSource,
GraphQlProperties properties, ResourceLoader resourceLoader) {
String path = properties.getPath();
Resource resource = resourceLoader.getResource("classpath:graphiql/index.html");
String graphQLPath = properties.getPath();
if (logger.isInfoEnabled()) {
logger.info("GraphQL endpoint HTTP POST " + path);
logger.info("GraphQL endpoint HTTP POST " + graphQLPath);
}
// @formatter:off
RouterFunctions.Builder builder = RouterFunctions.route()
.GET(path, (req) -> ServerResponse.ok().body(resource))
.POST(path, contentType(MediaType.APPLICATION_JSON).and(accept(MediaType.APPLICATION_JSON)), handler::handleRequest);
.POST(graphQLPath, contentType(MediaType.APPLICATION_JSON).and(accept(MediaType.APPLICATION_JSON)), handler::handleRequest);
if (properties.getGraphiql().isEnabled()) {
Resource resource = resourceLoader.getResource("classpath:graphiql/index.html");
WebMvcGraphiQlHandler graphiQLHandler = new WebMvcGraphiQlHandler(graphQLPath, resource);
builder = builder.GET(properties.getGraphiql().getPath(), graphiQLHandler::showGraphiQlPage);
}
if (properties.getSchema().getPrinter().isEnabled()) {
SchemaPrinter printer = new SchemaPrinter();
builder = builder.GET(path + properties.getSchema().getPrinter().getPath(),
builder = builder.GET(graphQLPath + properties.getSchema().getPrinter().getPath(),
(req) -> ServerResponse.ok()
.contentType(MediaType.TEXT_PLAIN)
.body(printer.print(graphQlSource.schema())));
@@ -118,7 +121,7 @@ public class WebMvcGraphQlAutoConfiguration {
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ ServerContainer.class, WebSocketHandler.class })
@ConditionalOnClass({ServerContainer.class, WebSocketHandler.class})
@ConditionalOnProperty(prefix = "spring.graphql.websocket", name = "path")
public static class WebSocketConfiguration {

View File

@@ -0,0 +1,47 @@
/*
* 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.boot;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType;
import org.springframework.web.servlet.function.ServerRequest;
import org.springframework.web.servlet.function.ServerResponse;
/**
* Servlet.fn handler for the GraphiQl UI.
* @author Brian Clozel
*/
class WebMvcGraphiQlHandler {
private final String graphQlPath;
private final Resource graphiQlResource;
public WebMvcGraphiQlHandler(String graphQlPath, Resource graphiQlResource) {
this.graphQlPath = graphQlPath;
this.graphiQlResource = graphiQlResource;
}
public ServerResponse showGraphiQlPage(ServerRequest request) {
if (request.param("path").isPresent()) {
return ServerResponse.ok().contentType(MediaType.TEXT_HTML).body(this.graphiQlResource);
}
else {
return ServerResponse.temporaryRedirect(request.uriBuilder().queryParam("path", this.graphQlPath).build()).build();
}
}
}

View File

@@ -59,11 +59,10 @@
src="https://unpkg.com/graphiql/graphiql.min.js"
type="application/javascript"
></script>
<script src="/renderExample.js" type="application/javascript"></script>
<script>
function graphQLFetcher(graphQLParams) {
return fetch(
window.location,
function graphQLFetcher(path) {
return graphQLParams => fetch(
`${location.protocol}//${location.host}${path}`,
{
method: 'post',
headers: {
@@ -80,9 +79,12 @@
});
}
const params = new URLSearchParams(window.location.search);
const path = params.get("path") || "/graphiql";
const gqlFetcher = graphQLFetcher(path);
ReactDOM.render(
React.createElement(GraphiQL, {
fetcher: graphQLFetcher,
fetcher: gqlFetcher,
defaultVariableEditorOpen: true,
}),
document.getElementById('graphiql'),