Merge branch '2.1.x'

This commit is contained in:
Spencer Gibb
2019-12-18 22:39:16 -05:00
6 changed files with 849 additions and 2 deletions

View File

@@ -16,6 +16,7 @@
package org.springframework.cloud.gateway.filter.factory.rewrite;
import java.util.function.Function;
import java.util.function.Supplier;
import org.reactivestreams.Publisher;
@@ -32,7 +33,7 @@ import org.springframework.web.server.ServerWebExchange;
/**
* Implementation of {@link ClientHttpRequest} that saves body as a field.
*/
class CachedBodyOutputMessage implements ReactiveHttpOutputMessage {
public class CachedBodyOutputMessage implements ReactiveHttpOutputMessage {
private final DataBufferFactory bufferFactory;
@@ -41,7 +42,7 @@ class CachedBodyOutputMessage implements ReactiveHttpOutputMessage {
private Flux<DataBuffer> body = Flux.error(new IllegalStateException(
"The body is not set. " + "Did handling complete with success?"));
CachedBodyOutputMessage(ServerWebExchange exchange, HttpHeaders httpHeaders) {
public CachedBodyOutputMessage(ServerWebExchange exchange, HttpHeaders httpHeaders) {
this.bufferFactory = exchange.getResponse().bufferFactory();
this.httpHeaders = httpHeaders;
}
@@ -74,6 +75,11 @@ class CachedBodyOutputMessage implements ReactiveHttpOutputMessage {
return this.body;
}
@Deprecated
public void setWriteHandler(Function<Flux<DataBuffer>, Mono<Void>> writeHandler) {
}
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
this.body = Flux.from(body);
return Mono.empty();

View File

@@ -0,0 +1,113 @@
/*
* Copyright 2013-2019 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.cloud.gateway.filter.factory.rewrite;
import java.util.function.Supplier;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseCookie;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.MultiValueMap;
/**
* This class is BETA and may be subject to change in a future release. Response who's job
* it is to gather the Publisher&lt;DataBuffer&gt; from the writeWith message during a
* call to HttpMessageWriter.write. Also gathers any headers set there.
*/
@Deprecated
public class HttpMessageWriterResponse implements ServerHttpResponse {
private final HttpHeaders headers = new HttpHeaders();
private final DataBufferFactory dataBufferFactory;
private Publisher<? extends DataBuffer> body;
public HttpMessageWriterResponse(DataBufferFactory dataBufferFactory) {
this.dataBufferFactory = dataBufferFactory;
}
@Override
public HttpHeaders getHeaders() {
return this.headers;
}
@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
this.body = body;
return Mono.empty();
}
@Override
public Mono<Void> writeAndFlushWith(
Publisher<? extends Publisher<? extends DataBuffer>> body) {
// TODO: is this kosher?
return writeWith(Flux.from(body).flatMapSequential(p -> p));
}
public Publisher<? extends DataBuffer> getBody() {
return body;
}
@Override
public boolean setStatusCode(HttpStatus status) {
return false;
}
@Override
public HttpStatus getStatusCode() {
return null;
}
@Override
public MultiValueMap<String, ResponseCookie> getCookies() {
return null;
}
@Override
public void addCookie(ResponseCookie cookie) {
}
@Override
public DataBufferFactory bufferFactory() {
return this.dataBufferFactory;
}
@Override
public void beforeCommit(Supplier<? extends Mono<Void>> action) {
}
@Override
public boolean isCommitted() {
return false;
}
@Override
public Mono<Void> setComplete() {
return null;
}
}

View File

@@ -31,9 +31,13 @@ import org.springframework.cloud.gateway.support.BodyInserterContext;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseCookie;
import org.springframework.http.client.reactive.ClientHttpResponse;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.util.MultiValueMap;
import org.springframework.lang.Nullable;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
@@ -265,4 +269,50 @@ public class ModifyResponseBodyGatewayFilterFactory extends
}
@Deprecated
@SuppressWarnings("unchecked")
public class ResponseAdapter implements ClientHttpResponse {
private final Flux<DataBuffer> flux;
private final HttpHeaders headers;
public ResponseAdapter(Publisher<? extends DataBuffer> body,
HttpHeaders headers) {
this.headers = headers;
if (body instanceof Flux) {
flux = (Flux) body;
}
else {
flux = ((Mono) body).flux();
}
}
@Override
public Flux<DataBuffer> getBody() {
return flux;
}
@Override
public HttpHeaders getHeaders() {
return headers;
}
@Override
public HttpStatus getStatusCode() {
return null;
}
@Override
public int getRawStatusCode() {
return 0;
}
@Override
public MultiValueMap<String, ResponseCookie> getCookies() {
return null;
}
}
}

View File

@@ -0,0 +1,246 @@
/*
* Copyright 2013-2019 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.cloud.gateway.support;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.OptionalLong;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseCookie;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.reactive.ClientHttpResponse;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.BodyExtractor;
import org.springframework.web.reactive.function.BodyExtractors;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.ExchangeStrategies;
/**
* Default implementation of {@link ClientResponse}.
*
* @author Arjen Poutsma
* @author Brian Clozel
* @since 5.0
* @deprecated Will be removed in future release.
*/
@Deprecated
public class DefaultClientResponse implements ClientResponse {
private final ClientHttpResponse response;
private final Headers headers;
private final ExchangeStrategies strategies;
public DefaultClientResponse(ClientHttpResponse response,
ExchangeStrategies strategies) {
this.response = response;
this.strategies = strategies;
this.headers = new DefaultHeaders();
}
@Override
public ExchangeStrategies strategies() {
return this.strategies;
}
@Override
public HttpStatus statusCode() {
return this.response.getStatusCode();
}
@Override
public int rawStatusCode() {
return this.response.getRawStatusCode();
}
@Override
public Headers headers() {
return this.headers;
}
@Override
public MultiValueMap<String, ResponseCookie> cookies() {
return this.response.getCookies();
}
@Override
public <T> T body(BodyExtractor<T, ? super ClientHttpResponse> extractor) {
return extractor.extract(this.response, new BodyExtractor.Context() {
@Override
public List<HttpMessageReader<?>> messageReaders() {
return strategies.messageReaders();
}
@Override
public Optional<ServerHttpResponse> serverResponse() {
return Optional.empty();
}
@Override
public Map<String, Object> hints() {
return Collections.emptyMap();
}
});
}
@Override
public <T> Mono<T> bodyToMono(Class<? extends T> elementClass) {
if (Void.class.isAssignableFrom(elementClass)) {
return consumeAndCancel();
}
else {
return body(BodyExtractors.toMono(elementClass));
}
}
@SuppressWarnings("unchecked")
private <T> Mono<T> consumeAndCancel() {
return (Mono<T>) this.response.getBody().map(buffer -> {
DataBufferUtils.release(buffer);
throw new ReadCancellationException();
}).onErrorResume(ReadCancellationException.class, ex -> Mono.empty()).then();
}
@Override
public <T> Mono<T> bodyToMono(ParameterizedTypeReference<T> typeReference) {
if (Void.class.isAssignableFrom(typeReference.getType().getClass())) {
return consumeAndCancel();
}
else {
return body(BodyExtractors.toMono(typeReference));
}
}
@Override
public <T> Flux<T> bodyToFlux(Class<? extends T> elementClass) {
if (Void.class.isAssignableFrom(elementClass)) {
return Flux.from(consumeAndCancel());
}
else {
return body(BodyExtractors.toFlux(elementClass));
}
}
@Override
public <T> Flux<T> bodyToFlux(ParameterizedTypeReference<T> typeReference) {
if (Void.class.isAssignableFrom(typeReference.getType().getClass())) {
return Flux.from(consumeAndCancel());
}
else {
return body(BodyExtractors.toFlux(typeReference));
}
}
@Override
public <T> Mono<ResponseEntity<T>> toEntity(Class<T> bodyType) {
if (Void.class.isAssignableFrom(bodyType)) {
return toEntityInternal(consumeAndCancel());
}
else {
return toEntityInternal(bodyToMono(bodyType));
}
}
@Override
public <T> Mono<ResponseEntity<T>> toEntity(
ParameterizedTypeReference<T> typeReference) {
if (Void.class.isAssignableFrom(typeReference.getType().getClass())) {
return toEntityInternal(consumeAndCancel());
}
else {
return toEntityInternal(bodyToMono(typeReference));
}
}
private <T> Mono<ResponseEntity<T>> toEntityInternal(Mono<T> bodyMono) {
HttpHeaders headers = headers().asHttpHeaders();
HttpStatus statusCode = statusCode();
return bodyMono.map(body -> new ResponseEntity<>(body, headers, statusCode))
.switchIfEmpty(Mono.defer(
() -> Mono.just(new ResponseEntity<>(headers, statusCode))));
}
@Override
public <T> Mono<ResponseEntity<List<T>>> toEntityList(Class<T> responseType) {
return toEntityListInternal(bodyToFlux(responseType));
}
@Override
public <T> Mono<ResponseEntity<List<T>>> toEntityList(
ParameterizedTypeReference<T> typeReference) {
return toEntityListInternal(bodyToFlux(typeReference));
}
private <T> Mono<ResponseEntity<List<T>>> toEntityListInternal(Flux<T> bodyFlux) {
HttpHeaders headers = headers().asHttpHeaders();
HttpStatus statusCode = statusCode();
return bodyFlux.collectList()
.map(body -> new ResponseEntity<>(body, headers, statusCode));
}
@SuppressWarnings("serial")
private static class ReadCancellationException extends RuntimeException {
}
private class DefaultHeaders implements Headers {
private HttpHeaders delegate() {
return response.getHeaders();
}
@Override
public OptionalLong contentLength() {
return toOptionalLong(delegate().getContentLength());
}
@Override
public Optional<MediaType> contentType() {
return Optional.ofNullable(delegate().getContentType());
}
@Override
public List<String> header(String headerName) {
List<String> headerValues = delegate().get(headerName);
return (headerValues != null ? headerValues : Collections.emptyList());
}
@Override
public HttpHeaders asHttpHeaders() {
return HttpHeaders.readOnlyHttpHeaders(delegate());
}
private OptionalLong toOptionalLong(long value) {
return (value != -1 ? OptionalLong.of(value) : OptionalLong.empty());
}
}
}

View File

@@ -0,0 +1,311 @@
/*
* Copyright 2013-2019 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.cloud.gateway.support;
import java.net.InetSocketAddress;
import java.net.URI;
import java.nio.charset.Charset;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.OptionalLong;
import java.util.function.Function;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRange;
import org.springframework.http.HttpRequest;
import org.springframework.http.MediaType;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.codec.multipart.Part;
import org.springframework.http.server.PathContainer;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.BodyExtractor;
import org.springframework.web.reactive.function.BodyExtractors;
import org.springframework.web.reactive.function.UnsupportedMediaTypeException;
import org.springframework.web.reactive.function.server.HandlerStrategies;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.UnsupportedMediaTypeStatusException;
import org.springframework.web.server.WebSession;
import org.springframework.web.util.UriBuilder;
import org.springframework.web.util.UriComponentsBuilder;
/**
* {@code ServerRequest} implementation based on a {@link ServerWebExchange}.
*
* @author Arjen Poutsma
* @since 5.0
* @deprecated Will be removed in a future release.
*/
@Deprecated
public class DefaultServerRequest implements ServerRequest {
private static final Function<UnsupportedMediaTypeException, UnsupportedMediaTypeStatusException> ERROR_MAPPER = ex -> (ex
.getContentType() != null
? new UnsupportedMediaTypeStatusException(ex.getContentType(),
ex.getSupportedMediaTypes())
: new UnsupportedMediaTypeStatusException(ex.getMessage()));
private final ServerWebExchange exchange;
private final Headers headers;
private final List<HttpMessageReader<?>> messageReaders;
public DefaultServerRequest(ServerWebExchange exchange) {
this(exchange, HandlerStrategies.withDefaults().messageReaders());
}
public DefaultServerRequest(ServerWebExchange exchange,
List<HttpMessageReader<?>> messageReaders) {
this.exchange = exchange;
this.messageReaders = Collections
.unmodifiableList(new ArrayList<>(messageReaders));
this.headers = new DefaultHeaders();
}
@Override
public String methodName() {
return request().getMethodValue();
}
@Override
public URI uri() {
return request().getURI();
}
@Override
public UriBuilder uriBuilder() {
return UriComponentsBuilder.fromHttpRequest(new ServerRequestAdapter());
}
@Override
public PathContainer pathContainer() {
return request().getPath();
}
@Override
public Headers headers() {
return this.headers;
}
@Override
public MultiValueMap<String, HttpCookie> cookies() {
return request().getCookies();
}
@Override
public <T> T body(BodyExtractor<T, ? super ServerHttpRequest> extractor) {
return body(extractor, Collections.emptyMap());
}
@Override
public Optional<InetSocketAddress> remoteAddress() {
return Optional.of(request().getRemoteAddress());
}
@Override
public List<HttpMessageReader<?>> messageReaders() {
return this.messageReaders;
}
@Override
public <T> T body(BodyExtractor<T, ? super ServerHttpRequest> extractor,
Map<String, Object> hints) {
return extractor.extract(request(), new BodyExtractor.Context() {
@Override
public List<HttpMessageReader<?>> messageReaders() {
return messageReaders;
}
@Override
public Optional<ServerHttpResponse> serverResponse() {
return Optional.of(exchange().getResponse());
}
@Override
public Map<String, Object> hints() {
return hints;
}
});
}
@Override
public <T> Mono<T> bodyToMono(Class<? extends T> elementClass) {
Mono<T> mono = body(BodyExtractors.toMono(elementClass));
return mono.onErrorMap(UnsupportedMediaTypeException.class, ERROR_MAPPER);
}
@Override
public <T> Mono<T> bodyToMono(ParameterizedTypeReference<T> typeReference) {
Mono<T> mono = body(BodyExtractors.toMono(typeReference));
return mono.onErrorMap(UnsupportedMediaTypeException.class, ERROR_MAPPER);
}
@Override
public <T> Flux<T> bodyToFlux(Class<? extends T> elementClass) {
Flux<T> flux = body(BodyExtractors.toFlux(elementClass));
return flux.onErrorMap(UnsupportedMediaTypeException.class, ERROR_MAPPER);
}
@Override
public <T> Flux<T> bodyToFlux(ParameterizedTypeReference<T> typeReference) {
Flux<T> flux = body(BodyExtractors.toFlux(typeReference));
return flux.onErrorMap(UnsupportedMediaTypeException.class, ERROR_MAPPER);
}
@Override
public Map<String, Object> attributes() {
return this.exchange.getAttributes();
}
@Override
public MultiValueMap<String, String> queryParams() {
return request().getQueryParams();
}
@Override
public Map<String, String> pathVariables() {
return this.exchange.getAttributeOrDefault(
RouterFunctions.URI_TEMPLATE_VARIABLES_ATTRIBUTE, Collections.emptyMap());
}
@Override
public Mono<WebSession> session() {
return this.exchange.getSession();
}
@Override
public Mono<? extends Principal> principal() {
return this.exchange.getPrincipal();
}
@Override
public Mono<MultiValueMap<String, String>> formData() {
return this.exchange.getFormData();
}
@Override
public Mono<MultiValueMap<String, Part>> multipartData() {
return this.exchange.getMultipartData();
}
private ServerHttpRequest request() {
return this.exchange.getRequest();
}
public ServerWebExchange exchange() {
return this.exchange;
}
@Override
public String toString() {
return String.format("%s %s", method(), path());
}
private class DefaultHeaders implements Headers {
private HttpHeaders delegate() {
return request().getHeaders();
}
@Override
public List<MediaType> accept() {
return delegate().getAccept();
}
@Override
public List<Charset> acceptCharset() {
return delegate().getAcceptCharset();
}
@Override
public List<Locale.LanguageRange> acceptLanguage() {
return delegate().getAcceptLanguage();
}
@Override
public OptionalLong contentLength() {
long value = delegate().getContentLength();
return (value != -1 ? OptionalLong.of(value) : OptionalLong.empty());
}
@Override
public Optional<MediaType> contentType() {
return Optional.ofNullable(delegate().getContentType());
}
@Override
public InetSocketAddress host() {
return delegate().getHost();
}
@Override
public List<HttpRange> range() {
return delegate().getRange();
}
@Override
public List<String> header(String headerName) {
List<String> headerValues = delegate().get(headerName);
return (headerValues != null ? headerValues : Collections.emptyList());
}
@Override
public HttpHeaders asHttpHeaders() {
return HttpHeaders.readOnlyHttpHeaders(delegate());
}
@Override
public String toString() {
return delegate().toString();
}
}
private final class ServerRequestAdapter implements HttpRequest {
@Override
public String getMethodValue() {
return methodName();
}
@Override
public URI getURI() {
return uri();
}
@Override
public HttpHeaders getHeaders() {
return request().getHeaders();
}
}
}

View File

@@ -0,0 +1,121 @@
/*
* Copyright 2013-2019 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.cloud.gateway.support;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import reactor.core.publisher.Mono;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseCookie;
import org.springframework.http.codec.HttpMessageWriter;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.Assert;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.server.HandlerStrategies;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.web.server.ServerWebExchange;
@Deprecated
public class DefaultServerResponse<T> implements ServerResponse {
private final ServerWebExchange exchange;
private final BodyInserter<T, ? super ServerHttpResponse> inserter;
private final Map<String, Object> hints;
public DefaultServerResponse(ServerWebExchange exchange,
BodyInserter<T, ? super ServerHttpResponse> body, Map<String, Object> hints) {
this.exchange = exchange;
Assert.notNull(exchange, "ServerWebExchange must not be null");
Assert.notNull(body, "BodyInserter must not be null");
this.inserter = body;
this.hints = hints;
}
private ServerHttpResponse response() {
return exchange.getResponse();
}
@Override
public final HttpStatus statusCode() {
// TODO: non standard status code
return HttpStatus.valueOf(response().getStatusCode().value());
}
@Override
public final HttpHeaders headers() {
return response().getHeaders();
}
@Override
public MultiValueMap<String, ResponseCookie> cookies() {
return response().getCookies();
}
@Override
public final Mono<Void> writeTo(ServerWebExchange exchange, Context context) {
return this.inserter.insert(exchange.getResponse(), new BodyInserter.Context() {
@Override
public List<HttpMessageWriter<?>> messageWriters() {
return context.messageWriters();
}
@Override
public Optional<ServerHttpRequest> serverRequest() {
return Optional.of(exchange.getRequest());
}
@Override
public Map<String, Object> hints() {
return hints;
}
});
}
public static class HandlerStrategiesResponseContext
implements ServerResponse.Context {
private HandlerStrategies strategies = HandlerStrategies.withDefaults();
public HandlerStrategiesResponseContext() {
}
public HandlerStrategiesResponseContext(HandlerStrategies strategies) {
this.strategies = strategies;
}
@Override
public List<HttpMessageWriter<?>> messageWriters() {
return this.strategies.messageWriters();
}
@Override
public List<ViewResolver> viewResolvers() {
return this.strategies.viewResolvers();
}
}
}