From 8c655cb25d92f93e5aad2ee62773e1c0e813dd03 Mon Sep 17 00:00:00 2001 From: rstoyanchev Date: Mon, 10 Jan 2022 10:08:52 +0000 Subject: [PATCH] Simplify access to response headers in WebOutput The response headers can now be changed directly without the need to go through the builder. See gh-229 --- .../graphql/web/WebOutput.java | 74 ++++--------------- ...ConsumeOneAndNeverCompleteInterceptor.java | 9 ++- .../graphql/web/WebInterceptorTests.java | 9 ++- 3 files changed, 25 insertions(+), 67 deletions(-) diff --git a/spring-graphql/src/main/java/org/springframework/graphql/web/WebOutput.java b/spring-graphql/src/main/java/org/springframework/graphql/web/WebOutput.java index 83068492..26059303 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/web/WebOutput.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/web/WebOutput.java @@ -29,6 +29,7 @@ import graphql.GraphQLError; import org.springframework.graphql.RequestOutput; import org.springframework.http.HttpHeaders; import org.springframework.lang.Nullable; +import org.springframework.util.Assert; /** * Decorate an {@link ExecutionResult}, provide a way to {@link #transform(Consumer) @@ -40,37 +41,35 @@ import org.springframework.lang.Nullable; */ public class WebOutput extends RequestOutput { - @Nullable private final HttpHeaders responseHeaders; /** - * Create an instance that wraps the given {@link ExecutionResult}. + * Create an instance from the given {@link RequestOutput}. * @param requestOutput the output from an executed request */ public WebOutput(RequestOutput requestOutput) { - this(requestOutput.getExecutionInput(), requestOutput, null); + this(requestOutput.getExecutionInput(), requestOutput, new HttpHeaders()); } - private WebOutput( - ExecutionInput executionInput, ExecutionResult executionResult, - @Nullable HttpHeaders responseHeaders) { + private WebOutput(ExecutionInput executionInput, ExecutionResult executionResult, + HttpHeaders responseHeaders) { super(executionInput, executionResult); + Assert.notNull(responseHeaders, "HttpHeaders is required"); this.responseHeaders = responseHeaders; } /** - * 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. - * @return the read-only HTTP response headers - * @see #transform(Consumer) - * @see Builder#responseHeader(String, String...) + * Return the headers to be added to the HTTP response. + *

By default, this is empty. + *

Note: This is for use with GraphQL over HTTP requests + * but not for GraphQL over WebSocket where the initial handshake HTTP + * request completes before queries begin. */ - @Nullable public HttpHeaders getResponseHeaders() { - return (this.responseHeaders != null) ? HttpHeaders.readOnlyHttpHeaders(this.responseHeaders) : null; + return this.responseHeaders; } /** @@ -91,7 +90,7 @@ public class WebOutput extends RequestOutput { */ public static final class Builder { - private final ExecutionInput executionInput; + private final WebOutput webOutput; @Nullable private Object data; @@ -101,15 +100,11 @@ public class WebOutput extends RequestOutput { @Nullable private Map extensions; - @Nullable - private HttpHeaders headers; - private Builder(WebOutput output) { - this.executionInput = output.getExecutionInput(); + this.webOutput = output; this.data = output.getData(); this.errors = output.getErrors(); this.extensions = output.getExtensions(); - this.headers = output.responseHeaders; } /** @@ -144,48 +139,9 @@ public class WebOutput extends RequestOutput { return this; } - /** - * Add a custom header to be set on the HTTP response. - * - *

- * Note: This can be used for GraphQL over HTTP requests but has - * no impact for queries over a WebSocket session where the initial handshake - * request completes before queries begin. - * @param name the HTTP header name - * @param values the HTTP header values - * @return the current builder - */ - public Builder responseHeader(String name, String... values) { - initHeaders(); - for (String value : values) { - this.headers.add(name, value); - } - return this; - } - - /** - * Consume and update the headers to be set on the HTTP response. - * - *

- * Note: This can be used for GraphQL over HTTP requests but has - * no impact for queries over a WebSocket session where the initial handshake - * request completes before queries begin. - * @param consumer callback that updates the HTTP headers - * @return the current builder - */ - public Builder responseHeaders(Consumer consumer) { - initHeaders(); - consumer.accept(this.headers); - return this; - } - - private void initHeaders() { - this.headers = (this.headers != null) ? this.headers : new HttpHeaders(); - } - public WebOutput build() { ExecutionResult result = new ExecutionResultImpl(this.data, this.errors, this.extensions); - return new WebOutput(this.executionInput, result, this.headers); + return new WebOutput(this.webOutput.getExecutionInput(), result, this.webOutput.getResponseHeaders()); } } diff --git a/spring-graphql/src/test/java/org/springframework/graphql/web/ConsumeOneAndNeverCompleteInterceptor.java b/spring-graphql/src/test/java/org/springframework/graphql/web/ConsumeOneAndNeverCompleteInterceptor.java index 5f99b915..af0e04d9 100644 --- a/spring-graphql/src/test/java/org/springframework/graphql/web/ConsumeOneAndNeverCompleteInterceptor.java +++ b/spring-graphql/src/test/java/org/springframework/graphql/web/ConsumeOneAndNeverCompleteInterceptor.java @@ -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. @@ -25,9 +25,10 @@ public class ConsumeOneAndNeverCompleteInterceptor implements WebInterceptor { @Override public Mono intercept(WebInput webInput, WebInterceptorChain chain) { return chain.next(webInput).map((output) -> output.transform(builder -> { - if (output.getData() instanceof Publisher) { - Flux flux = Flux.from((Publisher) output.getData()).take(1).concatWith(Flux.never()); - builder.data(flux); + Object originalData = output.getData(); + if (originalData instanceof Publisher) { + Flux updatedData = Flux.from((Publisher) originalData).take(1).concatWith(Flux.never()); + builder.data(updatedData); } })); } diff --git a/spring-graphql/src/test/java/org/springframework/graphql/web/WebInterceptorTests.java b/spring-graphql/src/test/java/org/springframework/graphql/web/WebInterceptorTests.java index 61a4f65b..e048c93a 100644 --- a/spring-graphql/src/test/java/org/springframework/graphql/web/WebInterceptorTests.java +++ b/spring-graphql/src/test/java/org/springframework/graphql/web/WebInterceptorTests.java @@ -60,11 +60,12 @@ public class WebInterceptorTests { @Test void responseHeader() { - Function headerFunction = (output) -> - output.transform((builder) -> builder.responseHeader("testHeader", "testValue")); - WebGraphQlHandler handler = WebGraphQlHandler.builder(this::emptyExecutionResult) - .interceptor((input, next) -> next.next(input).map(headerFunction)) + .interceptor((input, next) -> next.next(input) + .doOnNext(output -> { + HttpHeaders httpHeaders = output.getResponseHeaders(); + httpHeaders.add("testHeader", "testValue"); + })) .build(); HttpHeaders headers = handler.handleRequest(webInput).block().getResponseHeaders();