Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
adda25b
Initial support for Gateway Server MVC
spencergibb May 5, 2023
394f430
Adds ProxyExchange abstraction and RestTemplate impl.
spencergibb May 5, 2023
86ee7ca
Migrates from RestTemplateProxyExchange to ClientHttpRequestFactoryPr…
spencergibb May 9, 2023
a5780d5
Removes RestTemplateProxyExchange.javaˆ
spencergibb May 9, 2023
5e6dc23
Allows resolving uri by request attribute
spencergibb May 9, 2023
1d964e9
formattingˆ
spencergibb May 9, 2023
6dbab9c
Moved beans to GatewayServerMvcAutoConfiguration
spencergibb May 9, 2023
32b0a52
Removes unneeded GatewayServerRequestBuilder.javaˆ
spencergibb May 10, 2023
5d9aae3
Adds addRequestParameter filter
spencergibb May 10, 2023
640dca3
Adds FilterFunctions.setPath()
spencergibb May 10, 2023
827f214
Moves FilterFunctions.addResponseHeader()
spencergibb May 10, 2023
bed9dd8
Adds FilterFunctions.stripPrefix()
spencergibb May 10, 2023
44a31ac
Moves ProxyExchangeHandlerFunction to bean.
spencergibb May 10, 2023
ac44549
Adds HttpHeadersFilter and RemoveHopByHopR*HeadersFilter
spencergibb May 10, 2023
496258f
removes unused imports
spencergibb May 10, 2023
660d1c3
Initial local configuration compatibility with webflux server.
spencergibb Jun 8, 2023
f97e475
Organized classes into individual packages
spencergibb Jun 8, 2023
2001e60
Copy request body to proxy exchange client request body
spencergibb Jun 8, 2023
9532982
Changes getApplicationContext() to user RequestContextUtils
spencergibb Jun 8, 2023
9f8513a
Moves content to MvcUtils.java
spencergibb Jun 9, 2023
3b78b65
Allows for multiple predicates and filters with the same name.
spencergibb Jun 9, 2023
93ac492
a get only route is to0 broad and causes a request loop
spencergibb Jun 20, 2023
87edcc0
Save the anded predicate, whoops.
spencergibb Jun 20, 2023
e6bd159
Adds initial support for load balancing
spencergibb Jun 20, 2023
fdf1734
Moves TestRestClient to client package
spencergibb Jun 20, 2023
6b1b0ed
Moves handler related classes to handler package
spencergibb Jun 20, 2023
4624823
Creates HandlerDiscoverer to dynamically load HandlerFunctions
spencergibb Jun 20, 2023
41c9903
polish
spencergibb Jun 20, 2023
74a9a92
polish
spencergibb Jun 20, 2023
56ac22b
Extracts findOperation()ˆ
spencergibb Jun 20, 2023
f7faa71
Extracts invokeOperation()
spencergibb Jun 20, 2023
7e15b38
Adds support for config uri type lb://serviceId
spencergibb Jun 21, 2023
7930d75
Adds uri template variable support.
spencergibb Jun 21, 2023
7b2010a
Adds support for host predicate
spencergibb Jun 22, 2023
27227b9
Adds support for circuit breaker filter
spencergibb Jun 22, 2023
cef29cc
Adds initial support for retry filter
spencergibb Jun 23, 2023
254e95e
Adds initial support for Bucket4j rateLimit filter
spencergibb Jun 23, 2023
8550aa7
Removes separate *Supplier classes if not needed.
spencergibb Jun 23, 2023
b849376
Adds header request predicate
spencergibb Jun 23, 2023
fb5d93b
Fixes accept() methods to better values.
spencergibb Jun 23, 2023
488dcea
Adds support for cookie predicate
spencergibb Jun 24, 2023
5928002
Adds support for after, before and between time predicates
spencergibb Jun 24, 2023
76f7a52
Adds support for FilterFunctions.rewritePath()
spencergibb Jun 26, 2023
c6a8b84
Adds support for shortcut configuration.
spencergibb Jun 30, 2023
358d4ac
Format comments
spencergibb Jun 30, 2023
7f88e3f
Adds support for Forwarded and X-Forwarded-* Headers
spencergibb Jun 30, 2023
f644bb6
Moves TestLoadBalancerConfig to top level to shareˆ
spencergibb Jun 30, 2023
470c858
Uses JdkClientHttpRequestFactory.
spencergibb Jun 30, 2023
cf3e632
Revert "Uses JdkClientHttpRequestFactory."
spencergibb Jun 30, 2023
0c9efb2
Adds routeId to RouterFunction.withAttribute()
spencergibb Jul 1, 2023
d491588
Adds toString()ˆ
spencergibb Jul 1, 2023
39e7445
Shows sample of combining java dsl
spencergibb Jul 1, 2023
08ed0d4
Implements refresh of config based routes.
spencergibb Jul 1, 2023
863a199
Adds support for proxying multipart form data.
spencergibb Jul 3, 2023
70864af
removes unneeded multipart propertyˆ
spencergibb Jul 5, 2023
4d00ba0
Revert "Revert "Uses JdkClientHttpRequestFactory.""
spencergibb Jul 5, 2023
c790aec
Adds support for RestClientProxyExchange
spencergibb Jul 5, 2023
bcc43fb
polishes ProxyExchangeHandlerFunction
spencergibb Jul 5, 2023
ea58659
Adds ProxyExchange.ResponseConsumer
spencergibb Jul 5, 2023
2b632cb
Closes clientHttpResponse appropriately.
spencergibb Jul 5, 2023
d11d214
Closes clientHttpResponse appropriately.
spencergibb Jul 5, 2023
b97e0f0
Updates to use new WriteFunction interface
spencergibb Jul 5, 2023
da0f7a5
Removes unused apache httpclient5 dependency
spencergibb Jul 5, 2023
92cb892
Uses new RestClientCustomizer to add ClientHttpRequestFactory.
spencergibb Jul 5, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<bucket4j.version>8.3.0</bucket4j.version>
<blockhound.version>1.0.6.RELEASE</blockhound.version>
<java.version>17</java.version>
<junit-pioneer.version>1.6.1</junit-pioneer.version>
Expand Down Expand Up @@ -97,6 +98,16 @@
<artifactId>spring-boot-devtools</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>com.bucket4j</groupId>
<artifactId>bucket4j-core</artifactId>
<version>${bucket4j.version}</version>
</dependency>
<dependency>
<groupId>com.bucket4j</groupId>
<artifactId>bucket4j-caffeine</artifactId>
<version>${bucket4j.version}</version>
</dependency>
<dependency>
<groupId>io.projectreactor.tools</groupId>
<artifactId>blockhound</artifactId>
Expand Down Expand Up @@ -127,6 +138,7 @@
<module>spring-cloud-gateway-mvc</module>
<module>spring-cloud-gateway-webflux</module>
<module>spring-cloud-gateway-server</module>
<module>spring-cloud-gateway-server-mvc</module>
<module>spring-cloud-starter-gateway</module>
<module>spring-cloud-gateway-sample</module>
<module>spring-cloud-gateway-integration-tests</module>
Expand Down
5 changes: 5 additions & 0 deletions spring-cloud-gateway-dependencies/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@
<artifactId>spring-cloud-gateway-server</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gateway-server-mvc</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
Expand Down
90 changes: 90 additions & 0 deletions spring-cloud-gateway-server-mvc/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2013-2023 the original author or authors.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ https://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->

