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>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-build</artifactId>
|
||||
<version>1.3.0.BUILD-SNAPSHOT</version>
|
||||
<version>1.3.3.RELEASE</version>
|
||||
<relativePath />
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<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>
|
||||
<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>
|
||||
|
||||
<dependencyManagement>
|
||||
@@ -52,7 +52,6 @@
|
||||
<module>spring-cloud-function-compiler</module>
|
||||
<module>spring-cloud-function-core</module>
|
||||
<module>spring-cloud-function-context</module>
|
||||
<module>spring-cloud-function-gateway</module>
|
||||
<module>spring-cloud-function-stream</module>
|
||||
<module>spring-cloud-function-task</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