Update some deps and remove gateway (moved to separate project)
This commit is contained in:
7
pom.xml
7
pom.xml
@@ -10,16 +10,16 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>org.springframework.cloud</groupId>
|
<groupId>org.springframework.cloud</groupId>
|
||||||
<artifactId>spring-cloud-build</artifactId>
|
<artifactId>spring-cloud-build</artifactId>
|
||||||
<version>1.3.0.BUILD-SNAPSHOT</version>
|
<version>1.3.3.RELEASE</version>
|
||||||
<relativePath />
|
<relativePath />
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<java.version>1.8</java.version>
|
<java.version>1.8</java.version>
|
||||||
<reactor-bom.version>Aluminium-BUILD-SNAPSHOT</reactor-bom.version>
|
<reactor-bom.version>Aluminium-SR3</reactor-bom.version>
|
||||||
<spring-cloud-stream.version>Chelsea.SR1</spring-cloud-stream.version>
|
<spring-cloud-stream.version>Chelsea.SR1</spring-cloud-stream.version>
|
||||||
<wrapper.version>1.0.5.RELEASE</wrapper.version>
|
<wrapper.version>1.0.5.RELEASE</wrapper.version>
|
||||||
<spring-boot.version>1.5.2.RELEASE</spring-boot.version>
|
<spring-boot.version>1.5.4.RELEASE</spring-boot.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencyManagement>
|
<dependencyManagement>
|
||||||
@@ -52,7 +52,6 @@
|
|||||||
<module>spring-cloud-function-compiler</module>
|
<module>spring-cloud-function-compiler</module>
|
||||||
<module>spring-cloud-function-core</module>
|
<module>spring-cloud-function-core</module>
|
||||||
<module>spring-cloud-function-context</module>
|
<module>spring-cloud-function-context</module>
|
||||||
<module>spring-cloud-function-gateway</module>
|
|
||||||
<module>spring-cloud-function-stream</module>
|
<module>spring-cloud-function-stream</module>
|
||||||
<module>spring-cloud-function-task</module>
|
<module>spring-cloud-function-task</module>
|
||||||
<module>spring-cloud-function-web</module>
|
<module>spring-cloud-function-web</module>
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
|
|
||||||
<artifactId>spring-cloud-function-gateway</artifactId>
|
|
||||||
<packaging>jar</packaging>
|
|
||||||
<name>spring-cloud-function-gateway</name>
|
|
||||||
<description>Spring Cloud Function Gateway Support</description>
|
|
||||||
|
|
||||||
<parent>
|
|
||||||
<groupId>org.springframework.cloud</groupId>
|
|
||||||
<artifactId>spring-cloud-function-parent</artifactId>
|
|
||||||
<version>1.0.0.BUILD-SNAPSHOT</version>
|
|
||||||
</parent>
|
|
||||||
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-web</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-test</artifactId>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
|
||||||
<optional>true</optional>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
</project>
|
|
||||||
@@ -1,644 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016-2017 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
|
|
||||||
*
|
|
||||||
* http://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.function.gateway;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.lang.reflect.Type;
|
|
||||||
import java.lang.reflect.TypeVariable;
|
|
||||||
import java.lang.reflect.WildcardType;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.net.URISyntaxException;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Enumeration;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.LinkedHashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.Vector;
|
|
||||||
import java.util.function.Function;
|
|
||||||
|
|
||||||
import javax.servlet.ReadListener;
|
|
||||||
import javax.servlet.ServletInputStream;
|
|
||||||
import javax.servlet.ServletOutputStream;
|
|
||||||
import javax.servlet.WriteListener;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletRequestWrapper;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
import javax.servlet.http.HttpServletResponseWrapper;
|
|
||||||
|
|
||||||
import org.springframework.core.Conventions;
|
|
||||||
import org.springframework.core.MethodParameter;
|
|
||||||
import org.springframework.core.ParameterizedTypeReference;
|
|
||||||
import org.springframework.http.HttpHeaders;
|
|
||||||
import org.springframework.http.RequestEntity;
|
|
||||||
import org.springframework.http.RequestEntity.BodyBuilder;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.http.converter.HttpMessageConverter;
|
|
||||||
import org.springframework.http.converter.HttpMessageNotWritableException;
|
|
||||||
import org.springframework.util.ClassUtils;
|
|
||||||
import org.springframework.validation.BindingResult;
|
|
||||||
import org.springframework.web.HttpMediaTypeNotAcceptableException;
|
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
|
||||||
import org.springframework.web.bind.annotation.ResponseBody;
|
|
||||||
import org.springframework.web.bind.support.WebDataBinderFactory;
|
|
||||||
import org.springframework.web.client.RequestCallback;
|
|
||||||
import org.springframework.web.client.ResponseExtractor;
|
|
||||||
import org.springframework.web.client.RestTemplate;
|
|
||||||
import org.springframework.web.context.request.NativeWebRequest;
|
|
||||||
import org.springframework.web.context.request.ServletWebRequest;
|
|
||||||
import org.springframework.web.context.request.WebRequest;
|
|
||||||
import org.springframework.web.method.support.ModelAndViewContainer;
|
|
||||||
import org.springframework.web.servlet.HandlerMapping;
|
|
||||||
import org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor;
|
|
||||||
import org.springframework.web.util.AbstractUriTemplateHandler;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A <code>@RequestMapping</code> argument type that can proxy the request to a backend.
|
|
||||||
* Spring will inject one of these into your MVC handler method, and you get return a
|
|
||||||
* <code>ResponseEntity</code> that you get from one of the HTTP methods {@link #get()},
|
|
||||||
* {@link #post()}, {@link #put()}, {@link #patch()}, {@link #delete()} etc. Example:
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* @GetMapping("/proxy/{id}")
|
|
||||||
* public ResponseEntity<?> proxy(@PathVariable Integer id, ProxyExchange<?> proxy)
|
|
||||||
* throws Exception {
|
|
||||||
* return proxy.uri("http://localhost:9000/foos/" + id).get();
|
|
||||||
* }
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* <p>
|
|
||||||
* By default the incoming request body and headers are sent intact to the downstream
|
|
||||||
* service (with the exception of "sensitive" headers). To manipulate the downstream
|
|
||||||
* request there are "builder" style methods in {@link ProxyExchange}, but only the
|
|
||||||
* {@link #uri(String)} is mandatory. You can change the sensitive headers by calling the
|
|
||||||
* {@link #sensitive(String...)} method (Authorization and Cookie are sensitive by
|
|
||||||
* default).
|
|
||||||
* </p>
|
|
||||||
* <p>
|
|
||||||
* The type parameter <code>T</code> in <code>ProxyExchange<T></code> is the type of
|
|
||||||
* the response body, so it comes out in the {@link ResponseEntity} that you return from
|
|
||||||
* your <code>@RequestMapping</code>. If you don't care about the type of the request and
|
|
||||||
* response body (e.g. if it's just a passthru) then use a wildcard, or
|
|
||||||
* <code>byte[]</code> or <code>Object</code>. Use a concrete type if you want to
|
|
||||||
* transform or manipulate the response, or if you want to assert that it is convertible
|
|
||||||
* to the type you declare.
|
|
||||||
* </p>
|
|
||||||
* <p>
|
|
||||||
* To manipulate the response use the overloaded HTTP methods with a <code>Function</code>
|
|
||||||
* argument and pass in code to transform the response. E.g.
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* @PostMapping("/proxy")
|
|
||||||
* public ResponseEntity<Foo> proxy(ProxyExchange<Foo> proxy) throws Exception {
|
|
||||||
* return proxy.uri("http://localhost:9000/foos/") //
|
|
||||||
* .post(response -> ResponseEntity.status(response.getStatusCode()) //
|
|
||||||
* .headers(response.getHeaders()) //
|
|
||||||
* .header("X-Custom", "MyCustomHeader") //
|
|
||||||
* .body(response.getBody()) //
|
|
||||||
* );
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* </p>
|
|
||||||
* <p>
|
|
||||||
* The full machinery of Spring {@link HttpMessageConverter message converters} is applied
|
|
||||||
* to the incoming request and response and also to the backend request. If you need
|
|
||||||
* additional converters then they need to be added upstream in the MVC configuration and
|
|
||||||
* also to the {@link RestTemplate} that is used in the backend calls (see the
|
|
||||||
* {@link ProxyExchange#ProxyExchange(RestTemplate, NativeWebRequest, ModelAndViewContainer, WebDataBinderFactory, Type)
|
|
||||||
* constructor} for details).
|
|
||||||
* </p>
|
|
||||||
* <p>
|
|
||||||
* As well as the HTTP methods for a backend call you can also use
|
|
||||||
* {@link #forward(String)} for a local in-container dispatch.
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @author Dave Syer
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class ProxyExchange<T> {
|
|
||||||
|
|
||||||
public static Set<String> DEFAULT_SENSITIVE = new HashSet<>(
|
|
||||||
Arrays.asList("cookie", "authorization"));
|
|
||||||
|
|
||||||
private URI uri;
|
|
||||||
|
|
||||||
private NestedTemplate rest;
|
|
||||||
|
|
||||||
private Object body;
|
|
||||||
|
|
||||||
private RequestResponseBodyMethodProcessor delegate;
|
|
||||||
|
|
||||||
private NativeWebRequest webRequest;
|
|
||||||
|
|
||||||
private ModelAndViewContainer mavContainer;
|
|
||||||
|
|
||||||
private WebDataBinderFactory binderFactory;
|
|
||||||
|
|
||||||
private Set<String> sensitive;
|
|
||||||
|
|
||||||
private HttpHeaders headers = new HttpHeaders();
|
|
||||||
|
|
||||||
private Type responseType;
|
|
||||||
|
|
||||||
public ProxyExchange(RestTemplate rest, NativeWebRequest webRequest,
|
|
||||||
ModelAndViewContainer mavContainer, WebDataBinderFactory binderFactory,
|
|
||||||
Type type) {
|
|
||||||
this.responseType = type;
|
|
||||||
this.rest = createTemplate(rest);
|
|
||||||
this.webRequest = webRequest;
|
|
||||||
this.mavContainer = mavContainer;
|
|
||||||
this.binderFactory = binderFactory;
|
|
||||||
this.delegate = new RequestResponseBodyMethodProcessor(
|
|
||||||
rest.getMessageConverters());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the body for the downstream request (if using {@link #post()}, {@link #put()}
|
|
||||||
* or {@link #patch()}). The body can be omitted if you just want to pass the incoming
|
|
||||||
* request downstream without changing it. If you want to transform the incoming
|
|
||||||
* request you can declare it as a <code>@RequestBody</code> in your
|
|
||||||
* <code>@RequestMapping</code> in the usual Spring MVC way.
|
|
||||||
*
|
|
||||||
* @param body the request body to send downstream
|
|
||||||
* @return this for convenience
|
|
||||||
*/
|
|
||||||
public ProxyExchange<T> body(Object body) {
|
|
||||||
this.body = body;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets a header for the downstream call.
|
|
||||||
*
|
|
||||||
* @param name
|
|
||||||
* @param value
|
|
||||||
* @return this for convenience
|
|
||||||
*/
|
|
||||||
public ProxyExchange<T> header(String name, String... value) {
|
|
||||||
this.headers.put(name, Arrays.asList(value));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Additional headers, or overrides of the incoming ones, to be used in the downstream
|
|
||||||
* call.
|
|
||||||
*
|
|
||||||
* @param headers the http headers to use in the downstream call
|
|
||||||
* @return this for convenience
|
|
||||||
*/
|
|
||||||
public ProxyExchange<T> headers(HttpHeaders headers) {
|
|
||||||
this.headers.putAll(headers);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the names of sensitive headers that are not passed downstream to the backend
|
|
||||||
* service.
|
|
||||||
*
|
|
||||||
* @param names the names of sensitive headers
|
|
||||||
* @return this for convenience
|
|
||||||
*/
|
|
||||||
public ProxyExchange<T> sensitive(String... names) {
|
|
||||||
if (this.sensitive == null) {
|
|
||||||
this.sensitive = new HashSet<>();
|
|
||||||
}
|
|
||||||
for (String name : names) {
|
|
||||||
this.sensitive.add(name.toLowerCase());
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the uri for the backend call when triggered by the HTTP methods.
|
|
||||||
*
|
|
||||||
* @param uri the backend uri to send the request to
|
|
||||||
* @return this for convenience
|
|
||||||
*/
|
|
||||||
public ProxyExchange<T> uri(String uri) {
|
|
||||||
try {
|
|
||||||
this.uri = new URI(uri);
|
|
||||||
}
|
|
||||||
catch (URISyntaxException e) {
|
|
||||||
throw new IllegalStateException("Cannot create URI", e);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String path() {
|
|
||||||
return (String) this.webRequest.getAttribute(
|
|
||||||
HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE,
|
|
||||||
WebRequest.SCOPE_REQUEST);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String path(String prefix) {
|
|
||||||
String path = path();
|
|
||||||
if (!path.startsWith(prefix)) {
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
"Path does not start with prefix (" + prefix + "): " + path);
|
|
||||||
}
|
|
||||||
return path.substring(prefix.length());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void forward(String path) {
|
|
||||||
HttpServletRequest request = this.webRequest
|
|
||||||
.getNativeRequest(HttpServletRequest.class);
|
|
||||||
HttpServletResponse response = this.webRequest
|
|
||||||
.getNativeResponse(HttpServletResponse.class);
|
|
||||||
try {
|
|
||||||
request.getRequestDispatcher(path).forward(
|
|
||||||
new BodyForwardingHttpServletRequest(request, response), response);
|
|
||||||
}
|
|
||||||
catch (Exception e) {
|
|
||||||
throw new IllegalStateException("Cannot forward request", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ResponseEntity<T> get() {
|
|
||||||
RequestEntity<?> requestEntity = headers((BodyBuilder) RequestEntity.get(uri))
|
|
||||||
.build();
|
|
||||||
return exchange(requestEntity);
|
|
||||||
}
|
|
||||||
|
|
||||||
public <S> ResponseEntity<S> get(
|
|
||||||
Function<ResponseEntity<T>, ResponseEntity<S>> converter) {
|
|
||||||
return converter.apply(get());
|
|
||||||
}
|
|
||||||
|
|
||||||
public ResponseEntity<T> head() {
|
|
||||||
RequestEntity<?> requestEntity = headers((BodyBuilder) RequestEntity.head(uri))
|
|
||||||
.build();
|
|
||||||
return exchange(requestEntity);
|
|
||||||
}
|
|
||||||
|
|
||||||
public <S> ResponseEntity<S> head(
|
|
||||||
Function<ResponseEntity<T>, ResponseEntity<S>> converter) {
|
|
||||||
return converter.apply(head());
|
|
||||||
}
|
|
||||||
|
|
||||||
public ResponseEntity<T> options() {
|
|
||||||
RequestEntity<?> requestEntity = headers((BodyBuilder) RequestEntity.options(uri))
|
|
||||||
.build();
|
|
||||||
return exchange(requestEntity);
|
|
||||||
}
|
|
||||||
|
|
||||||
public <S> ResponseEntity<S> options(
|
|
||||||
Function<ResponseEntity<T>, ResponseEntity<S>> converter) {
|
|
||||||
return converter.apply(options());
|
|
||||||
}
|
|
||||||
|
|
||||||
public ResponseEntity<T> post() {
|
|
||||||
RequestEntity<Object> requestEntity = headers(RequestEntity.post(uri))
|
|
||||||
.body(body());
|
|
||||||
return exchange(requestEntity);
|
|
||||||
}
|
|
||||||
|
|
||||||
public <S> ResponseEntity<S> post(
|
|
||||||
Function<ResponseEntity<T>, ResponseEntity<S>> converter) {
|
|
||||||
return converter.apply(post());
|
|
||||||
}
|
|
||||||
|
|
||||||
public ResponseEntity<T> delete() {
|
|
||||||
RequestEntity<Void> requestEntity = headers(
|
|
||||||
(BodyBuilder) RequestEntity.delete(uri)).build();
|
|
||||||
return exchange(requestEntity);
|
|
||||||
}
|
|
||||||
|
|
||||||
public <S> ResponseEntity<S> delete(
|
|
||||||
Function<ResponseEntity<T>, ResponseEntity<S>> converter) {
|
|
||||||
return converter.apply(delete());
|
|
||||||
}
|
|
||||||
|
|
||||||
public ResponseEntity<T> put() {
|
|
||||||
RequestEntity<Object> requestEntity = headers(RequestEntity.put(uri))
|
|
||||||
.body(body());
|
|
||||||
return exchange(requestEntity);
|
|
||||||
}
|
|
||||||
|
|
||||||
public <S> ResponseEntity<S> put(
|
|
||||||
Function<ResponseEntity<T>, ResponseEntity<S>> converter) {
|
|
||||||
return converter.apply(put());
|
|
||||||
}
|
|
||||||
|
|
||||||
public ResponseEntity<T> patch() {
|
|
||||||
RequestEntity<Object> requestEntity = headers(RequestEntity.patch(uri))
|
|
||||||
.body(body());
|
|
||||||
return exchange(requestEntity);
|
|
||||||
}
|
|
||||||
|
|
||||||
public <S> ResponseEntity<S> patch(
|
|
||||||
Function<ResponseEntity<T>, ResponseEntity<S>> converter) {
|
|
||||||
return converter.apply(patch());
|
|
||||||
}
|
|
||||||
|
|
||||||
private ResponseEntity<T> exchange(RequestEntity<?> requestEntity) {
|
|
||||||
Type type = this.responseType;
|
|
||||||
if (type instanceof TypeVariable || type instanceof WildcardType) {
|
|
||||||
type = Object.class;
|
|
||||||
}
|
|
||||||
RequestCallback requestCallback = rest.httpEntityCallback((Object) requestEntity,
|
|
||||||
type);
|
|
||||||
ResponseExtractor<ResponseEntity<T>> responseExtractor = rest
|
|
||||||
.responseEntityExtractor(type);
|
|
||||||
return rest.execute(requestEntity.getUrl(), requestEntity.getMethod(),
|
|
||||||
requestCallback, responseExtractor);
|
|
||||||
}
|
|
||||||
|
|
||||||
private BodyBuilder headers(BodyBuilder builder) {
|
|
||||||
Set<String> sensitive = this.sensitive;
|
|
||||||
if (sensitive == null) {
|
|
||||||
sensitive = DEFAULT_SENSITIVE;
|
|
||||||
}
|
|
||||||
proxy();
|
|
||||||
for (String name : headers.keySet()) {
|
|
||||||
if (sensitive.contains(name.toLowerCase())) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
builder.header(name, headers.get(name).toArray(new String[0]));
|
|
||||||
}
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void proxy() {
|
|
||||||
try {
|
|
||||||
URI uri = new URI(webRequest.getNativeRequest(HttpServletRequest.class)
|
|
||||||
.getRequestURL().toString());
|
|
||||||
appendForwarded(uri);
|
|
||||||
appendXForwarded(uri);
|
|
||||||
}
|
|
||||||
catch (URISyntaxException e) {
|
|
||||||
throw new IllegalStateException("Cannot create URI for request: " + webRequest
|
|
||||||
.getNativeRequest(HttpServletRequest.class).getRequestURL());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void appendXForwarded(URI uri) {
|
|
||||||
// Append the legacy headers if they were already added upstream
|
|
||||||
String host = headers.getFirst("x-forwarded-host");
|
|
||||||
if (host == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
host = host + "," + uri.getHost();
|
|
||||||
headers.set("x-forwarded-host", host);
|
|
||||||
String proto = headers.getFirst("x-forwarded-proto");
|
|
||||||
if (proto == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
proto = proto + "," + uri.getScheme();
|
|
||||||
headers.set("x-forwarded-proto", proto);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void appendForwarded(URI uri) {
|
|
||||||
String forwarded = headers.getFirst("forwarded");
|
|
||||||
if (forwarded != null) {
|
|
||||||
forwarded = forwarded + ",";
|
|
||||||
} else {
|
|
||||||
forwarded = "";
|
|
||||||
}
|
|
||||||
forwarded = forwarded + forwarded(uri);
|
|
||||||
headers.set("forwarded", forwarded);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String forwarded(URI uri) {
|
|
||||||
if ("http".equals(uri.getScheme())) {
|
|
||||||
return "host=" + uri.getHost();
|
|
||||||
}
|
|
||||||
return String.format("host=%s;proto=%s", uri.getHost(), uri.getScheme());
|
|
||||||
}
|
|
||||||
|
|
||||||
private Object body() {
|
|
||||||
if (body != null) {
|
|
||||||
return body;
|
|
||||||
}
|
|
||||||
body = getRequestBody();
|
|
||||||
return body;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Search for the request body if it was already deserialized using
|
|
||||||
* <code>@RequestBody</code>. If it is not found then deserialize it in the same way
|
|
||||||
* that it would have been for a <code>@RequestBody</code>.
|
|
||||||
*
|
|
||||||
* @return the request body
|
|
||||||
*/
|
|
||||||
private Object getRequestBody() {
|
|
||||||
for (String key : mavContainer.getModel().keySet()) {
|
|
||||||
if (key.startsWith(BindingResult.MODEL_KEY_PREFIX)) {
|
|
||||||
BindingResult result = (BindingResult) mavContainer.getModel().get(key);
|
|
||||||
return result.getTarget();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
MethodParameter input = new MethodParameter(
|
|
||||||
ClassUtils.getMethod(BodyGrabber.class, "body", Object.class), 0);
|
|
||||||
try {
|
|
||||||
delegate.resolveArgument(input, mavContainer, webRequest, binderFactory);
|
|
||||||
}
|
|
||||||
catch (Exception e) {
|
|
||||||
throw new IllegalStateException("Cannot resolve body", e);
|
|
||||||
}
|
|
||||||
String name = Conventions.getVariableNameForParameter(input);
|
|
||||||
BindingResult result = (BindingResult) mavContainer.getModel()
|
|
||||||
.get(BindingResult.MODEL_KEY_PREFIX + name);
|
|
||||||
return result.getTarget();
|
|
||||||
}
|
|
||||||
|
|
||||||
private NestedTemplate createTemplate(RestTemplate input) {
|
|
||||||
NestedTemplate rest = new NestedTemplate();
|
|
||||||
rest.setMessageConverters(input.getMessageConverters());
|
|
||||||
rest.setErrorHandler(input.getErrorHandler());
|
|
||||||
rest.setDefaultUriVariables(
|
|
||||||
((AbstractUriTemplateHandler) input.getUriTemplateHandler())
|
|
||||||
.getDefaultUriVariables());
|
|
||||||
rest.setRequestFactory(input.getRequestFactory());
|
|
||||||
rest.setInterceptors(input.getInterceptors());
|
|
||||||
return rest;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A special {@link RestTemplate} that knows about the {@link Type} of its response
|
|
||||||
* body explicitly (rather than through a {@link ParameterizedTypeReference}, which is
|
|
||||||
* the only way to access this feature in a regular template).
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
class NestedTemplate extends RestTemplate {
|
|
||||||
@Override
|
|
||||||
protected <S> RequestCallback httpEntityCallback(Object requestBody,
|
|
||||||
Type responseType) {
|
|
||||||
return super.httpEntityCallback(requestBody, responseType);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected <S> ResponseExtractor<ResponseEntity<S>> responseEntityExtractor(
|
|
||||||
Type responseType) {
|
|
||||||
return super.responseEntityExtractor(responseType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A servlet request wrapper that can be safely passed downstream to an internal
|
|
||||||
* forward dispatch, caching its body, and making it available in converted form using
|
|
||||||
* Spring message converters.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
class BodyForwardingHttpServletRequest extends HttpServletRequestWrapper {
|
|
||||||
private HttpServletRequest request;
|
|
||||||
private HttpServletResponse response;
|
|
||||||
|
|
||||||
BodyForwardingHttpServletRequest(HttpServletRequest request,
|
|
||||||
HttpServletResponse response) {
|
|
||||||
super(request);
|
|
||||||
this.request = request;
|
|
||||||
this.response = response;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<String> header(String name) {
|
|
||||||
List<String> list = headers.get(name);
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ServletInputStream getInputStream() throws IOException {
|
|
||||||
Object body = body();
|
|
||||||
MethodParameter output = new MethodParameter(
|
|
||||||
ClassUtils.getMethod(BodySender.class, "body"), -1);
|
|
||||||
ServletOutputToInputConverter response = new ServletOutputToInputConverter(
|
|
||||||
this.response);
|
|
||||||
ServletWebRequest webRequest = new ServletWebRequest(this.request, response);
|
|
||||||
try {
|
|
||||||
delegate.handleReturnValue(body, output, mavContainer, webRequest);
|
|
||||||
}
|
|
||||||
catch (HttpMessageNotWritableException
|
|
||||||
| HttpMediaTypeNotAcceptableException e) {
|
|
||||||
throw new IllegalStateException("Cannot convert body", e);
|
|
||||||
}
|
|
||||||
return response.getInputStream();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Enumeration<String> getHeaderNames() {
|
|
||||||
Set<String> names = headers.keySet();
|
|
||||||
if (names.isEmpty()) {
|
|
||||||
return super.getHeaderNames();
|
|
||||||
}
|
|
||||||
Set<String> result = new LinkedHashSet<>(names);
|
|
||||||
result.addAll(Collections.list(super.getHeaderNames()));
|
|
||||||
return new Vector<String>(result).elements();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Enumeration<String> getHeaders(String name) {
|
|
||||||
List<String> list = header(name);
|
|
||||||
if (list != null) {
|
|
||||||
return new Vector<String>(list).elements();
|
|
||||||
}
|
|
||||||
return super.getHeaders(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getHeader(String name) {
|
|
||||||
List<String> list = header(name);
|
|
||||||
if (list != null && !list.isEmpty()) {
|
|
||||||
return list.iterator().next();
|
|
||||||
}
|
|
||||||
return super.getHeader(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static class BodyGrabber {
|
|
||||||
public Object body(@RequestBody Object body) {
|
|
||||||
return body;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static class BodySender {
|
|
||||||
@ResponseBody
|
|
||||||
public Object body() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convenience class that converts an incoming request input stream into a form that can
|
|
||||||
* be easily deserialized to a Java object using Spring message converters. It is only
|
|
||||||
* used in a local forward dispatch, in which case there is a danger that the request body
|
|
||||||
* will need to be read and analysed more than once. Apart from using the message
|
|
||||||
* converters the other main feature of this class is that the request body is cached and
|
|
||||||
* can be read repeatedly as necessary.
|
|
||||||
*
|
|
||||||
* @author Dave Syer
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
class ServletOutputToInputConverter extends HttpServletResponseWrapper {
|
|
||||||
|
|
||||||
private StringBuilder builder = new StringBuilder();
|
|
||||||
|
|
||||||
public ServletOutputToInputConverter(HttpServletResponse response) {
|
|
||||||
super(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ServletOutputStream getOutputStream() throws IOException {
|
|
||||||
return new ServletOutputStream() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(int b) throws IOException {
|
|
||||||
builder.append(new Character((char) b));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setWriteListener(WriteListener listener) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isReady() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public ServletInputStream getInputStream() {
|
|
||||||
ByteArrayInputStream body = new ByteArrayInputStream(
|
|
||||||
builder.toString().getBytes());
|
|
||||||
return new ServletInputStream() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int read() throws IOException {
|
|
||||||
return body.read();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setReadListener(ReadListener listener) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isReady() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isFinished() {
|
|
||||||
return body.available() <= 0;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016-2017 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
|
|
||||||
*
|
|
||||||
* http://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.function.gateway.config;
|
|
||||||
|
|
||||||
import java.lang.reflect.ParameterizedType;
|
|
||||||
import java.lang.reflect.Type;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import org.springframework.cloud.function.gateway.ProxyExchange;
|
|
||||||
import org.springframework.core.MethodParameter;
|
|
||||||
import org.springframework.http.HttpHeaders;
|
|
||||||
import org.springframework.web.bind.support.WebDataBinderFactory;
|
|
||||||
import org.springframework.web.client.RestTemplate;
|
|
||||||
import org.springframework.web.context.request.NativeWebRequest;
|
|
||||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
|
||||||
import org.springframework.web.method.support.ModelAndViewContainer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Dave Syer
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class ProxyExchangeArgumentResolver implements HandlerMethodArgumentResolver {
|
|
||||||
|
|
||||||
private RestTemplate rest;
|
|
||||||
|
|
||||||
private HttpHeaders headers;
|
|
||||||
|
|
||||||
private Set<String> sensitive;
|
|
||||||
|
|
||||||
public ProxyExchangeArgumentResolver(RestTemplate builder) {
|
|
||||||
this.rest = builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setHeaders(HttpHeaders headers) {
|
|
||||||
this.headers = headers;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSensitive(Set<String> sensitive) {
|
|
||||||
this.sensitive = sensitive;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsParameter(MethodParameter parameter) {
|
|
||||||
return ProxyExchange.class.isAssignableFrom(parameter.getParameterType());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object resolveArgument(MethodParameter parameter,
|
|
||||||
ModelAndViewContainer mavContainer, NativeWebRequest webRequest,
|
|
||||||
WebDataBinderFactory binderFactory) throws Exception {
|
|
||||||
ProxyExchange<?> proxy = new ProxyExchange<>(rest, webRequest, mavContainer,
|
|
||||||
binderFactory, type(parameter));
|
|
||||||
proxy.headers(headers);
|
|
||||||
if (sensitive != null) {
|
|
||||||
proxy.sensitive(sensitive.toArray(new String[0]));
|
|
||||||
}
|
|
||||||
return proxy;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Type type(MethodParameter parameter) {
|
|
||||||
Type type = parameter.getGenericParameterType();
|
|
||||||
if (type instanceof ParameterizedType) {
|
|
||||||
ParameterizedType param = (ParameterizedType) type;
|
|
||||||
type = param.getActualTypeArguments()[0];
|
|
||||||
}
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016-2017 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
|
|
||||||
*
|
|
||||||
* http://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.function.gateway.config;
|
|
||||||
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
|
||||||
import org.springframework.cloud.function.gateway.ProxyExchange;
|
|
||||||
import org.springframework.http.HttpHeaders;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Configuration properties for the {@link ProxyExchange} argument handler in
|
|
||||||
* <code>@RequestMapping</code> methods.
|
|
||||||
* @author Dave Syer
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
@ConfigurationProperties("spring.cloud.gateway.proxy")
|
|
||||||
public class ProxyProperties {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fixed header values that will be added to all downstream requests.
|
|
||||||
*/
|
|
||||||
private Map<String, String> headers = new LinkedHashMap<>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A set of sensitive header names that will not be sent downstream by default.
|
|
||||||
*/
|
|
||||||
private Set<String> sensitive = null;
|
|
||||||
|
|
||||||
public Map<String, String> getHeaders() {
|
|
||||||
return headers;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setHeaders(Map<String, String> headers) {
|
|
||||||
this.headers = headers;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Set<String> getSensitive() {
|
|
||||||
return sensitive;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSensitive(Set<String> sensitive) {
|
|
||||||
this.sensitive = sensitive;
|
|
||||||
}
|
|
||||||
|
|
||||||
public HttpHeaders convertHeaders() {
|
|
||||||
HttpHeaders headers = new HttpHeaders();
|
|
||||||
for (String key : this.headers.keySet()) {
|
|
||||||
headers.set(key, this.headers.get(key));
|
|
||||||
}
|
|
||||||
return headers;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016-2017 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
|
|
||||||
*
|
|
||||||
* http://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.function.gateway.config;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
|
||||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
|
||||||
import org.springframework.boot.web.client.RestTemplateBuilder;
|
|
||||||
import org.springframework.cloud.function.gateway.ProxyExchange;
|
|
||||||
import org.springframework.context.ApplicationContext;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.http.client.ClientHttpResponse;
|
|
||||||
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
|
|
||||||
import org.springframework.web.client.DefaultResponseErrorHandler;
|
|
||||||
import org.springframework.web.client.RestTemplate;
|
|
||||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
|
||||||
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
|
|
||||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Autoconfiguration for the {@link ProxyExchange} argument handler in Spring MVC
|
|
||||||
* <code>@RequestMapping</code> methods.
|
|
||||||
*
|
|
||||||
* @author Dave Syer
|
|
||||||
*/
|
|
||||||
@Configuration
|
|
||||||
@ConditionalOnWebApplication
|
|
||||||
@ConditionalOnClass({ HandlerMethodReturnValueHandler.class })
|
|
||||||
@EnableConfigurationProperties(ProxyProperties.class)
|
|
||||||
public class ProxyResponseAutoConfiguration extends WebMvcConfigurerAdapter {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private ApplicationContext context;
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnMissingBean
|
|
||||||
public ProxyExchangeArgumentResolver proxyExchangeArgumentResolver(
|
|
||||||
Optional<RestTemplateBuilder> optional, ProxyProperties proxy) {
|
|
||||||
RestTemplateBuilder builder = optional.orElse(new RestTemplateBuilder());
|
|
||||||
RestTemplate template = builder.build();
|
|
||||||
template.setErrorHandler(new NoOpResponseErrorHandler());
|
|
||||||
template.getMessageConverters().add(new ByteArrayHttpMessageConverter() {
|
|
||||||
@Override
|
|
||||||
public boolean supports(Class<?> clazz) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
ProxyExchangeArgumentResolver resolver = new ProxyExchangeArgumentResolver(
|
|
||||||
template);
|
|
||||||
resolver.setHeaders(proxy.convertHeaders());
|
|
||||||
resolver.setSensitive(proxy.getSensitive()); // can be null
|
|
||||||
return resolver;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addArgumentResolvers(
|
|
||||||
List<HandlerMethodArgumentResolver> argumentResolvers) {
|
|
||||||
argumentResolvers.add(context.getBean(ProxyExchangeArgumentResolver.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class NoOpResponseErrorHandler extends DefaultResponseErrorHandler {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handleError(ClientHttpResponse response) throws IOException {
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
|
||||||
org.springframework.cloud.function.gateway.config.ProxyResponseAutoConfiguration
|
|
||||||
|
|
||||||
org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureWebMvc=\
|
|
||||||
org.springframework.cloud.function.gateway.config.ProxyResponseAutoConfiguration
|
|
||||||
@@ -1,445 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2016-2017 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
|
|
||||||
*
|
|
||||||
* http://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.function.web.gateway;
|
|
||||||
|
|
||||||
import java.net.URI;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
|
||||||
import org.springframework.boot.context.embedded.LocalServerPort;
|
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
|
||||||
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
|
|
||||||
import org.springframework.boot.test.web.client.TestRestTemplate;
|
|
||||||
import org.springframework.cloud.function.gateway.ProxyExchange;
|
|
||||||
import org.springframework.cloud.function.web.gateway.ProductionConfigurationTests.TestApplication;
|
|
||||||
import org.springframework.cloud.function.web.gateway.ProductionConfigurationTests.TestApplication.Bar;
|
|
||||||
import org.springframework.cloud.function.web.gateway.ProductionConfigurationTests.TestApplication.Foo;
|
|
||||||
import org.springframework.core.ParameterizedTypeReference;
|
|
||||||
import org.springframework.http.HttpHeaders;
|
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.http.RequestEntity;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.test.context.ContextConfiguration;
|
|
||||||
import org.springframework.test.context.junit4.SpringRunner;
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
|
||||||
import org.springframework.web.bind.annotation.RequestHeader;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
import org.springframework.web.util.UriComponentsBuilder;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
|
||||||
|
|
||||||
@RunWith(SpringRunner.class)
|
|
||||||
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
|
|
||||||
@ContextConfiguration(classes = TestApplication.class)
|
|
||||||
public class ProductionConfigurationTests {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private TestRestTemplate rest;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private TestApplication application;
|
|
||||||
|
|
||||||
@LocalServerPort
|
|
||||||
private int port;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void init() throws Exception {
|
|
||||||
application.setHome(new URI("http://localhost:" + port));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void get() throws Exception {
|
|
||||||
assertThat(rest.getForObject("/proxy/0", Foo.class).getName()).isEqualTo("bye");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void path() throws Exception {
|
|
||||||
assertThat(rest.getForObject("/proxy/path/1", Foo.class).getName())
|
|
||||||
.isEqualTo("foo");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void resource() throws Exception {
|
|
||||||
assertThat(rest.getForObject("/proxy/html/test.html", String.class))
|
|
||||||
.contains("<body>Test");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void resourceWithNoType() throws Exception {
|
|
||||||
assertThat(rest.getForObject("/proxy/typeless/test.html", String.class))
|
|
||||||
.contains("<body>Test");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void missing() throws Exception {
|
|
||||||
assertThat(rest.getForEntity("/proxy/missing/0", Foo.class).getStatusCode())
|
|
||||||
.isEqualTo(HttpStatus.NOT_FOUND);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void uri() throws Exception {
|
|
||||||
assertThat(rest.getForObject("/proxy/0", Foo.class).getName()).isEqualTo("bye");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void post() throws Exception {
|
|
||||||
assertThat(rest.postForObject("/proxy/0", Collections.singletonMap("name", "foo"),
|
|
||||||
Bar.class).getName()).isEqualTo("host=localhost;foo");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void forward() throws Exception {
|
|
||||||
assertThat(rest.getForObject("/forward/foos/0", Foo.class).getName())
|
|
||||||
.isEqualTo("bye");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void forwardHeader() throws Exception {
|
|
||||||
ResponseEntity<Foo> result = rest.getForEntity("/forward/special/foos/0",
|
|
||||||
Foo.class);
|
|
||||||
assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK);
|
|
||||||
assertThat(result.getBody().getName()).isEqualTo("FOO");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void postForwardHeader() throws Exception {
|
|
||||||
ResponseEntity<List<Bar>> result = rest.exchange(
|
|
||||||
RequestEntity
|
|
||||||
.post(rest.getRestTemplate().getUriTemplateHandler().expand(
|
|
||||||
"/forward/special/bars"))
|
|
||||||
.body(Collections.singletonList(Collections.singletonMap("name", "foo"))),
|
|
||||||
new ParameterizedTypeReference<List<Bar>>() {
|
|
||||||
});
|
|
||||||
assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK);
|
|
||||||
assertThat(result.getBody().iterator().next().getName()).isEqualTo("FOOfoo");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void postForwardBody() throws Exception {
|
|
||||||
ResponseEntity<String> result = rest.exchange(
|
|
||||||
RequestEntity
|
|
||||||
.post(rest.getRestTemplate().getUriTemplateHandler().expand(
|
|
||||||
"/forward/body/bars"))
|
|
||||||
.body(Collections.singletonList(Collections.singletonMap("name", "foo"))),
|
|
||||||
String.class);
|
|
||||||
assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK);
|
|
||||||
assertThat(result.getBody()).contains("foo");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void postForwardForgetBody() throws Exception {
|
|
||||||
ResponseEntity<String> result = rest.exchange(
|
|
||||||
RequestEntity
|
|
||||||
.post(rest.getRestTemplate().getUriTemplateHandler().expand(
|
|
||||||
"/forward/forget/bars"))
|
|
||||||
.body(Collections.singletonList(Collections.singletonMap("name", "foo"))),
|
|
||||||
String.class);
|
|
||||||
assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK);
|
|
||||||
assertThat(result.getBody()).contains("foo");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void postForwardBodyFoo() throws Exception {
|
|
||||||
ResponseEntity<List<Bar>> result = rest.exchange(
|
|
||||||
RequestEntity
|
|
||||||
.post(rest.getRestTemplate().getUriTemplateHandler().expand(
|
|
||||||
"/forward/body/bars"))
|
|
||||||
.body(Collections.singletonList(Collections.singletonMap("name", "foo"))),
|
|
||||||
new ParameterizedTypeReference<List<Bar>>() {
|
|
||||||
});
|
|
||||||
assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK);
|
|
||||||
assertThat(result.getBody().iterator().next().getName()).isEqualTo("foo");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void list() throws Exception {
|
|
||||||
assertThat(rest.exchange(
|
|
||||||
RequestEntity
|
|
||||||
.post(rest.getRestTemplate().getUriTemplateHandler().expand(
|
|
||||||
"/proxy"))
|
|
||||||
.body(Collections.singletonList(Collections.singletonMap("name", "foo"))),
|
|
||||||
new ParameterizedTypeReference<List<Bar>>() {
|
|
||||||
}).getBody().iterator().next().getName()).isEqualTo("host=localhost;foo");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void bodyless() throws Exception {
|
|
||||||
assertThat(rest.postForObject("/proxy/0", Collections.singletonMap("name", "foo"),
|
|
||||||
Bar.class).getName()).isEqualTo("host=localhost;foo");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void entity() throws Exception {
|
|
||||||
assertThat(rest.exchange(
|
|
||||||
RequestEntity
|
|
||||||
.post(rest.getRestTemplate().getUriTemplateHandler()
|
|
||||||
.expand("/proxy/entity"))
|
|
||||||
.body(Collections.singletonMap("name", "foo")),
|
|
||||||
new ParameterizedTypeReference<List<Bar>>() {
|
|
||||||
}).getBody().iterator().next().getName()).isEqualTo("host=localhost;foo");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void entityWithType() throws Exception {
|
|
||||||
assertThat(rest.exchange(
|
|
||||||
RequestEntity
|
|
||||||
.post(rest.getRestTemplate().getUriTemplateHandler()
|
|
||||||
.expand("/proxy/type"))
|
|
||||||
.body(Collections.singletonMap("name", "foo")),
|
|
||||||
new ParameterizedTypeReference<List<Bar>>() {
|
|
||||||
}).getBody().iterator().next().getName()).isEqualTo("host=localhost;foo");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void single() throws Exception {
|
|
||||||
assertThat(rest.postForObject("/proxy/single",
|
|
||||||
Collections.singletonMap("name", "foobar"), Bar.class).getName())
|
|
||||||
.isEqualTo("host=localhost;foobar");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void converter() throws Exception {
|
|
||||||
assertThat(rest.postForObject("/proxy/converter",
|
|
||||||
Collections.singletonMap("name", "foobar"), Bar.class).getName())
|
|
||||||
.isEqualTo("host=localhost;foobar");
|
|
||||||
}
|
|
||||||
|
|
||||||
@SpringBootApplication
|
|
||||||
static class TestApplication {
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
static class ProxyController {
|
|
||||||
|
|
||||||
private URI home;
|
|
||||||
|
|
||||||
public void setHome(URI home) {
|
|
||||||
this.home = home;
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/proxy/{id}")
|
|
||||||
public ResponseEntity<?> proxyFoos(@PathVariable Integer id,
|
|
||||||
ProxyExchange<?> proxy) throws Exception {
|
|
||||||
return proxy.uri(home.toString() + "/foos/" + id).get();
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/proxy/path/**")
|
|
||||||
public ResponseEntity<?> proxyPath(ProxyExchange<?> proxy,
|
|
||||||
UriComponentsBuilder uri) throws Exception {
|
|
||||||
String path = proxy.path("/proxy/path/");
|
|
||||||
return proxy.uri(home.toString() + "/foos/" + path).get();
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/proxy/html/**")
|
|
||||||
public ResponseEntity<String> proxyHtml(ProxyExchange<String> proxy,
|
|
||||||
UriComponentsBuilder uri) throws Exception {
|
|
||||||
String path = proxy.path("/proxy/html");
|
|
||||||
return proxy.uri(home.toString() + path).get();
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/proxy/typeless/**")
|
|
||||||
public ResponseEntity<?> proxyTypeless(ProxyExchange<?> proxy,
|
|
||||||
UriComponentsBuilder uri) throws Exception {
|
|
||||||
String path = proxy.path("/proxy/typeless");
|
|
||||||
return proxy.uri(home.toString() + path).get();
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/proxy/missing/{id}")
|
|
||||||
public ResponseEntity<?> proxyMissing(@PathVariable Integer id,
|
|
||||||
ProxyExchange<?> proxy) throws Exception {
|
|
||||||
return proxy.uri(home.toString() + "/missing/" + id).get();
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/proxy")
|
|
||||||
public ResponseEntity<?> proxyUri(ProxyExchange<?> proxy) throws Exception {
|
|
||||||
return proxy.uri(home.toString() + "/foos").get();
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/proxy/{id}")
|
|
||||||
public ResponseEntity<?> proxyBars(@PathVariable Integer id,
|
|
||||||
@RequestBody Map<String, Object> body,
|
|
||||||
ProxyExchange<List<Object>> proxy) throws Exception {
|
|
||||||
body.put("id", id);
|
|
||||||
return proxy.uri(home.toString() + "/bars").body(Arrays.asList(body))
|
|
||||||
.post(this::first);
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/proxy")
|
|
||||||
public ResponseEntity<?> barsWithNoBody(ProxyExchange<?> proxy)
|
|
||||||
throws Exception {
|
|
||||||
return proxy.uri(home.toString() + "/bars").post();
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/proxy/entity")
|
|
||||||
public ResponseEntity<?> explicitEntity(@RequestBody Foo foo,
|
|
||||||
ProxyExchange<?> proxy) throws Exception {
|
|
||||||
return proxy.uri(home.toString() + "/bars").body(Arrays.asList(foo))
|
|
||||||
.post();
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/proxy/type")
|
|
||||||
public ResponseEntity<List<Bar>> explicitEntityWithType(@RequestBody Foo foo,
|
|
||||||
ProxyExchange<List<Bar>> proxy) throws Exception {
|
|
||||||
return proxy.uri(home.toString() + "/bars").body(Arrays.asList(foo))
|
|
||||||
.post();
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/proxy/single")
|
|
||||||
public ResponseEntity<?> implicitEntity(@RequestBody Foo foo,
|
|
||||||
ProxyExchange<List<Object>> proxy) throws Exception {
|
|
||||||
return proxy.uri(home.toString() + "/bars").body(Arrays.asList(foo))
|
|
||||||
.post(this::first);
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/proxy/converter")
|
|
||||||
public ResponseEntity<Bar> implicitEntityWithConverter(@RequestBody Foo foo,
|
|
||||||
ProxyExchange<List<Bar>> proxy) throws Exception {
|
|
||||||
return proxy.uri(home.toString() + "/bars").body(Arrays.asList(foo))
|
|
||||||
.post(response -> ResponseEntity.status(response.getStatusCode())
|
|
||||||
.headers(response.getHeaders())
|
|
||||||
.body(response.getBody().iterator().next()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/forward/**")
|
|
||||||
public void forward(ProxyExchange<?> proxy) throws Exception {
|
|
||||||
String path = proxy.path("/forward");
|
|
||||||
if (path.startsWith("/special")) {
|
|
||||||
proxy.header("X-Custom", "FOO");
|
|
||||||
path = proxy.path("/forward/special");
|
|
||||||
}
|
|
||||||
proxy.forward(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/forward/**")
|
|
||||||
public void postForward(ProxyExchange<?> proxy) throws Exception {
|
|
||||||
String path = proxy.path("/forward");
|
|
||||||
if (path.startsWith("/special")) {
|
|
||||||
proxy.header("X-Custom", "FOO");
|
|
||||||
path = proxy.path("/forward/special");
|
|
||||||
}
|
|
||||||
proxy.forward(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/forward/body/**")
|
|
||||||
public void postForwardBody(@RequestBody byte[] body, ProxyExchange<?> proxy)
|
|
||||||
throws Exception {
|
|
||||||
String path = proxy.path("/forward/body");
|
|
||||||
proxy.body(body).forward(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/forward/forget/**")
|
|
||||||
public void postForwardForgetBody(@RequestBody byte[] body,
|
|
||||||
ProxyExchange<?> proxy) throws Exception {
|
|
||||||
String path = proxy.path("/forward/forget");
|
|
||||||
proxy.forward(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
private <T> ResponseEntity<T> first(ResponseEntity<List<T>> response) {
|
|
||||||
return ResponseEntity.status(response.getStatusCode())
|
|
||||||
.headers(response.getHeaders())
|
|
||||||
.body(response.getBody().iterator().next());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private ProxyController controller;
|
|
||||||
|
|
||||||
public void setHome(URI home) {
|
|
||||||
controller.setHome(home);
|
|
||||||
}
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
static class TestController {
|
|
||||||
|
|
||||||
@GetMapping("/foos")
|
|
||||||
public List<Foo> foos() {
|
|
||||||
return Arrays.asList(new Foo("hello"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/foos/{id}")
|
|
||||||
public Foo foo(@PathVariable Integer id, @RequestHeader HttpHeaders headers) {
|
|
||||||
String custom = headers.getFirst("X-Custom");
|
|
||||||
return new Foo(id == 1 ? "foo" : custom != null ? custom : "bye");
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/bars")
|
|
||||||
public List<Bar> bars(@RequestBody List<Foo> foos,
|
|
||||||
@RequestHeader HttpHeaders headers) {
|
|
||||||
String custom = headers.getFirst("X-Custom");
|
|
||||||
custom = custom == null ? "" : custom;
|
|
||||||
custom = headers.getFirst("forwarded")==null ? custom : headers.getFirst("forwarded") + ";" + custom;
|
|
||||||
return Arrays.asList(new Bar(custom + foos.iterator().next().getName()));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
|
||||||
static class Foo {
|
|
||||||
private String name;
|
|
||||||
|
|
||||||
public Foo() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public Foo(String name) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setName(String name) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
|
||||||
static class Bar {
|
|
||||||
private String name;
|
|
||||||
|
|
||||||
public Bar() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public Bar(String name) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setName(String name) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
<html>
|
|
||||||
<body>Test
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
Reference in New Issue
Block a user