WebOutput minor updates
Rename "headers" to "responseHeaders" and clarify those work for HTTP request queries but not for queries over a WebSocket session. Update getter for response headers to return a read-only wrapper. Provide only one public constructor without headers. See gh-42
This commit is contained in:
@@ -141,8 +141,8 @@ class WebFluxApplicationContextTests {
|
||||
public WebInterceptor customWebInterceptor() {
|
||||
return new WebInterceptor() {
|
||||
@Override
|
||||
public Mono<WebOutput> postHandle(WebOutput webOutput) {
|
||||
return Mono.just(webOutput.transform(output -> output.header("X-Custom-Header", "42")));
|
||||
public Mono<WebOutput> postHandle(WebOutput output) {
|
||||
return Mono.just(output.transform(builder -> builder.responseHeader("X-Custom-Header", "42")));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -137,8 +137,9 @@ class WebMvcApplicationContextTests {
|
||||
public WebInterceptor customWebInterceptor() {
|
||||
return new WebInterceptor() {
|
||||
@Override
|
||||
public Mono<WebOutput> postHandle(WebOutput webOutput) {
|
||||
return Mono.just(webOutput.transform(output -> output.header("X-Custom-Header", "42")));
|
||||
public Mono<WebOutput> postHandle(WebOutput output) {
|
||||
return Mono.just(output.transform(builder ->
|
||||
builder.responseHeader("X-Custom-Header", "42")));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ public abstract class AbstractWebGraphQLService implements WebGraphQLService {
|
||||
public final Mono<WebOutput> execute(WebInput input) {
|
||||
return preHandle(input)
|
||||
.flatMap(executionInput -> Mono.fromFuture(executeInternal(executionInput)))
|
||||
.flatMap(executionResult -> postHandle(new WebOutput(input, executionResult, null)));
|
||||
.flatMap(executionResult -> postHandle(new WebOutput(input, executionResult)));
|
||||
}
|
||||
|
||||
private Mono<ExecutionInput> preHandle(WebInput input) {
|
||||
|
||||
@@ -21,17 +21,17 @@ import reactor.core.publisher.Mono;
|
||||
/**
|
||||
* Contract to execute a GraphQL request.
|
||||
*
|
||||
* @param <I> the GraphQL query container along with any additional context
|
||||
* depending on the environment in which the request is handled
|
||||
* @param <O> the result of query execution and additional environment output
|
||||
* @param <IN> container for the GraphQL query along additional transport
|
||||
* related context such as the HTTP url or headers for web.
|
||||
* @param <OUT> the query execution result
|
||||
*/
|
||||
public interface GraphQLService<I extends RequestInput, O extends ExecutionResult> {
|
||||
public interface GraphQLService<IN extends RequestInput, OUT extends ExecutionResult> {
|
||||
|
||||
/**
|
||||
* Perform the request and return the result.
|
||||
* @param input the GraphQL query container
|
||||
* @return the execution result
|
||||
*/
|
||||
Mono<O> execute(I input);
|
||||
Mono<OUT> execute(IN input);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2020 the original author or authors.
|
||||
* 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.
|
||||
@@ -30,8 +30,9 @@ import org.springframework.util.Assert;
|
||||
|
||||
|
||||
/**
|
||||
* {@link ExecutionResult} that wraps another in order to provide a convenient
|
||||
* way to {@link #transform(Consumer) transform} it.
|
||||
* Decorate an {@link ExecutionResult}, provide a way to
|
||||
* {@link #transform(Consumer) transform} it, and collect input for custom
|
||||
* HTTP response headers for GraphQL over HTTP requests.
|
||||
*/
|
||||
public class WebOutput implements ExecutionResult {
|
||||
|
||||
@@ -40,18 +41,22 @@ public class WebOutput implements ExecutionResult {
|
||||
private final ExecutionResult executionResult;
|
||||
|
||||
@Nullable
|
||||
private final HttpHeaders headers;
|
||||
private final HttpHeaders responseHeaders;
|
||||
|
||||
|
||||
/**
|
||||
* Create an instance that wraps the given {@link ExecutionResult}.
|
||||
*/
|
||||
public WebOutput(WebInput input, ExecutionResult executionResult, @Nullable HttpHeaders headers) {
|
||||
public WebOutput(WebInput input, ExecutionResult executionResult) {
|
||||
this(input, executionResult, null);
|
||||
}
|
||||
|
||||
private WebOutput(WebInput input, ExecutionResult executionResult, @Nullable HttpHeaders responseHeaders) {
|
||||
Assert.notNull(input, "WebInput is required.");
|
||||
Assert.notNull(executionResult, "ExecutionResult is required.");
|
||||
this.input = input;
|
||||
this.executionResult = executionResult;
|
||||
this.headers = headers;
|
||||
this.responseHeaders = responseHeaders;
|
||||
}
|
||||
|
||||
|
||||
@@ -88,11 +93,15 @@ public class WebOutput implements ExecutionResult {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return headers to be added to the HTTP response.
|
||||
* Return a read-only view of any custom headers to be added to the HTTP
|
||||
* response, or {@code null} until {@link #transform(Consumer)} is used to
|
||||
* add such headers.
|
||||
* @see #transform(Consumer)
|
||||
* @see Builder#responseHeader(String, String...)
|
||||
*/
|
||||
@Nullable
|
||||
public HttpHeaders getHeaders() {
|
||||
return this.headers;
|
||||
public HttpHeaders getResponseHeaders() {
|
||||
return (this.responseHeaders != null ? HttpHeaders.readOnlyHttpHeaders(this.responseHeaders) : null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -106,6 +115,9 @@ public class WebOutput implements ExecutionResult {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Builder to transform a {@link WebOutput}.
|
||||
*/
|
||||
public static class Builder {
|
||||
|
||||
private final WebInput input;
|
||||
@@ -127,12 +139,13 @@ public class WebOutput implements ExecutionResult {
|
||||
this.data = output.getData();
|
||||
this.errors = output.getErrors();
|
||||
this.extensions = output.getExtensions();
|
||||
this.headers = output.getHeaders();
|
||||
this.headers = output.responseHeaders;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the execution {@link ExecutionResult#getData() data}.
|
||||
* Set the {@link ExecutionResult#getData() data} of the GraphQL
|
||||
* execution result.
|
||||
*/
|
||||
public Builder data(@Nullable Object data) {
|
||||
this.data = data;
|
||||
@@ -140,7 +153,8 @@ public class WebOutput implements ExecutionResult {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the execution {@link ExecutionResult#getErrors() errors}.
|
||||
* Set the {@link ExecutionResult#getErrors() errors} of the GraphQL
|
||||
* execution result.
|
||||
*/
|
||||
public Builder errors(@Nullable List<GraphQLError> errors) {
|
||||
this.errors = (errors != null ? errors : Collections.emptyList());
|
||||
@@ -148,14 +162,22 @@ public class WebOutput implements ExecutionResult {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the execution {@link ExecutionResult#getExtensions() extensions}.
|
||||
* Set the {@link ExecutionResult#getExtensions() extensions} of the
|
||||
* GraphQL execution result.
|
||||
*/
|
||||
public Builder extensions(@Nullable Map<Object, Object> extensions) {
|
||||
this.extensions = extensions;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder header(String name, String... values) {
|
||||
/**
|
||||
* Add a custom header to be set on the HTTP response.
|
||||
*
|
||||
* <p><strong>Note:</strong> This can be used for GraphQL over HTTP query
|
||||
* requests but has no impact for queries over a WebSocket session where
|
||||
* the initial handshake request completes before queries begin.
|
||||
*/
|
||||
public Builder responseHeader(String name, String... values) {
|
||||
initHeaders();
|
||||
for (String value : values) {
|
||||
this.headers.add(name, value);
|
||||
@@ -163,7 +185,14 @@ public class WebOutput implements ExecutionResult {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder headers(Consumer<HttpHeaders> consumer) {
|
||||
/**
|
||||
* Consume and update the headers to be set on the HTTP response.
|
||||
*
|
||||
* <p><strong>Note:</strong> This can be used for GraphQL over HTTP query
|
||||
* requests but has no impact for queries over a WebSocket session where
|
||||
* the initial handshake request completes before queries begin.
|
||||
*/
|
||||
public Builder responseHeaders(Consumer<HttpHeaders> consumer) {
|
||||
initHeaders();
|
||||
consumer.accept(this.headers);
|
||||
return this;
|
||||
|
||||
@@ -71,8 +71,8 @@ public class GraphQLHttpHandler {
|
||||
logger.debug("Execution complete");
|
||||
}
|
||||
ServerResponse.BodyBuilder builder = ServerResponse.ok();
|
||||
if (output.getHeaders() != null) {
|
||||
builder.headers(headers -> headers.putAll(output.getHeaders()));
|
||||
if (output.getResponseHeaders() != null) {
|
||||
builder.headers(headers -> headers.putAll(output.getResponseHeaders()));
|
||||
}
|
||||
return builder.bodyValue(spec);
|
||||
});
|
||||
|
||||
@@ -75,8 +75,8 @@ public class GraphQLHttpHandler {
|
||||
logger.debug("Execution complete");
|
||||
}
|
||||
ServerResponse.BodyBuilder builder = ServerResponse.ok();
|
||||
if (output.getHeaders() != null) {
|
||||
builder.headers(headers -> headers.putAll(output.getHeaders()));
|
||||
if (output.getResponseHeaders() != null) {
|
||||
builder.headers(headers -> headers.putAll(output.getResponseHeaders()));
|
||||
}
|
||||
return builder.body(output.toSpecification());
|
||||
});
|
||||
|
||||
@@ -71,7 +71,7 @@ public class DefaultWebGraphQLServiceTests {
|
||||
|
||||
assertThat(sb.toString()).isEqualTo(":pre1:pre2:pre3:post3:post2:post1");
|
||||
assertThat(webOutput.isDataPresent()).isTrue();
|
||||
assertThat(webOutput.getHeaders().get("MyHeader")).containsExactly("MyValue3", "MyValue2", "MyValue1");
|
||||
assertThat(webOutput.getResponseHeaders().get("MyHeader")).containsExactly("MyValue3", "MyValue2", "MyValue1");
|
||||
}
|
||||
|
||||
|
||||
@@ -107,12 +107,11 @@ public class DefaultWebGraphQLServiceTests {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<WebOutput> postHandle(WebOutput webOutput) {
|
||||
public Mono<WebOutput> postHandle(WebOutput output) {
|
||||
this.output.append(":post").append(this.index);
|
||||
return Mono.delay(Duration.ofMillis(50))
|
||||
.map(aLong -> webOutput.transform(builder -> {
|
||||
builder.header("myHeader", "MyValue" + this.index);
|
||||
}));
|
||||
.map(aLong -> output.transform(builder ->
|
||||
builder.responseHeader("myHeader", "MyValue" + this.index)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user