<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>

<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gateway</artifactId>
<version>4.1.0-SNAPSHOT</version>
<relativePath>..</relativePath> <!-- lookup parent from repository -->
</parent>
<artifactId>spring-cloud-gateway-server-mvc</artifactId>
<packaging>jar</packaging>
<name>Spring Cloud Gateway Server MVC</name>
<description>Spring Cloud Gateway Server MVC</description>
<properties>
<main.basedir>${basedir}/..</main.basedir>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-reactor-resilience4j</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-commons</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-loadbalancer</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.bucket4j</groupId>
<artifactId>bucket4j-core</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.bucket4j</groupId>
<artifactId>bucket4j-caffeine</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.cloud.gateway.server.mvc;

import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration;
import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration;
import org.springframework.boot.web.client.RestClientCustomizer;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.cloud.gateway.server.mvc.config.GatewayMvcProperties;
import org.springframework.cloud.gateway.server.mvc.config.GatewayMvcPropertiesBeanDefinitionRegistrar;
import org.springframework.cloud.gateway.server.mvc.filter.ForwardedRequestHeadersFilter;
import org.springframework.cloud.gateway.server.mvc.filter.HttpHeadersFilter.RequestHttpHeadersFilter;
import org.springframework.cloud.gateway.server.mvc.filter.HttpHeadersFilter.ResponseHttpHeadersFilter;
import org.springframework.cloud.gateway.server.mvc.filter.RemoveHopByHopRequestHeadersFilter;
import org.springframework.cloud.gateway.server.mvc.filter.RemoveHopByHopResponseHeadersFilter;
import org.springframework.cloud.gateway.server.mvc.filter.XForwardedRequestHeadersFilter;
import org.springframework.cloud.gateway.server.mvc.handler.ClientHttpRequestFactoryProxyExchange;
import org.springframework.cloud.gateway.server.mvc.handler.ProxyExchange;
import org.springframework.cloud.gateway.server.mvc.handler.ProxyExchangeHandlerFunction;
import org.springframework.cloud.gateway.server.mvc.handler.RestClientProxyExchange;
import org.springframework.cloud.gateway.server.mvc.predicate.PredicateDiscoverer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.JdkClientHttpRequestFactory;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestClient;

