diff --git a/pom.xml b/pom.xml
index 6a6fe4057..779b3b893 100644
--- a/pom.xml
+++ b/pom.xml
@@ -18,8 +18,8 @@
1.8Aluminium-SR1Brooklyn.SR2
- 1.0.0.M2
- 1.5.1.RELEASE
+ 1.0.0.RC1
+ 1.5.2.RELEASE
@@ -45,6 +45,7 @@
spring-cloud-function-compilerspring-cloud-function-corespring-cloud-function-context
+ spring-cloud-function-gatewayspring-cloud-function-streamspring-cloud-function-taskspring-cloud-function-web
diff --git a/spring-cloud-function-gateway/.jdk8 b/spring-cloud-function-gateway/.jdk8
new file mode 100644
index 000000000..e69de29bb
diff --git a/spring-cloud-function-gateway/pom.xml b/spring-cloud-function-gateway/pom.xml
new file mode 100644
index 000000000..45f20ade0
--- /dev/null
+++ b/spring-cloud-function-gateway/pom.xml
@@ -0,0 +1,37 @@
+
+
+ 4.0.0
+
+ spring-cloud-function-gateway
+ jar
+ spring-cloud-function-gateway
+ Spring Cloud Function Web Support
+
+
+ org.springframework.cloud
+ spring-cloud-function-parent
+ 1.0.0.BUILD-SNAPSHOT
+
+
+
+ 1.0.0.BUILD-SNAPSHOT
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+ true
+
+
+
diff --git a/spring-cloud-function-gateway/src/main/java/org/springframework/cloud/function/gateway/ProxyExchange.java b/spring-cloud-function-gateway/src/main/java/org/springframework/cloud/function/gateway/ProxyExchange.java
new file mode 100644
index 000000000..a132706df
--- /dev/null
+++ b/spring-cloud-function-gateway/src/main/java/org/springframework/cloud/function/gateway/ProxyExchange.java
@@ -0,0 +1,611 @@
+/*
+ * Copyright 2012-2015 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 @RequestMapping 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
+ * ResponseEntity that you get from one of the HTTP methods {@link #get()},
+ * {@link #post()}, {@link #put()}, {@link #patch()}, {@link #delete()} etc. Example:
+ *
+ *
+ * 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).
+ *
+ *
+ * The type parameter T in ProxyExchange<T> is the type of
+ * the response body, so it comes out in the {@link ResponseEntity} that you return from
+ * your @RequestMapping. 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
+ * byte[] or Object. 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.
+ *
+ *
+ * To manipulate the response use the overloaded HTTP methods
+ * with a Function argument and pass in code to transform the response. E.g.
+ *
+ *
+ * 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).
+ *
+ *
+ * As well as the HTTP methods for a backend call you can also use
+ * {@link #forward(String)} for a local in-container dispatch.
+ *
+ *
+ * @author Dave Syer
+ *
+ */
+public class ProxyExchange {
+
+ public static Set 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 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 @RequestBody in your
+ * @RequestMapping in the usual Spring MVC way.
+ *
+ * @param body the request body to send downstream
+ * @return this for convenience
+ */
+ public ProxyExchange 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 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 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 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 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 get() {
+ RequestEntity> requestEntity = headers((BodyBuilder) RequestEntity.get(uri))
+ .build();
+ return exchange(requestEntity);
+ }
+
+ public ResponseEntity get(
+ Function, ResponseEntity> converter) {
+ RequestEntity> requestEntity = headers((BodyBuilder) RequestEntity.get(uri))
+ .build();
+ return converter.apply(exchange(requestEntity));
+ }
+
+ public ResponseEntity head() {
+ RequestEntity> requestEntity = headers((BodyBuilder) RequestEntity.head(uri))
+ .build();
+ return exchange(requestEntity);
+ }
+
+ public ResponseEntity head(
+ Function, ResponseEntity> converter) {
+ RequestEntity> requestEntity = headers((BodyBuilder) RequestEntity.head(uri))
+ .build();
+ return converter.apply(exchange(requestEntity));
+ }
+
+ public ResponseEntity options() {
+ RequestEntity> requestEntity = headers((BodyBuilder) RequestEntity.options(uri))
+ .build();
+ return exchange(requestEntity);
+ }
+
+ public ResponseEntity options(
+ Function, ResponseEntity> converter) {
+ RequestEntity> requestEntity = headers((BodyBuilder) RequestEntity.options(uri))
+ .build();
+ return converter.apply(exchange(requestEntity));
+ }
+
+ public ResponseEntity post() {
+ RequestEntity