Merge branch 'main' into pr/3751
This commit is contained in:
2
.github/workflows/maven.yml
vendored
2
.github/workflows/maven.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
distribution: 'liberica'
|
||||
java-version: '17'
|
||||
cache: 'maven'
|
||||
- name: Build with Maven
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[[addresponseheader-gatewayfilter-factory]]
|
||||
= `AddResponseHeader` `GatewayFilter` Factory
|
||||
|
||||
The `AddResponseHeader` `GatewayFilter` Factory takes a `name` and `value` parameter.
|
||||
The `AddResponseHeader` `GatewayFilter` Factory takes three parameters: `name`, `value` and `override`(default value is `true`) .
|
||||
The following example configures an `AddResponseHeader` `GatewayFilter`:
|
||||
|
||||
.application.yml
|
||||
@@ -15,9 +15,12 @@ spring:
|
||||
uri: https://example.org
|
||||
filters:
|
||||
- AddResponseHeader=X-Response-Red, Blue
|
||||
- AddResponseHeader=X-Response-Black, White, false
|
||||
----
|
||||
|
||||
This adds `X-Response-Red:Blue` header to the downstream response's headers for all matching requests.
|
||||
and if the response already contains the `X-Response-Black` header, this will not add the `X-Response-Black: White`
|
||||
header to the downstream response's headers for all matching requests.
|
||||
|
||||
`AddResponseHeader` is aware of URI variables used to match a path or host.
|
||||
URI variables may be used in the value and are expanded at runtime.
|
||||
|
||||
@@ -10,7 +10,7 @@ The following headers (shown with their default values) are added:
|
||||
* `X-Frame-Options (DENY)`
|
||||
* `X-Content-Type-Options (nosniff)`
|
||||
* `Referrer-Policy (no-referrer)`
|
||||
* `Content-Security-Policy (default-src 'self' https:; font-src 'self' https: data:; img-src 'self' https: data:; object-src 'none'; script-src https:; style-src 'self' https: 'unsafe-inline)'`
|
||||
* `Content-Security-Policy (default-src 'self' https:; font-src 'self' https: data:; img-src 'self' https: data:; object-src 'none'; script-src https:; style-src 'self' https: 'unsafe-inline')`
|
||||
* `X-Download-Options (noopen)`
|
||||
* `X-Permitted-Cross-Domain-Policies (none)`
|
||||
|
||||
@@ -29,10 +29,87 @@ The following properties are available:
|
||||
To disable the default values set the `spring.cloud.gateway.filter.secure-headers.disable` property with comma-separated values.
|
||||
The following example shows how to do so:
|
||||
|
||||
[source]
|
||||
.application.yml
|
||||
[source,yaml]
|
||||
----
|
||||
spring.cloud.gateway.filter.secure-headers.disable=x-frame-options,strict-transport-security
|
||||
spring:
|
||||
cloud:
|
||||
gateway:
|
||||
filter:
|
||||
secure-headers:
|
||||
disable: x-frame-options,strict-transport-security
|
||||
----
|
||||
|
||||
NOTE: The lowercase full name of the secure header needs to be used to disable it..
|
||||
To apply the `SecureHeaders` filter to a specific route, add the filter to the list of filters of that route.
|
||||
You can customize the route filter using arguments. Route configuration overrides the global default configuration for this route.
|
||||
|
||||
.application.yml
|
||||
[source,yaml]
|
||||
----
|
||||
- id: secureheaders_route
|
||||
uri: http://example.org
|
||||
predicates:
|
||||
- Path=/**
|
||||
filters:
|
||||
- name: SecureHeaders
|
||||
args:
|
||||
disable: x-frame-options
|
||||
----
|
||||
|
||||
NOTE: The lowercase full name of the secure header needs to be used to disable it.
|
||||
|
||||
== Further options
|
||||
|
||||
You may opt in to add the `Permissions-Policy` header to the response. Permissions Policy is a security header
|
||||
that allows web developers to manage which browser features a website can utilize. Please see
|
||||
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Permissions-Policy[Permissions-Policy] and
|
||||
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Permissions-Policy#directives[Directives] to configure it
|
||||
for your environment.
|
||||
|
||||
.application.yml
|
||||
[source,yaml]
|
||||
----
|
||||
spring:
|
||||
cloud:
|
||||
gateway:
|
||||
filter:
|
||||
secure-headers:
|
||||
enable: permissions-policy
|
||||
permissions-policy : geolocation=(self "https://example.com")
|
||||
----
|
||||
|
||||
In the above https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Permissions-Policy/geolocation[example]
|
||||
the Geolocation API is disabled within all browsing contexts except for its own origin and those whose origin is "https://example.com".
|
||||
The Permissions-Policy may be configured separately for each route.
|
||||
|
||||
.application.yml
|
||||
[source,yaml]
|
||||
----
|
||||
- id: secureheaders_route
|
||||
uri: http://anotherexample.org
|
||||
predicates:
|
||||
- Path=/**
|
||||
filters:
|
||||
- name: SecureHeaders
|
||||
args:
|
||||
disable: x-frame-options
|
||||
enable: permissions-policy
|
||||
permissions-policy : geolocation=("https://anotherexample.org")
|
||||
----
|
||||
|
||||
WARNING: When you enable Permissions-Policy and do not explicitly configure any directives, a default value will be applied.
|
||||
Specifically, this default value disables a wide range of standardized and experimental features.
|
||||
This behavior might not be appropriate for your specific environment or use case.
|
||||
|
||||
Permissions-Policy default value when enabled and no explicit configuration:
|
||||
|
||||
`Permissions-Policy: accelerometer=(), ambient-light-sensor=(), autoplay=(), battery=(), camera=(), cross-origin-isolated=(),
|
||||
display-capture=(), document-domain=(), encrypted-media=(), execution-while-not-rendered=(), execution-while-out-of-viewport=(),
|
||||
fullscreen=(), geolocation=(), gyroscope=(), keyboard-map=(), magnetometer=(), microphone=(), midi=(), navigation-override=(),
|
||||
payment=(), picture-in-picture=(), publickey-credentials-get=(), screen-wake-lock=(), sync-xhr=(), usb=(),
|
||||
web-share=(), xr-spatial-tracking=()`
|
||||
|
||||
|
||||
NOTE: You can check the Permissions Policy feature list for Chrome with https://developer.chrome.com/docs/privacy-security/permissions-policy#chrome_devtools_integration[DevTool Integration].
|
||||
|
||||
When you configure the header value for your environment, make sure to check the browser console for syntax errors.
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
[[seturi-gatewayfilter-factory]]
|
||||
= `SetRequestUri` `GatewayFilter` Factory
|
||||
|
||||
The `SetRequestUri` `GatewayFilter` factory takes a `uri` parameter.
|
||||
It offers a simple way to manipulate the request uri by allowing templated segments of the path.
|
||||
This uses the URI templates from Spring Framework.
|
||||
Multiple matching segments are allowed.
|
||||
The following listing configures a `SetRequestUri` `GatewayFilter`:
|
||||
|
||||
.application.yml
|
||||
[source,yaml]
|
||||
----
|
||||
spring:
|
||||
cloud:
|
||||
gateway:
|
||||
routes:
|
||||
- id: seturi_route
|
||||
uri: no://op
|
||||
predicates:
|
||||
- Path=/{appId}/**
|
||||
filters:
|
||||
- SetRequestUri=http://{appId}.example.com
|
||||
----
|
||||
|
||||
For a request path of `/red-application/blue`, this sets the uri to `http://red-application.example.com` before making the downstream request and the final url, including path is going to be `http://red-application.example.com/red-application/blue`
|
||||
|
||||
@@ -192,6 +192,8 @@ This route matches if the request path was, for example: `/red/1` or `/red/1/` o
|
||||
|
||||
If `matchTrailingSlash` is set to `false`, then request path `/red/1/` will not be matched.
|
||||
|
||||
If you have set `spring.webflux.base-path` property, this will influence the path matching. The property value will be automatically prepended to the path patterns. For example, with `spring.webflux.base-path=/app` and a path pattern of `/red/\{segment\}`, the full pattern used for matching would be `/app/red/\{segment\}`.
|
||||
|
||||
This predicate extracts the URI template variables (such as `segment`, defined in the preceding example) as a map of names and values and places it in the `ServerWebExchange.getAttributes()` with a key defined in `ServerWebExchangeUtils.URI_TEMPLATE_VARIABLES_ATTRIBUTE`.
|
||||
Those values are then available for use by <<gateway-route-filters,`GatewayFilter` factories>>
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
To include Spring Cloud Gateway Server Web MVC in your project, use the starter with a group ID of `org.springframework.cloud` and an artifact ID of `spring-cloud-starter-gateway-server-webmvc`.
|
||||
See the https://projects.spring.io/spring-cloud/[Spring Cloud Project page] for details on setting up your build system with the current Spring Cloud Release Train.
|
||||
|
||||
If you include the starter, but you do not want the gateway to be enabled, set `spring.cloud.gateway.mvc.enabled=false`.
|
||||
If you include the starter, but you do not want the gateway to be enabled, set `spring.cloud.gateway.server.webmvc.enabled=false`.
|
||||
|
||||
IMPORTANT: Spring Cloud Gateway Server MVC is built on https://spring.io/projects/spring-boot#learn[Spring Boot] and https://docs.spring.io/spring-framework/reference/web/webmvc-functional.html[Spring WebMvc.fn].
|
||||
As a consequence, many of the asynchronous or reactive libraries may not apply when you use Spring Cloud Gateway Server MVC.
|
||||
|
||||
@@ -240,13 +240,12 @@ The above route will add a `X-Response-Id` header to the response. Note the use
|
||||
|
||||
== How To Register Custom Predicates and Filters for Configuration
|
||||
|
||||
To use custom Predicates and Filters in external configuration you need to create a special Supplier class and register it in `META-INF/spring.factories`.
|
||||
To use custom Predicates and Filters in external configuration you need to create a special Supplier class and register it a bean in the application context.
|
||||
|
||||
=== Registering Custom Predicates
|
||||
|
||||
To register custom predicates you need to implement `PredicateSupplier`. The `PredicateDiscoverer` looks for static methods that return `RequestPredicates` to register.
|
||||
|
||||
|
||||
SampleFilterSupplier.java
|
||||
[source,java]
|
||||
----
|
||||
@@ -254,7 +253,6 @@ package com.example;
|
||||
|
||||
import org.springframework.cloud.gateway.server.mvc.predicate.PredicateSupplier;
|
||||
|
||||
@Configuration
|
||||
class SamplePredicateSupplier implements PredicateSupplier {
|
||||
|
||||
@Override
|
||||
@@ -265,7 +263,24 @@ class SamplePredicateSupplier implements PredicateSupplier {
|
||||
}
|
||||
----
|
||||
|
||||
You then need to add the class in `META-INF/spring.factories`.
|
||||
To register the `PredicateSupplier` for use in config files, you then need to add the class as a bean as in the example below:
|
||||
|
||||
.PredicateConfiguration.java
|
||||
[source,java]
|
||||
----
|
||||
package com.example;
|
||||
|
||||
@Configuration
|
||||
class PredicateConfiguration {
|
||||
|
||||
@Bean
|
||||
public SamplePredicateSupplier samplePredicateSupplier() {
|
||||
return new SamplePredicateSupplier();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
The requirement to add the class to `META-INF/spring.factories` is deprecated and will be removed in the next major release.
|
||||
|
||||
.META-INF/spring.factories
|
||||
[source]
|
||||
@@ -285,7 +300,6 @@ package com.example;
|
||||
|
||||
import org.springframework.cloud.gateway.server.mvc.filter.SimpleFilterSupplier;
|
||||
|
||||
@Configuration
|
||||
class SampleFilterSupplier extends SimpleFilterSupplier {
|
||||
|
||||
public SampleFilterSupplier() {
|
||||
@@ -294,7 +308,24 @@ class SampleFilterSupplier extends SimpleFilterSupplier {
|
||||
}
|
||||
----
|
||||
|
||||
You then need to add the class in `META-INF/spring.factories`.
|
||||
To register the `FilterSupplier` for use in config files, you then need to add the class as a bean as in the example below:
|
||||
|
||||
.FilterConfiguration.java
|
||||
[source,java]
|
||||
----
|
||||
package com.example;
|
||||
|
||||
@Configuration
|
||||
class FilterConfiguration {
|
||||
|
||||
@Bean
|
||||
public SampleFilterSupplier sampleFilterSupplier() {
|
||||
return new SampleFilterSupplier();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
The requirement to add the class to `META-INF/spring.factories` is deprecated and will be removed in the next major release.
|
||||
|
||||
.META-INF/spring.factories
|
||||
[source]
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
|===
|
||||
|Name | Default | Description
|
||||
|
||||
|spring.cloud.gateway | |
|
||||
|spring.cloud.gateway.default-filters | | List of filter definitions that are applied to every route.
|
||||
|spring.cloud.gateway.discovery.locator | |
|
||||
|spring.cloud.gateway.discovery.locator.enabled | `+++false+++` | Flag that enables DiscoveryClient gateway integration.
|
||||
|spring.cloud.gateway.discovery.locator.filters | |
|
||||
|spring.cloud.gateway.discovery.locator.include-expression | `+++true+++` | SpEL expression that will evaluate whether to include a service in gateway integration or not, defaults to: true.
|
||||
@@ -19,7 +21,9 @@
|
||||
|spring.cloud.gateway.filter.fallback-headers.enabled | `+++true+++` | Enables the fallback-headers filter.
|
||||
|spring.cloud.gateway.filter.hystrix.enabled | `+++true+++` | Enables the hystrix filter.
|
||||
|spring.cloud.gateway.filter.json-to-grpc.enabled | `+++true+++` | Enables the JSON to gRPC filter.
|
||||
|spring.cloud.gateway.filter.local-response-cache | |
|
||||
|spring.cloud.gateway.filter.local-response-cache.enabled | `+++false+++` | Enables the local-response-cache filter.
|
||||
|spring.cloud.gateway.filter.local-response-cache.request | |
|
||||
|spring.cloud.gateway.filter.local-response-cache.request.no-cache-strategy | `+++skip-update-cache-entry+++` |
|
||||
|spring.cloud.gateway.filter.local-response-cache.size | | Maximum size of the cache to evict entries for this route (in KB, MB and GB).
|
||||
|spring.cloud.gateway.filter.local-response-cache.time-to-live | `+++5m+++` | Time to expire a cache entry (expressed in s for seconds, m for minutes, and h for hours).
|
||||
@@ -29,6 +33,7 @@
|
||||
|spring.cloud.gateway.filter.prefix-path.enabled | `+++true+++` | Enables the prefix-path filter.
|
||||
|spring.cloud.gateway.filter.preserve-host-header.enabled | `+++true+++` | Enables the preserve-host-header filter.
|
||||
|spring.cloud.gateway.filter.redirect-to.enabled | `+++true+++` | Enables the redirect-to filter.
|
||||
|spring.cloud.gateway.filter.remove-hop-by-hop | |
|
||||
|spring.cloud.gateway.filter.remove-hop-by-hop.headers | |
|
||||
|spring.cloud.gateway.filter.remove-hop-by-hop.order | `+++0+++` |
|
||||
|spring.cloud.gateway.filter.remove-request-header.enabled | `+++true+++` | Enables the remove-request-header filter.
|
||||
@@ -36,6 +41,7 @@
|
||||
|spring.cloud.gateway.filter.remove-response-header.enabled | `+++true+++` | Enables the remove-response-header filter.
|
||||
|spring.cloud.gateway.filter.request-header-size.enabled | `+++true+++` | Enables the request-header-size filter.
|
||||
|spring.cloud.gateway.filter.request-header-to-request-uri.enabled | `+++true+++` | Enables the request-header-to-request-uri filter.
|
||||
|spring.cloud.gateway.filter.request-rate-limiter | |
|
||||
|spring.cloud.gateway.filter.request-rate-limiter.default-key-resolver | |
|
||||
|spring.cloud.gateway.filter.request-rate-limiter.default-rate-limiter | |
|
||||
|spring.cloud.gateway.filter.request-rate-limiter.enabled | `+++true+++` | Enables the request-rate-limiter filter.
|
||||
@@ -47,12 +53,17 @@
|
||||
|spring.cloud.gateway.filter.rewrite-request-parameter.enabled | `+++true+++` | Enables the rewrite-request-parameter filter.
|
||||
|spring.cloud.gateway.filter.rewrite-response-header.enabled | `+++true+++` | Enables the rewrite-response-header filter.
|
||||
|spring.cloud.gateway.filter.save-session.enabled | `+++true+++` | Enables the save-session filter.
|
||||
|spring.cloud.gateway.filter.secure-headers | |
|
||||
|spring.cloud.gateway.filter.secure-headers.content-security-policy | `+++default-src 'self' https:; font-src 'self' https: data:; img-src 'self' https: data:; object-src 'none'; script-src https:; style-src 'self' https: 'unsafe-inline'+++` |
|
||||
|spring.cloud.gateway.filter.secure-headers.content-type-options | `+++nosniff+++` |
|
||||
|spring.cloud.gateway.filter.secure-headers.default-headers | |
|
||||
|spring.cloud.gateway.filter.secure-headers.disable | |
|
||||
|spring.cloud.gateway.filter.secure-headers.disabled-headers | |
|
||||
|spring.cloud.gateway.filter.secure-headers.download-options | `+++noopen+++` |
|
||||
|spring.cloud.gateway.filter.secure-headers.enabled | `+++true+++` | Enables the secure-headers filter.
|
||||
|spring.cloud.gateway.filter.secure-headers.enabled-headers | |
|
||||
|spring.cloud.gateway.filter.secure-headers.frame-options | `+++DENY+++` |
|
||||
|spring.cloud.gateway.filter.secure-headers.permissions-policy | `+++accelerometer=(), ambient-light-sensor=(), autoplay=(), battery=(), camera=(), cross-origin-isolated=(), display-capture=(), document-domain=(), encrypted-media=(), execution-while-not-rendered=(), execution-while-out-of-viewport=(), fullscreen=(), geolocation=(), gyroscope=(), keyboard-map=(), magnetometer=(), microphone=(), midi=(), navigation-override=(), payment=(), picture-in-picture=(), publickey-credentials-get=(), screen-wake-lock=(), sync-xhr=(), usb=(), web-share=(), xr-spatial-tracking=()+++` |
|
||||
|spring.cloud.gateway.filter.secure-headers.permitted-cross-domain-policies | `+++none+++` |
|
||||
|spring.cloud.gateway.filter.secure-headers.referrer-policy | `+++no-referrer+++` |
|
||||
|spring.cloud.gateway.filter.secure-headers.strict-transport-security | `+++max-age=631138519+++` |
|
||||
@@ -76,13 +87,16 @@
|
||||
|spring.cloud.gateway.global-filter.remove-cached-body.enabled | `+++true+++` | Enables the remove-cached-body global filter.
|
||||
|spring.cloud.gateway.global-filter.route-to-request-url.enabled | `+++true+++` | Enables the route-to-request-url global filter.
|
||||
|spring.cloud.gateway.global-filter.websocket-routing.enabled | `+++true+++` | Enables the websocket-routing global filter.
|
||||
|spring.cloud.gateway.globalcors | |
|
||||
|spring.cloud.gateway.globalcors.add-to-simple-url-handler-mapping | `+++false+++` | If global CORS config should be added to the URL handler.
|
||||
|spring.cloud.gateway.globalcors.cors-configurations | |
|
||||
|spring.cloud.gateway.handler-mapping.order | `+++1+++` | The order of RoutePredicateHandlerMapping.
|
||||
|spring.cloud.gateway.httpclient | |
|
||||
|spring.cloud.gateway.httpclient.compression | `+++false+++` | Enables compression for Netty HttpClient.
|
||||
|spring.cloud.gateway.httpclient.connect-timeout | | The connect timeout in millis, the default is 30s.
|
||||
|spring.cloud.gateway.httpclient.max-header-size | | The max response header size.
|
||||
|spring.cloud.gateway.httpclient.max-initial-line-length | | The max initial line length.
|
||||
|spring.cloud.gateway.httpclient.pool | |
|
||||
|spring.cloud.gateway.httpclient.pool.acquire-timeout | | Only for type FIXED, the maximum time in millis to wait for acquiring.
|
||||
|spring.cloud.gateway.httpclient.pool.eviction-interval | `+++0+++` | Perform regular eviction checks in the background at a specified interval. Disabled by default ({@link Duration#ZERO})
|
||||
|spring.cloud.gateway.httpclient.pool.leasing-strategy | `+++fifo+++` | Configures the leasing strategy for the pool (fifo or lifo), defaults to FIFO which is Netty's default.
|
||||
@@ -92,6 +106,7 @@
|
||||
|spring.cloud.gateway.httpclient.pool.metrics | `+++false+++` | Enables channel pools metrics to be collected and registered in Micrometer. Disabled by default.
|
||||
|spring.cloud.gateway.httpclient.pool.name | `+++proxy+++` | The channel pool map name, defaults to proxy.
|
||||
|spring.cloud.gateway.httpclient.pool.type | `+++elastic+++` | Type of pool for HttpClient to use (elastic, fixed or disabled).
|
||||
|spring.cloud.gateway.httpclient.proxy | |
|
||||
|spring.cloud.gateway.httpclient.proxy.host | | Hostname for proxy configuration of Netty HttpClient.
|
||||
|spring.cloud.gateway.httpclient.proxy.non-proxy-hosts-pattern | | Regular expression (Java) for a configured list of hosts. that should be reached directly, bypassing the proxy
|
||||
|spring.cloud.gateway.httpclient.proxy.password | | Password for proxy configuration of Netty HttpClient.
|
||||
@@ -99,6 +114,7 @@
|
||||
|spring.cloud.gateway.httpclient.proxy.type | `+++http+++` | proxyType for proxy configuration of Netty HttpClient (http, socks4 or socks5).
|
||||
|spring.cloud.gateway.httpclient.proxy.username | | Username for proxy configuration of Netty HttpClient.
|
||||
|spring.cloud.gateway.httpclient.response-timeout | | The response timeout.
|
||||
|spring.cloud.gateway.httpclient.ssl | |
|
||||
|spring.cloud.gateway.httpclient.ssl.close-notify-flush-timeout | `+++3000ms+++` | SSL close_notify flush timeout. Default to 3000 ms.
|
||||
|spring.cloud.gateway.httpclient.ssl.close-notify-read-timeout | `+++0+++` | SSL close_notify read timeout. Default to 0 ms.
|
||||
|spring.cloud.gateway.httpclient.ssl.handshake-timeout | `+++10000ms+++` | SSL handshake timeout. Default to 10000 ms
|
||||
@@ -110,11 +126,14 @@
|
||||
|spring.cloud.gateway.httpclient.ssl.ssl-bundle | | The name of the SSL bundle to use.
|
||||
|spring.cloud.gateway.httpclient.ssl.trusted-x509-certificates | | Trusted certificates for verifying the remote endpoint's certificate.
|
||||
|spring.cloud.gateway.httpclient.ssl.use-insecure-trust-manager | `+++false+++` | Installs the netty InsecureTrustManagerFactory. This is insecure and not suitable for production.
|
||||
|spring.cloud.gateway.httpclient.websocket | |
|
||||
|spring.cloud.gateway.httpclient.websocket.max-frame-payload-length | | Max frame payload length.
|
||||
|spring.cloud.gateway.httpclient.websocket.proxy-ping | `+++true+++` | Proxy ping frames to downstream services, defaults to true.
|
||||
|spring.cloud.gateway.httpclient.wiretap | `+++false+++` | Enables wiretap debugging for Netty HttpClient.
|
||||
|spring.cloud.gateway.httpserver.wiretap | `+++false+++` | Enables wiretap debugging for Netty HttpServer.
|
||||
|spring.cloud.gateway.loadbalancer | |
|
||||
|spring.cloud.gateway.loadbalancer.use404 | `+++false+++` |
|
||||
|spring.cloud.gateway.metrics | |
|
||||
|spring.cloud.gateway.metrics.enabled | `+++false+++` | Enables the collection of metrics data.
|
||||
|spring.cloud.gateway.metrics.prefix | `+++spring.cloud.gateway+++` | The prefix of all metrics emitted by gateway.
|
||||
|spring.cloud.gateway.metrics.tags | | Tags map that added to metrics.
|
||||
@@ -125,8 +144,9 @@
|
||||
|spring.cloud.gateway.mvc.http-client.ssl-bundle | | The name of the SSL bundle to use.
|
||||
|spring.cloud.gateway.mvc.http-client.type | `+++jdk+++` | The HttpClient type. Defaults to JDK.
|
||||
|spring.cloud.gateway.mvc.remove-content-length-request-headers-filter.enabled | `+++true+++` | Enables the remove-content-length-request-headers-filter.
|
||||
|spring.cloud.gateway.mvc.remove-hop-by-hop-request-headers-filter.enabled | `+++true+++` | Enables the forwarded-request-headers-filter.
|
||||
|spring.cloud.gateway.mvc.remove-hop-by-hop-response-headers-filter.enabled | `+++true+++` | Enables the forwarded-request-headers-filter.
|
||||
|spring.cloud.gateway.mvc.remove-hop-by-hop-request-headers-filter.enabled | `+++true+++` | Enables the remove-hop-by-hop-request-headers-filter.
|
||||
|spring.cloud.gateway.mvc.remove-hop-by-hop-response-headers-filter.enabled | `+++true+++` | Enables the remove-hop-by-hop-response-headers-filter.
|
||||
|spring.cloud.gateway.mvc.remove-http2-status-response-headers-filter.enabled | `+++true+++` | Enables the remove-http2-status-response-headers-filter.
|
||||
|spring.cloud.gateway.mvc.routes | | List of Routes.
|
||||
|spring.cloud.gateway.mvc.routes-map | | Map of Routes.
|
||||
|spring.cloud.gateway.mvc.streaming-buffer-size | `+++16384+++` | Buffer size for streaming media mime-types.
|
||||
@@ -161,6 +181,7 @@
|
||||
|spring.cloud.gateway.predicate.remote-addr.enabled | `+++true+++` | Enables the remote-addr predicate.
|
||||
|spring.cloud.gateway.predicate.weight.enabled | `+++true+++` | Enables the weight predicate.
|
||||
|spring.cloud.gateway.predicate.xforwarded-remote-addr.enabled | `+++true+++` | Enables the xforwarded-remote-addr predicate.
|
||||
|spring.cloud.gateway.redis-rate-limiter | |
|
||||
|spring.cloud.gateway.redis-rate-limiter.burst-capacity-header | `+++X-RateLimit-Burst-Capacity+++` | The name of the header that returns the burst capacity configuration.
|
||||
|spring.cloud.gateway.redis-rate-limiter.config | |
|
||||
|spring.cloud.gateway.redis-rate-limiter.include-headers | `+++true+++` | Whether or not to include headers containing rate limiter information, defaults to true.
|
||||
@@ -172,8 +193,200 @@
|
||||
|spring.cloud.gateway.route-filter-cache-enabled | `+++false+++` | Enables the route filter cache, defaults to false.
|
||||
|spring.cloud.gateway.route-refresh-listener.enabled | `+++true+++` | If RouteRefreshListener should be turned on.
|
||||
|spring.cloud.gateway.routes | | List of Routes.
|
||||
|spring.cloud.gateway.server.webflux.default-filters | | List of filter definitions that are applied to every route.
|
||||
|spring.cloud.gateway.server.webflux.discovery.locator.enabled | `+++false+++` | Flag that enables DiscoveryClient gateway integration.
|
||||
|spring.cloud.gateway.server.webflux.discovery.locator.filters | |
|
||||
|spring.cloud.gateway.server.webflux.discovery.locator.include-expression | `+++true+++` | SpEL expression that will evaluate whether to include a service in gateway integration or not, defaults to: true.
|
||||
|spring.cloud.gateway.server.webflux.discovery.locator.lower-case-service-id | `+++false+++` | Option to lower case serviceId in predicates and filters, defaults to false. Useful with eureka when it automatically uppercases serviceId. so MYSERIVCE, would match /myservice/**
|
||||
|spring.cloud.gateway.server.webflux.discovery.locator.predicates | |
|
||||
|spring.cloud.gateway.server.webflux.discovery.locator.route-id-prefix | | The prefix for the routeId, defaults to discoveryClient.getClass().getSimpleName() + "_". Service Id will be appended to create the routeId.
|
||||
|spring.cloud.gateway.server.webflux.discovery.locator.url-expression | `+++'lb://'+serviceId+++` | SpEL expression that create the uri for each route, defaults to: 'lb://'+serviceId.
|
||||
|spring.cloud.gateway.server.webflux.enabled | `+++true+++` | Enables gateway functionality.
|
||||
|spring.cloud.gateway.server.webflux.fail-on-route-definition-error | `+++true+++` | Option to fail on route definition errors, defaults to true. Otherwise, a warning is logged.
|
||||
|spring.cloud.gateway.server.webflux.filter.add-request-header.enabled | `+++true+++` | Enables the add-request-header filter.
|
||||
|spring.cloud.gateway.server.webflux.filter.add-request-parameter.enabled | `+++true+++` | Enables the add-request-parameter filter.
|
||||
|spring.cloud.gateway.server.webflux.filter.add-response-header.enabled | `+++true+++` | Enables the add-response-header filter.
|
||||
|spring.cloud.gateway.server.webflux.filter.circuit-breaker.enabled | `+++true+++` | Enables the circuit-breaker filter.
|
||||
|spring.cloud.gateway.server.webflux.filter.dedupe-response-header.enabled | `+++true+++` | Enables the dedupe-response-header filter.
|
||||
|spring.cloud.gateway.server.webflux.filter.fallback-headers.enabled | `+++true+++` | Enables the fallback-headers filter.
|
||||
|spring.cloud.gateway.server.webflux.filter.hystrix.enabled | `+++true+++` | Enables the hystrix filter.
|
||||
|spring.cloud.gateway.server.webflux.filter.json-to-grpc.enabled | `+++true+++` | Enables the JSON to gRPC filter.
|
||||
|spring.cloud.gateway.server.webflux.filter.local-response-cache.enabled | `+++false+++` | Enables the local-response-cache filter.
|
||||
|spring.cloud.gateway.server.webflux.filter.local-response-cache.request.no-cache-strategy | `+++skip-update-cache-entry+++` |
|
||||
|spring.cloud.gateway.server.webflux.filter.local-response-cache.size | | Maximum size of the cache to evict entries for this route (in KB, MB and GB).
|
||||
|spring.cloud.gateway.server.webflux.filter.local-response-cache.time-to-live | `+++5m+++` | Time to expire a cache entry (expressed in s for seconds, m for minutes, and h for hours).
|
||||
|spring.cloud.gateway.server.webflux.filter.map-request-header.enabled | `+++true+++` | Enables the map-request-header filter.
|
||||
|spring.cloud.gateway.server.webflux.filter.modify-request-body.enabled | `+++true+++` | Enables the modify-request-body filter.
|
||||
|spring.cloud.gateway.server.webflux.filter.modify-response-body.enabled | `+++true+++` | Enables the modify-response-body filter.
|
||||
|spring.cloud.gateway.server.webflux.filter.prefix-path.enabled | `+++true+++` | Enables the prefix-path filter.
|
||||
|spring.cloud.gateway.server.webflux.filter.preserve-host-header.enabled | `+++true+++` | Enables the preserve-host-header filter.
|
||||
|spring.cloud.gateway.server.webflux.filter.redirect-to.enabled | `+++true+++` | Enables the redirect-to filter.
|
||||
|spring.cloud.gateway.server.webflux.filter.remove-hop-by-hop.headers | |
|
||||
|spring.cloud.gateway.server.webflux.filter.remove-hop-by-hop.order | `+++0+++` |
|
||||
|spring.cloud.gateway.server.webflux.filter.remove-request-header.enabled | `+++true+++` | Enables the remove-request-header filter.
|
||||
|spring.cloud.gateway.server.webflux.filter.remove-request-parameter.enabled | `+++true+++` | Enables the remove-request-parameter filter.
|
||||
|spring.cloud.gateway.server.webflux.filter.remove-response-header.enabled | `+++true+++` | Enables the remove-response-header filter.
|
||||
|spring.cloud.gateway.server.webflux.filter.request-header-size.enabled | `+++true+++` | Enables the request-header-size filter.
|
||||
|spring.cloud.gateway.server.webflux.filter.request-header-to-request-uri.enabled | `+++true+++` | Enables the request-header-to-request-uri filter.
|
||||
|spring.cloud.gateway.server.webflux.filter.request-rate-limiter.default-key-resolver | |
|
||||
|spring.cloud.gateway.server.webflux.filter.request-rate-limiter.default-rate-limiter | |
|
||||
|spring.cloud.gateway.server.webflux.filter.request-rate-limiter.enabled | `+++true+++` | Enables the request-rate-limiter filter.
|
||||
|spring.cloud.gateway.server.webflux.filter.request-size.enabled | `+++true+++` | Enables the request-size filter.
|
||||
|spring.cloud.gateway.server.webflux.filter.retry.enabled | `+++true+++` | Enables the retry filter.
|
||||
|spring.cloud.gateway.server.webflux.filter.rewrite-location-response-header.enabled | `+++true+++` | Enables the rewrite-location-response-header filter.
|
||||
|spring.cloud.gateway.server.webflux.filter.rewrite-location.enabled | `+++true+++` | Enables the rewrite-location filter.
|
||||
|spring.cloud.gateway.server.webflux.filter.rewrite-path.enabled | `+++true+++` | Enables the rewrite-path filter.
|
||||
|spring.cloud.gateway.server.webflux.filter.rewrite-request-parameter.enabled | `+++true+++` | Enables the rewrite-request-parameter filter.
|
||||
|spring.cloud.gateway.server.webflux.filter.rewrite-response-header.enabled | `+++true+++` | Enables the rewrite-response-header filter.
|
||||
|spring.cloud.gateway.server.webflux.filter.save-session.enabled | `+++true+++` | Enables the save-session filter.
|
||||
|spring.cloud.gateway.server.webflux.filter.secure-headers.content-security-policy | `+++default-src 'self' https:; font-src 'self' https: data:; img-src 'self' https: data:; object-src 'none'; script-src https:; style-src 'self' https: 'unsafe-inline'+++` |
|
||||
|spring.cloud.gateway.server.webflux.filter.secure-headers.content-type-options | `+++nosniff+++` |
|
||||
|spring.cloud.gateway.server.webflux.filter.secure-headers.default-headers | |
|
||||
|spring.cloud.gateway.server.webflux.filter.secure-headers.disable | |
|
||||
|spring.cloud.gateway.server.webflux.filter.secure-headers.disabled-headers | |
|
||||
|spring.cloud.gateway.server.webflux.filter.secure-headers.download-options | `+++noopen+++` |
|
||||
|spring.cloud.gateway.server.webflux.filter.secure-headers.enabled | `+++true+++` | Enables the secure-headers filter.
|
||||
|spring.cloud.gateway.server.webflux.filter.secure-headers.enabled-headers | |
|
||||
|spring.cloud.gateway.server.webflux.filter.secure-headers.frame-options | `+++DENY+++` |
|
||||
|spring.cloud.gateway.server.webflux.filter.secure-headers.permissions-policy | `+++accelerometer=(), ambient-light-sensor=(), autoplay=(), battery=(), camera=(), cross-origin-isolated=(), display-capture=(), document-domain=(), encrypted-media=(), execution-while-not-rendered=(), execution-while-out-of-viewport=(), fullscreen=(), geolocation=(), gyroscope=(), keyboard-map=(), magnetometer=(), microphone=(), midi=(), navigation-override=(), payment=(), picture-in-picture=(), publickey-credentials-get=(), screen-wake-lock=(), sync-xhr=(), usb=(), web-share=(), xr-spatial-tracking=()+++` |
|
||||
|spring.cloud.gateway.server.webflux.filter.secure-headers.permitted-cross-domain-policies | `+++none+++` |
|
||||
|spring.cloud.gateway.server.webflux.filter.secure-headers.referrer-policy | `+++no-referrer+++` |
|
||||
|spring.cloud.gateway.server.webflux.filter.secure-headers.strict-transport-security | `+++max-age=631138519+++` |
|
||||
|spring.cloud.gateway.server.webflux.filter.secure-headers.xss-protection-header | `+++1 ; mode=block+++` |
|
||||
|spring.cloud.gateway.server.webflux.filter.set-path.enabled | `+++true+++` | Enables the set-path filter.
|
||||
|spring.cloud.gateway.server.webflux.filter.set-request-header.enabled | `+++true+++` | Enables the set-request-header filter.
|
||||
|spring.cloud.gateway.server.webflux.filter.set-request-host-header.enabled | `+++true+++` | Enables the set-request-host-header filter.
|
||||
|spring.cloud.gateway.server.webflux.filter.set-response-header.enabled | `+++true+++` | Enables the set-response-header filter.
|
||||
|spring.cloud.gateway.server.webflux.filter.set-status.enabled | `+++true+++` | Enables the set-status filter.
|
||||
|spring.cloud.gateway.server.webflux.filter.strip-prefix.enabled | `+++true+++` | Enables the strip-prefix filter.
|
||||
|spring.cloud.gateway.server.webflux.forwarded.by.enabled | `+++false+++` | Enables the Forwarded: by header part.
|
||||
|spring.cloud.gateway.server.webflux.forwarded.enabled | `+++true+++` | Enables the ForwardedHeadersFilter.
|
||||
|spring.cloud.gateway.server.webflux.global-filter.adapt-cached-body.enabled | `+++true+++` | Enables the adapt-cached-body global filter.
|
||||
|spring.cloud.gateway.server.webflux.global-filter.forward-path.enabled | `+++true+++` | Enables the forward-path global filter.
|
||||
|spring.cloud.gateway.server.webflux.global-filter.forward-routing.enabled | `+++true+++` | Enables the forward-routing global filter.
|
||||
|spring.cloud.gateway.server.webflux.global-filter.load-balancer-client.enabled | `+++true+++` | Enables the load-balancer-client global filter.
|
||||
|spring.cloud.gateway.server.webflux.global-filter.local-response-cache.enabled | `+++true+++` | Enables the local-response-cache filter for all routes, it allows to add a specific configuration at route level using LocalResponseCache filter.
|
||||
|spring.cloud.gateway.server.webflux.global-filter.netty-routing.enabled | `+++true+++` | Enables the netty-routing global filter.
|
||||
|spring.cloud.gateway.server.webflux.global-filter.netty-write-response.enabled | `+++true+++` | Enables the netty-write-response global filter.
|
||||
|spring.cloud.gateway.server.webflux.global-filter.reactive-load-balancer-client.enabled | `+++true+++` | Enables the reactive-load-balancer-client global filter.
|
||||
|spring.cloud.gateway.server.webflux.global-filter.remove-cached-body.enabled | `+++true+++` | Enables the remove-cached-body global filter.
|
||||
|spring.cloud.gateway.server.webflux.global-filter.route-to-request-url.enabled | `+++true+++` | Enables the route-to-request-url global filter.
|
||||
|spring.cloud.gateway.server.webflux.global-filter.websocket-routing.enabled | `+++true+++` | Enables the websocket-routing global filter.
|
||||
|spring.cloud.gateway.server.webflux.globalcors.add-to-simple-url-handler-mapping | `+++false+++` | If global CORS config should be added to the URL handler.
|
||||
|spring.cloud.gateway.server.webflux.globalcors.cors-configurations | |
|
||||
|spring.cloud.gateway.server.webflux.handler-mapping.order | `+++1+++` | The order of RoutePredicateHandlerMapping.
|
||||
|spring.cloud.gateway.server.webflux.httpclient.compression | `+++false+++` | Enables compression for Netty HttpClient.
|
||||
|spring.cloud.gateway.server.webflux.httpclient.connect-timeout | | The connect timeout in millis, the default is 30s.
|
||||
|spring.cloud.gateway.server.webflux.httpclient.max-header-size | | The max response header size.
|
||||
|spring.cloud.gateway.server.webflux.httpclient.max-initial-line-length | | The max initial line length.
|
||||
|spring.cloud.gateway.server.webflux.httpclient.pool.acquire-timeout | | Only for type FIXED, the maximum time in millis to wait for acquiring.
|
||||
|spring.cloud.gateway.server.webflux.httpclient.pool.eviction-interval | `+++0+++` | Perform regular eviction checks in the background at a specified interval. Disabled by default ({@link Duration#ZERO})
|
||||
|spring.cloud.gateway.server.webflux.httpclient.pool.leasing-strategy | `+++fifo+++` | Configures the leasing strategy for the pool (fifo or lifo), defaults to FIFO which is Netty's default.
|
||||
|spring.cloud.gateway.server.webflux.httpclient.pool.max-connections | | Only for type FIXED, the maximum number of connections before starting pending acquisition on existing ones.
|
||||
|spring.cloud.gateway.server.webflux.httpclient.pool.max-idle-time | | Time in millis after which the channel will be closed. If NULL, there is no max idle time.
|
||||
|spring.cloud.gateway.server.webflux.httpclient.pool.max-life-time | | Duration after which the channel will be closed. If NULL, there is no max life time.
|
||||
|spring.cloud.gateway.server.webflux.httpclient.pool.metrics | `+++false+++` | Enables channel pools metrics to be collected and registered in Micrometer. Disabled by default.
|
||||
|spring.cloud.gateway.server.webflux.httpclient.pool.name | `+++proxy+++` | The channel pool map name, defaults to proxy.
|
||||
|spring.cloud.gateway.server.webflux.httpclient.pool.type | `+++elastic+++` | Type of pool for HttpClient to use (elastic, fixed or disabled).
|
||||
|spring.cloud.gateway.server.webflux.httpclient.proxy.host | | Hostname for proxy configuration of Netty HttpClient.
|
||||
|spring.cloud.gateway.server.webflux.httpclient.proxy.non-proxy-hosts-pattern | | Regular expression (Java) for a configured list of hosts. that should be reached directly, bypassing the proxy
|
||||
|spring.cloud.gateway.server.webflux.httpclient.proxy.password | | Password for proxy configuration of Netty HttpClient.
|
||||
|spring.cloud.gateway.server.webflux.httpclient.proxy.port | | Port for proxy configuration of Netty HttpClient.
|
||||
|spring.cloud.gateway.server.webflux.httpclient.proxy.type | `+++http+++` | proxyType for proxy configuration of Netty HttpClient (http, socks4 or socks5).
|
||||
|spring.cloud.gateway.server.webflux.httpclient.proxy.username | | Username for proxy configuration of Netty HttpClient.
|
||||
|spring.cloud.gateway.server.webflux.httpclient.response-timeout | | The response timeout.
|
||||
|spring.cloud.gateway.server.webflux.httpclient.ssl.close-notify-flush-timeout | `+++3000ms+++` | SSL close_notify flush timeout. Default to 3000 ms.
|
||||
|spring.cloud.gateway.server.webflux.httpclient.ssl.close-notify-read-timeout | `+++0+++` | SSL close_notify read timeout. Default to 0 ms.
|
||||
|spring.cloud.gateway.server.webflux.httpclient.ssl.handshake-timeout | `+++10000ms+++` | SSL handshake timeout. Default to 10000 ms
|
||||
|spring.cloud.gateway.server.webflux.httpclient.ssl.key-password | | Key password, default is same as keyStorePassword.
|
||||
|spring.cloud.gateway.server.webflux.httpclient.ssl.key-store | | Keystore path for Netty HttpClient.
|
||||
|spring.cloud.gateway.server.webflux.httpclient.ssl.key-store-password | | Keystore password.
|
||||
|spring.cloud.gateway.server.webflux.httpclient.ssl.key-store-provider | | Keystore provider for Netty HttpClient, optional field.
|
||||
|spring.cloud.gateway.server.webflux.httpclient.ssl.key-store-type | `+++JKS+++` | Keystore type for Netty HttpClient, default is JKS.
|
||||
|spring.cloud.gateway.server.webflux.httpclient.ssl.ssl-bundle | | The name of the SSL bundle to use.
|
||||
|spring.cloud.gateway.server.webflux.httpclient.ssl.trusted-x509-certificates | | Trusted certificates for verifying the remote endpoint's certificate.
|
||||
|spring.cloud.gateway.server.webflux.httpclient.ssl.use-insecure-trust-manager | `+++false+++` | Installs the netty InsecureTrustManagerFactory. This is insecure and not suitable for production.
|
||||
|spring.cloud.gateway.server.webflux.httpclient.websocket.max-frame-payload-length | | Max frame payload length.
|
||||
|spring.cloud.gateway.server.webflux.httpclient.websocket.proxy-ping | `+++true+++` | Proxy ping frames to downstream services, defaults to true.
|
||||
|spring.cloud.gateway.server.webflux.httpclient.wiretap | `+++false+++` | Enables wiretap debugging for Netty HttpClient.
|
||||
|spring.cloud.gateway.server.webflux.httpserver.wiretap | `+++false+++` | Enables wiretap debugging for Netty HttpServer.
|
||||
|spring.cloud.gateway.server.webflux.loadbalancer.use404 | `+++false+++` |
|
||||
|spring.cloud.gateway.server.webflux.metrics.enabled | `+++false+++` | Enables the collection of metrics data.
|
||||
|spring.cloud.gateway.server.webflux.metrics.prefix | `+++spring.cloud.gateway+++` | The prefix of all metrics emitted by gateway.
|
||||
|spring.cloud.gateway.server.webflux.metrics.tags | | Tags map that added to metrics.
|
||||
|spring.cloud.gateway.server.webflux.observability.enabled | `+++true+++` | If Micrometer Observability support should be turned on.
|
||||
|spring.cloud.gateway.server.webflux.predicate.after.enabled | `+++true+++` | Enables the after predicate.
|
||||
|spring.cloud.gateway.server.webflux.predicate.before.enabled | `+++true+++` | Enables the before predicate.
|
||||
|spring.cloud.gateway.server.webflux.predicate.between.enabled | `+++true+++` | Enables the between predicate.
|
||||
|spring.cloud.gateway.server.webflux.predicate.cloud-foundry-route-service.enabled | `+++true+++` | Enables the cloud-foundry-route-service predicate.
|
||||
|spring.cloud.gateway.server.webflux.predicate.cookie.enabled | `+++true+++` | Enables the cookie predicate.
|
||||
|spring.cloud.gateway.server.webflux.predicate.header.enabled | `+++true+++` | Enables the header predicate.
|
||||
|spring.cloud.gateway.server.webflux.predicate.host.enabled | `+++true+++` | Enables the host predicate.
|
||||
|spring.cloud.gateway.server.webflux.predicate.host.include-port | `+++true+++` | Include the port in matching the host name.
|
||||
|spring.cloud.gateway.server.webflux.predicate.method.enabled | `+++true+++` | Enables the method predicate.
|
||||
|spring.cloud.gateway.server.webflux.predicate.path.enabled | `+++true+++` | Enables the path predicate.
|
||||
|spring.cloud.gateway.server.webflux.predicate.query.enabled | `+++true+++` | Enables the query predicate.
|
||||
|spring.cloud.gateway.server.webflux.predicate.read-body.enabled | `+++true+++` | Enables the read-body predicate.
|
||||
|spring.cloud.gateway.server.webflux.predicate.remote-addr.enabled | `+++true+++` | Enables the remote-addr predicate.
|
||||
|spring.cloud.gateway.server.webflux.predicate.weight.enabled | `+++true+++` | Enables the weight predicate.
|
||||
|spring.cloud.gateway.server.webflux.predicate.xforwarded-remote-addr.enabled | `+++true+++` | Enables the xforwarded-remote-addr predicate.
|
||||
|spring.cloud.gateway.server.webflux.redis-rate-limiter.burst-capacity-header | `+++X-RateLimit-Burst-Capacity+++` | The name of the header that returns the burst capacity configuration.
|
||||
|spring.cloud.gateway.server.webflux.redis-rate-limiter.config | |
|
||||
|spring.cloud.gateway.server.webflux.redis-rate-limiter.include-headers | `+++true+++` | Whether or not to include headers containing rate limiter information, defaults to true.
|
||||
|spring.cloud.gateway.server.webflux.redis-rate-limiter.remaining-header | `+++X-RateLimit-Remaining+++` | The name of the header that returns number of remaining requests during the current second.
|
||||
|spring.cloud.gateway.server.webflux.redis-rate-limiter.replenish-rate-header | `+++X-RateLimit-Replenish-Rate+++` | The name of the header that returns the replenish rate configuration.
|
||||
|spring.cloud.gateway.server.webflux.redis-rate-limiter.requested-tokens-header | `+++X-RateLimit-Requested-Tokens+++` | The name of the header that returns the requested tokens configuration.
|
||||
|spring.cloud.gateway.server.webflux.redis-route-definition-repository.enabled | `+++true+++` | If RedisRouteDefinitionRepository should be enabled.
|
||||
|spring.cloud.gateway.server.webflux.restrictive-property-accessor.enabled | `+++true+++` | Restricts method and property access in SpEL.
|
||||
|spring.cloud.gateway.server.webflux.route-filter-cache-enabled | `+++false+++` | Enables the route filter cache, defaults to false.
|
||||
|spring.cloud.gateway.server.webflux.route-refresh-listener.enabled | `+++true+++` | If RouteRefreshListener should be turned on.
|
||||
|spring.cloud.gateway.server.webflux.routes | | List of Routes.
|
||||
|spring.cloud.gateway.server.webflux.set-status.original-status-header-name | | The name of the header which contains http code of the proxied request.
|
||||
|spring.cloud.gateway.server.webflux.streaming-media-types | |
|
||||
|spring.cloud.gateway.server.webflux.x-forwarded.enabled | `+++true+++` | If the XForwardedHeadersFilter is enabled.
|
||||
|spring.cloud.gateway.server.webflux.x-forwarded.for-append | `+++true+++` | If appending X-Forwarded-For as a list is enabled.
|
||||
|spring.cloud.gateway.server.webflux.x-forwarded.for-enabled | `+++true+++` | If X-Forwarded-For is enabled.
|
||||
|spring.cloud.gateway.server.webflux.x-forwarded.host-append | `+++true+++` | If appending X-Forwarded-Host as a list is enabled.
|
||||
|spring.cloud.gateway.server.webflux.x-forwarded.host-enabled | `+++true+++` | If X-Forwarded-Host is enabled.
|
||||
|spring.cloud.gateway.server.webflux.x-forwarded.order | `+++0+++` | The order of the XForwardedHeadersFilter.
|
||||
|spring.cloud.gateway.server.webflux.x-forwarded.port-append | `+++true+++` | If appending X-Forwarded-Port as a list is enabled.
|
||||
|spring.cloud.gateway.server.webflux.x-forwarded.port-enabled | `+++true+++` | If X-Forwarded-Port is enabled.
|
||||
|spring.cloud.gateway.server.webflux.x-forwarded.prefix-append | `+++true+++` | If appending X-Forwarded-Prefix as a list is enabled.
|
||||
|spring.cloud.gateway.server.webflux.x-forwarded.prefix-enabled | `+++true+++` | If X-Forwarded-Prefix is enabled.
|
||||
|spring.cloud.gateway.server.webflux.x-forwarded.proto-append | `+++true+++` | If appending X-Forwarded-Proto as a list is enabled.
|
||||
|spring.cloud.gateway.server.webflux.x-forwarded.proto-enabled | `+++true+++` | If X-Forwarded-Proto is enabled.
|
||||
|spring.cloud.gateway.server.webmvc.form-filter.enabled | `+++true+++` | Enables the form-filter.
|
||||
|spring.cloud.gateway.server.webmvc.forwarded-request-headers-filter.enabled | `+++true+++` | Enables the forwarded-request-headers-filter.
|
||||
|spring.cloud.gateway.server.webmvc.http-client.connect-timeout | | The HttpClient connect timeout.
|
||||
|spring.cloud.gateway.server.webmvc.http-client.read-timeout | | The HttpClient read timeout.
|
||||
|spring.cloud.gateway.server.webmvc.http-client.ssl-bundle | | The name of the SSL bundle to use.
|
||||
|spring.cloud.gateway.server.webmvc.http-client.type | `+++jdk+++` | The HttpClient type. Defaults to JDK.
|
||||
|spring.cloud.gateway.server.webmvc.remove-content-length-request-headers-filter.enabled | `+++true+++` | Enables the remove-content-length-request-headers-filter.
|
||||
|spring.cloud.gateway.server.webmvc.remove-hop-by-hop-request-headers-filter.enabled | `+++true+++` | Enables the remove-hop-by-hop-request-headers-filter.
|
||||
|spring.cloud.gateway.server.webmvc.remove-hop-by-hop-response-headers-filter.enabled | `+++true+++` | Enables the remove-hop-by-hop-response-headers-filter.
|
||||
|spring.cloud.gateway.server.webmvc.remove-http2-status-response-headers-filter.enabled | `+++true+++` | Enables the remove-http2-status-response-headers-filter.
|
||||
|spring.cloud.gateway.server.webmvc.routes | | List of Routes.
|
||||
|spring.cloud.gateway.server.webmvc.routes-map | | Map of Routes.
|
||||
|spring.cloud.gateway.server.webmvc.streaming-buffer-size | `+++16384+++` | Buffer size for streaming media mime-types.
|
||||
|spring.cloud.gateway.server.webmvc.streaming-media-types | | Mime-types that are streaming.
|
||||
|spring.cloud.gateway.server.webmvc.transfer-encoding-normalization-request-headers-filter.enabled | `+++true+++` | Enables the transfer-encoding-normalization-request-headers-filter.
|
||||
|spring.cloud.gateway.server.webmvc.weight-calculator-filter.enabled | `+++true+++` | Enables the weight-calculator-filter.
|
||||
|spring.cloud.gateway.server.webmvc.x-forwarded-request-headers-filter.enabled | `+++true+++` | If the XForwardedHeadersFilter is enabled.
|
||||
|spring.cloud.gateway.server.webmvc.x-forwarded-request-headers-filter.for-append | `+++true+++` | If appending X-Forwarded-For as a list is enabled.
|
||||
|spring.cloud.gateway.server.webmvc.x-forwarded-request-headers-filter.for-enabled | `+++true+++` | If X-Forwarded-For is enabled.
|
||||
|spring.cloud.gateway.server.webmvc.x-forwarded-request-headers-filter.host-append | `+++true+++` | If appending X-Forwarded-Host as a list is enabled.
|
||||
|spring.cloud.gateway.server.webmvc.x-forwarded-request-headers-filter.host-enabled | `+++true+++` | If X-Forwarded-Host is enabled.
|
||||
|spring.cloud.gateway.server.webmvc.x-forwarded-request-headers-filter.order | `+++0+++` | The order of the XForwardedHeadersFilter.
|
||||
|spring.cloud.gateway.server.webmvc.x-forwarded-request-headers-filter.port-append | `+++true+++` | If appending X-Forwarded-Port as a list is enabled.
|
||||
|spring.cloud.gateway.server.webmvc.x-forwarded-request-headers-filter.port-enabled | `+++true+++` | If X-Forwarded-Port is enabled.
|
||||
|spring.cloud.gateway.server.webmvc.x-forwarded-request-headers-filter.prefix-append | `+++true+++` | If appending X-Forwarded-Prefix as a list is enabled.
|
||||
|spring.cloud.gateway.server.webmvc.x-forwarded-request-headers-filter.prefix-enabled | `+++true+++` | If X-Forwarded-Prefix is enabled.
|
||||
|spring.cloud.gateway.server.webmvc.x-forwarded-request-headers-filter.proto-append | `+++true+++` | If appending X-Forwarded-Proto as a list is enabled.
|
||||
|spring.cloud.gateway.server.webmvc.x-forwarded-request-headers-filter.proto-enabled | `+++true+++` | If X-Forwarded-Proto is enabled.
|
||||
|spring.cloud.gateway.set-status | |
|
||||
|spring.cloud.gateway.set-status.original-status-header-name | | The name of the header which contains http code of the proxied request.
|
||||
|spring.cloud.gateway.streaming-media-types | |
|
||||
|spring.cloud.gateway.x-forwarded | |
|
||||
|spring.cloud.gateway.x-forwarded.enabled | `+++true+++` | If the XForwardedHeadersFilter is enabled.
|
||||
|spring.cloud.gateway.x-forwarded.for-append | `+++true+++` | If appending X-Forwarded-For as a list is enabled.
|
||||
|spring.cloud.gateway.x-forwarded.for-enabled | `+++true+++` | If X-Forwarded-For is enabled.
|
||||
|
||||
@@ -5,6 +5,6 @@
|
||||
"@antora/collector-extension": "1.0.1",
|
||||
"@asciidoctor/tabs": "1.0.0-beta.6",
|
||||
"@springio/antora-extensions": "1.14.4",
|
||||
"@springio/asciidoctor-extensions": "1.0.0-alpha.16"
|
||||
"@springio/asciidoctor-extensions": "1.0.0-alpha.17"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
<properties>
|
||||
<protoc.version>3.25.1</protoc.version>
|
||||
<grpc.version>1.71.0</grpc.version>
|
||||
<grpc.version>1.72.0</grpc.version>
|
||||
</properties>
|
||||
|
||||
<parent>
|
||||
|
||||
@@ -20,7 +20,7 @@ management:
|
||||
|
||||
spring:
|
||||
cloud:
|
||||
gateway:
|
||||
gateway.server.webflux:
|
||||
httpserver:
|
||||
wiretap: true
|
||||
httpclient:
|
||||
|
||||
@@ -13,7 +13,7 @@ server:
|
||||
|
||||
spring:
|
||||
cloud:
|
||||
gateway:
|
||||
gateway.server.webflux:
|
||||
# httpserver:
|
||||
# wiretap: true
|
||||
httpclient:
|
||||
|
||||
@@ -9,5 +9,5 @@ server:
|
||||
|
||||
spring:
|
||||
cloud:
|
||||
gateway:
|
||||
gateway.server.webflux:
|
||||
enabled: false
|
||||
77
spring-cloud-gateway-integration-tests/httpclient/pom.xml
Normal file
77
spring-cloud-gateway-integration-tests/httpclient/pom.xml
Normal file
@@ -0,0 +1,77 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>httpclient</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>Spring Cloud Gateway HttpClient Integration Test</name>
|
||||
<description>Spring Cloud Gateway HttpClient Integration Test</description>
|
||||
|
||||
<properties>
|
||||
</properties>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-gateway-integration-tests</artifactId>
|
||||
<version>4.3.0-SNAPSHOT</version>
|
||||
<relativePath>..</relativePath> <!-- lookup parent from repository -->
|
||||
</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-webflux</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-gateway-mvc</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.retry</groupId>
|
||||
<artifactId>spring-retry</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents.client5</groupId>
|
||||
<artifactId>httpclient5</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-deploy-plugin</artifactId>
|
||||
<configuration>
|
||||
<skip>true</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* Copyright 2013-2025 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.tests.httpclient;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.apache.hc.client5.http.config.RequestConfig;
|
||||
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
|
||||
import org.apache.hc.client5.http.impl.classic.HttpClients;
|
||||
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
|
||||
import org.apache.hc.core5.util.Timeout;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.SpringBootConfiguration;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.cloud.client.DefaultServiceInstance;
|
||||
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;
|
||||
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
|
||||
import org.springframework.cloud.loadbalancer.support.ServiceInstanceListSuppliers;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.servlet.function.RouterFunction;
|
||||
import org.springframework.web.servlet.function.ServerResponse;
|
||||
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.FilterFunctions.prefixPath;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.LoadBalancerFilterFunctions.lb;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.RetryFilterFunctions.retry;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;
|
||||
|
||||
/**
|
||||
* @author jiangyuan
|
||||
*/
|
||||
@SpringBootConfiguration
|
||||
@EnableAutoConfiguration
|
||||
@LoadBalancerClient(name = "myservice", configuration = MyServiceConf.class)
|
||||
public class HttpClientApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(HttpClientApplication.class, args);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public HttpComponentsClientHttpRequestFactory httpComponentsClientHttpRequestFactory() {
|
||||
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
|
||||
connectionManager.setMaxTotal(2);
|
||||
connectionManager.setDefaultMaxPerRoute(2);
|
||||
|
||||
CloseableHttpClient httpClient = HttpClients.custom()
|
||||
.setConnectionManager(connectionManager)
|
||||
.setDefaultRequestConfig(
|
||||
RequestConfig.custom().setConnectionRequestTimeout(Timeout.of(Duration.ofMillis(3000))).build())
|
||||
.build();
|
||||
|
||||
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);
|
||||
return factory;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> gatewayRouterFunctionsRetry() {
|
||||
return route("test-retry").GET("/retry", http())
|
||||
.filter(lb("myservice"))
|
||||
.filter(prefixPath("/do"))
|
||||
.filter(retry(3))
|
||||
.build();
|
||||
}
|
||||
|
||||
@RestController
|
||||
protected static class RetryController {
|
||||
|
||||
Log log = LogFactory.getLog(getClass());
|
||||
|
||||
ConcurrentHashMap<String, AtomicInteger> map = new ConcurrentHashMap<>();
|
||||
|
||||
@GetMapping("/do/retry")
|
||||
public ResponseEntity<String> retry(@RequestParam("key") String key,
|
||||
@RequestParam(name = "count", defaultValue = "3") int count,
|
||||
@RequestParam(name = "failStatus", required = false) Integer failStatus) {
|
||||
AtomicInteger num = map.computeIfAbsent(key, s -> new AtomicInteger());
|
||||
int i = num.incrementAndGet();
|
||||
log.warn("Retry count: " + i);
|
||||
String body = String.valueOf(i);
|
||||
if (i < count) {
|
||||
HttpStatus httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;
|
||||
if (failStatus != null) {
|
||||
httpStatus = HttpStatus.resolve(failStatus);
|
||||
}
|
||||
return ResponseEntity.status(httpStatus).header("X-Retry-Count", body).body("temporarily broken");
|
||||
}
|
||||
return ResponseEntity.status(HttpStatus.OK).header("X-Retry-Count", body).body(body);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class MyServiceConf {
|
||||
|
||||
@Value("${local.server.port}")
|
||||
private int port = 0;
|
||||
|
||||
@Bean
|
||||
public ServiceInstanceListSupplier staticServiceInstanceListSupplier() {
|
||||
return ServiceInstanceListSuppliers.from("myservice",
|
||||
new DefaultServiceInstance("myservice-1", "myservice", "localhost", port, false));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
logging:
|
||||
level:
|
||||
org.springframework.cloud.gateway: TRACE
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2013-2025 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.tests.httpclient;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.web.server.LocalServerPort;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
|
||||
/**
|
||||
* @author jiangyuan
|
||||
*/
|
||||
@SpringBootTest(classes = HttpClientApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
@DirtiesContext
|
||||
public class HttpClientApplicationTests {
|
||||
|
||||
@LocalServerPort
|
||||
private int port;
|
||||
|
||||
@Test
|
||||
public void retryWorks() {
|
||||
WebTestClient client = WebTestClient.bindToServer().baseUrl("http://localhost:" + port).build();
|
||||
client.get().uri("/retry?key=get").exchange().expectStatus().isOk().expectBody(String.class).isEqualTo("3");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -48,7 +48,7 @@ public class MvcFailureAnalyzerApplicationTests {
|
||||
@Test
|
||||
public void exceptionNotThrownWhenDisabled(CapturedOutput output) {
|
||||
assertThatCode(() -> new SpringApplication(MvcFailureAnalyzerApplication.class)
|
||||
.run("--spring.cloud.gateway.enabled=false", "--server.port=0")).doesNotThrowAnyException();
|
||||
.run("--spring.cloud.gateway.server.webflux.enabled=false", "--server.port=0")).doesNotThrowAnyException();
|
||||
assertThat(output).doesNotContain(MvcFoundOnClasspathFailureAnalyzer.MESSAGE,
|
||||
MvcFoundOnClasspathFailureAnalyzer.ACTION);
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
<module>grpc</module>
|
||||
<module>http2</module>
|
||||
<module>mvc-failure-analyzer</module>
|
||||
<module>httpclient</module>
|
||||
</modules>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -41,6 +41,11 @@
|
||||
<version>4.5.14</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-properties-migrator</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright 2016-2019 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.mvc.config;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.cloud.gateway.mvc.ProxyExchange;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
|
||||
/**
|
||||
* Configuration properties for the {@link ProxyExchange} argument handler in
|
||||
* <code>@RequestMapping</code> methods.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Tim Ysewyn
|
||||
* @author Joris Kuipers
|
||||
*
|
||||
*/
|
||||
@ConfigurationProperties(ProxyExchangeWebMvcProperties.PREFIX)
|
||||
public class ProxyExchangeWebMvcProperties {
|
||||
|
||||
/**
|
||||
* Properties prefix.
|
||||
*/
|
||||
public static final String PREFIX = "spring.cloud.gateway.proxy-exchange.webmvc";
|
||||
|
||||
/**
|
||||
* Contains headers that are considered sensitive by default.
|
||||
*/
|
||||
public static Set<String> DEFAULT_SENSITIVE = Set.of("cookie", "authorization");
|
||||
|
||||
/**
|
||||
* Contains headers that are skipped by default.
|
||||
*/
|
||||
public static Set<String> DEFAULT_SKIPPED = Set.of("content-length", "host");
|
||||
|
||||
/**
|
||||
* Fixed header values that will be added to all downstream requests.
|
||||
*/
|
||||
private Map<String, String> headers = new LinkedHashMap<>();
|
||||
|
||||
/**
|
||||
* A set of header names that should be sent downstream by default.
|
||||
*/
|
||||
private Set<String> autoForward = new HashSet<>();
|
||||
|
||||
/**
|
||||
* A set of sensitive header names that will not be sent downstream by default.
|
||||
*/
|
||||
private Set<String> sensitive = DEFAULT_SENSITIVE;
|
||||
|
||||
/**
|
||||
* A set of header names that will not be sent downstream because they could be
|
||||
* problematic.
|
||||
*/
|
||||
private Set<String> skipped = DEFAULT_SKIPPED;
|
||||
|
||||
public Map<String, String> getHeaders() {
|
||||
return headers;
|
||||
}
|
||||
|
||||
public void setHeaders(Map<String, String> headers) {
|
||||
this.headers = headers;
|
||||
}
|
||||
|
||||
public Set<String> getAutoForward() {
|
||||
return autoForward;
|
||||
}
|
||||
|
||||
public void setAutoForward(Set<String> autoForward) {
|
||||
this.autoForward = autoForward;
|
||||
}
|
||||
|
||||
public Set<String> getSensitive() {
|
||||
return sensitive;
|
||||
}
|
||||
|
||||
public void setSensitive(Set<String> sensitive) {
|
||||
this.sensitive = sensitive;
|
||||
}
|
||||
|
||||
public Set<String> getSkipped() {
|
||||
return skipped;
|
||||
}
|
||||
|
||||
public void setSkipped(Set<String> skipped) {
|
||||
this.skipped = skipped;
|
||||
}
|
||||
|
||||
public HttpHeaders convertHeaders() {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
for (String key : this.headers.keySet()) {
|
||||
headers.set(key, this.headers.get(key));
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.DeprecatedConfigurationProperty;
|
||||
import org.springframework.cloud.gateway.mvc.ProxyExchange;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
|
||||
@@ -32,8 +33,10 @@ import org.springframework.http.HttpHeaders;
|
||||
* @author Dave Syer
|
||||
* @author Tim Ysewyn
|
||||
* @author Joris Kuipers
|
||||
*
|
||||
* @author Spencer Gibb
|
||||
* @deprecated {@link ProxyExchangeWebMvcProperties}
|
||||
*/
|
||||
@Deprecated
|
||||
@ConfigurationProperties("spring.cloud.gateway.proxy")
|
||||
public class ProxyProperties {
|
||||
|
||||
@@ -68,6 +71,7 @@ public class ProxyProperties {
|
||||
*/
|
||||
private Set<String> skipped = DEFAULT_SKIPPED;
|
||||
|
||||
@DeprecatedConfigurationProperty(replacement = ProxyExchangeWebMvcProperties.PREFIX + ".headers", since = "4.3.0")
|
||||
public Map<String, String> getHeaders() {
|
||||
return headers;
|
||||
}
|
||||
@@ -76,6 +80,8 @@ public class ProxyProperties {
|
||||
this.headers = headers;
|
||||
}
|
||||
|
||||
@DeprecatedConfigurationProperty(replacement = ProxyExchangeWebMvcProperties.PREFIX + ".auto-forward",
|
||||
since = "4.3.0")
|
||||
public Set<String> getAutoForward() {
|
||||
return autoForward;
|
||||
}
|
||||
@@ -84,6 +90,7 @@ public class ProxyProperties {
|
||||
this.autoForward = autoForward;
|
||||
}
|
||||
|
||||
@DeprecatedConfigurationProperty(replacement = ProxyExchangeWebMvcProperties.PREFIX + ".sensitive", since = "4.3.0")
|
||||
public Set<String> getSensitive() {
|
||||
return sensitive;
|
||||
}
|
||||
@@ -92,6 +99,7 @@ public class ProxyProperties {
|
||||
this.sensitive = sensitive;
|
||||
}
|
||||
|
||||
@DeprecatedConfigurationProperty(replacement = ProxyExchangeWebMvcProperties.PREFIX + ".skipped", since = "4.3.0")
|
||||
public Set<String> getSkipped() {
|
||||
return skipped;
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnWebApplication
|
||||
@ConditionalOnClass({ HandlerMethodReturnValueHandler.class })
|
||||
@EnableConfigurationProperties(ProxyProperties.class)
|
||||
@EnableConfigurationProperties({ ProxyExchangeWebMvcProperties.class, ProxyProperties.class })
|
||||
public class ProxyResponseAutoConfiguration implements WebMvcConfigurer {
|
||||
|
||||
@Autowired
|
||||
@@ -66,7 +66,7 @@ public class ProxyResponseAutoConfiguration implements WebMvcConfigurer {
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public ProxyExchangeArgumentResolver proxyExchangeArgumentResolver(Optional<RestTemplateBuilder> optional,
|
||||
ProxyProperties proxy) {
|
||||
ProxyExchangeWebMvcProperties properties) {
|
||||
RestTemplateBuilder builder = optional.orElse(new RestTemplateBuilder());
|
||||
RestTemplate template = builder.build();
|
||||
template.setErrorHandler(new NoOpResponseErrorHandler());
|
||||
@@ -77,14 +77,14 @@ public class ProxyResponseAutoConfiguration implements WebMvcConfigurer {
|
||||
}
|
||||
});
|
||||
ProxyExchangeArgumentResolver resolver = new ProxyExchangeArgumentResolver(template);
|
||||
resolver.setHeaders(proxy.convertHeaders());
|
||||
resolver.setAutoForwardedHeaders(proxy.getAutoForward());
|
||||
resolver.setHeaders(properties.convertHeaders());
|
||||
resolver.setAutoForwardedHeaders(properties.getAutoForward());
|
||||
Set<String> excludedHeaderNames = new HashSet<>();
|
||||
if (proxy.getSensitive() != null) {
|
||||
excludedHeaderNames.addAll(proxy.getSensitive());
|
||||
if (properties.getSensitive() != null) {
|
||||
excludedHeaderNames.addAll(properties.getSensitive());
|
||||
}
|
||||
if (proxy.getSkipped() != null) {
|
||||
excludedHeaderNames.addAll(proxy.getSkipped());
|
||||
if (properties.getSkipped() != null) {
|
||||
excludedHeaderNames.addAll(properties.getSkipped());
|
||||
}
|
||||
resolver.setExcluded(excludedHeaderNames);
|
||||
return resolver;
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2013-2025 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.mvc.config;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.SpringBootConfiguration;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@SpringBootTest(properties = {}, webEnvironment = WebEnvironment.RANDOM_PORT)
|
||||
@ActiveProfiles("propertiesmigrationtests")
|
||||
public class ProxyExchangeWebmvcPropertiesMigrationTests {
|
||||
|
||||
@Autowired
|
||||
ProxyExchangeWebMvcProperties properties;
|
||||
|
||||
@Test
|
||||
public void deprecatedRoutePropertiesWork() {
|
||||
assertThat(properties.getHeaders()).hasSize(2);
|
||||
assertThat(properties.getAutoForward()).hasSize(2);
|
||||
assertThat(properties.getSensitive()).hasSize(2);
|
||||
assertThat(properties.getSkipped()).hasSize(3);
|
||||
}
|
||||
|
||||
@SpringBootConfiguration
|
||||
@EnableAutoConfiguration
|
||||
protected static class TestConfiguration {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
spring.cloud.gateway.proxy:
|
||||
headers:
|
||||
X-Foo: xfooval
|
||||
X-Bar: xbarval
|
||||
auto-forward: X-FWD1, X-FWD2
|
||||
sensitive: X-S1, X-S21
|
||||
skipped: X-SK1, X-SK2, X-SK3
|
||||
logging:
|
||||
level:
|
||||
org.springframework.cloud.gateway.server.mvc: TRACE
|
||||
@@ -0,0 +1,50 @@
|
||||
test:
|
||||
hostport: httpbin.org:80
|
||||
# hostport: localhost:5000
|
||||
uri: http://${test.hostport}
|
||||
#uri: lb://httpbin
|
||||
|
||||
|
||||
spring:
|
||||
cloud:
|
||||
gateway.server.webflux:
|
||||
filter:
|
||||
default-filters:
|
||||
#- PrefixPath=/httpbin
|
||||
#- AddResponseHeader=X-Response-Default-Foo, Default-Bar
|
||||
|
||||
routes:
|
||||
# =====================================
|
||||
# to run server
|
||||
# $ wscat --listen 9000
|
||||
# to run client
|
||||
# $ wscat --connect ws://localhost:8080/echo
|
||||
- id: websocket_test
|
||||
uri: ws://localhost:9000
|
||||
order: 9000
|
||||
predicates:
|
||||
- Path=/echo
|
||||
# =====================================
|
||||
- id: default_path_to_httpbin_secureheaders
|
||||
uri: ${test.uri}
|
||||
order: 10000
|
||||
predicates:
|
||||
- Path=/**
|
||||
filters:
|
||||
- name: SecureHeaders
|
||||
args:
|
||||
disable: x-frame-options
|
||||
enable: permissions-policy
|
||||
permissions-policy : geolocation=("https://example.net")
|
||||
|
||||
logging:
|
||||
level:
|
||||
org.springframework.cloud.gateway: TRACE
|
||||
org.springframework.http.server.reactive: DEBUG
|
||||
org.springframework.web.reactive: DEBUG
|
||||
reactor.ipc.netty: DEBUG
|
||||
reactor.netty: DEBUG
|
||||
|
||||
management.endpoints.web.exposure.include: '*'
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ spring:
|
||||
jmx:
|
||||
enabled: false
|
||||
cloud:
|
||||
gateway:
|
||||
gateway.server.webflux:
|
||||
default-filters:
|
||||
- PrefixPath=/httpbin
|
||||
- AddResponseHeader=X-Response-Default-Foo, Default-Bar
|
||||
|
||||
@@ -107,6 +107,16 @@
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>io.projectreactor.netty</groupId>
|
||||
<artifactId>reactor-netty</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!-- Third party test dependencies -->
|
||||
@@ -136,4 +146,30 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>github_actions</id>
|
||||
<!-- TODO: github actions fails with restricted header host -->
|
||||
<activation>
|
||||
<property>
|
||||
<name>env.GITHUB_ACTIONS</name>
|
||||
<value>true</value>
|
||||
</property>
|
||||
</activation>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<systemPropertyVariables>
|
||||
<jdk.httpclient.allowRestrictedHeaders>host</jdk.httpclient.allowRestrictedHeaders>
|
||||
</systemPropertyVariables>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
</profiles>
|
||||
</project>
|
||||
|
||||
@@ -22,11 +22,12 @@ import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.cloud.gateway.server.mvc.config.GatewayMvcProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@AutoConfigureBefore(GatewayServerMvcAutoConfiguration.class)
|
||||
@ConditionalOnProperty(name = "spring.cloud.gateway.mvc.enabled", matchIfMissing = true)
|
||||
@ConditionalOnProperty(name = GatewayMvcProperties.PREFIX + ".enabled", matchIfMissing = true)
|
||||
public class GatewayMvcClassPathWarningAutoConfiguration {
|
||||
|
||||
private static final Log log = LogFactory.getLog(GatewayMvcClassPathWarningAutoConfiguration.class);
|
||||
|
||||
@@ -18,23 +18,26 @@ package org.springframework.cloud.gateway.server.mvc;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.autoconfigure.http.client.AbstractHttpRequestFactoryProperties.Factory;
|
||||
import org.springframework.boot.autoconfigure.http.client.HttpClientAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.http.client.HttpClientProperties.Factory;
|
||||
import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration;
|
||||
import org.springframework.boot.env.EnvironmentPostProcessor;
|
||||
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings.Redirects;
|
||||
import org.springframework.boot.http.client.HttpRedirects;
|
||||
import org.springframework.boot.web.client.RestClientCustomizer;
|
||||
import org.springframework.cloud.gateway.server.mvc.common.ArgumentSupplierBeanPostProcessor;
|
||||
import org.springframework.cloud.gateway.server.mvc.config.GatewayMvcAotRuntimeHintsRegistrar;
|
||||
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.config.RouterFunctionHolderFactory;
|
||||
import org.springframework.cloud.gateway.server.mvc.filter.FilterAutoConfiguration;
|
||||
import org.springframework.cloud.gateway.server.mvc.filter.FilterBeanFactoryDiscoverer;
|
||||
import org.springframework.cloud.gateway.server.mvc.filter.FormFilter;
|
||||
import org.springframework.cloud.gateway.server.mvc.filter.ForwardedRequestHeadersFilter;
|
||||
import org.springframework.cloud.gateway.server.mvc.filter.HttpHeadersFilter.RequestHttpHeadersFilter;
|
||||
@@ -47,9 +50,12 @@ import org.springframework.cloud.gateway.server.mvc.filter.TransferEncodingNorma
|
||||
import org.springframework.cloud.gateway.server.mvc.filter.WeightCalculatorFilter;
|
||||
import org.springframework.cloud.gateway.server.mvc.filter.XForwardedRequestHeadersFilter;
|
||||
import org.springframework.cloud.gateway.server.mvc.filter.XForwardedRequestHeadersFilterProperties;
|
||||
import org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctionAutoConfiguration;
|
||||
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.PredicateAutoConfiguration;
|
||||
import org.springframework.cloud.gateway.server.mvc.predicate.PredicateBeanFactoryDiscoverer;
|
||||
import org.springframework.cloud.gateway.server.mvc.predicate.PredicateDiscoverer;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@@ -70,8 +76,9 @@ import org.springframework.web.client.RestClient;
|
||||
* @author Jürgen Wißkirchen
|
||||
*/
|
||||
@AutoConfiguration(after = { HttpClientAutoConfiguration.class, RestTemplateAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class })
|
||||
@ConditionalOnProperty(name = "spring.cloud.gateway.mvc.enabled", matchIfMissing = true)
|
||||
RestClientAutoConfiguration.class, FilterAutoConfiguration.class, HandlerFunctionAutoConfiguration.class,
|
||||
PredicateAutoConfiguration.class })
|
||||
@ConditionalOnProperty(name = GatewayMvcProperties.PREFIX + ".enabled", matchIfMissing = true)
|
||||
@Import(GatewayMvcPropertiesBeanDefinitionRegistrar.class)
|
||||
@ImportRuntimeHints(GatewayMvcAotRuntimeHintsRegistrar.class)
|
||||
public class GatewayServerMvcAutoConfiguration {
|
||||
@@ -83,8 +90,11 @@ public class GatewayServerMvcAutoConfiguration {
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RouterFunctionHolderFactory routerFunctionHolderFactory(Environment env) {
|
||||
return new RouterFunctionHolderFactory(env);
|
||||
public RouterFunctionHolderFactory routerFunctionHolderFactory(Environment env, BeanFactory beanFactory,
|
||||
FilterBeanFactoryDiscoverer filterBeanFactoryDiscoverer,
|
||||
PredicateBeanFactoryDiscoverer predicateBeanFactoryDiscoverer) {
|
||||
return new RouterFunctionHolderFactory(env, beanFactory, filterBeanFactoryDiscoverer,
|
||||
predicateBeanFactoryDiscoverer);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -206,17 +216,19 @@ public class GatewayServerMvcAutoConfiguration {
|
||||
static final boolean REACTOR_NETTY = ClassUtils.isPresent("reactor.netty.http.client.HttpClient", null);
|
||||
static final boolean JDK = ClassUtils.isPresent("java.net.http.HttpClient", null);
|
||||
static final boolean HIGHER_PRIORITY = APACHE || JETTY || REACTOR_NETTY;
|
||||
static final String SPRING_REDIRECTS_PROPERTY = "spring.http.client.redirects";
|
||||
static final String SPRING_HTTP_FACTORY_PROPERTY = "spring.http.client.factory";
|
||||
|
||||
@Override
|
||||
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
|
||||
Redirects redirects = environment.getProperty("spring.http.client.redirects", Redirects.class);
|
||||
HttpRedirects redirects = environment.getProperty(SPRING_REDIRECTS_PROPERTY, HttpRedirects.class);
|
||||
if (redirects == null) {
|
||||
// the user hasn't set anything, change the default
|
||||
environment.getPropertySources()
|
||||
.addFirst(new MapPropertySource("gatewayHttpClientProperties",
|
||||
Map.of("spring.http.client.redirects", Redirects.DONT_FOLLOW)));
|
||||
Map.of(SPRING_REDIRECTS_PROPERTY, HttpRedirects.DONT_FOLLOW)));
|
||||
}
|
||||
Factory factory = environment.getProperty("spring.http.client.factory", Factory.class);
|
||||
Factory factory = environment.getProperty(SPRING_HTTP_FACTORY_PROPERTY, Factory.class);
|
||||
boolean setJdkHttpClientProperties = false;
|
||||
|
||||
if (factory == null && !HIGHER_PRIORITY) {
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2013-2025 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.util.List;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
|
||||
public abstract class BeanFactoryGatewayDiscoverer extends AbstractGatewayDiscoverer {
|
||||
|
||||
protected final BeanFactory beanFactory;
|
||||
|
||||
protected BeanFactoryGatewayDiscoverer(BeanFactory beanFactory) {
|
||||
this.beanFactory = beanFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected <T> List<T> loadSuppliers(Class<T> supplierClass) {
|
||||
ObjectProvider<T> beanProvider = beanFactory.getBeanProvider(supplierClass);
|
||||
return beanProvider.orderedStream().toList();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2023 the original author or authors.
|
||||
* Copyright 2013-2025 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.
|
||||
@@ -21,6 +21,7 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
@@ -40,11 +41,14 @@ import org.springframework.http.HttpInputMessage;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.StreamUtils;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
import org.springframework.web.servlet.function.ServerRequest;
|
||||
import org.springframework.web.servlet.support.RequestContextUtils;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
import org.springframework.web.util.UriUtils;
|
||||
|
||||
import static org.springframework.web.servlet.function.RouterFunctions.URI_TEMPLATE_VARIABLES_ATTRIBUTE;
|
||||
|
||||
@@ -63,6 +67,11 @@ public abstract class MvcUtils {
|
||||
*/
|
||||
public static final String CLIENT_RESPONSE_INPUT_STREAM_ATTR = qualify("cachedClientResponseBody");
|
||||
|
||||
/**
|
||||
* Client response key.
|
||||
*/
|
||||
public static final String CLIENT_RESPONSE_ATTR = qualify("cachedClientResponse");
|
||||
|
||||
/**
|
||||
* CircuitBreaker execution exception attribute name.
|
||||
*/
|
||||
@@ -263,6 +272,17 @@ public abstract class MvcUtils {
|
||||
urls.add(url);
|
||||
}
|
||||
|
||||
public static MultiValueMap<String, String> encodeQueryParams(MultiValueMap<String, String> params) {
|
||||
MultiValueMap<String, String> encodedQueryParams = new LinkedMultiValueMap<>(params.size());
|
||||
for (Map.Entry<String, List<String>> entry : params.entrySet()) {
|
||||
for (String value : entry.getValue()) {
|
||||
encodedQueryParams.add(UriUtils.encode(entry.getKey(), StandardCharsets.UTF_8),
|
||||
UriUtils.encode(value, StandardCharsets.UTF_8));
|
||||
}
|
||||
}
|
||||
return CollectionUtils.unmodifiableMultiValueMap(encodedQueryParams);
|
||||
}
|
||||
|
||||
private record ByteArrayInputMessage(ServerRequest request, ByteArrayInputStream body) implements HttpInputMessage {
|
||||
|
||||
@Override
|
||||
|
||||
@@ -29,9 +29,9 @@ import org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions
|
||||
import org.springframework.cloud.gateway.server.mvc.filter.BodyFilterFunctions;
|
||||
import org.springframework.cloud.gateway.server.mvc.filter.Bucket4jFilterFunctions;
|
||||
import org.springframework.cloud.gateway.server.mvc.filter.CircuitBreakerFilterFunctions;
|
||||
import org.springframework.cloud.gateway.server.mvc.filter.FilterAutoConfiguration;
|
||||
import org.springframework.cloud.gateway.server.mvc.filter.FilterFunctions;
|
||||
import org.springframework.cloud.gateway.server.mvc.filter.LoadBalancerFilterFunctions;
|
||||
import org.springframework.cloud.gateway.server.mvc.filter.LoadBalancerHandlerSupplier;
|
||||
import org.springframework.cloud.gateway.server.mvc.filter.TokenRelayFilterFunctions;
|
||||
import org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions;
|
||||
import org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions;
|
||||
@@ -46,11 +46,12 @@ import org.springframework.util.ClassUtils;
|
||||
*/
|
||||
public class GatewayMvcAotRuntimeHintsRegistrar implements RuntimeHintsRegistrar {
|
||||
|
||||
// TODO: fix AOT HINTS
|
||||
private static final Set<Class<?>> FUNCTION_PROVIDERS = Set.of(HandlerFunctions.class,
|
||||
LoadBalancerHandlerSupplier.class, FilterFunctions.class, BeforeFilterFunctions.class,
|
||||
AfterFilterFunctions.class, TokenRelayFilterFunctions.class, BodyFilterFunctions.class,
|
||||
CircuitBreakerFilterFunctions.class, GatewayRouterFunctions.class, LoadBalancerFilterFunctions.class,
|
||||
GatewayRequestPredicates.class, Bucket4jFilterFunctions.class);
|
||||
FilterAutoConfiguration.LoadBalancerHandlerConfiguration.class, FilterFunctions.class,
|
||||
BeforeFilterFunctions.class, AfterFilterFunctions.class, TokenRelayFilterFunctions.class,
|
||||
BodyFilterFunctions.class, CircuitBreakerFilterFunctions.class, GatewayRouterFunctions.class,
|
||||
LoadBalancerFilterFunctions.class, GatewayRequestPredicates.class, Bucket4jFilterFunctions.class);
|
||||
|
||||
private static final Set<Class<?>> PROPERTIES = Set.of(FilterProperties.class, PredicateProperties.class,
|
||||
RouteProperties.class);
|
||||
|
||||
@@ -36,7 +36,7 @@ public class GatewayMvcProperties {
|
||||
/**
|
||||
* Properties prefix.
|
||||
*/
|
||||
public static final String PREFIX = "spring.cloud.gateway.mvc";
|
||||
public static final String PREFIX = "spring.cloud.gateway.server.webmvc";
|
||||
|
||||
/**
|
||||
* List of Routes.
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
/*
|
||||
* Copyright 2013-2025 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.config;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.boot.context.event.ApplicationFailedEvent;
|
||||
import org.springframework.boot.context.event.ApplicationPreparedEvent;
|
||||
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||
import org.springframework.boot.context.event.SpringApplicationEvent;
|
||||
import org.springframework.boot.context.properties.source.ConfigurationProperty;
|
||||
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
|
||||
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
|
||||
import org.springframework.boot.context.properties.source.ConfigurationPropertySources;
|
||||
import org.springframework.boot.context.properties.source.IterableConfigurationPropertySource;
|
||||
import org.springframework.boot.env.OriginTrackedMapPropertySource;
|
||||
import org.springframework.boot.origin.Origin;
|
||||
import org.springframework.boot.origin.OriginTrackedValue;
|
||||
import org.springframework.boot.origin.PropertySourceOrigin;
|
||||
import org.springframework.boot.origin.TextResourceOrigin;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.PropertySource;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
|
||||
class GatewayServerWebMvcPropertiesMigrationListener implements ApplicationListener<SpringApplicationEvent> {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(GatewayServerWebMvcPropertiesMigrationListener.class);
|
||||
|
||||
private static final String PROPERTIES_MIGRATOR_CLASS = "org.springframework.boot.context.properties.migrator.PropertiesMigrationListener";
|
||||
|
||||
private static final String DEPRECATED_ROOT = "spring.cloud.gateway.mvc";
|
||||
|
||||
private static final String DEPRECATED_ROUTES_LIST_KEY = DEPRECATED_ROOT + ".routes";
|
||||
|
||||
private static final String DEPRECATED_ROUTES_MAP_KEY = DEPRECATED_ROOT + ".routes-map";
|
||||
|
||||
private static final String DEPRECATED_ROUTESMAP_KEY = DEPRECATED_ROOT + ".routesMap";
|
||||
|
||||
private static final String GATEWAY_PROPERTY_SOURCE_PREFIX = "migrategatewaymvc";
|
||||
|
||||
private static final String NEW_ROUTES_LIST_KEY = GatewayMvcProperties.PREFIX + ".routes";
|
||||
|
||||
private static final String NEW_ROUTES_MAP_KEY = GatewayMvcProperties.PREFIX + ".routes-map";
|
||||
|
||||
private final List<Migration> routesMigrations = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(SpringApplicationEvent event) {
|
||||
// only run if spring-boot-properties-migrator is on the classpath
|
||||
if (!ClassUtils.isPresent(PROPERTIES_MIGRATOR_CLASS, null)) {
|
||||
return;
|
||||
}
|
||||
if (event instanceof ApplicationPreparedEvent preparedEvent) {
|
||||
onApplicationPreparedEvent(preparedEvent);
|
||||
}
|
||||
if (event instanceof ApplicationReadyEvent || event instanceof ApplicationFailedEvent) {
|
||||
logLegacyPropertiesReport();
|
||||
}
|
||||
}
|
||||
|
||||
private void onApplicationPreparedEvent(ApplicationPreparedEvent event) {
|
||||
// find deprecated keys
|
||||
ConfigurableEnvironment env = event.getApplicationContext().getEnvironment();
|
||||
|
||||
ConfigurationPropertySources.get(env).forEach(propertySource -> {
|
||||
routesMigrations.addAll(migrate(env, propertySource, GATEWAY_PROPERTY_SOURCE_PREFIX + "routes-",
|
||||
DEPRECATED_ROUTES_LIST_KEY, NEW_ROUTES_LIST_KEY));
|
||||
routesMigrations.addAll(migrate(env, propertySource, GATEWAY_PROPERTY_SOURCE_PREFIX + "routes-map-",
|
||||
DEPRECATED_ROUTES_MAP_KEY, NEW_ROUTES_MAP_KEY));
|
||||
routesMigrations.addAll(migrate(env, propertySource, GATEWAY_PROPERTY_SOURCE_PREFIX + "routesMap-",
|
||||
DEPRECATED_ROUTES_MAP_KEY, NEW_ROUTES_MAP_KEY));
|
||||
});
|
||||
}
|
||||
|
||||
private List<Migration> migrate(ConfigurableEnvironment env, ConfigurationPropertySource propertySource,
|
||||
String propertySourcePrefix, String deprecatedKey, String newKeyPrefix) {
|
||||
List<Migration> migrations = new ArrayList<>();
|
||||
|
||||
if (propertySource instanceof IterableConfigurationPropertySource iterableSource) {
|
||||
ConfigurationPropertyName routesParentName = ConfigurationPropertyName.of(deprecatedKey);
|
||||
List<ConfigurationPropertyName> matchingConfigProps = iterableSource.filter(n -> {
|
||||
if (n.getNumberOfElements() < routesParentName.getNumberOfElements()) {
|
||||
return false;
|
||||
}
|
||||
ConfigurationPropertyName chop = n.chop(routesParentName.getNumberOfElements());
|
||||
return routesParentName.equals(chop);
|
||||
}).stream().toList();
|
||||
if (!matchingConfigProps.isEmpty()) {
|
||||
String originalPropertySourceName;
|
||||
if (propertySource.getUnderlyingSource() instanceof PropertySource<?> underlyingSource) {
|
||||
originalPropertySourceName = underlyingSource.getName();
|
||||
}
|
||||
else {
|
||||
originalPropertySourceName = propertySource.getUnderlyingSource().toString();
|
||||
}
|
||||
String newPropertySourceName = propertySourcePrefix + originalPropertySourceName;
|
||||
Map<String, OriginTrackedValue> content = new LinkedHashMap<>();
|
||||
// migrate to new keys
|
||||
for (ConfigurationPropertyName originalPropertyName : matchingConfigProps) {
|
||||
ConfigurationPropertyName suffix = originalPropertyName
|
||||
.subName(routesParentName.getNumberOfElements());
|
||||
ConfigurationPropertyName newProperty = ConfigurationPropertyName.of(newKeyPrefix).append(suffix);
|
||||
ConfigurationProperty configurationProperty = propertySource
|
||||
.getConfigurationProperty(originalPropertyName);
|
||||
Object value = configurationProperty.getValue();
|
||||
OriginTrackedValue originTrackedValue = OriginTrackedValue.of(value,
|
||||
configurationProperty.getOrigin());
|
||||
content.put(newProperty.toString(), originTrackedValue);
|
||||
migrations.add(new Migration(originalPropertySourceName, originalPropertyName,
|
||||
configurationProperty, newProperty));
|
||||
}
|
||||
env.getPropertySources()
|
||||
.addBefore(originalPropertySourceName,
|
||||
new OriginTrackedMapPropertySource(newPropertySourceName, content));
|
||||
}
|
||||
}
|
||||
return migrations;
|
||||
}
|
||||
|
||||
private void logLegacyPropertiesReport() {
|
||||
// log warnings
|
||||
if (!routesMigrations.isEmpty()) {
|
||||
LinkedMultiValueMap<String, Migration> content = new LinkedMultiValueMap<>();
|
||||
routesMigrations.forEach(migration -> content.add(migration.originalPropertySourceName(), migration));
|
||||
|
||||
StringBuilder report = new StringBuilder();
|
||||
report.append(String
|
||||
.format("%nThe use of configuration keys that have been renamed was found in the environment:%n%n"));
|
||||
|
||||
content.forEach((name, properties) -> {
|
||||
report.append(String.format("Property source '%s':%n", name));
|
||||
// properties.sort(PropertyMigration.COMPARATOR);
|
||||
properties.forEach((property) -> {
|
||||
ConfigurationPropertyName originalPropertyName = property.originalPropertyName();
|
||||
report.append(String.format("\tKey: %s%n", originalPropertyName));
|
||||
Integer lineNumber = property.determineLineNumber();
|
||||
if (lineNumber != null) {
|
||||
report.append(String.format("\t\tLine: %d%n", lineNumber));
|
||||
}
|
||||
report.append(String.format("\t\tReplacement: %s%n", property.newProperty().toString()));
|
||||
});
|
||||
report.append(String.format("%n"));
|
||||
});
|
||||
|
||||
report.append(String.format("%n"));
|
||||
report.append("Each configuration key has been temporarily mapped to its "
|
||||
+ "replacement for your convenience. To silence this warning, please "
|
||||
+ "update your configuration to use the new keys.");
|
||||
report.append(String.format("%n"));
|
||||
logger.warn(report.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private record Migration(String originalPropertySourceName, ConfigurationPropertyName originalPropertyName,
|
||||
ConfigurationProperty originalProperty, ConfigurationPropertyName newProperty) {
|
||||
|
||||
private Integer determineLineNumber() {
|
||||
Origin origin = originalProperty.getOrigin();
|
||||
if (origin instanceof PropertySourceOrigin propertySourceOrigin) {
|
||||
origin = propertySourceOrigin.getOrigin();
|
||||
}
|
||||
if (origin instanceof TextResourceOrigin textOrigin) {
|
||||
if (textOrigin.getLocation() != null) {
|
||||
return textOrigin.getLocation().getLine() + 1;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -28,10 +28,15 @@ import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanNotOfRequiredTypeException;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||
import org.springframework.boot.context.properties.bind.Bindable;
|
||||
import org.springframework.boot.context.properties.bind.Binder;
|
||||
import org.springframework.boot.context.properties.bind.handler.IgnoreTopLevelConverterNotFoundBindHandler;
|
||||
@@ -39,8 +44,10 @@ import org.springframework.boot.context.properties.source.ConfigurationPropertyS
|
||||
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
|
||||
import org.springframework.cloud.gateway.server.mvc.common.Configurable;
|
||||
import org.springframework.cloud.gateway.server.mvc.common.MvcUtils;
|
||||
import org.springframework.cloud.gateway.server.mvc.filter.FilterBeanFactoryDiscoverer;
|
||||
import org.springframework.cloud.gateway.server.mvc.filter.FilterDiscoverer;
|
||||
import org.springframework.cloud.gateway.server.mvc.handler.HandlerDiscoverer;
|
||||
import org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctionDefinition;
|
||||
import org.springframework.cloud.gateway.server.mvc.invoke.InvocationContext;
|
||||
import org.springframework.cloud.gateway.server.mvc.invoke.OperationArgumentResolver;
|
||||
import org.springframework.cloud.gateway.server.mvc.invoke.OperationParameter;
|
||||
@@ -49,10 +56,13 @@ import org.springframework.cloud.gateway.server.mvc.invoke.ParameterValueMapper;
|
||||
import org.springframework.cloud.gateway.server.mvc.invoke.convert.ConversionServiceParameterValueMapper;
|
||||
import org.springframework.cloud.gateway.server.mvc.invoke.reflect.OperationMethod;
|
||||
import org.springframework.cloud.gateway.server.mvc.invoke.reflect.ReflectiveOperationInvoker;
|
||||
import org.springframework.cloud.gateway.server.mvc.predicate.PredicateBeanFactoryDiscoverer;
|
||||
import org.springframework.cloud.gateway.server.mvc.predicate.PredicateDiscoverer;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.log.LogMessage;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.servlet.function.HandlerFilterFunction;
|
||||
@@ -102,8 +112,37 @@ public class RouterFunctionHolderFactory {
|
||||
|
||||
private final ParameterValueMapper parameterValueMapper = new ConversionServiceParameterValueMapper();
|
||||
|
||||
private final BeanFactory beanFactory;
|
||||
|
||||
private final FilterBeanFactoryDiscoverer filterBeanFactoryDiscoverer;
|
||||
|
||||
private final PredicateBeanFactoryDiscoverer predicateBeanFactoryDiscoverer;
|
||||
|
||||
private final ConversionService conversionService;
|
||||
|
||||
@Deprecated
|
||||
public RouterFunctionHolderFactory(Environment env) {
|
||||
this(env, null, null, null);
|
||||
}
|
||||
|
||||
public RouterFunctionHolderFactory(Environment env, BeanFactory beanFactory,
|
||||
FilterBeanFactoryDiscoverer filterBeanFactoryDiscoverer,
|
||||
PredicateBeanFactoryDiscoverer predicateBeanFactoryDiscoverer) {
|
||||
this.env = env;
|
||||
this.beanFactory = beanFactory;
|
||||
this.filterBeanFactoryDiscoverer = filterBeanFactoryDiscoverer;
|
||||
this.predicateBeanFactoryDiscoverer = predicateBeanFactoryDiscoverer;
|
||||
if (beanFactory instanceof ConfigurableBeanFactory configurableBeanFactory) {
|
||||
if (configurableBeanFactory.getConversionService() != null) {
|
||||
this.conversionService = configurableBeanFactory.getConversionService();
|
||||
}
|
||||
else {
|
||||
this.conversionService = DefaultConversionService.getSharedInstance();
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.conversionService = DefaultConversionService.getSharedInstance();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -153,41 +192,65 @@ public class RouterFunctionHolderFactory {
|
||||
// TODO: cache?
|
||||
// translate handlerFunction
|
||||
String scheme = routeProperties.getUri().getScheme();
|
||||
Map<String, Object> handlerArgs = new HashMap<>();
|
||||
Optional<NormalizedOperationMethod> handlerOperationMethod = findOperation(handlerOperations,
|
||||
scheme.toLowerCase(Locale.ROOT), handlerArgs);
|
||||
if (handlerOperationMethod.isEmpty()) {
|
||||
// single RouteProperties param
|
||||
handlerArgs.clear();
|
||||
String routePropsKey = StringUtils.uncapitalize(RouteProperties.class.getSimpleName());
|
||||
handlerArgs.put(routePropsKey, routeProperties);
|
||||
handlerOperationMethod = findOperation(handlerOperations, scheme.toLowerCase(Locale.ROOT), handlerArgs);
|
||||
if (handlerOperationMethod.isEmpty()) {
|
||||
throw new IllegalStateException("Unable to find HandlerFunction for scheme: " + scheme);
|
||||
}
|
||||
}
|
||||
NormalizedOperationMethod normalizedOpMethod = handlerOperationMethod.get();
|
||||
Object response = invokeOperation(normalizedOpMethod, normalizedOpMethod.getNormalizedArgs());
|
||||
HandlerFunction<ServerResponse> handlerFunction = null;
|
||||
|
||||
// filters added by HandlerDiscoverer need to go last, so save them
|
||||
HandlerFunction<ServerResponse> handlerFunction = null;
|
||||
List<HandlerFilterFunction<ServerResponse, ServerResponse>> lowerPrecedenceFilters = new ArrayList<>();
|
||||
List<HandlerFilterFunction<ServerResponse, ServerResponse>> higherPrecedenceFilters = new ArrayList<>();
|
||||
if (response instanceof HandlerFunction<?>) {
|
||||
handlerFunction = (HandlerFunction<ServerResponse>) response;
|
||||
}
|
||||
else if (response instanceof HandlerDiscoverer.Result result) {
|
||||
handlerFunction = result.getHandlerFunction();
|
||||
lowerPrecedenceFilters.addAll(result.getLowerPrecedenceFilters());
|
||||
higherPrecedenceFilters.addAll(result.getHigherPrecedenceFilters());
|
||||
|
||||
if (beanFactory != null) {
|
||||
try {
|
||||
// TODO: configurable bean name?
|
||||
String name = scheme + "HandlerFunctionDefinition";
|
||||
Function factory = beanFactory.getBean(name, Function.class);
|
||||
HandlerFunctionDefinition definition = (HandlerFunctionDefinition) factory.apply(routeProperties);
|
||||
handlerFunction = definition.handlerFunction();
|
||||
lowerPrecedenceFilters.addAll(definition.lowerPrecedenceFilters());
|
||||
higherPrecedenceFilters.addAll(definition.higherPrecedenceFilters());
|
||||
}
|
||||
catch (NoSuchBeanDefinitionException | BeanNotOfRequiredTypeException | ClassCastException e) {
|
||||
log.trace(LogMessage.format("Unable to locate bean of HandlerFunction for scheme %s", scheme), e);
|
||||
}
|
||||
}
|
||||
|
||||
if (handlerFunction == null) {
|
||||
throw new IllegalStateException(
|
||||
"Unable to find HandlerFunction for scheme: " + scheme + " and response " + response);
|
||||
Map<String, Object> handlerArgs = new HashMap<>();
|
||||
Optional<NormalizedOperationMethod> handlerOperationMethod = findOperation(handlerOperations,
|
||||
scheme.toLowerCase(Locale.ROOT), handlerArgs);
|
||||
if (handlerOperationMethod.isEmpty()) {
|
||||
// single RouteProperties param
|
||||
handlerArgs.clear();
|
||||
String routePropsKey = StringUtils.uncapitalize(RouteProperties.class.getSimpleName());
|
||||
handlerArgs.put(routePropsKey, routeProperties);
|
||||
handlerOperationMethod = findOperation(handlerOperations, scheme.toLowerCase(Locale.ROOT), handlerArgs);
|
||||
if (handlerOperationMethod.isEmpty()) {
|
||||
throw new IllegalStateException("Unable to find HandlerFunction for scheme: " + scheme);
|
||||
}
|
||||
}
|
||||
|
||||
NormalizedOperationMethod normalizedOpMethod = handlerOperationMethod.get();
|
||||
Object response = invokeOperation(normalizedOpMethod, normalizedOpMethod.getNormalizedArgs());
|
||||
|
||||
if (response instanceof HandlerFunction<?>) {
|
||||
handlerFunction = (HandlerFunction<ServerResponse>) response;
|
||||
}
|
||||
else if (response instanceof HandlerDiscoverer.Result result) {
|
||||
handlerFunction = result.getHandlerFunction();
|
||||
lowerPrecedenceFilters.addAll(result.getLowerPrecedenceFilters());
|
||||
higherPrecedenceFilters.addAll(result.getHigherPrecedenceFilters());
|
||||
}
|
||||
if (handlerFunction == null) {
|
||||
throw new IllegalStateException(
|
||||
"Unable to find HandlerFunction for scheme: " + scheme + " and response " + response);
|
||||
}
|
||||
}
|
||||
|
||||
// translate predicates
|
||||
MultiValueMap<String, OperationMethod> predicateOperations = predicateDiscoverer.getOperations();
|
||||
MultiValueMap<String, OperationMethod> predicateOperations = new LinkedMultiValueMap<>();
|
||||
if (predicateBeanFactoryDiscoverer != null) {
|
||||
predicateOperations.addAll(predicateBeanFactoryDiscoverer.getOperations());
|
||||
}
|
||||
predicateOperations.addAll(predicateDiscoverer.getOperations());
|
||||
final AtomicReference<RequestPredicate> predicate = new AtomicReference<>();
|
||||
|
||||
routeProperties.getPredicates().forEach(predicateProperties -> {
|
||||
@@ -214,7 +277,11 @@ public class RouterFunctionHolderFactory {
|
||||
lowerPrecedenceFilters.forEach(builder::filter);
|
||||
|
||||
// translate filters
|
||||
MultiValueMap<String, OperationMethod> filterOperations = filterDiscoverer.getOperations();
|
||||
MultiValueMap<String, OperationMethod> filterOperations = new LinkedMultiValueMap<>();
|
||||
if (filterBeanFactoryDiscoverer != null) {
|
||||
filterOperations.addAll(filterBeanFactoryDiscoverer.getOperations());
|
||||
}
|
||||
filterOperations.addAll(filterDiscoverer.getOperations());
|
||||
routeProperties.getFilters().forEach(filterProperties -> {
|
||||
Map<String, Object> args = new LinkedHashMap<>(filterProperties.getArgs());
|
||||
translate(filterOperations, filterProperties.getName(), args, HandlerFilterFunction.class, builder::filter);
|
||||
@@ -295,7 +362,7 @@ public class RouterFunctionHolderFactory {
|
||||
return operationInvoker.invoke(context);
|
||||
}
|
||||
|
||||
private static Object bindConfigurable(OperationMethod operationMethod, Map<String, Object> args,
|
||||
private Object bindConfigurable(OperationMethod operationMethod, Map<String, Object> args,
|
||||
OperationParameter operationParameter) {
|
||||
Class<?> configurableType = operationParameter.getType();
|
||||
Configurable configurable = operationMethod.getMethod().getAnnotation(Configurable.class);
|
||||
@@ -305,8 +372,8 @@ public class RouterFunctionHolderFactory {
|
||||
Bindable<?> bindable = Bindable.of(configurableType);
|
||||
List<ConfigurationPropertySource> propertySources = Collections
|
||||
.singletonList(new MapConfigurationPropertySource(args));
|
||||
// TODO: potentially deal with conversion service
|
||||
Binder binder = new Binder(propertySources, null, DefaultConversionService.getSharedInstance());
|
||||
|
||||
Binder binder = new Binder(propertySources, null, conversionService);
|
||||
Object config = binder.bindOrCreate("", bindable, new IgnoreTopLevelConverterNotFoundBindHandler());
|
||||
return config;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2023 the original author or authors.
|
||||
* Copyright 2013-2025 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.
|
||||
@@ -24,6 +24,11 @@ import java.util.function.BiFunction;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
import org.springframework.cloud.gateway.server.mvc.common.HttpStatusHolder;
|
||||
import org.springframework.cloud.gateway.server.mvc.common.MvcUtils;
|
||||
import org.springframework.cloud.gateway.server.mvc.handler.GatewayServerResponse;
|
||||
@@ -34,8 +39,16 @@ import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.servlet.function.ServerRequest;
|
||||
import org.springframework.web.servlet.function.ServerResponse;
|
||||
|
||||
import static org.springframework.http.MediaType.APPLICATION_JSON;
|
||||
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
|
||||
|
||||
/**
|
||||
* @author raccoonback
|
||||
*/
|
||||
public abstract class AfterFilterFunctions {
|
||||
|
||||
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
||||
|
||||
private AfterFilterFunctions() {
|
||||
}
|
||||
|
||||
@@ -160,6 +173,38 @@ public abstract class AfterFilterFunctions {
|
||||
};
|
||||
}
|
||||
|
||||
public static BiFunction<ServerRequest, ServerResponse, ServerResponse> removeJsonAttributesResponseBody(
|
||||
List<String> fieldList, boolean deleteRecursively) {
|
||||
List<String> immutableFieldList = List.copyOf(fieldList);
|
||||
|
||||
return modifyResponseBody(String.class, String.class, APPLICATION_JSON_VALUE, (request, response, body) -> {
|
||||
String responseBody = body;
|
||||
if (APPLICATION_JSON.isCompatibleWith(response.headers().getContentType())) {
|
||||
try {
|
||||
JsonNode jsonBodyContent = OBJECT_MAPPER.readValue(responseBody, JsonNode.class);
|
||||
|
||||
removeJsonAttributes(jsonBodyContent, immutableFieldList, deleteRecursively);
|
||||
|
||||
responseBody = OBJECT_MAPPER.writeValueAsString(jsonBodyContent);
|
||||
}
|
||||
catch (JsonProcessingException exception) {
|
||||
throw new IllegalStateException("Failed to process JSON of response body.", exception);
|
||||
}
|
||||
}
|
||||
|
||||
return responseBody;
|
||||
});
|
||||
}
|
||||
|
||||
private static void removeJsonAttributes(JsonNode jsonNode, List<String> fieldNames, boolean deleteRecursively) {
|
||||
if (jsonNode instanceof ObjectNode objectNode) {
|
||||
objectNode.remove(fieldNames);
|
||||
}
|
||||
if (deleteRecursively) {
|
||||
jsonNode.forEach(childNode -> removeJsonAttributes(childNode, fieldNames, true));
|
||||
}
|
||||
}
|
||||
|
||||
public enum DedupeStrategy {
|
||||
|
||||
/**
|
||||
|
||||
@@ -45,7 +45,6 @@ import org.springframework.web.server.ResponseStatusException;
|
||||
import org.springframework.web.servlet.function.ServerRequest;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
import org.springframework.web.util.UriTemplate;
|
||||
import org.springframework.web.util.UriUtils;
|
||||
|
||||
import static org.springframework.cloud.gateway.server.mvc.common.MvcUtils.CIRCUITBREAKER_EXECUTION_EXCEPTION_ATTR;
|
||||
import static org.springframework.util.CollectionUtils.unmodifiableMultiValueMap;
|
||||
@@ -195,7 +194,7 @@ public abstract class BeforeFilterFunctions {
|
||||
|
||||
String newPath = uri.getRawPath() + request.uri().getRawPath();
|
||||
|
||||
URI prefixedUri = UriComponentsBuilder.fromUri(request.uri()).replacePath(newPath).build().toUri();
|
||||
URI prefixedUri = UriComponentsBuilder.fromUri(request.uri()).replacePath(newPath).build(true).toUri();
|
||||
return ServerRequest.from(request).uri(prefixedUri).build();
|
||||
};
|
||||
}
|
||||
@@ -216,7 +215,7 @@ public abstract class BeforeFilterFunctions {
|
||||
MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>(request.params());
|
||||
queryParams.remove(name);
|
||||
|
||||
MultiValueMap<String, String> encodedQueryParams = UriUtils.encodeQueryParams(queryParams);
|
||||
MultiValueMap<String, String> encodedQueryParams = MvcUtils.encodeQueryParams(queryParams);
|
||||
|
||||
// remove from uri
|
||||
URI newUri = UriComponentsBuilder.fromUri(request.uri())
|
||||
@@ -351,7 +350,7 @@ public abstract class BeforeFilterFunctions {
|
||||
queryParams.add(name, replacement);
|
||||
}
|
||||
|
||||
MultiValueMap<String, String> encodedQueryParams = UriUtils.encodeQueryParams(queryParams);
|
||||
MultiValueMap<String, String> encodedQueryParams = MvcUtils.encodeQueryParams(queryParams);
|
||||
URI rewrittenUri = UriComponentsBuilder.fromUri(request.uri())
|
||||
.replaceQueryParams(unmodifiableMultiValueMap(encodedQueryParams))
|
||||
.build(true)
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright 2013-2025 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.filter;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.function.Function;
|
||||
|
||||
import io.github.bucket4j.BucketConfiguration;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.cloud.client.circuitbreaker.CircuitBreaker;
|
||||
import org.springframework.cloud.gateway.server.mvc.config.RouteProperties;
|
||||
import org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctionDefinition;
|
||||
import org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions;
|
||||
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.retry.support.RetryTemplate;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
|
||||
|
||||
@AutoConfiguration
|
||||
public class FilterAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
public FilterBeanFactoryDiscoverer filterBeanFactoryDiscoverer(BeanFactory beanFactory) {
|
||||
return new FilterBeanFactoryDiscoverer(beanFactory);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public FilterFunctions.FilterSupplier filterFunctionsSupplier() {
|
||||
return new FilterFunctions.FilterSupplier();
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(BucketConfiguration.class)
|
||||
static class Bucket4jFilterConfiguration {
|
||||
|
||||
@Bean
|
||||
public Bucket4jFilterFunctions.FilterSupplier bucket4jFilterFunctionsSupplier() {
|
||||
return new Bucket4jFilterFunctions.FilterSupplier();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(CircuitBreaker.class)
|
||||
static class CircuitBreakerFilterConfiguration {
|
||||
|
||||
@Bean
|
||||
public CircuitBreakerFilterFunctions.FilterSupplier circuitBreakerFilterFunctionsSupplier() {
|
||||
return new CircuitBreakerFilterFunctions.FilterSupplier();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(LoadBalancerClient.class)
|
||||
public static class LoadBalancerHandlerConfiguration {
|
||||
|
||||
@Bean
|
||||
public Function<RouteProperties, HandlerFunctionDefinition> lbHandlerFunctionDefinition() {
|
||||
return routeProperties -> new HandlerFunctionDefinition.Default("lb", HandlerFunctions.http(),
|
||||
Collections.emptyList(),
|
||||
Collections.singletonList(LoadBalancerFilterFunctions.lb(routeProperties.getUri().getHost())));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(RetryTemplate.class)
|
||||
static class RetryFilterConfiguration {
|
||||
|
||||
@Bean
|
||||
public RetryFilterFunctions.FilterSupplier retryFilterFunctionsSupplier() {
|
||||
return new RetryFilterFunctions.FilterSupplier();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(OAuth2AuthorizedClient.class)
|
||||
static class TokenRelayFilterConfiguration {
|
||||
|
||||
@Bean
|
||||
public TokenRelayFilterFunctions.FilterSupplier tokenRelayFilterFunctionsSupplier() {
|
||||
return new TokenRelayFilterFunctions.FilterSupplier();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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.filter;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.cloud.gateway.server.mvc.common.BeanFactoryGatewayDiscoverer;
|
||||
import org.springframework.web.servlet.function.HandlerFilterFunction;
|
||||
|
||||
public class FilterBeanFactoryDiscoverer extends BeanFactoryGatewayDiscoverer {
|
||||
|
||||
protected FilterBeanFactoryDiscoverer(BeanFactory beanFactory) {
|
||||
super(beanFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void discover() {
|
||||
doDiscover(FilterSupplier.class, HandlerFilterFunction.class);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
/*
|
||||
* 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.filter;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URI;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.springframework.cloud.gateway.server.mvc.config.RouteProperties;
|
||||
import org.springframework.cloud.gateway.server.mvc.handler.HandlerDiscoverer;
|
||||
import org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions;
|
||||
import org.springframework.cloud.gateway.server.mvc.handler.HandlerSupplier;
|
||||
|
||||
public class LoadBalancerHandlerSupplier implements HandlerSupplier {
|
||||
|
||||
@Override
|
||||
public Collection<Method> get() {
|
||||
return Arrays.asList(getClass().getMethods());
|
||||
}
|
||||
|
||||
public static HandlerDiscoverer.Result lb(RouteProperties routeProperties) {
|
||||
return lb(routeProperties.getUri());
|
||||
}
|
||||
|
||||
public static HandlerDiscoverer.Result lb(URI uri) {
|
||||
// TODO: how to do something other than http
|
||||
return new HandlerDiscoverer.Result(HandlerFunctions.http(), Collections.emptyList(),
|
||||
Collections.singletonList(LoadBalancerFilterFunctions.lb(uri.getHost())));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -33,6 +33,7 @@ import org.springframework.core.NestedRuntimeException;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.HttpStatusCode;
|
||||
import org.springframework.http.client.ClientHttpResponse;
|
||||
import org.springframework.retry.RetryContext;
|
||||
import org.springframework.retry.RetryPolicy;
|
||||
import org.springframework.retry.policy.CompositeRetryPolicy;
|
||||
@@ -75,6 +76,7 @@ public abstract class RetryFilterFunctions {
|
||||
if (config.isCacheBody()) {
|
||||
MvcUtils.getOrCacheBody(request);
|
||||
}
|
||||
reset(request);
|
||||
ServerResponse serverResponse = next.handle(request);
|
||||
|
||||
if (isRetryableStatusCode(serverResponse.statusCode(), config)
|
||||
@@ -86,6 +88,14 @@ public abstract class RetryFilterFunctions {
|
||||
});
|
||||
}
|
||||
|
||||
private static void reset(ServerRequest request) throws IOException {
|
||||
ClientHttpResponse clientHttpResponse = MvcUtils.getAttribute(request, MvcUtils.CLIENT_RESPONSE_ATTR);
|
||||
if (clientHttpResponse != null) {
|
||||
clientHttpResponse.close();
|
||||
MvcUtils.putAttribute(request, MvcUtils.CLIENT_RESPONSE_ATTR, null);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isRetryableStatusCode(HttpStatusCode httpStatus, RetryConfig config) {
|
||||
return config.getSeries().stream().anyMatch(series -> HttpStatus.Series.resolve(httpStatus.value()) == series);
|
||||
}
|
||||
|
||||
@@ -22,9 +22,6 @@ import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.DeprecatedConfigurationProperty;
|
||||
import org.springframework.boot.context.properties.PropertyMapper;
|
||||
import org.springframework.cloud.gateway.server.mvc.common.MvcUtils;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
@@ -32,9 +29,6 @@ import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.servlet.function.ServerRequest;
|
||||
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.XForwardedRequestHeadersFilterProperties.PREFIX;
|
||||
|
||||
@ConfigurationProperties("spring.cloud.gateway.x-forwarded")
|
||||
public class XForwardedRequestHeadersFilter implements HttpHeadersFilter.RequestHttpHeadersFilter, Ordered {
|
||||
|
||||
/** Default http port. */
|
||||
@@ -64,310 +58,15 @@ public class XForwardedRequestHeadersFilter implements HttpHeadersFilter.Request
|
||||
/** X-Forwarded-Prefix Header. */
|
||||
public static final String X_FORWARDED_PREFIX_HEADER = "X-Forwarded-Prefix";
|
||||
|
||||
/** The order of the XForwardedHeadersFilter. */
|
||||
private int order = 0;
|
||||
private final XForwardedRequestHeadersFilterProperties properties;
|
||||
|
||||
/** If the XForwardedHeadersFilter is enabled. */
|
||||
private boolean enabled = true;
|
||||
|
||||
/** If X-Forwarded-For is enabled. */
|
||||
private boolean forEnabled = true;
|
||||
|
||||
/** If X-Forwarded-Host is enabled. */
|
||||
private boolean hostEnabled = true;
|
||||
|
||||
/** If X-Forwarded-Port is enabled. */
|
||||
private boolean portEnabled = true;
|
||||
|
||||
/** If X-Forwarded-Proto is enabled. */
|
||||
private boolean protoEnabled = true;
|
||||
|
||||
/** If X-Forwarded-Prefix is enabled. */
|
||||
private boolean prefixEnabled = true;
|
||||
|
||||
/** If appending X-Forwarded-For as a list is enabled. */
|
||||
private boolean forAppend = true;
|
||||
|
||||
/** If appending X-Forwarded-Host as a list is enabled. */
|
||||
private boolean hostAppend = true;
|
||||
|
||||
/** If appending X-Forwarded-Port as a list is enabled. */
|
||||
private boolean portAppend = true;
|
||||
|
||||
/** If appending X-Forwarded-Proto as a list is enabled. */
|
||||
private boolean protoAppend = true;
|
||||
|
||||
/** If appending X-Forwarded-Prefix as a list is enabled. */
|
||||
private boolean prefixAppend = true;
|
||||
|
||||
@Deprecated
|
||||
public XForwardedRequestHeadersFilter() {
|
||||
this(new XForwardedRequestHeadersFilterProperties());
|
||||
public XForwardedRequestHeadersFilter(XForwardedRequestHeadersFilterProperties properties) {
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
public XForwardedRequestHeadersFilter(XForwardedRequestHeadersFilterProperties props) {
|
||||
// TODO: remove individual properties in 4.2.0
|
||||
// this.properties = properties;
|
||||
PropertyMapper map = PropertyMapper.get();
|
||||
map.from(props::getOrder).to(o -> this.order = o);
|
||||
map.from(props::isEnabled).to(b -> this.enabled = b);
|
||||
map.from(props::isForEnabled).to(b -> this.forEnabled = b);
|
||||
map.from(props::isHostEnabled).to(b -> this.hostEnabled = b);
|
||||
map.from(props::isPortEnabled).to(b -> this.portEnabled = b);
|
||||
map.from(props::isProtoEnabled).to(b -> this.protoEnabled = b);
|
||||
map.from(props::isPrefixEnabled).to(b -> this.prefixEnabled = b);
|
||||
map.from(props::isForAppend).to(b -> this.forAppend = b);
|
||||
map.from(props::isHostAppend).to(b -> this.hostAppend = b);
|
||||
map.from(props::isPortAppend).to(b -> this.portAppend = b);
|
||||
map.from(props::isProtoAppend).to(b -> this.protoAppend = b);
|
||||
map.from(props::isPrefixAppend).to(b -> this.prefixAppend = b);
|
||||
}
|
||||
|
||||
@DeprecatedConfigurationProperty(replacement = PREFIX + ".order")
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return this.order;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since 4.1.2 for removal in 4.2.0 in favor of
|
||||
* {@link XForwardedRequestHeadersFilterProperties#setOrder(int)} )}
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
@Deprecated(since = "4.1.2", forRemoval = true)
|
||||
public void setOrder(int order) {
|
||||
this.order = order;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since 4.1.2 for removal in 4.2.0 in favor of
|
||||
* {@link XForwardedRequestHeadersFilterProperties#isEnabled()} )}
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
@Deprecated(since = "4.1.2", forRemoval = true)
|
||||
@DeprecatedConfigurationProperty(replacement = PREFIX + ".enabled")
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since 4.1.2 for removal in 4.2.0 in favor of
|
||||
* {@link XForwardedRequestHeadersFilterProperties#setEnabled(boolean)} )}
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
@Deprecated(since = "4.1.2", forRemoval = true)
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since 4.1.2 for removal in 4.2.0 in favor of
|
||||
* {@link XForwardedRequestHeadersFilterProperties#isForEnabled()} )}
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
@Deprecated(since = "4.1.2", forRemoval = true)
|
||||
@DeprecatedConfigurationProperty(replacement = PREFIX + ".for-enabled")
|
||||
public boolean isForEnabled() {
|
||||
return forEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since 4.1.2 for removal in 4.2.0 in favor of
|
||||
* {@link XForwardedRequestHeadersFilterProperties#setForEnabled(boolean)} )}
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
@Deprecated(since = "4.1.2", forRemoval = true)
|
||||
public void setForEnabled(boolean forEnabled) {
|
||||
this.forEnabled = forEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since 4.1.2 for removal in 4.2.0 in favor of
|
||||
* {@link XForwardedRequestHeadersFilterProperties#isHostEnabled()} )}
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
@Deprecated(since = "4.1.2", forRemoval = true)
|
||||
@DeprecatedConfigurationProperty(replacement = PREFIX + ".host-enabled")
|
||||
public boolean isHostEnabled() {
|
||||
return hostEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since 4.1.2 for removal in 4.2.0 in favor of
|
||||
* {@link XForwardedRequestHeadersFilterProperties#setHostEnabled(boolean)} )}
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
@Deprecated(since = "4.1.2", forRemoval = true)
|
||||
public void setHostEnabled(boolean hostEnabled) {
|
||||
this.hostEnabled = hostEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since 4.1.2 for removal in 4.2.0 in favor of
|
||||
* {@link XForwardedRequestHeadersFilterProperties#isPortEnabled()} )}
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
@Deprecated(since = "4.1.2", forRemoval = true)
|
||||
@DeprecatedConfigurationProperty(replacement = PREFIX + ".port-enabled")
|
||||
public boolean isPortEnabled() {
|
||||
return portEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since 4.1.2 for removal in 4.2.0 in favor of
|
||||
* {@link XForwardedRequestHeadersFilterProperties#setPortEnabled(boolean)} )}
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
@Deprecated(since = "4.1.2", forRemoval = true)
|
||||
public void setPortEnabled(boolean portEnabled) {
|
||||
this.portEnabled = portEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since 4.1.2 for removal in 4.2.0 in favor of
|
||||
* {@link XForwardedRequestHeadersFilterProperties#isProtoEnabled()} )}
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
@Deprecated(since = "4.1.2", forRemoval = true)
|
||||
@DeprecatedConfigurationProperty(replacement = PREFIX + ".proto-enabled")
|
||||
public boolean isProtoEnabled() {
|
||||
return protoEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since 4.1.2 for removal in 4.2.0 in favor of
|
||||
* {@link XForwardedRequestHeadersFilterProperties#setProtoEnabled(boolean)} )}
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
@Deprecated(since = "4.1.2", forRemoval = true)
|
||||
public void setProtoEnabled(boolean protoEnabled) {
|
||||
this.protoEnabled = protoEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since 4.1.2 for removal in 4.2.0 in favor of
|
||||
* {@link XForwardedRequestHeadersFilterProperties#isPrefixEnabled()} )}
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
@Deprecated(since = "4.1.2", forRemoval = true)
|
||||
@DeprecatedConfigurationProperty(replacement = PREFIX + ".prefix-enabled")
|
||||
public boolean isPrefixEnabled() {
|
||||
return prefixEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since 4.1.2 for removal in 4.2.0 in favor of
|
||||
* {@link XForwardedRequestHeadersFilterProperties#setPrefixEnabled(boolean)} )}
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
@Deprecated(since = "4.1.2", forRemoval = true)
|
||||
public void setPrefixEnabled(boolean prefixEnabled) {
|
||||
this.prefixEnabled = prefixEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since 4.1.2 for removal in 4.2.0 in favor of
|
||||
* {@link XForwardedRequestHeadersFilterProperties#isForAppend()} )}
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
@Deprecated(since = "4.1.2", forRemoval = true)
|
||||
@DeprecatedConfigurationProperty(replacement = PREFIX + ".for-append")
|
||||
public boolean isForAppend() {
|
||||
return forAppend;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since 4.1.2 for removal in 4.2.0 in favor of
|
||||
* {@link XForwardedRequestHeadersFilterProperties#setForAppend(boolean)} )}
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
@Deprecated(since = "4.1.2", forRemoval = true)
|
||||
public void setForAppend(boolean forAppend) {
|
||||
this.forAppend = forAppend;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since 4.1.2 for removal in 4.2.0 in favor of
|
||||
* {@link XForwardedRequestHeadersFilterProperties#isHostAppend()} )}
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
@Deprecated(since = "4.1.2", forRemoval = true)
|
||||
@DeprecatedConfigurationProperty(replacement = PREFIX + ".host-append")
|
||||
public boolean isHostAppend() {
|
||||
return hostAppend;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since 4.1.2 for removal in 4.2.0 in favor of
|
||||
* {@link XForwardedRequestHeadersFilterProperties#setHostAppend(boolean)} )}
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
@Deprecated(since = "4.1.2", forRemoval = true)
|
||||
public void setHostAppend(boolean hostAppend) {
|
||||
this.hostAppend = hostAppend;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since 4.1.2 for removal in 4.2.0 in favor of
|
||||
* {@link XForwardedRequestHeadersFilterProperties#isPortAppend()} )}
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
@Deprecated(since = "4.1.2", forRemoval = true)
|
||||
@DeprecatedConfigurationProperty(replacement = PREFIX + ".port-append")
|
||||
public boolean isPortAppend() {
|
||||
return portAppend;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since 4.1.2 for removal in 4.2.0 in favor of
|
||||
* {@link XForwardedRequestHeadersFilterProperties#setPortAppend(boolean)} )}
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
@Deprecated(since = "4.1.2", forRemoval = true)
|
||||
public void setPortAppend(boolean portAppend) {
|
||||
this.portAppend = portAppend;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since 4.1.2 for removal in 4.2.0 in favor of
|
||||
* {@link XForwardedRequestHeadersFilterProperties#isProtoAppend()} )}
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
@Deprecated(since = "4.1.2", forRemoval = true)
|
||||
@DeprecatedConfigurationProperty(replacement = PREFIX + ".proto-append")
|
||||
public boolean isProtoAppend() {
|
||||
return protoAppend;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since 4.1.2 for removal in 4.2.0 in favor of
|
||||
* {@link XForwardedRequestHeadersFilterProperties#setProtoAppend(boolean)} )}
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
@Deprecated(since = "4.1.2", forRemoval = true)
|
||||
public void setProtoAppend(boolean protoAppend) {
|
||||
this.protoAppend = protoAppend;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since 4.1.2 for removal in 4.2.0 in favor of
|
||||
* {@link XForwardedRequestHeadersFilterProperties#isPrefixAppend()} )}
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
@Deprecated(since = "4.1.2", forRemoval = true)
|
||||
@DeprecatedConfigurationProperty(replacement = PREFIX + ".prefix-append")
|
||||
public boolean isPrefixAppend() {
|
||||
return prefixAppend;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since 4.1.2 for removal in 4.2.0 in favor of
|
||||
* {@link XForwardedRequestHeadersFilterProperties#setPrefixAppend(boolean)} )}
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
@Deprecated(since = "4.1.2", forRemoval = true)
|
||||
public void setPrefixAppend(boolean prefixAppend) {
|
||||
this.prefixAppend = prefixAppend;
|
||||
return properties.getOrder();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -380,17 +79,17 @@ public class XForwardedRequestHeadersFilter implements HttpHeadersFilter.Request
|
||||
}
|
||||
|
||||
InetSocketAddress remoteAddress = request.remoteAddress().orElse(null);
|
||||
if (isForEnabled() && remoteAddress != null && remoteAddress.getAddress() != null) {
|
||||
if (properties.isForEnabled() && remoteAddress != null && remoteAddress.getAddress() != null) {
|
||||
String remoteAddr = remoteAddress.getAddress().getHostAddress();
|
||||
write(updated, X_FORWARDED_FOR_HEADER, remoteAddr, isForAppend());
|
||||
write(updated, X_FORWARDED_FOR_HEADER, remoteAddr, properties.isForAppend());
|
||||
}
|
||||
|
||||
String proto = request.uri().getScheme();
|
||||
if (isProtoEnabled()) {
|
||||
write(updated, X_FORWARDED_PROTO_HEADER, proto, isProtoAppend());
|
||||
if (properties.isProtoEnabled()) {
|
||||
write(updated, X_FORWARDED_PROTO_HEADER, proto, properties.isProtoAppend());
|
||||
}
|
||||
|
||||
if (isPrefixEnabled()) {
|
||||
if (properties.isPrefixEnabled()) {
|
||||
// If the path of the url that the gw is routing to is a subset
|
||||
// (and ending part) of the url that it is routing from then the difference
|
||||
// is the prefix e.g. if request original.com/prefix/get/ is routed
|
||||
@@ -419,17 +118,17 @@ public class XForwardedRequestHeadersFilter implements HttpHeadersFilter.Request
|
||||
}
|
||||
}
|
||||
|
||||
if (isPortEnabled()) {
|
||||
if (properties.isPortEnabled()) {
|
||||
String port = String.valueOf(request.uri().getPort());
|
||||
if (request.uri().getPort() < 0) {
|
||||
port = String.valueOf(getDefaultPort(proto));
|
||||
}
|
||||
write(updated, X_FORWARDED_PORT_HEADER, port, isPortAppend());
|
||||
write(updated, X_FORWARDED_PORT_HEADER, port, properties.isPortAppend());
|
||||
}
|
||||
|
||||
if (isHostEnabled()) {
|
||||
if (properties.isHostEnabled()) {
|
||||
String host = toHostHeader(request);
|
||||
write(updated, X_FORWARDED_HOST_HEADER, host, isHostAppend());
|
||||
write(updated, X_FORWARDED_HOST_HEADER, host, properties.isHostAppend());
|
||||
}
|
||||
|
||||
return updated;
|
||||
@@ -440,7 +139,7 @@ public class XForwardedRequestHeadersFilter implements HttpHeadersFilter.Request
|
||||
if (requestUriPath != null && (originalUriPath.endsWith(requestUriPath))) {
|
||||
prefix = substringBeforeLast(originalUriPath, requestUriPath);
|
||||
if (prefix != null && prefix.length() > 0 && prefix.length() <= originalUri.getPath().length()) {
|
||||
write(updated, X_FORWARDED_PREFIX_HEADER, prefix, isPrefixAppend());
|
||||
write(updated, X_FORWARDED_PREFIX_HEADER, prefix, properties.isPrefixAppend());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,6 +56,7 @@ public class ClientHttpRequestFactoryProxyExchange extends AbstractProxyExchange
|
||||
InputStream body = clientHttpResponse.getBody();
|
||||
// put the body input stream in a request attribute so filters can read it.
|
||||
MvcUtils.putAttribute(request.getServerRequest(), MvcUtils.CLIENT_RESPONSE_INPUT_STREAM_ATTR, body);
|
||||
MvcUtils.putAttribute(request.getServerRequest(), MvcUtils.CLIENT_RESPONSE_ATTR, clientHttpResponse);
|
||||
ServerResponse serverResponse = GatewayServerResponse.status(clientHttpResponse.getStatusCode())
|
||||
.build((req, httpServletResponse) -> {
|
||||
try (clientHttpResponse) {
|
||||
|
||||
@@ -1,113 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2025 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.handler;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URI;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.springframework.cloud.gateway.server.mvc.common.MvcUtils;
|
||||
import org.springframework.cloud.gateway.server.mvc.config.RouteProperties;
|
||||
import org.springframework.web.servlet.function.HandlerFilterFunction;
|
||||
import org.springframework.web.servlet.function.HandlerFunction;
|
||||
import org.springframework.web.servlet.function.ServerResponse;
|
||||
|
||||
class DefaultHandlerSupplier implements HandlerSupplier {
|
||||
|
||||
@Override
|
||||
public Collection<Method> get() {
|
||||
return Arrays.asList(getClass().getMethods());
|
||||
}
|
||||
|
||||
public static HandlerDiscoverer.Result fn(RouteProperties routeProperties) {
|
||||
// fn:fnName
|
||||
return fn(routeProperties.getUri().getSchemeSpecificPart());
|
||||
}
|
||||
|
||||
public static HandlerDiscoverer.Result fn(String functionName) {
|
||||
return new HandlerDiscoverer.Result(HandlerFunctions.fn(functionName), Collections.emptyList(),
|
||||
Collections.emptyList());
|
||||
}
|
||||
|
||||
public static HandlerDiscoverer.Result forward(RouteProperties routeProperties) {
|
||||
return forward(routeProperties.getId(), routeProperties.getUri());
|
||||
}
|
||||
|
||||
public static HandlerDiscoverer.Result forward(String id, URI uri) {
|
||||
return new HandlerDiscoverer.Result(HandlerFunctions.forward(uri.getPath()), Collections.emptyList());
|
||||
}
|
||||
|
||||
public static HandlerDiscoverer.Result http(RouteProperties routeProperties) {
|
||||
return http(routeProperties.getId(), routeProperties.getUri());
|
||||
}
|
||||
|
||||
public static HandlerDiscoverer.Result http(String id, URI uri) {
|
||||
HandlerFunction<ServerResponse> http = HandlerFunctions.http();
|
||||
return getResult(id, uri, http);
|
||||
}
|
||||
|
||||
public static HandlerDiscoverer.Result https(RouteProperties routeProperties) {
|
||||
return https(routeProperties.getId(), routeProperties.getUri());
|
||||
}
|
||||
|
||||
public static HandlerDiscoverer.Result https(String id, URI uri) {
|
||||
return getResult(id, uri, HandlerFunctions.https());
|
||||
}
|
||||
|
||||
public static HandlerDiscoverer.Result no(RouteProperties routeProperties) {
|
||||
return no(routeProperties.getId(), routeProperties.getUri());
|
||||
}
|
||||
|
||||
public static HandlerDiscoverer.Result no(String id, URI uri) {
|
||||
return getResult(id, uri, HandlerFunctions.no());
|
||||
}
|
||||
|
||||
// for properties
|
||||
public static HandlerDiscoverer.Result stream(RouteProperties routeProperties) {
|
||||
// stream:bindingName
|
||||
return stream(routeProperties.getUri().getSchemeSpecificPart());
|
||||
}
|
||||
|
||||
public static HandlerDiscoverer.Result stream(String bindingName) {
|
||||
return new HandlerDiscoverer.Result(HandlerFunctions.stream(bindingName), Collections.emptyList(),
|
||||
Collections.emptyList());
|
||||
}
|
||||
|
||||
private static HandlerDiscoverer.Result getResult(String id, URI uri,
|
||||
HandlerFunction<ServerResponse> handlerFunction) {
|
||||
HandlerFilterFunction<ServerResponse, ServerResponse> setId = setIdFilter(id);
|
||||
HandlerFilterFunction<ServerResponse, ServerResponse> setRequest = setRequestUrlFilter(uri);
|
||||
return new HandlerDiscoverer.Result(handlerFunction, Arrays.asList(setId, setRequest), Collections.emptyList());
|
||||
}
|
||||
|
||||
private static HandlerFilterFunction<ServerResponse, ServerResponse> setIdFilter(String id) {
|
||||
return (request, next) -> {
|
||||
MvcUtils.setRouteId(request, id);
|
||||
return next.handle(request);
|
||||
};
|
||||
}
|
||||
|
||||
private static HandlerFilterFunction<ServerResponse, ServerResponse> setRequestUrlFilter(URI uri) {
|
||||
return (request, next) -> {
|
||||
MvcUtils.setRequestUrl(request, uri);
|
||||
return next.handle(request);
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright 2013-2025 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.handler;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.cloud.gateway.server.mvc.common.MvcUtils;
|
||||
import org.springframework.cloud.gateway.server.mvc.config.RouteProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.web.servlet.function.HandlerFilterFunction;
|
||||
import org.springframework.web.servlet.function.HandlerFunction;
|
||||
import org.springframework.web.servlet.function.ServerResponse;
|
||||
|
||||
@AutoConfiguration
|
||||
public class HandlerFunctionAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
public Function<RouteProperties, HandlerFunctionDefinition> fnHandlerFunctionDefinition() {
|
||||
return routeProperties -> new HandlerFunctionDefinition.Default("fn",
|
||||
HandlerFunctions.fn(routeProperties.getUri().getSchemeSpecificPart()));
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Function<RouteProperties, HandlerFunctionDefinition> forwardHandlerFunctionDefinition() {
|
||||
return routeProperties -> new HandlerFunctionDefinition.Default("forward",
|
||||
HandlerFunctions.forward(routeProperties.getUri().getPath()));
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Function<RouteProperties, HandlerFunctionDefinition> httpHandlerFunctionDefinition() {
|
||||
return routeProperties -> getResult("http", routeProperties.getId(), routeProperties.getUri(),
|
||||
HandlerFunctions.http());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Function<RouteProperties, HandlerFunctionDefinition> httpsHandlerFunctionDefinition() {
|
||||
return routeProperties -> getResult("https", routeProperties.getId(), routeProperties.getUri(),
|
||||
HandlerFunctions.https());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Function<RouteProperties, HandlerFunctionDefinition> noHandlerFunctionDefinition() {
|
||||
return routeProperties -> getResult("no", routeProperties.getId(), routeProperties.getUri(),
|
||||
HandlerFunctions.no());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Function<RouteProperties, HandlerFunctionDefinition> streamHandlerFunctionDefinition() {
|
||||
return routeProperties -> new HandlerFunctionDefinition.Default("stream",
|
||||
HandlerFunctions.stream(routeProperties.getUri().getSchemeSpecificPart()));
|
||||
}
|
||||
|
||||
private static HandlerFunctionDefinition getResult(String scheme, String id, URI uri,
|
||||
HandlerFunction<ServerResponse> handlerFunction) {
|
||||
HandlerFilterFunction<ServerResponse, ServerResponse> setId = setIdFilter(id);
|
||||
HandlerFilterFunction<ServerResponse, ServerResponse> setRequest = setRequestUrlFilter(uri);
|
||||
return new HandlerFunctionDefinition.Default(scheme, handlerFunction, Arrays.asList(setId, setRequest),
|
||||
Collections.emptyList());
|
||||
}
|
||||
|
||||
private static HandlerFilterFunction<ServerResponse, ServerResponse> setIdFilter(String id) {
|
||||
return (request, next) -> {
|
||||
MvcUtils.setRouteId(request, id);
|
||||
return next.handle(request);
|
||||
};
|
||||
}
|
||||
|
||||
private static HandlerFilterFunction<ServerResponse, ServerResponse> setRequestUrlFilter(URI uri) {
|
||||
return (request, next) -> {
|
||||
MvcUtils.setRequestUrl(request, uri);
|
||||
return next.handle(request);
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2013-2025 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.handler;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.web.servlet.function.HandlerFilterFunction;
|
||||
import org.springframework.web.servlet.function.HandlerFunction;
|
||||
import org.springframework.web.servlet.function.ServerResponse;
|
||||
|
||||
public interface HandlerFunctionDefinition {
|
||||
|
||||
HandlerFunction<ServerResponse> handlerFunction();
|
||||
|
||||
List<HandlerFilterFunction<ServerResponse, ServerResponse>> lowerPrecedenceFilters();
|
||||
|
||||
List<HandlerFilterFunction<ServerResponse, ServerResponse>> higherPrecedenceFilters();
|
||||
|
||||
record Default(String scheme, HandlerFunction<ServerResponse> handlerFunction,
|
||||
List<HandlerFilterFunction<ServerResponse, ServerResponse>> lowerPrecedenceFilters,
|
||||
List<HandlerFilterFunction<ServerResponse, ServerResponse>> higherPrecedenceFilters)
|
||||
implements
|
||||
HandlerFunctionDefinition {
|
||||
|
||||
public Default(String scheme, HandlerFunction<ServerResponse> handlerFunction) {
|
||||
this(scheme, handlerFunction, Collections.emptyList(), Collections.emptyList());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2023 the original author or authors.
|
||||
* Copyright 2013-2025 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.
|
||||
@@ -37,6 +37,9 @@ import org.springframework.web.servlet.function.ServerRequest;
|
||||
import org.springframework.web.servlet.function.ServerResponse;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
/**
|
||||
* @author raccoonback
|
||||
*/
|
||||
public class ProxyExchangeHandlerFunction
|
||||
implements HandlerFunction<ServerResponse>, ApplicationListener<ContextRefreshedEvent> {
|
||||
|
||||
@@ -84,14 +87,14 @@ public class ProxyExchangeHandlerFunction
|
||||
@Override
|
||||
public ServerResponse handle(ServerRequest serverRequest) {
|
||||
URI uri = uriResolver.apply(serverRequest);
|
||||
boolean encoded = containsEncodedQuery(serverRequest.uri(), serverRequest.params());
|
||||
MultiValueMap<String, String> params = MvcUtils.encodeQueryParams(serverRequest.params());
|
||||
// @formatter:off
|
||||
URI url = UriComponentsBuilder.fromUri(serverRequest.uri())
|
||||
.scheme(uri.getScheme())
|
||||
.host(uri.getHost())
|
||||
.port(uri.getPort())
|
||||
.replaceQueryParams(serverRequest.params())
|
||||
.build(encoded)
|
||||
.replaceQueryParams(params)
|
||||
.build(true)
|
||||
.toUri();
|
||||
// @formatter:on
|
||||
|
||||
@@ -131,29 +134,6 @@ public class ProxyExchangeHandlerFunction
|
||||
return filtered;
|
||||
}
|
||||
|
||||
private static boolean containsEncodedQuery(URI uri, MultiValueMap<String, String> params) {
|
||||
String rawQuery = uri.getRawQuery();
|
||||
boolean encoded = (rawQuery != null && rawQuery.contains("%"))
|
||||
|| (uri.getRawPath() != null && uri.getRawPath().contains("%"));
|
||||
|
||||
// Verify if it is really fully encoded. Treat partial encoded as unencoded.
|
||||
if (encoded) {
|
||||
try {
|
||||
UriComponentsBuilder.fromUri(uri).replaceQueryParams(params).build(true);
|
||||
return true;
|
||||
}
|
||||
catch (IllegalArgumentException ignored) {
|
||||
if (log.isTraceEnabled()) {
|
||||
log.trace("Error in containsEncodedParts", ignored);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public interface URIResolver extends Function<ServerRequest, URI> {
|
||||
|
||||
}
|
||||
|
||||
@@ -72,6 +72,7 @@ public class RestClientProxyExchange extends AbstractProxyExchange {
|
||||
InputStream body = clientResponse.getBody();
|
||||
// put the body input stream in a request attribute so filters can read it.
|
||||
MvcUtils.putAttribute(request.getServerRequest(), MvcUtils.CLIENT_RESPONSE_INPUT_STREAM_ATTR, body);
|
||||
MvcUtils.putAttribute(request.getServerRequest(), MvcUtils.CLIENT_RESPONSE_ATTR, clientResponse);
|
||||
ServerResponse serverResponse = GatewayServerResponse.status(clientResponse.getStatusCode())
|
||||
.build((req, httpServletResponse) -> {
|
||||
try (clientResponse) {
|
||||
|
||||
@@ -272,7 +272,12 @@ public abstract class GatewayRequestPredicates {
|
||||
|
||||
@Override
|
||||
public void accept(RequestPredicates.Visitor visitor) {
|
||||
visitor.header(name, pattern.pattern());
|
||||
if (pattern != null) {
|
||||
visitor.header(name, pattern.pattern());
|
||||
}
|
||||
else {
|
||||
visitor.header(name, "");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2013-2025 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.predicate;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
@AutoConfiguration
|
||||
public class PredicateAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
public PredicateBeanFactoryDiscoverer predicateBeanFactoryDiscoverer(BeanFactory beanFactory) {
|
||||
return new PredicateBeanFactoryDiscoverer(beanFactory);
|
||||
}
|
||||
|
||||
@Bean
|
||||
MvcPredicateSupplier mvcPredicateSupplier() {
|
||||
return new MvcPredicateSupplier();
|
||||
}
|
||||
|
||||
@Bean
|
||||
GatewayRequestPredicates.PredicateSupplier gatewayRequestPredicateSupplier() {
|
||||
return new GatewayRequestPredicates.PredicateSupplier();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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.predicate;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.cloud.gateway.server.mvc.common.BeanFactoryGatewayDiscoverer;
|
||||
import org.springframework.web.servlet.function.RequestPredicate;
|
||||
|
||||
public class PredicateBeanFactoryDiscoverer extends BeanFactoryGatewayDiscoverer {
|
||||
|
||||
protected PredicateBeanFactoryDiscoverer(BeanFactory beanFactory) {
|
||||
super(beanFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void discover() {
|
||||
doDiscover(PredicateSupplier.class, RequestPredicate.class);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,46 +1,370 @@
|
||||
{
|
||||
"properties": [
|
||||
{
|
||||
"name": "spring.cloud.gateway.mvc.form-filter.enabled",
|
||||
"name": "spring.cloud.gateway.server.webmvc.form-filter.enabled",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "Enables the form-filter.",
|
||||
"defaultValue": "true"
|
||||
},
|
||||
{
|
||||
"name": "spring.cloud.gateway.mvc.forwarded-request-headers-filter.enabled",
|
||||
"name": "spring.cloud.gateway.server.webmvc.forwarded-request-headers-filter.enabled",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "Enables the forwarded-request-headers-filter.",
|
||||
"defaultValue": "true"
|
||||
},
|
||||
{
|
||||
"name": "spring.cloud.gateway.mvc.remove-content-length-request-headers-filter.enabled",
|
||||
"name": "spring.cloud.gateway.server.webmvc.remove-content-length-request-headers-filter.enabled",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "Enables the remove-content-length-request-headers-filter.",
|
||||
"defaultValue": "true"
|
||||
},
|
||||
{
|
||||
"name": "spring.cloud.gateway.mvc.remove-hop-by-hop-request-headers-filter.enabled",
|
||||
"name": "spring.cloud.gateway.server.webmvc.remove-hop-by-hop-request-headers-filter.enabled",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "Enables the forwarded-request-headers-filter.",
|
||||
"description": "Enables the remove-hop-by-hop-request-headers-filter.",
|
||||
"defaultValue": "true"
|
||||
},
|
||||
{
|
||||
"name": "spring.cloud.gateway.mvc.remove-hop-by-hop-response-headers-filter.enabled",
|
||||
"name": "spring.cloud.gateway.server.webmvc.remove-hop-by-hop-response-headers-filter.enabled",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "Enables the forwarded-request-headers-filter.",
|
||||
"description": "Enables the remove-hop-by-hop-response-headers-filter.",
|
||||
"defaultValue": "true"
|
||||
},
|
||||
{
|
||||
"name": "spring.cloud.gateway.mvc.transfer-encoding-normalization-request-headers-filter.enabled",
|
||||
"name": "spring.cloud.gateway.server.webmvc.remove-http2-status-response-headers-filter.enabled",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "Enables the remove-http2-status-response-headers-filter.",
|
||||
"defaultValue": "true"
|
||||
},
|
||||
{
|
||||
"name": "spring.cloud.gateway.server.webmvc.transfer-encoding-normalization-request-headers-filter.enabled",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "Enables the transfer-encoding-normalization-request-headers-filter.",
|
||||
"defaultValue": "true"
|
||||
},
|
||||
{
|
||||
"name": "spring.cloud.gateway.mvc.weight-calculator-filter.enabled",
|
||||
"name": "spring.cloud.gateway.server.webmvc.weight-calculator-filter.enabled",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "Enables the weight-calculator-filter.",
|
||||
"defaultValue": "true"
|
||||
},
|
||||
{
|
||||
"name": "spring.cloud.gateway.mvc.form-filter.enabled",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "Enables the form-filter.",
|
||||
"defaultValue": "true",
|
||||
"deprecated": true,
|
||||
"deprecation": {
|
||||
"replacement": "spring.cloud.gateway.server.webmvc.form-filter.enabled",
|
||||
"since": "4.3.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "spring.cloud.gateway.mvc.forwarded-request-headers-filter.enabled",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "Enables the forwarded-request-headers-filter.",
|
||||
"defaultValue": "true",
|
||||
"deprecated": true,
|
||||
"deprecation": {
|
||||
"replacement": "spring.cloud.gateway.server.webmvc.forwarded-request-headers-filter.enabled",
|
||||
"since": "4.3.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "spring.cloud.gateway.mvc.remove-content-length-request-headers-filter.enabled",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "Enables the remove-content-length-request-headers-filter.",
|
||||
"defaultValue": "true",
|
||||
"deprecated": true,
|
||||
"deprecation": {
|
||||
"replacement": "spring.cloud.gateway.server.webmvc.remove-content-length-request-headers-filter.enabled",
|
||||
"since": "4.3.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "spring.cloud.gateway.mvc.remove-hop-by-hop-request-headers-filter.enabled",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "Enables the remove-hop-by-hop-request-headers-filter.",
|
||||
"defaultValue": "true",
|
||||
"deprecated": true,
|
||||
"deprecation": {
|
||||
"replacement": "spring.cloud.gateway.server.webmvc.remove-hop-by-hop-request-headers-filter.enabled",
|
||||
"since": "4.3.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "spring.cloud.gateway.mvc.remove-hop-by-hop-response-headers-filter.enabled",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "Enables the remove-hop-by-hop-response-headers-filter.",
|
||||
"defaultValue": "true",
|
||||
"deprecated": true,
|
||||
"deprecation": {
|
||||
"replacement": "spring.cloud.gateway.server.webmvc.remove-hop-by-hop-response-headers-filter.enabled",
|
||||
"since": "4.3.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "spring.cloud.gateway.mvc.remove-http2-status-response-headers-filter.enabled",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "Enables the remove-http2-status-response-headers-filter.",
|
||||
"defaultValue": "true",
|
||||
"deprecated": true,
|
||||
"deprecation": {
|
||||
"replacement": "spring.cloud.gateway.server.webmvc.remove-http2-status-response-headers-filter.enabled",
|
||||
"since": "4.3.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "spring.cloud.gateway.mvc.routes",
|
||||
"type": "java.util.List<org.springframework.cloud.gateway.server.mvc.config.RouteProperties>",
|
||||
"description": "List of Routes.",
|
||||
"deprecated": true,
|
||||
"deprecation": {
|
||||
"replacement": "spring.cloud.gateway.server.webmvc.routes",
|
||||
"since": "4.3.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "spring.cloud.gateway.mvc.routes-map",
|
||||
"type": "java.util.LinkedHashMap<java.lang.String,org.springframework.cloud.gateway.server.mvc.config.RouteProperties>",
|
||||
"description": "Map of Routes.",
|
||||
"sourceType": "org.springframework.cloud.gateway.server.mvc.config.GatewayMvcProperties",
|
||||
"deprecated": true,
|
||||
"deprecation": {
|
||||
"replacement": "spring.cloud.gateway.server.webmvc.routes-map",
|
||||
"since": "4.3.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "spring.cloud.gateway.mvc.streaming-buffer-size",
|
||||
"type": "java.lang.Integer",
|
||||
"description": "Buffer size for streaming media mime-types.",
|
||||
"sourceType": "org.springframework.cloud.gateway.server.mvc.config.GatewayMvcProperties",
|
||||
"defaultValue": 16384,
|
||||
"deprecated": true,
|
||||
"deprecation": {
|
||||
"replacement": "spring.cloud.gateway.server.webmvc.streaming-buffer-size",
|
||||
"since": "4.3.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "spring.cloud.gateway.mvc.streaming-media-types",
|
||||
"type": "java.util.List<org.springframework.http.MediaType>",
|
||||
"description": "Mime-types that are streaming.",
|
||||
"sourceType": "org.springframework.cloud.gateway.server.mvc.config.GatewayMvcProperties",
|
||||
"deprecated": true,
|
||||
"deprecation": {
|
||||
"replacement": "spring.cloud.gateway.server.webmvc.streaming-media-types",
|
||||
"since": "4.3.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "spring.cloud.gateway.mvc.transfer-encoding-normalization-request-headers-filter.enabled",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "Enables the transfer-encoding-normalization-request-headers-filter.",
|
||||
"defaultValue": "true",
|
||||
"deprecated": true,
|
||||
"deprecation": {
|
||||
"replacement": "spring.cloud.gateway.server.webmvc.transfer-encoding-normalization-request-headers-filter.enabled",
|
||||
"since": "4.3.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "spring.cloud.gateway.mvc.weight-calculator-filter.enabled",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "Enables the weight-calculator-filter.",
|
||||
"defaultValue": "true",
|
||||
"deprecated": true,
|
||||
"deprecation": {
|
||||
"replacement": "spring.cloud.gateway.server.webmvc.weight-calculator-filter.enabled",
|
||||
"since": "4.3.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "spring.cloud.gateway.mvc.x-forwarded-request-headers-filter.enabled",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "If the XForwardedHeadersFilter is enabled.",
|
||||
"sourceType": "org.springframework.cloud.gateway.server.mvc.filter.XForwardedRequestHeadersFilterProperties",
|
||||
"defaultValue": true,
|
||||
"deprecated": true,
|
||||
"deprecation": {
|
||||
"replacement": "spring.cloud.gateway.server.webmvc.x-forwarded-request-headers-filter.enabled",
|
||||
"since": "4.3.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "spring.cloud.gateway.mvc.x-forwarded-request-headers-filter.for-append",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "If appending X-Forwarded-For as a list is enabled.",
|
||||
"sourceType": "org.springframework.cloud.gateway.server.mvc.filter.XForwardedRequestHeadersFilterProperties",
|
||||
"defaultValue": true,
|
||||
"deprecated": true,
|
||||
"deprecation": {
|
||||
"replacement": "spring.cloud.gateway.server.webmvc.x-forwarded-request-headers-filter.for-append",
|
||||
"since": "4.3.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "spring.cloud.gateway.mvc.x-forwarded-request-headers-filter.for-enabled",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "If X-Forwarded-For is enabled.",
|
||||
"sourceType": "org.springframework.cloud.gateway.server.mvc.filter.XForwardedRequestHeadersFilterProperties",
|
||||
"defaultValue": true,
|
||||
"deprecated": true,
|
||||
"deprecation": {
|
||||
"replacement": "spring.cloud.gateway.server.webmvc.x-forwarded-request-headers-filter.for-enabled",
|
||||
"since": "4.3.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "spring.cloud.gateway.mvc.x-forwarded-request-headers-filter.host-append",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "If appending X-Forwarded-Host as a list is enabled.",
|
||||
"sourceType": "org.springframework.cloud.gateway.server.mvc.filter.XForwardedRequestHeadersFilterProperties",
|
||||
"defaultValue": true,
|
||||
"deprecated": true,
|
||||
"deprecation": {
|
||||
"replacement": "spring.cloud.gateway.server.webmvc.x-forwarded-request-headers-filter.host-append",
|
||||
"since": "4.3.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "spring.cloud.gateway.mvc.x-forwarded-request-headers-filter.host-enabled",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "If X-Forwarded-Host is enabled.",
|
||||
"sourceType": "org.springframework.cloud.gateway.server.mvc.filter.XForwardedRequestHeadersFilterProperties",
|
||||
"defaultValue": true,
|
||||
"deprecated": true,
|
||||
"deprecation": {
|
||||
"replacement": "spring.cloud.gateway.server.webmvc.x-forwarded-request-headers-filter.host-enabled",
|
||||
"since": "4.3.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "spring.cloud.gateway.mvc.x-forwarded-request-headers-filter.order",
|
||||
"type": "java.lang.Integer",
|
||||
"description": "The order of the XForwardedHeadersFilter.",
|
||||
"sourceType": "org.springframework.cloud.gateway.server.mvc.filter.XForwardedRequestHeadersFilterProperties",
|
||||
"defaultValue": 0,
|
||||
"deprecated": true,
|
||||
"deprecation": {
|
||||
"replacement": "spring.cloud.gateway.server.webmvc.x-forwarded-request-headers-filter.order",
|
||||
"since": "4.3.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "spring.cloud.gateway.mvc.x-forwarded-request-headers-filter.port-append",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "If appending X-Forwarded-Port as a list is enabled.",
|
||||
"sourceType": "org.springframework.cloud.gateway.server.mvc.filter.XForwardedRequestHeadersFilterProperties",
|
||||
"defaultValue": true,
|
||||
"deprecated": true,
|
||||
"deprecation": {
|
||||
"replacement": "spring.cloud.gateway.server.webmvc.x-forwarded-request-headers-filter.port-append",
|
||||
"since": "4.3.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "spring.cloud.gateway.mvc.x-forwarded-request-headers-filter.port-enabled",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "If X-Forwarded-Port is enabled.",
|
||||
"sourceType": "org.springframework.cloud.gateway.server.mvc.filter.XForwardedRequestHeadersFilterProperties",
|
||||
"defaultValue": true,
|
||||
"deprecated": true,
|
||||
"deprecation": {
|
||||
"replacement": "spring.cloud.gateway.server.webmvc.x-forwarded-request-headers-filter.port-enabled",
|
||||
"since": "4.3.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "spring.cloud.gateway.mvc.x-forwarded-request-headers-filter.prefix-append",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "If appending X-Forwarded-Prefix as a list is enabled.",
|
||||
"sourceType": "org.springframework.cloud.gateway.server.mvc.filter.XForwardedRequestHeadersFilterProperties",
|
||||
"defaultValue": true,
|
||||
"deprecated": true,
|
||||
"deprecation": {
|
||||
"replacement": "spring.cloud.gateway.server.webmvc.x-forwarded-request-headers-filter.prefix-append",
|
||||
"since": "4.3.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "spring.cloud.gateway.mvc.x-forwarded-request-headers-filter.prefix-enabled",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "If X-Forwarded-Prefix is enabled.",
|
||||
"sourceType": "org.springframework.cloud.gateway.server.mvc.filter.XForwardedRequestHeadersFilterProperties",
|
||||
"defaultValue": true,
|
||||
"deprecated": true,
|
||||
"deprecation": {
|
||||
"replacement": "spring.cloud.gateway.server.webmvc.x-forwarded-request-headers-filter.prefix-enabled",
|
||||
"since": "4.3.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "spring.cloud.gateway.mvc.x-forwarded-request-headers-filter.proto-append",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "If appending X-Forwarded-Proto as a list is enabled.",
|
||||
"sourceType": "org.springframework.cloud.gateway.server.mvc.filter.XForwardedRequestHeadersFilterProperties",
|
||||
"defaultValue": true,
|
||||
"deprecated": true,
|
||||
"deprecation": {
|
||||
"replacement": "spring.cloud.gateway.server.webmvc.x-forwarded-request-headers-filter.proto-append",
|
||||
"since": "4.3.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "spring.cloud.gateway.mvc.x-forwarded-request-headers-filter.proto-enabled",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "If X-Forwarded-Proto is enabled.",
|
||||
"sourceType": "org.springframework.cloud.gateway.server.mvc.filter.XForwardedRequestHeadersFilterProperties",
|
||||
"defaultValue": true,
|
||||
"deprecated": true,
|
||||
"deprecation": {
|
||||
"replacement": "spring.cloud.gateway.server.webmvc.x-forwarded-request-headers-filter.proto-enabled",
|
||||
"since": "4.3.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "spring.cloud.gateway.mvc.http-client.connect-timeout",
|
||||
"type": "java.time.Duration",
|
||||
"description": "The HttpClient connect timeout.",
|
||||
"sourceType": "org.springframework.cloud.gateway.server.mvc.config.GatewayMvcProperties$HttpClient",
|
||||
"deprecated": true,
|
||||
"deprecation": {
|
||||
"replacement": "spring.http.client.connect-timeout",
|
||||
"since": "4.2.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "spring.cloud.gateway.mvc.http-client.read-timeout",
|
||||
"type": "java.time.Duration",
|
||||
"description": "The HttpClient read timeout.",
|
||||
"sourceType": "org.springframework.cloud.gateway.server.mvc.config.GatewayMvcProperties$HttpClient",
|
||||
"deprecated": true,
|
||||
"deprecation": {
|
||||
"replacement": "spring.http.client.read-timeout",
|
||||
"since": "4.2.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "spring.cloud.gateway.mvc.http-client.ssl-bundle",
|
||||
"type": "java.lang.String",
|
||||
"description": "The name of the SSL bundle to use.",
|
||||
"sourceType": "org.springframework.cloud.gateway.server.mvc.config.GatewayMvcProperties$HttpClient",
|
||||
"deprecated": true,
|
||||
"deprecation": {
|
||||
"replacement": "spring.http.client.ssl.bundle",
|
||||
"since": "4.2.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "spring.cloud.gateway.mvc.http-client.type",
|
||||
"type": "org.springframework.cloud.gateway.server.mvc.config.GatewayMvcProperties$HttpClientType",
|
||||
"description": "The HttpClient type. Defaults to JDK.",
|
||||
"sourceType": "org.springframework.cloud.gateway.server.mvc.config.GatewayMvcProperties$HttpClient",
|
||||
"defaultValue": "jdk",
|
||||
"deprecated": true,
|
||||
"deprecation": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -15,21 +15,10 @@
|
||||
#
|
||||
#
|
||||
|
||||
org.springframework.cloud.gateway.server.mvc.filter.FilterSupplier=\
|
||||
org.springframework.cloud.gateway.server.mvc.filter.Bucket4jFilterFunctions.FilterSupplier,\
|
||||
org.springframework.cloud.gateway.server.mvc.filter.CircuitBreakerFilterFunctions.FilterSupplier,\
|
||||
org.springframework.cloud.gateway.server.mvc.filter.RetryFilterFunctions.FilterSupplier,\
|
||||
org.springframework.cloud.gateway.server.mvc.filter.TokenRelayFilterFunctions.FilterSupplier,\
|
||||
org.springframework.cloud.gateway.server.mvc.filter.FilterFunctions.FilterSupplier
|
||||
|
||||
org.springframework.cloud.gateway.server.mvc.handler.HandlerSupplier=\
|
||||
org.springframework.cloud.gateway.server.mvc.handler.DefaultHandlerSupplier,\
|
||||
org.springframework.cloud.gateway.server.mvc.filter.LoadBalancerHandlerSupplier
|
||||
|
||||
org.springframework.cloud.gateway.server.mvc.predicate.PredicateSupplier=\
|
||||
org.springframework.cloud.gateway.server.mvc.predicate.MvcPredicateSupplier,\
|
||||
org.springframework.cloud.gateway.server.mvc.predicate.GatewayRequestPredicates.PredicateSupplier
|
||||
|
||||
org.springframework.boot.env.EnvironmentPostProcessor=\
|
||||
org.springframework.cloud.gateway.server.mvc.GatewayServerMvcAutoConfiguration.GatewayHttpClientEnvironmentPostProcessor,\
|
||||
org.springframework.cloud.gateway.server.mvc.common.MultipartEnvironmentPostProcessor
|
||||
|
||||
# Application Listeners
|
||||
org.springframework.context.ApplicationListener=\
|
||||
org.springframework.cloud.gateway.server.mvc.config.GatewayServerWebMvcPropertiesMigrationListener
|
||||
@@ -1,5 +1,8 @@
|
||||
org.springframework.cloud.gateway.server.mvc.GatewayServerMvcAutoConfiguration
|
||||
org.springframework.cloud.gateway.server.mvc.GatewayMvcClassPathWarningAutoConfiguration
|
||||
org.springframework.cloud.gateway.server.mvc.filter.FilterAutoConfiguration
|
||||
org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctionAutoConfiguration
|
||||
org.springframework.cloud.gateway.server.mvc.handler.GatewayMultipartAutoConfiguration
|
||||
org.springframework.cloud.gateway.server.mvc.predicate.PredicateAutoConfiguration
|
||||
org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration
|
||||
org.springframework.cloud.gateway.server.mvc.config.DefaultFunctionConfiguration
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* 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.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.SpringBootConfiguration;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
|
||||
import org.springframework.cloud.gateway.server.mvc.config.GatewayMvcProperties;
|
||||
import org.springframework.cloud.gateway.server.mvc.filter.FormFilter;
|
||||
import org.springframework.cloud.gateway.server.mvc.filter.ForwardedRequestHeadersFilter;
|
||||
import org.springframework.cloud.gateway.server.mvc.filter.RemoveContentLengthRequestHeadersFilter;
|
||||
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.RemoveHttp2StatusResponseHeadersFilter;
|
||||
import org.springframework.cloud.gateway.server.mvc.filter.TransferEncodingNormalizationRequestHeadersFilter;
|
||||
import org.springframework.cloud.gateway.server.mvc.filter.WeightCalculatorFilter;
|
||||
import org.springframework.cloud.gateway.server.mvc.filter.XForwardedRequestHeadersFilter;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@SpringBootTest(properties = {}, webEnvironment = WebEnvironment.RANDOM_PORT)
|
||||
@ActiveProfiles("propertiesmigrationtests")
|
||||
public class GatewayMvcPropertiesMigrationTests {
|
||||
|
||||
@Autowired
|
||||
ApplicationContext context;
|
||||
|
||||
@Autowired
|
||||
GatewayMvcProperties properties;
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Test
|
||||
public void deprecatedFilterEnabledPropertiesWork() {
|
||||
assertBeanDoesNotExist(FormFilter.class);
|
||||
assertBeanDoesNotExist(ForwardedRequestHeadersFilter.class);
|
||||
assertBeanDoesNotExist(RemoveContentLengthRequestHeadersFilter.class);
|
||||
assertBeanDoesNotExist(RemoveHopByHopRequestHeadersFilter.class);
|
||||
assertBeanDoesNotExist(RemoveHopByHopResponseHeadersFilter.class);
|
||||
assertBeanDoesNotExist(RemoveHttp2StatusResponseHeadersFilter.class);
|
||||
assertBeanDoesNotExist(TransferEncodingNormalizationRequestHeadersFilter.class);
|
||||
assertBeanDoesNotExist(WeightCalculatorFilter.class);
|
||||
assertBeanDoesNotExist(XForwardedRequestHeadersFilter.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deprecatedRoutePropertiesWork() {
|
||||
assertThat(properties.getRoutes()).hasSize(2);
|
||||
assertThat(properties.getRoutesMap()).hasSize(2);
|
||||
}
|
||||
|
||||
private void assertBeanDoesNotExist(Class<?> type) {
|
||||
assertThat(context.getBeanNamesForType(type)).isEmpty();
|
||||
}
|
||||
|
||||
@SpringBootConfiguration
|
||||
@EnableAutoConfiguration
|
||||
protected static class TestConfiguration {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2024 the original author or authors.
|
||||
* Copyright 2013-2025 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.
|
||||
@@ -34,7 +34,9 @@ import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
|
||||
import org.springframework.boot.http.client.ClientHttpRequestFactorySettings;
|
||||
import org.springframework.boot.http.client.SimpleClientHttpRequestFactoryBuilder;
|
||||
import org.springframework.boot.test.context.FilteredClassLoader;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.cloud.gateway.server.mvc.filter.FilterAutoConfiguration;
|
||||
import org.springframework.cloud.gateway.server.mvc.filter.FormFilter;
|
||||
import org.springframework.cloud.gateway.server.mvc.filter.ForwardedRequestHeadersFilter;
|
||||
import org.springframework.cloud.gateway.server.mvc.filter.RemoveContentLengthRequestHeadersFilter;
|
||||
@@ -44,6 +46,9 @@ import org.springframework.cloud.gateway.server.mvc.filter.RemoveHttp2StatusResp
|
||||
import org.springframework.cloud.gateway.server.mvc.filter.TransferEncodingNormalizationRequestHeadersFilter;
|
||||
import org.springframework.cloud.gateway.server.mvc.filter.WeightCalculatorFilter;
|
||||
import org.springframework.cloud.gateway.server.mvc.filter.XForwardedRequestHeadersFilter;
|
||||
import org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctionAutoConfiguration;
|
||||
import org.springframework.cloud.gateway.server.mvc.predicate.PredicateAutoConfiguration;
|
||||
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -109,18 +114,19 @@ public class GatewayServerMvcAutoConfigurationTests {
|
||||
@Test
|
||||
void filterEnabledPropertiesWork() {
|
||||
new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(GatewayServerMvcAutoConfiguration.class,
|
||||
.withConfiguration(AutoConfigurations.of(FilterAutoConfiguration.class, PredicateAutoConfiguration.class,
|
||||
HandlerFunctionAutoConfiguration.class, GatewayServerMvcAutoConfiguration.class,
|
||||
HttpClientAutoConfiguration.class, RestTemplateAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, SslAutoConfiguration.class))
|
||||
.withPropertyValues("spring.cloud.gateway.mvc.form-filter.enabled=false",
|
||||
"spring.cloud.gateway.mvc.forwarded-request-headers-filter.enabled=false",
|
||||
"spring.cloud.gateway.mvc.remove-content-length-request-headers-filter.enabled=false",
|
||||
"spring.cloud.gateway.mvc.remove-hop-by-hop-request-headers-filter.enabled=false",
|
||||
"spring.cloud.gateway.mvc.remove-hop-by-hop-response-headers-filter.enabled=false",
|
||||
"spring.cloud.gateway.mvc.remove-http2-status-response-headers-filter.enabled=false",
|
||||
"spring.cloud.gateway.mvc.transfer-encoding-normalization-request-headers-filter.enabled=false",
|
||||
"spring.cloud.gateway.mvc.weight-calculator-filter.enabled=false",
|
||||
"spring.cloud.gateway.mvc.x-forwarded-request-headers-filter.enabled=false")
|
||||
.withPropertyValues("spring.cloud.gateway.server.webmvc.form-filter.enabled=false",
|
||||
"spring.cloud.gateway.server.webmvc.forwarded-request-headers-filter.enabled=false",
|
||||
"spring.cloud.gateway.server.webmvc.remove-content-length-request-headers-filter.enabled=false",
|
||||
"spring.cloud.gateway.server.webmvc.remove-hop-by-hop-request-headers-filter.enabled=false",
|
||||
"spring.cloud.gateway.server.webmvc.remove-hop-by-hop-response-headers-filter.enabled=false",
|
||||
"spring.cloud.gateway.server.webmvc.remove-http2-status-response-headers-filter.enabled=false",
|
||||
"spring.cloud.gateway.server.webmvc.transfer-encoding-normalization-request-headers-filter.enabled=false",
|
||||
"spring.cloud.gateway.server.webmvc.weight-calculator-filter.enabled=false",
|
||||
"spring.cloud.gateway.server.webmvc.x-forwarded-request-headers-filter.enabled=false")
|
||||
.run(context -> {
|
||||
assertThat(context).doesNotHaveBean(FormFilter.class);
|
||||
assertThat(context).doesNotHaveBean(ForwardedRequestHeadersFilter.class);
|
||||
@@ -138,11 +144,8 @@ public class GatewayServerMvcAutoConfigurationTests {
|
||||
@Test
|
||||
void gatewayHttpClientPropertiesWork() {
|
||||
ConfigurableApplicationContext context = new SpringApplicationBuilder(TestConfig.class)
|
||||
.properties("spring.main.web-application-type=none",
|
||||
"spring.cloud.gateway.mvc.http-client.connect-timeout=1s",
|
||||
"spring.cloud.gateway.mvc.http-client.read-timeout=2s",
|
||||
"spring.cloud.gateway.mvc.http-client.ssl-bundle=mybundle",
|
||||
"spring.cloud.gateway.mvc.http-client.type=autodetect",
|
||||
.properties("spring.main.web-application-type=none", "spring.http.client.connect-timeout=1s",
|
||||
"spring.http.client.read-timeout=2s", "spring.http.client.ssl.bundle=mybundle",
|
||||
"spring.ssl.bundle.pem.mybundle.keystore.certificate=" + cert,
|
||||
"spring.ssl.bundle.pem.mybundle.keystore.key=" + key)
|
||||
.run();
|
||||
@@ -161,7 +164,8 @@ public class GatewayServerMvcAutoConfigurationTests {
|
||||
@Test
|
||||
void bootHttpClientPropertiesWork() {
|
||||
new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(GatewayServerMvcAutoConfiguration.class,
|
||||
.withConfiguration(AutoConfigurations.of(FilterAutoConfiguration.class, PredicateAutoConfiguration.class,
|
||||
HandlerFunctionAutoConfiguration.class, GatewayServerMvcAutoConfiguration.class,
|
||||
HttpClientAutoConfiguration.class, RestTemplateAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class, SslAutoConfiguration.class))
|
||||
.withPropertyValues("spring.http.client.connect-timeout=1s", "spring.http.client.read-timeout=2s",
|
||||
@@ -184,6 +188,15 @@ public class GatewayServerMvcAutoConfigurationTests {
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void settingHttpClientFactoryOldPropertyWorks() {
|
||||
ConfigurableApplicationContext context = new SpringApplicationBuilder(TestConfig.class)
|
||||
.properties("spring.main.web-application-type=none", "spring.http.client.factory=simple")
|
||||
.run();
|
||||
ClientHttpRequestFactoryBuilder<?> builder = context.getBean(ClientHttpRequestFactoryBuilder.class);
|
||||
assertThat(builder).isInstanceOf(SimpleClientHttpRequestFactoryBuilder.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void settingHttpClientFactoryWorks() {
|
||||
ConfigurableApplicationContext context = new SpringApplicationBuilder(TestConfig.class)
|
||||
@@ -193,6 +206,27 @@ public class GatewayServerMvcAutoConfigurationTests {
|
||||
assertThat(builder).isInstanceOf(SimpleClientHttpRequestFactoryBuilder.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void loadBalancerFunctionHandlerAdded() {
|
||||
new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(FilterAutoConfiguration.class, PredicateAutoConfiguration.class,
|
||||
HandlerFunctionAutoConfiguration.class, GatewayServerMvcAutoConfiguration.class,
|
||||
HttpClientAutoConfiguration.class, RestTemplateAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class))
|
||||
.run(context -> assertThat(context).hasBean("lbHandlerFunctionDefinition"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void loadBalancerFunctionHandlerNotAddedWhenNoLoadBalancerClientOnClasspath() {
|
||||
new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(FilterAutoConfiguration.class, PredicateAutoConfiguration.class,
|
||||
HandlerFunctionAutoConfiguration.class, GatewayServerMvcAutoConfiguration.class,
|
||||
HttpClientAutoConfiguration.class, RestTemplateAutoConfiguration.class,
|
||||
RestClientAutoConfiguration.class))
|
||||
.withClassLoader(new FilteredClassLoader(LoadBalancerClient.class))
|
||||
.run(context -> assertThat(context).doesNotHaveBean("lbHandlerFunctionDefinition"));
|
||||
}
|
||||
|
||||
@SpringBootConfiguration
|
||||
@EnableAutoConfiguration
|
||||
static class TestConfig {
|
||||
|
||||
@@ -142,8 +142,7 @@ import static org.springframework.web.servlet.function.RequestPredicates.POST;
|
||||
import static org.springframework.web.servlet.function.RequestPredicates.path;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@SpringBootTest(
|
||||
properties = { "spring.cloud.gateway.mvc.http-client.type=jdk", "spring.cloud.gateway.function.enabled=false" },
|
||||
@SpringBootTest(properties = { "spring.http.client.factory=jdk", "spring.cloud.gateway.function.enabled=false" },
|
||||
webEnvironment = WebEnvironment.RANDOM_PORT)
|
||||
@ContextConfiguration(initializers = HttpbinTestcontainers.class)
|
||||
@ExtendWith(OutputCaptureExtension.class)
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2013-2025 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.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.web.server.LocalServerPort;
|
||||
import org.springframework.cloud.gateway.server.mvc.filter.FilterAutoConfiguration;
|
||||
import org.springframework.cloud.gateway.server.mvc.test.HttpbinTestcontainers;
|
||||
import org.springframework.cloud.gateway.server.mvc.test.TestLoadBalancerConfig;
|
||||
import org.springframework.cloud.gateway.server.mvc.test.client.TestRestClient;
|
||||
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link FilterAutoConfiguration.LoadBalancerHandlerConfiguration}.
|
||||
*
|
||||
* @author Olga Maciaszek-Sharma
|
||||
*
|
||||
*/
|
||||
@SpringBootTest(classes = { ServerMvcLoadBalancerIntegrationTests.Config.class, FilterAutoConfiguration.class },
|
||||
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
@ContextConfiguration(initializers = HttpbinTestcontainers.class)
|
||||
@ActiveProfiles("lb")
|
||||
public class ServerMvcLoadBalancerIntegrationTests {
|
||||
|
||||
@LocalServerPort
|
||||
int port;
|
||||
|
||||
@Autowired
|
||||
TestRestClient testRestClient;
|
||||
|
||||
@Test
|
||||
void shouldUseLbHandlerFunctionDefinitionToResolveHost() {
|
||||
testRestClient.get().uri("http://localhost:" + port + "/test").exchange().expectStatus().isOk();
|
||||
}
|
||||
|
||||
@SpringBootApplication
|
||||
@LoadBalancerClient(name = "httpbin", configuration = TestLoadBalancerConfig.Httpbin.class)
|
||||
static class Config {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -43,7 +43,7 @@ import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFuncti
|
||||
import static org.springframework.cloud.gateway.server.mvc.predicate.GatewayRequestPredicates.host;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@SpringBootTest(properties = { "spring.cloud.gateway.mvc.http-client.type=jdk" },
|
||||
@SpringBootTest(properties = { "spring.http.client.factory=jdk" },
|
||||
webEnvironment = WebEnvironment.RANDOM_PORT)
|
||||
@ContextConfiguration(initializers = HttpbinTestcontainers.class)
|
||||
public class VanillaRouterFunctionTests {
|
||||
|
||||
@@ -199,14 +199,13 @@ public class GatewayMvcPropertiesBeanDefinitionRegistrarTests {
|
||||
void refreshWorks(ConfigurableApplicationContext context) {
|
||||
Map<String, RouterFunction> routerFunctions = getRouterFunctions(context);
|
||||
assertThat(routerFunctions).hasSize(6);
|
||||
TestPropertyValues
|
||||
.of("spring.cloud.gateway.mvc.routesMap.route3.uri=https://example3.com",
|
||||
"spring.cloud.gateway.mvc.routesMap.route3.predicates[0].name=Path",
|
||||
"spring.cloud.gateway.mvc.routesMap.route3.predicates[0].args.pattern=/anything/mapRoute3",
|
||||
"spring.cloud.gateway.mvc.routesMap.route3.filters[0].Name=HttpbinUriResolver",
|
||||
"spring.cloud.gateway.mvc.routesMap.route3.filters[1].Name=AddRequestHeader",
|
||||
"spring.cloud.gateway.mvc.routesMap.route3.filters[1].args.name=X-Test",
|
||||
"spring.cloud.gateway.mvc.routesMap.route3.filters[1].args.values=mapRoute3")
|
||||
TestPropertyValues.of("spring.cloud.gateway.server.webmvc.routesMap.route3.uri=https://example3.com",
|
||||
"spring.cloud.gateway.server.webmvc.routesMap.route3.predicates[0].name=Path",
|
||||
"spring.cloud.gateway.server.webmvc.routesMap.route3.predicates[0].args.pattern=/anything/mapRoute3",
|
||||
"spring.cloud.gateway.server.webmvc.routesMap.route3.filters[0].Name=HttpbinUriResolver",
|
||||
"spring.cloud.gateway.server.webmvc.routesMap.route3.filters[1].Name=AddRequestHeader",
|
||||
"spring.cloud.gateway.server.webmvc.routesMap.route3.filters[1].args.name=X-Test",
|
||||
"spring.cloud.gateway.server.webmvc.routesMap.route3.filters[1].args.values=mapRoute3")
|
||||
.applyTo(context);
|
||||
ContextRefresher contextRefresher = context.getBean(ContextRefresher.class);
|
||||
contextRefresher.refresh();
|
||||
|
||||
@@ -0,0 +1,218 @@
|
||||
/*
|
||||
* Copyright 2025-2025 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.filter;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.SpringBootConfiguration;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.cloud.gateway.server.mvc.test.HttpbinTestcontainers;
|
||||
import org.springframework.cloud.gateway.server.mvc.test.HttpbinUriResolver;
|
||||
import org.springframework.cloud.gateway.server.mvc.test.TestLoadBalancerConfig;
|
||||
import org.springframework.cloud.gateway.server.mvc.test.client.TestRestClient;
|
||||
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.servlet.function.RouterFunction;
|
||||
import org.springframework.web.servlet.function.ServerResponse;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.AfterFilterFunctions.modifyResponseBody;
|
||||
import static org.springframework.cloud.gateway.server.mvc.filter.AfterFilterFunctions.removeJsonAttributesResponseBody;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
|
||||
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;
|
||||
|
||||
/**
|
||||
* @author raccoonback
|
||||
*/
|
||||
@SpringBootTest(webEnvironment = RANDOM_PORT)
|
||||
@ContextConfiguration(initializers = HttpbinTestcontainers.class)
|
||||
class AfterFilterFunctionsTests {
|
||||
|
||||
@Autowired
|
||||
TestRestClient restClient;
|
||||
|
||||
@Test
|
||||
void doesNotRemoveJsonAttributes() {
|
||||
restClient.get()
|
||||
.uri("/anything/does_not/remove_json_attributes")
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isOk()
|
||||
.expectBody(Map.class)
|
||||
.consumeWith(res -> {
|
||||
assertThat(res.getResponseBody()).containsEntry("foo", "bar");
|
||||
assertThat(res.getResponseBody()).containsEntry("baz", "qux");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void removeJsonAttributesToAvoidBeingRecursive() {
|
||||
restClient.get()
|
||||
.uri("/anything/remove_json_attributes_to_avoid_being_recursive")
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isOk()
|
||||
.expectBody(Map.class)
|
||||
.consumeWith(res -> {
|
||||
assertThat(res.getResponseBody()).doesNotContainKey("foo");
|
||||
assertThat(res.getResponseBody()).containsEntry("baz", "qux");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void removeJsonAttributesRecursively() {
|
||||
restClient.get()
|
||||
.uri("/anything/remove_json_attributes_recursively")
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.isOk()
|
||||
.expectBody(Map.class)
|
||||
.consumeWith(res -> {
|
||||
assertThat(res.getResponseBody()).containsKey("foo");
|
||||
assertThat((Map<String, String>) res.getResponseBody().get("foo")).containsEntry("bar", "A");
|
||||
assertThat(res.getResponseBody()).containsEntry("quux", "C");
|
||||
assertThat(res.getResponseBody()).doesNotContainKey("qux");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void raisedErrorWhenRemoveJsonAttributes() {
|
||||
restClient.get()
|
||||
.uri("/anything/raised_error_when_remove_json_attributes")
|
||||
.exchange()
|
||||
.expectStatus()
|
||||
.is5xxServerError()
|
||||
.expectBody(String.class)
|
||||
.consumeWith(res -> {
|
||||
assertThat(res.getResponseBody()).isEqualTo("Failed to process JSON of response body.");
|
||||
});
|
||||
}
|
||||
|
||||
@SpringBootConfiguration
|
||||
@EnableAutoConfiguration
|
||||
@LoadBalancerClient(name = "httpbin", configuration = TestLoadBalancerConfig.Httpbin.class)
|
||||
protected static class TestConfiguration {
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> doesNotRemoveJsonAttributes() {
|
||||
// @formatter:off
|
||||
return route("does_not_remove_json_attributes")
|
||||
.GET("/anything/does_not/remove_json_attributes", http())
|
||||
.before(new HttpbinUriResolver())
|
||||
.after(
|
||||
removeJsonAttributesResponseBody(List.of("quux"), true)
|
||||
)
|
||||
.after(
|
||||
modifyResponseBody(
|
||||
String.class,
|
||||
String.class,
|
||||
MediaType.APPLICATION_JSON_VALUE,
|
||||
(request, response, s) -> "{\"foo\": \"bar\", \"baz\": \"qux\"}"
|
||||
)
|
||||
)
|
||||
.build();
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> removeJsonAttributesToAvoidBeingRecursively() {
|
||||
// @formatter:off
|
||||
return route("remove_json_attributes_to_avoid_being_recursive")
|
||||
.GET("/anything/remove_json_attributes_to_avoid_being_recursive", http())
|
||||
.before(new HttpbinUriResolver())
|
||||
.after(
|
||||
removeJsonAttributesResponseBody(List.of("foo"), false)
|
||||
)
|
||||
.after(
|
||||
modifyResponseBody(
|
||||
String.class,
|
||||
String.class,
|
||||
MediaType.APPLICATION_JSON_VALUE,
|
||||
(request, response, s) -> "{\"foo\": \"bar\", \"baz\": \"qux\"}"
|
||||
)
|
||||
)
|
||||
.build();
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> removeJsonAttributesRecursively() {
|
||||
// @formatter:off
|
||||
return route("remove_json_attributes_recursively")
|
||||
.GET("/anything/remove_json_attributes_recursively", http())
|
||||
.before(new HttpbinUriResolver())
|
||||
.after(
|
||||
removeJsonAttributesResponseBody(List.of("qux"), true)
|
||||
)
|
||||
.after(
|
||||
modifyResponseBody(
|
||||
String.class,
|
||||
String.class,
|
||||
MediaType.APPLICATION_JSON_VALUE,
|
||||
(request, response, s) -> "{\"foo\": { \"bar\": \"A\", \"qux\": \"B\"}, \"quux\": \"C\", \"qux\": {\"corge\": \"D\"}}"
|
||||
)
|
||||
)
|
||||
.build();
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RouterFunction<ServerResponse> raisedErrorWhenRemoveJsonAttributes() {
|
||||
// @formatter:off
|
||||
return route("raised_error_when_remove_json_attributes")
|
||||
.GET("/anything/raised_error_when_remove_json_attributes", http())
|
||||
.before(new HttpbinUriResolver())
|
||||
.after(
|
||||
removeJsonAttributesResponseBody(List.of("qux"), true)
|
||||
)
|
||||
.after(
|
||||
modifyResponseBody(
|
||||
String.class,
|
||||
String.class,
|
||||
MediaType.APPLICATION_JSON_VALUE,
|
||||
(request, response, s) -> "{\"invalid_json\": 123"
|
||||
)
|
||||
)
|
||||
.build();
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
@ControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
@ExceptionHandler(IllegalStateException.class)
|
||||
public ResponseEntity<String> handleIllegalException(IllegalStateException ex) {
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(ex.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -119,6 +119,7 @@ class BeforeFilterFunctionsTests {
|
||||
MockHttpServletRequest servletRequest = MockMvcRequestBuilders.get("http://localhost/path")
|
||||
.param("foo[]", "bar")
|
||||
.param("baz", "qux")
|
||||
.param("quux", "corge+")
|
||||
.buildRequest(null);
|
||||
|
||||
ServerRequest request = ServerRequest.create(servletRequest, Collections.emptyList());
|
||||
@@ -126,7 +127,24 @@ class BeforeFilterFunctionsTests {
|
||||
ServerRequest result = BeforeFilterFunctions.rewriteRequestParameter("foo[]", "replacement[]").apply(request);
|
||||
|
||||
assertThat(result.param("foo[]")).isPresent().hasValue("replacement[]");
|
||||
assertThat(result.uri().toString()).hasToString("http://localhost/path?baz=qux&foo%5B%5D=replacement%5B%5D");
|
||||
assertThat(result.param("quux")).isPresent().hasValue("corge+");
|
||||
assertThat(result.uri().toString())
|
||||
.hasToString("http://localhost/path?quux=corge%2B&baz=qux&foo%5B%5D=replacement%5B%5D");
|
||||
}
|
||||
|
||||
@Test
|
||||
void rewritePlusSignRequestParameter() {
|
||||
MockHttpServletRequest servletRequest = MockMvcRequestBuilders.get("http://localhost/path")
|
||||
.param("foo", "bar")
|
||||
.param("baz", "qux")
|
||||
.buildRequest(null);
|
||||
|
||||
ServerRequest request = ServerRequest.create(servletRequest, Collections.emptyList());
|
||||
|
||||
ServerRequest result = BeforeFilterFunctions.rewriteRequestParameter("foo", "replacement+").apply(request);
|
||||
|
||||
assertThat(result.param("foo")).isPresent().hasValue("replacement+");
|
||||
assertThat(result.uri().toString()).hasToString("http://localhost/path?baz=qux&foo=replacement%2B");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -134,6 +152,7 @@ class BeforeFilterFunctionsTests {
|
||||
MockHttpServletRequest servletRequest = MockMvcRequestBuilders.get("http://localhost/path")
|
||||
.param("foo", "bar")
|
||||
.param("baz[]", "qux[]")
|
||||
.param("quux", "corge+")
|
||||
.buildRequest(null);
|
||||
|
||||
ServerRequest request = ServerRequest.create(servletRequest, Collections.emptyList());
|
||||
@@ -141,7 +160,8 @@ class BeforeFilterFunctionsTests {
|
||||
ServerRequest result = BeforeFilterFunctions.rewriteRequestParameter("foo", "replacement").apply(request);
|
||||
|
||||
assertThat(result.param("foo")).isPresent().hasValue("replacement");
|
||||
assertThat(result.uri().toString()).hasToString("http://localhost/path?baz%5B%5D=qux%5B%5D&foo=replacement");
|
||||
assertThat(result.uri().toString())
|
||||
.hasToString("http://localhost/path?baz%5B%5D=qux%5B%5D&quux=corge%2B&foo=replacement");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -245,4 +265,26 @@ class BeforeFilterFunctionsTests {
|
||||
.isEqualTo("/modified/path/with%E2%80%93en%E2%80%93dashes%20and%20spaces");
|
||||
}
|
||||
|
||||
@Test
|
||||
void prefixPath() {
|
||||
MockHttpServletRequest servletRequest = MockMvcRequestBuilders.get("http://localhost/get").buildRequest(null);
|
||||
|
||||
ServerRequest request = ServerRequest.create(servletRequest, Collections.emptyList());
|
||||
|
||||
ServerRequest modified = BeforeFilterFunctions.prefixPath("/prefix").apply(request);
|
||||
|
||||
assertThat(modified.uri().getRawPath()).isEqualTo("/prefix/get");
|
||||
}
|
||||
|
||||
@Test
|
||||
void prefixEncodedPath() {
|
||||
MockHttpServletRequest servletRequest = MockMvcRequestBuilders.get("http://localhost/get/é").buildRequest(null);
|
||||
|
||||
ServerRequest request = ServerRequest.create(servletRequest, Collections.emptyList());
|
||||
|
||||
ServerRequest modified = BeforeFilterFunctions.prefixPath("/pre fix").apply(request);
|
||||
|
||||
assertThat(modified.uri().getRawPath()).isEqualTo("/pre%20fix/get/%C3%A9");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2013-2025 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.filter;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.SpringBootConfiguration;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
|
||||
import org.springframework.cloud.gateway.server.mvc.invoke.reflect.OperationMethod;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
|
||||
public class FilterDiscovererTests {
|
||||
|
||||
@Test
|
||||
void contextLoads() {
|
||||
MultiValueMap<String, OperationMethod> operations = new FilterDiscoverer().getOperations();
|
||||
assertThat(operations).isNotEmpty();
|
||||
}
|
||||
|
||||
@SpringBootConfiguration
|
||||
@EnableAutoConfiguration
|
||||
static class Config {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* Copyright 2025-2025 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.handler;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Collections;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.cloud.gateway.server.mvc.common.AbstractProxyExchange;
|
||||
import org.springframework.cloud.gateway.server.mvc.common.MvcUtils;
|
||||
import org.springframework.cloud.gateway.server.mvc.config.GatewayMvcProperties;
|
||||
import org.springframework.cloud.gateway.server.mvc.filter.HttpHeadersFilter.RequestHttpHeadersFilter;
|
||||
import org.springframework.cloud.gateway.server.mvc.filter.HttpHeadersFilter.ResponseHttpHeadersFilter;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
||||
import org.springframework.web.servlet.function.ServerRequest;
|
||||
import org.springframework.web.servlet.function.ServerResponse;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author raccoonback
|
||||
*/
|
||||
class ProxyExchangeHandlerFunctionTest {
|
||||
|
||||
@Test
|
||||
void keepOriginalEncodingOfQueryParameter() {
|
||||
TestProxyExchange proxyExchange = new TestProxyExchange();
|
||||
ProxyExchangeHandlerFunction function = new ProxyExchangeHandlerFunction(proxyExchange, new ObjectProvider<>() {
|
||||
@Override
|
||||
public RequestHttpHeadersFilter getObject() throws BeansException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestHttpHeadersFilter getObject(Object... args) throws BeansException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestHttpHeadersFilter getIfAvailable() throws BeansException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RequestHttpHeadersFilter getIfUnique() throws BeansException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<RequestHttpHeadersFilter> orderedStream() {
|
||||
return Stream.of((httpHeaders, serverRequest) -> new HttpHeaders());
|
||||
}
|
||||
|
||||
}, new ObjectProvider<>() {
|
||||
|
||||
@Override
|
||||
public ResponseHttpHeadersFilter getObject() throws BeansException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseHttpHeadersFilter getObject(Object... args) throws BeansException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseHttpHeadersFilter getIfAvailable() throws BeansException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseHttpHeadersFilter getIfUnique() throws BeansException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<ResponseHttpHeadersFilter> orderedStream() {
|
||||
return Stream.of((httpHeaders, serverRequest) -> new HttpHeaders());
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
function.onApplicationEvent(null);
|
||||
|
||||
MockHttpServletRequest servletRequest = MockMvcRequestBuilders
|
||||
.get("http://localhost/é?foo=value1 value2&bar=value3=&qux=value4+")
|
||||
.buildRequest(null);
|
||||
servletRequest.setAttribute(MvcUtils.GATEWAY_REQUEST_URL_ATTR, URI.create("http://localhost:8080"));
|
||||
ServerRequest request = ServerRequest.create(servletRequest, Collections.emptyList());
|
||||
|
||||
function.handle(request);
|
||||
|
||||
URI uri = proxyExchange.getRequest().getUri();
|
||||
|
||||
assertThat(uri).hasToString("http://localhost:8080/%C3%A9?foo=value1%20value2&bar=value3%3D&qux=value4%2B")
|
||||
.hasPath("/é")
|
||||
.hasParameter("foo", "value1 value2")
|
||||
.hasParameter("bar", "value3=")
|
||||
.hasParameter("qux", "value4+");
|
||||
}
|
||||
|
||||
private class TestProxyExchange extends AbstractProxyExchange {
|
||||
|
||||
private Request request;
|
||||
|
||||
protected TestProxyExchange() {
|
||||
super(new GatewayMvcProperties());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerResponse exchange(Request request) {
|
||||
this.request = request;
|
||||
|
||||
return ServerResponse.ok().build();
|
||||
}
|
||||
|
||||
public Request getRequest() {
|
||||
return request;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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.predicate;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.SpringBootConfiguration;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
|
||||
import org.springframework.cloud.gateway.server.mvc.invoke.reflect.OperationMethod;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
|
||||
public class PredicateBeanFactoryDiscovererTests {
|
||||
|
||||
@Autowired
|
||||
PredicateBeanFactoryDiscoverer discoverer;
|
||||
|
||||
@Test
|
||||
void contextLoads() {
|
||||
MultiValueMap<String, OperationMethod> operations = discoverer.getOperations();
|
||||
assertThat(operations).isNotEmpty();
|
||||
}
|
||||
|
||||
@SpringBootConfiguration
|
||||
@EnableAutoConfiguration
|
||||
static class Config {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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.test;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.cloud.gateway.server.mvc.predicate.PredicateSupplier;
|
||||
import org.springframework.web.servlet.function.RequestPredicate;
|
||||
|
||||
public class TestPredicateSupplier implements PredicateSupplier {
|
||||
|
||||
public static RequestPredicate alwaysTrue() {
|
||||
return request -> true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Method> get() {
|
||||
return List.of(TestPredicateSupplier.class.getMethods());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,2 +1,5 @@
|
||||
org.springframework.cloud.gateway.server.mvc.predicate.PredicateSupplier=\
|
||||
org.springframework.cloud.gateway.server.mvc.test.TestPredicateSupplier
|
||||
|
||||
org.springframework.cloud.gateway.server.mvc.filter.FilterSupplier=\
|
||||
org.springframework.cloud.gateway.server.mvc.test.TestFilterSupplier
|
||||
@@ -1,4 +1,4 @@
|
||||
spring.cloud.gateway.mvc:
|
||||
spring.cloud.gateway.server.webmvc:
|
||||
routesMap:
|
||||
testsimplefunction:
|
||||
uri: fn:upper
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
spring:
|
||||
cloud:
|
||||
gateway:
|
||||
mvc:
|
||||
routes:
|
||||
- id: test
|
||||
uri: lb://httpbin
|
||||
predicates:
|
||||
- Path=/test/**
|
||||
filters:
|
||||
- StripPrefix=1
|
||||
@@ -1,4 +1,4 @@
|
||||
spring.cloud.gateway.mvc:
|
||||
spring.cloud.gateway.server.webmvc:
|
||||
routesMap:
|
||||
route1:
|
||||
uri: https://example1.com
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
spring.cloud.gateway.mvc:
|
||||
form-filter.enabled: false
|
||||
forwarded-request-headers-filter.enabled: false
|
||||
remove-content-length-request-headers-filter.enabled: false
|
||||
remove-hop-by-hop-request-headers-filter.enabled: false
|
||||
remove-hop-by-hop-response-headers-filter.enabled: false
|
||||
remove-http2-status-response-headers-filter.enabled: false
|
||||
transfer-encoding-normalization-request-headers-filter.enabled: false
|
||||
weight-calculator-filter.enabled: false
|
||||
x-forwarded-request-headers-filter.enabled: false
|
||||
routes:
|
||||
- id: listRoute1
|
||||
uri: https://examplel1.com
|
||||
predicates:
|
||||
- name: Method
|
||||
args:
|
||||
methods: GET
|
||||
- name: Path
|
||||
args:
|
||||
pattern: /anything/listRoute1
|
||||
filters:
|
||||
- HttpbinUriResolver=
|
||||
- AddRequestHeader=X-Test,listRoute1
|
||||
- id: listRoute2
|
||||
uri: https://examplel2.com
|
||||
predicates:
|
||||
- name: Method
|
||||
args:
|
||||
methods: GET
|
||||
- name: Path
|
||||
args:
|
||||
pattern: /anything/listRoute2
|
||||
filters:
|
||||
- HttpbinUriResolver=
|
||||
- AddRequestHeader=X-Test,listRoute2
|
||||
routesMap:
|
||||
route1:
|
||||
uri: https://example1.com
|
||||
predicates:
|
||||
- Path=/anything/example1
|
||||
route2:
|
||||
uri: https://example2.com
|
||||
predicates:
|
||||
- Path=/anything/example2
|
||||
logging:
|
||||
level:
|
||||
org.springframework.cloud.gateway.server.mvc: TRACE
|
||||
@@ -1,4 +1,4 @@
|
||||
spring.cloud.gateway.mvc:
|
||||
spring.cloud.gateway.server.webmvc:
|
||||
routesMap:
|
||||
testsimplestream:
|
||||
uri: stream:hello-out-0
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
strip.prefix.static.uri: http://${httpbin.host}:${httpbin.port}
|
||||
spring.cloud.gateway.mvc:
|
||||
spring.cloud.gateway.server.webmvc:
|
||||
routes:
|
||||
- id: strip_prefix_static_port_config
|
||||
uri: ${strip.prefix.static.uri}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
spring.cloud.gateway.mvc:
|
||||
spring.cloud.gateway.server.webmvc:
|
||||
routes:
|
||||
- id: weight_high_test
|
||||
uri: https://examplel1.com
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<description>Spring Cloud Gateway Server</description>
|
||||
<properties>
|
||||
<main.basedir>${basedir}/..</main.basedir>
|
||||
<grpc.version>1.71.0</grpc.version>
|
||||
<grpc.version>1.72.0</grpc.version>
|
||||
<context-propagation.version>1.0.0</context-propagation.version>
|
||||
</properties>
|
||||
|
||||
@@ -192,6 +192,11 @@
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-properties-migrator</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-testcontainers</artifactId>
|
||||
@@ -212,11 +217,6 @@
|
||||
<artifactId>spring-cloud-test-support</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.projectreactor</groupId>
|
||||
<artifactId>reactor-test</artifactId>
|
||||
|
||||
@@ -57,6 +57,7 @@ import org.springframework.boot.autoconfigure.web.ServerProperties;
|
||||
import org.springframework.boot.autoconfigure.web.embedded.NettyWebServerFactoryCustomizer;
|
||||
import org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.reactive.WebFluxProperties;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.PropertyMapper;
|
||||
import org.springframework.boot.ssl.SslBundles;
|
||||
@@ -110,6 +111,7 @@ import org.springframework.cloud.gateway.filter.factory.SecureHeadersProperties;
|
||||
import org.springframework.cloud.gateway.filter.factory.SetPathGatewayFilterFactory;
|
||||
import org.springframework.cloud.gateway.filter.factory.SetRequestHeaderGatewayFilterFactory;
|
||||
import org.springframework.cloud.gateway.filter.factory.SetRequestHostHeaderGatewayFilterFactory;
|
||||
import org.springframework.cloud.gateway.filter.factory.SetRequestUriGatewayFilterFactory;
|
||||
import org.springframework.cloud.gateway.filter.factory.SetResponseHeaderGatewayFilterFactory;
|
||||
import org.springframework.cloud.gateway.filter.factory.SetStatusGatewayFilterFactory;
|
||||
import org.springframework.cloud.gateway.filter.factory.StripPrefixGatewayFilterFactory;
|
||||
@@ -192,9 +194,10 @@ import org.springframework.web.reactive.socket.server.upgrade.ReactorNettyReques
|
||||
* @author Mete Alpaslan Katırcıoğlu
|
||||
* @author Alberto C. Ríos
|
||||
* @author Olga Maciaszek-Sharma
|
||||
* @author FuYiNan Guo
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnProperty(name = "spring.cloud.gateway.enabled", matchIfMissing = true)
|
||||
@ConditionalOnProperty(name = "spring.cloud.gateway.server.webflux.enabled", matchIfMissing = true)
|
||||
@EnableConfigurationProperties
|
||||
@AutoConfigureBefore({ HttpHandlerAutoConfiguration.class, WebFluxAutoConfiguration.class })
|
||||
@AutoConfigureAfter({ GatewayReactiveLoadBalancerClientAutoConfiguration.class,
|
||||
@@ -270,7 +273,7 @@ public class GatewayAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnClass(name = "org.springframework.cloud.client.discovery.event.HeartbeatMonitor")
|
||||
@ConditionalOnProperty(prefix = GatewayProperties.PREFIX, name = ".route-refresh-listener.enabled",
|
||||
@ConditionalOnProperty(prefix = GatewayProperties.PREFIX, name = "route-refresh-listener.enabled",
|
||||
matchIfMissing = true)
|
||||
public RouteRefreshListener routeRefreshListener(ApplicationEventPublisher publisher) {
|
||||
return new RouteRefreshListener(publisher);
|
||||
@@ -288,7 +291,7 @@ public class GatewayAutoConfiguration {
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(name = "spring.cloud.gateway.globalcors.enabled", matchIfMissing = true)
|
||||
@ConditionalOnProperty(name = "spring.cloud.gateway.server.webflux.globalcors.enabled", matchIfMissing = true)
|
||||
public CorsGatewayFilterApplicationListener corsGatewayFilterApplicationListener(
|
||||
GlobalCorsProperties globalCorsProperties, RoutePredicateHandlerMapping routePredicateHandlerMapping,
|
||||
RouteLocator routeLocator) {
|
||||
@@ -316,9 +319,10 @@ public class GatewayAutoConfiguration {
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(name = "spring.cloud.gateway.forwarded.enabled", matchIfMissing = true)
|
||||
@ConditionalOnProperty(name = "spring.cloud.gateway.server.webflux.forwarded.enabled", matchIfMissing = true)
|
||||
public ForwardedHeadersFilter forwardedHeadersFilter(Environment env, ServerProperties serverProperties) {
|
||||
boolean forwardedByEnabled = env.getProperty("spring.cloud.gateway.forwarded.by.enabled", Boolean.class, false);
|
||||
boolean forwardedByEnabled = env.getProperty("spring.cloud.gateway.server.webflux.forwarded.by.enabled",
|
||||
Boolean.class, false);
|
||||
ForwardedHeadersFilter forwardedHeadersFilter = new ForwardedHeadersFilter();
|
||||
forwardedHeadersFilter.setForwardedByEnabled(forwardedByEnabled);
|
||||
forwardedHeadersFilter.setServerPort(serverProperties.getPort());
|
||||
@@ -333,7 +337,7 @@ public class GatewayAutoConfiguration {
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(name = "spring.cloud.gateway.x-forwarded.enabled", matchIfMissing = true)
|
||||
@ConditionalOnProperty(name = "spring.cloud.gateway.server.webflux.x-forwarded.enabled", matchIfMissing = true)
|
||||
public XForwardedHeadersFilter xForwardedHeadersFilter() {
|
||||
return new XForwardedHeadersFilter();
|
||||
}
|
||||
@@ -466,7 +470,8 @@ public class GatewayAutoConfiguration {
|
||||
@Bean
|
||||
@ConditionalOnEnabledPredicate
|
||||
public HostRoutePredicateFactory hostRoutePredicateFactory(Environment env) {
|
||||
boolean includePort = env.getProperty("spring.cloud.gateway.predicate.host.include-port", Boolean.class, true);
|
||||
boolean includePort = env.getProperty("spring.cloud.gateway.server.webflux.predicate.host.include-port",
|
||||
Boolean.class, true);
|
||||
return new HostRoutePredicateFactory(includePort);
|
||||
}
|
||||
|
||||
@@ -478,8 +483,8 @@ public class GatewayAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnEnabledPredicate
|
||||
public PathRoutePredicateFactory pathRoutePredicateFactory() {
|
||||
return new PathRoutePredicateFactory();
|
||||
public PathRoutePredicateFactory pathRoutePredicateFactory(WebFluxProperties webFluxProperties) {
|
||||
return new PathRoutePredicateFactory(webFluxProperties);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -718,6 +723,12 @@ public class GatewayAutoConfiguration {
|
||||
return new RequestHeaderToRequestUriGatewayFilterFactory();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnEnabledFilter
|
||||
public SetRequestUriGatewayFilterFactory setRequestUriGatewayFilterFactory() {
|
||||
return new SetRequestUriGatewayFilterFactory();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnEnabledFilter
|
||||
public RequestSizeGatewayFilterFactory requestSizeGatewayFilterFactory() {
|
||||
@@ -767,7 +778,7 @@ public class GatewayAutoConfiguration {
|
||||
protected final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(name = "spring.cloud.gateway.httpserver.wiretap")
|
||||
@ConditionalOnProperty(name = "spring.cloud.gateway.server.webflux.httpserver.wiretap")
|
||||
public NettyWebServerFactoryCustomizer nettyServerWiretapCustomizer(Environment environment,
|
||||
ServerProperties serverProperties) {
|
||||
return new NettyWebServerFactoryCustomizer(environment, serverProperties) {
|
||||
@@ -851,7 +862,8 @@ public class GatewayAutoConfiguration {
|
||||
protected static class GatewayActuatorConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(name = "spring.cloud.gateway.actuator.verbose.enabled", matchIfMissing = true)
|
||||
@ConditionalOnProperty(name = "spring.cloud.gateway.server.webflux.actuator.verbose.enabled",
|
||||
matchIfMissing = true)
|
||||
@ConditionalOnAvailableEndpoint
|
||||
public GatewayControllerEndpoint gatewayControllerEndpoint(List<GlobalFilter> globalFilters,
|
||||
List<GatewayFilterFactory> gatewayFilters, List<RoutePredicateFactory> routePredicates,
|
||||
@@ -881,7 +893,8 @@ public class GatewayAutoConfiguration {
|
||||
super(ConfigurationPhase.REGISTER_BEAN);
|
||||
}
|
||||
|
||||
@ConditionalOnProperty(name = "spring.cloud.gateway.actuator.verbose.enabled", matchIfMissing = true)
|
||||
@ConditionalOnProperty(name = "spring.cloud.gateway.server.webflux.actuator.verbose.enabled",
|
||||
matchIfMissing = true)
|
||||
static class VerboseDisabled {
|
||||
|
||||
}
|
||||
@@ -889,7 +902,7 @@ public class GatewayAutoConfiguration {
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnProperty(name = "spring.cloud.gateway.enabled", matchIfMissing = true)
|
||||
@ConditionalOnProperty(name = "spring.cloud.gateway.server.webflux.enabled", matchIfMissing = true)
|
||||
@ConditionalOnClass({ OAuth2AuthorizedClient.class, SecurityWebFilterChain.class, SecurityProperties.class })
|
||||
@ConditionalOnEnabledFilter(TokenRelayGatewayFilterFactory.class)
|
||||
protected static class TokenRelayConfiguration {
|
||||
|
||||
@@ -29,7 +29,7 @@ import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@AutoConfigureBefore(GatewayAutoConfiguration.class)
|
||||
@ConditionalOnProperty(name = "spring.cloud.gateway.enabled", matchIfMissing = true)
|
||||
@ConditionalOnProperty(name = GatewayProperties.PREFIX + ".enabled", matchIfMissing = true)
|
||||
public class GatewayClassPathWarningAutoConfiguration {
|
||||
|
||||
private static final Log log = LogFactory.getLog(GatewayClassPathWarningAutoConfiguration.class);
|
||||
|
||||
@@ -38,7 +38,7 @@ import org.springframework.web.reactive.DispatcherHandler;
|
||||
@AutoConfigureAfter(ContextFunctionCatalogAutoConfiguration.class)
|
||||
@AutoConfigureBefore({ HttpHandlerAutoConfiguration.class, GatewayAutoConfiguration.class })
|
||||
@ConditionalOnClass({ FunctionCatalog.class, DispatcherHandler.class })
|
||||
@ConditionalOnProperty(name = "spring.cloud.gateway.function.enabled", matchIfMissing = true)
|
||||
@ConditionalOnProperty(name = GatewayProperties.PREFIX + ".function.enabled", matchIfMissing = true)
|
||||
class GatewayFunctionAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -21,7 +21,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
/**
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
@ConfigurationProperties("spring.cloud.gateway.loadbalancer")
|
||||
@ConfigurationProperties(GatewayProperties.PREFIX + ".loadbalancer")
|
||||
public class GatewayLoadBalancerProperties {
|
||||
|
||||
private boolean use404;
|
||||
|
||||
@@ -28,7 +28,7 @@ import org.springframework.validation.annotation.Validated;
|
||||
/**
|
||||
* @author Ingyu Hwang
|
||||
*/
|
||||
@ConfigurationProperties("spring.cloud.gateway.metrics")
|
||||
@ConfigurationProperties(GatewayProperties.PREFIX + ".metrics")
|
||||
@Validated
|
||||
public class GatewayMetricsProperties {
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ public class GatewayProperties {
|
||||
/**
|
||||
* Properties prefix.
|
||||
*/
|
||||
public static final String PREFIX = "spring.cloud.gateway";
|
||||
public static final String PREFIX = "spring.cloud.gateway.server.webflux";
|
||||
|
||||
private final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ import org.springframework.web.reactive.DispatcherHandler;
|
||||
@AutoConfigureBefore(GatewayAutoConfiguration.class)
|
||||
@ConditionalOnBean(ReactiveRedisTemplate.class)
|
||||
@ConditionalOnClass({ RedisTemplate.class, DispatcherHandler.class })
|
||||
@ConditionalOnProperty(name = "spring.cloud.gateway.redis.enabled", matchIfMissing = true)
|
||||
@ConditionalOnProperty(name = GatewayProperties.PREFIX + ".redis.enabled", matchIfMissing = true)
|
||||
class GatewayRedisAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@@ -72,7 +72,7 @@ class GatewayRedisAutoConfiguration {
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(value = "spring.cloud.gateway.redis-route-definition-repository.enabled",
|
||||
@ConditionalOnProperty(value = GatewayProperties.PREFIX + ".redis-route-definition-repository.enabled",
|
||||
havingValue = "true")
|
||||
@ConditionalOnClass(ReactiveRedisTemplate.class)
|
||||
public RedisRouteDefinitionRepository redisRouteDefinitionRepository(
|
||||
|
||||
@@ -36,7 +36,7 @@ import org.springframework.web.reactive.DispatcherHandler;
|
||||
* @author Ryan Baxter
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnProperty(name = "spring.cloud.gateway.enabled", matchIfMissing = true)
|
||||
@ConditionalOnProperty(name = GatewayProperties.PREFIX + ".enabled", matchIfMissing = true)
|
||||
@AutoConfigureAfter({ ReactiveResilience4JAutoConfiguration.class })
|
||||
@ConditionalOnClass({ DispatcherHandler.class, ReactiveResilience4JAutoConfiguration.class,
|
||||
ReactiveCircuitBreakerFactory.class, ReactiveResilience4JCircuitBreakerFactory.class })
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
* Copyright 2013-2025 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.config;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.boot.context.event.ApplicationFailedEvent;
|
||||
import org.springframework.boot.context.event.ApplicationPreparedEvent;
|
||||
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||
import org.springframework.boot.context.event.SpringApplicationEvent;
|
||||
import org.springframework.boot.context.properties.source.ConfigurationProperty;
|
||||
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
|
||||
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
|
||||
import org.springframework.boot.context.properties.source.ConfigurationPropertySources;
|
||||
import org.springframework.boot.context.properties.source.IterableConfigurationPropertySource;
|
||||
import org.springframework.boot.env.OriginTrackedMapPropertySource;
|
||||
import org.springframework.boot.origin.Origin;
|
||||
import org.springframework.boot.origin.OriginTrackedValue;
|
||||
import org.springframework.boot.origin.PropertySourceOrigin;
|
||||
import org.springframework.boot.origin.TextResourceOrigin;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.PropertySource;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
|
||||
class GatewayServerWebfluxPropertiesMigrationListener implements ApplicationListener<SpringApplicationEvent> {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(GatewayServerWebfluxPropertiesMigrationListener.class);
|
||||
|
||||
private static final String PROPERTIES_MIGRATOR_CLASS = "org.springframework.boot.context.properties.migrator.PropertiesMigrationListener";
|
||||
|
||||
private static final String DEPRECATED_ROOT = "spring.cloud.gateway";
|
||||
|
||||
private static final String DEPRECATED_ROUTES_LIST_KEY = DEPRECATED_ROOT + ".routes";
|
||||
|
||||
private static final String GATEWAY_PROPERTY_SOURCE_PREFIX = "migrategatewayflux";
|
||||
|
||||
private static final String NEW_ROUTES_LIST_KEY = GatewayProperties.PREFIX + ".routes";
|
||||
|
||||
private final List<Migration> routesMigrations = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(SpringApplicationEvent event) {
|
||||
// only run if spring-boot-properties-migrator is on the classpath
|
||||
if (!ClassUtils.isPresent(PROPERTIES_MIGRATOR_CLASS, null)) {
|
||||
return;
|
||||
}
|
||||
if (event instanceof ApplicationPreparedEvent preparedEvent) {
|
||||
onApplicationPreparedEvent(preparedEvent);
|
||||
}
|
||||
if (event instanceof ApplicationReadyEvent || event instanceof ApplicationFailedEvent) {
|
||||
logLegacyPropertiesReport();
|
||||
}
|
||||
}
|
||||
|
||||
private void onApplicationPreparedEvent(ApplicationPreparedEvent event) {
|
||||
// find deprecated keys
|
||||
ConfigurableEnvironment env = event.getApplicationContext().getEnvironment();
|
||||
|
||||
ConfigurationPropertySources.get(env).forEach(propertySource -> {
|
||||
routesMigrations.addAll(migrate(env, propertySource, GATEWAY_PROPERTY_SOURCE_PREFIX + "routes-",
|
||||
DEPRECATED_ROUTES_LIST_KEY, NEW_ROUTES_LIST_KEY));
|
||||
});
|
||||
}
|
||||
|
||||
private List<Migration> migrate(ConfigurableEnvironment env, ConfigurationPropertySource propertySource,
|
||||
String propertySourcePrefix, String deprecatedKey, String newKeyPrefix) {
|
||||
List<Migration> migrations = new ArrayList<>();
|
||||
|
||||
if (propertySource instanceof IterableConfigurationPropertySource iterableSource) {
|
||||
ConfigurationPropertyName routesParentName = ConfigurationPropertyName.of(deprecatedKey);
|
||||
List<ConfigurationPropertyName> matchingConfigProps = iterableSource.filter(n -> {
|
||||
if (n.getNumberOfElements() < routesParentName.getNumberOfElements()) {
|
||||
return false;
|
||||
}
|
||||
ConfigurationPropertyName chop = n.chop(routesParentName.getNumberOfElements());
|
||||
return routesParentName.equals(chop);
|
||||
}).stream().toList();
|
||||
if (!matchingConfigProps.isEmpty()) {
|
||||
String originalPropertySourceName;
|
||||
if (propertySource.getUnderlyingSource() instanceof PropertySource<?> underlyingSource) {
|
||||
originalPropertySourceName = underlyingSource.getName();
|
||||
}
|
||||
else {
|
||||
originalPropertySourceName = propertySource.getUnderlyingSource().toString();
|
||||
}
|
||||
String newPropertySourceName = propertySourcePrefix + originalPropertySourceName;
|
||||
Map<String, OriginTrackedValue> content = new LinkedHashMap<>();
|
||||
// migrate to new keys
|
||||
for (ConfigurationPropertyName originalPropertyName : matchingConfigProps) {
|
||||
ConfigurationPropertyName suffix = originalPropertyName
|
||||
.subName(routesParentName.getNumberOfElements());
|
||||
ConfigurationPropertyName newProperty = ConfigurationPropertyName.of(newKeyPrefix).append(suffix);
|
||||
ConfigurationProperty configurationProperty = propertySource
|
||||
.getConfigurationProperty(originalPropertyName);
|
||||
Object value = configurationProperty.getValue();
|
||||
OriginTrackedValue originTrackedValue = OriginTrackedValue.of(value,
|
||||
configurationProperty.getOrigin());
|
||||
content.put(newProperty.toString(), originTrackedValue);
|
||||
migrations.add(new Migration(originalPropertySourceName, originalPropertyName,
|
||||
configurationProperty, newProperty));
|
||||
}
|
||||
env.getPropertySources()
|
||||
.addBefore(originalPropertySourceName,
|
||||
new OriginTrackedMapPropertySource(newPropertySourceName, content));
|
||||
}
|
||||
}
|
||||
return migrations;
|
||||
}
|
||||
|
||||
private void logLegacyPropertiesReport() {
|
||||
// log warnings
|
||||
if (!routesMigrations.isEmpty()) {
|
||||
LinkedMultiValueMap<String, Migration> content = new LinkedMultiValueMap<>();
|
||||
routesMigrations.forEach(migration -> content.add(migration.originalPropertySourceName(), migration));
|
||||
|
||||
StringBuilder report = new StringBuilder();
|
||||
report.append(String
|
||||
.format("%nThe use of configuration keys that have been renamed was found in the environment:%n%n"));
|
||||
|
||||
content.forEach((name, properties) -> {
|
||||
report.append(String.format("Property source '%s':%n", name));
|
||||
// properties.sort(PropertyMigration.COMPARATOR);
|
||||
properties.forEach((property) -> {
|
||||
ConfigurationPropertyName originalPropertyName = property.originalPropertyName();
|
||||
report.append(String.format("\tKey: %s%n", originalPropertyName));
|
||||
Integer lineNumber = property.determineLineNumber();
|
||||
if (lineNumber != null) {
|
||||
report.append(String.format("\t\tLine: %d%n", lineNumber));
|
||||
}
|
||||
report.append(String.format("\t\tReplacement: %s%n", property.newProperty().toString()));
|
||||
});
|
||||
report.append(String.format("%n"));
|
||||
});
|
||||
|
||||
report.append(String.format("%n"));
|
||||
report.append("Each configuration key has been temporarily mapped to its "
|
||||
+ "replacement for your convenience. To silence this warning, please "
|
||||
+ "update your configuration to use the new keys.");
|
||||
report.append(String.format("%n"));
|
||||
logger.warn(report.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private record Migration(String originalPropertySourceName, ConfigurationPropertyName originalPropertyName,
|
||||
ConfigurationProperty originalProperty, ConfigurationPropertyName newProperty) {
|
||||
|
||||
private Integer determineLineNumber() {
|
||||
Origin origin = originalProperty.getOrigin();
|
||||
if (origin instanceof PropertySourceOrigin propertySourceOrigin) {
|
||||
origin = propertySourceOrigin.getOrigin();
|
||||
}
|
||||
if (origin instanceof TextResourceOrigin textOrigin) {
|
||||
if (textOrigin.getLocation() != null) {
|
||||
return textOrigin.getLocation().getLine() + 1;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -35,7 +35,7 @@ import org.springframework.web.reactive.DispatcherHandler;
|
||||
@AutoConfigureAfter(BindingServiceConfiguration.class)
|
||||
@AutoConfigureBefore({ HttpHandlerAutoConfiguration.class, GatewayAutoConfiguration.class })
|
||||
@ConditionalOnClass({ StreamBridge.class, DispatcherHandler.class })
|
||||
@ConditionalOnProperty(name = "spring.cloud.gateway.stream.enabled", matchIfMissing = true)
|
||||
@ConditionalOnProperty(name = GatewayProperties.PREFIX + ".stream.enabled", matchIfMissing = true)
|
||||
class GatewayStreamAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -27,7 +27,7 @@ import org.springframework.web.cors.CorsConfiguration;
|
||||
* Configuration properties for global configuration of cors. See
|
||||
* {@link RoutePredicateHandlerMapping}
|
||||
*/
|
||||
@ConfigurationProperties("spring.cloud.gateway.globalcors")
|
||||
@ConfigurationProperties(GatewayProperties.PREFIX + ".globalcors")
|
||||
public class GlobalCorsProperties {
|
||||
|
||||
private final Map<String, CorsConfiguration> corsConfigurations = new LinkedHashMap<>();
|
||||
|
||||
@@ -32,7 +32,7 @@ import org.springframework.validation.annotation.Validated;
|
||||
/**
|
||||
* Configuration properties for the Netty {@link reactor.netty.http.client.HttpClient}.
|
||||
*/
|
||||
@ConfigurationProperties("spring.cloud.gateway.httpclient")
|
||||
@ConfigurationProperties(GatewayProperties.PREFIX + ".httpclient")
|
||||
@Validated
|
||||
public class HttpClientProperties {
|
||||
|
||||
|
||||
@@ -108,17 +108,19 @@ public class LocalResponseCacheAutoConfiguration {
|
||||
super(ConfigurationPhase.REGISTER_BEAN);
|
||||
}
|
||||
|
||||
@ConditionalOnProperty(value = "spring.cloud.gateway.enabled", havingValue = "true", matchIfMissing = true)
|
||||
@ConditionalOnProperty(value = GatewayProperties.PREFIX + ".enabled", havingValue = "true",
|
||||
matchIfMissing = true)
|
||||
static class OnGatewayPropertyEnabled {
|
||||
|
||||
}
|
||||
|
||||
@ConditionalOnProperty(value = "spring.cloud.gateway.filter.local-response-cache.enabled", havingValue = "true")
|
||||
@ConditionalOnProperty(value = GatewayProperties.PREFIX + ".filter.local-response-cache.enabled",
|
||||
havingValue = "true")
|
||||
static class OnLocalResponseCachePropertyEnabled {
|
||||
|
||||
}
|
||||
|
||||
@ConditionalOnProperty(name = "spring.cloud.gateway.global-filter.local-response-cache.enabled",
|
||||
@ConditionalOnProperty(name = GatewayProperties.PREFIX + ".global-filter.local-response-cache.enabled",
|
||||
havingValue = "true", matchIfMissing = true)
|
||||
static class OnGlobalLocalResponseCachePropertyEnabled {
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping;
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(SimpleUrlHandlerMapping.class)
|
||||
@ConditionalOnProperty(name = "spring.cloud.gateway.globalcors.add-to-simple-url-handler-mapping",
|
||||
@ConditionalOnProperty(name = GatewayProperties.PREFIX + ".globalcors.add-to-simple-url-handler-mapping",
|
||||
matchIfMissing = false)
|
||||
public class SimpleUrlHandlerMappingGlobalCorsAutoConfiguration {
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ import static org.springframework.boot.autoconfigure.condition.ConditionMessage.
|
||||
|
||||
public abstract class OnEnabledComponent<T> extends SpringBootCondition implements ConfigurationCondition {
|
||||
|
||||
private static final String PREFIX = "spring.cloud.gateway.";
|
||||
private static final String PREFIX = "spring.cloud.gateway.server.webflux.";
|
||||
|
||||
private static final String SUFFIX = ".enabled";
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ import org.springframework.cloud.gateway.filter.FilterDefinition;
|
||||
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
|
||||
import org.springframework.core.style.ToStringCreator;
|
||||
|
||||
@ConfigurationProperties("spring.cloud.gateway.discovery.locator")
|
||||
@ConfigurationProperties("spring.cloud.gateway.server.webflux.discovery.locator")
|
||||
public class DiscoveryLocatorProperties {
|
||||
|
||||
/** Flag that enables DiscoveryClient gateway integration. */
|
||||
|
||||
@@ -45,7 +45,7 @@ import static org.springframework.cloud.gateway.support.NameUtils.normalizeRoute
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnProperty(name = "spring.cloud.gateway.enabled", matchIfMissing = true)
|
||||
@ConditionalOnProperty(name = "spring.cloud.gateway.server.webflux.enabled", matchIfMissing = true)
|
||||
@AutoConfigureBefore(GatewayAutoConfiguration.class)
|
||||
@AutoConfigureAfter(CompositeDiscoveryClientAutoConfiguration.class)
|
||||
@ConditionalOnClass({ DispatcherHandler.class, CompositeDiscoveryClientAutoConfiguration.class })
|
||||
@@ -92,7 +92,7 @@ public class GatewayDiscoveryClientAutoConfiguration {
|
||||
public static class ReactiveDiscoveryClientRouteDefinitionLocatorConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(name = "spring.cloud.gateway.discovery.locator.enabled")
|
||||
@ConditionalOnProperty(name = "spring.cloud.gateway.server.webflux.discovery.locator.enabled")
|
||||
public DiscoveryClientRouteDefinitionLocator discoveryClientRouteDefinitionLocator(
|
||||
ReactiveDiscoveryClient discoveryClient, DiscoveryLocatorProperties properties) {
|
||||
return new DiscoveryClientRouteDefinitionLocator(discoveryClient, properties);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2020 the original author or authors.
|
||||
* Copyright 2013-2025 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.
|
||||
@@ -254,7 +254,8 @@ public class NettyRoutingFilter implements GlobalFilter, Ordered {
|
||||
* @return the configured HttpClient.
|
||||
*/
|
||||
protected HttpClient getHttpClient(Route route, ServerWebExchange exchange) {
|
||||
Object connectTimeoutAttr = route.getMetadata().get(CONNECT_TIMEOUT_ATTR);
|
||||
Object connectTimeoutAttr = route.getMetadata().get(CONNECT_TIMEOUT_ATTR) != null
|
||||
? route.getMetadata().get(CONNECT_TIMEOUT_ATTR) : properties.getConnectTimeout();
|
||||
if (connectTimeoutAttr != null) {
|
||||
Integer connectTimeout = getInteger(connectTimeoutAttr);
|
||||
return this.httpClient.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectTimeout);
|
||||
|
||||
@@ -25,7 +25,7 @@ import java.util.Optional;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.springframework.cloud.gateway.config.GlobalCorsProperties;
|
||||
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
|
||||
import org.springframework.cloud.gateway.event.RefreshRoutesResultEvent;
|
||||
import org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping;
|
||||
import org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory;
|
||||
import org.springframework.cloud.gateway.route.Route;
|
||||
@@ -34,14 +34,14 @@ import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
|
||||
/**
|
||||
* This class updates Cors configuration each time a {@link RefreshRoutesEvent} is
|
||||
* This class updates Cors configuration each time a {@link RefreshRoutesResultEvent} is
|
||||
* consumed. The {@link Route}'s predicates are inspected for a
|
||||
* {@link PathRoutePredicateFactory} and the first pattern is used.
|
||||
*
|
||||
* @author Fredrich Ombico
|
||||
* @author Abel Salgado Romero
|
||||
*/
|
||||
public class CorsGatewayFilterApplicationListener implements ApplicationListener<RefreshRoutesEvent> {
|
||||
public class CorsGatewayFilterApplicationListener implements ApplicationListener<RefreshRoutesResultEvent> {
|
||||
|
||||
private final GlobalCorsProperties globalCorsProperties;
|
||||
|
||||
@@ -61,7 +61,7 @@ public class CorsGatewayFilterApplicationListener implements ApplicationListener
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(RefreshRoutesEvent event) {
|
||||
public void onApplicationEvent(RefreshRoutesResultEvent event) {
|
||||
routeLocator.getRoutes().collectList().subscribe(routes -> {
|
||||
// pre-populate with pre-existing global cors configurations to combine with.
|
||||
var corsConfigurations = new HashMap<>(globalCorsProperties.getCorsConfigurations());
|
||||
|
||||
@@ -16,12 +16,17 @@
|
||||
|
||||
package org.springframework.cloud.gateway.filter.factory;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilter;
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
||||
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
|
||||
import org.springframework.core.style.ToStringCreator;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
|
||||
import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator;
|
||||
@@ -31,6 +36,23 @@ import static org.springframework.cloud.gateway.support.GatewayToStringStyler.fi
|
||||
*/
|
||||
public class AddResponseHeaderGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory {
|
||||
|
||||
private static final String OVERRIDE_KEY = "override";
|
||||
|
||||
@Override
|
||||
public Class getConfigClass() {
|
||||
return Config.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NameValueConfig newConfig() {
|
||||
return new Config();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> shortcutFieldOrder() {
|
||||
return Arrays.asList(GatewayFilter.NAME_KEY, GatewayFilter.VALUE_KEY, OVERRIDE_KEY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GatewayFilter apply(NameValueConfig config) {
|
||||
return new GatewayFilter() {
|
||||
@@ -41,6 +63,13 @@ public class AddResponseHeaderGatewayFilterFactory extends AbstractNameValueGate
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (config instanceof Config) {
|
||||
return filterToStringCreator(AddResponseHeaderGatewayFilterFactory.this)
|
||||
.append(GatewayFilter.NAME_KEY, config.getName())
|
||||
.append(GatewayFilter.VALUE_KEY, config.getValue())
|
||||
.append(OVERRIDE_KEY, ((Config) config).isOverride())
|
||||
.toString();
|
||||
}
|
||||
return filterToStringCreator(AddResponseHeaderGatewayFilterFactory.this)
|
||||
.append(config.getName(), config.getValue())
|
||||
.toString();
|
||||
@@ -49,12 +78,51 @@ public class AddResponseHeaderGatewayFilterFactory extends AbstractNameValueGate
|
||||
}
|
||||
|
||||
void addHeader(ServerWebExchange exchange, NameValueConfig config) {
|
||||
final String value = ServerWebExchangeUtils.expand(exchange, config.getValue());
|
||||
HttpHeaders headers = exchange.getResponse().getHeaders();
|
||||
// if response has been commited, no more response headers will bee added.
|
||||
if (!exchange.getResponse().isCommitted()) {
|
||||
headers.add(config.getName(), value);
|
||||
final String value = ServerWebExchangeUtils.expand(exchange, config.getValue());
|
||||
HttpHeaders headers = exchange.getResponse().getHeaders();
|
||||
|
||||
boolean override = true; // default is true
|
||||
if (config instanceof Config) {
|
||||
override = ((Config) config).isOverride();
|
||||
}
|
||||
|
||||
if (override) {
|
||||
headers.add(config.getName(), value);
|
||||
}
|
||||
else {
|
||||
boolean headerIsMissingOrBlank = headers.getOrEmpty(config.getName())
|
||||
.stream()
|
||||
.allMatch(h -> !StringUtils.hasText(h));
|
||||
if (headerIsMissingOrBlank) {
|
||||
headers.add(config.getName(), value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class Config extends AbstractNameValueGatewayFilterFactory.NameValueConfig {
|
||||
|
||||
private boolean override = true;
|
||||
|
||||
public boolean isOverride() {
|
||||
return override;
|
||||
}
|
||||
|
||||
public Config setOverride(boolean override) {
|
||||
this.override = override;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringCreator(this).append(NAME_KEY, name)
|
||||
.append(VALUE_KEY, value)
|
||||
.append(OVERRIDE_KEY, override)
|
||||
.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2020 the original author or authors.
|
||||
* Copyright 2013-2025 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.
|
||||
@@ -22,7 +22,6 @@ import java.util.List;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@@ -34,6 +33,7 @@ import org.springframework.http.MediaType;
|
||||
|
||||
/**
|
||||
* @author Marta Medio
|
||||
* @author raccoonback
|
||||
*/
|
||||
public class RemoveJsonAttributesResponseBodyGatewayFilterFactory extends
|
||||
AbstractGatewayFilterFactory<RemoveJsonAttributesResponseBodyGatewayFilterFactory.FieldListConfiguration> {
|
||||
@@ -72,14 +72,14 @@ public class RemoveJsonAttributesResponseBodyGatewayFilterFactory extends
|
||||
RewriteFunction<String, String> rewriteFunction = (exchange, body) -> {
|
||||
if (MediaType.APPLICATION_JSON.isCompatibleWith(exchange.getResponse().getHeaders().getContentType())) {
|
||||
try {
|
||||
JsonNode jsonBodyContent = mapper.readValue(body, JsonNode.class);
|
||||
JsonNode jsonNode = mapper.readValue(body, JsonNode.class);
|
||||
|
||||
removeJsonAttribute(jsonBodyContent, config.getFieldList(), config.isDeleteRecursively());
|
||||
removeJsonAttributes(jsonNode, config.getFieldList(), config.isDeleteRecursively());
|
||||
|
||||
body = mapper.writeValueAsString(jsonBodyContent);
|
||||
body = mapper.writeValueAsString(jsonNode);
|
||||
}
|
||||
catch (JsonProcessingException e) {
|
||||
throw new RuntimeException(e);
|
||||
return Mono.error(new IllegalStateException("Failed to process JSON of response body.", e));
|
||||
}
|
||||
}
|
||||
return Mono.just(body);
|
||||
@@ -93,22 +93,12 @@ public class RemoveJsonAttributesResponseBodyGatewayFilterFactory extends
|
||||
|
||||
private ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
private void removeJsonAttribute(JsonNode jsonBodyContent, List<String> fieldsToRemove, boolean deleteRecursively) {
|
||||
if (deleteRecursively) {
|
||||
for (JsonNode jsonNode : jsonBodyContent) {
|
||||
if (jsonNode instanceof ObjectNode) {
|
||||
((ObjectNode) jsonNode).remove(fieldsToRemove);
|
||||
removeJsonAttribute(jsonNode, fieldsToRemove, true);
|
||||
}
|
||||
if (jsonNode instanceof ArrayNode) {
|
||||
for (JsonNode node : jsonNode) {
|
||||
removeJsonAttribute(node, fieldsToRemove, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
private void removeJsonAttributes(JsonNode jsonNode, List<String> fieldNames, boolean deleteRecursively) {
|
||||
if (jsonNode instanceof ObjectNode objectNode) {
|
||||
objectNode.remove(fieldNames);
|
||||
}
|
||||
if (jsonBodyContent instanceof ObjectNode) {
|
||||
((ObjectNode) jsonBodyContent).remove(fieldsToRemove);
|
||||
if (deleteRecursively) {
|
||||
jsonNode.forEach(childNode -> removeJsonAttributes(childNode, fieldNames, true));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,12 +24,12 @@ import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilter;
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
||||
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
import org.springframework.web.util.UriUtils;
|
||||
|
||||
import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator;
|
||||
import static org.springframework.util.CollectionUtils.unmodifiableMultiValueMap;
|
||||
@@ -59,7 +59,8 @@ public class RemoveRequestParameterGatewayFilterFactory
|
||||
queryParams.remove(config.getName());
|
||||
|
||||
try {
|
||||
MultiValueMap<String, String> encodedQueryParams = UriUtils.encodeQueryParams(queryParams);
|
||||
MultiValueMap<String, String> encodedQueryParams = ServerWebExchangeUtils
|
||||
.encodeQueryParams(queryParams);
|
||||
URI newUri = UriComponentsBuilder.fromUri(request.getURI())
|
||||
.replaceQueryParams(unmodifiableMultiValueMap(encodedQueryParams))
|
||||
.build(true)
|
||||
|
||||
@@ -47,8 +47,11 @@ public class RemoveResponseHeaderGatewayFilterFactory
|
||||
return new GatewayFilter() {
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
||||
return chain.filter(exchange)
|
||||
.then(Mono.fromRunnable(() -> exchange.getResponse().getHeaders().remove(config.getName())));
|
||||
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
|
||||
if (!exchange.getResponse().isCommitted()) {
|
||||
exchange.getResponse().getHeaders().remove(config.getName());
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -34,7 +34,7 @@ import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.s
|
||||
* User Request Rate Limiter filter. See https://stripe.com/blog/rate-limiters and
|
||||
* https://gist.github.com/ptarjan/e38f45f2dfe601419ca3af937fff574d#file-1-check_request_rate_limiter-rb-L11-L34.
|
||||
*/
|
||||
@ConfigurationProperties("spring.cloud.gateway.filter.request-rate-limiter")
|
||||
@ConfigurationProperties("spring.cloud.gateway.server.webflux.filter.request-rate-limiter")
|
||||
public class RequestRateLimiterGatewayFilterFactory
|
||||
extends AbstractGatewayFilterFactory<RequestRateLimiterGatewayFilterFactory.Config> {
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user