@AutoConfiguration(after = { RestTemplateAutoConfiguration.class, RestClientAutoConfiguration.class })
@Import(GatewayMvcPropertiesBeanDefinitionRegistrar.class)
public class GatewayServerMvcAutoConfiguration {

@Bean
@ConditionalOnMissingBean(ProxyExchange.class)
public ClientHttpRequestFactoryProxyExchange clientHttpRequestFactoryProxyExchange(
ClientHttpRequestFactory requestFactory) {
return new ClientHttpRequestFactoryProxyExchange(requestFactory);
}

@Bean
public RestClientCustomizer gatewayRestClientCustomizer(ClientHttpRequestFactory requestFactory) {
return restClientBuilder -> restClientBuilder.requestFactory(requestFactory);
}

// Make default when reflection is no longer needed to function
// @Bean
@ConditionalOnMissingBean(ProxyExchange.class)
public RestClientProxyExchange restClientProxyExchange(RestClient.Builder restClientBuilder) {
return new RestClientProxyExchange(restClientBuilder.build());
}

@Bean
@ConditionalOnMissingBean
public ForwardedRequestHeadersFilter forwardedRequestHeadersFilter() {
return new ForwardedRequestHeadersFilter();
}

@Bean
@ConditionalOnMissingBean
public ClientHttpRequestFactory gatewayClientHttpRequestFactory(RestTemplateBuilder restTemplateBuilder) {
// TODO: set property if jdk HttpClient
// TODO: temporarily force Jdk HttpClient, copied from
// https://github.com/spring-projects/spring-boot/pull/36118
String restrictedHeaders = System.getProperty("jdk.httpclient.allowRestrictedHeaders");
if (!StringUtils.hasText(restrictedHeaders)) {
System.setProperty("jdk.httpclient.allowRestrictedHeaders", "host");
}
else if (StringUtils.hasText(restrictedHeaders) && !restrictedHeaders.contains("host")) {
System.setProperty("jdk.httpclient.allowRestrictedHeaders", restrictedHeaders + ",host");
}
return restTemplateBuilder.requestFactory(settings -> {
java.net.http.HttpClient.Builder builder = java.net.http.HttpClient.newBuilder();
if (settings.connectTimeout() != null) {
builder.connectTimeout(settings.connectTimeout());
}
if (settings.sslBundle() != null) {
builder.sslContext(settings.sslBundle().createSslContext());
}
java.net.http.HttpClient httpClient = builder.build();
JdkClientHttpRequestFactory requestFactory = new JdkClientHttpRequestFactory(httpClient);
if (settings.readTimeout() != null) {
requestFactory.setReadTimeout(settings.readTimeout());
}
return requestFactory;
}).buildRequestFactory();
}

@Bean
@ConditionalOnMissingBean
public GatewayMvcProperties gatewayMvcProperties() {
return new GatewayMvcProperties();
}

@Bean
public PredicateDiscoverer predicateDiscoverer() {
return new PredicateDiscoverer();
}

@Bean
@ConditionalOnMissingBean
public ProxyExchangeHandlerFunction proxyExchangeHandlerFunction(ProxyExchange proxyExchange,
ObjectProvider<RequestHttpHeadersFilter> requestHttpHeadersFilters,
ObjectProvider<ResponseHttpHeadersFilter> responseHttpHeadersFilters) {
return new ProxyExchangeHandlerFunction(proxyExchange, requestHttpHeadersFilters, responseHttpHeadersFilters);
}

@Bean
@ConditionalOnMissingBean
public RemoveHopByHopRequestHeadersFilter removeHopByHopRequestHeadersFilter() {
return new RemoveHopByHopRequestHeadersFilter();
}

@Bean
@ConditionalOnMissingBean
public RemoveHopByHopResponseHeadersFilter removeHopByHopResponseHeadersFilter() {
return new RemoveHopByHopResponseHeadersFilter();
}

@Bean
@ConditionalOnMissingBean
public XForwardedRequestHeadersFilter xForwardedRequestHeadersFilter() {
return new XForwardedRequestHeadersFilter();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Copyright 2013-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.cloud.gateway.server.mvc.common;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.function.Supplier;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.cloud.gateway.server.mvc.invoke.reflect.DefaultOperationMethod;
import org.springframework.cloud.gateway.server.mvc.invoke.reflect.OperationMethod;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.core.log.LogMessage;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

public abstract class AbstractGatewayDiscoverer {

protected final Log log = LogFactory.getLog(getClass());

protected volatile MultiValueMap<String, OperationMethod> operations = new LinkedMultiValueMap<>();

public <T extends Supplier<Collection<Method>>, R> void doDiscover(Class<T> supplierClass, Class<R> returnType) {
List<T> suppliers = loadSuppliers(supplierClass);

List<Method> methods = new ArrayList<>();
for (Supplier<Collection<Method>> supplier : suppliers) {
methods.addAll(supplier.get());
}

for (Method method : methods) {
// TODO: replace with a BiPredicate of some kind
if (returnType.isAssignableFrom(method.getReturnType())) {
addOperationMethod(method);
}
}
}

protected void addOperationMethod(Method method) {
OperationMethod operationMethod = new DefaultOperationMethod(method);
String key = method.getName();
operations.add(key, operationMethod);
log.trace(LogMessage.format("Discovered %s", operationMethod));
}

public abstract void discover();

protected <T> List<T> loadSuppliers(Class<T> supplierClass) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

List<T> suppliers = SpringFactoriesLoader.loadFactories(supplierClass, classLoader);
return suppliers;
}

public MultiValueMap<String, OperationMethod> getOperations() {
if (operations == null || operations.isEmpty()) {
discover();
}
return operations;
}

}
Loading