diff --git a/docs/modules/ROOT/pages/spring-cloud-gateway-server-webflux/gatewayfilter-factories/secureheaders-factory.adoc b/docs/modules/ROOT/pages/spring-cloud-gateway-server-webflux/gatewayfilter-factories/secureheaders-factory.adoc index 12177b8a..6bc7077f 100644 --- a/docs/modules/ROOT/pages/spring-cloud-gateway-server-webflux/gatewayfilter-factories/secureheaders-factory.adoc +++ b/docs/modules/ROOT/pages/spring-cloud-gateway-server-webflux/gatewayfilter-factories/secureheaders-factory.adoc @@ -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)` diff --git a/docs/modules/ROOT/pages/spring-cloud-gateway-server-webflux/request-predicates-factories.adoc b/docs/modules/ROOT/pages/spring-cloud-gateway-server-webflux/request-predicates-factories.adoc index 93a1e10e..2a786bb9 100644 --- a/docs/modules/ROOT/pages/spring-cloud-gateway-server-webflux/request-predicates-factories.adoc +++ b/docs/modules/ROOT/pages/spring-cloud-gateway-server-webflux/request-predicates-factories.adoc @@ -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 <> diff --git a/docs/modules/ROOT/pages/spring-cloud-gateway-server-webmvc/writing-custom-predicates-and-filters.adoc b/docs/modules/ROOT/pages/spring-cloud-gateway-server-webmvc/writing-custom-predicates-and-filters.adoc index 597b00fe..1364150e 100644 --- a/docs/modules/ROOT/pages/spring-cloud-gateway-server-webmvc/writing-custom-predicates-and-filters.adoc +++ b/docs/modules/ROOT/pages/spring-cloud-gateway-server-webmvc/writing-custom-predicates-and-filters.adoc @@ -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] diff --git a/docs/modules/ROOT/partials/_configprops.adoc b/docs/modules/ROOT/partials/_configprops.adoc index a92c4dd8..d4660a70 100644 --- a/docs/modules/ROOT/partials/_configprops.adoc +++ b/docs/modules/ROOT/partials/_configprops.adoc @@ -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,6 +53,7 @@ |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 | | @@ -80,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. @@ -96,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. @@ -103,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 @@ -114,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. @@ -129,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. @@ -165,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. @@ -176,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. diff --git a/docs/package.json b/docs/package.json index 6c97a4cc..1e4c440b 100644 --- a/docs/package.json +++ b/docs/package.json @@ -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" } } diff --git a/spring-cloud-gateway-integration-tests/grpc/pom.xml b/spring-cloud-gateway-integration-tests/grpc/pom.xml index 6842696a..7fb5e368 100644 --- a/spring-cloud-gateway-integration-tests/grpc/pom.xml +++ b/spring-cloud-gateway-integration-tests/grpc/pom.xml @@ -12,7 +12,7 @@ 3.25.1 - 1.71.0 + 1.72.0 diff --git a/spring-cloud-gateway-integration-tests/grpc/src/test/resources/application.yml b/spring-cloud-gateway-integration-tests/grpc/src/test/resources/application.yml index 97c1b950..1edaa78e 100644 --- a/spring-cloud-gateway-integration-tests/grpc/src/test/resources/application.yml +++ b/spring-cloud-gateway-integration-tests/grpc/src/test/resources/application.yml @@ -20,7 +20,7 @@ management: spring: cloud: - gateway: + gateway.server.webflux: httpserver: wiretap: true httpclient: diff --git a/spring-cloud-gateway-integration-tests/http2/src/main/resources/application.yml b/spring-cloud-gateway-integration-tests/http2/src/main/resources/application.yml index 8438b1d5..4db549c3 100644 --- a/spring-cloud-gateway-integration-tests/http2/src/main/resources/application.yml +++ b/spring-cloud-gateway-integration-tests/http2/src/main/resources/application.yml @@ -13,7 +13,7 @@ server: spring: cloud: - gateway: + gateway.server.webflux: # httpserver: # wiretap: true httpclient: diff --git a/spring-cloud-gateway-integration-tests/http2/src/test/resources/application-nossl.yml b/spring-cloud-gateway-integration-tests/http2/src/test/resources/application-nossl.yml index 6143bf9d..4b2cf96a 100644 --- a/spring-cloud-gateway-integration-tests/http2/src/test/resources/application-nossl.yml +++ b/spring-cloud-gateway-integration-tests/http2/src/test/resources/application-nossl.yml @@ -9,5 +9,5 @@ server: spring: cloud: - gateway: + gateway.server.webflux: enabled: false \ No newline at end of file diff --git a/spring-cloud-gateway-integration-tests/httpclient/pom.xml b/spring-cloud-gateway-integration-tests/httpclient/pom.xml new file mode 100644 index 00000000..f10b23de --- /dev/null +++ b/spring-cloud-gateway-integration-tests/httpclient/pom.xml @@ -0,0 +1,77 @@ + + + 4.0.0 + + httpclient + jar + + Spring Cloud Gateway HttpClient Integration Test + Spring Cloud Gateway HttpClient Integration Test + + + + + + org.springframework.cloud + spring-cloud-gateway-integration-tests + 4.3.0-SNAPSHOT + .. + + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-webflux + + + + org.springframework.cloud + spring-cloud-starter-gateway-mvc + + + + org.springframework.retry + spring-retry + + + + org.springframework.cloud + spring-cloud-starter-loadbalancer + + + + org.apache.httpcomponents.client5 + httpclient5 + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.assertj + assertj-core + test + + + + + + + maven-deploy-plugin + + true + + + + + diff --git a/spring-cloud-gateway-integration-tests/httpclient/src/main/java/org/springframework/cloud/gateway/tests/httpclient/HttpClientApplication.java b/spring-cloud-gateway-integration-tests/httpclient/src/main/java/org/springframework/cloud/gateway/tests/httpclient/HttpClientApplication.java new file mode 100644 index 00000000..9390e611 --- /dev/null +++ b/spring-cloud-gateway-integration-tests/httpclient/src/main/java/org/springframework/cloud/gateway/tests/httpclient/HttpClientApplication.java @@ -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 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 map = new ConcurrentHashMap<>(); + + @GetMapping("/do/retry") + public ResponseEntity 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)); + } + +} diff --git a/spring-cloud-gateway-integration-tests/httpclient/src/main/resources/application.yml b/spring-cloud-gateway-integration-tests/httpclient/src/main/resources/application.yml new file mode 100644 index 00000000..106caafa --- /dev/null +++ b/spring-cloud-gateway-integration-tests/httpclient/src/main/resources/application.yml @@ -0,0 +1,3 @@ +logging: + level: + org.springframework.cloud.gateway: TRACE diff --git a/spring-cloud-gateway-integration-tests/httpclient/src/test/java/org/springframework/cloud/gateway/tests/httpclient/HttpClientApplicationTests.java b/spring-cloud-gateway-integration-tests/httpclient/src/test/java/org/springframework/cloud/gateway/tests/httpclient/HttpClientApplicationTests.java new file mode 100644 index 00000000..13269d41 --- /dev/null +++ b/spring-cloud-gateway-integration-tests/httpclient/src/test/java/org/springframework/cloud/gateway/tests/httpclient/HttpClientApplicationTests.java @@ -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"); + } + +} diff --git a/spring-cloud-gateway-integration-tests/mvc-failure-analyzer/src/test/java/org/springframework/cloud/gateway/sample/MvcFailureAnalyzerApplicationTests.java b/spring-cloud-gateway-integration-tests/mvc-failure-analyzer/src/test/java/org/springframework/cloud/gateway/sample/MvcFailureAnalyzerApplicationTests.java index ea702715..f9a33ba3 100644 --- a/spring-cloud-gateway-integration-tests/mvc-failure-analyzer/src/test/java/org/springframework/cloud/gateway/sample/MvcFailureAnalyzerApplicationTests.java +++ b/spring-cloud-gateway-integration-tests/mvc-failure-analyzer/src/test/java/org/springframework/cloud/gateway/sample/MvcFailureAnalyzerApplicationTests.java @@ -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); } diff --git a/spring-cloud-gateway-integration-tests/pom.xml b/spring-cloud-gateway-integration-tests/pom.xml index 85b3c76d..6c0074b1 100644 --- a/spring-cloud-gateway-integration-tests/pom.xml +++ b/spring-cloud-gateway-integration-tests/pom.xml @@ -24,6 +24,7 @@ grpc http2 mvc-failure-analyzer + httpclient diff --git a/spring-cloud-gateway-mvc/pom.xml b/spring-cloud-gateway-mvc/pom.xml index 14d0d2aa..e339f496 100644 --- a/spring-cloud-gateway-mvc/pom.xml +++ b/spring-cloud-gateway-mvc/pom.xml @@ -41,6 +41,11 @@ 4.5.14 test + + org.springframework.boot + spring-boot-properties-migrator + test + org.springframework.boot spring-boot-configuration-processor diff --git a/spring-cloud-gateway-mvc/src/main/java/org/springframework/cloud/gateway/mvc/config/ProxyExchangeWebMvcProperties.java b/spring-cloud-gateway-mvc/src/main/java/org/springframework/cloud/gateway/mvc/config/ProxyExchangeWebMvcProperties.java new file mode 100644 index 00000000..12e75015 --- /dev/null +++ b/spring-cloud-gateway-mvc/src/main/java/org/springframework/cloud/gateway/mvc/config/ProxyExchangeWebMvcProperties.java @@ -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 + * @RequestMapping 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 DEFAULT_SENSITIVE = Set.of("cookie", "authorization"); + + /** + * Contains headers that are skipped by default. + */ + public static Set DEFAULT_SKIPPED = Set.of("content-length", "host"); + + /** + * Fixed header values that will be added to all downstream requests. + */ + private Map headers = new LinkedHashMap<>(); + + /** + * A set of header names that should be sent downstream by default. + */ + private Set autoForward = new HashSet<>(); + + /** + * A set of sensitive header names that will not be sent downstream by default. + */ + private Set sensitive = DEFAULT_SENSITIVE; + + /** + * A set of header names that will not be sent downstream because they could be + * problematic. + */ + private Set skipped = DEFAULT_SKIPPED; + + public Map getHeaders() { + return headers; + } + + public void setHeaders(Map headers) { + this.headers = headers; + } + + public Set getAutoForward() { + return autoForward; + } + + public void setAutoForward(Set autoForward) { + this.autoForward = autoForward; + } + + public Set getSensitive() { + return sensitive; + } + + public void setSensitive(Set sensitive) { + this.sensitive = sensitive; + } + + public Set getSkipped() { + return skipped; + } + + public void setSkipped(Set 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; + } + +} diff --git a/spring-cloud-gateway-mvc/src/main/java/org/springframework/cloud/gateway/mvc/config/ProxyProperties.java b/spring-cloud-gateway-mvc/src/main/java/org/springframework/cloud/gateway/mvc/config/ProxyProperties.java index 59296583..e9b546bf 100644 --- a/spring-cloud-gateway-mvc/src/main/java/org/springframework/cloud/gateway/mvc/config/ProxyProperties.java +++ b/spring-cloud-gateway-mvc/src/main/java/org/springframework/cloud/gateway/mvc/config/ProxyProperties.java @@ -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 skipped = DEFAULT_SKIPPED; + @DeprecatedConfigurationProperty(replacement = ProxyExchangeWebMvcProperties.PREFIX + ".headers", since = "4.3.0") public Map 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 getAutoForward() { return autoForward; } @@ -84,6 +90,7 @@ public class ProxyProperties { this.autoForward = autoForward; } + @DeprecatedConfigurationProperty(replacement = ProxyExchangeWebMvcProperties.PREFIX + ".sensitive", since = "4.3.0") public Set getSensitive() { return sensitive; } @@ -92,6 +99,7 @@ public class ProxyProperties { this.sensitive = sensitive; } + @DeprecatedConfigurationProperty(replacement = ProxyExchangeWebMvcProperties.PREFIX + ".skipped", since = "4.3.0") public Set getSkipped() { return skipped; } diff --git a/spring-cloud-gateway-mvc/src/main/java/org/springframework/cloud/gateway/mvc/config/ProxyResponseAutoConfiguration.java b/spring-cloud-gateway-mvc/src/main/java/org/springframework/cloud/gateway/mvc/config/ProxyResponseAutoConfiguration.java index 0a840d78..b7cfeb18 100644 --- a/spring-cloud-gateway-mvc/src/main/java/org/springframework/cloud/gateway/mvc/config/ProxyResponseAutoConfiguration.java +++ b/spring-cloud-gateway-mvc/src/main/java/org/springframework/cloud/gateway/mvc/config/ProxyResponseAutoConfiguration.java @@ -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 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 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; diff --git a/spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/config/ProxyExchangeWebmvcPropertiesMigrationTests.java b/spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/config/ProxyExchangeWebmvcPropertiesMigrationTests.java new file mode 100644 index 00000000..4c15b7f3 --- /dev/null +++ b/spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/config/ProxyExchangeWebmvcPropertiesMigrationTests.java @@ -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 { + + } + +} diff --git a/spring-cloud-gateway-mvc/src/test/resources/application-propertiesmigrationtests.yml b/spring-cloud-gateway-mvc/src/test/resources/application-propertiesmigrationtests.yml new file mode 100644 index 00000000..d6b6dfe5 --- /dev/null +++ b/spring-cloud-gateway-mvc/src/test/resources/application-propertiesmigrationtests.yml @@ -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 diff --git a/spring-cloud-gateway-sample/src/main/resources/application-secureheaders.yml b/spring-cloud-gateway-sample/src/main/resources/application-secureheaders.yml index ddae382f..c6952c89 100644 --- a/spring-cloud-gateway-sample/src/main/resources/application-secureheaders.yml +++ b/spring-cloud-gateway-sample/src/main/resources/application-secureheaders.yml @@ -7,7 +7,7 @@ test: spring: cloud: - gateway: + gateway.server.webflux: filter: default-filters: #- PrefixPath=/httpbin diff --git a/spring-cloud-gateway-sample/src/main/resources/application.yml b/spring-cloud-gateway-sample/src/main/resources/application.yml index 4cf839df..10339112 100644 --- a/spring-cloud-gateway-sample/src/main/resources/application.yml +++ b/spring-cloud-gateway-sample/src/main/resources/application.yml @@ -9,7 +9,7 @@ spring: jmx: enabled: false cloud: - gateway: + gateway.server.webflux: default-filters: - PrefixPath=/httpbin - AddResponseHeader=X-Response-Default-Foo, Default-Bar diff --git a/spring-cloud-gateway-server-mvc/pom.xml b/spring-cloud-gateway-server-mvc/pom.xml index e9f6b80c..ad20bf03 100644 --- a/spring-cloud-gateway-server-mvc/pom.xml +++ b/spring-cloud-gateway-server-mvc/pom.xml @@ -107,6 +107,16 @@ org.springframework.cloud spring-cloud-starter-stream-rabbit + + + io.projectreactor.netty + reactor-netty + + + org.springframework.boot + spring-boot-starter-webflux + + test diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/GatewayServerMvcAutoConfiguration.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/GatewayServerMvcAutoConfiguration.java index dabf7d2d..37cd5245 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/GatewayServerMvcAutoConfiguration.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/GatewayServerMvcAutoConfiguration.java @@ -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,7 +76,8 @@ import org.springframework.web.client.RestClient; * @author Jürgen Wißkirchen */ @AutoConfiguration(after = { HttpClientAutoConfiguration.class, RestTemplateAutoConfiguration.class, - RestClientAutoConfiguration.class }) + RestClientAutoConfiguration.class, FilterAutoConfiguration.class, HandlerFunctionAutoConfiguration.class, + PredicateAutoConfiguration.class }) @ConditionalOnProperty(name = "spring.cloud.gateway.mvc.enabled", matchIfMissing = true) @Import(GatewayMvcPropertiesBeanDefinitionRegistrar.class) @ImportRuntimeHints(GatewayMvcAotRuntimeHintsRegistrar.class) @@ -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) { diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/BeanFactoryGatewayDiscoverer.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/BeanFactoryGatewayDiscoverer.java new file mode 100644 index 00000000..5a519573 --- /dev/null +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/BeanFactoryGatewayDiscoverer.java @@ -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 List loadSuppliers(Class supplierClass) { + ObjectProvider beanProvider = beanFactory.getBeanProvider(supplierClass); + return beanProvider.orderedStream().toList(); + } + +} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/MvcUtils.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/MvcUtils.java index 05c073ed..69fcb1cd 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/MvcUtils.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/MvcUtils.java @@ -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 encodeQueryParams(MultiValueMap params) { + MultiValueMap encodedQueryParams = new LinkedMultiValueMap<>(params.size()); + for (Map.Entry> 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 diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/GatewayMvcAotRuntimeHintsRegistrar.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/GatewayMvcAotRuntimeHintsRegistrar.java index e4482d3a..c171c240 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/GatewayMvcAotRuntimeHintsRegistrar.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/GatewayMvcAotRuntimeHintsRegistrar.java @@ -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> 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> PROPERTIES = Set.of(FilterProperties.class, PredicateProperties.class, RouteProperties.class); diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/GatewayMvcProperties.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/GatewayMvcProperties.java index 728ee4b8..fac05cb0 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/GatewayMvcProperties.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/GatewayMvcProperties.java @@ -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. diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/GatewayServerWebMvcPropertiesMigrationListener.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/GatewayServerWebMvcPropertiesMigrationListener.java new file mode 100644 index 00000000..8fdf6de4 --- /dev/null +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/GatewayServerWebMvcPropertiesMigrationListener.java @@ -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 { + + 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 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 migrate(ConfigurableEnvironment env, ConfigurationPropertySource propertySource, + String propertySourcePrefix, String deprecatedKey, String newKeyPrefix) { + List migrations = new ArrayList<>(); + + if (propertySource instanceof IterableConfigurationPropertySource iterableSource) { + ConfigurationPropertyName routesParentName = ConfigurationPropertyName.of(deprecatedKey); + List 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 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 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; + } + } + +} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/RouterFunctionHolderFactory.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/RouterFunctionHolderFactory.java index 6bc52a52..0973f657 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/RouterFunctionHolderFactory.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/RouterFunctionHolderFactory.java @@ -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 handlerArgs = new HashMap<>(); - Optional 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 handlerFunction = null; // filters added by HandlerDiscoverer need to go last, so save them + HandlerFunction handlerFunction = null; List> lowerPrecedenceFilters = new ArrayList<>(); List> higherPrecedenceFilters = new ArrayList<>(); - if (response instanceof HandlerFunction) { - handlerFunction = (HandlerFunction) 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 handlerArgs = new HashMap<>(); + Optional 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) 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 predicateOperations = predicateDiscoverer.getOperations(); + MultiValueMap predicateOperations = new LinkedMultiValueMap<>(); + if (predicateBeanFactoryDiscoverer != null) { + predicateOperations.addAll(predicateBeanFactoryDiscoverer.getOperations()); + } + predicateOperations.addAll(predicateDiscoverer.getOperations()); final AtomicReference predicate = new AtomicReference<>(); routeProperties.getPredicates().forEach(predicateProperties -> { @@ -214,7 +277,11 @@ public class RouterFunctionHolderFactory { lowerPrecedenceFilters.forEach(builder::filter); // translate filters - MultiValueMap filterOperations = filterDiscoverer.getOperations(); + MultiValueMap filterOperations = new LinkedMultiValueMap<>(); + if (filterBeanFactoryDiscoverer != null) { + filterOperations.addAll(filterBeanFactoryDiscoverer.getOperations()); + } + filterOperations.addAll(filterDiscoverer.getOperations()); routeProperties.getFilters().forEach(filterProperties -> { Map 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 args, + private Object bindConfigurable(OperationMethod operationMethod, Map 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 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; } diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/AfterFilterFunctions.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/AfterFilterFunctions.java index 695d3fce..14795efa 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/AfterFilterFunctions.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/AfterFilterFunctions.java @@ -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 removeJsonAttributesResponseBody( + List fieldList, boolean deleteRecursively) { + List 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 fieldNames, boolean deleteRecursively) { + if (jsonNode instanceof ObjectNode objectNode) { + objectNode.remove(fieldNames); + } + if (deleteRecursively) { + jsonNode.forEach(childNode -> removeJsonAttributes(childNode, fieldNames, true)); + } + } + public enum DedupeStrategy { /** diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/BeforeFilterFunctions.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/BeforeFilterFunctions.java index 6cd2eeed..6fb71b8d 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/BeforeFilterFunctions.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/BeforeFilterFunctions.java @@ -195,7 +195,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(); }; } diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/FilterAutoConfiguration.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/FilterAutoConfiguration.java new file mode 100644 index 00000000..8f4126cf --- /dev/null +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/FilterAutoConfiguration.java @@ -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 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(); + } + + } + +} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/FilterBeanFactoryDiscoverer.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/FilterBeanFactoryDiscoverer.java new file mode 100644 index 00000000..cb9f47f0 --- /dev/null +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/FilterBeanFactoryDiscoverer.java @@ -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); + } + +} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/LoadBalancerHandlerSupplier.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/LoadBalancerHandlerSupplier.java deleted file mode 100644 index c2a0ad58..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/LoadBalancerHandlerSupplier.java +++ /dev/null @@ -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 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()))); - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/RetryFilterFunctions.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/RetryFilterFunctions.java index 272b1e6c..4de07b1d 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/RetryFilterFunctions.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/RetryFilterFunctions.java @@ -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); } diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/XForwardedRequestHeadersFilter.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/XForwardedRequestHeadersFilter.java index bee93756..da95fa4b 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/XForwardedRequestHeadersFilter.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/XForwardedRequestHeadersFilter.java @@ -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()); } } } diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/ClientHttpRequestFactoryProxyExchange.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/ClientHttpRequestFactoryProxyExchange.java index 5c17dd2d..db1685d9 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/ClientHttpRequestFactoryProxyExchange.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/ClientHttpRequestFactoryProxyExchange.java @@ -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) { diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/DefaultHandlerSupplier.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/DefaultHandlerSupplier.java deleted file mode 100644 index 0adc142e..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/DefaultHandlerSupplier.java +++ /dev/null @@ -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 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 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 handlerFunction) { - HandlerFilterFunction setId = setIdFilter(id); - HandlerFilterFunction setRequest = setRequestUrlFilter(uri); - return new HandlerDiscoverer.Result(handlerFunction, Arrays.asList(setId, setRequest), Collections.emptyList()); - } - - private static HandlerFilterFunction setIdFilter(String id) { - return (request, next) -> { - MvcUtils.setRouteId(request, id); - return next.handle(request); - }; - } - - private static HandlerFilterFunction setRequestUrlFilter(URI uri) { - return (request, next) -> { - MvcUtils.setRequestUrl(request, uri); - return next.handle(request); - }; - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/HandlerFunctionAutoConfiguration.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/HandlerFunctionAutoConfiguration.java new file mode 100644 index 00000000..3e4d3781 --- /dev/null +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/HandlerFunctionAutoConfiguration.java @@ -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 fnHandlerFunctionDefinition() { + return routeProperties -> new HandlerFunctionDefinition.Default("fn", + HandlerFunctions.fn(routeProperties.getUri().getSchemeSpecificPart())); + } + + @Bean + public Function forwardHandlerFunctionDefinition() { + return routeProperties -> new HandlerFunctionDefinition.Default("forward", + HandlerFunctions.forward(routeProperties.getUri().getPath())); + } + + @Bean + public Function httpHandlerFunctionDefinition() { + return routeProperties -> getResult("http", routeProperties.getId(), routeProperties.getUri(), + HandlerFunctions.http()); + } + + @Bean + public Function httpsHandlerFunctionDefinition() { + return routeProperties -> getResult("https", routeProperties.getId(), routeProperties.getUri(), + HandlerFunctions.https()); + } + + @Bean + public Function noHandlerFunctionDefinition() { + return routeProperties -> getResult("no", routeProperties.getId(), routeProperties.getUri(), + HandlerFunctions.no()); + } + + @Bean + public Function streamHandlerFunctionDefinition() { + return routeProperties -> new HandlerFunctionDefinition.Default("stream", + HandlerFunctions.stream(routeProperties.getUri().getSchemeSpecificPart())); + } + + private static HandlerFunctionDefinition getResult(String scheme, String id, URI uri, + HandlerFunction handlerFunction) { + HandlerFilterFunction setId = setIdFilter(id); + HandlerFilterFunction setRequest = setRequestUrlFilter(uri); + return new HandlerFunctionDefinition.Default(scheme, handlerFunction, Arrays.asList(setId, setRequest), + Collections.emptyList()); + } + + private static HandlerFilterFunction setIdFilter(String id) { + return (request, next) -> { + MvcUtils.setRouteId(request, id); + return next.handle(request); + }; + } + + private static HandlerFilterFunction setRequestUrlFilter(URI uri) { + return (request, next) -> { + MvcUtils.setRequestUrl(request, uri); + return next.handle(request); + }; + } + +} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/HandlerFunctionDefinition.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/HandlerFunctionDefinition.java new file mode 100644 index 00000000..12d79fe6 --- /dev/null +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/HandlerFunctionDefinition.java @@ -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 handlerFunction(); + + List> lowerPrecedenceFilters(); + + List> higherPrecedenceFilters(); + + record Default(String scheme, HandlerFunction handlerFunction, + List> lowerPrecedenceFilters, + List> higherPrecedenceFilters) + implements + HandlerFunctionDefinition { + + public Default(String scheme, HandlerFunction handlerFunction) { + this(scheme, handlerFunction, Collections.emptyList(), Collections.emptyList()); + } + } + +} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/ProxyExchangeHandlerFunction.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/ProxyExchangeHandlerFunction.java index 0ea445fc..6923f2d6 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/ProxyExchangeHandlerFunction.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/ProxyExchangeHandlerFunction.java @@ -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, ApplicationListener { @@ -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 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 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 { } diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/RestClientProxyExchange.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/RestClientProxyExchange.java index 46e9d032..315a061f 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/RestClientProxyExchange.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/RestClientProxyExchange.java @@ -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) { diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/predicate/GatewayRequestPredicates.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/predicate/GatewayRequestPredicates.java index 68f54b73..c25671da 100644 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/predicate/GatewayRequestPredicates.java +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/predicate/GatewayRequestPredicates.java @@ -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 diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/predicate/PredicateAutoConfiguration.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/predicate/PredicateAutoConfiguration.java new file mode 100644 index 00000000..4a3c612d --- /dev/null +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/predicate/PredicateAutoConfiguration.java @@ -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(); + } + +} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/predicate/PredicateBeanFactoryDiscoverer.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/predicate/PredicateBeanFactoryDiscoverer.java new file mode 100644 index 00000000..90f77967 --- /dev/null +++ b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/predicate/PredicateBeanFactoryDiscoverer.java @@ -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); + } + +} diff --git a/spring-cloud-gateway-server-mvc/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-cloud-gateway-server-mvc/src/main/resources/META-INF/additional-spring-configuration-metadata.json index ff054ce3..ce35f3ef 100644 --- a/spring-cloud-gateway-server-mvc/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-cloud-gateway-server-mvc/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -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", + "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", + "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", + "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": {} } ] } diff --git a/spring-cloud-gateway-server-mvc/src/main/resources/META-INF/spring.factories b/spring-cloud-gateway-server-mvc/src/main/resources/META-INF/spring.factories index b7002f15..6ce983dc 100644 --- a/spring-cloud-gateway-server-mvc/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-gateway-server-mvc/src/main/resources/META-INF/spring.factories @@ -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 \ No newline at end of file diff --git a/spring-cloud-gateway-server-mvc/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-cloud-gateway-server-mvc/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 94282943..81772855 100644 --- a/spring-cloud-gateway-server-mvc/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/spring-cloud-gateway-server-mvc/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -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 \ No newline at end of file diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/GatewayMvcPropertiesMigrationTests.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/GatewayMvcPropertiesMigrationTests.java new file mode 100644 index 00000000..2043fc73 --- /dev/null +++ b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/GatewayMvcPropertiesMigrationTests.java @@ -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 { + + } + +} diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/GatewayServerMvcAutoConfigurationTests.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/GatewayServerMvcAutoConfigurationTests.java index 7eb56405..01f8f9f4 100644 --- a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/GatewayServerMvcAutoConfigurationTests.java +++ b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/GatewayServerMvcAutoConfigurationTests.java @@ -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 { diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/ServerMvcIntegrationTests.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/ServerMvcIntegrationTests.java index f72b1943..4efa9b8c 100644 --- a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/ServerMvcIntegrationTests.java +++ b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/ServerMvcIntegrationTests.java @@ -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) diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/ServerMvcLoadBalancerIntegrationTests.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/ServerMvcLoadBalancerIntegrationTests.java new file mode 100644 index 00000000..d2e5889b --- /dev/null +++ b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/ServerMvcLoadBalancerIntegrationTests.java @@ -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 { + + } + +} diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/config/GatewayMvcPropertiesBeanDefinitionRegistrarTests.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/config/GatewayMvcPropertiesBeanDefinitionRegistrarTests.java index 41f6bff8..f0d34697 100644 --- a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/config/GatewayMvcPropertiesBeanDefinitionRegistrarTests.java +++ b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/config/GatewayMvcPropertiesBeanDefinitionRegistrarTests.java @@ -199,14 +199,13 @@ public class GatewayMvcPropertiesBeanDefinitionRegistrarTests { void refreshWorks(ConfigurableApplicationContext context) { Map 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(); diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/filter/AfterFilterFunctionsTests.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/filter/AfterFilterFunctionsTests.java new file mode 100644 index 00000000..cbb6e0a6 --- /dev/null +++ b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/filter/AfterFilterFunctionsTests.java @@ -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) 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 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 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 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 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 handleIllegalException(IllegalStateException ex) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(ex.getMessage()); + } + + } + + } + +} diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/filter/BeforeFilterFunctionsTests.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/filter/BeforeFilterFunctionsTests.java index 2f23adc2..0608ca9a 100644 --- a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/filter/BeforeFilterFunctionsTests.java +++ b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/filter/BeforeFilterFunctionsTests.java @@ -245,4 +245,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"); + } + } diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/filter/FilterDiscovererTests.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/filter/FilterDiscovererTests.java new file mode 100644 index 00000000..0edce139 --- /dev/null +++ b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/filter/FilterDiscovererTests.java @@ -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 operations = new FilterDiscoverer().getOperations(); + assertThat(operations).isNotEmpty(); + } + + @SpringBootConfiguration + @EnableAutoConfiguration + static class Config { + + } + +} diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/handler/ProxyExchangeHandlerFunctionTest.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/handler/ProxyExchangeHandlerFunctionTest.java new file mode 100644 index 00000000..e8f650c7 --- /dev/null +++ b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/handler/ProxyExchangeHandlerFunctionTest.java @@ -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 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 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; + } + + } + +} diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/predicate/PredicateBeanFactoryDiscovererTests.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/predicate/PredicateBeanFactoryDiscovererTests.java new file mode 100644 index 00000000..dcbe4ba1 --- /dev/null +++ b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/predicate/PredicateBeanFactoryDiscovererTests.java @@ -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 operations = discoverer.getOperations(); + assertThat(operations).isNotEmpty(); + } + + @SpringBootConfiguration + @EnableAutoConfiguration + static class Config { + + } + +} diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/TestPredicateSupplier.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/TestPredicateSupplier.java new file mode 100644 index 00000000..2ace83bd --- /dev/null +++ b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/TestPredicateSupplier.java @@ -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 get() { + return List.of(TestPredicateSupplier.class.getMethods()); + } + +} diff --git a/spring-cloud-gateway-server-mvc/src/test/resources/META-INF/spring.factories b/spring-cloud-gateway-server-mvc/src/test/resources/META-INF/spring.factories index bca78c94..b99d378a 100644 --- a/spring-cloud-gateway-server-mvc/src/test/resources/META-INF/spring.factories +++ b/spring-cloud-gateway-server-mvc/src/test/resources/META-INF/spring.factories @@ -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 \ No newline at end of file diff --git a/spring-cloud-gateway-server-mvc/src/test/resources/application-lb.yml b/spring-cloud-gateway-server-mvc/src/test/resources/application-lb.yml new file mode 100644 index 00000000..9d0437b3 --- /dev/null +++ b/spring-cloud-gateway-server-mvc/src/test/resources/application-lb.yml @@ -0,0 +1,11 @@ +spring: + cloud: + gateway: + mvc: + routes: + - id: test + uri: lb://httpbin + predicates: + - Path=/test/** + filters: + - StripPrefix=1 \ No newline at end of file diff --git a/spring-cloud-gateway-server-mvc/src/test/resources/application-propertiesmigrationtests.yml b/spring-cloud-gateway-server-mvc/src/test/resources/application-propertiesmigrationtests.yml new file mode 100644 index 00000000..32dc4133 --- /dev/null +++ b/spring-cloud-gateway-server-mvc/src/test/resources/application-propertiesmigrationtests.yml @@ -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 diff --git a/spring-cloud-gateway-server/pom.xml b/spring-cloud-gateway-server/pom.xml index 2a61283f..6430185a 100644 --- a/spring-cloud-gateway-server/pom.xml +++ b/spring-cloud-gateway-server/pom.xml @@ -16,7 +16,7 @@ Spring Cloud Gateway Server ${basedir}/.. - 1.71.0 + 1.72.0 1.0.0 @@ -192,6 +192,11 @@ spring-boot-starter-test test + + org.springframework.boot + spring-boot-properties-migrator + test + org.springframework.boot spring-boot-testcontainers @@ -212,11 +217,6 @@ spring-cloud-test-support test - - org.springframework.cloud - spring-cloud-starter-stream-rabbit - test - io.projectreactor reactor-test diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java index 60108fa6..b5022c46 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java @@ -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; @@ -193,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, @@ -271,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); @@ -289,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) { @@ -317,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()); @@ -334,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(); } @@ -467,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); } @@ -479,8 +483,8 @@ public class GatewayAutoConfiguration { @Bean @ConditionalOnEnabledPredicate - public PathRoutePredicateFactory pathRoutePredicateFactory() { - return new PathRoutePredicateFactory(); + public PathRoutePredicateFactory pathRoutePredicateFactory(WebFluxProperties webFluxProperties) { + return new PathRoutePredicateFactory(webFluxProperties); } @Bean @@ -774,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) { @@ -858,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 globalFilters, List gatewayFilters, List routePredicates, @@ -888,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 { } @@ -896,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 { diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayClassPathWarningAutoConfiguration.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayClassPathWarningAutoConfiguration.java index 46507016..88cfed09 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayClassPathWarningAutoConfiguration.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayClassPathWarningAutoConfiguration.java @@ -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); diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayFunctionAutoConfiguration.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayFunctionAutoConfiguration.java index 0afedfbe..fdb7d659 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayFunctionAutoConfiguration.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayFunctionAutoConfiguration.java @@ -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 diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayLoadBalancerProperties.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayLoadBalancerProperties.java index b2be55c9..0814fb31 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayLoadBalancerProperties.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayLoadBalancerProperties.java @@ -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; diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayMetricsProperties.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayMetricsProperties.java index b0ca9d43..0d9e4183 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayMetricsProperties.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayMetricsProperties.java @@ -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 { diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayProperties.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayProperties.java index 356185f5..67e85c6d 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayProperties.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayProperties.java @@ -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()); diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayRedisAutoConfiguration.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayRedisAutoConfiguration.java index 7b52c0a3..65407533 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayRedisAutoConfiguration.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayRedisAutoConfiguration.java @@ -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( diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayResilience4JCircuitBreakerAutoConfiguration.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayResilience4JCircuitBreakerAutoConfiguration.java index 598b1ef2..af44c795 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayResilience4JCircuitBreakerAutoConfiguration.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayResilience4JCircuitBreakerAutoConfiguration.java @@ -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 }) diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayServerWebfluxPropertiesMigrationListener.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayServerWebfluxPropertiesMigrationListener.java new file mode 100644 index 00000000..eb7ef6d2 --- /dev/null +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayServerWebfluxPropertiesMigrationListener.java @@ -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 { + + 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 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 migrate(ConfigurableEnvironment env, ConfigurationPropertySource propertySource, + String propertySourcePrefix, String deprecatedKey, String newKeyPrefix) { + List migrations = new ArrayList<>(); + + if (propertySource instanceof IterableConfigurationPropertySource iterableSource) { + ConfigurationPropertyName routesParentName = ConfigurationPropertyName.of(deprecatedKey); + List 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 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 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; + } + } + +} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayStreamAutoConfiguration.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayStreamAutoConfiguration.java index ba5dcbb4..262a6078 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayStreamAutoConfiguration.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayStreamAutoConfiguration.java @@ -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 diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GlobalCorsProperties.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GlobalCorsProperties.java index db102fd8..b0b3851d 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GlobalCorsProperties.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GlobalCorsProperties.java @@ -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 corsConfigurations = new LinkedHashMap<>(); diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/HttpClientProperties.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/HttpClientProperties.java index fef1d54c..96538e93 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/HttpClientProperties.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/HttpClientProperties.java @@ -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 { diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/LocalResponseCacheAutoConfiguration.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/LocalResponseCacheAutoConfiguration.java index 908e2883..71e2641c 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/LocalResponseCacheAutoConfiguration.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/LocalResponseCacheAutoConfiguration.java @@ -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 { diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/SimpleUrlHandlerMappingGlobalCorsAutoConfiguration.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/SimpleUrlHandlerMappingGlobalCorsAutoConfiguration.java index 62c7ffad..28bdea81 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/SimpleUrlHandlerMappingGlobalCorsAutoConfiguration.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/SimpleUrlHandlerMappingGlobalCorsAutoConfiguration.java @@ -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 { diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/conditional/OnEnabledComponent.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/conditional/OnEnabledComponent.java index b3211dd5..5b48cabd 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/conditional/OnEnabledComponent.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/conditional/OnEnabledComponent.java @@ -34,7 +34,7 @@ import static org.springframework.boot.autoconfigure.condition.ConditionMessage. public abstract class OnEnabledComponent 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"; diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/discovery/DiscoveryLocatorProperties.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/discovery/DiscoveryLocatorProperties.java index 9426ed37..ba0d5557 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/discovery/DiscoveryLocatorProperties.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/discovery/DiscoveryLocatorProperties.java @@ -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. */ diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/discovery/GatewayDiscoveryClientAutoConfiguration.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/discovery/GatewayDiscoveryClientAutoConfiguration.java index 94cc64e2..06efe748 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/discovery/GatewayDiscoveryClientAutoConfiguration.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/discovery/GatewayDiscoveryClientAutoConfiguration.java @@ -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); diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/NettyRoutingFilter.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/NettyRoutingFilter.java index bf4f6ad7..2350d4d2 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/NettyRoutingFilter.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/NettyRoutingFilter.java @@ -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); diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/cors/CorsGatewayFilterApplicationListener.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/cors/CorsGatewayFilterApplicationListener.java index 337588b8..ebe3e148 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/cors/CorsGatewayFilterApplicationListener.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/cors/CorsGatewayFilterApplicationListener.java @@ -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 { +public class CorsGatewayFilterApplicationListener implements ApplicationListener { 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()); diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RemoveJsonAttributesResponseBodyGatewayFilterFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RemoveJsonAttributesResponseBodyGatewayFilterFactory.java index cb5b7d49..deea4c63 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RemoveJsonAttributesResponseBodyGatewayFilterFactory.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RemoveJsonAttributesResponseBodyGatewayFilterFactory.java @@ -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 { @@ -72,14 +72,14 @@ public class RemoveJsonAttributesResponseBodyGatewayFilterFactory extends RewriteFunction 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 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 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)); } } diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RemoveResponseHeaderGatewayFilterFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RemoveResponseHeaderGatewayFilterFactory.java index bf563fe0..e7cc8993 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RemoveResponseHeaderGatewayFilterFactory.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RemoveResponseHeaderGatewayFilterFactory.java @@ -47,8 +47,11 @@ public class RemoveResponseHeaderGatewayFilterFactory return new GatewayFilter() { @Override public Mono 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 diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RequestRateLimiterGatewayFilterFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RequestRateLimiterGatewayFilterFactory.java index e0eb2102..e3f0b2f6 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RequestRateLimiterGatewayFilterFactory.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RequestRateLimiterGatewayFilterFactory.java @@ -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 { diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/SecureHeadersProperties.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/SecureHeadersProperties.java index 2456a6f4..7ad3cfaf 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/SecureHeadersProperties.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/SecureHeadersProperties.java @@ -27,7 +27,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties; /** * @author Spencer Gibb, Thirunavukkarasu Ravichandran, Jörg Richter */ -@ConfigurationProperties("spring.cloud.gateway.filter.secure-headers") +@ConfigurationProperties("spring.cloud.gateway.server.webflux.filter.secure-headers") public class SecureHeadersProperties { /** diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/SetStatusGatewayFilterFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/SetStatusGatewayFilterFactory.java index 4ca59ec8..428e267e 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/SetStatusGatewayFilterFactory.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/SetStatusGatewayFilterFactory.java @@ -35,7 +35,7 @@ import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.s /** * @author Spencer Gibb */ -@ConfigurationProperties("spring.cloud.gateway.set-status") +@ConfigurationProperties("spring.cloud.gateway.server.webflux.set-status") public class SetStatusGatewayFilterFactory extends AbstractGatewayFilterFactory { /** diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/LocalResponseCacheGatewayFilterFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/LocalResponseCacheGatewayFilterFactory.java index a1738827..c28a00e4 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/LocalResponseCacheGatewayFilterFactory.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/LocalResponseCacheGatewayFilterFactory.java @@ -40,7 +40,8 @@ import org.springframework.validation.annotation.Validated; * @author Marta Medio * @author Ignacio Lozano */ -@ConditionalOnProperty(value = "spring.cloud.gateway.filter.local-response-cache.enabled", havingValue = "true") +@ConditionalOnProperty(value = "spring.cloud.gateway.server.webflux.filter.local-response-cache.enabled", + havingValue = "true") public class LocalResponseCacheGatewayFilterFactory extends AbstractGatewayFilterFactory { diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/LocalResponseCacheProperties.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/LocalResponseCacheProperties.java index 1aead89a..56e026e4 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/LocalResponseCacheProperties.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/LocalResponseCacheProperties.java @@ -30,7 +30,7 @@ import org.springframework.util.unit.DataSize; @ConfigurationProperties(prefix = LocalResponseCacheProperties.PREFIX) public class LocalResponseCacheProperties { - static final String PREFIX = "spring.cloud.gateway.filter.local-response-cache"; + static final String PREFIX = "spring.cloud.gateway.server.webflux.filter.local-response-cache"; private static final Log LOGGER = LogFactory.getLog(LocalResponseCacheProperties.class); diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/RemoveHopByHopHeadersFilter.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/RemoveHopByHopHeadersFilter.java index 556f1c97..fa8ff08a 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/RemoveHopByHopHeadersFilter.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/RemoveHopByHopHeadersFilter.java @@ -30,7 +30,7 @@ import org.springframework.http.HttpHeaders; import org.springframework.util.Assert; import org.springframework.web.server.ServerWebExchange; -@ConfigurationProperties("spring.cloud.gateway.filter.remove-hop-by-hop") +@ConfigurationProperties("spring.cloud.gateway.server.webflux.filter.remove-hop-by-hop") public class RemoveHopByHopHeadersFilter implements HttpHeadersFilter, Ordered { /** diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/XForwardedHeadersFilter.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/XForwardedHeadersFilter.java index 2c1e8363..2dea38fc 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/XForwardedHeadersFilter.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/XForwardedHeadersFilter.java @@ -32,7 +32,7 @@ import org.springframework.web.server.ServerWebExchange; import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR; import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR; -@ConfigurationProperties("spring.cloud.gateway.x-forwarded") +@ConfigurationProperties("spring.cloud.gateway.server.webflux.x-forwarded") public class XForwardedHeadersFilter implements HttpHeadersFilter, Ordered { /** Default http port. */ diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/ratelimit/RedisRateLimiter.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/ratelimit/RedisRateLimiter.java index 6ae0c467..5d4d73cb 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/ratelimit/RedisRateLimiter.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/ratelimit/RedisRateLimiter.java @@ -31,6 +31,7 @@ import reactor.core.publisher.Mono; import org.springframework.beans.BeansException; import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.cloud.gateway.config.GatewayProperties; import org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator; import org.springframework.cloud.gateway.support.ConfigurationService; import org.springframework.context.ApplicationContext; @@ -50,7 +51,7 @@ import org.springframework.validation.annotation.Validated; * @author Denis Cutic * @author Andrey Muchnik */ -@ConfigurationProperties("spring.cloud.gateway.redis-rate-limiter") +@ConfigurationProperties(GatewayProperties.PREFIX + ".redis-rate-limiter") public class RedisRateLimiter extends AbstractRateLimiter implements ApplicationContextAware { /** diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/AsyncPredicate.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/AsyncPredicate.java index e15694eb..60277232 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/AsyncPredicate.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/AsyncPredicate.java @@ -77,8 +77,7 @@ public interface AsyncPredicate extends Function>, HasC @Override public void accept(Visitor visitor) { - if (delegate instanceof GatewayPredicate) { - GatewayPredicate gatewayPredicate = (GatewayPredicate) delegate; + if (delegate instanceof GatewayPredicate gatewayPredicate) { gatewayPredicate.accept(visitor); } } diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/CookieRoutePredicateFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/CookieRoutePredicateFactory.java index 0f017025..ded55566 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/CookieRoutePredicateFactory.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/CookieRoutePredicateFactory.java @@ -58,12 +58,7 @@ public class CookieRoutePredicateFactory extends AbstractRoutePredicateFactory cookie.getValue().matches(config.regexp)); } @Override diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/GatewayPredicate.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/GatewayPredicate.java index c373296a..40001870 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/GatewayPredicate.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/GatewayPredicate.java @@ -47,8 +47,8 @@ public interface GatewayPredicate extends Predicate, HasConfi static GatewayPredicate wrapIfNeeded(Predicate other) { GatewayPredicate right; - if (other instanceof GatewayPredicate) { - right = (GatewayPredicate) other; + if (other instanceof GatewayPredicate gatewayPredicate) { + right = gatewayPredicate; } else { right = new GatewayPredicateWrapper(other); @@ -72,8 +72,7 @@ public interface GatewayPredicate extends Predicate, HasConfi @Override public void accept(Visitor visitor) { - if (delegate instanceof GatewayPredicate) { - GatewayPredicate gatewayPredicate = (GatewayPredicate) delegate; + if (delegate instanceof GatewayPredicate gatewayPredicate) { gatewayPredicate.accept(visitor); } } diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicateFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicateFactory.java index 509b78d7..45550df5 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicateFactory.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicateFactory.java @@ -24,8 +24,10 @@ import java.util.function.Predicate; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.boot.autoconfigure.web.reactive.WebFluxProperties; import org.springframework.core.style.ToStringCreator; import org.springframework.http.server.PathContainer; +import org.springframework.util.StringUtils; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.util.pattern.PathPattern; import org.springframework.web.util.pattern.PathPattern.PathMatchInfo; @@ -41,6 +43,7 @@ import static org.springframework.http.server.PathContainer.parsePath; /** * @author Spencer Gibb * @author Dhawal Kapil + * @author FuYiNan Guo */ public class PathRoutePredicateFactory extends AbstractRoutePredicateFactory { @@ -50,8 +53,20 @@ public class PathRoutePredicateFactory extends AbstractRoutePredicateFactory { - PathPattern pathPattern = this.pathPatternParser.parse(pattern); + String basePath = webFluxProperties.getBasePath(); + boolean basePathIsNotBlank = StringUtils.hasText(basePath); + String pathPatternStr = pattern; + if (basePathIsNotBlank) { + if (pattern.length() > 1 && !pattern.startsWith("/")) { + basePath += ("/"); + } + pathPatternStr = basePath + pattern; + } + PathPattern pathPattern = this.pathPatternParser.parse(pathPatternStr); pathPatterns.add(pathPattern); }); } diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/CompositeRouteDefinitionLocator.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/CompositeRouteDefinitionLocator.java index 8e54feb1..db116fa7 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/CompositeRouteDefinitionLocator.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/CompositeRouteDefinitionLocator.java @@ -65,7 +65,7 @@ public class CompositeRouteDefinitionLocator implements RouteDefinitionLocator { } protected Mono randomId() { - return Mono.fromSupplier(idGenerator::generateId).map(UUID::toString).publishOn(Schedulers.boundedElastic()); + return Mono.fromSupplier(idGenerator::generateId).publishOn(Schedulers.boundedElastic()).map(UUID::toString); } } diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/RouteDefinitionRouteLocator.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/RouteDefinitionRouteLocator.java index fce1faee..aef1a699 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/RouteDefinitionRouteLocator.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/RouteDefinitionRouteLocator.java @@ -201,14 +201,10 @@ public class RouteDefinitionRouteLocator implements RouteLocator { // this is a very rare case, but possible, just match all return AsyncPredicate.from(exchange -> true); } - AsyncPredicate predicate = lookup(routeDefinition, predicates.get(0)); - for (PredicateDefinition andPredicate : predicates.subList(1, predicates.size())) { - AsyncPredicate found = lookup(routeDefinition, andPredicate); - predicate = predicate.and(found); - } - - return predicate; + return predicates.stream() + .map(nextPredicate -> lookup(routeDefinition, nextPredicate)) + .reduce(AsyncPredicate.from(exchange -> true), AsyncPredicate::and); } @SuppressWarnings("unchecked") diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ShortcutConfigurable.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ShortcutConfigurable.java index 30e2cc2e..16c3f30e 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ShortcutConfigurable.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ShortcutConfigurable.java @@ -183,8 +183,8 @@ public interface ShortcutConfigurable { public GatewayEvaluationContext(BeanFactory beanFactory) { this.beanFactoryResolver = new BeanFactoryResolver(beanFactory); Environment env = beanFactory.getBean(Environment.class); - boolean restrictive = env.getProperty("spring.cloud.gateway.restrictive-property-accessor.enabled", - Boolean.class, true); + boolean restrictive = env.getProperty( + "spring.cloud.gateway.server.webflux.restrictive-property-accessor.enabled", Boolean.class, true); if (restrictive) { delegate = SimpleEvaluationContext.forPropertyAccessors(new RestrictivePropertyAccessor()) .withMethodResolvers((context, targetObject, name, argumentTypes) -> null) diff --git a/spring-cloud-gateway-server/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-cloud-gateway-server/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 1aea9f85..1a646d71 100644 --- a/spring-cloud-gateway-server/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-cloud-gateway-server/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -1,447 +1,2447 @@ { "properties": [ { - "name": "spring.cloud.gateway.filter.add-request-header.enabled", + "name": "spring.cloud.gateway.server.webflux.filter.add-request-header.enabled", "type": "java.lang.Boolean", "description": "Enables the add-request-header filter.", "defaultValue": "true" }, { - "name": "spring.cloud.gateway.filter.map-request-header.enabled", + "name": "spring.cloud.gateway.server.webflux.filter.map-request-header.enabled", "type": "java.lang.Boolean", "description": "Enables the map-request-header filter.", "defaultValue": "true" }, { - "name": "spring.cloud.gateway.filter.add-request-parameter.enabled", + "name": "spring.cloud.gateway.server.webflux.filter.add-request-parameter.enabled", "type": "java.lang.Boolean", "description": "Enables the add-request-parameter filter.", "defaultValue": "true" }, { - "name": "spring.cloud.gateway.filter.add-response-header.enabled", + "name": "spring.cloud.gateway.server.webflux.filter.add-response-header.enabled", "type": "java.lang.Boolean", "description": "Enables the add-response-header filter.", "defaultValue": "true" }, { - "name": "spring.cloud.gateway.filter.json-to-grpc.enabled", + "name": "spring.cloud.gateway.server.webflux.filter.json-to-grpc.enabled", "type": "java.lang.Boolean", "description": "Enables the JSON to gRPC filter.", "defaultValue": "true" }, { - "name": "spring.cloud.gateway.filter.modify-request-body.enabled", + "name": "spring.cloud.gateway.server.webflux.filter.modify-request-body.enabled", "type": "java.lang.Boolean", "description": "Enables the modify-request-body filter.", "defaultValue": "true" }, { - "name": "spring.cloud.gateway.filter.local-response-cache.enabled", + "name": "spring.cloud.gateway.server.webflux.filter.local-response-cache.enabled", "type": "java.lang.Boolean", "description": "Enables the local-response-cache filter.", "defaultValue": "false" }, { - "name": "spring.cloud.gateway.filter.local-response-cache.size", + "name": "spring.cloud.gateway.server.webflux.filter.local-response-cache.size", "type": "org.springframework.util.unit.DataSize", "description": "Maximum size of the cache to evict entries for this route (in KB, MB and GB)." }, { - "name": "spring.cloud.gateway.filter.local-response-cache.time-to-live", + "name": "spring.cloud.gateway.server.webflux.filter.local-response-cache.time-to-live", "type": "java.time.Duration", "description": "Time to expire a cache entry (expressed in s for seconds, m for minutes, and h for hours).", "defaultValue": "5m" }, { - "name": "spring.cloud.gateway.filter.dedupe-response-header.enabled", + "name": "spring.cloud.gateway.server.webflux.filter.dedupe-response-header.enabled", "type": "java.lang.Boolean", "description": "Enables the dedupe-response-header filter.", "defaultValue": "true" }, { - "name": "spring.cloud.gateway.filter.modify-response-body.enabled", + "name": "spring.cloud.gateway.server.webflux.filter.modify-response-body.enabled", "type": "java.lang.Boolean", "description": "Enables the modify-response-body filter.", "defaultValue": "true" }, { - "name": "spring.cloud.gateway.filter.prefix-path.enabled", + "name": "spring.cloud.gateway.server.webflux.filter.prefix-path.enabled", "type": "java.lang.Boolean", "description": "Enables the prefix-path filter.", "defaultValue": "true" }, { - "name": "spring.cloud.gateway.filter.preserve-host-header.enabled", + "name": "spring.cloud.gateway.server.webflux.filter.preserve-host-header.enabled", "type": "java.lang.Boolean", "description": "Enables the preserve-host-header filter.", "defaultValue": "true" }, { - "name": "spring.cloud.gateway.filter.redirect-to.enabled", + "name": "spring.cloud.gateway.server.webflux.filter.redirect-to.enabled", "type": "java.lang.Boolean", "description": "Enables the redirect-to filter.", "defaultValue": "true" }, { - "name": "spring.cloud.gateway.filter.remove-request-header.enabled", + "name": "spring.cloud.gateway.server.webflux.filter.remove-request-header.enabled", "type": "java.lang.Boolean", "description": "Enables the remove-request-header filter.", "defaultValue": "true" }, { - "name": "spring.cloud.gateway.filter.remove-request-parameter.enabled", + "name": "spring.cloud.gateway.server.webflux.filter.remove-request-parameter.enabled", "type": "java.lang.Boolean", "description": "Enables the remove-request-parameter filter.", "defaultValue": "true" }, { - "name": "spring.cloud.gateway.filter.remove-response-header.enabled", + "name": "spring.cloud.gateway.server.webflux.filter.remove-response-header.enabled", "type": "java.lang.Boolean", "description": "Enables the remove-response-header filter.", "defaultValue": "true" }, { - "name": "spring.cloud.gateway.filter.request-rate-limiter.enabled", + "name": "spring.cloud.gateway.server.webflux.filter.request-rate-limiter.enabled", "type": "java.lang.Boolean", "description": "Enables the request-rate-limiter filter.", "defaultValue": "true" }, { - "name": "spring.cloud.gateway.filter.rewrite-path.enabled", + "name": "spring.cloud.gateway.server.webflux.filter.rewrite-path.enabled", "type": "java.lang.Boolean", "description": "Enables the rewrite-path filter.", "defaultValue": "true" }, { - "name": "spring.cloud.gateway.filter.retry.enabled", + "name": "spring.cloud.gateway.server.webflux.filter.retry.enabled", "type": "java.lang.Boolean", "description": "Enables the retry filter.", "defaultValue": "true" }, { - "name": "spring.cloud.gateway.filter.set-path.enabled", + "name": "spring.cloud.gateway.server.webflux.filter.set-path.enabled", "type": "java.lang.Boolean", "description": "Enables the set-path filter.", "defaultValue": "true" }, { - "name": "spring.cloud.gateway.filter.secure-headers.enabled", + "name": "spring.cloud.gateway.server.webflux.filter.secure-headers.enabled", "type": "java.lang.Boolean", "description": "Enables the secure-headers filter.", "defaultValue": "true" }, { - "name": "spring.cloud.gateway.filter.set-request-header.enabled", + "name": "spring.cloud.gateway.server.webflux.filter.set-request-header.enabled", "type": "java.lang.Boolean", "description": "Enables the set-request-header filter.", "defaultValue": "true" }, { - "name": "spring.cloud.gateway.filter.set-request-host-header.enabled", + "name": "spring.cloud.gateway.server.webflux.filter.set-request-host-header.enabled", "type": "java.lang.Boolean", "description": "Enables the set-request-host-header filter.", "defaultValue": "true" }, { - "name": "spring.cloud.gateway.filter.set-response-header.enabled", + "name": "spring.cloud.gateway.server.webflux.filter.set-response-header.enabled", "type": "java.lang.Boolean", "description": "Enables the set-response-header filter.", "defaultValue": "true" }, { - "name": "spring.cloud.gateway.filter.rewrite-response-header.enabled", + "name": "spring.cloud.gateway.server.webflux.filter.rewrite-response-header.enabled", "type": "java.lang.Boolean", "description": "Enables the rewrite-response-header filter.", "defaultValue": "true" }, { - "name": "spring.cloud.gateway.filter.rewrite-location-response-header.enabled", + "name": "spring.cloud.gateway.server.webflux.filter.rewrite-location-response-header.enabled", "type": "java.lang.Boolean", "description": "Enables the rewrite-location-response-header filter.", "defaultValue": "true" }, { - "name": "spring.cloud.gateway.filter.rewrite-location.enabled", + "name": "spring.cloud.gateway.server.webflux.filter.rewrite-location.enabled", "type": "java.lang.Boolean", "description": "Enables the rewrite-location filter.", "defaultValue": "true" }, { - "name": "spring.cloud.gateway.filter.rewrite-request-parameter.enabled", + "name": "spring.cloud.gateway.server.webflux.filter.rewrite-request-parameter.enabled", "type": "java.lang.Boolean", "description": "Enables the rewrite-request-parameter filter.", "defaultValue": "true" }, { - "name": "spring.cloud.gateway.filter.set-status.enabled", + "name": "spring.cloud.gateway.server.webflux.filter.set-status.enabled", "type": "java.lang.Boolean", "description": "Enables the set-status filter.", "defaultValue": "true" }, { - "name": "spring.cloud.gateway.filter.save-session.enabled", + "name": "spring.cloud.gateway.server.webflux.filter.save-session.enabled", "type": "java.lang.Boolean", "description": "Enables the save-session filter.", "defaultValue": "true" }, { - "name": "spring.cloud.gateway.filter.strip-prefix.enabled", + "name": "spring.cloud.gateway.server.webflux.filter.strip-prefix.enabled", "type": "java.lang.Boolean", "description": "Enables the strip-prefix filter.", "defaultValue": "true" }, { - "name": "spring.cloud.gateway.filter.request-header-to-request-uri.enabled", + "name": "spring.cloud.gateway.server.webflux.filter.request-header-to-request-uri.enabled", "type": "java.lang.Boolean", "description": "Enables the request-header-to-request-uri filter.", "defaultValue": "true" }, { - "name": "spring.cloud.gateway.filter.request-size.enabled", + "name": "spring.cloud.gateway.server.webflux.filter.request-size.enabled", "type": "java.lang.Boolean", "description": "Enables the request-size filter.", "defaultValue": "true" }, { - "name": "spring.cloud.gateway.filter.request-header-size.enabled", + "name": "spring.cloud.gateway.server.webflux.filter.request-header-size.enabled", "type": "java.lang.Boolean", "description": "Enables the request-header-size filter.", "defaultValue": "true" }, { - "name": "spring.cloud.gateway.filter.circuit-breaker.enabled", + "name": "spring.cloud.gateway.server.webflux.filter.circuit-breaker.enabled", "type": "java.lang.Boolean", "description": "Enables the circuit-breaker filter.", "defaultValue": "true" }, { - "name": "spring.cloud.gateway.filter.hystrix.enabled", + "name": "spring.cloud.gateway.server.webflux.filter.hystrix.enabled", "type": "java.lang.Boolean", "description": "Enables the hystrix filter.", "defaultValue": "true" }, { - "name": "spring.cloud.gateway.filter.fallback-headers.enabled", + "name": "spring.cloud.gateway.server.webflux.filter.fallback-headers.enabled", "type": "java.lang.Boolean", "description": "Enables the fallback-headers filter.", "defaultValue": "true" }, { - "name": "spring.cloud.gateway.global-filter.adapt-cached-body.enabled", + "name": "spring.cloud.gateway.server.webflux.global-filter.adapt-cached-body.enabled", "type": "java.lang.Boolean", "description": "Enables the adapt-cached-body global filter.", "defaultValue": "true" }, { - "name": "spring.cloud.gateway.global-filter.remove-cached-body.enabled", + "name": "spring.cloud.gateway.server.webflux.global-filter.remove-cached-body.enabled", "type": "java.lang.Boolean", "description": "Enables the remove-cached-body global filter.", "defaultValue": "true" }, { - "name": "spring.cloud.gateway.global-filter.route-to-request-url.enabled", + "name": "spring.cloud.gateway.server.webflux.global-filter.route-to-request-url.enabled", "type": "java.lang.Boolean", "description": "Enables the route-to-request-url global filter.", "defaultValue": "true" }, { - "name": "spring.cloud.gateway.global-filter.forward-routing.enabled", + "name": "spring.cloud.gateway.server.webflux.global-filter.forward-routing.enabled", "type": "java.lang.Boolean", "description": "Enables the forward-routing global filter.", "defaultValue": "true" }, { - "name": "spring.cloud.gateway.global-filter.forward-path.enabled", + "name": "spring.cloud.gateway.server.webflux.global-filter.forward-path.enabled", "type": "java.lang.Boolean", "description": "Enables the forward-path global filter.", "defaultValue": "true" }, { - "name": "spring.cloud.gateway.global-filter.websocket-routing.enabled", + "name": "spring.cloud.gateway.server.webflux.global-filter.websocket-routing.enabled", "type": "java.lang.Boolean", "description": "Enables the websocket-routing global filter.", "defaultValue": "true" }, { - "name": "spring.cloud.gateway.global-filter.netty-write-response.enabled", + "name": "spring.cloud.gateway.server.webflux.global-filter.netty-write-response.enabled", "type": "java.lang.Boolean", "description": "Enables the netty-write-response global filter.", "defaultValue": "true" }, { - "name": "spring.cloud.gateway.global-filter.netty-routing.enabled", + "name": "spring.cloud.gateway.server.webflux.global-filter.netty-routing.enabled", "type": "java.lang.Boolean", "description": "Enables the netty-routing global filter.", "defaultValue": "true" }, { - "name": "spring.cloud.gateway.global-filter.reactive-load-balancer-client.enabled", + "name": "spring.cloud.gateway.server.webflux.global-filter.reactive-load-balancer-client.enabled", "type": "java.lang.Boolean", "description": "Enables the reactive-load-balancer-client global filter.", "defaultValue": "true" }, { - "name": "spring.cloud.gateway.global-filter.load-balancer-client.enabled", + "name": "spring.cloud.gateway.server.webflux.global-filter.load-balancer-client.enabled", "type": "java.lang.Boolean", "description": "Enables the load-balancer-client global filter.", "defaultValue": "true" }, { - "name": "spring.cloud.gateway.global-filter.local-response-cache.enabled", + "name": "spring.cloud.gateway.server.webflux.global-filter.local-response-cache.enabled", "type": "java.lang.Boolean", "description": "Enables the local-response-cache filter for all routes, it allows to add a specific configuration at route level using LocalResponseCache filter.", "defaultValue": "true" }, { - "name": "spring.cloud.gateway.predicate.after.enabled", + "name": "spring.cloud.gateway.server.webflux.predicate.after.enabled", "type": "java.lang.Boolean", "description": "Enables the after predicate.", "defaultValue": "true" }, { - "name": "spring.cloud.gateway.predicate.before.enabled", + "name": "spring.cloud.gateway.server.webflux.predicate.before.enabled", "type": "java.lang.Boolean", "description": "Enables the before predicate.", "defaultValue": "true" }, { - "name": "spring.cloud.gateway.predicate.between.enabled", + "name": "spring.cloud.gateway.server.webflux.predicate.between.enabled", "type": "java.lang.Boolean", "description": "Enables the between predicate.", "defaultValue": "true" }, { - "name": "spring.cloud.gateway.predicate.cookie.enabled", + "name": "spring.cloud.gateway.server.webflux.predicate.cookie.enabled", "type": "java.lang.Boolean", "description": "Enables the cookie predicate.", "defaultValue": "true" }, { - "name": "spring.cloud.gateway.predicate.header.enabled", + "name": "spring.cloud.gateway.server.webflux.predicate.header.enabled", "type": "java.lang.Boolean", "description": "Enables the header predicate.", "defaultValue": "true" }, { - "name": "spring.cloud.gateway.predicate.host.enabled", + "name": "spring.cloud.gateway.server.webflux.predicate.host.enabled", "type": "java.lang.Boolean", "description": "Enables the host predicate.", "defaultValue": "true" }, { - "name": "spring.cloud.gateway.predicate.method.enabled", + "name": "spring.cloud.gateway.server.webflux.predicate.method.enabled", "type": "java.lang.Boolean", "description": "Enables the method predicate.", "defaultValue": "true" }, { - "name": "spring.cloud.gateway.predicate.path.enabled", + "name": "spring.cloud.gateway.server.webflux.predicate.path.enabled", "type": "java.lang.Boolean", "description": "Enables the path predicate.", "defaultValue": "true" }, { - "name": "spring.cloud.gateway.predicate.query.enabled", + "name": "spring.cloud.gateway.server.webflux.predicate.query.enabled", "type": "java.lang.Boolean", "description": "Enables the query predicate.", "defaultValue": "true" }, { - "name": "spring.cloud.gateway.predicate.read-body.enabled", + "name": "spring.cloud.gateway.server.webflux.predicate.read-body.enabled", "type": "java.lang.Boolean", "description": "Enables the read-body predicate.", "defaultValue": "true" }, { - "name": "spring.cloud.gateway.predicate.remote-addr.enabled", + "name": "spring.cloud.gateway.server.webflux.predicate.remote-addr.enabled", "type": "java.lang.Boolean", "description": "Enables the remote-addr predicate.", "defaultValue": "true" }, { - "name": "spring.cloud.gateway.predicate.xforwarded-remote-addr.enabled", + "name": "spring.cloud.gateway.server.webflux.predicate.xforwarded-remote-addr.enabled", "type": "java.lang.Boolean", "description": "Enables the xforwarded-remote-addr predicate.", "defaultValue": "true" }, { - "name": "spring.cloud.gateway.predicate.weight.enabled", + "name": "spring.cloud.gateway.server.webflux.predicate.weight.enabled", "type": "java.lang.Boolean", "description": "Enables the weight predicate.", "defaultValue": "true" }, { - "name": "spring.cloud.gateway.predicate.cloud-foundry-route-service.enabled", + "name": "spring.cloud.gateway.server.webflux.predicate.cloud-foundry-route-service.enabled", "type": "java.lang.Boolean", "description": "Enables the cloud-foundry-route-service predicate.", "defaultValue": "true" }, { - "name": "spring.cloud.gateway.enabled", + "name": "spring.cloud.gateway.server.webflux.enabled", "type": "java.lang.Boolean", "description": "Enables gateway functionality.", "defaultValue": "true" }, { - "name": "spring.cloud.gateway.forwarded.enabled", + "name": "spring.cloud.gateway.server.webflux.forwarded.enabled", "type": "java.lang.Boolean", "description": "Enables the ForwardedHeadersFilter.", "defaultValue": "true" }, { - "name": "spring.cloud.gateway.forwarded.by.enabled", + "name": "spring.cloud.gateway.server.webflux.forwarded.by.enabled", "type": "java.lang.Boolean", "description": "Enables the Forwarded: by header part.", "defaultValue": "false" }, { - "name": "spring.cloud.gateway.httpserver.wiretap", + "name": "spring.cloud.gateway.server.webflux.httpserver.wiretap", "type": "java.lang.Boolean", "description": "Enables wiretap debugging for Netty HttpServer.", "defaultValue": "false" }, { - "name": "spring.cloud.gateway.globalcors.add-to-simple-url-handler-mapping", + "name": "spring.cloud.gateway.server.webflux.globalcors.add-to-simple-url-handler-mapping", "type": "java.lang.Boolean", "description": "If global CORS config should be added to the URL handler.", "defaultValue": "false" }, { - "name": "spring.cloud.gateway.handler-mapping.order", + "name": "spring.cloud.gateway.server.webflux.handler-mapping.order", "type": "java.lang.Integer", "description": "The order of RoutePredicateHandlerMapping.", "defaultValue": "1" }, { - "name": "spring.cloud.gateway.restrictive-property-accessor.enabled", + "name": "spring.cloud.gateway.server.webflux.restrictive-property-accessor.enabled", "type": "java.lang.Boolean", "description": "Restricts method and property access in SpEL.", "defaultValue": "true" }, { - "name": "spring.cloud.gateway.observability.enabled", + "name": "spring.cloud.gateway.server.webflux.observability.enabled", "type": "java.lang.Boolean", "description": "If Micrometer Observability support should be turned on.", "defaultValue": "true" }, { - "name": "spring.cloud.gateway.predicate.host.include-port", + "name": "spring.cloud.gateway.server.webflux.predicate.host.include-port", "type": "java.lang.Boolean", "description": "Include the port in matching the host name.", "defaultValue": "true" }, { - "name": "spring.cloud.gateway.route-refresh-listener.enabled", + "name": "spring.cloud.gateway.server.webflux.route-refresh-listener.enabled", "type": "java.lang.Boolean", "description": "If RouteRefreshListener should be turned on.", "defaultValue": "true" }, { - "name": "spring.cloud.gateway.redis-route-definition-repository.enabled", + "name": "spring.cloud.gateway.server.webflux.redis-route-definition-repository.enabled", "type": "java.lang.Boolean", "description": "If RedisRouteDefinitionRepository should be enabled.", "defaultValue": "true" + }, + { + "name": "spring.cloud.gateway", + "type": "org.springframework.cloud.gateway.config.GatewayProperties", + "sourceType": "org.springframework.cloud.gateway.config.GatewayProperties", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.discovery.locator", + "type": "org.springframework.cloud.gateway.discovery.DiscoveryLocatorProperties", + "sourceType": "org.springframework.cloud.gateway.discovery.DiscoveryLocatorProperties", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.discovery.locator", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.filter.local-response-cache", + "type": "org.springframework.cloud.gateway.filter.factory.cache.LocalResponseCacheProperties", + "sourceType": "org.springframework.cloud.gateway.filter.factory.cache.LocalResponseCacheProperties", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.filter.local-response-cache", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.filter.local-response-cache.request", + "type": "org.springframework.cloud.gateway.filter.factory.cache.LocalResponseCacheProperties$RequestOptions", + "sourceType": "org.springframework.cloud.gateway.filter.factory.cache.LocalResponseCacheProperties", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.filter.local-response-cache.request", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.filter.remove-hop-by-hop", + "type": "org.springframework.cloud.gateway.filter.headers.RemoveHopByHopHeadersFilter", + "sourceType": "org.springframework.cloud.gateway.filter.headers.RemoveHopByHopHeadersFilter", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.filter.remove-hop-by-hop", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.filter.request-rate-limiter", + "type": "org.springframework.cloud.gateway.filter.factory.RequestRateLimiterGatewayFilterFactory", + "sourceType": "org.springframework.cloud.gateway.filter.factory.RequestRateLimiterGatewayFilterFactory", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.filter.request-rate-limiter", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.filter.secure-headers", + "type": "org.springframework.cloud.gateway.filter.factory.SecureHeadersProperties", + "sourceType": "org.springframework.cloud.gateway.filter.factory.SecureHeadersProperties", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.filter.secure-headers", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.globalcors", + "type": "org.springframework.cloud.gateway.config.GlobalCorsProperties", + "sourceType": "org.springframework.cloud.gateway.config.GlobalCorsProperties", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.globalcors", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.httpclient", + "type": "org.springframework.cloud.gateway.config.HttpClientProperties", + "sourceType": "org.springframework.cloud.gateway.config.HttpClientProperties", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.httpclient", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.httpclient.pool", + "type": "org.springframework.cloud.gateway.config.HttpClientProperties$Pool", + "sourceType": "org.springframework.cloud.gateway.config.HttpClientProperties", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.httpclient.pool", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.httpclient.proxy", + "type": "org.springframework.cloud.gateway.config.HttpClientProperties$Proxy", + "sourceType": "org.springframework.cloud.gateway.config.HttpClientProperties", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.httpclient.proxy", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.httpclient.ssl", + "type": "org.springframework.cloud.gateway.config.HttpClientProperties$Ssl", + "sourceType": "org.springframework.cloud.gateway.config.HttpClientProperties", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.httpclient.ssl", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.httpclient.websocket", + "type": "org.springframework.cloud.gateway.config.HttpClientProperties$Websocket", + "sourceType": "org.springframework.cloud.gateway.config.HttpClientProperties", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.httpclient.websocket", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.loadbalancer", + "type": "org.springframework.cloud.gateway.config.GatewayLoadBalancerProperties", + "sourceType": "org.springframework.cloud.gateway.config.GatewayLoadBalancerProperties", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.loadbalancer", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.metrics", + "type": "org.springframework.cloud.gateway.config.GatewayMetricsProperties", + "sourceType": "org.springframework.cloud.gateway.config.GatewayMetricsProperties", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.metrics", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.redis-rate-limiter", + "type": "org.springframework.cloud.gateway.filter.ratelimit.RedisRateLimiter", + "sourceType": "org.springframework.cloud.gateway.filter.ratelimit.RedisRateLimiter", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.redis-rate-limiter", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.set-status", + "type": "org.springframework.cloud.gateway.filter.factory.SetStatusGatewayFilterFactory", + "sourceType": "org.springframework.cloud.gateway.filter.factory.SetStatusGatewayFilterFactory", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.set-status", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.x-forwarded", + "type": "org.springframework.cloud.gateway.filter.headers.XForwardedHeadersFilter", + "sourceType": "org.springframework.cloud.gateway.filter.headers.XForwardedHeadersFilter", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.x-forwarded", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.default-filters", + "type": "java.util.List", + "description": "List of filter definitions that are applied to every route.", + "sourceType": "org.springframework.cloud.gateway.config.GatewayProperties", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.default-filters", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.discovery.locator.enabled", + "type": "java.lang.Boolean", + "description": "Flag that enables DiscoveryClient gateway integration.", + "sourceType": "org.springframework.cloud.gateway.discovery.DiscoveryLocatorProperties", + "defaultValue": false, + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.discovery.locator.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.discovery.locator.filters", + "type": "java.util.List", + "sourceType": "org.springframework.cloud.gateway.discovery.DiscoveryLocatorProperties", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.discovery.locator.filters", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.discovery.locator.include-expression", + "type": "java.lang.String", + "description": "SpEL expression that will evaluate whether to include a service in gateway integration or not, defaults to: true.", + "sourceType": "org.springframework.cloud.gateway.discovery.DiscoveryLocatorProperties", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.discovery.locator.include-expression", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.discovery.locator.lower-case-service-id", + "type": "java.lang.Boolean", + "description": "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\/**", + "sourceType": "org.springframework.cloud.gateway.discovery.DiscoveryLocatorProperties", + "defaultValue": false, + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.discovery.locator.lower-case-service-id", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.discovery.locator.predicates", + "type": "java.util.List", + "sourceType": "org.springframework.cloud.gateway.discovery.DiscoveryLocatorProperties", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.discovery.locator.predicates", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.discovery.locator.route-id-prefix", + "type": "java.lang.String", + "description": "The prefix for the routeId, defaults to discoveryClient.getClass().getSimpleName() + \"_\". Service Id will be appended to create the routeId.", + "sourceType": "org.springframework.cloud.gateway.discovery.DiscoveryLocatorProperties", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.discovery.locator.route-id-prefix", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.discovery.locator.url-expression", + "type": "java.lang.String", + "description": "SpEL expression that create the uri for each route, defaults to: 'lb:\/\/'+serviceId.", + "sourceType": "org.springframework.cloud.gateway.discovery.DiscoveryLocatorProperties", + "defaultValue": "'lb:\/\/'+serviceId", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.discovery.locator.url-expression", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.enabled", + "type": "java.lang.Boolean", + "description": "Enables gateway functionality.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.fail-on-route-definition-error", + "type": "java.lang.Boolean", + "description": "Option to fail on route definition errors, defaults to true. Otherwise, a warning is logged.", + "sourceType": "org.springframework.cloud.gateway.config.GatewayProperties", + "defaultValue": true, + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.fail-on-route-definition-error", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.filter.add-request-header.enabled", + "type": "java.lang.Boolean", + "description": "Enables the add-request-header filter.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.filter.add-request-header.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.filter.add-request-parameter.enabled", + "type": "java.lang.Boolean", + "description": "Enables the add-request-parameter filter.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.filter.add-request-parameter.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.filter.add-response-header.enabled", + "type": "java.lang.Boolean", + "description": "Enables the add-response-header filter.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.filter.add-response-header.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.filter.circuit-breaker.enabled", + "type": "java.lang.Boolean", + "description": "Enables the circuit-breaker filter.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.filter.circuit-breaker.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.filter.dedupe-response-header.enabled", + "type": "java.lang.Boolean", + "description": "Enables the dedupe-response-header filter.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.filter.dedupe-response-header.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.filter.fallback-headers.enabled", + "type": "java.lang.Boolean", + "description": "Enables the fallback-headers filter.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.filter.fallback-headers.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.filter.hystrix.enabled", + "type": "java.lang.Boolean", + "description": "Enables the hystrix filter.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.filter.hystrix.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.filter.json-to-grpc.enabled", + "type": "java.lang.Boolean", + "description": "Enables the JSON to gRPC filter.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.filter.json-to-grpc.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.filter.local-response-cache.enabled", + "type": "java.lang.Boolean", + "description": "Enables the local-response-cache filter.", + "defaultValue": "false", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.filter.local-response-cache.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.filter.local-response-cache.request.no-cache-strategy", + "type": "org.springframework.cloud.gateway.filter.factory.cache.LocalResponseCacheProperties$NoCacheStrategy", + "sourceType": "org.springframework.cloud.gateway.filter.factory.cache.LocalResponseCacheProperties$RequestOptions", + "defaultValue": "skip-update-cache-entry", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.filter.local-response-cache.request.no-cache-strategy", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.filter.local-response-cache.size", + "type": "org.springframework.util.unit.DataSize", + "description": "Maximum size of the cache to evict entries for this route (in KB, MB and GB).", + "sourceType": "org.springframework.cloud.gateway.filter.factory.cache.LocalResponseCacheProperties", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.filter.local-response-cache.size", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.filter.local-response-cache.time-to-live", + "type": "java.time.Duration", + "description": "Time to expire a cache entry (expressed in s for seconds, m for minutes, and h for hours).", + "sourceType": "org.springframework.cloud.gateway.filter.factory.cache.LocalResponseCacheProperties", + "defaultValue": "5m", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.filter.local-response-cache.time-to-live", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.filter.map-request-header.enabled", + "type": "java.lang.Boolean", + "description": "Enables the map-request-header filter.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.filter.map-request-header.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.filter.modify-request-body.enabled", + "type": "java.lang.Boolean", + "description": "Enables the modify-request-body filter.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.filter.modify-request-body.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.filter.modify-response-body.enabled", + "type": "java.lang.Boolean", + "description": "Enables the modify-response-body filter.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.filter.modify-response-body.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.filter.prefix-path.enabled", + "type": "java.lang.Boolean", + "description": "Enables the prefix-path filter.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.filter.prefix-path.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.filter.preserve-host-header.enabled", + "type": "java.lang.Boolean", + "description": "Enables the preserve-host-header filter.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.filter.preserve-host-header.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.filter.redirect-to.enabled", + "type": "java.lang.Boolean", + "description": "Enables the redirect-to filter.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.filter.redirect-to.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.filter.remove-hop-by-hop.headers", + "type": "java.util.Set", + "sourceType": "org.springframework.cloud.gateway.filter.headers.RemoveHopByHopHeadersFilter", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.filter.remove-hop-by-hop.headers", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.filter.remove-hop-by-hop.order", + "type": "java.lang.Integer", + "sourceType": "org.springframework.cloud.gateway.filter.headers.RemoveHopByHopHeadersFilter", + "defaultValue": 0, + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.filter.remove-hop-by-hop.order", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.filter.remove-request-header.enabled", + "type": "java.lang.Boolean", + "description": "Enables the remove-request-header filter.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.filter.remove-request-header.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.filter.remove-request-parameter.enabled", + "type": "java.lang.Boolean", + "description": "Enables the remove-request-parameter filter.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.filter.remove-request-parameter.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.filter.remove-response-header.enabled", + "type": "java.lang.Boolean", + "description": "Enables the remove-response-header filter.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.filter.remove-response-header.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.filter.request-header-size.enabled", + "type": "java.lang.Boolean", + "description": "Enables the request-header-size filter.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.filter.request-header-size.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.filter.request-header-to-request-uri.enabled", + "type": "java.lang.Boolean", + "description": "Enables the request-header-to-request-uri filter.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.filter.request-header-to-request-uri.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.filter.request-rate-limiter.default-key-resolver", + "type": "org.springframework.cloud.gateway.filter.ratelimit.KeyResolver", + "sourceType": "org.springframework.cloud.gateway.filter.factory.RequestRateLimiterGatewayFilterFactory", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.filter.request-rate-limiter.default-key-resolver", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.filter.request-rate-limiter.default-rate-limiter", + "type": "org.springframework.cloud.gateway.filter.ratelimit.RateLimiter", + "sourceType": "org.springframework.cloud.gateway.filter.factory.RequestRateLimiterGatewayFilterFactory", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.filter.request-rate-limiter.default-rate-limiter", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.filter.request-rate-limiter.enabled", + "type": "java.lang.Boolean", + "description": "Enables the request-rate-limiter filter.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.filter.request-rate-limiter.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.filter.request-size.enabled", + "type": "java.lang.Boolean", + "description": "Enables the request-size filter.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.filter.request-size.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.filter.retry.enabled", + "type": "java.lang.Boolean", + "description": "Enables the retry filter.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.filter.retry.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.filter.rewrite-location-response-header.enabled", + "type": "java.lang.Boolean", + "description": "Enables the rewrite-location-response-header filter.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.filter.rewrite-location-response-header.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.filter.rewrite-location.enabled", + "type": "java.lang.Boolean", + "description": "Enables the rewrite-location filter.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.filter.rewrite-location.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.filter.rewrite-path.enabled", + "type": "java.lang.Boolean", + "description": "Enables the rewrite-path filter.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.filter.rewrite-path.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.filter.rewrite-request-parameter.enabled", + "type": "java.lang.Boolean", + "description": "Enables the rewrite-request-parameter filter.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.filter.rewrite-request-parameter.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.filter.rewrite-response-header.enabled", + "type": "java.lang.Boolean", + "description": "Enables the rewrite-response-header filter.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.filter.rewrite-response-header.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.filter.save-session.enabled", + "type": "java.lang.Boolean", + "description": "Enables the save-session filter.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.filter.save-session.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.filter.secure-headers.content-security-policy", + "type": "java.lang.String", + "sourceType": "org.springframework.cloud.gateway.filter.factory.SecureHeadersProperties", + "defaultValue": "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'", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.filter.secure-headers.content-security-policy", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.filter.secure-headers.content-type-options", + "type": "java.lang.String", + "sourceType": "org.springframework.cloud.gateway.filter.factory.SecureHeadersProperties", + "defaultValue": "nosniff", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.filter.secure-headers.content-type-options", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.filter.secure-headers.default-headers", + "type": "java.util.Set", + "sourceType": "org.springframework.cloud.gateway.filter.factory.SecureHeadersProperties", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.filter.secure-headers.default-headers", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.filter.secure-headers.disable", + "type": "java.util.List", + "sourceType": "org.springframework.cloud.gateway.filter.factory.SecureHeadersProperties", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.filter.secure-headers.disable", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.filter.secure-headers.disabled-headers", + "type": "java.util.Set", + "sourceType": "org.springframework.cloud.gateway.filter.factory.SecureHeadersProperties", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.filter.secure-headers.disabled-headers", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.filter.secure-headers.download-options", + "type": "java.lang.String", + "sourceType": "org.springframework.cloud.gateway.filter.factory.SecureHeadersProperties", + "defaultValue": "noopen", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.filter.secure-headers.download-options", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.filter.secure-headers.enabled", + "type": "java.lang.Boolean", + "description": "Enables the secure-headers filter.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.filter.secure-headers.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.filter.secure-headers.enabled-headers", + "type": "java.util.Set", + "sourceType": "org.springframework.cloud.gateway.filter.factory.SecureHeadersProperties", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.filter.secure-headers.enabled-headers", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.filter.secure-headers.frame-options", + "type": "java.lang.String", + "sourceType": "org.springframework.cloud.gateway.filter.factory.SecureHeadersProperties", + "defaultValue": "DENY", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.filter.secure-headers.frame-options", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.filter.secure-headers.permissions-policy", + "type": "java.lang.String", + "sourceType": "org.springframework.cloud.gateway.filter.factory.SecureHeadersProperties", + "defaultValue": "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=()", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.filter.secure-headers.permissions-policy", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.filter.secure-headers.permitted-cross-domain-policies", + "type": "java.lang.String", + "sourceType": "org.springframework.cloud.gateway.filter.factory.SecureHeadersProperties", + "defaultValue": "none", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.filter.secure-headers.permitted-cross-domain-policies", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.filter.secure-headers.referrer-policy", + "type": "java.lang.String", + "sourceType": "org.springframework.cloud.gateway.filter.factory.SecureHeadersProperties", + "defaultValue": "no-referrer", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.filter.secure-headers.referrer-policy", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.filter.secure-headers.strict-transport-security", + "type": "java.lang.String", + "sourceType": "org.springframework.cloud.gateway.filter.factory.SecureHeadersProperties", + "defaultValue": "max-age=631138519", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.filter.secure-headers.strict-transport-security", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.filter.secure-headers.xss-protection-header", + "type": "java.lang.String", + "sourceType": "org.springframework.cloud.gateway.filter.factory.SecureHeadersProperties", + "defaultValue": "1 ; mode=block", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.filter.secure-headers.xss-protection-header", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.filter.set-path.enabled", + "type": "java.lang.Boolean", + "description": "Enables the set-path filter.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.filter.set-path.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.filter.set-request-header.enabled", + "type": "java.lang.Boolean", + "description": "Enables the set-request-header filter.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.filter.set-request-header.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.filter.set-request-host-header.enabled", + "type": "java.lang.Boolean", + "description": "Enables the set-request-host-header filter.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.filter.set-request-host-header.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.filter.set-response-header.enabled", + "type": "java.lang.Boolean", + "description": "Enables the set-response-header filter.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.filter.set-response-header.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.filter.set-status.enabled", + "type": "java.lang.Boolean", + "description": "Enables the set-status filter.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.filter.set-status.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.filter.strip-prefix.enabled", + "type": "java.lang.Boolean", + "description": "Enables the strip-prefix filter.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.filter.strip-prefix.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.forwarded.by.enabled", + "type": "java.lang.Boolean", + "description": "Enables the Forwarded: by header part.", + "defaultValue": "false", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.forwarded.by.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.forwarded.enabled", + "type": "java.lang.Boolean", + "description": "Enables the ForwardedHeadersFilter.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.forwarded.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.global-filter.adapt-cached-body.enabled", + "type": "java.lang.Boolean", + "description": "Enables the adapt-cached-body global filter.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.global-filter.adapt-cached-body.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.global-filter.forward-path.enabled", + "type": "java.lang.Boolean", + "description": "Enables the forward-path global filter.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.global-filter.forward-path.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.global-filter.forward-routing.enabled", + "type": "java.lang.Boolean", + "description": "Enables the forward-routing global filter.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.global-filter.forward-routing.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.global-filter.load-balancer-client.enabled", + "type": "java.lang.Boolean", + "description": "Enables the load-balancer-client global filter.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.global-filter.load-balancer-client.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.global-filter.local-response-cache.enabled", + "type": "java.lang.Boolean", + "description": "Enables the local-response-cache filter for all routes, it allows to add a specific configuration at route level using LocalResponseCache filter.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.global-filter.local-response-cache.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.global-filter.netty-routing.enabled", + "type": "java.lang.Boolean", + "description": "Enables the netty-routing global filter.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.global-filter.netty-routing.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.global-filter.netty-write-response.enabled", + "type": "java.lang.Boolean", + "description": "Enables the netty-write-response global filter.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.global-filter.netty-write-response.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.global-filter.reactive-load-balancer-client.enabled", + "type": "java.lang.Boolean", + "description": "Enables the reactive-load-balancer-client global filter.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.global-filter.reactive-load-balancer-client.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.global-filter.remove-cached-body.enabled", + "type": "java.lang.Boolean", + "description": "Enables the remove-cached-body global filter.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.global-filter.remove-cached-body.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.global-filter.route-to-request-url.enabled", + "type": "java.lang.Boolean", + "description": "Enables the route-to-request-url global filter.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.global-filter.route-to-request-url.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.global-filter.websocket-routing.enabled", + "type": "java.lang.Boolean", + "description": "Enables the websocket-routing global filter.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.global-filter.websocket-routing.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.globalcors.add-to-simple-url-handler-mapping", + "type": "java.lang.Boolean", + "description": "If global CORS config should be added to the URL handler.", + "defaultValue": "false", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.globalcors.add-to-simple-url-handler-mapping", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.globalcors.cors-configurations", + "type": "java.util.Map", + "sourceType": "org.springframework.cloud.gateway.config.GlobalCorsProperties", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.globalcors.cors-configurations", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.handler-mapping.order", + "type": "java.lang.Integer", + "description": "The order of RoutePredicateHandlerMapping.", + "defaultValue": "1", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.handler-mapping.order", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.httpclient.compression", + "type": "java.lang.Boolean", + "description": "Enables compression for Netty HttpClient.", + "sourceType": "org.springframework.cloud.gateway.config.HttpClientProperties", + "defaultValue": false, + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.httpclient.compression", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.httpclient.connect-timeout", + "type": "java.lang.Integer", + "description": "The connect timeout in millis, the default is 30s.", + "sourceType": "org.springframework.cloud.gateway.config.HttpClientProperties", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.httpclient.connect-timeout", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.httpclient.max-header-size", + "type": "org.springframework.util.unit.DataSize", + "description": "The max response header size.", + "sourceType": "org.springframework.cloud.gateway.config.HttpClientProperties", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.httpclient.max-header-size", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.httpclient.max-initial-line-length", + "type": "org.springframework.util.unit.DataSize", + "description": "The max initial line length.", + "sourceType": "org.springframework.cloud.gateway.config.HttpClientProperties", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.httpclient.max-initial-line-length", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.httpclient.pool.acquire-timeout", + "type": "java.lang.Long", + "description": "Only for type FIXED, the maximum time in millis to wait for acquiring.", + "sourceType": "org.springframework.cloud.gateway.config.HttpClientProperties$Pool", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.httpclient.pool.acquire-timeout", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.httpclient.pool.eviction-interval", + "type": "java.time.Duration", + "description": "Perform regular eviction checks in the background at a specified interval. Disabled by default ({@link Duration#ZERO})", + "sourceType": "org.springframework.cloud.gateway.config.HttpClientProperties$Pool", + "defaultValue": 0, + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.httpclient.pool.eviction-interval", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.httpclient.pool.leasing-strategy", + "type": "org.springframework.cloud.gateway.config.HttpClientProperties$Pool$LeasingStrategy", + "description": "Configures the leasing strategy for the pool (fifo or lifo), defaults to FIFO which is Netty's default.", + "sourceType": "org.springframework.cloud.gateway.config.HttpClientProperties$Pool", + "defaultValue": "fifo", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.httpclient.pool.leasing-strategy", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.httpclient.pool.max-connections", + "type": "java.lang.Integer", + "description": "Only for type FIXED, the maximum number of connections before starting pending acquisition on existing ones.", + "sourceType": "org.springframework.cloud.gateway.config.HttpClientProperties$Pool", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.httpclient.pool.max-connections", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.httpclient.pool.max-idle-time", + "type": "java.time.Duration", + "description": "Time in millis after which the channel will be closed. If NULL, there is no max idle time.", + "sourceType": "org.springframework.cloud.gateway.config.HttpClientProperties$Pool", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.httpclient.pool.max-idle-time", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.httpclient.pool.max-life-time", + "type": "java.time.Duration", + "description": "Duration after which the channel will be closed. If NULL, there is no max life time.", + "sourceType": "org.springframework.cloud.gateway.config.HttpClientProperties$Pool", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.httpclient.pool.max-life-time", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.httpclient.pool.metrics", + "type": "java.lang.Boolean", + "description": "Enables channel pools metrics to be collected and registered in Micrometer. Disabled by default.", + "sourceType": "org.springframework.cloud.gateway.config.HttpClientProperties$Pool", + "defaultValue": false, + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.httpclient.pool.metrics", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.httpclient.pool.name", + "type": "java.lang.String", + "description": "The channel pool map name, defaults to proxy.", + "sourceType": "org.springframework.cloud.gateway.config.HttpClientProperties$Pool", + "defaultValue": "proxy", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.httpclient.pool.name", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.httpclient.pool.type", + "type": "org.springframework.cloud.gateway.config.HttpClientProperties$Pool$PoolType", + "description": "Type of pool for HttpClient to use (elastic, fixed or disabled).", + "sourceType": "org.springframework.cloud.gateway.config.HttpClientProperties$Pool", + "defaultValue": "elastic", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.httpclient.pool.type", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.httpclient.proxy.host", + "type": "java.lang.String", + "description": "Hostname for proxy configuration of Netty HttpClient.", + "sourceType": "org.springframework.cloud.gateway.config.HttpClientProperties$Proxy", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.httpclient.proxy.host", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.httpclient.proxy.non-proxy-hosts-pattern", + "type": "java.lang.String", + "description": "Regular expression (Java) for a configured list of hosts. that should be reached directly, bypassing the proxy", + "sourceType": "org.springframework.cloud.gateway.config.HttpClientProperties$Proxy", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.httpclient.proxy.non-proxy-hosts-pattern", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.httpclient.proxy.password", + "type": "java.lang.String", + "description": "Password for proxy configuration of Netty HttpClient.", + "sourceType": "org.springframework.cloud.gateway.config.HttpClientProperties$Proxy", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.httpclient.proxy.password", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.httpclient.proxy.port", + "type": "java.lang.Integer", + "description": "Port for proxy configuration of Netty HttpClient.", + "sourceType": "org.springframework.cloud.gateway.config.HttpClientProperties$Proxy", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.httpclient.proxy.port", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.httpclient.proxy.type", + "type": "reactor.netty.transport.ProxyProvider$Proxy", + "description": "proxyType for proxy configuration of Netty HttpClient (http, socks4 or socks5).", + "sourceType": "org.springframework.cloud.gateway.config.HttpClientProperties$Proxy", + "defaultValue": "http", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.httpclient.proxy.type", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.httpclient.proxy.username", + "type": "java.lang.String", + "description": "Username for proxy configuration of Netty HttpClient.", + "sourceType": "org.springframework.cloud.gateway.config.HttpClientProperties$Proxy", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.httpclient.proxy.username", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.httpclient.response-timeout", + "type": "java.time.Duration", + "description": "The response timeout.", + "sourceType": "org.springframework.cloud.gateway.config.HttpClientProperties", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.httpclient.response-timeout", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.httpclient.ssl.close-notify-flush-timeout", + "type": "java.time.Duration", + "description": "SSL close_notify flush timeout. Default to 3000 ms.", + "sourceType": "org.springframework.cloud.gateway.config.HttpClientProperties$Ssl", + "defaultValue": "3000ms", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.httpclient.ssl.close-notify-flush-timeout", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.httpclient.ssl.close-notify-read-timeout", + "type": "java.time.Duration", + "description": "SSL close_notify read timeout. Default to 0 ms.", + "sourceType": "org.springframework.cloud.gateway.config.HttpClientProperties$Ssl", + "defaultValue": 0, + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.httpclient.ssl.close-notify-read-timeout", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.httpclient.ssl.handshake-timeout", + "type": "java.time.Duration", + "description": "SSL handshake timeout. Default to 10000 ms", + "sourceType": "org.springframework.cloud.gateway.config.HttpClientProperties$Ssl", + "defaultValue": "10000ms", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.httpclient.ssl.handshake-timeout", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.httpclient.ssl.key-password", + "type": "java.lang.String", + "description": "Key password, default is same as keyStorePassword.", + "sourceType": "org.springframework.cloud.gateway.config.HttpClientProperties$Ssl", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.httpclient.ssl.key-password", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.httpclient.ssl.key-store", + "type": "java.lang.String", + "description": "Keystore path for Netty HttpClient.", + "sourceType": "org.springframework.cloud.gateway.config.HttpClientProperties$Ssl", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.httpclient.ssl.key-store", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.httpclient.ssl.key-store-password", + "type": "java.lang.String", + "description": "Keystore password.", + "sourceType": "org.springframework.cloud.gateway.config.HttpClientProperties$Ssl", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.httpclient.ssl.key-store-password", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.httpclient.ssl.key-store-provider", + "type": "java.lang.String", + "description": "Keystore provider for Netty HttpClient, optional field.", + "sourceType": "org.springframework.cloud.gateway.config.HttpClientProperties$Ssl", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.httpclient.ssl.key-store-provider", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.httpclient.ssl.key-store-type", + "type": "java.lang.String", + "description": "Keystore type for Netty HttpClient, default is JKS.", + "sourceType": "org.springframework.cloud.gateway.config.HttpClientProperties$Ssl", + "defaultValue": "JKS", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.httpclient.ssl.key-store-type", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.httpclient.ssl.ssl-bundle", + "type": "java.lang.String", + "description": "The name of the SSL bundle to use.", + "sourceType": "org.springframework.cloud.gateway.config.HttpClientProperties$Ssl", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.httpclient.ssl.ssl-bundle", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.httpclient.ssl.trusted-x509-certificates", + "type": "java.util.List", + "description": "Trusted certificates for verifying the remote endpoint's certificate.", + "sourceType": "org.springframework.cloud.gateway.config.HttpClientProperties$Ssl", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.httpclient.ssl.trusted-x509-certificates", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.httpclient.ssl.use-insecure-trust-manager", + "type": "java.lang.Boolean", + "description": "Installs the netty InsecureTrustManagerFactory. This is insecure and not suitable for production.", + "sourceType": "org.springframework.cloud.gateway.config.HttpClientProperties$Ssl", + "defaultValue": false, + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.httpclient.ssl.use-insecure-trust-manager", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.httpclient.websocket.max-frame-payload-length", + "type": "java.lang.Integer", + "description": "Max frame payload length.", + "sourceType": "org.springframework.cloud.gateway.config.HttpClientProperties$Websocket", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.httpclient.websocket.max-frame-payload-length", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.httpclient.websocket.proxy-ping", + "type": "java.lang.Boolean", + "description": "Proxy ping frames to downstream services, defaults to true.", + "sourceType": "org.springframework.cloud.gateway.config.HttpClientProperties$Websocket", + "defaultValue": true, + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.httpclient.websocket.proxy-ping", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.httpclient.wiretap", + "type": "java.lang.Boolean", + "description": "Enables wiretap debugging for Netty HttpClient.", + "sourceType": "org.springframework.cloud.gateway.config.HttpClientProperties", + "defaultValue": false, + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.httpclient.wiretap", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.httpserver.wiretap", + "type": "java.lang.Boolean", + "description": "Enables wiretap debugging for Netty HttpServer.", + "defaultValue": "false", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.httpserver.wiretap", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.loadbalancer.use404", + "type": "java.lang.Boolean", + "sourceType": "org.springframework.cloud.gateway.config.GatewayLoadBalancerProperties", + "defaultValue": false, + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.loadbalancer.use404", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.metrics.enabled", + "type": "java.lang.Boolean", + "description": "Enables the collection of metrics data.", + "sourceType": "org.springframework.cloud.gateway.config.GatewayMetricsProperties", + "defaultValue": false, + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.metrics.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.metrics.prefix", + "type": "java.lang.String", + "description": "The prefix of all metrics emitted by gateway.", + "sourceType": "org.springframework.cloud.gateway.config.GatewayMetricsProperties", + "defaultValue": "spring.cloud.gateway", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.metrics.prefix", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.metrics.tags", + "type": "java.util.Map", + "description": "Tags map that added to metrics.", + "sourceType": "org.springframework.cloud.gateway.config.GatewayMetricsProperties", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.metrics.tags", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.mvc.routes", + "type": "java.util.List", + "description": "List of Routes.", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.mvc.routes", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.observability.enabled", + "type": "java.lang.Boolean", + "description": "If Micrometer Observability support should be turned on.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.observability.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.predicate.after.enabled", + "type": "java.lang.Boolean", + "description": "Enables the after predicate.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.predicate.after.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.predicate.before.enabled", + "type": "java.lang.Boolean", + "description": "Enables the before predicate.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.predicate.before.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.predicate.between.enabled", + "type": "java.lang.Boolean", + "description": "Enables the between predicate.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.predicate.between.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.predicate.cloud-foundry-route-service.enabled", + "type": "java.lang.Boolean", + "description": "Enables the cloud-foundry-route-service predicate.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.predicate.cloud-foundry-route-service.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.predicate.cookie.enabled", + "type": "java.lang.Boolean", + "description": "Enables the cookie predicate.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.predicate.cookie.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.predicate.header.enabled", + "type": "java.lang.Boolean", + "description": "Enables the header predicate.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.predicate.header.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.predicate.host.enabled", + "type": "java.lang.Boolean", + "description": "Enables the host predicate.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.predicate.host.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.predicate.host.include-port", + "type": "java.lang.Boolean", + "description": "Include the port in matching the host name.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.predicate.host.include-port", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.predicate.method.enabled", + "type": "java.lang.Boolean", + "description": "Enables the method predicate.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.predicate.method.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.predicate.path.enabled", + "type": "java.lang.Boolean", + "description": "Enables the path predicate.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.predicate.path.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.predicate.query.enabled", + "type": "java.lang.Boolean", + "description": "Enables the query predicate.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.predicate.query.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.predicate.read-body.enabled", + "type": "java.lang.Boolean", + "description": "Enables the read-body predicate.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.predicate.read-body.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.predicate.remote-addr.enabled", + "type": "java.lang.Boolean", + "description": "Enables the remote-addr predicate.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.predicate.remote-addr.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.predicate.weight.enabled", + "type": "java.lang.Boolean", + "description": "Enables the weight predicate.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.predicate.weight.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.predicate.xforwarded-remote-addr.enabled", + "type": "java.lang.Boolean", + "description": "Enables the xforwarded-remote-addr predicate.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.predicate.xforwarded-remote-addr.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.redis-rate-limiter.burst-capacity-header", + "type": "java.lang.String", + "description": "The name of the header that returns the burst capacity configuration.", + "sourceType": "org.springframework.cloud.gateway.filter.ratelimit.RedisRateLimiter", + "defaultValue": "X-RateLimit-Burst-Capacity", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.redis-rate-limiter.burst-capacity-header", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.redis-rate-limiter.config", + "type": "java.util.Map", + "sourceType": "org.springframework.cloud.gateway.filter.ratelimit.RedisRateLimiter", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.redis-rate-limiter.config", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.redis-rate-limiter.include-headers", + "type": "java.lang.Boolean", + "description": "Whether or not to include headers containing rate limiter information, defaults to true.", + "sourceType": "org.springframework.cloud.gateway.filter.ratelimit.RedisRateLimiter", + "defaultValue": true, + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.redis-rate-limiter.include-headers", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.redis-rate-limiter.remaining-header", + "type": "java.lang.String", + "description": "The name of the header that returns number of remaining requests during the current second.", + "sourceType": "org.springframework.cloud.gateway.filter.ratelimit.RedisRateLimiter", + "defaultValue": "X-RateLimit-Remaining", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.redis-rate-limiter.remaining-header", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.redis-rate-limiter.replenish-rate-header", + "type": "java.lang.String", + "description": "The name of the header that returns the replenish rate configuration.", + "sourceType": "org.springframework.cloud.gateway.filter.ratelimit.RedisRateLimiter", + "defaultValue": "X-RateLimit-Replenish-Rate", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.redis-rate-limiter.replenish-rate-header", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.redis-rate-limiter.requested-tokens-header", + "type": "java.lang.String", + "description": "The name of the header that returns the requested tokens configuration.", + "sourceType": "org.springframework.cloud.gateway.filter.ratelimit.RedisRateLimiter", + "defaultValue": "X-RateLimit-Requested-Tokens", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.redis-rate-limiter.requested-tokens-header", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.redis-route-definition-repository.enabled", + "type": "java.lang.Boolean", + "description": "If RedisRouteDefinitionRepository should be enabled.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.redis-route-definition-repository.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.restrictive-property-accessor.enabled", + "type": "java.lang.Boolean", + "description": "Restricts method and property access in SpEL.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.restrictive-property-accessor.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.route-filter-cache-enabled", + "type": "java.lang.Boolean", + "description": "Enables the route filter cache, defaults to false.", + "sourceType": "org.springframework.cloud.gateway.config.GatewayProperties", + "defaultValue": false, + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.route-filter-cache-enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.route-refresh-listener.enabled", + "type": "java.lang.Boolean", + "description": "If RouteRefreshListener should be turned on.", + "defaultValue": "true", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.route-refresh-listener.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.routes", + "type": "java.util.List", + "description": "List of Routes.", + "sourceType": "org.springframework.cloud.gateway.config.GatewayProperties", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.routes", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.set-status.original-status-header-name", + "type": "java.lang.String", + "description": "The name of the header which contains http code of the proxied request.", + "sourceType": "org.springframework.cloud.gateway.filter.factory.SetStatusGatewayFilterFactory", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.set-status.original-status-header-name", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.streaming-media-types", + "type": "java.util.List", + "sourceType": "org.springframework.cloud.gateway.config.GatewayProperties", + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.streaming-media-types", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.x-forwarded.enabled", + "type": "java.lang.Boolean", + "description": "If the XForwardedHeadersFilter is enabled.", + "sourceType": "org.springframework.cloud.gateway.filter.headers.XForwardedHeadersFilter", + "defaultValue": true, + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.x-forwarded.enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.x-forwarded.for-append", + "type": "java.lang.Boolean", + "description": "If appending X-Forwarded-For as a list is enabled.", + "sourceType": "org.springframework.cloud.gateway.filter.headers.XForwardedHeadersFilter", + "defaultValue": true, + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.x-forwarded.for-append", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.x-forwarded.for-enabled", + "type": "java.lang.Boolean", + "description": "If X-Forwarded-For is enabled.", + "sourceType": "org.springframework.cloud.gateway.filter.headers.XForwardedHeadersFilter", + "defaultValue": true, + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.x-forwarded.for-enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.x-forwarded.host-append", + "type": "java.lang.Boolean", + "description": "If appending X-Forwarded-Host as a list is enabled.", + "sourceType": "org.springframework.cloud.gateway.filter.headers.XForwardedHeadersFilter", + "defaultValue": true, + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.x-forwarded.host-append", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.x-forwarded.host-enabled", + "type": "java.lang.Boolean", + "description": "If X-Forwarded-Host is enabled.", + "sourceType": "org.springframework.cloud.gateway.filter.headers.XForwardedHeadersFilter", + "defaultValue": true, + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.x-forwarded.host-enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.x-forwarded.order", + "type": "java.lang.Integer", + "description": "The order of the XForwardedHeadersFilter.", + "sourceType": "org.springframework.cloud.gateway.filter.headers.XForwardedHeadersFilter", + "defaultValue": 0, + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.x-forwarded.order", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.x-forwarded.port-append", + "type": "java.lang.Boolean", + "description": "If appending X-Forwarded-Port as a list is enabled.", + "sourceType": "org.springframework.cloud.gateway.filter.headers.XForwardedHeadersFilter", + "defaultValue": true, + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.x-forwarded.port-append", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.x-forwarded.port-enabled", + "type": "java.lang.Boolean", + "description": "If X-Forwarded-Port is enabled.", + "sourceType": "org.springframework.cloud.gateway.filter.headers.XForwardedHeadersFilter", + "defaultValue": true, + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.x-forwarded.port-enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.x-forwarded.prefix-append", + "type": "java.lang.Boolean", + "description": "If appending X-Forwarded-Prefix as a list is enabled.", + "sourceType": "org.springframework.cloud.gateway.filter.headers.XForwardedHeadersFilter", + "defaultValue": true, + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.x-forwarded.prefix-append", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.x-forwarded.prefix-enabled", + "type": "java.lang.Boolean", + "description": "If X-Forwarded-Prefix is enabled.", + "sourceType": "org.springframework.cloud.gateway.filter.headers.XForwardedHeadersFilter", + "defaultValue": true, + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.x-forwarded.prefix-enabled", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.x-forwarded.proto-append", + "type": "java.lang.Boolean", + "description": "If appending X-Forwarded-Proto as a list is enabled.", + "sourceType": "org.springframework.cloud.gateway.filter.headers.XForwardedHeadersFilter", + "defaultValue": true, + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.x-forwarded.proto-append", + "since": "4.3.0" + } + }, + { + "name": "spring.cloud.gateway.x-forwarded.proto-enabled", + "type": "java.lang.Boolean", + "description": "If X-Forwarded-Proto is enabled.", + "sourceType": "org.springframework.cloud.gateway.filter.headers.XForwardedHeadersFilter", + "defaultValue": true, + "deprecated": true, + "deprecation": { + "replacement": "spring.cloud.gateway.server.webflux.x-forwarded.proto-enabled", + "since": "4.3.0" + } } ] } diff --git a/spring-cloud-gateway-server/src/main/resources/META-INF/spring.factories b/spring-cloud-gateway-server/src/main/resources/META-INF/spring.factories index dd7e5a51..38fc4166 100644 --- a/spring-cloud-gateway-server/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-gateway-server/src/main/resources/META-INF/spring.factories @@ -3,4 +3,8 @@ org.springframework.cloud.gateway.config.GatewayEnvironmentPostProcessor # Failure Analyzers org.springframework.boot.diagnostics.FailureAnalyzer=\ -org.springframework.cloud.gateway.support.MvcFoundOnClasspathFailureAnalyzer \ No newline at end of file +org.springframework.cloud.gateway.support.MvcFoundOnClasspathFailureAnalyzer + +# Application Listeners +org.springframework.context.ApplicationListener=\ + org.springframework.cloud.gateway.config.GatewayServerWebfluxPropertiesMigrationListener \ No newline at end of file diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/actuate/GatewayControllerEndpointRedisRefreshTest.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/actuate/GatewayControllerEndpointRedisRefreshTest.java new file mode 100644 index 00000000..b2b331bf --- /dev/null +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/actuate/GatewayControllerEndpointRedisRefreshTest.java @@ -0,0 +1,149 @@ +/* + * Copyright 2013-2024 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.actuate; + +import java.net.URI; +import java.time.Duration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.awaitility.Awaitility; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition; +import org.springframework.cloud.gateway.route.RouteDefinition; +import org.springframework.http.MediaType; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.web.reactive.function.BodyInserters; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; + +/** + * @author Peter Müller + */ +@SpringBootTest(properties = { "management.endpoint.gateway.enabled=true", + "management.endpoints.web.exposure.include=*", "spring.cloud.gateway.actuator.verbose.enabled=true" }, + webEnvironment = RANDOM_PORT) +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) +@ActiveProfiles("redis-route-repository") +@Testcontainers +@Tag("DockerRequired") +public class GatewayControllerEndpointRedisRefreshTest { + + @Container + public static GenericContainer redis = new GenericContainer<>("redis:5.0.14-alpine").withExposedPorts(6379); + + @BeforeAll + public static void startRedisContainer() { + redis.start(); + } + + @DynamicPropertySource + static void containerProperties(DynamicPropertyRegistry registry) { + registry.add("spring.data.redis.host", redis::getHost); + registry.add("spring.data.redis.port", redis::getFirstMappedPort); + } + + @Autowired + WebTestClient testClient; + + @LocalServerPort + int port; + + @Test + public void testCorsConfigurationAfterReload() { + Map cors = new HashMap<>(); + cors.put("allowCredentials", false); + cors.put("allowedOrigins", "*"); + cors.put("allowedMethods", "GET"); + + createOrUpdateRouteWithCors(cors); + + Awaitility.await().atMost(Duration.ofSeconds(3)).untilAsserted(() -> assertRouteHasCorsConfig(cors)); + Awaitility.await().atMost(Duration.ofSeconds(3)).untilAsserted(() -> assertPreflightAllowOrigin("*")); + + cors.put("allowedOrigins", "http://example.org"); + createOrUpdateRouteWithCors(cors); + + Awaitility.await().atMost(Duration.ofSeconds(3)).untilAsserted(() -> assertRouteHasCorsConfig(cors)); + Awaitility.await() + .atMost(Duration.ofSeconds(3)) + .untilAsserted(() -> assertPreflightAllowOrigin("http://example.org")); + } + + void createOrUpdateRouteWithCors(Map cors) { + RouteDefinition testRouteDefinition = new RouteDefinition(); + testRouteDefinition.setUri(URI.create("http://example.org")); + + PredicateDefinition methodRoutePredicateDefinition = new PredicateDefinition("Method=GET"); + testRouteDefinition.setPredicates(List.of(methodRoutePredicateDefinition)); + + testRouteDefinition.setMetadata(Map.of("cors", cors)); + + testClient.post() + .uri("http://localhost:" + port + "/actuator/gateway/routes/cors-test-route") + .accept(MediaType.APPLICATION_JSON) + .body(BodyInserters.fromValue(testRouteDefinition)) + .exchange() + .expectStatus() + .isCreated(); + + testClient.post() + .uri("http://localhost:" + port + "/actuator/gateway/refresh") + .exchange() + .expectStatus() + .isOk(); + } + + void assertRouteHasCorsConfig(Map cors) { + testClient.get() + .uri("http://localhost:" + port + "/actuator/gateway/routes/cors-test-route") + .exchange() + .expectStatus() + .isOk() + .expectBody() + .jsonPath("$.metadata") + .value(map -> assertThat((Map) map).hasSize(1).containsEntry("cors", cors)); + } + + void assertPreflightAllowOrigin(String origin) { + testClient.options() + .uri("http://localhost:" + port + "/") + .header("Origin", "http://example.org") + .header("Access-Control-Request-Method", "GET") + .exchange() + .expectStatus() + .isOk() + .expectHeader() + .valueEquals("Access-Control-Allow-Origin", origin); + } + +} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/GatewayAutoConfigurationTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/GatewayAutoConfigurationTests.java index 34fdd032..0e1445dd 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/GatewayAutoConfigurationTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/GatewayAutoConfigurationTests.java @@ -118,19 +118,20 @@ public class GatewayAutoConfigurationTests { .withConfiguration(AutoConfigurations.of(WebFluxAutoConfiguration.class, MetricsAutoConfiguration.class, SimpleMetricsExportAutoConfiguration.class, GatewayAutoConfiguration.class, HttpClientCustomizedConfig.class, ServerPropertiesConfig.class)) - .withPropertyValues("spring.cloud.gateway.httpclient.ssl.use-insecure-trust-manager=true", - "spring.cloud.gateway.httpclient.ssl.ssl-bundle=mybundle", - "spring.cloud.gateway.httpclient.connect-timeout=10", - "spring.cloud.gateway.httpclient.response-timeout=10s", - "spring.cloud.gateway.httpclient.pool.eviction-interval=10s", - "spring.cloud.gateway.httpclient.pool.type=fixed", - "spring.cloud.gateway.httpclient.pool.metrics=true", - "spring.cloud.gateway.httpclient.pool.leasing-strategy=lifo", - "spring.cloud.gateway.httpclient.compression=true", "spring.cloud.gateway.httpclient.wiretap=true", + .withPropertyValues("spring.cloud.gateway.server.webflux.httpclient.ssl.use-insecure-trust-manager=true", + "spring.cloud.gateway.server.webflux.httpclient.ssl.ssl-bundle=mybundle", + "spring.cloud.gateway.server.webflux.httpclient.connect-timeout=10", + "spring.cloud.gateway.server.webflux.httpclient.response-timeout=10s", + "spring.cloud.gateway.server.webflux.httpclient.pool.eviction-interval=10s", + "spring.cloud.gateway.server.webflux.httpclient.pool.type=fixed", + "spring.cloud.gateway.server.webflux.httpclient.pool.metrics=true", + "spring.cloud.gateway.server.webflux.httpclient.pool.leasing-strategy=lifo", + "spring.cloud.gateway.server.webflux.httpclient.compression=true", + "spring.cloud.gateway.server.webflux.httpclient.wiretap=true", // greater than integer max value - "spring.cloud.gateway.httpclient.max-initial-line-length=2147483647", - "spring.cloud.gateway.httpclient.proxy.host=myhost", - "spring.cloud.gateway.httpclient.websocket.max-frame-payload-length=1024") + "spring.cloud.gateway.server.webflux.httpclient.max-initial-line-length=2147483647", + "spring.cloud.gateway.server.webflux.httpclient.proxy.host=myhost", + "spring.cloud.gateway.server.webflux.httpclient.websocket.max-frame-payload-length=1024") .run(context -> { assertThat(context).hasSingleBean(HttpClient.class); HttpClient httpClient = context.getBean(HttpClient.class); @@ -185,7 +186,7 @@ public class GatewayAutoConfigurationTests { @Test public void verboseActuatorDisabled() { try (ConfigurableApplicationContext ctx = SpringApplication.run(Config.class, "--spring.jmx.enabled=false", - "--server.port=0", "--spring.cloud.gateway.actuator.verbose.enabled=false", + "--server.port=0", "--spring.cloud.gateway.server.webflux.actuator.verbose.enabled=false", "--management.endpoint.gateway.enabled=true")) { assertThat(ctx.getBeanNamesForType(GatewayLegacyControllerEndpoint.class)).hasSize(1); } @@ -233,7 +234,8 @@ public class GatewayAutoConfigurationTests { public void noTokenRelayFilter() { assertThatThrownBy(() -> { try (ConfigurableApplicationContext ctx = SpringApplication.run(RouteLocatorBuilderConfig.class, - "--spring.jmx.enabled=false", "--spring.cloud.gateway.filter.token-relay.enabled=false", + "--spring.jmx.enabled=false", + "--spring.cloud.gateway.server.webflux.filter.token-relay.enabled=false", "--spring.security.oauth2.client.provider[testprovider].authorization-uri=http://localhost", "--spring.security.oauth2.client.provider[testprovider].token-uri=http://localhost/token", "--spring.security.oauth2.client.registration[test].provider=testprovider", diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/GatewayMetricsAutoConfigurationTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/GatewayMetricsAutoConfigurationTests.java index d8e71d86..e06d7226 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/GatewayMetricsAutoConfigurationTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/GatewayMetricsAutoConfigurationTests.java @@ -88,7 +88,7 @@ public class GatewayMetricsAutoConfigurationTests { } @Nested - @SpringBootTest(classes = Config.class, properties = "spring.cloud.gateway.metrics.enabled=false") + @SpringBootTest(classes = Config.class, properties = GatewayProperties.PREFIX + ".metrics.enabled=false") public class DisabledByProperty { @Autowired(required = false) @@ -110,7 +110,7 @@ public class GatewayMetricsAutoConfigurationTests { } @Nested - @SpringBootTest(classes = Config.class, properties = "spring.cloud.gateway.observability.enabled=false") + @SpringBootTest(classes = Config.class, properties = GatewayProperties.PREFIX + ".observability.enabled=false") public class ObservabilityDisabledByProperty { @Autowired diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/GatewayPropertiesMigrationTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/GatewayPropertiesMigrationTests.java new file mode 100644 index 00000000..9cb9c4d0 --- /dev/null +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/GatewayPropertiesMigrationTests.java @@ -0,0 +1,71 @@ +/* + * 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 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.filter.factory.AddRequestParameterGatewayFilterFactory; +import org.springframework.cloud.gateway.filter.headers.ForwardedHeadersFilter; +import org.springframework.cloud.gateway.filter.headers.XForwardedHeadersFilter; +import org.springframework.cloud.gateway.handler.predicate.AfterRoutePredicateFactory; +import org.springframework.cloud.gateway.route.RouteRefreshListener; +import org.springframework.context.ApplicationContext; +import org.springframework.test.context.ActiveProfiles; + +import static org.assertj.core.api.Assertions.assertThat; + +@SuppressWarnings("unchecked") +@SpringBootTest(properties = { "debug=true" }, webEnvironment = WebEnvironment.RANDOM_PORT) +@ActiveProfiles("propertiesmigrationtests") +public class GatewayPropertiesMigrationTests { + + @Autowired + ApplicationContext context; + + @Autowired + GatewayProperties properties; + + @Test + public void deprecatedPropertiesWork() { + assertBeanDoesNotExist(AddRequestParameterGatewayFilterFactory.class); + assertBeanDoesNotExist(AfterRoutePredicateFactory.class); + assertBeanDoesNotExist(ForwardedHeadersFilter.class); + assertBeanDoesNotExist(RouteRefreshListener.class); + assertBeanDoesNotExist(XForwardedHeadersFilter.class); + } + + @Test + public void deprecatedRoutePropertiesWork() { + assertThat(properties.getRoutes()).hasSize(2); + } + + private void assertBeanDoesNotExist(Class type) { + assertThat(context.getBeanNamesForType(type)).isEmpty(); + } + + @SpringBootConfiguration + @EnableAutoConfiguration + protected static class TestConfiguration { + + } + +} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/GatewayRedisAutoConfigurationTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/GatewayRedisAutoConfigurationTests.java index a3a1b6ce..66028470 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/GatewayRedisAutoConfigurationTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/GatewayRedisAutoConfigurationTests.java @@ -75,7 +75,7 @@ public class GatewayRedisAutoConfigurationTests { } @Nested - @SpringBootTest(classes = Config.class, properties = "spring.cloud.gateway.redis.enabled=false") + @SpringBootTest(classes = Config.class, properties = GatewayProperties.PREFIX + ".redis.enabled=false") class DisabledByProperty { @Autowired(required = false) @@ -97,7 +97,7 @@ public class GatewayRedisAutoConfigurationTests { */ @Nested @SpringBootTest(classes = GatewayRedisAutoConfigurationTests.Config.class, - properties = "spring.cloud.gateway.redis-route-definition-repository.enabled=false") + properties = GatewayProperties.PREFIX + ".redis-route-definition-repository.enabled=false") @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) class RedisRouteDefinitionRepositoryDisabledByProperty { diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/GatewayRedisRouteDefinitionRepositoryEnabledByPropertyTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/GatewayRedisRouteDefinitionRepositoryEnabledByPropertyTests.java index ccf240e8..232d6e93 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/GatewayRedisRouteDefinitionRepositoryEnabledByPropertyTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/GatewayRedisRouteDefinitionRepositoryEnabledByPropertyTests.java @@ -33,7 +33,7 @@ import org.springframework.test.context.DynamicPropertySource; import static org.assertj.core.api.Assertions.assertThat; @SpringBootTest(classes = GatewayRedisAutoConfigurationTests.Config.class, - properties = "spring.cloud.gateway.redis-route-definition-repository.enabled=true") + properties = GatewayProperties.PREFIX + ".redis-route-definition-repository.enabled=true") @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) @Testcontainers @Tag("DockerRequired") diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/LocalResponseCacheAutoConfigurationTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/LocalResponseCacheAutoConfigurationTests.java index e0aee8cf..8b61ae27 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/LocalResponseCacheAutoConfigurationTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/LocalResponseCacheAutoConfigurationTests.java @@ -33,7 +33,7 @@ public class LocalResponseCacheAutoConfigurationTests { void onlyOneCacheManagerBeanCreated() { new ApplicationContextRunner() .withConfiguration(AutoConfigurations.of(LocalResponseCacheAutoConfiguration.class)) - .withPropertyValues("spring.cloud.gateway.filter.local-response-cache.enabled=true") + .withPropertyValues(GatewayProperties.PREFIX + ".filter.local-response-cache.enabled=true") .run(context -> { context.containsBean(LocalResponseCacheAutoConfiguration.RESPONSE_CACHE_MANAGER_NAME); context.assertThat().hasSingleBean(GlobalLocalResponseCacheGatewayFilter.class); @@ -45,7 +45,7 @@ public class LocalResponseCacheAutoConfigurationTests { new ApplicationContextRunner() .withConfiguration( AutoConfigurations.of(CustomCacheManagerConfig.class, LocalResponseCacheAutoConfiguration.class)) - .withPropertyValues("spring.cloud.gateway.filter.local-response-cache.enabled=true") + .withPropertyValues(GatewayProperties.PREFIX + ".filter.local-response-cache.enabled=true") .run(context -> { context.containsBean(LocalResponseCacheAutoConfiguration.RESPONSE_CACHE_MANAGER_NAME); context.containsBean("myCacheManager"); diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/conditional/DisableBuiltInFiltersTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/conditional/DisableBuiltInFiltersTests.java index 2669059d..b581dfa0 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/conditional/DisableBuiltInFiltersTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/conditional/DisableBuiltInFiltersTests.java @@ -56,8 +56,8 @@ public class DisableBuiltInFiltersTests { @Nested @SpringBootTest(classes = Config.class, - properties = { "spring.cloud.gateway.filter.add-request-header.enabled=false", - "spring.cloud.gateway.filter.map-request-header.enabled=false" }) + properties = { "spring.cloud.gateway.server.webflux.filter.add-request-header.enabled=false", + "spring.cloud.gateway.server.webflux.filter.map-request-header.enabled=false" }) @ActiveProfiles("disable-components") public class DisableSpecificsFiltersByProperty { @@ -76,45 +76,45 @@ public class DisableBuiltInFiltersTests { @Nested @SpringBootTest(classes = Config.class, - properties = { "spring.cloud.gateway.filter.add-request-header.enabled=false", - "spring.cloud.gateway.filter.map-request-header.enabled=false", - "spring.cloud.gateway.filter.add-request-headers-if-not-present.enabled=false", - "spring.cloud.gateway.filter.add-request-parameter.enabled=false", - "spring.cloud.gateway.filter.add-response-header.enabled=false", - "spring.cloud.gateway.filter.json-to-grpc.enabled=false", - "spring.cloud.gateway.filter.modify-request-body.enabled=false", - "spring.cloud.gateway.filter.local-response-cache.enabled=false", - "spring.cloud.gateway.filter.dedupe-response-header.enabled=false", - "spring.cloud.gateway.filter.modify-response-body.enabled=false", - "spring.cloud.gateway.filter.prefix-path.enabled=false", - "spring.cloud.gateway.filter.preserve-host-header.enabled=false", - "spring.cloud.gateway.filter.redirect-to.enabled=false", - "spring.cloud.gateway.filter.remove-json-attributes-response-body.enabled=false", - "spring.cloud.gateway.filter.remove-request-header.enabled=false", - "spring.cloud.gateway.filter.remove-request-parameter.enabled=false", - "spring.cloud.gateway.filter.remove-response-header.enabled=false", - "spring.cloud.gateway.filter.request-rate-limiter.enabled=false", - "spring.cloud.gateway.filter.rewrite-path.enabled=false", - "spring.cloud.gateway.filter.retry.enabled=false", - "spring.cloud.gateway.filter.set-path.enabled=false", - "spring.cloud.gateway.filter.secure-headers.enabled=false", - "spring.cloud.gateway.filter.set-request-header.enabled=false", - "spring.cloud.gateway.filter.set-request-host-header.enabled=false", - "spring.cloud.gateway.filter.set-response-header.enabled=false", - "spring.cloud.gateway.filter.rewrite-response-header.enabled=false", - "spring.cloud.gateway.filter.rewrite-location-response-header.enabled=false", - "spring.cloud.gateway.filter.rewrite-location.enabled=false", - "spring.cloud.gateway.filter.rewrite-request-parameter.enabled=false", - "spring.cloud.gateway.filter.set-status.enabled=false", - "spring.cloud.gateway.filter.save-session.enabled=false", - "spring.cloud.gateway.filter.strip-prefix.enabled=false", - "spring.cloud.gateway.filter.request-header-to-request-uri.enabled=false", - "spring.cloud.gateway.filter.request-size.enabled=false", - "spring.cloud.gateway.filter.request-header-size.enabled=false", - "spring.cloud.gateway.filter.circuit-breaker.enabled=false", - "spring.cloud.gateway.filter.token-relay.enabled=false", - "spring.cloud.gateway.filter.cache-request-body.enabled=false", - "spring.cloud.gateway.filter.fallback-headers.enabled=false" }) + properties = { "spring.cloud.gateway.server.webflux.filter.add-request-header.enabled=false", + "spring.cloud.gateway.server.webflux.filter.map-request-header.enabled=false", + "spring.cloud.gateway.server.webflux.filter.add-request-headers-if-not-present.enabled=false", + "spring.cloud.gateway.server.webflux.filter.add-request-parameter.enabled=false", + "spring.cloud.gateway.server.webflux.filter.add-response-header.enabled=false", + "spring.cloud.gateway.server.webflux.filter.json-to-grpc.enabled=false", + "spring.cloud.gateway.server.webflux.filter.modify-request-body.enabled=false", + "spring.cloud.gateway.server.webflux.filter.local-response-cache.enabled=false", + "spring.cloud.gateway.server.webflux.filter.dedupe-response-header.enabled=false", + "spring.cloud.gateway.server.webflux.filter.modify-response-body.enabled=false", + "spring.cloud.gateway.server.webflux.filter.prefix-path.enabled=false", + "spring.cloud.gateway.server.webflux.filter.preserve-host-header.enabled=false", + "spring.cloud.gateway.server.webflux.filter.redirect-to.enabled=false", + "spring.cloud.gateway.server.webflux.filter.remove-json-attributes-response-body.enabled=false", + "spring.cloud.gateway.server.webflux.filter.remove-request-header.enabled=false", + "spring.cloud.gateway.server.webflux.filter.remove-request-parameter.enabled=false", + "spring.cloud.gateway.server.webflux.filter.remove-response-header.enabled=false", + "spring.cloud.gateway.server.webflux.filter.request-rate-limiter.enabled=false", + "spring.cloud.gateway.server.webflux.filter.rewrite-path.enabled=false", + "spring.cloud.gateway.server.webflux.filter.retry.enabled=false", + "spring.cloud.gateway.server.webflux.filter.set-path.enabled=false", + "spring.cloud.gateway.server.webflux.filter.secure-headers.enabled=false", + "spring.cloud.gateway.server.webflux.filter.set-request-header.enabled=false", + "spring.cloud.gateway.server.webflux.filter.set-request-host-header.enabled=false", + "spring.cloud.gateway.server.webflux.filter.set-response-header.enabled=false", + "spring.cloud.gateway.server.webflux.filter.rewrite-response-header.enabled=false", + "spring.cloud.gateway.server.webflux.filter.rewrite-location-response-header.enabled=false", + "spring.cloud.gateway.server.webflux.filter.rewrite-location.enabled=false", + "spring.cloud.gateway.server.webflux.filter.rewrite-request-parameter.enabled=false", + "spring.cloud.gateway.server.webflux.filter.set-status.enabled=false", + "spring.cloud.gateway.server.webflux.filter.save-session.enabled=false", + "spring.cloud.gateway.server.webflux.filter.strip-prefix.enabled=false", + "spring.cloud.gateway.server.webflux.filter.request-header-to-request-uri.enabled=false", + "spring.cloud.gateway.server.webflux.filter.request-size.enabled=false", + "spring.cloud.gateway.server.webflux.filter.request-header-size.enabled=false", + "spring.cloud.gateway.server.webflux.filter.circuit-breaker.enabled=false", + "spring.cloud.gateway.server.webflux.filter.token-relay.enabled=false", + "spring.cloud.gateway.server.webflux.filter.cache-request-body.enabled=false", + "spring.cloud.gateway.server.webflux.filter.fallback-headers.enabled=false" }) @ActiveProfiles("disable-components") public class DisableAllFiltersByProperty { diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/conditional/DisableBuiltInGlobalFiltersTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/conditional/DisableBuiltInGlobalFiltersTests.java index 95217bab..519ea449 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/conditional/DisableBuiltInGlobalFiltersTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/conditional/DisableBuiltInGlobalFiltersTests.java @@ -56,8 +56,8 @@ public class DisableBuiltInGlobalFiltersTests { @Nested @SpringBootTest(classes = Config.class, - properties = { "spring.cloud.gateway.global-filter.remove-cached-body.enabled=false", - "spring.cloud.gateway.global-filter.route-to-request-url.enabled=false" }) + properties = { "spring.cloud.gateway.server.webflux.global-filter.remove-cached-body.enabled=false", + "spring.cloud.gateway.server.webflux.global-filter.route-to-request-url.enabled=false" }) @ActiveProfiles("disable-components") public class DisableSpecificsFiltersByProperty { @@ -74,21 +74,21 @@ public class DisableBuiltInGlobalFiltersTests { } @Nested - @SpringBootTest(classes = Config.class, - properties = { "spring.cloud.gateway.global-filter.adapt-cached-body.enabled=false", - "spring.cloud.gateway.global-filter.remove-cached-body.enabled=false", - "spring.cloud.gateway.global-filter.route-to-request-url.enabled=false", - "spring.cloud.gateway.global-filter.forward-routing.enabled=false", - "spring.cloud.gateway.global-filter.forward-path.enabled=false", - "spring.cloud.gateway.global-filter.websocket-routing.enabled=false", - "spring.cloud.gateway.global-filter.netty-write-response.enabled=false", - "spring.cloud.gateway.global-filter.netty-routing.enabled=false", - "spring.cloud.gateway.global-filter.reactive-load-balancer-client.enabled=false", - "spring.cloud.gateway.global-filter.load-balancer-client.enabled=false", - "spring.cloud.gateway.global-filter.load-balancer-service-instance-cookie.enabled=false", - "spring.cloud.gateway.global-filter.function-routing.enabled=false", - "spring.cloud.gateway.global-filter.stream-routing.enabled=false", - "spring.cloud.gateway.metrics.enabled=false" }) + @SpringBootTest(classes = Config.class, properties = { + "spring.cloud.gateway.server.webflux.global-filter.adapt-cached-body.enabled=false", + "spring.cloud.gateway.server.webflux.global-filter.remove-cached-body.enabled=false", + "spring.cloud.gateway.server.webflux.global-filter.route-to-request-url.enabled=false", + "spring.cloud.gateway.server.webflux.global-filter.forward-routing.enabled=false", + "spring.cloud.gateway.server.webflux.global-filter.forward-path.enabled=false", + "spring.cloud.gateway.server.webflux.global-filter.websocket-routing.enabled=false", + "spring.cloud.gateway.server.webflux.global-filter.netty-write-response.enabled=false", + "spring.cloud.gateway.server.webflux.global-filter.netty-routing.enabled=false", + "spring.cloud.gateway.server.webflux.global-filter.reactive-load-balancer-client.enabled=false", + "spring.cloud.gateway.server.webflux.global-filter.load-balancer-client.enabled=false", + "spring.cloud.gateway.server.webflux.global-filter.load-balancer-service-instance-cookie.enabled=false", + "spring.cloud.gateway.server.webflux.global-filter.function-routing.enabled=false", + "spring.cloud.gateway.server.webflux.global-filter.stream-routing.enabled=false", + "spring.cloud.gateway.server.webflux.metrics.enabled=false" }) @ActiveProfiles("disable-components") public class DisableAllGlobalFiltersByProperty { diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/conditional/DisableBuiltInPredicatesTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/conditional/DisableBuiltInPredicatesTests.java index e5871bd9..1e0a4453 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/conditional/DisableBuiltInPredicatesTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/conditional/DisableBuiltInPredicatesTests.java @@ -56,8 +56,8 @@ public class DisableBuiltInPredicatesTests { @Nested @SpringBootTest(classes = Config.class, - properties = { "spring.cloud.gateway.predicate.after.enabled=false", - "spring.cloud.gateway.predicate.before.enabled=false" }) + properties = { "spring.cloud.gateway.server.webflux.predicate.after.enabled=false", + "spring.cloud.gateway.server.webflux.predicate.before.enabled=false" }) @ActiveProfiles("disable-components") public class DisableSpecificsPredicatesByProperty { @@ -74,18 +74,21 @@ public class DisableBuiltInPredicatesTests { } @Nested - @SpringBootTest(classes = Config.class, properties = { "spring.cloud.gateway.predicate.after.enabled=false", - "spring.cloud.gateway.predicate.before.enabled=false", - "spring.cloud.gateway.predicate.between.enabled=false", - "spring.cloud.gateway.predicate.cookie.enabled=false", - "spring.cloud.gateway.predicate.header.enabled=false", "spring.cloud.gateway.predicate.host.enabled=false", - "spring.cloud.gateway.predicate.method.enabled=false", "spring.cloud.gateway.predicate.path.enabled=false", - "spring.cloud.gateway.predicate.query.enabled=false", - "spring.cloud.gateway.predicate.read-body.enabled=false", - "spring.cloud.gateway.predicate.remote-addr.enabled=false", - "spring.cloud.gateway.predicate.xforwarded-remote-addr.enabled=false", - "spring.cloud.gateway.predicate.weight.enabled=false", - "spring.cloud.gateway.predicate.cloud-foundry-route-service.enabled=false" }) + @SpringBootTest(classes = Config.class, + properties = { "spring.cloud.gateway.server.webflux.predicate.after.enabled=false", + "spring.cloud.gateway.server.webflux.predicate.before.enabled=false", + "spring.cloud.gateway.server.webflux.predicate.between.enabled=false", + "spring.cloud.gateway.server.webflux.predicate.cookie.enabled=false", + "spring.cloud.gateway.server.webflux.predicate.header.enabled=false", + "spring.cloud.gateway.server.webflux.predicate.host.enabled=false", + "spring.cloud.gateway.server.webflux.predicate.method.enabled=false", + "spring.cloud.gateway.server.webflux.predicate.path.enabled=false", + "spring.cloud.gateway.server.webflux.predicate.query.enabled=false", + "spring.cloud.gateway.server.webflux.predicate.read-body.enabled=false", + "spring.cloud.gateway.server.webflux.predicate.remote-addr.enabled=false", + "spring.cloud.gateway.server.webflux.predicate.xforwarded-remote-addr.enabled=false", + "spring.cloud.gateway.server.webflux.predicate.weight.enabled=false", + "spring.cloud.gateway.server.webflux.predicate.cloud-foundry-route-service.enabled=false" }) @ActiveProfiles("disable-components") public class DisableAllPredicatesByProperty { diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/conditional/OnEnabledComponentTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/conditional/OnEnabledComponentTests.java index 0bc62eeb..b1853ab2 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/conditional/OnEnabledComponentTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/conditional/OnEnabledComponentTests.java @@ -61,7 +61,7 @@ class OnEnabledComponentTests { String componentName = "disabled-component"; this.onEnabledComponent = createOnEnabledComponent(componentName); when(conditionContext.getEnvironment()).thenReturn(environment); - environment.setProperty("spring.cloud.gateway." + componentName + ".enabled", "false"); + environment.setProperty("spring.cloud.gateway.server.webflux." + componentName + ".enabled", "false"); ConditionOutcome outcome = onEnabledComponent.getMatchOutcome(conditionContext, mockMetaData(DisabledComponent.class)); diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/cors/CorsGlobalTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/cors/CorsGlobalTests.java index f4e2f791..4ce14ce0 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/cors/CorsGlobalTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/cors/CorsGlobalTests.java @@ -89,7 +89,8 @@ public class CorsGlobalTests extends BaseWebClientTests { } - @SpringBootTest(classes = TestConfig.class, properties = "spring.cloud.gateway.globalcors.enabled=false") + @SpringBootTest(classes = TestConfig.class, + properties = "spring.cloud.gateway.server.webflux.globalcors.enabled=false") public static class DisabledByProperty { @Autowired(required = false) diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/cors/SimpleUrlHandlerCorsTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/cors/SimpleUrlHandlerCorsTests.java index 4784fd3f..1ed009f9 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/cors/SimpleUrlHandlerCorsTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/cors/SimpleUrlHandlerCorsTests.java @@ -40,7 +40,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; @SpringBootTest(webEnvironment = RANDOM_PORT, - properties = "spring.cloud.gateway.globalcors.add-to-simple-url-handler-mapping=true") + properties = "spring.cloud.gateway.server.webflux.globalcors.add-to-simple-url-handler-mapping=true") @DirtiesContext @ActiveProfiles("request-header-web-filter") public class SimpleUrlHandlerCorsTests extends BaseWebClientTests { diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/discovery/DiscoveryClientRouteDefinitionLocatorIntegrationTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/discovery/DiscoveryClientRouteDefinitionLocatorIntegrationTests.java index 1178515c..16b4fd2a 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/discovery/DiscoveryClientRouteDefinitionLocatorIntegrationTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/discovery/DiscoveryClientRouteDefinitionLocatorIntegrationTests.java @@ -38,8 +38,8 @@ import org.springframework.context.annotation.Bean; import static org.assertj.core.api.Assertions.assertThat; @SpringBootTest(classes = DiscoveryClientRouteDefinitionLocatorIntegrationTests.Config.class, - properties = { "spring.cloud.gateway.discovery.locator.enabled=true", - "spring.cloud.gateway.discovery.locator.route-id-prefix=test__" }) + properties = { "spring.cloud.gateway.server.webflux.discovery.locator.enabled=true", + "spring.cloud.gateway.server.webflux.discovery.locator.route-id-prefix=test__" }) public class DiscoveryClientRouteDefinitionLocatorIntegrationTests { @Autowired diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/discovery/DiscoveryClientRouteDefinitionLocatorTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/discovery/DiscoveryClientRouteDefinitionLocatorTests.java index fc26a822..6e2f6d60 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/discovery/DiscoveryClientRouteDefinitionLocatorTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/discovery/DiscoveryClientRouteDefinitionLocatorTests.java @@ -42,17 +42,17 @@ import static org.springframework.cloud.gateway.filter.factory.RewritePathGatewa import static org.springframework.cloud.gateway.handler.predicate.RoutePredicateFactory.PATTERN_KEY; @SpringBootTest(classes = DiscoveryClientRouteDefinitionLocatorTests.Config.class, - properties = { "spring.cloud.gateway.discovery.locator.enabled=true", - "spring.cloud.gateway.discovery.locator.route-id-prefix=testedge_", - "spring.cloud.gateway.discovery.locator.include-expression=metadata['edge'] == 'true'", - "spring.cloud.gateway.discovery.locator.lower-case-service-id=true" + properties = { "spring.cloud.gateway.server.webflux.discovery.locator.enabled=true", + "spring.cloud.gateway.server.webflux.discovery.locator.route-id-prefix=testedge_", + "spring.cloud.gateway.server.webflux.discovery.locator.include-expression=metadata['edge'] == 'true'", + "spring.cloud.gateway.server.webflux.discovery.locator.lower-case-service-id=true" /* - * "spring.cloud.gateway.discovery.locator.predicates[0].name=Path", - * "spring.cloud.gateway.discovery.locator.predicates[0].args[pattern]='/'+serviceId.toLowerCase(Locale.ROOT)+'/**'", - * "spring.cloud.gateway.discovery.locator.filters[0].name=RewritePath", - * "spring.cloud.gateway.discovery.locator.filters[0].args[regexp]='/' + serviceId.toLowerCase(Locale.ROOT) + '/(?.*)'" + * "spring.cloud.gateway.server.webflux.discovery.locator.predicates[0].name=Path", + * "spring.cloud.gateway.server.webflux.discovery.locator.predicates[0].args[pattern]='/'+serviceId.toLowerCase(Locale.ROOT)+'/**'", + * "spring.cloud.gateway.server.webflux.discovery.locator.filters[0].name=RewritePath", + * "spring.cloud.gateway.server.webflux.discovery.locator.filters[0].args[regexp]='/' + serviceId.toLowerCase(Locale.ROOT) + '/(?.*)'" * , - * "spring.cloud.gateway.discovery.locator.filters[0].args[replacement]='/$\\\\{remaining}'", + * "spring.cloud.gateway.server.webflux.discovery.locator.filters[0].args[replacement]='/$\\\\{remaining}'", */ }) public class DiscoveryClientRouteDefinitionLocatorTests { diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/discovery/ReactiveGatewayDiscoveryClientAutoConfigurationTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/discovery/ReactiveGatewayDiscoveryClientAutoConfigurationTests.java index ef03d723..22eadc7e 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/discovery/ReactiveGatewayDiscoveryClientAutoConfigurationTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/discovery/ReactiveGatewayDiscoveryClientAutoConfigurationTests.java @@ -35,8 +35,8 @@ public class ReactiveGatewayDiscoveryClientAutoConfigurationTests { @Nested @SpringBootTest(classes = Config.class, - properties = { "spring.cloud.gateway.discovery.locator.enabled=true", - "spring.cloud.gateway.loadbalancer.use404=true", + properties = { "spring.cloud.gateway.server.webflux.discovery.locator.enabled=true", + "spring.cloud.gateway.server.webflux.loadbalancer.use404=true", "spring.cloud.discovery.client.simple.instances.service[0].uri=https://service1:443" }) public class EnabledByProperty { diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/NettyRoutingFilterTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/NettyRoutingFilterTests.java index ace0bcc2..744dbc4e 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/NettyRoutingFilterTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/NettyRoutingFilterTests.java @@ -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. @@ -16,18 +16,26 @@ package org.springframework.cloud.gateway.filter; +import java.util.Collections; +import java.util.HashMap; + +import io.netty.channel.ChannelOption; import org.hamcrest.Matchers; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import reactor.core.publisher.Mono; import reactor.netty.DisposableServer; +import reactor.netty.http.client.HttpClient; +import reactor.netty.http.client.HttpClientConfig; import reactor.netty.http.server.HttpServer; 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.context.environment.EnvironmentChangeEvent; +import org.springframework.cloud.gateway.route.Route; import org.springframework.cloud.gateway.route.RouteLocator; import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; import org.springframework.cloud.gateway.test.BaseWebClientTests; @@ -35,6 +43,8 @@ import org.springframework.cloud.gateway.test.PermitAllSecurityConfiguration; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.MapPropertySource; import org.springframework.test.util.TestSocketUtils; import org.springframework.test.web.reactive.server.WebTestClient; @@ -48,6 +58,12 @@ class NettyRoutingFilterTests extends BaseWebClientTests { @Autowired private ApplicationContext context; + @Autowired + private NettyRoutingFilter nettyRoutingFilter; + + @Autowired + private RouteLocator routeLocator; + @BeforeAll public static void beforeAll() { port = TestSocketUtils.findAvailableTcpPort(); @@ -88,6 +104,31 @@ class NettyRoutingFilterTests extends BaseWebClientTests { } } + @Test + void testConnectTimeoutConfigurationReloadWorks() { + ConfigurableEnvironment environment = (ConfigurableEnvironment) this.context.getEnvironment(); + HashMap map = new HashMap<>(); + environment.getPropertySources().addFirst(new MapPropertySource("mock", map)); + map.put("spring.cloud.gateway.server.webflux.httpclient.connect-timeout", "1000"); + this.context.publishEvent(new EnvironmentChangeEvent(this.context, + Collections.singleton("spring.cloud.gateway.server.webflux.httpclient.connect-timeout"))); + Route route = routeLocator.getRoutes() + .filter(r -> r.getId().equals("refreshable_configuration_test")) + .blockLast(); + assertThat(route).isNotNull(); + HttpClient httpClient = nettyRoutingFilter.getHttpClient(route, null); + HttpClientConfig configuration = httpClient.configuration(); + assertThat(configuration.options().get(ChannelOption.CONNECT_TIMEOUT_MILLIS)).isEqualTo(1000); + + map.put("spring.cloud.gateway.server.webflux.httpclient.connect-timeout", "5000"); + this.context.publishEvent(new EnvironmentChangeEvent(this.context, + Collections.singleton("spring.cloud.gateway.server.webflux.httpclient.connect-timeout"))); + httpClient = nettyRoutingFilter.getHttpClient(route, null); + configuration = httpClient.configuration(); + assertThat(configuration.options().get(ChannelOption.CONNECT_TIMEOUT_MILLIS)).isEqualTo(5000); + environment.getPropertySources().remove("mock"); + } + @SpringBootConfiguration @EnableAutoConfiguration @Import(PermitAllSecurityConfiguration.class) @@ -98,6 +139,7 @@ class NettyRoutingFilterTests extends BaseWebClientTests { return builder.routes() .route(p -> p.path("/mockexample").filters(f -> f.prefixPath("/httpbin")).uri("http://example.com")) .route(p -> p.path("/issue").uri("HTTP://127.0.0.1:" + port)) + .route("refreshable_configuration_test", p -> p.path("/refresh").uri("http://example.com")) .build(); } diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RemoveJsonAttributesResponseBodyGatewayFilterFactoryTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RemoveJsonAttributesResponseBodyGatewayFilterFactoryTests.java index 12482d1a..228a1990 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RemoveJsonAttributesResponseBodyGatewayFilterFactoryTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RemoveJsonAttributesResponseBodyGatewayFilterFactoryTests.java @@ -19,6 +19,7 @@ package org.springframework.cloud.gateway.filter.factory; import java.util.Map; import org.junit.jupiter.api.Test; +import reactor.core.publisher.Mono; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringBootConfiguration; @@ -30,8 +31,12 @@ import org.springframework.cloud.gateway.test.BaseWebClientTests; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; import org.springframework.test.annotation.DirtiesContext; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; @@ -42,10 +47,10 @@ import static org.springframework.cloud.gateway.test.TestUtils.getMap; */ @SpringBootTest(webEnvironment = RANDOM_PORT) @DirtiesContext -public class RemoveJsonAttributesResponseBodyGatewayFilterFactoryTests extends BaseWebClientTests { +class RemoveJsonAttributesResponseBodyGatewayFilterFactoryTests extends BaseWebClientTests { @Test - public void removeJsonAttributeRootWorks() { + void removeJsonAttributeRootWorks() { testClient.post() .uri("/post") .header("Host", "www.removejsonattributes.org") @@ -70,8 +75,7 @@ public class RemoveJsonAttributesResponseBodyGatewayFilterFactoryTests extends B } @Test - public void removeJsonAttributeRecursivelyWorks() { - + void removeJsonAttributeRecursivelyWorks() { testClient.post() .uri("/post") .header("Host", "www.removejsonattributesrecursively.org") @@ -93,8 +97,7 @@ public class RemoveJsonAttributesResponseBodyGatewayFilterFactoryTests extends B } @Test - public void removeJsonAttributeNoMatchesWorks() { - + void removeJsonAttributeNoMatchesWorks() { testClient.post() .uri("/post") .header("Host", "www.removejsonattributesnomatches.org") @@ -113,6 +116,21 @@ public class RemoveJsonAttributesResponseBodyGatewayFilterFactoryTests extends B }); } + @Test + void raisedWhenRemoveJsonAttributes() { + testClient.post() + .uri("/post") + .header("Host", "www.raisederrorwhenremovejsonattributes.org") + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .exchange() + .expectStatus() + .is5xxServerError() + .expectBody(String.class) + .consumeWith(result -> { + assertThat(result.getResponseBody()).isEqualTo("Failed to process JSON of response body."); + }); + } + @EnableAutoConfiguration @SpringBootConfiguration @Import(DefaultTestConfig.class) @@ -142,9 +160,27 @@ public class RemoveJsonAttributesResponseBodyGatewayFilterFactoryTests extends B .host("{sub}.removejsonattributesnomatches.org") .filters(f -> f.removeJsonAttributes("test")) .uri(uri)) + .route("raised_error_when_remove_json_attributes", + r -> r.path("/post") + .and() + .host("{sub}.raisederrorwhenremovejsonattributes.org") + .filters(f -> f.removeJsonAttributes("test") + .modifyResponseBody(String.class, String.class, + (exchange, response) -> Mono.just("{\"invalid_json\": test"))) + .uri(uri)) .build(); } + @ControllerAdvice + public class GlobalExceptionHandler { + + @ExceptionHandler(IllegalStateException.class) + public ResponseEntity handleIllegalException(IllegalStateException ex) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(ex.getMessage()); + } + + } + } } diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RetryGatewayFilterFactoryIntegrationTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RetryGatewayFilterFactoryIntegrationTests.java index fb1439d1..fdc20f33 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RetryGatewayFilterFactoryIntegrationTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RetryGatewayFilterFactoryIntegrationTests.java @@ -64,8 +64,8 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; @SpringBootTest(webEnvironment = RANDOM_PORT, - properties = { "spring.cloud.gateway.httpclient.connect-timeout=500", - "spring.cloud.gateway.httpclient.response-timeout=2s", + properties = { "spring.cloud.gateway.server.webflux.httpclient.connect-timeout=500", + "spring.cloud.gateway.server.webflux.httpclient.response-timeout=2s", "logging.level.org.springframework.cloud.gateway.filter.factory.RetryGatewayFilterFactory=TRACE" }) @DirtiesContext // default filter AddResponseHeader suppresses bug diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/cache/LocalResponseCacheGatewayFilterFactoryTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/cache/LocalResponseCacheGatewayFilterFactoryTests.java index 1325e75e..9c218228 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/cache/LocalResponseCacheGatewayFilterFactoryTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/cache/LocalResponseCacheGatewayFilterFactoryTests.java @@ -69,7 +69,7 @@ public class LocalResponseCacheGatewayFilterFactoryTests extends BaseWebClientTe } @Nested - @SpringBootTest(properties = { "spring.cloud.gateway.filter.local-response-cache.enabled=true" }, + @SpringBootTest(properties = { "spring.cloud.gateway.server.webflux.filter.local-response-cache.enabled=true" }, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class UsingFilterParams extends BaseWebClientTests { @@ -441,8 +441,8 @@ public class LocalResponseCacheGatewayFilterFactoryTests extends BaseWebClientTe @Nested @SpringBootTest( - properties = { "spring.cloud.gateway.filter.local-response-cache.enabled=true", - "spring.cloud.gateway.filter.local-response-cache.timeToLive=20s" }, + properties = { "spring.cloud.gateway.server.webflux.filter.local-response-cache.enabled=true", + "spring.cloud.gateway.server.webflux.filter.local-response-cache.timeToLive=20s" }, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class UsingPropertiesAsDefault extends BaseWebClientTests { @@ -550,9 +550,9 @@ public class LocalResponseCacheGatewayFilterFactoryTests extends BaseWebClientTe } @Nested - @SpringBootTest(properties = { "spring.cloud.gateway.filter.local-response-cache.enabled=true", - "spring.cloud.gateway.filter.local-response-cache.time-to-live=2m", - "spring.cloud.gateway.filter.local-response-cache.request.no-cache-strategy=skip-update-cache-entry" }, + @SpringBootTest(properties = { "spring.cloud.gateway.server.webflux.filter.local-response-cache.enabled=true", + "spring.cloud.gateway.server.webflux.filter.local-response-cache.time-to-live=2m", + "spring.cloud.gateway.server.webflux.filter.local-response-cache.request.no-cache-strategy=skip-update-cache-entry" }, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class DirectiveNoCacheSkippingUpdate extends BaseWebClientTests { @@ -656,10 +656,9 @@ public class LocalResponseCacheGatewayFilterFactoryTests extends BaseWebClientTe } @Nested - @SpringBootTest( - properties = { "spring.cloud.gateway.filter.local-response-cache.enabled=true", - "spring.cloud.gateway.filter.local-response-cache.time-to-live=2m", - "spring.cloud.gateway.filter.local-response-cache.request.no-cache-strategy=update-cache-entry" }, + @SpringBootTest(properties = { "spring.cloud.gateway.server.webflux.filter.local-response-cache.enabled=true", + "spring.cloud.gateway.server.webflux.filter.local-response-cache.time-to-live=2m", + "spring.cloud.gateway.server.webflux.filter.local-response-cache.request.no-cache-strategy=update-cache-entry" }, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class DirectiveNoCacheWithUpdate extends BaseWebClientTests { diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/cache/LocalResponseCacheGlobalFilterTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/cache/LocalResponseCacheGlobalFilterTests.java index f5f4c668..11b8cef4 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/cache/LocalResponseCacheGlobalFilterTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/cache/LocalResponseCacheGlobalFilterTests.java @@ -46,8 +46,8 @@ public class LocalResponseCacheGlobalFilterTests { @Nested @SpringBootTest( - properties = { "spring.cloud.gateway.filter.local-response-cache.enabled=true", - "spring.cloud.gateway.global-filter.local-response-cache.enabled=false" }, + properties = { "spring.cloud.gateway.server.webflux.filter.local-response-cache.enabled=true", + "spring.cloud.gateway.server.webflux.global-filter.local-response-cache.enabled=false" }, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class GlobalCacheNotEnabled extends BaseWebClientTests { @@ -98,7 +98,7 @@ public class LocalResponseCacheGlobalFilterTests { } @Nested - @SpringBootTest(properties = { "spring.cloud.gateway.filter.local-response-cache.enabled=true" }, + @SpringBootTest(properties = { "spring.cloud.gateway.server.webflux.filter.local-response-cache.enabled=true" }, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class GlobalCacheEnabled extends BaseWebClientTests { diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/rewrite/ModifyRequestBodyGatewayFilterFactorySslTimeoutTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/rewrite/ModifyRequestBodyGatewayFilterFactorySslTimeoutTests.java index 8ffc9794..0f7113c5 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/rewrite/ModifyRequestBodyGatewayFilterFactorySslTimeoutTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/rewrite/ModifyRequestBodyGatewayFilterFactorySslTimeoutTests.java @@ -60,7 +60,7 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen * @author fangfeikun */ @SpringBootTest(webEnvironment = RANDOM_PORT, - properties = { "spring.cloud.gateway.httpclient.ssl.handshake-timeout=1ms", + properties = { "spring.cloud.gateway.server.webflux.httpclient.ssl.handshake-timeout=1ms", "spring.main.allow-bean-definition-overriding=true" }) @DirtiesContext @ActiveProfiles("single-cert-ssl") diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/ratelimit/Bucket4jRateLimiterTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/ratelimit/Bucket4jRateLimiterTests.java index f262300b..43831cd8 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/ratelimit/Bucket4jRateLimiterTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/ratelimit/Bucket4jRateLimiterTests.java @@ -43,7 +43,7 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen /** * @author Spencer Gibb */ -@SpringBootTest(webEnvironment = RANDOM_PORT, properties = "spring.cloud.gateway.redis.enabled=false") +@SpringBootTest(webEnvironment = RANDOM_PORT, properties = "spring.cloud.gateway.server.webflux.redis.enabled=false") @DirtiesContext public class Bucket4jRateLimiterTests extends BaseWebClientTests { diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/ratelimit/RedisRateLimiterTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/ratelimit/RedisRateLimiterTests.java index 2be6617a..febb514c 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/ratelimit/RedisRateLimiterTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/ratelimit/RedisRateLimiterTests.java @@ -54,7 +54,8 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen * @author Denis Cutic * @author Andrey Muchnik */ -@SpringBootTest(webEnvironment = RANDOM_PORT, properties = { "spring.cloud.gateway.function.enabled=false" }) +@SpringBootTest(webEnvironment = RANDOM_PORT, + properties = { "spring.cloud.gateway.server.webflux.function.enabled=false" }) @DirtiesContext @Testcontainers @Tag("DockerRequired") diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/FilteringWebHandlerCacheEnabledIntegrationTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/FilteringWebHandlerCacheEnabledIntegrationTests.java index 650a8a08..8ce29893 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/FilteringWebHandlerCacheEnabledIntegrationTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/FilteringWebHandlerCacheEnabledIntegrationTests.java @@ -42,9 +42,9 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; @SpringBootTest(webEnvironment = RANDOM_PORT, - properties = { "spring.cloud.gateway.route-filter-cache-enabled=true", + properties = { "spring.cloud.gateway.server.webflux.route-filter-cache-enabled=true", "management.endpoint.gateway.enabled=true", "management.endpoints.web.exposure.include=*", - "spring.cloud.gateway.actuator.verbose.enabled=true" }) + "spring.cloud.gateway.server.webflux.actuator.verbose.enabled=true" }) @DirtiesContext public class FilteringWebHandlerCacheEnabledIntegrationTests extends BaseWebClientTests { diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/GatewayPredicateVisitorTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/GatewayPredicateVisitorTests.java index bb5c0d12..c97992ad 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/GatewayPredicateVisitorTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/GatewayPredicateVisitorTests.java @@ -16,25 +16,41 @@ package org.springframework.cloud.gateway.handler.predicate; +import java.net.URI; import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; import java.util.function.Predicate; import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import reactor.core.publisher.Mono; +import org.springframework.boot.autoconfigure.web.reactive.WebFluxProperties; +import org.springframework.cloud.gateway.filter.GatewayFilter; +import org.springframework.cloud.gateway.filter.GatewayFilterChain; +import org.springframework.cloud.gateway.filter.factory.StripPrefixGatewayFilterFactory; import org.springframework.cloud.gateway.handler.AsyncPredicate; import org.springframework.cloud.gateway.route.Route; +import org.springframework.mock.http.server.reactive.MockServerHttpRequest; +import org.springframework.mock.web.server.MockServerWebExchange; import org.springframework.web.server.ServerWebExchange; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR; +import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR; /** * @author Spencer Gibb + * @author FuYiNan Guo */ public class GatewayPredicateVisitorTests { @Test public void asyncPredicateVisitVisitsEachNode() { - PathRoutePredicateFactory pathRoutePredicateFactory = new PathRoutePredicateFactory(); + PathRoutePredicateFactory pathRoutePredicateFactory = new PathRoutePredicateFactory(new WebFluxProperties()); HostRoutePredicateFactory hostRoutePredicateFactory = new HostRoutePredicateFactory(); ReadBodyRoutePredicateFactory readBodyRoutePredicateFactory1 = new ReadBodyRoutePredicateFactory(); ReadBodyRoutePredicateFactory readBodyRoutePredicateFactory2 = new ReadBodyRoutePredicateFactory(); @@ -55,7 +71,7 @@ public class GatewayPredicateVisitorTests { @Test public void predicateVisitVisitsEachNode() { - PathRoutePredicateFactory pathRoutePredicateFactory = new PathRoutePredicateFactory(); + PathRoutePredicateFactory pathRoutePredicateFactory = new PathRoutePredicateFactory(new WebFluxProperties()); HostRoutePredicateFactory hostRoutePredicateFactory = new HostRoutePredicateFactory(); Predicate predicate = pathRoutePredicateFactory.apply(pathRoutePredicateFactory.newConfig()) .and(hostRoutePredicateFactory.apply(hostRoutePredicateFactory.newConfig())); @@ -68,4 +84,60 @@ public class GatewayPredicateVisitorTests { .hasExactlyElementsOfTypes(PathRoutePredicateFactory.Config.class, HostRoutePredicateFactory.Config.class); } + @Test + public void pathRoutePredicateVisitWithSetWebfluxBasePath() { + WebFluxProperties webFluxProperties = new WebFluxProperties(); + webFluxProperties.setBasePath("/gw/api/v1"); + + PathRoutePredicateFactory pathRoutePredicateFactory = new PathRoutePredicateFactory(webFluxProperties); + PathRoutePredicateFactory.Config config = new PathRoutePredicateFactory.Config() + .setPatterns(List.of("/temp/**")) + .setMatchTrailingSlash(true); + + Predicate predicate = pathRoutePredicateFactory.apply(config); + + ServerWebExchange exchange = MockServerWebExchange + .from(MockServerHttpRequest.get("http://127.0.0.1:8080/gw/api/v1/temp/test").build()); + + assertThat(predicate.test(exchange)).isEqualTo(true); + } + + @Test + public void pathRoutePredicateVisitWithSetWebfluxBasePathStripPrefix() { + WebFluxProperties webFluxProperties = new WebFluxProperties(); + webFluxProperties.setBasePath("/gw/api/v1"); + + PathRoutePredicateFactory pathRoutePredicateFactory = new PathRoutePredicateFactory(webFluxProperties); + PathRoutePredicateFactory.Config config = new PathRoutePredicateFactory.Config() + .setPatterns(List.of("/temp/**")) + .setMatchTrailingSlash(true); + + Predicate predicate = pathRoutePredicateFactory.apply(config); + + ServerWebExchange exchange = MockServerWebExchange + .from(MockServerHttpRequest.get("http://127.0.0.1:8080/gw/api/v1/temp/test").build()); + + assertThat(predicate.test(exchange)).isEqualTo(true); + + // webflux base path strips prefix is 3 + GatewayFilter filter = new StripPrefixGatewayFilterFactory().apply(c -> c.setParts(3)); + + GatewayFilterChain filterChain = mock(GatewayFilterChain.class); + + ArgumentCaptor captor = ArgumentCaptor.forClass(ServerWebExchange.class); + when(filterChain.filter(captor.capture())).thenReturn(Mono.empty()); + + filter.filter(exchange, filterChain); + + ServerWebExchange webExchange = captor.getValue(); + + assertThat(webExchange.getRequest().getURI()).hasPath("/temp/test"); + + URI requestUrl = webExchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR); + assertThat(requestUrl).hasScheme("http").hasHost("127.0.0.1").hasPort(8080).hasPath("/temp/test"); + + LinkedHashSet uris = webExchange.getRequiredAttribute(GATEWAY_ORIGINAL_REQUEST_URL_ATTR); + assertThat(uris).contains(exchange.getRequest().getURI()); + } + } diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicateFactoryTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicateFactoryTests.java index a27c7559..539d8564 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicateFactoryTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicateFactoryTests.java @@ -26,6 +26,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.web.reactive.WebFluxProperties; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping; import org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory.Config; @@ -133,14 +134,14 @@ public class PathRoutePredicateFactoryTests extends BaseWebClientTests { @Test public void toStringFormat() { Config config = new Config().setPatterns(Arrays.asList("patternA", "patternB")).setMatchTrailingSlash(false); - Predicate predicate = new PathRoutePredicateFactory().apply(config); + Predicate predicate = new PathRoutePredicateFactory(new WebFluxProperties()).apply(config); assertThat(predicate.toString()).contains("patternA").contains("patternB").contains("false"); } @Test public void toStringFormatMatchTrailingSlashTrue() { Config config = new Config().setPatterns(Arrays.asList("patternA", "patternB")).setMatchTrailingSlash(true); - Predicate predicate = new PathRoutePredicateFactory().apply(config); + Predicate predicate = new PathRoutePredicateFactory(new WebFluxProperties()).apply(config); assertThat(predicate.toString()).contains("patternA").contains("patternB").contains("true"); } diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicatePathContainerAttrBenchMarkTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicatePathContainerAttrBenchMarkTests.java index 41055e5c..a607248b 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicatePathContainerAttrBenchMarkTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicatePathContainerAttrBenchMarkTests.java @@ -30,6 +30,7 @@ import org.openjdk.jmh.annotations.Mode; import org.openjdk.jmh.annotations.Threads; import org.openjdk.jmh.annotations.Warmup; +import org.springframework.boot.autoconfigure.web.reactive.WebFluxProperties; import org.springframework.mock.http.server.reactive.MockServerHttpRequest; import org.springframework.mock.web.server.MockServerWebExchange; import org.springframework.web.server.ServerWebExchange; @@ -54,7 +55,8 @@ public class PathRoutePredicatePathContainerAttrBenchMarkTests { PathRoutePredicateFactory.Config config = new PathRoutePredicateFactory.Config() .setPatterns(Collections.singletonList(PATH_PATTERN_PREFIX + i)) .setMatchTrailingSlash(true); - Predicate predicate = new PathRoutePredicateFactory().apply(config); + Predicate predicate = new PathRoutePredicateFactory(new WebFluxProperties()) + .apply(config); predicates.add(predicate); } } diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/support/ShortcutConfigurableNonRestrictiveTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/support/ShortcutConfigurableNonRestrictiveTests.java index c4cf3c80..daf6413a 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/support/ShortcutConfigurableNonRestrictiveTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/support/ShortcutConfigurableNonRestrictiveTests.java @@ -34,7 +34,7 @@ import org.springframework.expression.spel.standard.SpelExpressionParser; import static org.assertj.core.api.Assertions.assertThat; -@SpringBootTest(properties = "spring.cloud.gateway.restrictive-property-accessor.enabled=false") +@SpringBootTest(properties = "spring.cloud.gateway.server.webflux.restrictive-property-accessor.enabled=false") public class ShortcutConfigurableNonRestrictiveTests { @Autowired diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/support/tagsprovider/GatewayPathTagsProviderTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/support/tagsprovider/GatewayPathTagsProviderTests.java index bb058821..ccdc9ba6 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/support/tagsprovider/GatewayPathTagsProviderTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/support/tagsprovider/GatewayPathTagsProviderTests.java @@ -22,6 +22,7 @@ import java.util.List; import io.micrometer.core.instrument.Tags; import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.web.reactive.WebFluxProperties; import org.springframework.cloud.gateway.handler.predicate.HostRoutePredicateFactory; import org.springframework.cloud.gateway.handler.predicate.MethodRoutePredicateFactory; import org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory; @@ -56,7 +57,7 @@ public class GatewayPathTagsProviderTests { Route route = Route.async() .id("git") .uri(ROUTE_URI) - .predicate(new PathRoutePredicateFactory().apply(pathConfig) + .predicate(new PathRoutePredicateFactory(new WebFluxProperties()).apply(pathConfig) .and(new HostRoutePredicateFactory().apply(hostConfig))) .build(); @@ -81,8 +82,8 @@ public class GatewayPathTagsProviderTests { Route route = Route.async() .id("git") .uri(ROUTE_URI) - .predicate(new PathRoutePredicateFactory().apply(pathConfig) - .or(new PathRoutePredicateFactory().apply(pathConfig2))) + .predicate(new PathRoutePredicateFactory(new WebFluxProperties()).apply(pathConfig) + .or(new PathRoutePredicateFactory(new WebFluxProperties()).apply(pathConfig2))) .build(); ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get(ROUTE_URI).build()); diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/support/tagsprovider/PropertiesTagsProviderTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/support/tagsprovider/PropertiesTagsProviderTests.java index 922b43f4..e3745435 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/support/tagsprovider/PropertiesTagsProviderTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/support/tagsprovider/PropertiesTagsProviderTests.java @@ -37,8 +37,8 @@ public class PropertiesTagsProviderTests { @Test public void test() { contextRunner.withConfiguration(AutoConfigurations.of(GatewayMetricsAutoConfiguration.class)) - .withPropertyValues("spring.cloud.gateway.metrics.tags.foo1=bar1", - "spring.cloud.gateway.metrics.tags.foo2=bar2") + .withPropertyValues("spring.cloud.gateway.server.webflux.metrics.tags.foo1=bar1", + "spring.cloud.gateway.server.webflux.metrics.tags.foo2=bar2") .run(context -> { PropertiesTagsProvider provider = context.getBean(PropertiesTagsProvider.class); Tags tags = provider.apply(MockServerWebExchange.from(MockServerHttpRequest.get("").build())); diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/GatewayIntegrationTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/GatewayIntegrationTests.java index 2ca53a27..8af9e456 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/GatewayIntegrationTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/GatewayIntegrationTests.java @@ -62,7 +62,8 @@ 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.test.TestUtils.getMap; -@SpringBootTest(webEnvironment = RANDOM_PORT, properties = "spring.cloud.gateway.forwarded.by.enabled=true") +@SpringBootTest(webEnvironment = RANDOM_PORT, + properties = "spring.cloud.gateway.server.webflux.forwarded.by.enabled=true") @DirtiesContext @SuppressWarnings("unchecked") @ExtendWith(OutputCaptureExtension.class) diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/ssl/ClientCertAuthSSLTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/ssl/ClientCertAuthSSLTests.java index 39c13f74..d9bd3d4a 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/ssl/ClientCertAuthSSLTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/ssl/ClientCertAuthSSLTests.java @@ -44,13 +44,13 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen @ActiveProfiles("client-auth-ssl") public class ClientCertAuthSSLTests extends SingleCertSSLTests { - @Value("${spring.cloud.gateway.httpclient.ssl.key-store}") + @Value("${spring.cloud.gateway.server.webflux.httpclient.ssl.key-store}") private String keyStore; - @Value("${spring.cloud.gateway.httpclient.ssl.key-store-password}") + @Value("${spring.cloud.gateway.server.webflux.httpclient.ssl.key-store-password}") private String keyStorePassword; - @Value("${spring.cloud.gateway.httpclient.ssl.key-password}") + @Value("${spring.cloud.gateway.server.webflux.httpclient.ssl.key-password}") private String keyPassword; @BeforeEach diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/ssl/SSLHandshakeTimeoutTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/ssl/SSLHandshakeTimeoutTests.java index a0f5a479..f7b0c90f 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/ssl/SSLHandshakeTimeoutTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/ssl/SSLHandshakeTimeoutTests.java @@ -29,7 +29,7 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen // this test works because it assumes TLS hand shake cannot be done in 1ms. It takes // closer to 80ms @SpringBootTest(webEnvironment = RANDOM_PORT, - properties = { "spring.cloud.gateway.httpclient.ssl.handshake-timeout=1ms" }) + properties = { "spring.cloud.gateway.server.webflux.httpclient.ssl.handshake-timeout=1ms" }) @DirtiesContext @ActiveProfiles("ssl") public class SSLHandshakeTimeoutTests extends SingleCertSSLTests { diff --git a/spring-cloud-gateway-server/src/test/resources/application-client-auth-ssl-bundle.yml b/spring-cloud-gateway-server/src/test/resources/application-client-auth-ssl-bundle.yml index 2ca408d5..07660e90 100644 --- a/spring-cloud-gateway-server/src/test/resources/application-client-auth-ssl-bundle.yml +++ b/spring-cloud-gateway-server/src/test/resources/application-client-auth-ssl-bundle.yml @@ -15,7 +15,7 @@ server: client-auth: Need spring: cloud: - gateway: + gateway.server.webflux: httpclient: ssl: ssl-bundle: scg-keystore-with-different-key-password diff --git a/spring-cloud-gateway-server/src/test/resources/application-client-auth-ssl.yml b/spring-cloud-gateway-server/src/test/resources/application-client-auth-ssl.yml index 88d1b138..cb01f770 100644 --- a/spring-cloud-gateway-server/src/test/resources/application-client-auth-ssl.yml +++ b/spring-cloud-gateway-server/src/test/resources/application-client-auth-ssl.yml @@ -15,7 +15,7 @@ server: client-auth: Need spring: cloud: - gateway: + gateway.server.webflux: httpclient: ssl: key-store: classpath:scg-keystore-with-different-key-password.jks diff --git a/spring-cloud-gateway-server/src/test/resources/application-cors-global-config.yml b/spring-cloud-gateway-server/src/test/resources/application-cors-global-config.yml index f210add2..3205c85c 100644 --- a/spring-cloud-gateway-server/src/test/resources/application-cors-global-config.yml +++ b/spring-cloud-gateway-server/src/test/resources/application-cors-global-config.yml @@ -1,6 +1,6 @@ spring: cloud: - gateway: + gateway.server.webflux: globalcors: cors-configurations: '[/**]': diff --git a/spring-cloud-gateway-server/src/test/resources/application-cors-per-route-config.yml b/spring-cloud-gateway-server/src/test/resources/application-cors-per-route-config.yml index 0017f4a6..b1a87536 100644 --- a/spring-cloud-gateway-server/src/test/resources/application-cors-per-route-config.yml +++ b/spring-cloud-gateway-server/src/test/resources/application-cors-per-route-config.yml @@ -1,6 +1,6 @@ spring: cloud: - gateway: + gateway.server.webflux: routes: - id: cors_preflight_test uri: ${test.uri} diff --git a/spring-cloud-gateway-server/src/test/resources/application-defaultid.yml b/spring-cloud-gateway-server/src/test/resources/application-defaultid.yml index ecb94e4c..29133646 100644 --- a/spring-cloud-gateway-server/src/test/resources/application-defaultid.yml +++ b/spring-cloud-gateway-server/src/test/resources/application-defaultid.yml @@ -1,6 +1,6 @@ spring: cloud: - gateway: + gateway.server.webflux: default-filters: - PrefixPath=/httpbin routes: diff --git a/spring-cloud-gateway-server/src/test/resources/application-disable-components.yml b/spring-cloud-gateway-server/src/test/resources/application-disable-components.yml index 7ebfe2f5..ade6bdf3 100644 --- a/spring-cloud-gateway-server/src/test/resources/application-disable-components.yml +++ b/spring-cloud-gateway-server/src/test/resources/application-disable-components.yml @@ -7,7 +7,7 @@ spring: import: classpath:application-logging.yml cloud: - gateway: + gateway.server.webflux: globalcors: cors-configurations: '[/**]': diff --git a/spring-cloud-gateway-server/src/test/resources/application-forwardstatic.yml b/spring-cloud-gateway-server/src/test/resources/application-forwardstatic.yml index 35b5b4a0..8cb53a4b 100644 --- a/spring-cloud-gateway-server/src/test/resources/application-forwardstatic.yml +++ b/spring-cloud-gateway-server/src/test/resources/application-forwardstatic.yml @@ -1,6 +1,6 @@ spring: cloud: - gateway: + gateway.server.webflux: routes: - uri: forward:/docs.html id: static_docs_route diff --git a/spring-cloud-gateway-server/src/test/resources/application-multi-cert-ssl.yml b/spring-cloud-gateway-server/src/test/resources/application-multi-cert-ssl.yml index 5ab3bed3..f58715a8 100644 --- a/spring-cloud-gateway-server/src/test/resources/application-multi-cert-ssl.yml +++ b/spring-cloud-gateway-server/src/test/resources/application-multi-cert-ssl.yml @@ -11,7 +11,7 @@ server: spring: cloud: - gateway: + gateway.server.webflux: httpclient: ssl: trustedX509Certificates: diff --git a/spring-cloud-gateway-server/src/test/resources/application-netty-routing-filter.yml b/spring-cloud-gateway-server/src/test/resources/application-netty-routing-filter.yml index 8f81445f..9356078b 100644 --- a/spring-cloud-gateway-server/src/test/resources/application-netty-routing-filter.yml +++ b/spring-cloud-gateway-server/src/test/resources/application-netty-routing-filter.yml @@ -14,7 +14,7 @@ spring: - logging cloud: - gateway: + gateway.server.webflux: routes: # ===================================== - id: per_route_connect_timeout diff --git a/spring-cloud-gateway-server/src/test/resources/application-propertiesmigrationtests.yml b/spring-cloud-gateway-server/src/test/resources/application-propertiesmigrationtests.yml new file mode 100644 index 00000000..2f781c6a --- /dev/null +++ b/spring-cloud-gateway-server/src/test/resources/application-propertiesmigrationtests.yml @@ -0,0 +1,32 @@ +spring.cloud.gateway: + route-refresh-listener.enabled: false + forwarded.enabled: false + x-forwarded.enabled: false + predicate.after.enabled: false + filter.add-request-parameter.enabled: false + routes: + - id: listRoute1 + uri: https://examplel1.com + predicates: + - name: Method + args: + methods: GET + - name: Path + args: + pattern: /anything/listRoute1 + filters: + - AddRequestHeader=X-Test,listRoute1 + - id: listRoute2 + uri: https://examplel2.com + predicates: + - name: Method + args: + methods: GET + - name: Path + args: + pattern: /anything/listRoute2 + filters: + - AddRequestHeader=X-Test,listRoute2 +logging: + level: + org.springframework.cloud.gateway: TRACE diff --git a/spring-cloud-gateway-server/src/test/resources/application-redis-rate-limiter-config.yml b/spring-cloud-gateway-server/src/test/resources/application-redis-rate-limiter-config.yml index a6c5726d..dd37bd2b 100644 --- a/spring-cloud-gateway-server/src/test/resources/application-redis-rate-limiter-config.yml +++ b/spring-cloud-gateway-server/src/test/resources/application-redis-rate-limiter-config.yml @@ -1,6 +1,6 @@ spring: cloud: - gateway: + gateway.server.webflux: default-filters: routes: # ===================================== diff --git a/spring-cloud-gateway-server/src/test/resources/application-redis-rate-limiter-default-config.yml b/spring-cloud-gateway-server/src/test/resources/application-redis-rate-limiter-default-config.yml index 94ae146a..2cdd500e 100644 --- a/spring-cloud-gateway-server/src/test/resources/application-redis-rate-limiter-default-config.yml +++ b/spring-cloud-gateway-server/src/test/resources/application-redis-rate-limiter-default-config.yml @@ -1,6 +1,6 @@ spring: cloud: - gateway: + gateway.server.webflux: default-filters: - name: RequestRateLimiter args: diff --git a/spring-cloud-gateway-server/src/test/resources/application-redis-route-repository.yml b/spring-cloud-gateway-server/src/test/resources/application-redis-route-repository.yml index 3baeb637..25300f92 100644 --- a/spring-cloud-gateway-server/src/test/resources/application-redis-route-repository.yml +++ b/spring-cloud-gateway-server/src/test/resources/application-redis-route-repository.yml @@ -1,5 +1,5 @@ spring: cloud: - gateway: + gateway.server.webflux: redis-route-definition-repository: enabled: true diff --git a/spring-cloud-gateway-server/src/test/resources/application-remote-address.yml b/spring-cloud-gateway-server/src/test/resources/application-remote-address.yml index 12795440..dbb64c4d 100644 --- a/spring-cloud-gateway-server/src/test/resources/application-remote-address.yml +++ b/spring-cloud-gateway-server/src/test/resources/application-remote-address.yml @@ -1,6 +1,6 @@ spring: cloud: - gateway: + gateway.server.webflux: default-filters: routes: # ===================================== diff --git a/spring-cloud-gateway-server/src/test/resources/application-removenonproxyheaders.yml b/spring-cloud-gateway-server/src/test/resources/application-removenonproxyheaders.yml index f95895db..aa7903f0 100644 --- a/spring-cloud-gateway-server/src/test/resources/application-removenonproxyheaders.yml +++ b/spring-cloud-gateway-server/src/test/resources/application-removenonproxyheaders.yml @@ -1,6 +1,6 @@ spring: cloud: - gateway: + gateway.server.webflux: default-filters: - RemoveNonProxyHeaders - PrefixPath=/httpbin diff --git a/spring-cloud-gateway-server/src/test/resources/application-request-header-web-filter.yml b/spring-cloud-gateway-server/src/test/resources/application-request-header-web-filter.yml index 021ac0af..b42959b5 100644 --- a/spring-cloud-gateway-server/src/test/resources/application-request-header-web-filter.yml +++ b/spring-cloud-gateway-server/src/test/resources/application-request-header-web-filter.yml @@ -1,6 +1,6 @@ spring: cloud: - gateway: + gateway.server.webflux: routes: - id: add_request_header_test uri: ${test.uri} diff --git a/spring-cloud-gateway-server/src/test/resources/application-request-headers-if-not-present-web-filter.yml b/spring-cloud-gateway-server/src/test/resources/application-request-headers-if-not-present-web-filter.yml index 23a88f20..e073bada 100644 --- a/spring-cloud-gateway-server/src/test/resources/application-request-headers-if-not-present-web-filter.yml +++ b/spring-cloud-gateway-server/src/test/resources/application-request-headers-if-not-present-web-filter.yml @@ -1,6 +1,6 @@ spring: cloud: - gateway: + gateway.server.webflux: routes: - id: add_request_headers_if_not_present_test uri: ${test.uri} diff --git a/spring-cloud-gateway-server/src/test/resources/application-request-map-header-web-filter.yml b/spring-cloud-gateway-server/src/test/resources/application-request-map-header-web-filter.yml index fefe27db..8f3713e5 100644 --- a/spring-cloud-gateway-server/src/test/resources/application-request-map-header-web-filter.yml +++ b/spring-cloud-gateway-server/src/test/resources/application-request-map-header-web-filter.yml @@ -1,6 +1,6 @@ spring: cloud: - gateway: + gateway.server.webflux: routes: - id: map_request_header_test_singleresponse uri: ${test.uri} diff --git a/spring-cloud-gateway-server/src/test/resources/application-request-parameter-web-filter.yml b/spring-cloud-gateway-server/src/test/resources/application-request-parameter-web-filter.yml index b0190007..b80f0fd9 100644 --- a/spring-cloud-gateway-server/src/test/resources/application-request-parameter-web-filter.yml +++ b/spring-cloud-gateway-server/src/test/resources/application-request-parameter-web-filter.yml @@ -1,6 +1,6 @@ spring: cloud: - gateway: + gateway.server.webflux: routes: - id: add_request_parameter_test uri: ${test.uri} diff --git a/spring-cloud-gateway-server/src/test/resources/application-retrytests.yml b/spring-cloud-gateway-server/src/test/resources/application-retrytests.yml index ea5e1be8..afc8dc7a 100644 --- a/spring-cloud-gateway-server/src/test/resources/application-retrytests.yml +++ b/spring-cloud-gateway-server/src/test/resources/application-retrytests.yml @@ -1,6 +1,6 @@ spring: cloud: - gateway: + gateway.server.webflux: default-filters: - PrefixPath=/httpbin routes: diff --git a/spring-cloud-gateway-server/src/test/resources/application-save-session-web-filter.yml b/spring-cloud-gateway-server/src/test/resources/application-save-session-web-filter.yml index 110bb930..87539c45 100644 --- a/spring-cloud-gateway-server/src/test/resources/application-save-session-web-filter.yml +++ b/spring-cloud-gateway-server/src/test/resources/application-save-session-web-filter.yml @@ -1,6 +1,6 @@ spring: cloud: - gateway: + gateway.server.webflux: routes: - id: save_session_test uri: ${test.uri} diff --git a/spring-cloud-gateway-server/src/test/resources/application-single-cert-ssl.yml b/spring-cloud-gateway-server/src/test/resources/application-single-cert-ssl.yml index 61866d25..7483e2a7 100644 --- a/spring-cloud-gateway-server/src/test/resources/application-single-cert-ssl.yml +++ b/spring-cloud-gateway-server/src/test/resources/application-single-cert-ssl.yml @@ -11,7 +11,7 @@ server: spring: cloud: - gateway: + gateway.server.webflux: httpclient: ssl: trustedX509Certificates: diff --git a/spring-cloud-gateway-server/src/test/resources/application-verification-route.yml b/spring-cloud-gateway-server/src/test/resources/application-verification-route.yml index eb648d38..75a109e3 100644 --- a/spring-cloud-gateway-server/src/test/resources/application-verification-route.yml +++ b/spring-cloud-gateway-server/src/test/resources/application-verification-route.yml @@ -1,7 +1,7 @@ server.port: 0 spring: cloud: - gateway: + gateway.server.webflux: routes: - uri: https://example.com predicates: diff --git a/spring-cloud-gateway-server/src/test/resources/application-weights-404.yml b/spring-cloud-gateway-server/src/test/resources/application-weights-404.yml index 49ffb6d5..776c12f9 100644 --- a/spring-cloud-gateway-server/src/test/resources/application-weights-404.yml +++ b/spring-cloud-gateway-server/src/test/resources/application-weights-404.yml @@ -1,6 +1,6 @@ spring: cloud: - gateway: + gateway.server.webflux: default-filters: - PrefixPath=/httpbin routes: diff --git a/spring-cloud-gateway-server/src/test/resources/application.yml b/spring-cloud-gateway-server/src/test/resources/application.yml index ac989a01..c8b6a23f 100644 --- a/spring-cloud-gateway-server/src/test/resources/application.yml +++ b/spring-cloud-gateway-server/src/test/resources/application.yml @@ -14,7 +14,7 @@ spring: - logging cloud: - gateway: + gateway.server.webflux: globalcors: cors-configurations: '[/**]': diff --git a/spring-cloud-gateway-webflux/pom.xml b/spring-cloud-gateway-webflux/pom.xml index 5859d52d..c74106d3 100644 --- a/spring-cloud-gateway-webflux/pom.xml +++ b/spring-cloud-gateway-webflux/pom.xml @@ -30,6 +30,11 @@ spring-boot-starter-actuator test + + org.springframework.boot + spring-boot-properties-migrator + test + org.springframework.boot spring-boot-configuration-processor diff --git a/spring-cloud-gateway-webflux/src/main/java/org/springframework/cloud/gateway/webflux/config/ProxyExchangeWebfluxProperties.java b/spring-cloud-gateway-webflux/src/main/java/org/springframework/cloud/gateway/webflux/config/ProxyExchangeWebfluxProperties.java new file mode 100644 index 00000000..3c83cceb --- /dev/null +++ b/spring-cloud-gateway-webflux/src/main/java/org/springframework/cloud/gateway/webflux/config/ProxyExchangeWebfluxProperties.java @@ -0,0 +1,117 @@ +/* + * 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.webflux.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.webflux.ProxyExchange; +import org.springframework.http.HttpHeaders; + +/** + * Configuration properties for the {@link ProxyExchange} argument handler in + * @RequestMapping methods. + * + * @author Dave Syer + * @author Tim Ysewyn + * @author Joris Kuipers + * @author Spencer Gibb + * + */ +@ConfigurationProperties(ProxyExchangeWebfluxProperties.PREFIX) +public class ProxyExchangeWebfluxProperties { + + /** + * Properties prefix. + */ + public static final String PREFIX = "spring.cloud.gateway.proxy-exchange.webflux"; + + /** + * Contains headers that are considered case-sensitive by default. + */ + public static Set DEFAULT_SENSITIVE = Set.of("cookie", "authorization"); + + /** + * Contains headers that are skipped by default. + */ + public static Set DEFAULT_SKIPPED = Set.of("content-length", "host"); + + /** + * Fixed header values that will be added to all downstream requests. + */ + private Map headers = new LinkedHashMap<>(); + + /** + * A set of header names that should be sent downstream by default. + */ + private Set autoForward = new HashSet<>(); + + /** + * A set of sensitive header names that will not be sent downstream by default. + */ + private Set sensitive = DEFAULT_SENSITIVE; + + /** + * A set of header names that will not be sent downstream because they could be + * problematic. + */ + private Set skipped = DEFAULT_SKIPPED; + + public Map getHeaders() { + return headers; + } + + public void setHeaders(Map headers) { + this.headers = headers; + } + + public Set getAutoForward() { + return autoForward; + } + + public void setAutoForward(Set autoForward) { + this.autoForward = autoForward; + } + + public Set getSensitive() { + return sensitive; + } + + public void setSensitive(Set sensitive) { + this.sensitive = sensitive; + } + + public Set getSkipped() { + return skipped; + } + + public void setSkipped(Set 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; + } + +} diff --git a/spring-cloud-gateway-webflux/src/main/java/org/springframework/cloud/gateway/webflux/config/ProxyProperties.java b/spring-cloud-gateway-webflux/src/main/java/org/springframework/cloud/gateway/webflux/config/ProxyProperties.java index 38e000d5..0c7184ca 100644 --- a/spring-cloud-gateway-webflux/src/main/java/org/springframework/cloud/gateway/webflux/config/ProxyProperties.java +++ b/spring-cloud-gateway-webflux/src/main/java/org/springframework/cloud/gateway/webflux/config/ProxyProperties.java @@ -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.webflux.ProxyExchange; import org.springframework.http.HttpHeaders; @@ -32,8 +33,10 @@ import org.springframework.http.HttpHeaders; * @author Dave Syer * @author Tim Ysewyn * @author Joris Kuipers + * @deprecated {@link ProxyExchangeWebfluxProperties} * */ +@Deprecated @ConfigurationProperties("spring.cloud.gateway.proxy") public class ProxyProperties { @@ -53,7 +56,7 @@ public class ProxyProperties { private Map headers = new LinkedHashMap<>(); /** - * A set of header names that should be send downstream by default. + * A set of header names that should be sent downstream by default. */ private Set autoForward = new HashSet<>(); @@ -68,6 +71,7 @@ public class ProxyProperties { */ private Set skipped = DEFAULT_SKIPPED; + @DeprecatedConfigurationProperty(replacement = ProxyExchangeWebfluxProperties.PREFIX + ".headers", since = "4.3.0") public Map getHeaders() { return headers; } @@ -76,6 +80,8 @@ public class ProxyProperties { this.headers = headers; } + @DeprecatedConfigurationProperty(replacement = ProxyExchangeWebfluxProperties.PREFIX + ".auto-forward", + since = "4.3.0") public Set getAutoForward() { return autoForward; } @@ -84,6 +90,8 @@ public class ProxyProperties { this.autoForward = autoForward; } + @DeprecatedConfigurationProperty(replacement = ProxyExchangeWebfluxProperties.PREFIX + ".sensitive", + since = "4.3.0") public Set getSensitive() { return sensitive; } @@ -92,6 +100,7 @@ public class ProxyProperties { this.sensitive = sensitive; } + @DeprecatedConfigurationProperty(replacement = ProxyExchangeWebfluxProperties.PREFIX + ".skipped", since = "4.3.0") public Set getSkipped() { return skipped; } diff --git a/spring-cloud-gateway-webflux/src/main/java/org/springframework/cloud/gateway/webflux/config/ProxyResponseAutoConfiguration.java b/spring-cloud-gateway-webflux/src/main/java/org/springframework/cloud/gateway/webflux/config/ProxyResponseAutoConfiguration.java index 20ff720d..116f0b10 100644 --- a/spring-cloud-gateway-webflux/src/main/java/org/springframework/cloud/gateway/webflux/config/ProxyResponseAutoConfiguration.java +++ b/spring-cloud-gateway-webflux/src/main/java/org/springframework/cloud/gateway/webflux/config/ProxyResponseAutoConfiguration.java @@ -48,7 +48,7 @@ import org.springframework.web.reactive.result.method.annotation.ArgumentResolve @Configuration(proxyBeanMethods = false) @ConditionalOnWebApplication @ConditionalOnClass({ HandlerMethodReturnValueHandler.class, WebClient.class }) -@EnableConfigurationProperties(ProxyProperties.class) +@EnableConfigurationProperties({ ProxyExchangeWebfluxProperties.class, ProxyProperties.class }) public class ProxyResponseAutoConfiguration implements WebFluxConfigurer { @Autowired @@ -57,18 +57,18 @@ public class ProxyResponseAutoConfiguration implements WebFluxConfigurer { @Bean @ConditionalOnMissingBean public ProxyExchangeArgumentResolver proxyExchangeArgumentResolver(Optional optional, - ProxyProperties proxy) { + ProxyExchangeWebfluxProperties properties) { WebClient.Builder builder = optional.orElse(WebClient.builder()); WebClient template = builder.build(); ProxyExchangeArgumentResolver resolver = new ProxyExchangeArgumentResolver(template); - resolver.setHeaders(proxy.convertHeaders()); - resolver.setAutoForwardedHeaders(proxy.getAutoForward()); + resolver.setHeaders(properties.convertHeaders()); + resolver.setAutoForwardedHeaders(properties.getAutoForward()); Set 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; diff --git a/spring-cloud-gateway-webflux/src/test/java/org/springframework/cloud/gateway/webflux/ProductionConfigurationTests.java b/spring-cloud-gateway-webflux/src/test/java/org/springframework/cloud/gateway/webflux/ProductionConfigurationTests.java index 62b26201..732f71f8 100644 --- a/spring-cloud-gateway-webflux/src/test/java/org/springframework/cloud/gateway/webflux/ProductionConfigurationTests.java +++ b/spring-cloud-gateway-webflux/src/test/java/org/springframework/cloud/gateway/webflux/ProductionConfigurationTests.java @@ -62,7 +62,8 @@ import org.springframework.web.util.UriComponentsBuilder; import static org.assertj.core.api.Assertions.assertThat; -@SpringBootTest(properties = { "spring.cloud.gateway.proxy.skipped=host" }, webEnvironment = WebEnvironment.RANDOM_PORT) +@SpringBootTest(properties = { "spring.cloud.gateway.proxy-exchange.webflux.skipped=host" }, + webEnvironment = WebEnvironment.RANDOM_PORT) @ContextConfiguration(classes = TestApplication.class) @DirtiesContext @ExtendWith(OutputCaptureExtension.class) diff --git a/spring-cloud-gateway-webflux/src/test/java/org/springframework/cloud/gateway/webflux/config/ProxyExchangeWebfluxPropertiesMigrationTests.java b/spring-cloud-gateway-webflux/src/test/java/org/springframework/cloud/gateway/webflux/config/ProxyExchangeWebfluxPropertiesMigrationTests.java new file mode 100644 index 00000000..974294ba --- /dev/null +++ b/spring-cloud-gateway-webflux/src/test/java/org/springframework/cloud/gateway/webflux/config/ProxyExchangeWebfluxPropertiesMigrationTests.java @@ -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.webflux.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 ProxyExchangeWebfluxPropertiesMigrationTests { + + @Autowired + ProxyExchangeWebfluxProperties 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 { + + } + +} diff --git a/spring-cloud-gateway-webflux/src/test/resources/application-propertiesmigrationtests.yml b/spring-cloud-gateway-webflux/src/test/resources/application-propertiesmigrationtests.yml new file mode 100644 index 00000000..d6b6dfe5 --- /dev/null +++ b/spring-cloud-gateway-webflux/src/test/resources/application-propertiesmigrationtests.yml @@ -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 diff --git a/spring-cloud-starter-gateway-mvc/pom.xml b/spring-cloud-starter-gateway-mvc/pom.xml index e86d2d1f..8e66ee4e 100644 --- a/spring-cloud-starter-gateway-mvc/pom.xml +++ b/spring-cloud-starter-gateway-mvc/pom.xml @@ -34,5 +34,10 @@ org.springframework.boot spring-boot-starter-web + + + org.springframework.boot + spring-boot-properties-migrator + diff --git a/spring-cloud-starter-gateway-server-webflux/pom.xml b/spring-cloud-starter-gateway-server-webflux/pom.xml index ab94fcc5..ed3b79c4 100644 --- a/spring-cloud-starter-gateway-server-webflux/pom.xml +++ b/spring-cloud-starter-gateway-server-webflux/pom.xml @@ -33,5 +33,10 @@ org.springframework.boot spring-boot-starter-webflux + + + org.springframework.boot + spring-boot-properties-migrator + diff --git a/spring-cloud-starter-gateway-server-webmvc/pom.xml b/spring-cloud-starter-gateway-server-webmvc/pom.xml index 00420392..14b339e1 100644 --- a/spring-cloud-starter-gateway-server-webmvc/pom.xml +++ b/spring-cloud-starter-gateway-server-webmvc/pom.xml @@ -33,5 +33,10 @@ org.springframework.boot spring-boot-starter-web + + + org.springframework.boot + spring-boot-properties-migrator + diff --git a/spring-cloud-starter-gateway/pom.xml b/spring-cloud-starter-gateway/pom.xml index 8de3f70e..9705acd7 100644 --- a/spring-cloud-starter-gateway/pom.xml +++ b/spring-cloud-starter-gateway/pom.xml @@ -34,5 +34,10 @@ org.springframework.boot spring-boot-starter-webflux + + + org.springframework.boot + spring-boot-properties-migrator +