Skip to content

Commit 0900dae

Browse files
committed
Merge branch 'main' into pr/3711
2 parents 3bf194d + 50243c2 commit 0900dae

File tree

15 files changed

+307
-55
lines changed

15 files changed

+307
-55
lines changed

README.adoc

+87-2
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,92 @@ image::https://codecov.io/gh/spring-cloud/spring-cloud-gateway/branch/main/graph
2727
[[building]]
2828
= Building
2929

30-
Unresolved directive in <stdin> - include::https:///raw.githubusercontent.com/spring-cloud/spring-cloud-build/main/docs/modules/ROOT/partials/building.adoc[]
30+
:jdkversion: 17
31+
32+
[[basic-compile-and-test]]
33+
== Basic Compile and Test
34+
35+
To build the source you will need to install JDK {jdkversion}.
36+
37+
Spring Cloud uses Maven for most build-related activities, and you
38+
should be able to get off the ground quite quickly by cloning the
39+
project you are interested in and typing
40+
41+
----
42+
$ ./mvnw install
43+
----
44+
45+
NOTE: You can also install Maven (>=3.3.3) yourself and run the `mvn` command
46+
in place of `./mvnw` in the examples below. If you do that you also
47+
might need to add `-P spring` if your local Maven settings do not
48+
contain repository declarations for spring pre-release artifacts.
49+
50+
NOTE: Be aware that you might need to increase the amount of memory
51+
available to Maven by setting a `MAVEN_OPTS` environment variable with
52+
a value like `-Xmx512m -XX:MaxPermSize=128m`. We try to cover this in
53+
the `.mvn` configuration, so if you find you have to do it to make a
54+
build succeed, please raise a ticket to get the settings added to
55+
source control.
56+
57+
The projects that require middleware (i.e. Redis) for testing generally
58+
require that a local instance of [Docker](https://www.docker.com/get-started) is installed and running.
59+
60+
[[documentation]]
61+
== Documentation
62+
63+
The spring-cloud-build module has a "docs" profile, and if you switch
64+
that on it will try to build asciidoc sources using https://docs.antora.org/antora/latest/[Antora] from
65+
`modules/ROOT/`.
66+
67+
As part of that process it will look for a
68+
`docs/src/main/asciidoc/README.adoc` and process it by loading all the includes, but not
69+
parsing or rendering it, just copying it to `${main.basedir}`
70+
(defaults to `$\{basedir}`, i.e. the root of the project). If there are
71+
any changes in the README it will then show up after a Maven build as
72+
a modified file in the correct place. Just commit it and push the change.
73+
74+
[[working-with-the-code]]
75+
== Working with the code
76+
If you don't have an IDE preference we would recommend that you use
77+
https://www.springsource.com/developer/sts[Spring Tools Suite] or
78+
https://eclipse.org[Eclipse] when working with the code. We use the
79+
https://eclipse.org/m2e/[m2eclipse] eclipse plugin for maven support. Other IDEs and tools
80+
should also work without issue as long as they use Maven 3.3.3 or better.
81+
82+
[[activate-the-spring-maven-profile]]
83+
=== Activate the Spring Maven profile
84+
Spring Cloud projects require the 'spring' Maven profile to be activated to resolve
85+
the spring milestone and snapshot repositories. Use your preferred IDE to set this
86+
profile to be active, or you may experience build errors.
87+
88+
[[importing-into-eclipse-with-m2eclipse]]
89+
=== Importing into eclipse with m2eclipse
90+
We recommend the https://eclipse.org/m2e/[m2eclipse] eclipse plugin when working with
91+
eclipse. If you don't already have m2eclipse installed it is available from the "eclipse
92+
marketplace".
93+
94+
NOTE: Older versions of m2e do not support Maven 3.3, so once the
95+
projects are imported into Eclipse you will also need to tell
96+
m2eclipse to use the right profile for the projects. If you
97+
see many different errors related to the POMs in the projects, check
98+
that you have an up to date installation. If you can't upgrade m2e,
99+
add the "spring" profile to your `settings.xml`. Alternatively you can
100+
copy the repository settings from the "spring" profile of the parent
101+
pom into your `settings.xml`.
102+
103+
[[importing-into-eclipse-without-m2eclipse]]
104+
=== Importing into eclipse without m2eclipse
105+
If you prefer not to use m2eclipse you can generate eclipse project metadata using the
106+
following command:
107+
108+
[indent=0]
109+
----
110+
$ ./mvnw eclipse:eclipse
111+
----
112+
113+
The generated eclipse projects can be imported by selecting `import existing projects`
114+
from the `file` menu.
115+
31116

32117
[[contributing]]
33118
= Contributing
@@ -224,7 +309,7 @@ Spring Cloud Build brings along the `basepom:duplicate-finder-maven-plugin`, th
224309
[[duplicate-finder-configuration]]
225310
=== Duplicate Finder configuration
226311

227-
Duplicate finder is *enabled by default* and will run in the `verify` phase of your Maven build, but it will only take effect in your project if you add the `duplicate-finder-maven-plugin` to the `build` section of the projecst's `pom.xml`.
312+
Duplicate finder is *enabled by default* and will run in the `verify` phase of your Maven build, but it will only take effect in your project if you add the `duplicate-finder-maven-plugin` to the `build` section of the project's `pom.xml`.
228313

229314
.pom.xml
230315
[source,xml]

docs/modules/ROOT/pages/spring-cloud-gateway-server-webmvc/filters/addrequestheader.adoc

+8-4
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,10 @@ class RouteConfiguration {
3030
3131
@Bean
3232
public RouterFunction<ServerResponse> gatewayRouterFunctionsAddReqHeader() {
33-
return route(GET("/red"), http("https://example.org"))
34-
.before(addRequestHeader("X-Request-red", "blue"));
33+
return route("addRequestHeader")
34+
.route(GET("/red"), http("https://example.org"))
35+
.before(addRequestHeader("X-Request-red", "blue"))
36+
.build();
3537
}
3638
}
3739
----
@@ -50,8 +52,10 @@ class RouteConfiguration {
5052
5153
@Bean
5254
public RouterFunction<ServerResponse> gatewayRouterFunctionsAddReqHeader() {
53-
return route(GET("/red/{segment}"), http("https://example.org"))
54-
.before(addRequestHeader("X-Request-red", "blue-{segment}"));
55+
return route("addRequestHeader")
56+
.route(GET("/red/{segment}"), http("https://example.org"))
57+
.before(addRequestHeader("X-Request-red", "blue-{segment}"))
58+
.build();
5559
}
5660
}
5761
----

docs/modules/ROOT/pages/spring-cloud-gateway-server-webmvc/writing-custom-predicates-and-filters.adoc

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ The `RequestPredicate` implementations in Spring WebMvc.fn https://docs.spring.i
2929
.SampleRequestPredicates.java
3030
[source,java]
3131
----
32-
import org.springframework.web.reactive.function.server.RequestPredicate;
32+
import org.springframework.web.servlet.function.RequestPredicate;
3333
3434
class SampleRequestPredicates {
3535
public static RequestPredicate headerExists(String header) {

docs/package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"@antora/atlas-extension": "1.0.0-alpha.2",
55
"@antora/collector-extension": "1.0.1",
66
"@asciidoctor/tabs": "1.0.0-beta.6",
7-
"@springio/antora-extensions": "1.14.2",
8-
"@springio/asciidoctor-extensions": "1.0.0-alpha.14"
7+
"@springio/antora-extensions": "1.14.4",
8+
"@springio/asciidoctor-extensions": "1.0.0-alpha.16"
99
}
1010
}

docs/src/main/asciidoc/README.adoc

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ image::https://codecov.io/gh/spring-cloud/spring-cloud-gateway/branch/main/graph
2020
[[building]]
2121
= Building
2222

23-
include::https:///raw.githubusercontent.com/spring-cloud/spring-cloud-build/main/docs/modules/ROOT/partials/building.adoc[]
23+
include::https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/main/docs/modules/ROOT/partials/building.adoc[]
2424

2525
[[contributing]]
2626
= Contributing

spring-cloud-gateway-integration-tests/grpc/pom.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
<properties>
1414
<protoc.version>3.25.1</protoc.version>
15-
<grpc.version>1.70.0</grpc.version>
15+
<grpc.version>1.71.0</grpc.version>
1616
</properties>
1717

1818
<parent>

spring-cloud-gateway-server/pom.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
<description>Spring Cloud Gateway Server</description>
1717
<properties>
1818
<main.basedir>${basedir}/..</main.basedir>
19-
<grpc.version>1.70.0</grpc.version>
19+
<grpc.version>1.71.0</grpc.version>
2020
<context-propagation.version>1.0.0</context-propagation.version>
2121
</properties>
2222

spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java

+1
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ public StringToZonedDateTimeConverter stringToZonedDateTimeConverter() {
210210
* @deprecated in favour of
211211
* {@link org.springframework.cloud.gateway.support.config.KeyValueConverter}
212212
*/
213+
@Deprecated
213214
@Bean
214215
public org.springframework.cloud.gateway.support.KeyValueConverter deprecatedKeyValueConverter() {
215216
return new org.springframework.cloud.gateway.support.KeyValueConverter();

spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/QueryRoutePredicateFactory.java

+38-5
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.util.List;
2121
import java.util.function.Predicate;
2222

23+
import jakarta.validation.constraints.AssertTrue;
2324
import jakarta.validation.constraints.NotEmpty;
2425

2526
import org.springframework.util.StringUtils;
@@ -40,21 +41,26 @@ public class QueryRoutePredicateFactory extends AbstractRoutePredicateFactory<Qu
4041
*/
4142
public static final String REGEXP_KEY = "regexp";
4243

44+
/**
45+
* Predicate key.
46+
*/
47+
public static final String PREDICATE_KEY = "predicate";
48+
4349
public QueryRoutePredicateFactory() {
4450
super(Config.class);
4551
}
4652

4753
@Override
4854
public List<String> shortcutFieldOrder() {
49-
return Arrays.asList(PARAM_KEY, REGEXP_KEY);
55+
return Arrays.asList(PARAM_KEY, REGEXP_KEY, PREDICATE_KEY);
5056
}
5157

5258
@Override
5359
public Predicate<ServerWebExchange> apply(Config config) {
5460
return new GatewayPredicate() {
5561
@Override
5662
public boolean test(ServerWebExchange exchange) {
57-
if (!StringUtils.hasText(config.regexp)) {
63+
if (!StringUtils.hasText(config.regexp) && config.predicate == null) {
5864
// check existence of header
5965
return exchange.getRequest().getQueryParams().containsKey(config.param);
6066
}
@@ -63,8 +69,13 @@ public boolean test(ServerWebExchange exchange) {
6369
if (values == null) {
6470
return false;
6571
}
72+
73+
Predicate<String> predicate = config.predicate;
74+
if (StringUtils.hasText(config.regexp)) {
75+
predicate = value -> value.matches(config.regexp);
76+
}
6677
for (String value : values) {
67-
if (value != null && value.matches(config.regexp)) {
78+
if (value != null && predicate.test(value)) {
6879
return true;
6980
}
7081
}
@@ -90,8 +101,10 @@ public static class Config {
90101

91102
private String regexp;
92103

104+
private Predicate<String> predicate;
105+
93106
public String getParam() {
94-
return param;
107+
return this.param;
95108
}
96109

97110
public Config setParam(String param) {
@@ -100,14 +113,34 @@ public Config setParam(String param) {
100113
}
101114

102115
public String getRegexp() {
103-
return regexp;
116+
return this.regexp;
104117
}
105118

106119
public Config setRegexp(String regexp) {
107120
this.regexp = regexp;
108121
return this;
109122
}
110123

124+
public Predicate<String> getPredicate() {
125+
return this.predicate;
126+
}
127+
128+
public Config setPredicate(Predicate<String> predicate) {
129+
this.predicate = predicate;
130+
return this;
131+
}
132+
133+
/**
134+
* Enforces the validation done on predicate configuration: {@link #regexp} and
135+
* {@link #predicate} can't be both set at runtime.
136+
* @return <code>false</code> if {@link #regexp} and {@link #predicate} are both
137+
* set in this predicate factory configuration
138+
*/
139+
@AssertTrue
140+
public boolean isValid() {
141+
return !(StringUtils.hasText(this.regexp) && this.predicate != null);
142+
}
143+
111144
}
112145

113146
}

spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/builder/PredicateSpec.java

+12
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,18 @@ public <T> BooleanSpec readBody(Class<T> inClass, Predicate<T> predicate) {
204204
getBean(ReadBodyRoutePredicateFactory.class).applyAsync(c -> c.setPredicate(inClass, predicate)));
205205
}
206206

207+
/**
208+
* A predicate that checks if a query parameter value matches criteria of a given
209+
* predicate.
210+
* @param param the query parameter name
211+
* @param predicate a predicate to check the value of the param
212+
* @return a {@link BooleanSpec} to be used to add logical operators
213+
*/
214+
public BooleanSpec query(String param, Predicate<String> predicate) {
215+
return asyncPredicate(
216+
getBean(QueryRoutePredicateFactory.class).applyAsync(c -> c.setParam(param).setPredicate(predicate)));
217+
}
218+
207219
/**
208220
* A predicate that checks if a query parameter matches a regular expression.
209221
* @param param the query parameter name

spring-cloud-gateway-server/src/main/resources/META-INF/scripts/request_rate_limiter.lua

+5-38
Original file line numberDiff line numberDiff line change
@@ -2,59 +2,26 @@ redis.replicate_commands()
22

33
local tokens_key = KEYS[1]
44
local timestamp_key = KEYS[2]
5-
--redis.log(redis.LOG_WARNING, "tokens_key " .. tokens_key)
65

76
local rate = tonumber(ARGV[1])
87
local capacity = tonumber(ARGV[2])
9-
local now = tonumber(ARGV[3])
8+
local now = tonumber(ARGV[3]) or redis.call('TIME')[1]
109
local requested = tonumber(ARGV[4])
1110

1211
local fill_time = capacity / rate
1312
local ttl = math.floor(fill_time * 2)
1413

15-
-- for testing, it should use redis system time in production
16-
if now == nil then
17-
now = redis.call('TIME')[1]
18-
end
19-
20-
--redis.log(redis.LOG_WARNING, "rate " .. ARGV[1])
21-
--redis.log(redis.LOG_WARNING, "capacity " .. ARGV[2])
22-
--redis.log(redis.LOG_WARNING, "now " .. now)
23-
--redis.log(redis.LOG_WARNING, "requested " .. ARGV[4])
24-
--redis.log(redis.LOG_WARNING, "filltime " .. fill_time)
25-
--redis.log(redis.LOG_WARNING, "ttl " .. ttl)
26-
27-
local last_tokens = tonumber(redis.call("get", tokens_key))
28-
if last_tokens == nil then
29-
last_tokens = capacity
30-
end
31-
--redis.log(redis.LOG_WARNING, "last_tokens " .. last_tokens)
32-
33-
local last_refreshed = tonumber(redis.call("get", timestamp_key))
34-
if last_refreshed == nil then
35-
last_refreshed = 0
36-
end
37-
--redis.log(redis.LOG_WARNING, "last_refreshed " .. last_refreshed)
14+
local last_tokens = tonumber(redis.call("get", tokens_key)) or capacity
15+
local last_refreshed = tonumber(redis.call("get", timestamp_key)) or 0
3816

3917
local delta = math.max(0, now-last_refreshed)
4018
local filled_tokens = math.min(capacity, last_tokens+(delta*rate))
4119
local allowed = filled_tokens >= requested
42-
local new_tokens = filled_tokens
43-
local allowed_num = 0
44-
if allowed then
45-
new_tokens = filled_tokens - requested
46-
allowed_num = 1
47-
end
48-
49-
--redis.log(redis.LOG_WARNING, "delta " .. delta)
50-
--redis.log(redis.LOG_WARNING, "filled_tokens " .. filled_tokens)
51-
--redis.log(redis.LOG_WARNING, "allowed_num " .. allowed_num)
52-
--redis.log(redis.LOG_WARNING, "new_tokens " .. new_tokens)
20+
local new_tokens = allowed and filled_tokens - requested or filled_tokens
5321

5422
if ttl > 0 then
5523
redis.call("setex", tokens_key, ttl, new_tokens)
5624
redis.call("setex", timestamp_key, ttl, now)
5725
end
5826

59-
-- return { allowed_num, new_tokens, capacity, filled_tokens, requested, new_tokens }
60-
return { allowed_num, new_tokens }
27+
return { allowed and 1 or 0, new_tokens }

0 commit comments

Comments
 (0)