From ef02038d1e1b563d4c1e02e8742ea65be96cc6a9 Mon Sep 17 00:00:00 2001 From: sgibb Date: Mon, 11 Sep 2023 16:10:01 -0400 Subject: [PATCH] Migrate Structure --- .editorconfig | 12 - .github/CONTRIBUTING.md | 44 - .github/ISSUE_TEMPLATE.md | 20 - .github/ISSUE_TEMPLATE/bug_report.md | 17 - .github/ISSUE_TEMPLATE/feature_request.md | 20 - .github/workflows/deploy-docs.yml | 53 + .github/workflows/maven.yml | 37 - .gitignore | 37 +- .mvn/maven.config | 1 - .mvn/wrapper/MavenWrapperDownloader.java | 117 + .mvn/wrapper/maven-wrapper.jar | Bin 59925 -> 50710 bytes .mvn/wrapper/maven-wrapper.properties | 20 +- .sdkmanrc | 3 - .settings.xml | 83 - LICENSE | 201 -- LICENSE.txt | 202 -- README.adoc | 342 +-- SECURITY.md | 5 - antora-playbook.yml | 44 + .../ROOT/pages}/README.adoc | 0 .../ROOT/pages}/_attributes.adoc | 0 .../ROOT/pages}/_configprops.adoc | 0 .../ROOT/pages}/appendix.adoc | 0 .../ROOT/pages}/index.adoc | 0 .../ROOT/pages}/intro.adoc | 0 .../ROOT/pages}/sagan-boot.adoc | 0 .../ROOT/pages}/sagan-index.adoc | 0 .../ROOT/pages}/spring-cloud-gateway.adoc | 0 docs/pom.xml | 100 - .../asciidoc/gateway-grafana-dashboard.json | 2068 ----------------- docs/src/main/asciidoc/ghpages.sh | 330 --- .../images/gateway-grafana-dashboard.jpeg | Bin 241759 -> 0 bytes .../images/spring_cloud_gateway_diagram.png | Bin 27306 -> 0 bytes mvnw | 57 +- mvnw.cmd | 45 +- pom.xml | 311 +-- spring-cloud-gateway-dependencies/pom.xml | 114 - .../grpc/pom.xml | 131 -- .../gateway/tests/grpc/GRPCApplication.java | 128 - .../grpc/src/main/proto/hello.pb | Bin 442 -> 0 bytes .../grpc/src/main/proto/hello.proto | 17 - .../grpc/src/main/resources/application.yml | 0 .../grpc/src/main/resources/keystore.p12 | Bin 2586 -> 0 bytes .../grpc/src/main/resources/private.key | 32 - .../grpc/src/main/resources/public.cert | 18 - .../tests/grpc/GRPCApplicationTests.java | 103 - .../grpc/JsonToGrpcApplicationTests.java | 107 - .../gateway/tests/grpc/RouteConfigurer.java | 114 - .../grpc/src/test/resources/application.yml | 37 - .../http2/pom.xml | 71 - .../gateway/tests/http2/Http2Application.java | 91 - .../http2/src/main/resources/application.yml | 22 - .../http2/src/main/resources/sample.jks | Bin 2264 -> 0 bytes .../tests/http2/Http2ApplicationTests.java | 86 - .../tests/http2/nossl/NosslConfiguration.java | 34 - .../gateway/tests/http2/nossl/NosslTests.java | 75 - .../src/test/resources/application-nossl.yml | 13 - .../mvc-failure-analyzer/pom.xml | 63 - .../sample/MvcFailureAnalyzerApplication.java | 67 - .../src/main/resources/application.yml | 3 - .../MvcFailureAnalyzerApplicationTests.java | 72 - .../pom.xml | 40 - spring-cloud-gateway-mvc/.jdk8 | 0 spring-cloud-gateway-mvc/pom.xml | 55 - .../cloud/gateway/mvc/ProxyExchange.java | 612 ----- .../config/ProxyExchangeArgumentResolver.java | 124 - .../gateway/mvc/config/ProxyProperties.java | 86 - .../ProxyResponseAutoConfiguration.java | 90 - .../main/resources/META-INF/spring.factories | 2 - ...ot.autoconfigure.AutoConfiguration.imports | 1 - .../gateway/mvc/GetWithBodyRequestTests.java | 203 -- .../mvc/ProductionConfigurationTests.java | 540 ----- .../ProxyExchangeArgumentResolverTest.java | 118 - .../AbstractBufferingClientHttpRequest.java | 48 - ...thBodyRequestClientHttpRequestFactory.java | 131 -- .../http/HttpComponentsClientHttpRequest.java | 99 - .../HttpComponentsClientHttpResponse.java | 88 - .../src/test/resources/static/test.html | 4 - spring-cloud-gateway-sample/.jdk8 | 0 spring-cloud-gateway-sample/pom.xml | 177 -- .../sample/GatewaySampleApplication.java | 194 -- .../gateway/sample/ThrottleGatewayFilter.java | 99 - .../cloud/gateway/sample/AdditionalRoutes.kt | 44 - .../src/main/resources/application.yml | 43 - .../sample/GatewaySampleApplicationTests.java | 217 -- ...ySampleApplicationWithoutMetricsTests.java | 80 - spring-cloud-gateway-server-mvc/pom.xml | 104 - .../GatewayServerMvcAutoConfiguration.java | 164 -- .../mvc/common/AbstractGatewayDiscoverer.java | 80 - .../server/mvc/common/ArgumentSupplier.java | 33 - .../ArgumentSupplierBeanPostProcessor.java | 188 -- .../AttributedArugmentSuppliedEvent.java | 50 - .../common/DefaultArgumentSuppliedEvent.java | 44 - .../server/mvc/common/HttpStatusHolder.java | 144 -- .../gateway/server/mvc/common/KeyValues.java | 79 - .../gateway/server/mvc/common/MvcUtils.java | 218 -- .../gateway/server/mvc/common/NameUtils.java | 34 - .../gateway/server/mvc/common/Shortcut.java | 77 - .../server/mvc/common/WeightConfig.java | 87 - .../server/mvc/config/FilterProperties.java | 105 - .../mvc/config/GatewayMvcProperties.java | 144 -- ...yMvcPropertiesBeanDefinitionRegistrar.java | 386 --- .../mvc/config/NormalizedOperationMethod.java | 121 - .../mvc/config/PredicateProperties.java | 106 - .../server/mvc/config/RouteProperties.java | 151 -- .../mvc/filter/AfterFilterFunctions.java | 171 -- .../mvc/filter/BeforeFilterFunctions.java | 442 ---- .../mvc/filter/BodyFilterFunctions.java | 367 --- .../mvc/filter/Bucket4jFilterFunctions.java | 194 -- .../filter/CircuitBreakerFilterFunctions.java | 174 -- .../server/mvc/filter/FilterDiscoverer.java | 29 - .../server/mvc/filter/FilterFunctions.java | 236 -- .../server/mvc/filter/FilterSupplier.java | 25 - .../filter/ForwardedRequestHeadersFilter.java | 194 -- .../server/mvc/filter/HttpHeadersFilter.java | 35 - .../filter/LoadBalancerFilterFunctions.java | 198 -- .../filter/LoadBalancerHandlerSupplier.java | 42 - ...moveContentLengthRequestHeadersFilter.java | 45 - .../RemoveHopByHopRequestHeadersFilter.java | 71 - .../RemoveHopByHopResponseHeadersFilter.java | 44 - .../mvc/filter/RetryFilterFunctions.java | 149 -- ...LocationResponseHeaderFilterFunctions.java | 226 -- .../mvc/filter/TokenRelayFilterFunctions.java | 56 - ...dingNormalizationRequestHeadersFilter.java | 46 - .../mvc/filter/WeightCalculatorFilter.java | 263 --- .../XForwardedRequestHeadersFilter.java | 331 --- .../AbstractGatewayServerResponse.java | 142 -- ...ClientHttpRequestFactoryProxyExchange.java | 62 - .../handler/ClientHttpResponseAdapter.java | 48 - .../handler/GatewayAsyncServerResponse.java | 179 -- .../GatewayDelegatingRouterFunction.java | 61 - .../handler/GatewayEntityResponseBuilder.java | 380 --- .../GatewayErrorHandlingServerResponse.java | 112 - .../GatewayMultipartAutoConfiguration.java | 50 - .../handler/GatewayMvcMultipartResolver.java | 77 - .../GatewayRenderingResponseBuilder.java | 186 -- .../mvc/handler/GatewayRouterFunctions.java | 47 - .../GatewayRouterFunctionsBuilder.java | 326 --- .../mvc/handler/GatewayServerResponse.java | 209 -- .../handler/GatewayServerResponseBuilder.java | 223 -- .../server/mvc/handler/HandlerDiscoverer.java | 58 - .../server/mvc/handler/HandlerFunctions.java | 122 - .../server/mvc/handler/HandlerSupplier.java | 25 - .../server/mvc/handler/ProxyExchange.java | 149 -- .../handler/ProxyExchangeHandlerFunction.java | 143 -- .../mvc/handler/RestClientProxyExchange.java | 62 - .../server/mvc/invoke/InvocationContext.java | 103 - .../invoke/MissingParametersException.java | 58 - .../mvc/invoke/OperationArgumentResolver.java | 74 - .../server/mvc/invoke/OperationInvoker.java | 37 - .../mvc/invoke/OperationInvokerAdvisor.java | 43 - .../server/mvc/invoke/OperationParameter.java | 58 - .../mvc/invoke/OperationParameters.java | 65 - .../mvc/invoke/ParameterMappingException.java | 67 - .../mvc/invoke/ParameterValueMapper.java | 42 - ...ConversionServiceParameterValueMapper.java | 64 - .../convert/IsoOffsetDateTimeConverter.java | 48 - .../reflect/DefaultOperationMethod.java | 74 - .../mvc/invoke/reflect/OperationMethod.java | 29 - .../reflect/OperationMethodParameter.java | 95 - .../reflect/OperationMethodParameters.java | 83 - .../reflect/ReflectiveOperationInvoker.java | 128 - .../predicate/GatewayRequestPredicates.java | 539 ----- .../mvc/predicate/MvcPredicateSupplier.java | 32 - .../mvc/predicate/PredicateDiscoverer.java | 29 - .../mvc/predicate/PredicateSupplier.java | 25 - .../main/resources/META-INF/spring.factories | 12 - ...ot.autoconfigure.AutoConfiguration.imports | 3 - .../server/mvc/ServerMvcIntegrationTests.java | 1188 ---------- .../mvc/TokenRelayFilterFunctionsTests.java | 114 - ...ropertiesBeanDefinitionRegistrarTests.java | 317 --- .../ForwardedRequestHeadersFilterTests.java | 207 -- ...ormalizationRequestHeadersFilterTests.java | 66 - .../filter/WeightCalculatorFilterTests.java | 155 -- ...eightRequestPredicateIntegrationTests.java | 102 - .../predicate/PredicateDiscovererTests.java | 49 - .../mvc/test/HttpbinTestcontainers.java | 99 - .../server/mvc/test/HttpbinUriResolver.java | 54 - .../mvc/test/LocalHostUriBuilderFactory.java | 356 --- .../mvc/test/LocalServerPortUriResolver.java | 50 - .../mvc/test/TestAutoConfiguration.java | 59 - .../server/mvc/test/TestController.java | 131 -- .../server/mvc/test/TestFilterSupplier.java | 42 - .../mvc/test/TestLoadBalancerConfig.java | 50 - .../gateway/server/mvc/test/TestUtils.java | 49 - .../mvc/test/client/CookieAssertions.java | 220 -- .../test/client/DefaultTestRestClient.java | 589 ----- .../mvc/test/client/EntityExchangeResult.java | 47 - .../mvc/test/client/ExchangeResult.java | 296 --- .../mvc/test/client/HeaderAssertions.java | 322 --- .../mvc/test/client/JsonPathAssertions.java | 191 -- .../mvc/test/client/StatusAssertions.java | 237 -- .../mvc/test/client/TestRestClient.java | 665 ------ .../mvc/test/client/XpathAssertions.java | 208 -- .../test/resources/META-INF/spring.factories | 2 - ...ot.autoconfigure.AutoConfiguration.imports | 1 - ...propertiesbeandefinitionregistrartests.yml | 48 - ...weightrequestpredicateintegrationtests.yml | 20 - .../src/test/resources/application.yml | 9 - .../src/test/resources/test/1x1.png | Bin 1047 -> 0 bytes spring-cloud-gateway-server/pom.xml | 384 --- .../AbstractGatewayControllerEndpoint.java | 251 -- .../actuate/GatewayControllerEndpoint.java | 99 - .../GatewayLegacyControllerEndpoint.java | 109 - .../gateway/config/AbstractSslConfigurer.java | 139 -- ...onfigurableHintsRegistrationProcessor.java | 160 -- .../config/GatewayAutoConfiguration.java | 882 ------- ...ewayClassPathWarningAutoConfiguration.java | 62 - .../GatewayEnvironmentPostProcessor.java | 34 - .../config/GatewayLoadBalancerProperties.java | 37 - .../GatewayMetricsAutoConfiguration.java | 150 -- .../config/GatewayMetricsProperties.java | 87 - ...NoLoadBalancerClientAutoConfiguration.java | 84 - .../gateway/config/GatewayProperties.java | 114 - ...veLoadBalancerClientAutoConfiguration.java | 64 - ...atewayReactiveOAuth2AutoConfiguration.java | 60 - .../config/GatewayRedisAutoConfiguration.java | 96 - ...ence4JCircuitBreakerAutoConfiguration.java | 61 - .../gateway/config/GlobalCorsProperties.java | 39 - .../gateway/config/GrpcSslConfigurer.java | 59 - .../gateway/config/HttpClientCustomizer.java | 31 - .../gateway/config/HttpClientFactory.java | 195 -- .../gateway/config/HttpClientProperties.java | 542 ----- .../config/HttpClientSslConfigurer.java | 76 - .../LocalResponseCacheAutoConfiguration.java | 141 -- .../gateway/config/MaxDataSizeValidator.java | 44 - .../PropertiesRouteDefinitionLocator.java | 40 - ...lerMappingGlobalCorsAutoConfiguration.java | 48 - .../ConditionalOnEnabledFilter.java | 40 - .../ConditionalOnEnabledGlobalFilter.java | 40 - .../ConditionalOnEnabledPredicate.java | 40 - .../conditional/OnEnabledComponent.java | 90 - .../config/conditional/OnEnabledFilter.java | 56 - .../conditional/OnEnabledGlobalFilter.java | 52 - .../conditional/OnEnabledPredicate.java | 57 - ...DiscoveryClientRouteDefinitionLocator.java | 212 -- .../discovery/DiscoveryLocatorProperties.java | 125 - ...tewayDiscoveryClientAutoConfiguration.java | 103 - .../gateway/event/EnableBodyCachingEvent.java | 34 - .../cloud/gateway/event/FilterArgsEvent.java | 43 - .../gateway/event/PredicateArgsEvent.java | 43 - .../gateway/event/RefreshRoutesEvent.java | 58 - .../event/RefreshRoutesResultEvent.java | 45 - .../gateway/event/WeightDefinedEvent.java | 35 - .../filter/AdaptCachedBodyGlobalFilter.java | 80 - .../gateway/filter/FilterDefinition.java | 105 - .../gateway/filter/ForwardPathFilter.java | 55 - .../gateway/filter/ForwardRoutingFilter.java | 78 - .../cloud/gateway/filter/GatewayFilter.java | 55 - .../gateway/filter/GatewayFilterChain.java | 40 - .../gateway/filter/GatewayMetricsFilter.java | 102 - .../cloud/gateway/filter/GlobalFilter.java | 46 - ...adBalancerServiceInstanceCookieFilter.java | 91 - .../gateway/filter/NettyRoutingFilter.java | 291 --- .../filter/NettyWriteResponseFilter.java | 141 -- .../gateway/filter/OrderedGatewayFilter.java | 57 - .../ReactiveLoadBalancerClientFilter.java | 183 -- .../filter/RemoveCachedBodyFilter.java | 54 - .../filter/RouteToRequestUrlFilter.java | 95 - .../filter/WebClientHttpRoutingFilter.java | 126 - .../filter/WebClientWriteResponseFilter.java | 73 - .../filter/WebsocketRoutingFilter.java | 289 --- .../filter/WeightCalculatorWebFilter.java | 306 --- .../CorsGatewayFilterApplicationListener.java | 154 -- ...tChangeRequestUriGatewayFilterFactory.java | 63 - .../factory/AbstractGatewayFilterFactory.java | 65 - ...AbstractNameValueGatewayFilterFactory.java | 74 - .../AddRequestHeaderGatewayFilterFactory.java | 54 - ...adersIfNotPresentGatewayFilterFactory.java | 165 -- ...dRequestParameterGatewayFilterFactory.java | 80 - ...AddResponseHeaderGatewayFilterFactory.java | 59 - .../CacheRequestBodyGatewayFilterFactory.java | 122 - ...upeResponseHeaderGatewayFilterFactory.java | 169 -- .../FallbackHeadersGatewayFilterFactory.java | 139 -- .../filter/factory/GatewayFilterFactory.java | 80 - .../JsonToGrpcGatewayFilterFactory.java | 320 --- .../MapRequestHeaderGatewayFilterFactory.java | 121 - .../PrefixPathGatewayFilterFactory.java | 114 - ...reserveHostHeaderGatewayFilterFactory.java | 54 - .../RedirectToGatewayFilterFactory.java | 130 -- ...butesResponseBodyGatewayFilterFactory.java | 147 -- ...moveRequestHeaderGatewayFilterFactory.java | 65 - ...eRequestParameterGatewayFilterFactory.java | 76 - ...oveResponseHeaderGatewayFilterFactory.java | 62 - ...RequestHeaderSizeGatewayFilterFactory.java | 134 -- ...eaderToRequestUriGatewayFilterFactory.java | 83 - ...equestRateLimiterGatewayFilterFactory.java | 203 -- .../RequestSizeGatewayFilterFactory.java | 116 - .../factory/RetryGatewayFilterFactory.java | 437 ---- ...ionResponseHeaderGatewayFilterFactory.java | 258 -- .../RewritePathGatewayFilterFactory.java | 115 - ...iteResponseHeaderGatewayFilterFactory.java | 124 - .../SaveSessionGatewayFilterFactory.java | 55 - .../SecureHeadersGatewayFilterFactory.java | 271 --- .../factory/SecureHeadersProperties.java | 176 -- .../factory/SetPathGatewayFilterFactory.java | 100 - .../SetRequestHeaderGatewayFilterFactory.java | 54 - ...RequestHostHeaderGatewayFilterFactory.java | 88 - ...SetResponseHeaderGatewayFilterFactory.java | 51 - .../SetStatusGatewayFilterFactory.java | 118 - ...pringCloudCircuitBreakerFilterFactory.java | 238 -- ...rcuitBreakerResilience4JFilterFactory.java | 54 - .../StripPrefixGatewayFilterFactory.java | 111 - .../TokenRelayGatewayFilterFactory.java | 96 - .../filter/factory/cache/CachedResponse.java | 164 -- .../factory/cache/CachedResponseMetadata.java | 27 - ...GlobalLocalResponseCacheGatewayFilter.java | 62 - ...ocalResponseCacheGatewayFilterFactory.java | 135 -- .../cache/LocalResponseCacheProperties.java | 72 - .../cache/ResponseCacheGatewayFilter.java | 109 - .../factory/cache/ResponseCacheManager.java | 204 -- .../cache/ResponseCacheManagerFactory.java | 40 - .../cache/ResponseCacheSizeWeigher.java | 46 - .../cache/keygenerator/CacheKeyGenerator.java | 91 - .../CookiesKeyValueGenerator.java | 51 - .../keygenerator/HeaderKeyValueGenerator.java | 62 - .../cache/keygenerator/KeyValueGenerator.java | 42 - .../keygenerator/UriKeyValueGenerator.java | 34 - .../AfterCacheExchangeMutator.java | 33 - ...MaxAgeHeaderAfterCacheExchangeMutator.java | 102 - ...ponseHeadersAfterCacheExchangeMutator.java | 38 - ...etStatusCodeAfterCacheExchangeMutator.java | 54 - .../rewrite/CachedBodyOutputMessage.java | 102 - .../rewrite/GzipMessageBodyResolver.java | 60 - .../factory/rewrite/MessageBodyDecoder.java | 29 - .../factory/rewrite/MessageBodyEncoder.java | 31 - ...ModifyRequestBodyGatewayFilterFactory.java | 214 -- ...odifyResponseBodyGatewayFilterFactory.java | 300 --- .../factory/rewrite/RewriteFunction.java | 33 - .../headers/ForwardedHeadersFilter.java | 198 -- .../headers/GRPCRequestHeadersFilter.java | 61 - .../headers/GRPCResponseHeadersFilter.java | 91 - .../filter/headers/HttpHeadersFilter.java | 73 - .../headers/RemoveHopByHopHeadersFilter.java | 84 - ...ferEncodingNormalizationHeadersFilter.java | 49 - .../headers/XForwardedHeadersFilter.java | 333 --- .../DefaultGatewayObservationConvention.java | 87 - .../headers/observation/GatewayContext.java | 57 - .../GatewayDocumentedObservation.java | 108 - .../GatewayObservationConvention.java | 35 - ...gatingSenderTracingObservationHandler.java | 67 - ...ObservationClosingWebExceptionHandler.java | 56 - .../ObservedRequestHttpHeadersFilter.java | 106 - .../ObservedResponseHttpHeadersFilter.java | 67 - .../filter/ratelimit/AbstractRateLimiter.java | 77 - .../gateway/filter/ratelimit/KeyResolver.java | 30 - .../ratelimit/PrincipalNameKeyResolver.java | 35 - .../gateway/filter/ratelimit/RateLimiter.java | 69 - .../filter/ratelimit/RedisRateLimiter.java | 360 --- .../cloud/gateway/handler/AsyncPredicate.java | 171 -- .../gateway/handler/FilteringWebHandler.java | 150 -- .../handler/RoutePredicateHandlerMapping.java | 195 -- .../AbstractRoutePredicateFactory.java | 28 - .../predicate/AfterRoutePredicateFactory.java | 83 - .../BeforeRoutePredicateFactory.java | 80 - .../BetweenRoutePredicateFactory.java | 107 - ...ndryRouteServiceRoutePredicateFactory.java | 67 - .../CookieRoutePredicateFactory.java | 111 - .../handler/predicate/GatewayPredicate.java | 176 -- .../HeaderRoutePredicateFactory.java | 123 - .../predicate/HostRoutePredicateFactory.java | 114 - .../MethodRoutePredicateFactory.java | 85 - .../predicate/PathRoutePredicateFactory.java | 187 -- .../predicate/PredicateDefinition.java | 106 - .../predicate/QueryRoutePredicateFactory.java | 115 - .../ReadBodyRoutePredicateFactory.java | 161 -- .../RemoteAddrRoutePredicateFactory.java | 151 -- .../predicate/RoutePredicateFactory.java | 78 - .../WeightRoutePredicateFactory.java | 125 - ...wardedRemoteAddrRoutePredicateFactory.java | 148 -- .../route/CachingRouteDefinitionLocator.java | 71 - .../gateway/route/CachingRouteLocator.java | 135 -- .../CompositeRouteDefinitionLocator.java | 71 - .../gateway/route/CompositeRouteLocator.java | 44 - .../InMemoryRouteDefinitionRepository.java | 65 - .../route/RedisRouteDefinitionRepository.java | 91 - .../cloud/gateway/route/Route.java | 322 --- .../cloud/gateway/route/RouteDefinition.java | 153 -- .../gateway/route/RouteDefinitionLocator.java | 28 - .../gateway/route/RouteDefinitionMetrics.java | 71 - .../route/RouteDefinitionRepository.java | 24 - .../route/RouteDefinitionRouteLocator.java | 238 -- .../gateway/route/RouteDefinitionWriter.java | 30 - .../cloud/gateway/route/RouteLocator.java | 52 - .../gateway/route/RouteRefreshListener.java | 76 - .../gateway/route/builder/BooleanSpec.java | 134 -- .../gateway/route/builder/Buildable.java | 23 - .../route/builder/GatewayFilterSpec.java | 937 -------- .../gateway/route/builder/PredicateSpec.java | 310 --- .../route/builder/RouteLocatorBuilder.java | 130 -- .../cloud/gateway/route/builder/UriSpec.java | 81 - .../gateway/support/AbstractConfigurable.java | 45 - .../support/AbstractStatefulConfigurable.java | 43 - .../gateway/support/BodyInserterContext.java | 56 - .../cloud/gateway/support/Configurable.java | 25 - .../gateway/support/ConfigurationService.java | 256 -- .../support/DelegatingServiceInstance.java | 83 - .../support/GatewayToStringStyler.java | 65 - .../cloud/gateway/support/HasConfig.java | 25 - .../cloud/gateway/support/HasRouteId.java | 25 - .../gateway/support/HttpStatusHolder.java | 126 - .../gateway/support/KeyValueConverter.java | 41 - .../support/MvcFoundOnClasspathException.java | 21 - .../MvcFoundOnClasspathFailureAnalyzer.java | 39 - .../cloud/gateway/support/NameUtils.java | 94 - .../gateway/support/NotFoundException.java | 53 - .../gateway/support/RouteMetadataUtils.java | 35 - .../support/ServerWebExchangeUtils.java | 429 ---- .../support/ServiceUnavailableException.java | 42 - .../gateway/support/ShortcutConfigurable.java | 259 --- .../gateway/support/StatefulConfigurable.java | 25 - .../StringToZonedDateTimeConverter.java | 43 - .../gateway/support/TimeoutException.java | 42 - .../cloud/gateway/support/Visitor.java | 30 - .../cloud/gateway/support/WeightConfig.java | 87 - .../ipresolver/RemoteAddressResolver.java | 32 - .../XForwardedRemoteAddressResolver.java | 135 -- .../tagsprovider/GatewayHttpTagsProvider.java | 71 - .../tagsprovider/GatewayPathTagsProvider.java | 52 - .../GatewayRouteTagsProvider.java | 42 - .../tagsprovider/GatewayTagsProvider.java | 37 - .../tagsprovider/PropertiesTagsProvider.java | 44 - .../cloud/gateway/route/builder/RouteDsl.kt | 106 - ...itional-spring-configuration-metadata.json | 417 ---- .../META-INF/scripts/request_rate_limiter.lua | 60 - .../jakarta.validation.ConstraintValidator | 17 - .../main/resources/META-INF/spring.factories | 6 - .../resources/META-INF/spring/aot.factories | 2 - ...ot.autoconfigure.AutoConfiguration.imports | 11 - .../GatewayControllerEndpointTests.java | 520 ----- ...urableHintsRegistrationProcessorTests.java | 98 - .../config/GatewayAutoConfigurationTests.java | 481 ---- .../GatewayMetricsAutoConfigurationTests.java | 200 -- ...dBalancerClientAutoConfigurationTests.java | 70 - .../GatewayRedisAutoConfigurationTests.java | 114 - ...itionRepositoryEnabledByPropertyTests.java | 64 - ...alResponseCacheAutoConfigurationTests.java | 70 - .../DisableBuiltInFiltersTests.java | 129 - .../DisableBuiltInGlobalFiltersTests.java | 103 - .../DisableBuiltInPredicatesTests.java | 102 - .../conditional/OnEnabledComponentTests.java | 107 - .../conditional/OnEnabledFilterTests.java | 61 - .../OnEnabledGlobalFilterTests.java | 57 - .../conditional/OnEnabledPredicateTests.java | 59 - .../cloud/gateway/cors/CorsGlobalTests.java | 97 - .../cloud/gateway/cors/CorsPerRouteTests.java | 132 -- .../cors/SimpleUrlHandlerCorsTests.java | 83 - ...outeDefinitionLocatorIntegrationTests.java | 121 - ...veryClientRouteDefinitionLocatorTests.java | 138 -- ...yDiscoveryClientAutoConfigurationTest.java | 93 - ...DiscoveryClientAutoConfigurationTests.java | 83 - ...rdRoutingFilterStaticIntegrationTests.java | 50 - .../filter/ForwardRoutingFilterTests.java | 140 -- .../GatewayMetricsFilterCustomTagsTests.java | 88 - .../filter/GatewayMetricsFilterTests.java | 129 - ...ancerServiceInstanceCookieFilterTests.java | 127 - .../NettyRoutingFilterCompatibleTests.java | 78 - .../NettyRoutingFilterIntegrationTests.java | 189 -- .../filter/NettyRoutingFilterTests.java | 98 - .../filter/NettyWriteResponseFilterTests.java | 65 - ...ReactiveLoadBalancerClientFilterTests.java | 448 ---- .../filter/RouteToRequestUrlFilterTests.java | 208 -- .../filter/WebsocketRoutingFilterTests.java | 114 - ...ghtCalculatorWebFilterConcurrentTests.java | 121 - .../WeightCalculatorWebFilterTests.java | 157 -- ...equestHeaderGatewayFilterFactoryTests.java | 119 - ...IfNotPresentGatewayFilterFactoryTests.java | 145 -- ...estParameterGatewayFilterFactoryTests.java | 130 -- ...sponseHeaderGatewayFilterFactoryTests.java | 88 - ...eRequestBodyGatewayFilterFactoryTests.java | 189 -- ...sponseHeaderGatewayFilterFactoryTests.java | 50 - ...seHeaderGatewayFilterFactoryUnitTests.java | 130 -- ...equestHeaderGatewayFilterFactoryTests.java | 124 - .../PrefixPathGatewayFilterFactoryTest.java | 101 - ...veHostHeaderGatewayFilterFactoryTests.java | 84 - .../RedirectToGatewayFilterFactoryTests.java | 88 - ...ResponseBodyGatewayFilterFactoryTests.java | 119 - ...equestHeaderGatewayFilterFactoryTests.java | 64 - ...rGatewayFilterFactoryIntegrationTests.java | 81 - ...estParameterGatewayFilterFactoryTests.java | 119 - ...sponseHeaderGatewayFilterFactoryTests.java | 58 - ...estHeaderSizeGatewayFilterFactoryTest.java | 115 - ...iGatewayFilterFactoryIntegrationTests.java | 90 - ...ToRequestUriGatewayFilterFactoryTests.java | 103 - ...tRateLimiterGatewayFilterFactoryTests.java | 156 -- .../RequestSizeGatewayFilterFactoryTest.java | 83 - ...yGatewayFilterFactoryIntegrationTests.java | 318 --- ...sponseHeaderGatewayFilterFactoryTests.java | 48 - ...seHeaderGatewayFilterFactoryUnitTests.java | 218 -- ...hGatewayFilterFactoryIntegrationTests.java | 46 - .../RewritePathGatewayFilterFactoryTests.java | 105 - ...sponseHeaderGatewayFilterFactoryTests.java | 47 - ...seHeaderGatewayFilterFactoryUnitTests.java | 87 - .../SaveSessionGatewayFilterFactoryTests.java | 88 - ...ecureHeadersGatewayFilterFactoryTests.java | 93 - ...eHeadersGatewayFilterFactoryUnitTests.java | 166 -- ...hGatewayFilterFactoryIntegrationTests.java | 53 - .../SetPathGatewayFilterFactoryTests.java | 109 - ...equestHeaderGatewayFilterFactoryTests.java | 85 - ...stHostHeaderGatewayFilterFactoryTests.java | 63 - ...sponseHeaderGatewayFilterFactoryTests.java | 79 - .../SetStatusGatewayFilterFactoryTests.java | 138 -- ...CloudCircuitBreakerFilterFactoryTests.java | 132 -- ...BreakerResilience4JFilterFactoryTests.java | 115 - .../SpringCloudCircuitBreakerTestConfig.java | 161 -- ...xGatewayFilterFactoryIntegrationTests.java | 54 - .../StripPrefixGatewayFilterFactoryTests.java | 90 - .../TokenRelayGatewayFilterFactoryTests.java | 220 -- .../factory/cache/CacheKeyGeneratorTest.java | 254 -- .../factory/cache/CachedResponseTest.java | 93 - ...esponseCacheGatewayFilterFactoryTests.java | 329 --- .../LocalResponseCacheGlobalFilterTests.java | 123 - .../cache/ResponseCacheGatewayFilterTest.java | 107 - .../DefaultKeyValueGeneratorTests.java | 80 - .../HeaderKeyValueGeneratorTest.java | 83 - ...geHeaderAfterCacheExchangeMutatorTest.java | 166 -- ...eHeadersAfterCacheExchangeMutatorTest.java | 75 - ...atusCodeAfterCacheExchangeMutatorTest.java | 88 - ...dyGatewayFilterFactorySslTimeoutTests.java | 176 -- ...yRequestBodyGatewayFilterFactoryTests.java | 126 - ...uestBodyGatewayFilterFactoryUnitTests.java | 51 - ...onseBodyGatewayFilterFactoryGzipTests.java | 75 - ...ResponseBodyGatewayFilterFactoryTests.java | 105 - ...onseBodyGatewayFilterFactoryUnitTests.java | 41 - .../headers/ForwardedHeadersFilterTests.java | 198 -- .../headers/GRPCRequestHeadersFilterTest.java | 58 - .../HttpHeadersFilterMixedTypeTests.java | 75 - .../headers/HttpHeadersFilterTests.java | 58 - ...ttpStatusInResponseHeadersFilterTests.java | 72 - .../NonStandardHeadersInResponseTests.java | 98 - .../RemoveHopByHopHeadersFilterTests.java | 75 - ...lizationHeadersFilterIntegrationTests.java | 152 -- ...codingNormalizationHeadersFilterTests.java | 64 - .../headers/XForwardedHeadersFilterTests.java | 312 --- ...B3BraveObservedHttpHeadersFilterTests.java | 120 - ...gSenderTracingObservationHandlerTests.java | 77 - ...vationClosingWebExceptionHandlerTests.java | 63 - .../ObservedHttpHeadersFilterTests.java | 109 - ...bservedResponseHttpHeadersFilterTests.java | 42 - ...ncipalNameKeyResolverIntegrationTests.java | 157 -- .../PrincipalNameKeyResolverTests.java | 41 - .../RedisRateLimiterConfigTests.java | 129 - ...isRateLimiterDefaultFilterConfigTests.java | 79 - .../RedisRateLimiterLuaScriptTests.java | 156 -- .../ratelimit/RedisRateLimiterTests.java | 203 -- .../ratelimit/RedisRateLimiterUnitTests.java | 103 - .../gateway/handler/AsyncPredicateTest.java | 123 - ...ateHandlerMappingEmptyPredicatesTests.java | 59 - ...edicateHandlerMappingIntegrationTests.java | 113 - ...ingSameManagementPortIntegrationTests.java | 65 - .../RoutePredicateHandlerMappingTests.java | 90 - ...hAutoconfiguredClientIntegrationTests.java | 79 - .../AfterRoutePredicateFactoryTests.java | 102 - .../BeforeRoutePredicateFactoryTests.java | 101 - ...RoutePredicateFactoryIntegrationTests.java | 77 - .../BetweenRoutePredicateFactoryTests.java | 170 -- ...RoutePredicateFactoryIntegrationTests.java | 85 - ...RouteServiceRoutePredicateFactoryTest.java | 72 - ...RoutePredicateFactoryIntegrationTests.java | 62 - .../CookieRoutePredicateFactoryTests.java | 66 - .../GatewayPredicateVisitorTests.java | 71 - .../HeaderRoutePredicateFactoryTests.java | 93 - .../HostRoutePredicateFactoryTests.java | 100 - .../MethodRoutePredicateFactoryTests.java | 111 - .../PathRoutePredicateFactoryTests.java | 139 -- ...dicatePathContainerAttrBenchMarkTests.java | 82 - .../QueryRoutePredicateFactoryTests.java | 91 - .../ReadBodyRoutePredicateFactoryTests.java | 150 -- .../RemoteAddrRoutePredicateFactoryTests.java | 104 - ...RoutePredicateFactoryIntegrationTests.java | 102 - ...ightRoutePredicateFactoryYaml404Tests.java | 73 - ...dRemoteAddrRoutePredicateFactoryTests.java | 90 - .../CachingRouteDefinitionLocatorTests.java | 137 -- .../route/CachingRouteLocatorTests.java | 137 -- ...nMemoryRouteDefinitionRepositoryTests.java | 92 - .../RedisRouteDefinitionRepositoryTests.java | 178 -- .../route/RouteDefinitionDefaultIdTests.java | 58 - .../route/RouteDefinitionMetricsTests.java | 100 - .../RouteDefinitionRouteLocatorTests.java | 212 -- .../gateway/route/RouteDefinitionTest.java | 68 - .../route/RouteRefreshListenerTests.java | 95 - .../cloud/gateway/route/RouteTests.java | 86 - .../route/builder/GatewayFilterSpecTests.java | 194 -- .../route/builder/RouteBuilderTests.java | 115 - .../support/ConfigurationServiceTests.java | 100 - .../cloud/gateway/support/NameUtilsTests.java | 131 -- .../support/ServerWebExchangeUtilsTests.java | 118 - ...ortcutConfigurableNonRestrictiveTests.java | 107 - .../support/ShortcutConfigurableTests.java | 212 -- .../XForwardedRemoteAddressResolverTest.java | 148 -- .../GatewayHttpTagsProviderTests.java | 76 - .../GatewayPathTagsProviderTests.java | 106 - .../GatewayRouteTagsProviderTests.java | 63 - .../PropertiesTagsProviderTests.java | 50 - .../cloud/gateway/test/AdhocTestSuite.java | 144 -- .../gateway/test/BaseWebClientTests.java | 152 -- .../test/CustomBlockHoundIntegration.java | 101 - .../test/CustomBlockHoundIntegrationTest.java | 40 - .../gateway/test/FormIntegrationTests.java | 120 - .../cloud/gateway/test/ForwardTests.java | 79 - .../gateway/test/GatewayIntegrationTests.java | 193 -- .../gateway/test/GatewayTestApplication.java | 58 - .../test/HttpBinCompatibleController.java | 228 -- .../cloud/gateway/test/HttpStatusTests.java | 92 - .../test/PermitAllSecurityConfiguration.java | 32 - .../cloud/gateway/test/PostTests.java | 56 - .../RouteConstructionIntegrationTests.java | 77 - .../cloud/gateway/test/TestUtils.java | 42 - .../gateway/test/WebfluxNotIncludedTests.java | 43 - .../gateway/test/sse/SseIntegrationTests.java | 281 --- .../test/ssl/ClientCertAuthSSLTests.java | 93 - .../gateway/test/ssl/MultiCertSSLTests.java | 30 - .../test/ssl/SSLHandshakeTimeoutTests.java | 46 - .../gateway/test/ssl/SingleCertSSLTests.java | 78 - .../test/support/AbstractHttpServer.java | 178 -- .../gateway/test/support/HttpServer.java | 36 - .../test/support/ReactorHttpServer.java | 66 - .../websocket/WebSocketIntegrationTests.java | 417 ---- .../gateway/route/builder/RouteDslTests.kt | 124 - .../src/test/resources/1x1.png | Bin 1047 -> 0 bytes ...ockhound.integration.BlockHoundIntegration | 17 - .../resources/application-client-auth-ssl.yml | 38 - .../application-cors-global-config.yml | 10 - .../application-cors-per-route-config.yml | 28 - .../test/resources/application-defaultid.yml | 11 - .../application-disable-components.yml | 26 - .../resources/application-forwardstatic.yml | 8 - .../test/resources/application-logging.yml | 9 - .../resources/application-multi-cert-ssl.yml | 29 - .../application-netty-routing-filter.yml | 95 - .../application-redis-rate-limiter-config.yml | 26 - ...tion-redis-rate-limiter-default-config.yml | 16 - .../application-redis-route-repository.yml | 5 - .../resources/application-remote-address.yml | 24 - .../application-removenonproxyheaders.yml | 16 - .../application-request-header-web-filter.yml | 17 - ...uest-headers-if-not-present-web-filter.yml | 17 - ...lication-request-map-header-web-filter.yml | 16 - ...plication-request-parameter-web-filter.yml | 11 - .../test/resources/application-retrytests.yml | 31 - .../application-save-session-web-filter.yml | 10 - .../resources/application-single-cert-ssl.yml | 29 - .../application-verification-route.yml | 12 - .../resources/application-weights-404.yml | 19 - .../application-weights-concurrent.yml | 8 - .../src/test/resources/application.yml | 509 ---- .../src/test/resources/multi-cert.pem | 62 - ...g-keystore-with-different-key-password.jks | Bin 2263 -> 0 bytes .../src/test/resources/scg-keystore.p12 | Bin 3422 -> 0 bytes .../src/test/resources/scg-truststore.jks | Bin 3208 -> 0 bytes ...single-cert-for-different-key-password.pem | Bin 913 -> 0 bytes .../src/test/resources/single-cert.pem | 27 - .../test/resources/static/httpbin/docs.html | 21 - .../transfer-encoding/invalid-request.bin | 13 - .../transfer-encoding/valid-request.bin | 7 - spring-cloud-gateway-webflux/pom.xml | 44 - .../cloud/gateway/webflux/ProxyExchange.java | 492 ---- .../config/ProxyExchangeArgumentResolver.java | 106 - .../webflux/config/ProxyProperties.java | 86 - .../ProxyResponseAutoConfiguration.java | 70 - .../main/resources/META-INF/spring.factories | 2 - ...ot.autoconfigure.AutoConfiguration.imports | 1 - .../webflux/ProductionConfigurationTests.java | 474 ---- .../cloud/gateway/webflux/ReactiveTests.java | 215 -- .../src/test/resources/application.properties | 1 - .../src/test/resources/static/test.html | 4 - spring-cloud-starter-gateway-mvc/pom.xml | 42 - spring-cloud-starter-gateway/pom.xml | 42 - src/checkstyle/checkstyle-suppressions.xml | 33 - supplemental-ui/partials/nav-search.hbs | 11 + supplemental-ui/partials/search.hbs | 27 + 671 files changed, 394 insertions(+), 74536 deletions(-) delete mode 100644 .editorconfig delete mode 100644 .github/CONTRIBUTING.md delete mode 100644 .github/ISSUE_TEMPLATE.md delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.md delete mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/workflows/deploy-docs.yml delete mode 100644 .github/workflows/maven.yml delete mode 100644 .mvn/maven.config create mode 100644 .mvn/wrapper/MavenWrapperDownloader.java delete mode 100644 .sdkmanrc delete mode 100644 .settings.xml delete mode 100644 LICENSE delete mode 100644 LICENSE.txt delete mode 100644 SECURITY.md create mode 100644 antora-playbook.yml rename docs/{src/main/asciidoc => modules/ROOT/pages}/README.adoc (100%) rename docs/{src/main/asciidoc => modules/ROOT/pages}/_attributes.adoc (100%) rename docs/{src/main/asciidoc => modules/ROOT/pages}/_configprops.adoc (100%) rename docs/{src/main/asciidoc => modules/ROOT/pages}/appendix.adoc (100%) rename docs/{src/main/asciidoc => modules/ROOT/pages}/index.adoc (100%) rename docs/{src/main/asciidoc => modules/ROOT/pages}/intro.adoc (100%) rename docs/{src/main/asciidoc => modules/ROOT/pages}/sagan-boot.adoc (100%) rename docs/{src/main/asciidoc => modules/ROOT/pages}/sagan-index.adoc (100%) rename docs/{src/main/asciidoc => modules/ROOT/pages}/spring-cloud-gateway.adoc (100%) delete mode 100644 docs/pom.xml delete mode 100644 docs/src/main/asciidoc/gateway-grafana-dashboard.json delete mode 100755 docs/src/main/asciidoc/ghpages.sh delete mode 100644 docs/src/main/asciidoc/images/gateway-grafana-dashboard.jpeg delete mode 100644 docs/src/main/asciidoc/images/spring_cloud_gateway_diagram.png delete mode 100644 spring-cloud-gateway-dependencies/pom.xml delete mode 100644 spring-cloud-gateway-integration-tests/grpc/pom.xml delete mode 100644 spring-cloud-gateway-integration-tests/grpc/src/main/java/org/springframework/cloud/gateway/tests/grpc/GRPCApplication.java delete mode 100644 spring-cloud-gateway-integration-tests/grpc/src/main/proto/hello.pb delete mode 100644 spring-cloud-gateway-integration-tests/grpc/src/main/proto/hello.proto delete mode 100644 spring-cloud-gateway-integration-tests/grpc/src/main/resources/application.yml delete mode 100644 spring-cloud-gateway-integration-tests/grpc/src/main/resources/keystore.p12 delete mode 100644 spring-cloud-gateway-integration-tests/grpc/src/main/resources/private.key delete mode 100644 spring-cloud-gateway-integration-tests/grpc/src/main/resources/public.cert delete mode 100644 spring-cloud-gateway-integration-tests/grpc/src/test/java/org/springframework/cloud/gateway/tests/grpc/GRPCApplicationTests.java delete mode 100644 spring-cloud-gateway-integration-tests/grpc/src/test/java/org/springframework/cloud/gateway/tests/grpc/JsonToGrpcApplicationTests.java delete mode 100644 spring-cloud-gateway-integration-tests/grpc/src/test/java/org/springframework/cloud/gateway/tests/grpc/RouteConfigurer.java delete mode 100644 spring-cloud-gateway-integration-tests/grpc/src/test/resources/application.yml delete mode 100644 spring-cloud-gateway-integration-tests/http2/pom.xml delete mode 100644 spring-cloud-gateway-integration-tests/http2/src/main/java/org/springframework/cloud/gateway/tests/http2/Http2Application.java delete mode 100644 spring-cloud-gateway-integration-tests/http2/src/main/resources/application.yml delete mode 100644 spring-cloud-gateway-integration-tests/http2/src/main/resources/sample.jks delete mode 100644 spring-cloud-gateway-integration-tests/http2/src/test/java/org/springframework/cloud/gateway/tests/http2/Http2ApplicationTests.java delete mode 100644 spring-cloud-gateway-integration-tests/http2/src/test/java/org/springframework/cloud/gateway/tests/http2/nossl/NosslConfiguration.java delete mode 100644 spring-cloud-gateway-integration-tests/http2/src/test/java/org/springframework/cloud/gateway/tests/http2/nossl/NosslTests.java delete mode 100644 spring-cloud-gateway-integration-tests/http2/src/test/resources/application-nossl.yml delete mode 100644 spring-cloud-gateway-integration-tests/mvc-failure-analyzer/pom.xml delete mode 100644 spring-cloud-gateway-integration-tests/mvc-failure-analyzer/src/main/java/org/springframework/cloud/gateway/sample/MvcFailureAnalyzerApplication.java delete mode 100644 spring-cloud-gateway-integration-tests/mvc-failure-analyzer/src/main/resources/application.yml delete mode 100644 spring-cloud-gateway-integration-tests/mvc-failure-analyzer/src/test/java/org/springframework/cloud/gateway/sample/MvcFailureAnalyzerApplicationTests.java delete mode 100644 spring-cloud-gateway-integration-tests/pom.xml delete mode 100644 spring-cloud-gateway-mvc/.jdk8 delete mode 100644 spring-cloud-gateway-mvc/pom.xml delete mode 100644 spring-cloud-gateway-mvc/src/main/java/org/springframework/cloud/gateway/mvc/ProxyExchange.java delete mode 100644 spring-cloud-gateway-mvc/src/main/java/org/springframework/cloud/gateway/mvc/config/ProxyExchangeArgumentResolver.java delete mode 100644 spring-cloud-gateway-mvc/src/main/java/org/springframework/cloud/gateway/mvc/config/ProxyProperties.java delete mode 100644 spring-cloud-gateway-mvc/src/main/java/org/springframework/cloud/gateway/mvc/config/ProxyResponseAutoConfiguration.java delete mode 100644 spring-cloud-gateway-mvc/src/main/resources/META-INF/spring.factories delete mode 100644 spring-cloud-gateway-mvc/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports delete mode 100644 spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/GetWithBodyRequestTests.java delete mode 100644 spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/ProductionConfigurationTests.java delete mode 100644 spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/config/ProxyExchangeArgumentResolverTest.java delete mode 100644 spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/http/AbstractBufferingClientHttpRequest.java delete mode 100644 spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/http/GetWithBodyRequestClientHttpRequestFactory.java delete mode 100644 spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/http/HttpComponentsClientHttpRequest.java delete mode 100644 spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/http/HttpComponentsClientHttpResponse.java delete mode 100644 spring-cloud-gateway-mvc/src/test/resources/static/test.html delete mode 100644 spring-cloud-gateway-sample/.jdk8 delete mode 100644 spring-cloud-gateway-sample/pom.xml delete mode 100644 spring-cloud-gateway-sample/src/main/java/org/springframework/cloud/gateway/sample/GatewaySampleApplication.java delete mode 100644 spring-cloud-gateway-sample/src/main/java/org/springframework/cloud/gateway/sample/ThrottleGatewayFilter.java delete mode 100644 spring-cloud-gateway-sample/src/main/kotlin/org/springframework/cloud/gateway/sample/AdditionalRoutes.kt delete mode 100644 spring-cloud-gateway-sample/src/main/resources/application.yml delete mode 100644 spring-cloud-gateway-sample/src/test/java/org/springframework/cloud/gateway/sample/GatewaySampleApplicationTests.java delete mode 100644 spring-cloud-gateway-sample/src/test/java/org/springframework/cloud/gateway/sample/GatewaySampleApplicationWithoutMetricsTests.java delete mode 100644 spring-cloud-gateway-server-mvc/pom.xml delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/GatewayServerMvcAutoConfiguration.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/AbstractGatewayDiscoverer.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/ArgumentSupplier.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/ArgumentSupplierBeanPostProcessor.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/AttributedArugmentSuppliedEvent.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/DefaultArgumentSuppliedEvent.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/HttpStatusHolder.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/KeyValues.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/MvcUtils.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/NameUtils.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/Shortcut.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/WeightConfig.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/FilterProperties.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/GatewayMvcProperties.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/GatewayMvcPropertiesBeanDefinitionRegistrar.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/NormalizedOperationMethod.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/PredicateProperties.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/RouteProperties.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/AfterFilterFunctions.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/BeforeFilterFunctions.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/BodyFilterFunctions.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/Bucket4jFilterFunctions.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/CircuitBreakerFilterFunctions.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/FilterDiscoverer.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/FilterFunctions.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/FilterSupplier.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/ForwardedRequestHeadersFilter.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/HttpHeadersFilter.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/LoadBalancerFilterFunctions.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/LoadBalancerHandlerSupplier.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/RemoveContentLengthRequestHeadersFilter.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/RemoveHopByHopRequestHeadersFilter.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/RemoveHopByHopResponseHeadersFilter.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/RetryFilterFunctions.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/RewriteLocationResponseHeaderFilterFunctions.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/TokenRelayFilterFunctions.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/TransferEncodingNormalizationRequestHeadersFilter.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/WeightCalculatorFilter.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/XForwardedRequestHeadersFilter.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/AbstractGatewayServerResponse.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/ClientHttpRequestFactoryProxyExchange.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/ClientHttpResponseAdapter.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/GatewayAsyncServerResponse.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/GatewayDelegatingRouterFunction.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/GatewayEntityResponseBuilder.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/GatewayErrorHandlingServerResponse.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/GatewayMultipartAutoConfiguration.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/GatewayMvcMultipartResolver.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/GatewayRenderingResponseBuilder.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/GatewayRouterFunctions.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/GatewayRouterFunctionsBuilder.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/GatewayServerResponse.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/GatewayServerResponseBuilder.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/HandlerDiscoverer.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/HandlerFunctions.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/HandlerSupplier.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/ProxyExchange.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/ProxyExchangeHandlerFunction.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/RestClientProxyExchange.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/invoke/InvocationContext.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/invoke/MissingParametersException.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/invoke/OperationArgumentResolver.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/invoke/OperationInvoker.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/invoke/OperationInvokerAdvisor.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/invoke/OperationParameter.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/invoke/OperationParameters.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/invoke/ParameterMappingException.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/invoke/ParameterValueMapper.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/invoke/convert/ConversionServiceParameterValueMapper.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/invoke/convert/IsoOffsetDateTimeConverter.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/invoke/reflect/DefaultOperationMethod.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/invoke/reflect/OperationMethod.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/invoke/reflect/OperationMethodParameter.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/invoke/reflect/OperationMethodParameters.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/invoke/reflect/ReflectiveOperationInvoker.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/predicate/GatewayRequestPredicates.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/predicate/MvcPredicateSupplier.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/predicate/PredicateDiscoverer.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/predicate/PredicateSupplier.java delete mode 100644 spring-cloud-gateway-server-mvc/src/main/resources/META-INF/spring.factories delete mode 100644 spring-cloud-gateway-server-mvc/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports delete mode 100644 spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/ServerMvcIntegrationTests.java delete mode 100644 spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/TokenRelayFilterFunctionsTests.java delete mode 100644 spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/config/GatewayMvcPropertiesBeanDefinitionRegistrarTests.java delete mode 100644 spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/filter/ForwardedRequestHeadersFilterTests.java delete mode 100644 spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/filter/TransferEncodingNormalizationRequestHeadersFilterTests.java delete mode 100644 spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/filter/WeightCalculatorFilterTests.java delete mode 100644 spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/filter/WeightRequestPredicateIntegrationTests.java delete mode 100644 spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/predicate/PredicateDiscovererTests.java delete mode 100644 spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/HttpbinTestcontainers.java delete mode 100644 spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/HttpbinUriResolver.java delete mode 100644 spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/LocalHostUriBuilderFactory.java delete mode 100644 spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/LocalServerPortUriResolver.java delete mode 100644 spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/TestAutoConfiguration.java delete mode 100644 spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/TestController.java delete mode 100644 spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/TestFilterSupplier.java delete mode 100644 spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/TestLoadBalancerConfig.java delete mode 100644 spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/TestUtils.java delete mode 100644 spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/client/CookieAssertions.java delete mode 100644 spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/client/DefaultTestRestClient.java delete mode 100644 spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/client/EntityExchangeResult.java delete mode 100644 spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/client/ExchangeResult.java delete mode 100644 spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/client/HeaderAssertions.java delete mode 100644 spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/client/JsonPathAssertions.java delete mode 100644 spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/client/StatusAssertions.java delete mode 100644 spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/client/TestRestClient.java delete mode 100644 spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/client/XpathAssertions.java delete mode 100644 spring-cloud-gateway-server-mvc/src/test/resources/META-INF/spring.factories delete mode 100644 spring-cloud-gateway-server-mvc/src/test/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports delete mode 100644 spring-cloud-gateway-server-mvc/src/test/resources/application-propertiesbeandefinitionregistrartests.yml delete mode 100644 spring-cloud-gateway-server-mvc/src/test/resources/application-weightrequestpredicateintegrationtests.yml delete mode 100644 spring-cloud-gateway-server-mvc/src/test/resources/application.yml delete mode 100644 spring-cloud-gateway-server-mvc/src/test/resources/test/1x1.png delete mode 100644 spring-cloud-gateway-server/pom.xml delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/actuate/AbstractGatewayControllerEndpoint.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/actuate/GatewayControllerEndpoint.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/actuate/GatewayLegacyControllerEndpoint.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/AbstractSslConfigurer.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/ConfigurableHintsRegistrationProcessor.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayClassPathWarningAutoConfiguration.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayEnvironmentPostProcessor.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayLoadBalancerProperties.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayMetricsAutoConfiguration.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayMetricsProperties.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayNoLoadBalancerClientAutoConfiguration.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayProperties.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayReactiveLoadBalancerClientAutoConfiguration.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayReactiveOAuth2AutoConfiguration.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayRedisAutoConfiguration.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayResilience4JCircuitBreakerAutoConfiguration.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GlobalCorsProperties.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GrpcSslConfigurer.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/HttpClientCustomizer.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/HttpClientFactory.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/HttpClientProperties.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/HttpClientSslConfigurer.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/LocalResponseCacheAutoConfiguration.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/MaxDataSizeValidator.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/PropertiesRouteDefinitionLocator.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/SimpleUrlHandlerMappingGlobalCorsAutoConfiguration.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/conditional/ConditionalOnEnabledFilter.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/conditional/ConditionalOnEnabledGlobalFilter.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/conditional/ConditionalOnEnabledPredicate.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/conditional/OnEnabledComponent.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/conditional/OnEnabledFilter.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/conditional/OnEnabledGlobalFilter.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/conditional/OnEnabledPredicate.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/discovery/DiscoveryClientRouteDefinitionLocator.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/discovery/DiscoveryLocatorProperties.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/discovery/GatewayDiscoveryClientAutoConfiguration.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/event/EnableBodyCachingEvent.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/event/FilterArgsEvent.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/event/PredicateArgsEvent.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/event/RefreshRoutesEvent.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/event/RefreshRoutesResultEvent.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/event/WeightDefinedEvent.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/AdaptCachedBodyGlobalFilter.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/FilterDefinition.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/ForwardPathFilter.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/ForwardRoutingFilter.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/GatewayFilter.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/GatewayFilterChain.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/GatewayMetricsFilter.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/GlobalFilter.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/LoadBalancerServiceInstanceCookieFilter.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/NettyRoutingFilter.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/NettyWriteResponseFilter.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/OrderedGatewayFilter.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/ReactiveLoadBalancerClientFilter.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/RemoveCachedBodyFilter.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/RouteToRequestUrlFilter.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/WebClientHttpRoutingFilter.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/WebClientWriteResponseFilter.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/WebsocketRoutingFilter.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/WeightCalculatorWebFilter.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/cors/CorsGatewayFilterApplicationListener.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/AbstractChangeRequestUriGatewayFilterFactory.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/AbstractGatewayFilterFactory.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/AbstractNameValueGatewayFilterFactory.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/AddRequestHeaderGatewayFilterFactory.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/AddRequestHeadersIfNotPresentGatewayFilterFactory.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/AddRequestParameterGatewayFilterFactory.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/AddResponseHeaderGatewayFilterFactory.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/CacheRequestBodyGatewayFilterFactory.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/DedupeResponseHeaderGatewayFilterFactory.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/FallbackHeadersGatewayFilterFactory.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/GatewayFilterFactory.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/JsonToGrpcGatewayFilterFactory.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/MapRequestHeaderGatewayFilterFactory.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/PrefixPathGatewayFilterFactory.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/PreserveHostHeaderGatewayFilterFactory.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RedirectToGatewayFilterFactory.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RemoveJsonAttributesResponseBodyGatewayFilterFactory.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RemoveRequestHeaderGatewayFilterFactory.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RemoveRequestParameterGatewayFilterFactory.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RemoveResponseHeaderGatewayFilterFactory.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RequestHeaderSizeGatewayFilterFactory.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RequestHeaderToRequestUriGatewayFilterFactory.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RequestRateLimiterGatewayFilterFactory.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RequestSizeGatewayFilterFactory.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RetryGatewayFilterFactory.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RewriteLocationResponseHeaderGatewayFilterFactory.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RewritePathGatewayFilterFactory.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RewriteResponseHeaderGatewayFilterFactory.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/SaveSessionGatewayFilterFactory.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/SecureHeadersGatewayFilterFactory.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/SecureHeadersProperties.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/SetPathGatewayFilterFactory.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/SetRequestHeaderGatewayFilterFactory.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/SetRequestHostHeaderGatewayFilterFactory.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/SetResponseHeaderGatewayFilterFactory.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/SetStatusGatewayFilterFactory.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/SpringCloudCircuitBreakerFilterFactory.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/SpringCloudCircuitBreakerResilience4JFilterFactory.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/StripPrefixGatewayFilterFactory.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/TokenRelayGatewayFilterFactory.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/CachedResponse.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/CachedResponseMetadata.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/GlobalLocalResponseCacheGatewayFilter.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/LocalResponseCacheGatewayFilterFactory.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/LocalResponseCacheProperties.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/ResponseCacheGatewayFilter.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/ResponseCacheManager.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/ResponseCacheManagerFactory.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/ResponseCacheSizeWeigher.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/keygenerator/CacheKeyGenerator.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/keygenerator/CookiesKeyValueGenerator.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/keygenerator/HeaderKeyValueGenerator.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/keygenerator/KeyValueGenerator.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/keygenerator/UriKeyValueGenerator.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/postprocessor/AfterCacheExchangeMutator.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/postprocessor/SetMaxAgeHeaderAfterCacheExchangeMutator.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/postprocessor/SetResponseHeadersAfterCacheExchangeMutator.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/postprocessor/SetStatusCodeAfterCacheExchangeMutator.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/rewrite/CachedBodyOutputMessage.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/rewrite/GzipMessageBodyResolver.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/rewrite/MessageBodyDecoder.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/rewrite/MessageBodyEncoder.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/rewrite/ModifyRequestBodyGatewayFilterFactory.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/rewrite/ModifyResponseBodyGatewayFilterFactory.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/rewrite/RewriteFunction.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/ForwardedHeadersFilter.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/GRPCRequestHeadersFilter.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/GRPCResponseHeadersFilter.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/HttpHeadersFilter.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/RemoveHopByHopHeadersFilter.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/TransferEncodingNormalizationHeadersFilter.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/XForwardedHeadersFilter.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/observation/DefaultGatewayObservationConvention.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/observation/GatewayContext.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/observation/GatewayDocumentedObservation.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/observation/GatewayObservationConvention.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/observation/GatewayPropagatingSenderTracingObservationHandler.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/observation/ObservationClosingWebExceptionHandler.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/observation/ObservedRequestHttpHeadersFilter.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/observation/ObservedResponseHttpHeadersFilter.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/ratelimit/AbstractRateLimiter.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/ratelimit/KeyResolver.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/ratelimit/PrincipalNameKeyResolver.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/ratelimit/RateLimiter.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/ratelimit/RedisRateLimiter.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/AsyncPredicate.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/FilteringWebHandler.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/RoutePredicateHandlerMapping.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/AbstractRoutePredicateFactory.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/AfterRoutePredicateFactory.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/BeforeRoutePredicateFactory.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/BetweenRoutePredicateFactory.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/CloudFoundryRouteServiceRoutePredicateFactory.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/CookieRoutePredicateFactory.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/GatewayPredicate.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/HeaderRoutePredicateFactory.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/HostRoutePredicateFactory.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/MethodRoutePredicateFactory.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicateFactory.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/PredicateDefinition.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/QueryRoutePredicateFactory.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/ReadBodyRoutePredicateFactory.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/RemoteAddrRoutePredicateFactory.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/RoutePredicateFactory.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/WeightRoutePredicateFactory.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/XForwardedRemoteAddrRoutePredicateFactory.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/CachingRouteDefinitionLocator.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/CachingRouteLocator.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/CompositeRouteDefinitionLocator.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/CompositeRouteLocator.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/InMemoryRouteDefinitionRepository.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/RedisRouteDefinitionRepository.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/Route.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/RouteDefinition.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/RouteDefinitionLocator.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/RouteDefinitionMetrics.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/RouteDefinitionRepository.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/RouteDefinitionRouteLocator.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/RouteDefinitionWriter.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/RouteLocator.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/RouteRefreshListener.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/builder/BooleanSpec.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/builder/Buildable.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/builder/GatewayFilterSpec.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/builder/PredicateSpec.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/builder/RouteLocatorBuilder.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/builder/UriSpec.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/AbstractConfigurable.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/AbstractStatefulConfigurable.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/BodyInserterContext.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/Configurable.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ConfigurationService.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/DelegatingServiceInstance.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/GatewayToStringStyler.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/HasConfig.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/HasRouteId.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/HttpStatusHolder.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/KeyValueConverter.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/MvcFoundOnClasspathException.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/MvcFoundOnClasspathFailureAnalyzer.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/NameUtils.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/NotFoundException.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/RouteMetadataUtils.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ServerWebExchangeUtils.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ServiceUnavailableException.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ShortcutConfigurable.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/StatefulConfigurable.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/StringToZonedDateTimeConverter.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/TimeoutException.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/Visitor.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/WeightConfig.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ipresolver/RemoteAddressResolver.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ipresolver/XForwardedRemoteAddressResolver.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/tagsprovider/GatewayHttpTagsProvider.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/tagsprovider/GatewayPathTagsProvider.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/tagsprovider/GatewayRouteTagsProvider.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/tagsprovider/GatewayTagsProvider.java delete mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/tagsprovider/PropertiesTagsProvider.java delete mode 100644 spring-cloud-gateway-server/src/main/kotlin/org/springframework/cloud/gateway/route/builder/RouteDsl.kt delete mode 100644 spring-cloud-gateway-server/src/main/resources/META-INF/additional-spring-configuration-metadata.json delete mode 100644 spring-cloud-gateway-server/src/main/resources/META-INF/scripts/request_rate_limiter.lua delete mode 100644 spring-cloud-gateway-server/src/main/resources/META-INF/services/jakarta.validation.ConstraintValidator delete mode 100644 spring-cloud-gateway-server/src/main/resources/META-INF/spring.factories delete mode 100644 spring-cloud-gateway-server/src/main/resources/META-INF/spring/aot.factories delete mode 100644 spring-cloud-gateway-server/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/actuate/GatewayControllerEndpointTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/ConfigurableHintsRegistrationProcessorTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/GatewayAutoConfigurationTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/GatewayMetricsAutoConfigurationTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/GatewayNoLoadBalancerClientAutoConfigurationTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/GatewayRedisAutoConfigurationTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/GatewayRedisRouteDefinitionRepositoryEnabledByPropertyTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/LocalResponseCacheAutoConfigurationTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/conditional/DisableBuiltInFiltersTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/conditional/DisableBuiltInGlobalFiltersTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/conditional/DisableBuiltInPredicatesTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/conditional/OnEnabledComponentTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/conditional/OnEnabledFilterTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/conditional/OnEnabledGlobalFilterTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/conditional/OnEnabledPredicateTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/cors/CorsGlobalTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/cors/CorsPerRouteTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/cors/SimpleUrlHandlerCorsTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/discovery/DiscoveryClientRouteDefinitionLocatorIntegrationTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/discovery/DiscoveryClientRouteDefinitionLocatorTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/discovery/GatewayDiscoveryClientAutoConfigurationTest.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/discovery/ReactiveGatewayDiscoveryClientAutoConfigurationTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/ForwardRoutingFilterStaticIntegrationTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/ForwardRoutingFilterTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/GatewayMetricsFilterCustomTagsTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/GatewayMetricsFilterTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/LoadBalancerServiceInstanceCookieFilterTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/NettyRoutingFilterCompatibleTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/NettyRoutingFilterIntegrationTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/NettyRoutingFilterTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/NettyWriteResponseFilterTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/ReactiveLoadBalancerClientFilterTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/RouteToRequestUrlFilterTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/WebsocketRoutingFilterTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/WeightCalculatorWebFilterConcurrentTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/WeightCalculatorWebFilterTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/AddRequestHeaderGatewayFilterFactoryTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/AddRequestHeadersIfNotPresentGatewayFilterFactoryTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/AddRequestParameterGatewayFilterFactoryTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/AddResponseHeaderGatewayFilterFactoryTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/CacheRequestBodyGatewayFilterFactoryTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/DedupeResponseHeaderGatewayFilterFactoryTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/DedupeResponseHeaderGatewayFilterFactoryUnitTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/MapRequestHeaderGatewayFilterFactoryTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/PrefixPathGatewayFilterFactoryTest.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/PreserveHostHeaderGatewayFilterFactoryTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RedirectToGatewayFilterFactoryTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RemoveJsonAttributesResponseBodyGatewayFilterFactoryTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RemoveRequestHeaderGatewayFilterFactoryTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RemoveRequestParameterGatewayFilterFactoryIntegrationTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RemoveRequestParameterGatewayFilterFactoryTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RemoveResponseHeaderGatewayFilterFactoryTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RequestHeaderSizeGatewayFilterFactoryTest.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RequestHeaderToRequestUriGatewayFilterFactoryIntegrationTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RequestHeaderToRequestUriGatewayFilterFactoryTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RequestRateLimiterGatewayFilterFactoryTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RequestSizeGatewayFilterFactoryTest.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RetryGatewayFilterFactoryIntegrationTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RewriteLocationResponseHeaderGatewayFilterFactoryTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RewriteLocationResponseHeaderGatewayFilterFactoryUnitTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RewritePathGatewayFilterFactoryIntegrationTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RewritePathGatewayFilterFactoryTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RewriteResponseHeaderGatewayFilterFactoryTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RewriteResponseHeaderGatewayFilterFactoryUnitTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/SaveSessionGatewayFilterFactoryTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/SecureHeadersGatewayFilterFactoryTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/SecureHeadersGatewayFilterFactoryUnitTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/SetPathGatewayFilterFactoryIntegrationTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/SetPathGatewayFilterFactoryTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/SetRequestHeaderGatewayFilterFactoryTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/SetRequestHostHeaderGatewayFilterFactoryTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/SetResponseHeaderGatewayFilterFactoryTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/SetStatusGatewayFilterFactoryTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/SpringCloudCircuitBreakerFilterFactoryTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/SpringCloudCircuitBreakerResilience4JFilterFactoryTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/SpringCloudCircuitBreakerTestConfig.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/StripPrefixGatewayFilterFactoryIntegrationTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/StripPrefixGatewayFilterFactoryTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/TokenRelayGatewayFilterFactoryTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/cache/CacheKeyGeneratorTest.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/cache/CachedResponseTest.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/cache/LocalResponseCacheGatewayFilterFactoryTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/cache/LocalResponseCacheGlobalFilterTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/cache/ResponseCacheGatewayFilterTest.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/cache/keygenerator/DefaultKeyValueGeneratorTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/cache/keygenerator/HeaderKeyValueGeneratorTest.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/cache/postprocessor/SetMaxAgeHeaderAfterCacheExchangeMutatorTest.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/cache/postprocessor/SetResponseHeadersAfterCacheExchangeMutatorTest.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/cache/postprocessor/SetStatusCodeAfterCacheExchangeMutatorTest.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/rewrite/ModifyRequestBodyGatewayFilterFactorySslTimeoutTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/rewrite/ModifyRequestBodyGatewayFilterFactoryTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/rewrite/ModifyRequestBodyGatewayFilterFactoryUnitTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/rewrite/ModifyResponseBodyGatewayFilterFactoryGzipTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/rewrite/ModifyResponseBodyGatewayFilterFactoryTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/rewrite/ModifyResponseBodyGatewayFilterFactoryUnitTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/ForwardedHeadersFilterTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/GRPCRequestHeadersFilterTest.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/HttpHeadersFilterMixedTypeTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/HttpHeadersFilterTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/HttpStatusInResponseHeadersFilterTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/NonStandardHeadersInResponseTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/RemoveHopByHopHeadersFilterTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/TransferEncodingNormalizationHeadersFilterIntegrationTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/TransferEncodingNormalizationHeadersFilterTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/XForwardedHeadersFilterTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/observation/B3BraveObservedHttpHeadersFilterTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/observation/GatewayPropagatingSenderTracingObservationHandlerTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/observation/ObservationClosingWebExceptionHandlerTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/observation/ObservedHttpHeadersFilterTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/observation/ObservedResponseHttpHeadersFilterTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/ratelimit/PrincipalNameKeyResolverIntegrationTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/ratelimit/PrincipalNameKeyResolverTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/ratelimit/RedisRateLimiterConfigTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/ratelimit/RedisRateLimiterDefaultFilterConfigTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/ratelimit/RedisRateLimiterLuaScriptTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/ratelimit/RedisRateLimiterTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/ratelimit/RedisRateLimiterUnitTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/AsyncPredicateTest.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/RoutePredicateHandlerMappingEmptyPredicatesTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/RoutePredicateHandlerMappingIntegrationTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/RoutePredicateHandlerMappingSameManagementPortIntegrationTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/RoutePredicateHandlerMappingTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/RoutePredicateHandlerMappingWithAutoconfiguredClientIntegrationTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/AfterRoutePredicateFactoryTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/BeforeRoutePredicateFactoryTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/BetweenRoutePredicateFactoryIntegrationTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/BetweenRoutePredicateFactoryTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/CloudFoundryRouteServiceRoutePredicateFactoryIntegrationTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/CloudFoundryRouteServiceRoutePredicateFactoryTest.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/CookieRoutePredicateFactoryIntegrationTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/CookieRoutePredicateFactoryTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/GatewayPredicateVisitorTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/HeaderRoutePredicateFactoryTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/HostRoutePredicateFactoryTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/MethodRoutePredicateFactoryTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicateFactoryTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicatePathContainerAttrBenchMarkTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/QueryRoutePredicateFactoryTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/ReadBodyRoutePredicateFactoryTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/RemoteAddrRoutePredicateFactoryTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/WeightRoutePredicateFactoryIntegrationTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/WeightRoutePredicateFactoryYaml404Tests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/XForwardedRemoteAddrRoutePredicateFactoryTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/route/CachingRouteDefinitionLocatorTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/route/CachingRouteLocatorTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/route/InMemoryRouteDefinitionRepositoryTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/route/RedisRouteDefinitionRepositoryTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/route/RouteDefinitionDefaultIdTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/route/RouteDefinitionMetricsTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/route/RouteDefinitionRouteLocatorTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/route/RouteDefinitionTest.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/route/RouteRefreshListenerTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/route/RouteTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/route/builder/GatewayFilterSpecTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/route/builder/RouteBuilderTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/support/ConfigurationServiceTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/support/NameUtilsTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/support/ServerWebExchangeUtilsTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/support/ShortcutConfigurableNonRestrictiveTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/support/ShortcutConfigurableTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/support/ipresolver/XForwardedRemoteAddressResolverTest.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/support/tagsprovider/GatewayHttpTagsProviderTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/support/tagsprovider/GatewayPathTagsProviderTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/support/tagsprovider/GatewayRouteTagsProviderTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/support/tagsprovider/PropertiesTagsProviderTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/AdhocTestSuite.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/BaseWebClientTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/CustomBlockHoundIntegration.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/CustomBlockHoundIntegrationTest.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/FormIntegrationTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/ForwardTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/GatewayIntegrationTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/GatewayTestApplication.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/HttpBinCompatibleController.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/HttpStatusTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/PermitAllSecurityConfiguration.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/PostTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/RouteConstructionIntegrationTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/TestUtils.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/WebfluxNotIncludedTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/sse/SseIntegrationTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/ssl/ClientCertAuthSSLTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/ssl/MultiCertSSLTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/ssl/SSLHandshakeTimeoutTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/ssl/SingleCertSSLTests.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/support/AbstractHttpServer.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/support/HttpServer.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/support/ReactorHttpServer.java delete mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/websocket/WebSocketIntegrationTests.java delete mode 100644 spring-cloud-gateway-server/src/test/kotlin/org/springframework/cloud/gateway/route/builder/RouteDslTests.kt delete mode 100644 spring-cloud-gateway-server/src/test/resources/1x1.png delete mode 100644 spring-cloud-gateway-server/src/test/resources/META-INF/services/reactor.blockhound.integration.BlockHoundIntegration delete mode 100644 spring-cloud-gateway-server/src/test/resources/application-client-auth-ssl.yml delete mode 100644 spring-cloud-gateway-server/src/test/resources/application-cors-global-config.yml delete mode 100644 spring-cloud-gateway-server/src/test/resources/application-cors-per-route-config.yml delete mode 100644 spring-cloud-gateway-server/src/test/resources/application-defaultid.yml delete mode 100644 spring-cloud-gateway-server/src/test/resources/application-disable-components.yml delete mode 100644 spring-cloud-gateway-server/src/test/resources/application-forwardstatic.yml delete mode 100644 spring-cloud-gateway-server/src/test/resources/application-logging.yml delete mode 100644 spring-cloud-gateway-server/src/test/resources/application-multi-cert-ssl.yml delete mode 100644 spring-cloud-gateway-server/src/test/resources/application-netty-routing-filter.yml delete mode 100644 spring-cloud-gateway-server/src/test/resources/application-redis-rate-limiter-config.yml delete mode 100644 spring-cloud-gateway-server/src/test/resources/application-redis-rate-limiter-default-config.yml delete mode 100644 spring-cloud-gateway-server/src/test/resources/application-redis-route-repository.yml delete mode 100644 spring-cloud-gateway-server/src/test/resources/application-remote-address.yml delete mode 100644 spring-cloud-gateway-server/src/test/resources/application-removenonproxyheaders.yml delete mode 100644 spring-cloud-gateway-server/src/test/resources/application-request-header-web-filter.yml delete mode 100644 spring-cloud-gateway-server/src/test/resources/application-request-headers-if-not-present-web-filter.yml delete mode 100644 spring-cloud-gateway-server/src/test/resources/application-request-map-header-web-filter.yml delete mode 100644 spring-cloud-gateway-server/src/test/resources/application-request-parameter-web-filter.yml delete mode 100644 spring-cloud-gateway-server/src/test/resources/application-retrytests.yml delete mode 100644 spring-cloud-gateway-server/src/test/resources/application-save-session-web-filter.yml delete mode 100644 spring-cloud-gateway-server/src/test/resources/application-single-cert-ssl.yml delete mode 100644 spring-cloud-gateway-server/src/test/resources/application-verification-route.yml delete mode 100644 spring-cloud-gateway-server/src/test/resources/application-weights-404.yml delete mode 100644 spring-cloud-gateway-server/src/test/resources/application-weights-concurrent.yml delete mode 100644 spring-cloud-gateway-server/src/test/resources/application.yml delete mode 100644 spring-cloud-gateway-server/src/test/resources/multi-cert.pem delete mode 100644 spring-cloud-gateway-server/src/test/resources/scg-keystore-with-different-key-password.jks delete mode 100644 spring-cloud-gateway-server/src/test/resources/scg-keystore.p12 delete mode 100644 spring-cloud-gateway-server/src/test/resources/scg-truststore.jks delete mode 100644 spring-cloud-gateway-server/src/test/resources/single-cert-for-different-key-password.pem delete mode 100644 spring-cloud-gateway-server/src/test/resources/single-cert.pem delete mode 100644 spring-cloud-gateway-server/src/test/resources/static/httpbin/docs.html delete mode 100644 spring-cloud-gateway-server/src/test/resources/transfer-encoding/invalid-request.bin delete mode 100644 spring-cloud-gateway-server/src/test/resources/transfer-encoding/valid-request.bin delete mode 100644 spring-cloud-gateway-webflux/pom.xml delete mode 100644 spring-cloud-gateway-webflux/src/main/java/org/springframework/cloud/gateway/webflux/ProxyExchange.java delete mode 100644 spring-cloud-gateway-webflux/src/main/java/org/springframework/cloud/gateway/webflux/config/ProxyExchangeArgumentResolver.java delete mode 100644 spring-cloud-gateway-webflux/src/main/java/org/springframework/cloud/gateway/webflux/config/ProxyProperties.java delete mode 100644 spring-cloud-gateway-webflux/src/main/java/org/springframework/cloud/gateway/webflux/config/ProxyResponseAutoConfiguration.java delete mode 100644 spring-cloud-gateway-webflux/src/main/resources/META-INF/spring.factories delete mode 100644 spring-cloud-gateway-webflux/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports delete mode 100644 spring-cloud-gateway-webflux/src/test/java/org/springframework/cloud/gateway/webflux/ProductionConfigurationTests.java delete mode 100644 spring-cloud-gateway-webflux/src/test/java/org/springframework/cloud/gateway/webflux/ReactiveTests.java delete mode 100644 spring-cloud-gateway-webflux/src/test/resources/application.properties delete mode 100644 spring-cloud-gateway-webflux/src/test/resources/static/test.html delete mode 100644 spring-cloud-starter-gateway-mvc/pom.xml delete mode 100644 spring-cloud-starter-gateway/pom.xml delete mode 100644 src/checkstyle/checkstyle-suppressions.xml create mode 100644 supplemental-ui/partials/nav-search.hbs create mode 100644 supplemental-ui/partials/search.hbs diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index 4db3a243..00000000 --- a/.editorconfig +++ /dev/null @@ -1,12 +0,0 @@ -root=true - -[*.java] -indent_style = tab -indent_size = 4 -continuation_indent_size = 8 - -[*.xml] -indent_style = tab -indent_size = 4 -continuation_indent_size = 8 - diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md deleted file mode 100644 index 6430ceef..00000000 --- a/.github/CONTRIBUTING.md +++ /dev/null @@ -1,44 +0,0 @@ - -# Contributing - -Spring Cloud is released under the non-restrictive Apache 2.0 license, -and follows a very standard Github development process, using Github -tracker for issues and merging pull requests into master. If you want -to contribute even something trivial please do not hesitate, but -follow the guidelines below. - -## Sign the Contributor License Agreement -Before we accept a non-trivial patch or pull request we will need you to sign the -[Contributor License Agreement](https://cla.pivotal.io/sign/spring). -Signing the contributor's agreement does not grant anyone commit rights to the main -repository, but it does mean that we can accept your contributions, and you will get an -author credit if we do. Active contributors might be asked to join the core team, and -given the ability to merge pull requests. - -## Code of Conduct -This project adheres to the Contributor Covenant [Code of conduct](https://github.com/spring-cloud/spring-cloud-build/blob/master/docs/src/main/asciidoc/code-of-conduct.adoc). By participating, you are expected to uphold this code. Please report -unacceptable behavior to spring-code-of-conduct@pivotal.io. - -## Code Conventions and Housekeeping -None of these is essential for a pull request, but they will all help. They can also be -added after the original pull request but before a merge. - -* Use the Spring Framework code format conventions. If you use Eclipse - you can import formatter settings using the - `eclipse-code-formatter.xml` file from the - [Spring Cloud Build](https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/master/spring-cloud-dependencies-parent/eclipse-code-formatter.xml) project. If using IntelliJ, you can use the - [Eclipse Code Formatter Plugin](https://plugins.jetbrains.com/plugin/6546) to import the same file. -* Make sure all new `.java` files to have a simple Javadoc class comment with at least an - `@author` tag identifying you, and preferably at least a paragraph on what the class is - for. -* Add the ASF license header comment to all new `.java` files (copy from existing files - in the project) -* Add yourself as an `@author` to the .java files that you modify substantially (more - than cosmetic changes). -* Add some Javadocs and, if you change the namespace, some XSD doc elements. -* A few unit tests would help a lot as well -- someone has to do it. -* If no-one else is using your branch, please rebase it against the current master (or - other target branch in the main project). -* When writing a commit message please follow [these conventions](https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html), - if you are fixing an existing issue please add `Fixes gh-XXXX` at the end of the commit - message (where XXXX is the issue number). \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index 0bc5ef4f..00000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,20 +0,0 @@ - diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index d3204a48..00000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -title: '' -labels: waiting-for-triage -assignees: '' - ---- - -**Describe the bug** -Please provide details of the problem, including the version of Spring Cloud that you -are using. - -**Sample** -If possible, please provide a test case or sample application that reproduces -the problem. This makes it much easier for us to diagnose the problem and to verify that -we have fixed it. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index b2e33c5f..00000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this project -title: '' -labels: waiting-for-triage -assignees: '' - ---- - -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] - -**Describe the solution you'd like** -A clear and concise description of what you want to happen. - -**Describe alternatives you've considered** -A clear and concise description of any alternative solutions or features you've considered. - -**Additional context** -Add any other context or screenshots about the feature request here. \ No newline at end of file diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml new file mode 100644 index 00000000..c96e16b6 --- /dev/null +++ b/.github/workflows/deploy-docs.yml @@ -0,0 +1,53 @@ +name: Deploy Docs +run-name: ${{ format('{0} ({1})', github.workflow, github.event.inputs.build-refname || 'all') }} +on: + workflow_dispatch: + inputs: + build-refname: + description: Enter git refname to build (e.g., 5.7.x). + required: false + push: + branches: docs-build +env: + GRADLE_ENTERPRISE_SECRET_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }} +permissions: + contents: write +jobs: + build: + if: github.repository_owner == 'spring-cloud' + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 5 + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + - name: Set up refname build + if: github.event.inputs.build-refname + run: | + git fetch --depth 1 https://github.com/$GITHUB_REPOSITORY ${{ github.event.inputs.build-refname }} + export BUILD_REFNAME=${{ github.event.inputs.build-refname }} + echo "BUILD_REFNAME=$BUILD_REFNAME" >> $GITHUB_ENV + export BUILD_VERSION=$(git cat-file --textconv FETCH_HEAD:pom.xml | python3 -c "import xml.etree.ElementTree as xml; from sys import stdin; print(xml.parse(stdin).getroot().find('{http://maven.apache.org/POM/4.0.0}version').text)") + echo BUILD_VERSION=$BUILD_VERSION >> $GITHUB_ENV + - name: Run Antora + run: | + ./mvnw --no-transfer-progress -B antora + - name: Publish Docs + uses: rwinch/spring-doc-actions/rsync-antora-reference@httpdocs-path + with: + docs-username: ${{ secrets.DOCS_USERNAME }} + docs-host: ${{ secrets.DOCS_HOST }} + docs-ssh-key: ${{ secrets.DOCS_SSH_KEY }} + docs-ssh-host-key: ${{ secrets.DOCS_SSH_HOST_KEY }} + site-path: target/antora/site + - name: Bust Cloudflare Cache + uses: spring-io/spring-doc-actions/bust-cloudflare-antora-cache@v0.0.11 + with: + context-root: spring-cloud-gateway + cloudflare-zone-id: ${{ secrets.CLOUDFLARE_ZONE_ID }} + cloudflare-cache-token: ${{ secrets.CLOUDFLARE_CACHE_TOKEN }} diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml deleted file mode 100644 index c269dc44..00000000 --- a/.github/workflows/maven.yml +++ /dev/null @@ -1,37 +0,0 @@ -# This workflow will build a Java project with Maven -# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven - -name: Build - -on: - push: - branches: [ main, 3.1.x ] - pull_request: - branches: [ main, 3.1.x ] - -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - name: Set up JDK - uses: actions/setup-java@v3 - with: - distribution: 'temurin' - java-version: '17' - - name: Cache local Maven repository - uses: actions/cache@v3 - with: - path: ~/.m2/repository - key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} - restore-keys: | - ${{ runner.os }}-maven- - - name: Build with Maven - run: ./mvnw clean install -B -U -Pspring -Dmaven.test.redirectTestOutputToFile=true -Dmaven.wagon.http.pool=false -Dmaven.wagon.httpconnectionManager.ttlSeconds=120 - - name: Publish Test Report - uses: mikepenz/action-junit-report@v2 - if: always() # always run even if the previous step fails - with: - report_paths: '**/surefire-reports/TEST-*.xml' diff --git a/.gitignore b/.gitignore index 62227c8c..6be8a4a1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,23 +1,22 @@ -.DS_Store -*~ -#* -*# -.#* -.classpath -.project -.settings/ -.springBeans target/ -bin/ -_site/ -.idea +.settings/ +.project +.classpath +*.orig +.springBeans +.factorypath +.sts4-cache +.ant-targets-build.xml +src/ant/.ant-targets-upload-dist.xml +*.sonar4clipse* +.DS_Store *.iml *.ipr *.iws -.factorypath -*.log -.shelf -*.swp -*.swo -.vscode/ -.flattened-pom.xml +/.idea/ +*.graphml +node +node_modules +build +package.json +package-lock.json diff --git a/.mvn/maven.config b/.mvn/maven.config deleted file mode 100644 index a6829905..00000000 --- a/.mvn/maven.config +++ /dev/null @@ -1 +0,0 @@ --P spring diff --git a/.mvn/wrapper/MavenWrapperDownloader.java b/.mvn/wrapper/MavenWrapperDownloader.java new file mode 100644 index 00000000..b901097f --- /dev/null +++ b/.mvn/wrapper/MavenWrapperDownloader.java @@ -0,0 +1,117 @@ +/* + * Copyright 2007-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import java.net.*; +import java.io.*; +import java.nio.channels.*; +import java.util.Properties; + +public class MavenWrapperDownloader { + + private static final String WRAPPER_VERSION = "0.5.6"; + /** + * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. + */ + private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" + + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; + + /** + * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to + * use instead of the default one. + */ + private static final String MAVEN_WRAPPER_PROPERTIES_PATH = + ".mvn/wrapper/maven-wrapper.properties"; + + /** + * Path where the maven-wrapper.jar will be saved to. + */ + private static final String MAVEN_WRAPPER_JAR_PATH = + ".mvn/wrapper/maven-wrapper.jar"; + + /** + * Name of the property which should be used to override the default download url for the wrapper. + */ + private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; + + public static void main(String args[]) { + System.out.println("- Downloader started"); + File baseDirectory = new File(args[0]); + System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); + + // If the maven-wrapper.properties exists, read it and check if it contains a custom + // wrapperUrl parameter. + File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); + String url = DEFAULT_DOWNLOAD_URL; + if(mavenWrapperPropertyFile.exists()) { + FileInputStream mavenWrapperPropertyFileInputStream = null; + try { + mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); + Properties mavenWrapperProperties = new Properties(); + mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); + url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); + } catch (IOException e) { + System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); + } finally { + try { + if(mavenWrapperPropertyFileInputStream != null) { + mavenWrapperPropertyFileInputStream.close(); + } + } catch (IOException e) { + // Ignore ... + } + } + } + System.out.println("- Downloading from: " + url); + + File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); + if(!outputFile.getParentFile().exists()) { + if(!outputFile.getParentFile().mkdirs()) { + System.out.println( + "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); + } + } + System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); + try { + downloadFileFromURL(url, outputFile); + System.out.println("Done"); + System.exit(0); + } catch (Throwable e) { + System.out.println("- Error downloading"); + e.printStackTrace(); + System.exit(1); + } + } + + private static void downloadFileFromURL(String urlString, File destination) throws Exception { + if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { + String username = System.getenv("MVNW_USERNAME"); + char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); + Authenticator.setDefault(new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(username, password); + } + }); + } + URL website = new URL(urlString); + ReadableByteChannel rbc; + rbc = Channels.newChannel(website.openStream()); + FileOutputStream fos = new FileOutputStream(destination); + fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); + fos.close(); + rbc.close(); + } + +} diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar index bf82ff01c6cdae4a1bb754a6e062954d77ac5c11..2cc7d4a55c0cd0092912bf49ae38b3a9e3fd0054 100644 GIT binary patch literal 50710 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 EfAhh=DF6Tf literal 59925 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

dy!05UMhSKsK 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 VCNKd3JGtOvEm(5t0lFyE{{i--k}m)N diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties index 6686a643..642d572c 100644 --- a/.mvn/wrapper/maven-wrapper.properties +++ b/.mvn/wrapper/maven-wrapper.properties @@ -1,18 +1,2 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you 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. -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.0/apache-maven-3.9.0-bin.zip -wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar diff --git a/.sdkmanrc b/.sdkmanrc deleted file mode 100644 index 415f9083..00000000 --- a/.sdkmanrc +++ /dev/null @@ -1,3 +0,0 @@ -# Enable auto-env through the sdkman_auto_env config -# Add key=value pairs of SDKs to use below -java=17.0.1-tem diff --git a/.settings.xml b/.settings.xml deleted file mode 100644 index 25c049f8..00000000 --- a/.settings.xml +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - - repo.spring.io - ${env.CI_DEPLOY_USERNAME} - ${env.CI_DEPLOY_PASSWORD} - - - - - - spring - true - - - spring-snapshots - Spring Snapshots - https://repo.spring.io/libs-snapshot-local - - true - - - - spring-milestones - Spring Milestones - https://repo.spring.io/libs-milestone-local - - false - - - - spring-releases - Spring Releases - https://repo.spring.io/release - - false - - - - - - spring-snapshots - Spring Snapshots - https://repo.spring.io/libs-snapshot-local - - true - - - - spring-milestones - Spring Milestones - https://repo.spring.io/libs-milestone-local - - false - - - - - - diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 20e4bd85..00000000 --- a/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - https://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - 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. diff --git a/LICENSE.txt b/LICENSE.txt deleted file mode 100644 index 62589edd..00000000 --- a/LICENSE.txt +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - https://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - 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. diff --git a/README.adoc b/README.adoc index 39d20fae..8e25a0ca 100644 --- a/README.adoc +++ b/README.adoc @@ -1,341 +1,23 @@ -//// -DO NOT EDIT THIS FILE. IT WAS GENERATED. -Manual changes to this file will be lost when it is generated again. -Edit the files in the src/main/asciidoc/ directory instead. -//// += Spring Cloud Gateway Docs Build +You're currently viewing the Antora playbook branch. +The playbook branch hosts the docs build that is used to build and publish the production docs site. -image::https://github.com/spring-cloud/spring-cloud-gateway/workflows/Build/badge.svg?style=svg["Actions Status", link="https://github.com/spring-cloud/spring-cloud-gateway/actions"] -image::https://codecov.io/gh/spring-cloud/spring-cloud-gateway/branch/main/graph/badge.svg["Codecov", link="https://codecov.io/gh/spring-cloud/spring-cloud-gateway/branch/main"] +The Spring Cloud Gateway reference docs are built using https://antora.org[Antora]. +This README covers how to build the docs in a software branch as well as how to build the production docs site locally. +== Building the Site -This project provides an API Gateway built on top of the Spring Ecosystem, including: Spring 6, Spring Boot 3 and Project Reactor. Spring Cloud Gateway aims to provide a simple, yet effective way to route to APIs and provide cross cutting concerns to them such as: security, monitoring/metrics, and resiliency. - - -== Features - -* Java 17 -* Spring Framework 6 -* Spring Boot 3 -* Dynamic routing -* Route matching built into Spring Handler Mapping -* Route matching on HTTP Request (Path, Method, Header, Host, etc...) -* Filters scoped to Matching Route -* Filters can modify downstream HTTP Request and HTTP Response (Add/Remove Headers, Add/Remove Parameters, Rewrite Path, Set Path, Hystrix, etc...) -* API or configuration driven -* Supports Spring Cloud `DiscoveryClient` for configuring Routes - -== Building - -:jdkversion: 17 - -=== Basic Compile and Test - -To build the source you will need to install JDK {jdkversion}. - -Spring Cloud uses Maven for most build-related activities, and you -should be able to get off the ground quite quickly by cloning the -project you are interested in and typing +You can build the entire site by invoking the following on the docs-build branch and then viewing the site at `target/site/index.html` +[source,bash] ---- -$ ./mvnw install +./mvnw antora ---- -NOTE: You can also install Maven (>=3.3.3) yourself and run the `mvn` command -in place of `./mvnw` in the examples below. If you do that you also -might need to add `-P spring` if your local Maven settings do not -contain repository declarations for spring pre-release artifacts. +== Building a Specific Branch -NOTE: Be aware that you might need to increase the amount of memory -available to Maven by setting a `MAVEN_OPTS` environment variable with -a value like `-Xmx512m -XX:MaxPermSize=128m`. We try to cover this in -the `.mvn` configuration, so if you find you have to do it to make a -build succeed, please raise a ticket to get the settings added to -source control. - -The projects that require middleware (i.e. Redis) for testing generally -require that a local instance of [Docker](https://www.docker.com/get-started) is installed and running. - - -=== Documentation - -The spring-cloud-build module has a "docs" profile, and if you switch -that on it will try to build asciidoc sources from -`src/main/asciidoc`. As part of that process it will look for a -`README.adoc` and process it by loading all the includes, but not -parsing or rendering it, just copying it to `${main.basedir}` -(defaults to `${basedir}`, i.e. the root of the project). If there are -any changes in the README it will then show up after a Maven build as -a modified file in the correct place. Just commit it and push the change. - -=== Working with the code -If you don't have an IDE preference we would recommend that you use -https://www.springsource.com/developer/sts[Spring Tools Suite] or -https://eclipse.org[Eclipse] when working with the code. We use the -https://eclipse.org/m2e/[m2eclipse] eclipse plugin for maven support. Other IDEs and tools -should also work without issue as long as they use Maven 3.3.3 or better. - -==== Activate the Spring Maven profile -Spring Cloud projects require the 'spring' Maven profile to be activated to resolve -the spring milestone and snapshot repositories. Use your preferred IDE to set this -profile to be active, or you may experience build errors. - -==== Importing into eclipse with m2eclipse -We recommend the https://eclipse.org/m2e/[m2eclipse] eclipse plugin when working with -eclipse. If you don't already have m2eclipse installed it is available from the "eclipse -marketplace". - -NOTE: Older versions of m2e do not support Maven 3.3, so once the -projects are imported into Eclipse you will also need to tell -m2eclipse to use the right profile for the projects. If you -see many different errors related to the POMs in the projects, check -that you have an up to date installation. If you can't upgrade m2e, -add the "spring" profile to your `settings.xml`. Alternatively you can -copy the repository settings from the "spring" profile of the parent -pom into your `settings.xml`. - -==== Importing into eclipse without m2eclipse -If you prefer not to use m2eclipse you can generate eclipse project metadata using the -following command: - -[indent=0] +[source,bash] ---- - $ ./mvnw eclipse:eclipse +./mvnw antora ---- - -The generated eclipse projects can be imported by selecting `import existing projects` -from the `file` menu. - - -== Contributing - -:spring-cloud-build-branch: master - -Spring Cloud is released under the non-restrictive Apache 2.0 license, -and follows a very standard Github development process, using Github -tracker for issues and merging pull requests into master. If you want -to contribute even something trivial please do not hesitate, but -follow the guidelines below. - -=== Sign the Contributor License Agreement -Before we accept a non-trivial patch or pull request we will need you to sign the -https://cla.pivotal.io/sign/spring[Contributor License Agreement]. -Signing the contributor's agreement does not grant anyone commit rights to the main -repository, but it does mean that we can accept your contributions, and you will get an -author credit if we do. Active contributors might be asked to join the core team, and -given the ability to merge pull requests. - -=== Code of Conduct -This project adheres to the Contributor Covenant https://github.com/spring-cloud/spring-cloud-build/blob/master/docs/src/main/asciidoc/code-of-conduct.adoc[code of -conduct]. By participating, you are expected to uphold this code. Please report -unacceptable behavior to spring-code-of-conduct@pivotal.io. - -=== Code Conventions and Housekeeping -None of these is essential for a pull request, but they will all help. They can also be -added after the original pull request but before a merge. - -* Use the Spring Framework code format conventions. If you use Eclipse - you can import formatter settings using the - `eclipse-code-formatter.xml` file from the - https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/master/spring-cloud-dependencies-parent/eclipse-code-formatter.xml[Spring - Cloud Build] project. If using IntelliJ, you can use the - https://plugins.jetbrains.com/plugin/6546[Eclipse Code Formatter - Plugin] to import the same file. -* Make sure all new `.java` files to have a simple Javadoc class comment with at least an - `@author` tag identifying you, and preferably at least a paragraph on what the class is - for. -* Add the ASF license header comment to all new `.java` files (copy from existing files - in the project) -* Add yourself as an `@author` to the .java files that you modify substantially (more - than cosmetic changes). -* Add some Javadocs and, if you change the namespace, some XSD doc elements. -* A few unit tests would help a lot as well -- someone has to do it. -* If no-one else is using your branch, please rebase it against the current master (or - other target branch in the main project). -* When writing a commit message please follow https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html[these conventions], - if you are fixing an existing issue please add `Fixes gh-XXXX` at the end of the commit - message (where XXXX is the issue number). - -=== Checkstyle - -Spring Cloud Build comes with a set of checkstyle rules. You can find them in the `spring-cloud-build-tools` module. The most notable files under the module are: - -.spring-cloud-build-tools/ ----- -└── src -    ├── checkstyle -    │   └── checkstyle-suppressions.xml <3> -    └── main -    └── resources -    ├── checkstyle-header.txt <2> -    └── checkstyle.xml <1> ----- -<1> Default Checkstyle rules -<2> File header setup -<3> Default suppression rules - -==== Checkstyle configuration - -Checkstyle rules are *disabled by default*. To add checkstyle to your project just define the following properties and plugins. - -.pom.xml ----- - -true <1> - true - <2> - true - <3> - - - - - <4> - io.spring.javaformat - spring-javaformat-maven-plugin - - <5> - org.apache.maven.plugins - maven-checkstyle-plugin - - - - - - <5> - org.apache.maven.plugins - maven-checkstyle-plugin - - - - ----- -<1> Fails the build upon Checkstyle errors -<2> Fails the build upon Checkstyle violations -<3> Checkstyle analyzes also the test sources -<4> Add the Spring Java Format plugin that will reformat your code to pass most of the Checkstyle formatting rules -<5> Add checkstyle plugin to your build and reporting phases - -If you need to suppress some rules (e.g. line length needs to be longer), then it's enough for you to define a file under `${project.root}/src/checkstyle/checkstyle-suppressions.xml` with your suppressions. Example: - -.projectRoot/src/checkstyle/checkstyle-suppresions.xml ----- - - - - - - ----- - -It's advisable to copy the `${spring-cloud-build.rootFolder}/.editorconfig` and `${spring-cloud-build.rootFolder}/.springformat` to your project. That way, some default formatting rules will be applied. You can do so by running this script: - -```bash -$ curl https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/master/.editorconfig -o .editorconfig -$ touch .springformat -``` - -=== IDE setup - -==== Intellij IDEA - -In order to setup Intellij you should import our coding conventions, inspection profiles and set up the checkstyle plugin. -The following files can be found in the https://github.com/spring-cloud/spring-cloud-build/tree/master/spring-cloud-build-tools[Spring Cloud Build] project. - -.spring-cloud-build-tools/ ----- -└── src -    ├── checkstyle -    │   └── checkstyle-suppressions.xml <3> -    └── main -    └── resources -    ├── checkstyle-header.txt <2> -    ├── checkstyle.xml <1> -    └── intellij -       ├── Intellij_Project_Defaults.xml <4> -       └── Intellij_Spring_Boot_Java_Conventions.xml <5> ----- -<1> Default Checkstyle rules -<2> File header setup -<3> Default suppression rules -<4> Project defaults for Intellij that apply most of Checkstyle rules -<5> Project style conventions for Intellij that apply most of Checkstyle rules - -.Code style - -image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/{spring-cloud-build-branch}/docs/src/main/asciidoc/images/intellij-code-style.png[Code style] - -Go to `File` -> `Settings` -> `Editor` -> `Code style`. There click on the icon next to the `Scheme` section. There, click on the `Import Scheme` value and pick the `Intellij IDEA code style XML` option. Import the `spring-cloud-build-tools/src/main/resources/intellij/Intellij_Spring_Boot_Java_Conventions.xml` file. - -.Inspection profiles - -image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/{spring-cloud-build-branch}/docs/src/main/asciidoc/images/intellij-inspections.png[Code style] - -Go to `File` -> `Settings` -> `Editor` -> `Inspections`. There click on the icon next to the `Profile` section. There, click on the `Import Profile` and import the `spring-cloud-build-tools/src/main/resources/intellij/Intellij_Project_Defaults.xml` file. - -.Checkstyle - -To have Intellij work with Checkstyle, you have to install the `Checkstyle` plugin. It's advisable to also install the `Assertions2Assertj` to automatically convert the JUnit assertions - -image::https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/{spring-cloud-build-branch}/docs/src/main/asciidoc/images/intellij-checkstyle.png[Checkstyle] - -Go to `File` -> `Settings` -> `Other settings` -> `Checkstyle`. There click on the `+` icon in the `Configuration file` section. There, you'll have to define where the checkstyle rules should be picked from. In the image above, we've picked the rules from the cloned Spring Cloud Build repository. However, you can point to the Spring Cloud Build's GitHub repository (e.g. for the `checkstyle.xml` : `https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/master/spring-cloud-build-tools/src/main/resources/checkstyle.xml`). We need to provide the following variables: - -- `checkstyle.header.file` - please point it to the Spring Cloud Build's, `spring-cloud-build-tools/src/main/resources/checkstyle-header.txt` file either in your cloned repo or via the `https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/master/spring-cloud-build-tools/src/main/resources/checkstyle-header.txt` URL. -- `checkstyle.suppressions.file` - default suppressions. Please point it to the Spring Cloud Build's, `spring-cloud-build-tools/src/checkstyle/checkstyle-suppressions.xml` file either in your cloned repo or via the `https://raw.githubusercontent.com/spring-cloud/spring-cloud-build/master/spring-cloud-build-tools/src/checkstyle/checkstyle-suppressions.xml` URL. -- `checkstyle.additional.suppressions.file` - this variable corresponds to suppressions in your local project. E.g. you're working on `spring-cloud-contract`. Then point to the `project-root/src/checkstyle/checkstyle-suppressions.xml` folder. Example for `spring-cloud-contract` would be: `/home/username/spring-cloud-contract/src/checkstyle/checkstyle-suppressions.xml`. - -IMPORTANT: Remember to set the `Scan Scope` to `All sources` since we apply checkstyle rules for production and test sources. - -=== Duplicate Finder - -Spring Cloud Build brings along the `basepom:duplicate-finder-maven-plugin`, that enables flagging duplicate and conflicting classes and resources on the java classpath. - -==== Duplicate Finder configuration - -Duplicate finder is *enabled by default* and will run in the `verify` phase of your Maven build, but it will only take effect in your project if you add the `duplicate-finder-maven-plugin` to the `build` section of the projecst's `pom.xml`. - -.pom.xml -[source,xml] ----- - - - - org.basepom.maven - duplicate-finder-maven-plugin - - - ----- - -For other properties, we have set defaults as listed in the https://github.com/basepom/duplicate-finder-maven-plugin/wiki[plugin documentation]. - -You can easily override them but setting the value of the selected property prefixed with `duplicate-finder-maven-plugin`. For example, set `duplicate-finder-maven-plugin.skip` to `true` in order to skip duplicates check in your build. - -If you need to add `ignoredClassPatterns` or `ignoredResourcePatterns` to your setup, make sure to add them in the plugin configuration section of your project: - -[source,xml] ----- - - - - org.basepom.maven - duplicate-finder-maven-plugin - - - org.joda.time.base.BaseDateTime - .*module-info - - - changelog.txt - - - - - - - ----- - diff --git a/SECURITY.md b/SECURITY.md deleted file mode 100644 index 04685956..00000000 --- a/SECURITY.md +++ /dev/null @@ -1,5 +0,0 @@ -# Security Policy - -## Reporting a Vulnerability - -To report security vulnerabilities, please go to https://pivotal.io/security. diff --git a/antora-playbook.yml b/antora-playbook.yml new file mode 100644 index 00000000..7cc4071c --- /dev/null +++ b/antora-playbook.yml @@ -0,0 +1,44 @@ +antora: + extensions: + - '@springio/antora-extensions/partial-build-extension' + - require: '@springio/antora-extensions/latest-version-extension' + - require: '@springio/antora-extensions/inject-collector-cache-config-extension' + - '@antora/collector-extension' + - '@antora/atlas-extension' + - require: '@springio/antora-extensions/root-component-extension' + root_component_name: 'cloud-gateway' +site: + title: Spring Cloud Gateway + url: https://docs.spring.io/spring-cloud-gateway/reference + robots: allow +git: + ensure_git_suffix: false +content: + sources: + - url: https://github.com/spring-cloud/spring-cloud-gateway + # Refname matching: + # https://docs.antora.org/antora/latest/playbook/content-refname-matching/ + branches: [ main ] + tags: [ '({4..9}).+({1..9}).+({0..9})?(-{RC,M}+({0..9}))', '!4.1.0-M1' ] + start_path: docs +asciidoc: + attributes: + page-stackoverflow-url: https://stackoverflow.com/tags/spring-cloud + page-pagination: '' + hide-uri-scheme: '@' + tabs-sync-option: '@' + extensions: + - '@asciidoctor/tabs' + - '@springio/asciidoctor-extensions' +urls: + latest_version_segment_strategy: redirect:to + latest_version_segment: '' + redirect_facility: httpd +ui: + bundle: + url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.3.5/ui-bundle.zip + snapshot: true +runtime: + log: + failure_level: warn + format: pretty diff --git a/docs/src/main/asciidoc/README.adoc b/docs/modules/ROOT/pages/README.adoc similarity index 100% rename from docs/src/main/asciidoc/README.adoc rename to docs/modules/ROOT/pages/README.adoc diff --git a/docs/src/main/asciidoc/_attributes.adoc b/docs/modules/ROOT/pages/_attributes.adoc similarity index 100% rename from docs/src/main/asciidoc/_attributes.adoc rename to docs/modules/ROOT/pages/_attributes.adoc diff --git a/docs/src/main/asciidoc/_configprops.adoc b/docs/modules/ROOT/pages/_configprops.adoc similarity index 100% rename from docs/src/main/asciidoc/_configprops.adoc rename to docs/modules/ROOT/pages/_configprops.adoc diff --git a/docs/src/main/asciidoc/appendix.adoc b/docs/modules/ROOT/pages/appendix.adoc similarity index 100% rename from docs/src/main/asciidoc/appendix.adoc rename to docs/modules/ROOT/pages/appendix.adoc diff --git a/docs/src/main/asciidoc/index.adoc b/docs/modules/ROOT/pages/index.adoc similarity index 100% rename from docs/src/main/asciidoc/index.adoc rename to docs/modules/ROOT/pages/index.adoc diff --git a/docs/src/main/asciidoc/intro.adoc b/docs/modules/ROOT/pages/intro.adoc similarity index 100% rename from docs/src/main/asciidoc/intro.adoc rename to docs/modules/ROOT/pages/intro.adoc diff --git a/docs/src/main/asciidoc/sagan-boot.adoc b/docs/modules/ROOT/pages/sagan-boot.adoc similarity index 100% rename from docs/src/main/asciidoc/sagan-boot.adoc rename to docs/modules/ROOT/pages/sagan-boot.adoc diff --git a/docs/src/main/asciidoc/sagan-index.adoc b/docs/modules/ROOT/pages/sagan-index.adoc similarity index 100% rename from docs/src/main/asciidoc/sagan-index.adoc rename to docs/modules/ROOT/pages/sagan-index.adoc diff --git a/docs/src/main/asciidoc/spring-cloud-gateway.adoc b/docs/modules/ROOT/pages/spring-cloud-gateway.adoc similarity index 100% rename from docs/src/main/asciidoc/spring-cloud-gateway.adoc rename to docs/modules/ROOT/pages/spring-cloud-gateway.adoc diff --git a/docs/pom.xml b/docs/pom.xml deleted file mode 100644 index aefdaea7..00000000 --- a/docs/pom.xml +++ /dev/null @@ -1,100 +0,0 @@ - - - 4.0.0 - - org.springframework.cloud - spring-cloud-gateway - 4.1.0-SNAPSHOT - - spring-cloud-gateway-docs - jar - Spring Cloud Gateway Docs - Spring Cloud Docs - - spring-cloud-gateway - ${basedir}/.. - spring.cloud.gateway.* - deploy - - none - - - 1.0.0 - ${maven.multiModuleProjectDirectory}/spring-cloud-gateway-server/ - .* - ${maven.multiModuleProjectDirectory}/docs/target/observability/ - - - - ${project.groupId} - spring-cloud-starter-gateway - - - - src/main/asciidoc - - - - docs - - - - pl.project13.maven - git-commit-id-plugin - - - org.codehaus.mojo - exec-maven-plugin - - - generate-docs - prepare-package - - java - - - io.micrometer.docs.DocsGeneratorCommand - true - - ${micrometer-docs-generator.inputPath} - ${micrometer-docs-generator.inclusionPattern} - ${micrometer-docs-generator.outputPath} - - - - - - - io.micrometer - micrometer-docs-generator - ${micrometer-docs-generator.version} - jar - - - - - org.apache.maven.plugins - maven-dependency-plugin - - - org.apache.maven.plugins - maven-resources-plugin - - - org.asciidoctor - asciidoctor-maven-plugin - - - org.apache.maven.plugins - maven-antrun-plugin - - - maven-deploy-plugin - - - - - - diff --git a/docs/src/main/asciidoc/gateway-grafana-dashboard.json b/docs/src/main/asciidoc/gateway-grafana-dashboard.json deleted file mode 100644 index ea479bdb..00000000 --- a/docs/src/main/asciidoc/gateway-grafana-dashboard.json +++ /dev/null @@ -1,2068 +0,0 @@ -{ - "__inputs": [ - { - "name": "DS_PROMETHEUS", - "label": "prometheus", - "description": "", - "type": "datasource", - "pluginId": "prometheus", - "pluginName": "Prometheus" - } - ], - "__requires": [ - { - "type": "grafana", - "id": "grafana", - "name": "Grafana", - "version": "5.0.0" - }, - { - "type": "panel", - "id": "graph", - "name": "Graph", - "version": "5.0.0" - }, - { - "type": "datasource", - "id": "prometheus", - "name": "Prometheus", - "version": "5.0.0" - } - ], - "annotations": { - "list": [ - { - "builtIn": 1, - "datasource": "-- Grafana --", - "enable": true, - "hide": true, - "iconColor": "rgba(0, 211, 255, 1)", - "name": "Annotations & Alerts", - "type": "dashboard" - } - ] - }, - "editable": true, - "gnetId": null, - "graphTooltip": 0, - "id": null, - "iteration": 1550777954006, - "links": [], - "panels": [ - { - "collapsed": true, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 0 - }, - "id": 50, - "panels": [ - { - "cacheTimeout": null, - "colorBackground": false, - "colorValue": false, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(191, 148, 112, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "datasource": "${DS_PROMETHEUS}", - "description": "", - "format": "none", - "gauge": { - "maxValue": 100, - "minValue": 0, - "show": false, - "thresholdLabels": false, - "thresholdMarkers": true - }, - "gridPos": { - "h": 3, - "w": 8, - "x": 0, - "y": 2 - }, - "hideTimeOverride": false, - "id": 28, - "interval": null, - "links": [], - "mappingType": 1, - "mappingTypes": [ - { - "name": "value to text", - "value": 1 - }, - { - "name": "range to text", - "value": 2 - } - ], - "maxDataPoints": 100, - "nullPointMode": "connected", - "nullText": null, - "postfix": "", - "postfixFontSize": "50%", - "prefix": "", - "prefixFontSize": "50%", - "rangeMaps": [ - { - "from": "null", - "text": "N/A", - "to": "null" - } - ], - "sparkline": { - "fillColor": "rgba(31, 118, 189, 0.18)", - "full": false, - "lineColor": "rgb(31, 120, 193)", - "show": false - }, - "tableColumn": "Value", - "targets": [ - { - "expr": "sum(spring_cloud_gateway_requests_seconds_count{job=~\"$gatewayService\"})", - "format": "time_series", - "interval": "", - "intervalFactor": 2, - "legendFormat": "", - "metric": "spring_cloud_gateway_requests_seconds_count", - "refId": "A", - "step": 1200 - } - ], - "thresholds": "", - "timeFrom": null, - "timeShift": null, - "title": "Total Requests Served", - "type": "singlestat", - "valueFontSize": "80%", - "valueMaps": [ - { - "op": "=", - "text": "N/A", - "value": "null" - } - ], - "valueName": "current" - }, - { - "cacheTimeout": null, - "colorBackground": false, - "colorValue": false, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(191, 148, 112, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "datasource": "${DS_PROMETHEUS}", - "description": "", - "format": "none", - "gauge": { - "maxValue": 100, - "minValue": 0, - "show": false, - "thresholdLabels": false, - "thresholdMarkers": true - }, - "gridPos": { - "h": 3, - "w": 8, - "x": 8, - "y": 2 - }, - "hideTimeOverride": false, - "id": 29, - "interval": null, - "links": [], - "mappingType": 1, - "mappingTypes": [ - { - "name": "value to text", - "value": 1 - }, - { - "name": "range to text", - "value": 2 - } - ], - "maxDataPoints": 100, - "nullPointMode": "connected", - "nullText": null, - "postfix": "", - "postfixFontSize": "50%", - "prefix": "", - "prefixFontSize": "50%", - "rangeMaps": [ - { - "from": "null", - "text": "N/A", - "to": "null" - } - ], - "sparkline": { - "fillColor": "rgba(31, 118, 189, 0.18)", - "full": false, - "lineColor": "rgb(31, 120, 193)", - "show": false - }, - "tableColumn": "Value", - "targets": [ - { - "expr": "sum(spring_cloud_gateway_requests_seconds_count{outcome=\"SUCCESSFUL\", job=~\"$gatewayService\"})", - "format": "time_series", - "interval": "", - "intervalFactor": 2, - "legendFormat": "", - "metric": "spring_cloud_gateway_requests_seconds_count", - "refId": "A", - "step": 1200 - } - ], - "thresholds": "", - "timeFrom": null, - "timeShift": null, - "title": "Total Successful Requests Served", - "type": "singlestat", - "valueFontSize": "80%", - "valueMaps": [ - { - "op": "=", - "text": "N/A", - "value": "null" - } - ], - "valueName": "current" - }, - { - "cacheTimeout": null, - "colorBackground": false, - "colorValue": false, - "colors": [ - "rgba(245, 54, 54, 0.9)", - "rgba(191, 148, 112, 0.89)", - "rgba(50, 172, 45, 0.97)" - ], - "datasource": "${DS_PROMETHEUS}", - "description": "", - "format": "none", - "gauge": { - "maxValue": 100, - "minValue": 0, - "show": false, - "thresholdLabels": false, - "thresholdMarkers": true - }, - "gridPos": { - "h": 3, - "w": 8, - "x": 16, - "y": 2 - }, - "hideTimeOverride": false, - "id": 30, - "interval": null, - "links": [], - "mappingType": 1, - "mappingTypes": [ - { - "name": "value to text", - "value": 1 - }, - { - "name": "range to text", - "value": 2 - } - ], - "maxDataPoints": 100, - "nullPointMode": "connected", - "nullText": null, - "postfix": "", - "postfixFontSize": "50%", - "prefix": "", - "prefixFontSize": "50%", - "rangeMaps": [ - { - "from": "null", - "text": "N/A", - "to": "null" - } - ], - "sparkline": { - "fillColor": "rgba(31, 118, 189, 0.18)", - "full": false, - "lineColor": "rgb(31, 120, 193)", - "show": false - }, - "tableColumn": "Value", - "targets": [ - { - "expr": "sum(spring_cloud_gateway_requests_seconds_count{outcome!=\"SUCCESSFUL\", job=~\"$gatewayService\"})", - "format": "time_series", - "interval": "", - "intervalFactor": 2, - "legendFormat": "", - "metric": "spring_cloud_gateway_requests_seconds_count", - "refId": "A", - "step": 1200 - } - ], - "thresholds": "", - "timeFrom": null, - "timeShift": null, - "title": "Total Unsuccessful Requests Served", - "type": "singlestat", - "valueFontSize": "80%", - "valueMaps": [ - { - "op": "=", - "text": "N/A", - "value": "null" - } - ], - "valueName": "current" - } - ], - "repeat": null, - "title": "Total Throughput", - "type": "row" - }, - { - "collapsed": true, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 1 - }, - "id": 51, - "panels": [ - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "${DS_PROMETHEUS}", - "fill": 1, - "gridPos": { - "h": 7, - "w": 6, - "x": 0, - "y": 3 - }, - "id": 49, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": false, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "repeat": "routeId", - "scopedVars": { - "routeId": { - "selected": false, - "text": "ms-test", - "value": "ms-test" - } - }, - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "spring_cloud_gateway_requests_seconds_sum{routeId=~\"$routeId\", job=~\"$gatewayService\"}/spring_cloud_gateway_requests_seconds_count{routeId=~\"$routeId\", job=~\"$gatewayService\"}", - "format": "time_series", - "interval": "", - "intervalFactor": 2, - "legendFormat": "{{ status }}", - "metric": "gateway_api_time_seconds_sum", - "refId": "A", - "step": 240 - } - ], - "thresholds": [], - "timeFrom": null, - "timeShift": null, - "title": "$routeId", - "tooltip": { - "shared": false, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "s", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ] - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "${DS_PROMETHEUS}", - "fill": 1, - "gridPos": { - "h": 7, - "w": 6, - "x": 6, - "y": 3 - }, - "id": 70, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": false, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "repeat": null, - "repeatIteration": 1550777954005, - "repeatPanelId": 49, - "scopedVars": { - "routeId": { - "selected": false, - "text": "msnext", - "value": "msnext" - } - }, - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "spring_cloud_gateway_requests_seconds_sum{routeId=~\"$routeId\", job=~\"$gatewayService\"}/spring_cloud_gateway_requests_seconds_count{routeId=~\"$routeId\", job=~\"$gatewayService\"}", - "format": "time_series", - "interval": "", - "intervalFactor": 2, - "legendFormat": "{{ status }}", - "metric": "gateway_api_time_seconds_sum", - "refId": "A", - "step": 240 - } - ], - "thresholds": [], - "timeFrom": null, - "timeShift": null, - "title": "$routeId", - "tooltip": { - "shared": false, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "s", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ] - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "${DS_PROMETHEUS}", - "fill": 1, - "gridPos": { - "h": 7, - "w": 6, - "x": 12, - "y": 3 - }, - "id": 71, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": false, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "repeat": null, - "repeatIteration": 1550777954005, - "repeatPanelId": 49, - "scopedVars": { - "routeId": { - "selected": false, - "text": "msnext-a3s", - "value": "msnext-a3s" - } - }, - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "spring_cloud_gateway_requests_seconds_sum{routeId=~\"$routeId\", job=~\"$gatewayService\"}/spring_cloud_gateway_requests_seconds_count{routeId=~\"$routeId\", job=~\"$gatewayService\"}", - "format": "time_series", - "interval": "", - "intervalFactor": 2, - "legendFormat": "{{ status }}", - "metric": "gateway_api_time_seconds_sum", - "refId": "A", - "step": 240 - } - ], - "thresholds": [], - "timeFrom": null, - "timeShift": null, - "title": "$routeId", - "tooltip": { - "shared": false, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "s", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ] - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "${DS_PROMETHEUS}", - "fill": 1, - "gridPos": { - "h": 7, - "w": 6, - "x": 18, - "y": 3 - }, - "id": 72, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": false, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "repeat": null, - "repeatIteration": 1550777954005, - "repeatPanelId": 49, - "scopedVars": { - "routeId": { - "selected": false, - "text": "msnext-sample", - "value": "msnext-sample" - } - }, - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "spring_cloud_gateway_requests_seconds_sum{routeId=~\"$routeId\", job=~\"$gatewayService\"}/spring_cloud_gateway_requests_seconds_count{routeId=~\"$routeId\", job=~\"$gatewayService\"}", - "format": "time_series", - "interval": "", - "intervalFactor": 2, - "legendFormat": "{{ status }}", - "metric": "gateway_api_time_seconds_sum", - "refId": "A", - "step": 240 - } - ], - "thresholds": [], - "timeFrom": null, - "timeShift": null, - "title": "$routeId", - "tooltip": { - "shared": false, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "s", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ] - } - ], - "repeat": null, - "title": "Request Processing Time", - "type": "row" - }, - { - "collapsed": true, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 2 - }, - "id": 52, - "panels": [ - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "${DS_PROMETHEUS}", - "description": "", - "fill": 1, - "gridPos": { - "h": 7, - "w": 6, - "x": 0, - "y": 4 - }, - "id": 2, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": false, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [], - "minSpan": null, - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "repeat": "routeId", - "scopedVars": { - "routeId": { - "selected": false, - "text": "ms-test", - "value": "ms-test" - } - }, - "seriesOverrides": [], - "spaceLength": 10, - "stack": true, - "steppedLine": false, - "targets": [ - { - "expr": "spring_cloud_gateway_requests_seconds_count{outcome=\"SUCCESSFUL\", routeId=~\"$routeId\", job=~\"$gatewayService\"}", - "format": "time_series", - "hide": false, - "interval": "", - "intervalFactor": 2, - "legendFormat": "{{ job }} / {{ routeUri }}", - "metric": "spring_cloud_gateway_requests_seconds_count", - "refId": "A", - "step": 240 - } - ], - "thresholds": [], - "timeFrom": null, - "timeShift": null, - "title": "$routeId Success API Gateway Calls", - "tooltip": { - "shared": false, - "sort": 0, - "value_type": "cumulative" - }, - "transparent": false, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ] - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "${DS_PROMETHEUS}", - "description": "", - "fill": 1, - "gridPos": { - "h": 7, - "w": 6, - "x": 6, - "y": 4 - }, - "id": 73, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": false, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [], - "minSpan": null, - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "repeat": null, - "repeatIteration": 1550777954005, - "repeatPanelId": 2, - "scopedVars": { - "routeId": { - "selected": false, - "text": "msnext", - "value": "msnext" - } - }, - "seriesOverrides": [], - "spaceLength": 10, - "stack": true, - "steppedLine": false, - "targets": [ - { - "expr": "spring_cloud_gateway_requests_seconds_count{outcome=\"SUCCESSFUL\", routeId=~\"$routeId\", job=~\"$gatewayService\"}", - "format": "time_series", - "hide": false, - "interval": "", - "intervalFactor": 2, - "legendFormat": "{{ job }} / {{ routeUri }}", - "metric": "spring_cloud_gateway_requests_seconds_count", - "refId": "A", - "step": 240 - } - ], - "thresholds": [], - "timeFrom": null, - "timeShift": null, - "title": "$routeId Success API Gateway Calls", - "tooltip": { - "shared": false, - "sort": 0, - "value_type": "cumulative" - }, - "transparent": false, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ] - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "${DS_PROMETHEUS}", - "description": "", - "fill": 1, - "gridPos": { - "h": 7, - "w": 6, - "x": 12, - "y": 4 - }, - "id": 74, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": false, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [], - "minSpan": null, - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "repeat": null, - "repeatIteration": 1550777954005, - "repeatPanelId": 2, - "scopedVars": { - "routeId": { - "selected": false, - "text": "msnext-a3s", - "value": "msnext-a3s" - } - }, - "seriesOverrides": [], - "spaceLength": 10, - "stack": true, - "steppedLine": false, - "targets": [ - { - "expr": "spring_cloud_gateway_requests_seconds_count{outcome=\"SUCCESSFUL\", routeId=~\"$routeId\", job=~\"$gatewayService\"}", - "format": "time_series", - "hide": false, - "interval": "", - "intervalFactor": 2, - "legendFormat": "{{ job }} / {{ routeUri }}", - "metric": "spring_cloud_gateway_requests_seconds_count", - "refId": "A", - "step": 240 - } - ], - "thresholds": [], - "timeFrom": null, - "timeShift": null, - "title": "$routeId Success API Gateway Calls", - "tooltip": { - "shared": false, - "sort": 0, - "value_type": "cumulative" - }, - "transparent": false, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ] - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "${DS_PROMETHEUS}", - "description": "", - "fill": 1, - "gridPos": { - "h": 7, - "w": 6, - "x": 18, - "y": 4 - }, - "id": 75, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": false, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [], - "minSpan": null, - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "repeat": null, - "repeatIteration": 1550777954005, - "repeatPanelId": 2, - "scopedVars": { - "routeId": { - "selected": false, - "text": "msnext-sample", - "value": "msnext-sample" - } - }, - "seriesOverrides": [], - "spaceLength": 10, - "stack": true, - "steppedLine": false, - "targets": [ - { - "expr": "spring_cloud_gateway_requests_seconds_count{outcome=\"SUCCESSFUL\", routeId=~\"$routeId\", job=~\"$gatewayService\"}", - "format": "time_series", - "hide": false, - "interval": "", - "intervalFactor": 2, - "legendFormat": "{{ job }} / {{ routeUri }}", - "metric": "spring_cloud_gateway_requests_seconds_count", - "refId": "A", - "step": 240 - } - ], - "thresholds": [], - "timeFrom": null, - "timeShift": null, - "title": "$routeId Success API Gateway Calls", - "tooltip": { - "shared": false, - "sort": 0, - "value_type": "cumulative" - }, - "transparent": false, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ] - } - ], - "repeat": null, - "title": "Successful API Calls", - "type": "row" - }, - { - "collapsed": true, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 3 - }, - "id": 53, - "panels": [ - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "${DS_PROMETHEUS}", - "description": "", - "fill": 1, - "gridPos": { - "h": 7, - "w": 8, - "x": 0, - "y": 14 - }, - "id": 3, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": false, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "repeat": "routeId", - "seriesOverrides": [], - "spaceLength": 10, - "stack": true, - "steppedLine": false, - "targets": [ - { - "expr": "spring_cloud_gateway_requests_seconds_count{outcome!=\"SUCCESSFUL\", routeId=~\"$routeId\", job=~\"$gatewayService\"}", - "format": "time_series", - "interval": "", - "intervalFactor": 2, - "legendFormat": "{{ job }} / {{ routeUri }}", - "metric": "spring_cloud_gateway_requests_seconds_count", - "refId": "A", - "step": 240 - } - ], - "thresholds": [], - "timeFrom": null, - "timeShift": null, - "title": "$routeId Unsuccessful GW Calls", - "tooltip": { - "shared": false, - "sort": 0, - "value_type": "cumulative" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ] - } - ], - "repeat": null, - "title": "Unsuccessful API Calls", - "type": "row" - }, - { - "collapsed": true, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 4 - }, - "id": 54, - "panels": [ - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "${DS_PROMETHEUS}", - "description": "", - "fill": 1, - "gridPos": { - "h": 7, - "w": 6, - "x": 0, - "y": 15 - }, - "id": 35, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": false, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "repeat": "routeId", - "scopedVars": { - "routeId": { - "selected": false, - "text": "ms-test", - "value": "ms-test" - } - }, - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "sum(spring_cloud_gateway_requests_seconds_count{routeId=~\"$routeId\", job=~\"$gatewayService\"}) by (job, status)", - "format": "time_series", - "interval": "", - "intervalFactor": 2, - "legendFormat": "{{ job }} / {{ status }}", - "metric": "spring_cloud_gateway_requests_seconds_count", - "refId": "A", - "step": 240 - } - ], - "thresholds": [], - "timeFrom": null, - "timeShift": null, - "title": "$routeId", - "tooltip": { - "shared": false, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ] - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "${DS_PROMETHEUS}", - "description": "", - "fill": 1, - "gridPos": { - "h": 7, - "w": 6, - "x": 6, - "y": 15 - }, - "id": 58, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": false, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "repeat": null, - "repeatIteration": 1550775166360, - "repeatPanelId": 35, - "scopedVars": { - "routeId": { - "selected": false, - "text": "msnext", - "value": "msnext" - } - }, - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "sum(spring_cloud_gateway_requests_seconds_count{routeId=~\"$routeId\", job=~\"$gatewayService\"}) by (job, status)", - "format": "time_series", - "interval": "", - "intervalFactor": 2, - "legendFormat": "{{ job }} / {{ status }}", - "metric": "spring_cloud_gateway_requests_seconds_count", - "refId": "A", - "step": 240 - } - ], - "thresholds": [], - "timeFrom": null, - "timeShift": null, - "title": "$routeId", - "tooltip": { - "shared": false, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ] - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "${DS_PROMETHEUS}", - "description": "", - "fill": 1, - "gridPos": { - "h": 7, - "w": 6, - "x": 12, - "y": 15 - }, - "id": 59, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": false, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "repeat": null, - "repeatIteration": 1550775166360, - "repeatPanelId": 35, - "scopedVars": { - "routeId": { - "selected": false, - "text": "msnext-a3s", - "value": "msnext-a3s" - } - }, - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "sum(spring_cloud_gateway_requests_seconds_count{routeId=~\"$routeId\", job=~\"$gatewayService\"}) by (job, status)", - "format": "time_series", - "interval": "", - "intervalFactor": 2, - "legendFormat": "{{ job }} / {{ status }}", - "metric": "spring_cloud_gateway_requests_seconds_count", - "refId": "A", - "step": 240 - } - ], - "thresholds": [], - "timeFrom": null, - "timeShift": null, - "title": "$routeId", - "tooltip": { - "shared": false, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ] - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "${DS_PROMETHEUS}", - "description": "", - "fill": 1, - "gridPos": { - "h": 7, - "w": 6, - "x": 18, - "y": 15 - }, - "id": 60, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": false, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "repeat": null, - "repeatIteration": 1550775166360, - "repeatPanelId": 35, - "scopedVars": { - "routeId": { - "selected": false, - "text": "msnext-sample", - "value": "msnext-sample" - } - }, - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "sum(spring_cloud_gateway_requests_seconds_count{routeId=~\"$routeId\", job=~\"$gatewayService\"}) by (job, status)", - "format": "time_series", - "interval": "", - "intervalFactor": 2, - "legendFormat": "{{ job }} / {{ status }}", - "metric": "spring_cloud_gateway_requests_seconds_count", - "refId": "A", - "step": 240 - } - ], - "thresholds": [], - "timeFrom": null, - "timeShift": null, - "title": "$routeId", - "tooltip": { - "shared": false, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ] - } - ], - "repeat": null, - "title": "API / HTTP Status", - "type": "row" - }, - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 5 - }, - "id": 64, - "panels": [], - "title": "JVM", - "type": "row" - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "${DS_PROMETHEUS}", - "fill": 1, - "gridPos": { - "h": 9, - "w": 6, - "x": 0, - "y": 6 - }, - "id": 62, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": false, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "process_cpu_usage{job=~\"$gatewayService\"}", - "format": "time_series", - "hide": false, - "interval": "", - "intervalFactor": 1, - "legendFormat": "{{ instance }}", - "refId": "A" - } - ], - "thresholds": [], - "timeFrom": null, - "timeShift": null, - "title": "CPU Usage", - "tooltip": { - "shared": false, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ] - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "${DS_PROMETHEUS}", - "fill": 1, - "gridPos": { - "h": 9, - "w": 6, - "x": 6, - "y": 6 - }, - "id": 69, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": false, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "sum(jvm_memory_used_bytes{job=~\"$gatewayService\"}) by (job, instance)", - "format": "time_series", - "hide": false, - "interval": "", - "intervalFactor": 1, - "legendFormat": "{{ instance }}", - "refId": "A" - } - ], - "thresholds": [], - "timeFrom": null, - "timeShift": null, - "title": "Memory Used", - "tooltip": { - "shared": false, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ] - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "${DS_PROMETHEUS}", - "fill": 1, - "gridPos": { - "h": 9, - "w": 6, - "x": 12, - "y": 6 - }, - "id": 77, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": false, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "jvm_threads_live_threads{job=~\"$gatewayService\"}", - "format": "time_series", - "hide": false, - "intervalFactor": 1, - "legendFormat": "{{ instance }}", - "refId": "A" - } - ], - "thresholds": [], - "timeFrom": null, - "timeShift": null, - "title": "Live Threads", - "tooltip": { - "shared": false, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ] - }, - { - "alert": { - "conditions": [ - { - "evaluator": { - "params": [ - 0.3 - ], - "type": "gt" - }, - "operator": { - "type": "and" - }, - "query": { - "params": [ - "A", - "5m", - "now" - ] - }, - "reducer": { - "params": [], - "type": "avg" - }, - "type": "query" - } - ], - "executionErrorState": "alerting", - "frequency": "60s", - "handler": 1, - "name": "Memory Saturation alert", - "noDataState": "no_data", - "notifications": [] - }, - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "${DS_PROMETHEUS}", - "fill": 1, - "gridPos": { - "h": 9, - "w": 6, - "x": 18, - "y": 6 - }, - "id": 79, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": false, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "jvm_gc_memory_promoted_bytes_total/jvm_gc_memory_allocated_bytes_total", - "format": "time_series", - "hide": false, - "intervalFactor": 1, - "legendFormat": "{{ instance }}", - "refId": "A" - } - ], - "thresholds": [ - { - "colorMode": "critical", - "fill": true, - "line": true, - "op": "gt", - "value": 0.3 - } - ], - "timeFrom": null, - "timeShift": null, - "title": "Memory Saturation", - "tooltip": { - "shared": false, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ] - } - ], - "refresh": "1m", - "schemaVersion": 16, - "style": "dark", - "tags": [], - "templating": { - "list": [ - { - "allValue": null, - "current": {}, - "datasource": "${DS_PROMETHEUS}", - "hide": 0, - "includeAll": true, - "label": "Gateway Service", - "multi": true, - "name": "gatewayService", - "options": [], - "query": "label_values(spring_cloud_gateway_requests_seconds_count,job)", - "refresh": 1, - "regex": "", - "sort": 1, - "tagValuesQuery": "", - "tags": [], - "tagsQuery": "", - "type": "query", - "useTags": false - }, - { - "allValue": null, - "current": {}, - "datasource": "${DS_PROMETHEUS}", - "hide": 0, - "includeAll": true, - "label": "Route Id", - "multi": true, - "name": "routeId", - "options": [], - "query": "label_values(spring_cloud_gateway_requests_seconds_count,routeId)", - "refresh": 1, - "regex": "", - "sort": 1, - "tagValuesQuery": "", - "tags": [], - "tagsQuery": "", - "type": "query", - "useTags": false - } - ] - }, - "time": { - "from": "now/d", - "to": "now" - }, - "timepicker": { - "refresh_intervals": [ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" - ], - "time_options": [ - "5m", - "15m", - "1h", - "6h", - "12h", - "24h", - "2d", - "7d", - "30d" - ] - }, - "timezone": "", - "title": "Spring Cloud Gateway", - "uid": "7voUGKrik", - "version": 5 -} diff --git a/docs/src/main/asciidoc/ghpages.sh b/docs/src/main/asciidoc/ghpages.sh deleted file mode 100755 index 55e76be1..00000000 --- a/docs/src/main/asciidoc/ghpages.sh +++ /dev/null @@ -1,330 +0,0 @@ -#!/bin/bash -x - -set -e - -# Set default props like MAVEN_PATH, ROOT_FOLDER etc. -function set_default_props() { - # The script should be executed from the root folder - ROOT_FOLDER=`pwd` - echo "Current folder is ${ROOT_FOLDER}" - - if [[ ! -e "${ROOT_FOLDER}/.git" ]]; then - echo "You're not in the root folder of the project!" - exit 1 - fi - - # Prop that will let commit the changes - COMMIT_CHANGES="no" - MAVEN_PATH=${MAVEN_PATH:-} - echo "Path to Maven is [${MAVEN_PATH}]" - REPO_NAME=${PWD##*/} - echo "Repo name is [${REPO_NAME}]" - SPRING_CLOUD_STATIC_REPO=${SPRING_CLOUD_STATIC_REPO:-git@github.com:spring-cloud/spring-cloud-static.git} - echo "Spring Cloud Static repo is [${SPRING_CLOUD_STATIC_REPO}" -} - -# Check if gh-pages exists and docs have been built -function check_if_anything_to_sync() { - git remote set-url --push origin `git config remote.origin.url | sed -e 's/^git:/https:/'` - - if ! (git remote set-branches --add origin gh-pages && git fetch -q); then - echo "No gh-pages, so not syncing" - exit 0 - fi - - if ! [ -d docs/target/generated-docs ] && ! [ "${BUILD}" == "yes" ]; then - echo "No gh-pages sources in docs/target/generated-docs, so not syncing" - exit 0 - fi -} - -function retrieve_current_branch() { - # Code getting the name of the current branch. For master we want to publish as we did until now - # https://stackoverflow.com/questions/1593051/how-to-programmatically-determine-the-current-checked-out-git-branch - # If there is a branch already passed will reuse it - otherwise will try to find it - CURRENT_BRANCH=${BRANCH} - if [[ -z "${CURRENT_BRANCH}" ]] ; then - CURRENT_BRANCH=$(git symbolic-ref -q HEAD) - CURRENT_BRANCH=${CURRENT_BRANCH##refs/heads/} - CURRENT_BRANCH=${CURRENT_BRANCH:-HEAD} - fi - echo "Current branch is [${CURRENT_BRANCH}]" - git checkout ${CURRENT_BRANCH} || echo "Failed to check the branch... continuing with the script" -} - -# Switches to the provided value of the release version. We always prefix it with `v` -function switch_to_tag() { - git checkout v${VERSION} -} - -# Build the docs if switch is on -function build_docs_if_applicable() { - if [[ "${BUILD}" == "yes" ]] ; then - ./mvnw clean install -P docs -pl docs -DskipTests - fi -} - -# Get the name of the `docs.main` property -# Get whitelisted branches - assumes that a `docs` module is available under `docs` profile -function retrieve_doc_properties() { - MAIN_ADOC_VALUE=$("${MAVEN_PATH}"mvn -q \ - -Dexec.executable="echo" \ - -Dexec.args='${docs.main}' \ - --non-recursive \ - org.codehaus.mojo:exec-maven-plugin:1.3.1:exec) - echo "Extracted 'main.adoc' from Maven build [${MAIN_ADOC_VALUE}]" - - - WHITELIST_PROPERTY=${WHITELIST_PROPERTY:-"docs.whitelisted.branches"} - WHITELISTED_BRANCHES_VALUE=$("${MAVEN_PATH}"mvn -q \ - -Dexec.executable="echo" \ - -Dexec.args="\${${WHITELIST_PROPERTY}}" \ - org.codehaus.mojo:exec-maven-plugin:1.3.1:exec \ - -P docs \ - -pl docs) - echo "Extracted '${WHITELIST_PROPERTY}' from Maven build [${WHITELISTED_BRANCHES_VALUE}]" -} - -# Stash any outstanding changes -function stash_changes() { - git diff-index --quiet HEAD && dirty=$? || (echo "Failed to check if the current repo is dirty. Assuming that it is." && dirty="1") - if [ "$dirty" != "0" ]; then git stash; fi -} - -# Switch to gh-pages branch to sync it with current branch -function add_docs_from_target() { - local DESTINATION_REPO_FOLDER - if [[ -z "${DESTINATION}" && -z "${CLONE}" ]] ; then - DESTINATION_REPO_FOLDER=${ROOT_FOLDER} - elif [[ "${CLONE}" == "yes" ]]; then - mkdir -p ${ROOT_FOLDER}/target - local clonedStatic=${ROOT_FOLDER}/target/spring-cloud-static - if [[ ! -e "${clonedStatic}/.git" ]]; then - echo "Cloning Spring Cloud Static to target" - git clone ${SPRING_CLOUD_STATIC_REPO} ${clonedStatic} && git checkout gh-pages - else - echo "Spring Cloud Static already cloned - will pull changes" - cd ${clonedStatic} && git checkout gh-pages && git pull origin gh-pages - fi - DESTINATION_REPO_FOLDER=${clonedStatic}/${REPO_NAME} - mkdir -p ${DESTINATION_REPO_FOLDER} - else - if [[ ! -e "${DESTINATION}/.git" ]]; then - echo "[${DESTINATION}] is not a git repository" - exit 1 - fi - DESTINATION_REPO_FOLDER=${DESTINATION}/${REPO_NAME} - mkdir -p ${DESTINATION_REPO_FOLDER} - echo "Destination was provided [${DESTINATION}]" - fi - cd ${DESTINATION_REPO_FOLDER} - git checkout gh-pages - git pull origin gh-pages - - # Add git branches - ################################################################### - if [[ -z "${VERSION}" ]] ; then - copy_docs_for_current_version - else - copy_docs_for_provided_version - fi - commit_changes_if_applicable -} - - -# Copies the docs by using the retrieved properties from Maven build -function copy_docs_for_current_version() { - if [[ "${CURRENT_BRANCH}" == "master" ]] ; then - echo -e "Current branch is master - will copy the current docs only to the root folder" - for f in docs/target/generated-docs/*; do - file=${f#docs/target/generated-docs/*} - if ! git ls-files -i -o --exclude-standard --directory | grep -q ^$file$; then - # Not ignored... - cp -rf $f ${ROOT_FOLDER}/ - git add -A ${ROOT_FOLDER}/$file - fi - done - COMMIT_CHANGES="yes" - else - echo -e "Current branch is [${CURRENT_BRANCH}]" - # https://stackoverflow.com/questions/29300806/a-bash-script-to-check-if-a-string-is-present-in-a-comma-separated-list-of-strin - if [[ ",${WHITELISTED_BRANCHES_VALUE}," = *",${CURRENT_BRANCH},"* ]] ; then - mkdir -p ${ROOT_FOLDER}/${CURRENT_BRANCH} - echo -e "Branch [${CURRENT_BRANCH}] is whitelisted! Will copy the current docs to the [${CURRENT_BRANCH}] folder" - for f in docs/target/generated-docs/*; do - file=${f#docs/target/generated-docs/*} - if ! git ls-files -i -o --exclude-standard --directory | grep -q ^$file$; then - # Not ignored... - # We want users to access 1.0.0.RELEASE/ instead of 1.0.0.RELEASE/spring-cloud.sleuth.html - if [[ "${file}" == "${MAIN_ADOC_VALUE}.html" ]] ; then - # We don't want to copy the spring-cloud-sleuth.html - # we want it to be converted to index.html - cp -rf $f ${ROOT_FOLDER}/${CURRENT_BRANCH}/index.html - git add -A ${ROOT_FOLDER}/${CURRENT_BRANCH}/index.html - else - cp -rf $f ${ROOT_FOLDER}/${CURRENT_BRANCH} - git add -A ${ROOT_FOLDER}/${CURRENT_BRANCH}/$file - fi - fi - done - COMMIT_CHANGES="yes" - else - echo -e "Branch [${CURRENT_BRANCH}] is not on the white list! Check out the Maven [${WHITELIST_PROPERTY}] property in - [docs] module available under [docs] profile. Won't commit any changes to gh-pages for this branch." - fi - fi -} - -# Copies the docs by using the explicitly provided version -function copy_docs_for_provided_version() { - local FOLDER=${DESTINATION_REPO_FOLDER}/${VERSION} - mkdir -p ${FOLDER} - echo -e "Current tag is [v${VERSION}] Will copy the current docs to the [${FOLDER}] folder" - for f in ${ROOT_FOLDER}/docs/target/generated-docs/*; do - file=${f#${ROOT_FOLDER}/docs/target/generated-docs/*} - copy_docs_for_branch ${file} ${FOLDER} - done - COMMIT_CHANGES="yes" - CURRENT_BRANCH="v${VERSION}" -} - -# Copies the docs from target to the provided destination -# Params: -# $1 - file from target -# $2 - destination to which copy the files -function copy_docs_for_branch() { - local file=$1 - local destination=$2 - if ! git ls-files -i -o --exclude-standard --directory | grep -q ^${file}$; then - # Not ignored... - # We want users to access 1.0.0.RELEASE/ instead of 1.0.0.RELEASE/spring-cloud.sleuth.html - if [[ ("${file}" == "${MAIN_ADOC_VALUE}.html") || ("${file}" == "${REPO_NAME}.html") ]] ; then - # We don't want to copy the spring-cloud-sleuth.html - # we want it to be converted to index.html - cp -rf $f ${destination}/index.html - git add -A ${destination}/index.html - else - cp -rf $f ${destination} - git add -A ${destination}/$file - fi - fi -} - -function commit_changes_if_applicable() { - if [[ "${COMMIT_CHANGES}" == "yes" ]] ; then - COMMIT_SUCCESSFUL="no" - git commit -a -m "Sync docs from ${CURRENT_BRANCH} to gh-pages" && COMMIT_SUCCESSFUL="yes" || echo "Failed to commit changes" - - # Uncomment the following push if you want to auto push to - # the gh-pages branch whenever you commit to master locally. - # This is a little extreme. Use with care! - ################################################################### - if [[ "${COMMIT_SUCCESSFUL}" == "yes" ]] ; then - git push origin gh-pages - fi - fi -} - -# Switch back to the previous branch and exit block -function checkout_previous_branch() { - # If -version was provided we need to come back to root project - cd ${ROOT_FOLDER} - git checkout ${CURRENT_BRANCH} || echo "Failed to check the branch... continuing with the script" - if [ "$dirty" != "0" ]; then git stash pop; fi - exit 0 -} - -# Assert if properties have been properly passed -function assert_properties() { -echo "VERSION [${VERSION}], DESTINATION [${DESTINATION}], CLONE [${CLONE}]" -if [[ "${VERSION}" != "" && (-z "${DESTINATION}" && -z "${CLONE}") ]] ; then echo "Version was set but destination / clone was not!"; exit 1;fi -if [[ ("${DESTINATION}" != "" && "${CLONE}" != "") && -z "${VERSION}" ]] ; then echo "Destination / clone was set but version was not!"; exit 1;fi -if [[ "${DESTINATION}" != "" && "${CLONE}" == "yes" ]] ; then echo "Destination and clone was set. Pick one!"; exit 1;fi -} - -# Prints the usage -function print_usage() { -cat </` -- if the destination switch is passed (-d) then the script will check if the provided dir is a git repo and then will - switch to gh-pages of that repo and copy the generated docs to `docs//` - -USAGE: - -You can use the following options: - --v|--version - the script will apply the whole procedure for a particular library version --d|--destination - the root of destination folder where the docs should be copied. You have to use the full path. - E.g. point to spring-cloud-static folder. Can't be used with (-c) --b|--build - will run the standard build process after checking out the branch --c|--clone - will automatically clone the spring-cloud-static repo instead of providing the destination. - Obviously can't be used with (-d) - -EOF -} - - -# ========================================== -# ____ ____ _____ _____ _____ _______ -# / ____|/ ____| __ \|_ _| __ \__ __| -# | (___ | | | |__) | | | | |__) | | | -# \___ \| | | _ / | | | ___/ | | -# ____) | |____| | \ \ _| |_| | | | -# |_____/ \_____|_| \_\_____|_| |_| -# -# ========================================== - -while [[ $# > 0 ]] -do -key="$1" -case ${key} in - -v|--version) - VERSION="$2" - shift # past argument - ;; - -d|--destination) - DESTINATION="$2" - shift # past argument - ;; - -b|--build) - BUILD="yes" - ;; - -c|--clone) - CLONE="yes" - ;; - -h|--help) - print_usage - exit 0 - ;; - *) - echo "Invalid option: [$1]" - print_usage - exit 1 - ;; -esac -shift # past argument or value -done - -assert_properties -set_default_props -check_if_anything_to_sync -if [[ -z "${VERSION}" ]] ; then - retrieve_current_branch -else - switch_to_tag -fi -build_docs_if_applicable -retrieve_doc_properties -stash_changes -add_docs_from_target -checkout_previous_branch \ No newline at end of file diff --git a/docs/src/main/asciidoc/images/gateway-grafana-dashboard.jpeg b/docs/src/main/asciidoc/images/gateway-grafana-dashboard.jpeg deleted file mode 100644 index 3a231f70f646825b66c51c6c7c6d3c4535650d55..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 241759 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 AKmY&$ diff --git a/mvnw b/mvnw index f44ad2b7..41c0f0c2 100755 --- a/mvnw +++ b/mvnw @@ -8,7 +8,7 @@ # "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 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an @@ -19,7 +19,7 @@ # ---------------------------------------------------------------------------- # ---------------------------------------------------------------------------- -# Maven2 Start Up Batch script +# Maven Start Up Batch script # # Required ENV vars: # ------------------ @@ -114,7 +114,6 @@ if $mingw ; then M2_HOME="`(cd "$M2_HOME"; pwd)`" [ -n "$JAVA_HOME" ] && JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" - # TODO classpath? fi if [ -z "$JAVA_HOME" ]; then @@ -212,7 +211,11 @@ else if [ "$MVNW_VERBOSE" = true ]; then echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." fi - jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar" + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + fi while IFS="=" read key value; do case "$key" in (wrapperUrl) jarUrl="$value"; break ;; esac @@ -221,22 +224,38 @@ else echo "Downloading from: $jarUrl" fi wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi if command -v wget > /dev/null; then if [ "$MVNW_VERBOSE" = true ]; then echo "Found wget ... using wget" fi - wget "$jarUrl" -O "$wrapperJarPath" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi elif command -v curl > /dev/null; then if [ "$MVNW_VERBOSE" = true ]; then echo "Found curl ... using curl" fi - curl -o "$wrapperJarPath" "$jarUrl" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + else if [ "$MVNW_VERBOSE" = true ]; then echo "Falling back to using Java to download" fi javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi if [ -e "$javaClass" ]; then if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then if [ "$MVNW_VERBOSE" = true ]; then @@ -277,29 +296,13 @@ if $cygwin; then MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` fi +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain -echo "Running version check" -VERSION=$( sed '\!//' -e 's!.*$!!' ) -echo "The found version is [${VERSION}]" - -if echo $VERSION | egrep -q 'M|RC'; then - echo Activating \"milestone\" profile for version=\"$VERSION\" - echo $MAVEN_CONFIG | grep -q milestone || MAVEN_CONFIG="$MAVEN_CONFIG -Pmilestone" -else - echo Deactivating \"milestone\" profile for version=\"$VERSION\" - echo $MAVEN_CONFIG | grep -q milestone && MAVEN_CONFIG=$(echo $MAVEN_CONFIG | sed -e 's/-Pmilestone//') -fi - -if echo $VERSION | egrep -q 'RELEASE'; then - echo Activating \"central\" profile for version=\"$VERSION\" - echo $MAVEN_CONFIG | grep -q milestone || MAVEN_CONFIG="$MAVEN_CONFIG -Pcentral" -else - echo Deactivating \"central\" profile for version=\"$VERSION\" - echo $MAVEN_CONFIG | grep -q central && MAVEN_CONFIG=$(echo $MAVEN_CONFIG | sed -e 's/-Pcentral//') -fi - - exec "$JAVACMD" \ $MAVEN_OPTS \ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ diff --git a/mvnw.cmd b/mvnw.cmd index fef5a8f7..86115719 100644 --- a/mvnw.cmd +++ b/mvnw.cmd @@ -7,7 +7,7 @@ @REM "License"); you may not use this file except in compliance @REM with the License. You may obtain a copy of the License at @REM -@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM http://www.apache.org/licenses/LICENSE-2.0 @REM @REM Unless required by applicable law or agreed to in writing, @REM software distributed under the License is distributed on an @@ -18,7 +18,7 @@ @REM ---------------------------------------------------------------------------- @REM ---------------------------------------------------------------------------- -@REM Maven2 Start Up Batch script +@REM Maven Start Up Batch script @REM @REM Required ENV vars: @REM JAVA_HOME - location of a JDK home dir @@ -26,7 +26,7 @@ @REM Optional ENV vars @REM M2_HOME - location of maven2's installed home dir @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands -@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven @REM e.g. to debug Maven itself, use @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 @@ -37,7 +37,7 @@ @echo off @REM set title of command window title %0 -@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% @REM set %HOME% to equivalent of $HOME @@ -120,23 +120,44 @@ SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain -set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar" -FOR /F "tokens=1,2 delims==" %%A IN (%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties) DO ( - IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B ) @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central @REM This allows using the maven wrapper in projects that prohibit checking in binary data. if exist %WRAPPER_JAR% ( - echo Found %WRAPPER_JAR% + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) ) else ( - echo Couldn't find %WRAPPER_JAR%, downloading it ... - echo Downloading from: %DOWNLOAD_URL% - powershell -Command "(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')" - echo Finished downloading %WRAPPER_JAR% + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) ) @REM End of extension +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* if ERRORLEVEL 1 goto error goto end diff --git a/pom.xml b/pom.xml index 208dfbd9..84688ecf 100644 --- a/pom.xml +++ b/pom.xml @@ -1,287 +1,70 @@ - - + + + 4.0.0 org.springframework.cloud - spring-cloud-gateway - 4.1.0-SNAPSHOT - pom + spring-cloud-gateway-docs-build + 0.0.1-SNAPSHOT - Spring Cloud Gateway - Spring Cloud Gateway - - - org.springframework.cloud - spring-cloud-build - 4.1.0-SNAPSHOT - - + Spring Cloud Gateway Docs Build + Builds Spring Cloud Gateway Docs. + https://spring.io/projects/spring-cloud-gateway - https://github.com/spring-cloud/spring-cloud-gateway - - scm:git:git://github.com/spring-cloud/spring-cloud-gateway.git + scm:git:https://github.com/spring-cloud/spring-cloud-gateway.git - scm:git:ssh://git@github.com/spring-cloud/spring-cloud-gateway.git + scm:git:git@github.com:spring-cloud/spring-cloud-gateway.git - HEAD + https://github.com/spring-cloud/spring-cloud-gateway - - - - Apache License, Version 2.0 - https://www.apache.org/licenses/LICENSE-2.0 - - - - - sgibb - Spencer Gibb - sgibb at pivotal.io - Pivotal Software, Inc. - https://www.spring.io - - Project lead - - - + + https://github.com/spring-cloud/spring-cloud-gateway/issues + - UTF-8 - UTF-8 - 8.3.0 - 1.0.6.RELEASE - 17 - 1.6.1 - 3.1.0-SNAPSHOT - 4.1.0-SNAPSHOT - 1.17.3 + 0.0.3 - - - - org.springframework.cloud - spring-cloud-gateway-dependencies - ${project.version} - pom - import - - - org.springframework.cloud - spring-cloud-commons-dependencies - ${spring-cloud-commons.version} - pom - import - - - org.springframework.cloud - spring-cloud-test-support - ${spring-cloud-commons.version} - - - org.springframework.cloud - spring-cloud-circuitbreaker-dependencies - ${spring-cloud-circuitbreaker.version} - pom - import - - - org.springframework.cloud - spring-cloud-starter-circuitbreaker-reactor-resilience4j - ${spring-cloud-circuitbreaker.version} - - - org.springframework.boot - spring-boot-devtools - ${spring-boot.version} - - - com.bucket4j - bucket4j-core - ${bucket4j.version} - - - com.bucket4j - bucket4j-caffeine - ${bucket4j.version} - - - io.projectreactor.tools - blockhound - ${blockhound.version} - - - io.projectreactor.tools - blockhound-junit-platform - ${blockhound.version} - - - org.junit-pioneer - junit-pioneer - ${junit-pioneer.version} - - - org.testcontainers - testcontainers-bom - ${testcontainers.version} - pom - import - - - - - - spring-cloud-gateway-dependencies - spring-cloud-gateway-mvc - spring-cloud-gateway-webflux - spring-cloud-gateway-server - spring-cloud-gateway-server-mvc - spring-cloud-starter-gateway - spring-cloud-starter-gateway-mvc - spring-cloud-gateway-sample - spring-cloud-gateway-integration-tests - docs - - org.codehaus.mojo - flatten-maven-plugin - - - io.spring.javaformat - spring-javaformat-maven-plugin - - - org.apache.maven.plugins - maven-checkstyle-plugin + io.spring.maven.antora + antora-maven-plugin + ${io.spring.maven.antora-version} + true + + + + + + + + 9d489079e5ec46dbb238909fee5c9c29 + WB1FQYI187 + springcloudgateway + + - - - spring - - - spring-snapshots - Spring Snapshots - https://repo.spring.io/snapshot - - true - - - false - - - - spring-milestones - Spring Milestones - https://repo.spring.io/milestone - - false - - - - spring-releases - Spring Releases - https://repo.spring.io/release - - false - - - - - - spring-snapshots - Spring Snapshots - https://repo.spring.io/snapshot - - true - - - false - - - - spring-milestones - Spring Milestones - https://repo.spring.io/milestone - - false - - - - spring-releases - Spring Releases - https://repo.spring.io/release - - false - - - - - - sonar - - - - org.jacoco - jacoco-maven-plugin - - - pre-unit-test - - prepare-agent - - - surefireArgLine - ${project.build.directory}/jacoco.exec - - - - - post-unit-test - test - - report - - - - ${project.build.directory}/jacoco.exec - - - - - - - org.apache.maven.plugins - maven-surefire-plugin - - - ${surefireArgLine} - - - - - - - withoutDockerTests - - - - maven-surefire-plugin - - DockerRequired - - - - - - + + + spring-snapshot + https://repo.spring.io/snapshot + + true + + + false + + + + spring-milestone + https://repo.spring.io/milestone + + diff --git a/spring-cloud-gateway-dependencies/pom.xml b/spring-cloud-gateway-dependencies/pom.xml deleted file mode 100644 index 93b032be..00000000 --- a/spring-cloud-gateway-dependencies/pom.xml +++ /dev/null @@ -1,114 +0,0 @@ - - - 4.0.0 - - spring-cloud-dependencies-parent - org.springframework.cloud - 4.1.0-SNAPSHOT - - - - spring-cloud-gateway-dependencies - 4.1.0-SNAPSHOT - pom - - spring-cloud-gateway-dependencies - Spring Cloud Gateway Dependencies - - - - - - - - org.springframework.cloud - spring-cloud-gateway-webflux - ${project.version} - - - org.springframework.cloud - spring-cloud-gateway-mvc - ${project.version} - - - org.springframework.cloud - spring-cloud-gateway-server - ${project.version} - - - org.springframework.cloud - spring-cloud-gateway-server-mvc - ${project.version} - - - org.springframework.cloud - spring-cloud-starter-gateway - ${project.version} - - - org.springframework.cloud - spring-cloud-starter-gateway-mvc - ${project.version} - - - - - - - spring - - - spring-snapshots - Spring Snapshots - https://repo.spring.io/snapshot - - true - - - false - - - - spring-milestones - Spring Milestones - https://repo.spring.io/milestone - - false - - - - spring-releases - Spring Releases - https://repo.spring.io/release - - false - - - - - - spring-snapshots - Spring Snapshots - https://repo.spring.io/snapshot - - true - - - false - - - - spring-milestones - Spring Milestones - https://repo.spring.io/milestone - - false - - - - - - - diff --git a/spring-cloud-gateway-integration-tests/grpc/pom.xml b/spring-cloud-gateway-integration-tests/grpc/pom.xml deleted file mode 100644 index 6b243179..00000000 --- a/spring-cloud-gateway-integration-tests/grpc/pom.xml +++ /dev/null @@ -1,131 +0,0 @@ - - - 4.0.0 - - grpc - jar - - Spring Cloud Gateway gRPC Integration Test - Spring Cloud Gateway gRPC Integration Test - - - 1.47.0 - - - - org.springframework.cloud - spring-cloud-gateway-integration-tests - 4.1.0-SNAPSHOT - .. - - - - - org.springframework.boot - spring-boot-starter-webflux - - - org.springframework.cloud - spring-cloud-starter-gateway - - - org.springframework.boot - spring-boot-starter-actuator - - - io.grpc - grpc-netty - ${grpc.version} - - - io.grpc - grpc-protobuf - ${grpc.version} - - - io.grpc - grpc-stub - ${grpc.version} - - - com.fasterxml.jackson.dataformat - jackson-dataformat-protobuf - - - com.fasterxml.jackson.core - jackson-databind - - - io.netty - netty-tcnative-boringssl-static - - - org.apache.httpcomponents.client5 - httpclient5 - test - - - org.apache.tomcat - annotations-api - 6.0.53 - provided - - - org.springframework.boot - spring-boot-starter-test - test - - - org.springframework.cloud - spring-cloud-test-support - - - io.projectreactor - reactor-test - test - - - org.assertj - assertj-core - test - - - - - - kr.motd.maven - os-maven-plugin - 1.6.2 - - - - - maven-deploy-plugin - - true - - - - org.xolstice.maven.plugins - protobuf-maven-plugin - 0.6.1 - - com.google.protobuf:protoc:3.19.4:exe:${os.detected.classifier} - grpc-java - io.grpc:protoc-gen-grpc-java:1.44.0:exe:${os.detected.classifier} - - - - - compile - compile-custom - - - - - - - - diff --git a/spring-cloud-gateway-integration-tests/grpc/src/main/java/org/springframework/cloud/gateway/tests/grpc/GRPCApplication.java b/spring-cloud-gateway-integration-tests/grpc/src/main/java/org/springframework/cloud/gateway/tests/grpc/GRPCApplication.java deleted file mode 100644 index 1f7bd2b3..00000000 --- a/spring-cloud-gateway-integration-tests/grpc/src/main/java/org/springframework/cloud/gateway/tests/grpc/GRPCApplication.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright 2013-2021 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.grpc; - -import java.io.File; -import java.io.IOException; -import java.util.concurrent.TimeUnit; - -import io.grpc.Grpc; -import io.grpc.Server; -import io.grpc.ServerCredentials; -import io.grpc.Status; -import io.grpc.StatusRuntimeException; -import io.grpc.TlsServerCredentials; -import io.grpc.stub.StreamObserver; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import org.springframework.boot.ApplicationArguments; -import org.springframework.boot.ApplicationRunner; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.core.env.Environment; -import org.springframework.core.io.ClassPathResource; -import org.springframework.stereotype.Component; - -/** - * @author Alberto C. Ríos - */ -@SpringBootConfiguration -@EnableAutoConfiguration -public class GRPCApplication { - - public static void main(String[] args) { - SpringApplication.run(GRPCApplication.class, args); - } - - @Component - static class GRPCServer implements ApplicationRunner { - - private static final Logger log = LoggerFactory.getLogger(GRPCServer.class); - - private final Environment environment; - - private Server server; - - GRPCServer(Environment environment) { - this.environment = environment; - } - - @Override - public void run(ApplicationArguments args) throws Exception { - final GRPCServer server = new GRPCServer(environment); - server.start(); - } - - private void start() throws IOException { - Integer serverPort = environment.getProperty("local.server.port", Integer.class); - int grpcPort = serverPort + 1; - ServerCredentials creds = createServerCredentials(); - server = Grpc.newServerBuilderForPort(grpcPort, creds).addService(new HelloService()).build().start(); - - log.info("Starting gRPC server in port " + grpcPort); - - Runtime.getRuntime().addShutdownHook(new Thread(() -> { - try { - GRPCServer.this.stop(); - } - catch (InterruptedException e) { - e.printStackTrace(System.err); - } - })); - } - - private ServerCredentials createServerCredentials() throws IOException { - File certChain = new ClassPathResource("public.cert").getFile(); - File privateKey = new ClassPathResource("private.key").getFile(); - - return TlsServerCredentials.create(certChain, privateKey); - } - - private void stop() throws InterruptedException { - if (server != null) { - server.shutdown().awaitTermination(30, TimeUnit.SECONDS); - } - } - - static class HelloService extends HelloServiceGrpc.HelloServiceImplBase { - - @Override - public void hello(HelloRequest request, StreamObserver responseObserver) { - if ("failWithRuntimeException!".equals(request.getFirstName())) { - StatusRuntimeException exception = Status.FAILED_PRECONDITION.withDescription("Invalid firstName") - .asRuntimeException(); - responseObserver.onError(exception); - responseObserver.onCompleted(); - return; - } - - String greeting = String.format("Hello, %s %s", request.getFirstName(), request.getLastName()); - log.info("Sending response: " + greeting); - - HelloResponse response = HelloResponse.newBuilder().setGreeting(greeting).build(); - - responseObserver.onNext(response); - responseObserver.onCompleted(); - } - - } - - } - -} diff --git a/spring-cloud-gateway-integration-tests/grpc/src/main/proto/hello.pb b/spring-cloud-gateway-integration-tests/grpc/src/main/proto/hello.pb deleted file mode 100644 index 30c69524193aa7082b69890f481cc25eb6cfe7f1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 442 zcmdXcwqVASCBUU7BwcwPe diff --git a/spring-cloud-gateway-integration-tests/grpc/src/main/proto/hello.proto b/spring-cloud-gateway-integration-tests/grpc/src/main/proto/hello.proto deleted file mode 100644 index 400d4601..00000000 --- a/spring-cloud-gateway-integration-tests/grpc/src/main/proto/hello.proto +++ /dev/null @@ -1,17 +0,0 @@ -syntax = "proto3"; -option java_multiple_files = true; -option java_package = "org.springframework.cloud.gateway.tests.grpc"; -package org.springframework.cloud.gateway.tests.grpc; - -message HelloRequest { - optional string firstName = 1; - optional string lastName = 2; -} - -message HelloResponse { - optional string greeting = 1; -} - -service HelloService { - rpc hello(HelloRequest) returns (HelloResponse); -} diff --git a/spring-cloud-gateway-integration-tests/grpc/src/main/resources/application.yml b/spring-cloud-gateway-integration-tests/grpc/src/main/resources/application.yml deleted file mode 100644 index e69de29b..00000000 diff --git a/spring-cloud-gateway-integration-tests/grpc/src/main/resources/keystore.p12 b/spring-cloud-gateway-integration-tests/grpc/src/main/resources/keystore.p12 deleted file mode 100644 index 991ecbcf7a1628d62f5ef50135b44550e111331a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2586 zcma)8c{CIX7dI<|v4t!RBV|it2$Ln*%6QM3J!A>lvowhaLo{P2J7dk3v1JRP>^zZV zkBlYR8OrW?=X>Y9^ZoUG=bn4+xxe52-T&_ITr{4Z69@pI@vv4XOe_o=_6rW61!Uu4 zm0&!KdMtT4+8xK)F0~OH@|0p1Ev>E#PzbzyB z3Yh(}PR^>-PWR3YjADt>>;XsSJ!Lo$D9i!`AkmCay8mnh!XPwg1Qcc+h6UJzzyL8Y z3&`gelJw@}A)3%qG<43~1B?d`2DD+vOhNqd8AcBNJ!s#gpUu$%z>=^>?*Vd>r!Y$_ z0qM4v-`QUETvhXBbinD|=6Y5awBNxuc~c?gJ4Ba;thxBvChNg6T#$3A>N4cwEt|k4 z;dIq$*ULs4O=>%fUwv&l?Rq~Fl$y+Cr?fzE`)0f3X3R-;AAjpgLErh0dlf6usaCmU zv(HmQWn()hLrNWd=N8f#J?o$Jm%TPqL!KrC#RMIQp2t%AzvottH853+58N(TpS1nv z1s#Nap$cP7dE!f5#g>o~2aDMgQ-Y|rM9!JAOp_sq>hZ#;N1AB%^s}fnR*{>5c%yx4 z=%p73C@G-b-mB3ru`R}#=YdXZW$a`!*LSv^BjsrI?vUcvAk;dPKeP1`0d zusYkDUzBNmoK_)g?e%iPaT7>rxwn)#Orj%jVZ^E9+VDB9RhlQ(cvKILa1}`exNDtX zuI>oFl`16UA{@^?)}qrLnZal4dzZS&rO}Claj7i6mY7kA(Dp7emSC6nDDoe$kCZ7? zN-o}Z5&coVP^MHU4lSwHxT3&V9!N&m?M8nN(Yc_0>f&B5_O6ha*IkGjDrN!)cCx)Ecp-)|$a2 zt&ew?9|$@$5-{@xp4@KdTx#n6iJ-i?uj%V*&n**9hc~oVr3!8MNcYahb*&4q(rbuQ zI2o>UBEoNRH;Hr=zr4A>VNq1U`fKf!_5tvfwqUns%QloL{F8sf^3?E$9}DJ~UnMw; z(hKTY#kHug!IA{O#*rZjTkQet;j+x6`Yk2IdfYt! zT0C=qyT|y(P}JLNf~R5c4s-;!Kg8a|@859iUnJMKsc|2heoA}qb*Rj6+QBQ=p68QX zofz{;QuQy>``c7u?2;^eOf@iw?L$fe^DG3Ug*~Jec|nJs%9UPq`XpWYBPwWW%e%7O-6FX}-f| z#p>9E#!xdv9{TMvNREL($5~2g20fnU&+K0D%`B;gRoE+A5PO6Xg9N69MOWWNlMB0x zM#aHRsj*OE#G?ojr!&np!PK9QeRKoc&VaUJQ&W-kyem?L3IzVvNfA_jSO(&;dhXzO z0!hJOQTKpt{&=R*$)zz!>|&j7VTD^x{}05=65>TkXh}`sM4Q(+HACH|5^uy2%D0oQ z0bglU--)dWw2_UkDAw0XI4Wu}r#B{)NHN=jpX5i@IuEJt&CvaxDm(h7=5qAy99qWx zXPX}mg)I0tHYqZ!V%THW^*IF-)~#w|Y|c+|=md0S8jybsM@nAouaOh#(r>4sR62X` zM98!vCY1fi(Fdw&NrVTQY`CUmym9lq-RC@neOr}b@O^9cJwqZ~(^u^n?yeXAGf-Ey zp5Udu({UttWpFejl>(whuqJms-gvu}tISgx3QZScepRsAr=!W zW!aLhq)e<+QcEl?fF33zgO~b8M9CW7R-(Jttj2?B?10a~$l(fo^M0xi%G7*Psz18> zc7Lu!;%=FJp`LAWU0g&1j_*^PTKxHk(eM1f7zkcY%YQNWX0xN$Vp_Ic(2_{ot5eqZ zd^s#PxNGin(I&nOE zl|%`k2Fj{RI3^7xG&dcGqvRPbY)@;tLnwj;o#Y1*Z*?-tBmbav>O0TpWmFmQG+6dx*h%EVIgNNhDeu zEs2Kz^DhAb5E?Kt?)qLL#8+akUp-F)?Tj&>*+Ci5s+)AU1XXc2o80IJvqS25^oQ80 ULQp3jIQ_1QoD;_0FMpl*pK8ad-T(jq diff --git a/spring-cloud-gateway-integration-tests/grpc/src/main/resources/private.key b/spring-cloud-gateway-integration-tests/grpc/src/main/resources/private.key deleted file mode 100644 index d9952a36..00000000 --- a/spring-cloud-gateway-integration-tests/grpc/src/main/resources/private.key +++ /dev/null @@ -1,32 +0,0 @@ -Bag Attributes - friendlyName: bootapp - localKeyID: 54 69 6D 65 20 31 36 35 39 39 37 34 34 38 34 30 33 30 -Key Attributes: ------BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQClCZtSgRjM2uYM -O70+acD4rhwcc8ND23SwoKTNLPH6s1K2Pi5MZqF4Rnx+REJwerQrkgfMnsQ0QHcb -NjI9dhSwHw6aPigAGJQEzR8whDu2PkR57iBmySr3GLtpR+WkVGo/xAK92ozKt9aW -Saxm0miygYI8mgzrNpbaQAu6sZcS3RHkPYWw/izYVZLBB6IEpAJJrUqjzlu53TyN -X4VC4FWddItjOCiyrHe2+A+tFLNK3IZnFZBqoMcYR6/Rq+zsXx8ynCggkUwTFaPd -IVyzSYtooE2oq+aSjkJzKvoPA7BObLnrW+5Dklwj1iPtLhfuCflz2oa54/Hpl60R -Ke6y6/t5AgMBAAECggEAQvJ/sFswDUGq2kGNhfjuT7KJMr1+81Ldphy0XYqi6li9 -77GPpGxpidnF/I6CCRCtb5NAWK/61VtlNYOpo6b3w24FxWn7XfaabMwsn8i0VDw0 -GOYQ/MEUDcJZm96PeDbKFu3TUuKKBF3IzZQ7PEaUM/03MJApN20gio46c9RAfWTK -hN9jX9YkBpkSWPfx9RA9BOIXRrRMTXhidsVPnNxhhM7OAGZ1LhggDPaeiCIbVx6l -yMsyCkNvqdQbKu0hFcVyeQsGjovCC1Ye71F3em3ACguqDu+VAy3flpTbcDVf2D7T -zP2T5e5OEJ2hRTGdnEVSsXxWOWnq6Gt6ydNEe2AWVQKBgQC5HcQAUTnvIMaTez44 -7SBfELNY1AzF24dha24JFIGY78QNRZi+4Agsvxjkx4fKSd4DfmPsCqentmEkEcDr -w6fH0CAFm/rrkzLHmesBCC8byl4WtfMW5i8h1yzOeQbYpx3MGIu5xhkE92gbNj+o -ioM7nkoBvloU0odiYTVnoNYnFwKBgQDkO5ldA8Vl3+BWhmhOtPu+1EKssWry5rdO -m6IiZ1uB6uG4bhAj5dSVVgDRX6pcuppMVZqIbPpdkS7/i0LVwohT7EmpMMJ5bezz -EhG1z6AvZDcGMDC/PPMp7SLjJviYZDaTWLekKwY6yV1T9gtOY70jjWkUcfN1IHpA -renHNNyL7wKBgQCEYvIqW+y3xFPfY1MzePoeop3wl+3ujjo6hI7z9XNdgZNO/ofn -cebGwX+3Fa9aDwu0qe4h/9i4y2ibWAsFUS6ran+MI2oGkYXOU5hKa6TtFgPF8CfC -J6prZCxKGSm5RYK81I0Qtchs0dblJx3NlgmWWHSK3Kwlmg5yYBzGWLLuzwKBgDzg -rNrDq76txcAun9oGqnPPWG2J8XYTFmgQWWIF4cG4rjasnP+GSXr/8r3mX6HWYFvm -JY8oSmv00u107wHnnseL6mYHzIfpS1/WvQSa+iZJ++dZqVcJYe8YAstGVN8JNAl/ -i5RtqX66wXso0QE613OJP7MlZgQjApkICqiJMB7fAoGAVFdv/Nphcju7MCkXGuQe -amk7Z6Q2PjMWvLwiyB39PrIzvTOdaQyQk7oYdnAGFJbkmi7mmGiet5uvcE9OjNRb -+XeXo2LEZ2QWJEnHi9uCXSQF3eeBlJ2wGUsELPTHglWIUTpi5iDB2hHzczPzO8GY -qPb+ckCl8mTsHRKQQse4Bm0= ------END PRIVATE KEY----- diff --git a/spring-cloud-gateway-integration-tests/grpc/src/main/resources/public.cert b/spring-cloud-gateway-integration-tests/grpc/src/main/resources/public.cert deleted file mode 100644 index ecb750c6..00000000 --- a/spring-cloud-gateway-integration-tests/grpc/src/main/resources/public.cert +++ /dev/null @@ -1,18 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIC1zCCAb+gAwIBAgIIEsL9TgSEch8wDQYJKoZIhvcNAQELBQAwGjEYMBYGA1UE -AxMPbXktYm9vdC1hcHAub3JnMB4XDTIyMDgwODE2MDEyNFoXDTMyMDgwNTE2MDEy -NFowGjEYMBYGA1UEAxMPbXktYm9vdC1hcHAub3JnMIIBIjANBgkqhkiG9w0BAQEF -AAOCAQ8AMIIBCgKCAQEApQmbUoEYzNrmDDu9PmnA+K4cHHPDQ9t0sKCkzSzx+rNS -tj4uTGaheEZ8fkRCcHq0K5IHzJ7ENEB3GzYyPXYUsB8Omj4oABiUBM0fMIQ7tj5E -ee4gZskq9xi7aUflpFRqP8QCvdqMyrfWlkmsZtJosoGCPJoM6zaW2kALurGXEt0R -5D2FsP4s2FWSwQeiBKQCSa1Ko85bud08jV+FQuBVnXSLYzgosqx3tvgPrRSzStyG -ZxWQaqDHGEev0avs7F8fMpwoIJFMExWj3SFcs0mLaKBNqKvmko5Ccyr6DwOwTmy5 -61vuQ5JcI9Yj7S4X7gn5c9qGuePx6ZetESnusuv7eQIDAQABoyEwHzAdBgNVHQ4E -FgQUf72EgsQASMCzSGwrFDaKnBoufm8wDQYJKoZIhvcNAQELBQADggEBAAHzJkp/ -q+U0ki+JaFaUBYg85h+yJbi262hmFjLYDQEcz2tvAw93X9ytOzVtmo2H9AgsTJYA -z4UbJHfnTTa1KAHDHhzL/Adh/s2OZ7Y2kazLrHArFteKtGUo815JDL935JpiRBWc -Qn1WJJ6EFb+ZRMG7oROQ3LgvrradH8G51qxvSbH/fpmEFDTSHJNIG8tXcIA3fJkI -dsVJLygrXLzZQo5xI8rwMDX9aujRFU4Dgit58xquCkuzob/BonN7Es1Hp3beQjro -gddts4x5wWyiFun7uYrbPVhGMJNgwRKB5e3QQA6vYDyeQqvIy6n1lnDNIxvaOfPW -Je5gI59jCbxHs5g= ------END CERTIFICATE----- diff --git a/spring-cloud-gateway-integration-tests/grpc/src/test/java/org/springframework/cloud/gateway/tests/grpc/GRPCApplicationTests.java b/spring-cloud-gateway-integration-tests/grpc/src/test/java/org/springframework/cloud/gateway/tests/grpc/GRPCApplicationTests.java deleted file mode 100644 index e5a6bbcb..00000000 --- a/spring-cloud-gateway-integration-tests/grpc/src/test/java/org/springframework/cloud/gateway/tests/grpc/GRPCApplicationTests.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright 2013-2021 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.grpc; - -import java.security.cert.X509Certificate; - -import javax.net.ssl.SSLException; -import javax.net.ssl.TrustManager; -import javax.net.ssl.X509TrustManager; - -import io.grpc.ManagedChannel; -import io.grpc.StatusRuntimeException; -import io.grpc.netty.GrpcSslContexts; -import io.grpc.netty.NettyChannelBuilder; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.web.server.LocalServerPort; - -import static io.grpc.Status.FAILED_PRECONDITION; -import static io.grpc.netty.NegotiationType.TLS; -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment; - -/** - * @author Alberto C. Ríos - */ -@SpringBootTest(classes = org.springframework.cloud.gateway.tests.grpc.GRPCApplication.class, - webEnvironment = WebEnvironment.RANDOM_PORT) -public class GRPCApplicationTests { - - @LocalServerPort - private int gatewayPort; - - @BeforeEach - void setUp() { - int grpcServerPort = gatewayPort + 1; - final RouteConfigurer configurer = new RouteConfigurer(gatewayPort); - configurer.addRoute(grpcServerPort, "/**", null); - } - - @Test - public void gRPCUnaryCallShouldReturnResponse() throws SSLException { - ManagedChannel channel = createSecuredChannel(gatewayPort); - - final HelloResponse response = HelloServiceGrpc.newBlockingStub(channel) - .hello(HelloRequest.newBuilder().setFirstName("Sir").setLastName("FromClient").build()); - - Assertions.assertThat(response.getGreeting()).isEqualTo("Hello, Sir FromClient"); - } - - private ManagedChannel createSecuredChannel(int port) throws SSLException { - TrustManager[] trustAllCerts = createTrustAllTrustManager(); - - return NettyChannelBuilder.forAddress("localhost", port).useTransportSecurity() - .sslContext(GrpcSslContexts.forClient().trustManager(trustAllCerts[0]).build()).negotiationType(TLS) - .build(); - } - - @Test - public void gRPCUnaryCallShouldHandleRuntimeException() throws SSLException { - ManagedChannel channel = createSecuredChannel(gatewayPort); - - try { - HelloServiceGrpc.newBlockingStub(channel) - .hello(HelloRequest.newBuilder().setFirstName("failWithRuntimeException!").build()); - } - catch (StatusRuntimeException e) { - Assertions.assertThat(FAILED_PRECONDITION.getCode()).isEqualTo(e.getStatus().getCode()); - Assertions.assertThat("Invalid firstName").isEqualTo(e.getStatus().getDescription()); - } - } - - private TrustManager[] createTrustAllTrustManager() { - return new TrustManager[] { new X509TrustManager() { - public X509Certificate[] getAcceptedIssuers() { - return new X509Certificate[0]; - } - - public void checkClientTrusted(X509Certificate[] certs, String authType) { - } - - public void checkServerTrusted(X509Certificate[] certs, String authType) { - } - } }; - } - -} diff --git a/spring-cloud-gateway-integration-tests/grpc/src/test/java/org/springframework/cloud/gateway/tests/grpc/JsonToGrpcApplicationTests.java b/spring-cloud-gateway-integration-tests/grpc/src/test/java/org/springframework/cloud/gateway/tests/grpc/JsonToGrpcApplicationTests.java deleted file mode 100644 index 95af1853..00000000 --- a/spring-cloud-gateway-integration-tests/grpc/src/test/java/org/springframework/cloud/gateway/tests/grpc/JsonToGrpcApplicationTests.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright 2013-2021 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.grpc; - -import java.security.KeyManagementException; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; - -import javax.net.ssl.SSLContext; - -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.BasicHttpClientConnectionManager; -import org.apache.hc.client5.http.io.HttpClientConnectionManager; -import org.apache.hc.client5.http.socket.ConnectionSocketFactory; -import org.apache.hc.client5.http.socket.PlainConnectionSocketFactory; -import org.apache.hc.client5.http.ssl.NoopHostnameVerifier; -import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory; -import org.apache.hc.core5.http.config.Registry; -import org.apache.hc.core5.http.config.RegistryBuilder; -import org.apache.hc.core5.ssl.SSLContexts; -import org.apache.hc.core5.ssl.TrustStrategy; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; - -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.web.server.LocalServerPort; -import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; -import org.springframework.web.client.RestTemplate; - -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment; - -/** - * @author Alberto C. Ríos - * @author Abel Salgado Romero - */ -@Disabled -@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) -public class JsonToGrpcApplicationTests { - - @LocalServerPort - private int gatewayPort; - - private RestTemplate restTemplate; - - @BeforeEach - void setUp() { - restTemplate = createUnsecureClient(); - } - - @Test - public void shouldConvertFromJSONToGRPC() { - // Since GRPC server and GW run in same instance and don't know server port until - // test starts, - // we need to configure route dynamically using the actuator endpoint. - final RouteConfigurer configurer = new RouteConfigurer(gatewayPort); - int grpcServerPort = gatewayPort + 1; - configurer.addRoute(grpcServerPort, "/json/hello", - "JsonToGrpc=file:src/main/proto/hello.pb,file:src/main/proto/hello.proto,HelloService,hello"); - - String response = restTemplate.postForEntity("https://localhost:" + this.gatewayPort + "/json/hello", - "{\"firstName\":\"Duff\", \"lastName\":\"McKagan\"}", String.class).getBody(); - - Assertions.assertThat(response).isNotNull(); - Assertions.assertThat(response).contains("{\"greeting\":\"Hello, Duff McKagan\"}"); - } - - private RestTemplate createUnsecureClient() { - TrustStrategy acceptingTrustStrategy = (cert, authType) -> true; - SSLContext sslContext; - try { - sslContext = SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy).build(); - } - catch (NoSuchAlgorithmException | KeyStoreException | KeyManagementException e) { - throw new RuntimeException(e); - } - SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslContext, - NoopHostnameVerifier.INSTANCE); - - Registry socketFactoryRegistry = RegistryBuilder.create() - .register("https", sslSocketFactory).register("http", new PlainConnectionSocketFactory()).build(); - - HttpClientConnectionManager connectionManager = new BasicHttpClientConnectionManager(socketFactoryRegistry); - CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(connectionManager).build(); - - HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient); - - return new RestTemplate(requestFactory); - } - -} diff --git a/spring-cloud-gateway-integration-tests/grpc/src/test/java/org/springframework/cloud/gateway/tests/grpc/RouteConfigurer.java b/spring-cloud-gateway-integration-tests/grpc/src/test/java/org/springframework/cloud/gateway/tests/grpc/RouteConfigurer.java deleted file mode 100644 index a2994833..00000000 --- a/spring-cloud-gateway-integration-tests/grpc/src/test/java/org/springframework/cloud/gateway/tests/grpc/RouteConfigurer.java +++ /dev/null @@ -1,114 +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.tests.grpc; - -import java.security.KeyManagementException; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -import javax.net.ssl.SSLContext; - -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.BasicHttpClientConnectionManager; -import org.apache.hc.client5.http.io.HttpClientConnectionManager; -import org.apache.hc.client5.http.socket.ConnectionSocketFactory; -import org.apache.hc.client5.http.socket.PlainConnectionSocketFactory; -import org.apache.hc.client5.http.ssl.NoopHostnameVerifier; -import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory; -import org.apache.hc.core5.http.config.Registry; -import org.apache.hc.core5.http.config.RegistryBuilder; -import org.apache.hc.core5.ssl.SSLContexts; -import org.apache.hc.core5.ssl.TrustStrategy; - -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; -import org.springframework.web.client.RestTemplate; - -public class RouteConfigurer { - - private final int actuatorPort; - - private final RestTemplate restTemplate; - - RouteConfigurer(int actuatorPort) { - this.actuatorPort = actuatorPort; - this.restTemplate = createUnsecureClient(); - } - - public void addRoute(int grpcServerPort, String path, String filter) { - final String routeId = "test-route-" + UUID.randomUUID(); - - Map route = new HashMap<>(); - route.put("id", routeId); - route.put("uri", "https://localhost:" + grpcServerPort); - route.put("predicates", Collections.singletonList("Path=" + path)); - if (filter != null) { - route.put("filters", Arrays.asList(filter)); - } - - ResponseEntity exchange = restTemplate.exchange(url("/actuator/gateway/routes/" + routeId), - HttpMethod.POST, new HttpEntity<>(route), String.class); - - assert exchange.getStatusCode() == HttpStatus.CREATED; - - refreshRoutes(); - } - - private void refreshRoutes() { - ResponseEntity exchange = restTemplate.exchange(url("/actuator/gateway/refresh"), HttpMethod.POST, - new HttpEntity<>(""), String.class); - - assert exchange.getStatusCode() == HttpStatus.OK; - } - - private String url(String context) { - return String.format("https://localhost:%s%s", this.actuatorPort, context); - } - - private RestTemplate createUnsecureClient() { - TrustStrategy acceptingTrustStrategy = (cert, authType) -> true; - SSLContext sslContext; - try { - sslContext = SSLContexts.custom().loadTrustMaterial(null, acceptingTrustStrategy).build(); - } - catch (NoSuchAlgorithmException | KeyStoreException | KeyManagementException e) { - throw new RuntimeException(e); - } - SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslContext, - NoopHostnameVerifier.INSTANCE); - - Registry socketFactoryRegistry = RegistryBuilder.create() - .register("https", sslSocketFactory).register("http", new PlainConnectionSocketFactory()).build(); - - HttpClientConnectionManager connectionManager = new BasicHttpClientConnectionManager(socketFactoryRegistry); - CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(connectionManager).build(); - - HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient); - - return new RestTemplate(requestFactory); - } - -} 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 deleted file mode 100644 index 97c1b950..00000000 --- a/spring-cloud-gateway-integration-tests/grpc/src/test/resources/application.yml +++ /dev/null @@ -1,37 +0,0 @@ -server: - http2: - enabled: true - ssl: - key-store: classpath:keystore.p12 - key-store-type: pkcs12 - key-store-password: password - key-alias: bootapp - -management: - endpoint: - health: - show-details: when_authorized - gateway: - enabled: true - endpoints: - web: - exposure: - include: "*" - -spring: - cloud: - gateway: - httpserver: - wiretap: true - httpclient: - wiretap: true - ssl: - use-insecure-trust-manager: true -# Requires faking domain name (modifying /etc/hosts) -# trustedX509Certificates: -# - classpath:public.cert - -logging: - level: - reactor.netty: DEBUG - org.springframework.cloud.gateway.filter: DEBUG diff --git a/spring-cloud-gateway-integration-tests/http2/pom.xml b/spring-cloud-gateway-integration-tests/http2/pom.xml deleted file mode 100644 index 2f2093c2..00000000 --- a/spring-cloud-gateway-integration-tests/http2/pom.xml +++ /dev/null @@ -1,71 +0,0 @@ - - - 4.0.0 - - http2 - jar - - Spring Cloud Gateway HTTP2 Integration Test - Spring Cloud Gateway HTTP2 Integration Test - - - - - - org.springframework.cloud - spring-cloud-gateway-integration-tests - 4.1.0-SNAPSHOT - .. - - - - - org.springframework.boot - spring-boot-starter-webflux - - - org.springframework.cloud - spring-cloud-starter-gateway - - - org.springframework.cloud - spring-cloud-starter-loadbalancer - - - io.netty - netty-tcnative-boringssl-static - - - org.springframework.boot - spring-boot-starter-test - test - - - org.springframework.cloud - spring-cloud-test-support - - - io.projectreactor - reactor-test - test - - - org.assertj - assertj-core - test - - - - - - maven-deploy-plugin - - true - - - - - - diff --git a/spring-cloud-gateway-integration-tests/http2/src/main/java/org/springframework/cloud/gateway/tests/http2/Http2Application.java b/spring-cloud-gateway-integration-tests/http2/src/main/java/org/springframework/cloud/gateway/tests/http2/Http2Application.java deleted file mode 100644 index 0fe5ed34..00000000 --- a/spring-cloud-gateway-integration-tests/http2/src/main/java/org/springframework/cloud/gateway/tests/http2/Http2Application.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2013-2021 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.http2; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -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.gateway.route.RouteLocator; -import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; -import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient; -import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients; -import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier; -import org.springframework.cloud.loadbalancer.support.ServiceInstanceListSuppliers; -import org.springframework.context.annotation.Bean; -import org.springframework.core.env.Environment; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -/** - * @author Spencer Gibb - */ -// curl -i --insecure https://localhost:8443/hello -@SpringBootConfiguration -@EnableAutoConfiguration -@RestController -@LoadBalancerClients({ @LoadBalancerClient(name = "myservice", configuration = Http2Application.MyServiceConf.class), - @LoadBalancerClient(name = "nossl", configuration = Http2Application.NosslServiceConf.class) }) -public class Http2Application { - - private static Log log = LogFactory.getLog(Http2Application.class); - - @GetMapping("hello") - public String hello() { - return "Hello"; - } - - @Bean - public RouteLocator myRouteLocator(RouteLocatorBuilder builder) { - return builder.routes().route(r -> r.path("/myprefix/**").filters(f -> f.stripPrefix(1)).uri("lb://myservice")) - .route(r -> r.path("/nossl/**").filters(f -> f.stripPrefix(1)).uri("lb://nossl")) - .route(r -> r.path("/neverssl/**").filters(f -> f.stripPrefix(1)).uri("http://neverssl.com")) - .route(r -> r.path("/httpbin/**").uri("https://nghttp2.org")).build(); - } - - public static void main(String[] args) { - SpringApplication.run(Http2Application.class, args); - } - - static class MyServiceConf { - - @Bean - public ServiceInstanceListSupplier staticServiceInstanceListSupplier(Environment env) { - Integer port = env.getProperty("local.server.port", Integer.class, 8443); - log.info("local.server.port = " + port); - return ServiceInstanceListSuppliers.from("myservice", - new DefaultServiceInstance("myservice-1", "myservice", "localhost", port, true)); - } - - } - - static class NosslServiceConf { - - @Bean - public ServiceInstanceListSupplier noSslStaticServiceInstanceListSupplier() { - int port = Integer.parseInt(System.getProperty("nossl.port", "8080")); - log.info("nossl.port = " + port); - return ServiceInstanceListSuppliers.from("nossl", - new DefaultServiceInstance("nossl-1", "nossl", "localhost", port, false)); - } - - } - -} 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 deleted file mode 100644 index 8438b1d5..00000000 --- a/spring-cloud-gateway-integration-tests/http2/src/main/resources/application.yml +++ /dev/null @@ -1,22 +0,0 @@ -logging: - level: - org.springframework.cloud.gateway: TRACE - reactor.netty.http.client: DEBUG - -server: - ssl: - key-store: classpath:sample.jks - key-store-password: secret - key-password: password - http2: - enabled: true - -spring: - cloud: - gateway: -# httpserver: -# wiretap: true - httpclient: - wiretap: true - ssl: - use-insecure-trust-manager: true \ No newline at end of file diff --git a/spring-cloud-gateway-integration-tests/http2/src/main/resources/sample.jks b/spring-cloud-gateway-integration-tests/http2/src/main/resources/sample.jks deleted file mode 100644 index 6aa9a28053a591e41453e665e5024e8a8cb78b3d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2264 zcmchYX*3iJ7sqE|hQS!q5Mv)4GM2$i#uAFqC`%7x7baWA*i&dRX>3`uq(XS?3XSYp z%38`&ib7E$8j~$cF^}gt?|I+noW8#w?uYxk=iGD8|K9Vzd#pVc0002(2k@T|2@MMI zqxqr2AhQO*TVi`j@((S;e;g;l$#dAA{>vf0kX$R(Qn4oKgGEYjZ5zti2dw?Z6A zh%LuFCNI?9o+Z1duJL-++e#cjO`zlK?u9s030=k_*wD1#-$FbIDRDnA^vo@fm( zzjt(3VJrGOr0iHXSTM|rYN#>RZ@Dp`PwB2zrDQffLvuoR2~V3ReYa0&vU^dXd8isV zsAf*@!8s%xBvHLseXn6f?1kefe(8uAmAbaF$x{Ykzb6c6jdUwY1$y4tFzsj7 zIghr!T#ODfu@Po!a29@kXQ8kY#(LE<0o7?7PQ|eMeY@Equ?R-6*f@Na3o&stDQ=6( zQzDSQhCnS(9Bu9W_~giknP0vECqUsr4_9y_}nEU`cy z4}dApnAip92wMwgzciAFpc3i}+-#Zlq+iF7d1y}d4Qsp8=%l1N8NIs161I`HmkcpQ zY4*CUCFJJf(2!M{`&qQ}3($KeTQ=)mMrBs`DOb;%Of0tC)9he_p~w&CO#DfCgx(%s z{@|D(brX_Gb}ZDLmGej*JgEl0Et>q~kgTXuJg-PwvRjNx8sBbIShxD=xOySzw{;^X zAvrh5HTg>Xq@<{#^!Kg}B?qz@b<{ebD)yaSf&RChBIJQo-?Ahzw@qopSe^e&>^IuU zydM4Y1_C&>k7u|}=; z63R7$H6zat=hNExxEwXu1fQ*ytuEkP!{w{|#6TIEq1#*ck=6_NM*ILF65tmD-O5&R zMI!-MT<3U~t@}(CN4@RlZ~1I>C=!ywF)dNI{VvH;5Y3(Z4jY^%_c&fsm4Q`<1g|qX z&!h29jXjVE3nJnet*L)XL?-8<>qDbVGP%i^NwOZfwWO7?Mr!X7 zl}sG@9S_5}}td}$xrWIYY=e(VVBiv%A+M-{M z!3_^Tc=pV?niT!{D`!{e@W;MvrZ(OER{x7itVAtwE~spPtPtma|J=5dv&_oE!5H#` zdgXJ;+gJ4hI}*9QX9jpL`Gb)yCe%1}t!&O-^sihyZys%%5uF~WhsR_w(q7;vV5d4P zr%ZUA2}kO+L^2ePTgGT9Ua71w<+)poSyjTdLq&xbUn`<6&SpwFp(HRHUyU6J3WZ_! zfztko79+94Tq%mTYj53(RYcL&1~5`I#+w3`(Q|r+P(aT z%?r(^?IWw~19CB&uvXf(f7&BnEE{zwK4piVU`I4j1j?v5d4N<7VUJ8nM`$7S*mfKR z#9-JzPRZ?{M!@L+0N^V)IyeeP2T|^UK|m0QD+Ibs!wEoml^N!YO#vW~j~jraX(0A3 z6Kux?IRLez`O^X;{!4g%BhcRn>^H*qKZ3*|{_YGuz)KCJcu;)DSES5D2tDE`C02YR0R%Vy1T7k|RQ;3g<0icA$AuP0pOvc~jGl zz+NeKv_FT_;GWK&8XlDUv&hv9kxg?@c!bu?83i=YQ$S!K09Y)Glg3Hz?@|)ZCBlVz zP8i}#XZkMoje3I=h&I!!s_m?Qi@1MR`yv7X*yEs47qOs^t^?&=;*IQ!q&)gq_Sx5* z?fhU8Q*PSe*w7y)FH#P!9R^Xw!lTT+zI39L<&8cViaj$A(Z2Cg7!{V?uuyi#vlNCg z40i}2ivw&y&1-&Nh&WMG`&aIt>)(#tKTJ}^@696Kw1-{IzSOTnFF+0@k$o3%ZHS;Q#;t diff --git a/spring-cloud-gateway-integration-tests/http2/src/test/java/org/springframework/cloud/gateway/tests/http2/Http2ApplicationTests.java b/spring-cloud-gateway-integration-tests/http2/src/test/java/org/springframework/cloud/gateway/tests/http2/Http2ApplicationTests.java deleted file mode 100644 index f609a031..00000000 --- a/spring-cloud-gateway-integration-tests/http2/src/test/java/org/springframework/cloud/gateway/tests/http2/Http2ApplicationTests.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2013-2021 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.http2; - -import java.time.Duration; - -import io.netty.handler.ssl.util.InsecureTrustManagerFactory; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import reactor.core.publisher.Hooks; -import reactor.core.publisher.Mono; -import reactor.netty.http.Http2SslContextSpec; -import reactor.netty.http.HttpProtocol; -import reactor.netty.http.client.HttpClient; -import reactor.netty.resources.ConnectionProvider; -import reactor.test.StepVerifier; - -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.system.CapturedOutput; -import org.springframework.boot.test.system.OutputCaptureExtension; -import org.springframework.boot.test.web.server.LocalServerPort; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.http.client.reactive.ReactorClientHttpConnector; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.web.reactive.function.client.WebClient; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment; - -/** - * @author Spencer Gibb - */ -@ExtendWith(OutputCaptureExtension.class) -@SpringBootTest(classes = Http2Application.class, webEnvironment = WebEnvironment.RANDOM_PORT) -@DirtiesContext -public class Http2ApplicationTests { - - @LocalServerPort - int port; - - @Test - public void http2Works(CapturedOutput output) { - Hooks.onOperatorDebug(); - String uri = "https://localhost:" + port + "/myprefix/hello"; - String expected = "Hello"; - assertResponse(uri, expected); - Assertions.assertThat(output).contains("Negotiated application-level protocol [h2]", "PRI * HTTP/2.0"); - } - - public static void assertResponse(String uri, String expected) { - WebClient client = WebClient.builder().clientConnector(new ReactorClientHttpConnector(getHttpClient())).build(); - Mono> responseEntityMono = client.get().uri(uri).retrieve().toEntity(String.class); - StepVerifier.create(responseEntityMono).assertNext(entity -> { - assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK); - assertThat(entity.getBody()).isEqualTo(expected); - }).expectComplete().verify(); - } - - static HttpClient getHttpClient() { - return HttpClient - .create(ConnectionProvider.builder("test").maxConnections(100) - .pendingAcquireTimeout(Duration.ofMillis(0)).pendingAcquireMaxCount(-1).build()) - .protocol(HttpProtocol.HTTP11, HttpProtocol.H2).secure(sslContextSpec -> { - Http2SslContextSpec clientSslCtxt = Http2SslContextSpec.forClient() - .configure(builder -> builder.trustManager(InsecureTrustManagerFactory.INSTANCE)); - sslContextSpec.sslContext(clientSslCtxt); - }); - } - -} diff --git a/spring-cloud-gateway-integration-tests/http2/src/test/java/org/springframework/cloud/gateway/tests/http2/nossl/NosslConfiguration.java b/spring-cloud-gateway-integration-tests/http2/src/test/java/org/springframework/cloud/gateway/tests/http2/nossl/NosslConfiguration.java deleted file mode 100644 index 8a2a2d12..00000000 --- a/spring-cloud-gateway-integration-tests/http2/src/test/java/org/springframework/cloud/gateway/tests/http2/nossl/NosslConfiguration.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2013-2021 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.http2.nossl; - -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@Configuration -@EnableAutoConfiguration -public class NosslConfiguration { - - @GetMapping("/") - public String home() { - return "nossl"; - } - -} diff --git a/spring-cloud-gateway-integration-tests/http2/src/test/java/org/springframework/cloud/gateway/tests/http2/nossl/NosslTests.java b/spring-cloud-gateway-integration-tests/http2/src/test/java/org/springframework/cloud/gateway/tests/http2/nossl/NosslTests.java deleted file mode 100644 index 78d71e2f..00000000 --- a/spring-cloud-gateway-integration-tests/http2/src/test/java/org/springframework/cloud/gateway/tests/http2/nossl/NosslTests.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2013-2021 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.http2.nossl; - -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import reactor.core.publisher.Hooks; - -import org.springframework.boot.builder.SpringApplicationBuilder; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.system.CapturedOutput; -import org.springframework.boot.test.system.OutputCaptureExtension; -import org.springframework.boot.test.web.server.LocalServerPort; -import org.springframework.cloud.gateway.tests.http2.Http2Application; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.util.TestSocketUtils; - -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment; -import static org.springframework.cloud.gateway.tests.http2.Http2ApplicationTests.assertResponse; - -/** - * @author Spencer Gibb - */ -@ExtendWith(OutputCaptureExtension.class) -@SpringBootTest(classes = Http2Application.class, webEnvironment = WebEnvironment.RANDOM_PORT) -@DirtiesContext -public class NosslTests { - - @LocalServerPort - int port; - - @BeforeAll - static void beforeAll() { - int noSslPort = TestSocketUtils.findAvailableTcpPort(); - System.setProperty("nossl.port", String.valueOf(noSslPort)); - } - - @AfterAll - static void afterAll() { - System.clearProperty("nossl.port"); - } - - @Test - public void http2TerminationWorks(CapturedOutput output) { - int nosslPort = Integer.parseInt(System.getProperty("nossl.port")); - System.err.println("nossl.port = " + nosslPort); - Hooks.onOperatorDebug(); - try (ConfigurableApplicationContext context = new SpringApplicationBuilder(NosslConfiguration.class) - .properties("server.port=" + nosslPort).profiles("nossl").run()) { - String uri = "https://localhost:" + port + "/nossl"; - String expected = "nossl"; - assertResponse(uri, expected); - Assertions.assertThat(output).doesNotContain("PRI * HTTP/2.0"); - } - } - -} 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 deleted file mode 100644 index 6143bf9d..00000000 --- a/spring-cloud-gateway-integration-tests/http2/src/test/resources/application-nossl.yml +++ /dev/null @@ -1,13 +0,0 @@ -server: - ssl: - enabled: false - key-store: ~ - key-store-password: ~ - key-password: ~ - http2: - enabled: false - -spring: - cloud: - gateway: - enabled: false \ No newline at end of file diff --git a/spring-cloud-gateway-integration-tests/mvc-failure-analyzer/pom.xml b/spring-cloud-gateway-integration-tests/mvc-failure-analyzer/pom.xml deleted file mode 100644 index 37d9e85c..00000000 --- a/spring-cloud-gateway-integration-tests/mvc-failure-analyzer/pom.xml +++ /dev/null @@ -1,63 +0,0 @@ - - - 4.0.0 - - mvc-failure-analyzer - jar - - Spring Cloud Gateway MVC Failure Analyzer Integration Test - Spring Cloud Gateway MVC Failure Analyzer Integration Test - - - - - - org.springframework.cloud - spring-cloud-gateway-integration-tests - 4.1.0-SNAPSHOT - .. - - - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.cloud - spring-cloud-starter-gateway - - - org.springframework.cloud - spring-cloud-starter-loadbalancer - - - org.springframework.boot - spring-boot-starter-test - test - - - io.projectreactor - reactor-test - test - - - org.assertj - assertj-core - test - - - - - - maven-deploy-plugin - - true - - - - - - diff --git a/spring-cloud-gateway-integration-tests/mvc-failure-analyzer/src/main/java/org/springframework/cloud/gateway/sample/MvcFailureAnalyzerApplication.java b/spring-cloud-gateway-integration-tests/mvc-failure-analyzer/src/main/java/org/springframework/cloud/gateway/sample/MvcFailureAnalyzerApplication.java deleted file mode 100644 index 7c23213b..00000000 --- a/spring-cloud-gateway-integration-tests/mvc-failure-analyzer/src/main/java/org/springframework/cloud/gateway/sample/MvcFailureAnalyzerApplication.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2013-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.sample; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; -import org.springframework.cloud.client.DefaultServiceInstance; -import org.springframework.cloud.gateway.route.RouteLocator; -import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; -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.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -/** - * @author Spencer Gibb - */ -@SpringBootConfiguration -@EnableAutoConfiguration -@RestController -@LoadBalancerClient(name = "myservice", configuration = MyServiceConf.class) -public class MvcFailureAnalyzerApplication { - - @GetMapping("hello") - public String hello() { - return "Hello"; - } - - @Bean - @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE) - public RouteLocator myRouteLocator(RouteLocatorBuilder builder) { - return builder.routes().route(r -> r.path("/myprefix/**").filters(f -> f.stripPrefix(1)).uri("lb://myservice")) - .build(); - } - -} - -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/mvc-failure-analyzer/src/main/resources/application.yml b/spring-cloud-gateway-integration-tests/mvc-failure-analyzer/src/main/resources/application.yml deleted file mode 100644 index 106caafa..00000000 --- a/spring-cloud-gateway-integration-tests/mvc-failure-analyzer/src/main/resources/application.yml +++ /dev/null @@ -1,3 +0,0 @@ -logging: - level: - org.springframework.cloud.gateway: TRACE 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 deleted file mode 100644 index d6528f40..00000000 --- a/spring-cloud-gateway-integration-tests/mvc-failure-analyzer/src/test/java/org/springframework/cloud/gateway/sample/MvcFailureAnalyzerApplicationTests.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2013-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.sample; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.test.system.CapturedOutput; -import org.springframework.boot.test.system.OutputCaptureExtension; -import org.springframework.cloud.gateway.support.MvcFoundOnClasspathException; -import org.springframework.cloud.gateway.support.MvcFoundOnClasspathFailureAnalyzer; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.test.web.reactive.server.WebTestClient; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatCode; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -/** - * @author Spencer Gibb - */ -@ExtendWith(OutputCaptureExtension.class) -public class MvcFailureAnalyzerApplicationTests { - - @Test - public void exceptionThrown(CapturedOutput output) { - assertThatThrownBy(() -> new SpringApplication(MvcFailureAnalyzerApplication.class).run("--server.port=0")) - .hasRootCauseInstanceOf(MvcFoundOnClasspathException.class); - assertThat(output).contains(MvcFoundOnClasspathFailureAnalyzer.MESSAGE, - MvcFoundOnClasspathFailureAnalyzer.ACTION); - } - - @Test - public void exceptionNotThrownWhenDisabled(CapturedOutput output) { - assertThatCode(() -> new SpringApplication(MvcFailureAnalyzerApplication.class) - .run("--spring.cloud.gateway.enabled=false", "--server.port=0")).doesNotThrowAnyException(); - assertThat(output).doesNotContain(MvcFoundOnClasspathFailureAnalyzer.MESSAGE, - MvcFoundOnClasspathFailureAnalyzer.ACTION); - } - - @Test - public void exceptionNotThrownWhenReactiveTypeSet(CapturedOutput output) { - assertThatCode(() -> { - ConfigurableApplicationContext context = new SpringApplication(MvcFailureAnalyzerApplication.class) - .run("--spring.main.web-application-type=reactive", "--server.port=0", "--debug=true"); - Integer port = context.getEnvironment().getProperty("local.server.port", Integer.class); - WebTestClient client = WebTestClient.bindToServer().baseUrl("http://localhost:" + port).build(); - client.get().uri("/myprefix/hello").exchange().expectStatus().isOk().expectBody(String.class) - .isEqualTo("Hello"); - context.close(); - }).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 deleted file mode 100644 index 5282307a..00000000 --- a/spring-cloud-gateway-integration-tests/pom.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - 4.0.0 - - spring-cloud-gateway-integration-tests - pom - - Spring Cloud Gateway Integration Tests - Spring Cloud Gateway Integration Tests - - - - - - org.springframework.cloud - spring-cloud-gateway - 4.1.0-SNAPSHOT - .. - - - - grpc - http2 - mvc-failure-analyzer - - - - - - maven-deploy-plugin - - true - - - - - - diff --git a/spring-cloud-gateway-mvc/.jdk8 b/spring-cloud-gateway-mvc/.jdk8 deleted file mode 100644 index e69de29b..00000000 diff --git a/spring-cloud-gateway-mvc/pom.xml b/spring-cloud-gateway-mvc/pom.xml deleted file mode 100644 index cf3703c7..00000000 --- a/spring-cloud-gateway-mvc/pom.xml +++ /dev/null @@ -1,55 +0,0 @@ - - - 4.0.0 - - spring-cloud-gateway-mvc - spring-cloud-gateway-mvc - Spring Cloud Gateway MVC - - - org.springframework.cloud - spring-cloud-gateway - 4.1.0-SNAPSHOT - .. - - - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-autoconfigure-processor - true - - - org.springframework.boot - spring-boot-starter-test - test - - - org.junit.vintage - junit-vintage-engine - test - - - org.springframework.boot - spring-boot-starter-actuator - test - - - org.apache.httpcomponents - httpclient - 4.5.14 - test - - - org.springframework.boot - spring-boot-configuration-processor - true - - - diff --git a/spring-cloud-gateway-mvc/src/main/java/org/springframework/cloud/gateway/mvc/ProxyExchange.java b/spring-cloud-gateway-mvc/src/main/java/org/springframework/cloud/gateway/mvc/ProxyExchange.java deleted file mode 100644 index e1bb63f8..00000000 --- a/spring-cloud-gateway-mvc/src/main/java/org/springframework/cloud/gateway/mvc/ProxyExchange.java +++ /dev/null @@ -1,612 +0,0 @@ -/* - * 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; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.lang.reflect.Type; -import java.lang.reflect.TypeVariable; -import java.lang.reflect.WildcardType; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Enumeration; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; -import java.util.Vector; -import java.util.function.Function; -import java.util.stream.Collectors; - -import jakarta.servlet.ReadListener; -import jakarta.servlet.ServletInputStream; -import jakarta.servlet.ServletOutputStream; -import jakarta.servlet.WriteListener; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletRequestWrapper; -import jakarta.servlet.http.HttpServletResponse; -import jakarta.servlet.http.HttpServletResponseWrapper; - -import org.springframework.core.Conventions; -import org.springframework.core.MethodParameter; -import org.springframework.core.ParameterizedTypeReference; -import org.springframework.http.HttpHeaders; -import org.springframework.http.RequestEntity; -import org.springframework.http.RequestEntity.BodyBuilder; -import org.springframework.http.ResponseEntity; -import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.http.converter.HttpMessageNotWritableException; -import org.springframework.util.ClassUtils; -import org.springframework.util.StringUtils; -import org.springframework.validation.BindingResult; -import org.springframework.web.HttpMediaTypeNotAcceptableException; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.ResponseBody; -import org.springframework.web.bind.support.WebDataBinderFactory; -import org.springframework.web.client.RestTemplate; -import org.springframework.web.context.request.NativeWebRequest; -import org.springframework.web.context.request.ServletWebRequest; -import org.springframework.web.context.request.WebRequest; -import org.springframework.web.method.support.ModelAndViewContainer; -import org.springframework.web.servlet.HandlerMapping; -import org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor; - -/** - * A @RequestMapping argument type that can proxy the request to a backend. - * Spring will inject one of these into your MVC handler method, and you get return a - * ResponseEntity that you get from one of the HTTP methods {@link #get()}, - * {@link #post()}, {@link #put()}, {@link #patch()}, {@link #delete()} etc. Example: - * - *

- * @GetMapping("/proxy/{id}")
- * public ResponseEntity<?> proxy(@PathVariable Integer id, ProxyExchange<?> proxy)
- * 		throws Exception {
- * 	return proxy.uri("http://localhost:9000/foos/" + id).get();
- * }
- * 
- * - *

- * By default the incoming request body and headers are sent intact to the downstream - * service (with the exception of "sensitive" headers). To manipulate the downstream - * request there are "builder" style methods in {@link ProxyExchange}, but only the - * {@link #uri(String)} is mandatory. You can change the sensitive headers by calling the - * {@link #sensitive(String...)} method (Authorization and Cookie are sensitive by - * default). - *

- *

- * The type parameter T in ProxyExchange<T> is the type of - * the response body, so it comes out in the {@link ResponseEntity} that you return from - * your @RequestMapping. If you don't care about the type of the request and - * response body (e.g. if it's just a passthru) then use a wildcard, or - * byte[] or Object. Use a concrete type if you want to - * transform or manipulate the response, or if you want to assert that it is convertible - * to the type you declare. - *

- *

- * To manipulate the response use the overloaded HTTP methods with a Function - * argument and pass in code to transform the response. E.g. - * - *

- * @PostMapping("/proxy")
- * public ResponseEntity<Foo> proxy(ProxyExchange<Foo> proxy) throws Exception {
- * 	return proxy.uri("http://localhost:9000/foos/") //
- * 			.post(response -> ResponseEntity.status(response.getStatusCode()) //
- * 					.headers(response.getHeaders()) //
- * 					.header("X-Custom", "MyCustomHeader") //
- * 					.body(response.getBody()) //
- * 			);
- * }
- *
- * 
- * - *

- *

- * The full machinery of Spring {@link HttpMessageConverter message converters} is applied - * to the incoming request and response and also to the backend request. If you need - * additional converters then they need to be added upstream in the MVC configuration and - * also to the {@link RestTemplate} that is used in the backend calls (see the - * {@link ProxyExchange#ProxyExchange(RestTemplate, NativeWebRequest, ModelAndViewContainer, WebDataBinderFactory, Type) - * constructor} for details). - *

- *

- * As well as the HTTP methods for a backend call you can also use - * {@link #forward(String)} for a local in-container dispatch. - *

- *

- * - * @author Dave Syer - * - */ -public class ProxyExchange { - - /** - * Contains headers that are considered case-sensitive by default. - */ - public static Set DEFAULT_SENSITIVE = Collections - .unmodifiableSet(new HashSet<>(Arrays.asList("cookie", "authorization"))); - - private URI uri; - - private RestTemplate rest; - - private Object body; - - private RequestResponseBodyMethodProcessor delegate; - - private NativeWebRequest webRequest; - - private ModelAndViewContainer mavContainer; - - private WebDataBinderFactory binderFactory; - - private Set sensitive; - - private HttpHeaders headers = new HttpHeaders(); - - private Type responseType; - - public ProxyExchange(RestTemplate rest, NativeWebRequest webRequest, ModelAndViewContainer mavContainer, - WebDataBinderFactory binderFactory, Type type) { - this.responseType = type; - this.rest = rest; - this.webRequest = webRequest; - this.mavContainer = mavContainer; - this.binderFactory = binderFactory; - this.delegate = new RequestResponseBodyMethodProcessor(rest.getMessageConverters()); - } - - /** - * Sets the body for the downstream request (if using {@link #post()}, {@link #put()} - * or {@link #patch()}). The body can be omitted if you just want to pass the incoming - * request downstream without changing it. If you want to transform the incoming - * request you can declare it as a @RequestBody in your - * @RequestMapping in the usual Spring MVC way. - * @param body the request body to send downstream - * @return this for convenience - */ - public ProxyExchange body(Object body) { - this.body = body; - return this; - } - - /** - * Sets a header for the downstream call. - * @param name Header name - * @param value Header values - * @return this for convenience - */ - public ProxyExchange header(String name, String... value) { - this.headers.put(name, Arrays.asList(value)); - return this; - } - - /** - * Additional headers, or overrides of the incoming ones, to be used in the downstream - * call. - * @param headers the http headers to use in the downstream call - * @return this for convenience - */ - public ProxyExchange headers(HttpHeaders headers) { - this.headers.putAll(headers); - return this; - } - - /** - * Sets the names of sensitive headers that are not passed downstream to the backend - * service. - * @param names the names of sensitive headers - * @return this for convenience - */ - public ProxyExchange sensitive(String... names) { - if (this.sensitive == null) { - this.sensitive = new HashSet<>(); - } - - this.sensitive.clear(); - for (String name : names) { - this.sensitive.add(name.toLowerCase()); - } - return this; - } - - /** - * Sets the uri for the backend call when triggered by the HTTP methods. - * @param uri the backend uri to send the request to - * @return this for convenience - */ - public ProxyExchange uri(URI uri) { - this.uri = uri; - return this; - } - - /** - * Sets the uri for the backend call when triggered by the HTTP methods. - * @param uri the backend uri to send the request to - * @return this for convenience - */ - public ProxyExchange uri(String uri) { - try { - return this.uri(new URI(uri)); - } - catch (URISyntaxException e) { - throw new IllegalStateException("Cannot create URI", e); - } - } - - public String path() { - return (String) this.webRequest.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, - WebRequest.SCOPE_REQUEST); - } - - public String path(String prefix) { - String path = path(); - if (!path.startsWith(prefix)) { - throw new IllegalArgumentException("Path does not start with prefix (" + prefix + "): " + path); - } - return path.substring(prefix.length()); - } - - public void forward(String path) { - HttpServletRequest request = this.webRequest.getNativeRequest(HttpServletRequest.class); - HttpServletResponse response = this.webRequest.getNativeResponse(HttpServletResponse.class); - try { - request.getRequestDispatcher(path).forward(new BodyForwardingHttpServletRequest(request, response), - response); - } - catch (Exception e) { - throw new IllegalStateException("Cannot forward request", e); - } - } - - public ResponseEntity get() { - RequestEntity requestEntity = headers((BodyBuilder) RequestEntity.get(uri)).body(body()); - return exchange(requestEntity); - } - - public ResponseEntity get(Function, ResponseEntity> converter) { - return converter.apply(get()); - } - - public ResponseEntity head() { - RequestEntity requestEntity = headers((BodyBuilder) RequestEntity.head(uri)).build(); - return exchange(requestEntity); - } - - public ResponseEntity head(Function, ResponseEntity> converter) { - return converter.apply(head()); - } - - public ResponseEntity options() { - RequestEntity requestEntity = headers((BodyBuilder) RequestEntity.options(uri)).build(); - return exchange(requestEntity); - } - - public ResponseEntity options(Function, ResponseEntity> converter) { - return converter.apply(options()); - } - - public ResponseEntity post() { - RequestEntity requestEntity = headers(RequestEntity.post(uri)).body(body()); - return exchange(requestEntity); - } - - public ResponseEntity post(Function, ResponseEntity> converter) { - return converter.apply(post()); - } - - public ResponseEntity delete() { - RequestEntity requestEntity = headers((BodyBuilder) RequestEntity.delete(uri)).body(body()); - return exchange(requestEntity); - } - - public ResponseEntity delete(Function, ResponseEntity> converter) { - return converter.apply(delete()); - } - - public ResponseEntity put() { - RequestEntity requestEntity = headers(RequestEntity.put(uri)).body(body()); - return exchange(requestEntity); - } - - public ResponseEntity put(Function, ResponseEntity> converter) { - return converter.apply(put()); - } - - public ResponseEntity patch() { - RequestEntity requestEntity = headers(RequestEntity.patch(uri)).body(body()); - return exchange(requestEntity); - } - - public ResponseEntity patch(Function, ResponseEntity> converter) { - return converter.apply(patch()); - } - - private ResponseEntity exchange(RequestEntity requestEntity) { - Type type = this.responseType; - if (type instanceof TypeVariable || type instanceof WildcardType) { - type = Object.class; - } - return rest.exchange(requestEntity, ParameterizedTypeReference.forType(type)); - } - - private void addHeaders(HttpHeaders headers) { - ArrayList headerNames = new ArrayList<>(); - webRequest.getHeaderNames().forEachRemaining(headerNames::add); - Set filteredKeys = filterHeaderKeys(headerNames); - filteredKeys.stream().filter(key -> !headers.containsKey(key)) - .forEach(header -> headers.addAll(header, Arrays.asList(webRequest.getHeaderValues(header)))); - } - - private BodyBuilder headers(BodyBuilder builder) { - proxy(); - for (String name : filterHeaderKeys(headers)) { - builder.header(name, headers.get(name).toArray(new String[0])); - } - builder.headers(this::addHeaders); - return builder; - } - - private Set filterHeaderKeys(HttpHeaders headers) { - return filterHeaderKeys(headers.keySet()); - } - - private Set filterHeaderKeys(Collection headerNames) { - final Set sensitiveHeaders = this.sensitive != null ? this.sensitive : DEFAULT_SENSITIVE; - return headerNames.stream().filter(header -> !sensitiveHeaders.contains(header.toLowerCase())) - .collect(Collectors.toSet()); - } - - private void proxy() { - try { - URI uri = new URI(webRequest.getNativeRequest(HttpServletRequest.class).getRequestURL().toString()); - appendForwarded(uri); - appendXForwarded(uri); - } - catch (URISyntaxException e) { - throw new IllegalStateException("Cannot create URI for request: " - + webRequest.getNativeRequest(HttpServletRequest.class).getRequestURL()); - } - } - - private void appendXForwarded(URI uri) { - // Append the legacy headers if they were already added upstream - String host = headers.getFirst("x-forwarded-host"); - if (host == null) { - return; - } - host = host + "," + uri.getHost(); - headers.set("x-forwarded-host", host); - String proto = headers.getFirst("x-forwarded-proto"); - if (proto == null) { - return; - } - proto = proto + "," + uri.getScheme(); - headers.set("x-forwarded-proto", proto); - } - - private void appendForwarded(URI uri) { - String forwarded = headers.getFirst("forwarded"); - if (forwarded != null) { - forwarded = forwarded + ","; - } - else { - forwarded = ""; - } - forwarded = forwarded + forwarded(uri, webRequest.getHeader("host")); - headers.set("forwarded", forwarded); - } - - private String forwarded(URI uri, String hostHeader) { - if (StringUtils.hasText(hostHeader)) { - return "host=" + hostHeader; - } - if ("http".equals(uri.getScheme())) { - return "host=" + uri.getHost(); - } - return String.format("host=%s;proto=%s", uri.getHost(), uri.getScheme()); - } - - private Object body() { - if (body != null) { - return body; - } - body = getRequestBody(); - return body; - } - - /** - * Search for the request body if it was already deserialized using - * @RequestBody. If it is not found then deserialize it in the same way - * that it would have been for a @RequestBody. - * @return the request body - */ - private Object getRequestBody() { - for (String key : mavContainer.getModel().keySet()) { - if (key.startsWith(BindingResult.MODEL_KEY_PREFIX)) { - BindingResult result = (BindingResult) mavContainer.getModel().get(key); - return result.getTarget(); - } - } - MethodParameter input = new MethodParameter(ClassUtils.getMethod(BodyGrabber.class, "body", Object.class), 0); - try { - delegate.resolveArgument(input, mavContainer, webRequest, binderFactory); - } - catch (Exception e) { - throw new IllegalStateException("Cannot resolve body", e); - } - String name = Conventions.getVariableNameForParameter(input); - BindingResult result = (BindingResult) mavContainer.getModel().get(BindingResult.MODEL_KEY_PREFIX + name); - return result.getTarget(); - } - - protected static class BodyGrabber { - - public Object body(@RequestBody(required = false) Object body) { - return body; - } - - } - - protected static class BodySender { - - @ResponseBody - public Object body() { - return null; - } - - } - - /** - * A servlet request wrapper that can be safely passed downstream to an internal - * forward dispatch, caching its body, and making it available in converted form using - * Spring message converters. - * - */ - class BodyForwardingHttpServletRequest extends HttpServletRequestWrapper { - - private HttpServletRequest request; - - private HttpServletResponse response; - - BodyForwardingHttpServletRequest(HttpServletRequest request, HttpServletResponse response) { - super(request); - this.request = request; - this.response = response; - } - - private List header(String name) { - List list = headers.get(name); - return list; - } - - @Override - public ServletInputStream getInputStream() throws IOException { - Object body = body(); - MethodParameter output = new MethodParameter(ClassUtils.getMethod(BodySender.class, "body"), -1); - ServletOutputToInputConverter response = new ServletOutputToInputConverter(this.response); - ServletWebRequest webRequest = new ServletWebRequest(this.request, response); - try { - delegate.handleReturnValue(body, output, mavContainer, webRequest); - } - catch (HttpMessageNotWritableException | HttpMediaTypeNotAcceptableException e) { - throw new IllegalStateException("Cannot convert body", e); - } - return response.getInputStream(); - } - - @Override - public Enumeration getHeaderNames() { - Set names = headers.keySet(); - if (names.isEmpty()) { - return super.getHeaderNames(); - } - Set result = new LinkedHashSet<>(names); - result.addAll(Collections.list(super.getHeaderNames())); - return new Vector(result).elements(); - } - - @Override - public Enumeration getHeaders(String name) { - List list = header(name); - if (list != null) { - return new Vector(list).elements(); - } - return super.getHeaders(name); - } - - @Override - public String getHeader(String name) { - List list = header(name); - if (list != null && !list.isEmpty()) { - return list.iterator().next(); - } - return super.getHeader(name); - } - - } - -} - -/** - * Convenience class that converts an incoming request input stream into a form that can - * be easily deserialized to a Java object using Spring message converters. It is only - * used in a local forward dispatch, in which case there is a danger that the request body - * will need to be read and analysed more than once. Apart from using the message - * converters the other main feature of this class is that the request body is cached and - * can be read repeatedly as necessary. - * - * @author Dave Syer - * - */ -class ServletOutputToInputConverter extends HttpServletResponseWrapper { - - private StringBuilder builder = new StringBuilder(); - - ServletOutputToInputConverter(HttpServletResponse response) { - super(response); - } - - @Override - public ServletOutputStream getOutputStream() throws IOException { - return new ServletOutputStream() { - - @Override - public void write(int b) throws IOException { - builder.append(Character.valueOf((char) b)); - } - - @Override - public void setWriteListener(WriteListener listener) { - } - - @Override - public boolean isReady() { - return true; - } - }; - } - - public ServletInputStream getInputStream() { - ByteArrayInputStream body = new ByteArrayInputStream(builder.toString().getBytes()); - return new ServletInputStream() { - - @Override - public int read() throws IOException { - return body.read(); - } - - @Override - public void setReadListener(ReadListener listener) { - } - - @Override - public boolean isReady() { - return true; - } - - @Override - public boolean isFinished() { - return body.available() <= 0; - } - }; - } - -} diff --git a/spring-cloud-gateway-mvc/src/main/java/org/springframework/cloud/gateway/mvc/config/ProxyExchangeArgumentResolver.java b/spring-cloud-gateway-mvc/src/main/java/org/springframework/cloud/gateway/mvc/config/ProxyExchangeArgumentResolver.java deleted file mode 100644 index 01f05cf1..00000000 --- a/spring-cloud-gateway-mvc/src/main/java/org/springframework/cloud/gateway/mvc/config/ProxyExchangeArgumentResolver.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * 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.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.util.Collections; -import java.util.Enumeration; -import java.util.Set; - -import jakarta.servlet.http.HttpServletRequest; - -import org.springframework.cloud.gateway.mvc.ProxyExchange; -import org.springframework.core.MethodParameter; -import org.springframework.http.HttpHeaders; -import org.springframework.web.bind.support.WebDataBinderFactory; -import org.springframework.web.client.RestTemplate; -import org.springframework.web.context.request.NativeWebRequest; -import org.springframework.web.method.support.HandlerMethodArgumentResolver; -import org.springframework.web.method.support.ModelAndViewContainer; - -import static java.util.stream.Collectors.toSet; - -/** - * @author Dave Syer - * @author Tim Ysewyn - */ -public class ProxyExchangeArgumentResolver implements HandlerMethodArgumentResolver { - - private final RestTemplate rest; - - private HttpHeaders headers; - - private Set autoForwardedHeaders; - - private Set sensitive; - - public ProxyExchangeArgumentResolver(RestTemplate builder) { - this.rest = builder; - } - - public void setHeaders(HttpHeaders headers) { - this.headers = headers; - } - - public void setAutoForwardedHeaders(Set autoForwardedHeaders) { - this.autoForwardedHeaders = autoForwardedHeaders == null ? null - : autoForwardedHeaders.stream().map(String::toLowerCase).collect(toSet()); - } - - public void setSensitive(Set sensitive) { - this.sensitive = sensitive; - } - - @Override - public boolean supportsParameter(MethodParameter parameter) { - return ProxyExchange.class.isAssignableFrom(parameter.getParameterType()); - } - - @Override - public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, - NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { - ProxyExchange proxy = new ProxyExchange<>(rest, webRequest, mavContainer, binderFactory, type(parameter)); - configureHeaders(proxy); - configureAutoForwardedHeaders(proxy, webRequest); - configureSensitive(proxy); - return proxy; - } - - private Type type(MethodParameter parameter) { - Type type = parameter.getGenericParameterType(); - if (type instanceof ParameterizedType) { - ParameterizedType param = (ParameterizedType) type; - type = param.getActualTypeArguments()[0]; - } - return type; - } - - private HttpHeaders extractAutoForwardedHeaders(NativeWebRequest webRequest) { - HttpServletRequest nativeRequest = webRequest.getNativeRequest(HttpServletRequest.class); - Enumeration headerNames = nativeRequest.getHeaderNames(); - HttpHeaders headers = new HttpHeaders(); - while (headerNames.hasMoreElements()) { - String header = headerNames.nextElement(); - if (this.autoForwardedHeaders.contains(header.toLowerCase())) { - headers.addAll(header, Collections.list(nativeRequest.getHeaders(header))); - } - } - return headers; - } - - private void configureHeaders(final ProxyExchange proxy) { - if (headers != null) { - proxy.headers(headers); - } - } - - private void configureAutoForwardedHeaders(final ProxyExchange proxy, final NativeWebRequest webRequest) { - if ((autoForwardedHeaders != null) && (autoForwardedHeaders.size() > 0)) { - proxy.headers(extractAutoForwardedHeaders(webRequest)); - } - } - - private void configureSensitive(final ProxyExchange proxy) { - if (sensitive != null) { - proxy.sensitive(sensitive.toArray(new String[0])); - } - } - -} 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 deleted file mode 100644 index 5ee4b605..00000000 --- a/spring-cloud-gateway-mvc/src/main/java/org/springframework/cloud/gateway/mvc/config/ProxyProperties.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * 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 - * - */ -@ConfigurationProperties("spring.cloud.gateway.proxy") -public class ProxyProperties { - - /** - * Fixed header values that will be added to all downstream requests. - */ - private Map headers = new LinkedHashMap<>(); - - /** - * A set of header names that should be send 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 = null; - - 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 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/ProxyResponseAutoConfiguration.java b/spring-cloud-gateway-mvc/src/main/java/org/springframework/cloud/gateway/mvc/config/ProxyResponseAutoConfiguration.java deleted file mode 100644 index 0b69bab0..00000000 --- a/spring-cloud-gateway-mvc/src/main/java/org/springframework/cloud/gateway/mvc/config/ProxyResponseAutoConfiguration.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * 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.io.IOException; -import java.util.List; -import java.util.Optional; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.boot.web.client.RestTemplateBuilder; -import org.springframework.cloud.gateway.mvc.ProxyExchange; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.http.client.ClientHttpResponse; -import org.springframework.http.converter.ByteArrayHttpMessageConverter; -import org.springframework.web.client.DefaultResponseErrorHandler; -import org.springframework.web.client.RestTemplate; -import org.springframework.web.method.support.HandlerMethodArgumentResolver; -import org.springframework.web.method.support.HandlerMethodReturnValueHandler; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; - -/** - * Autoconfiguration for the {@link ProxyExchange} argument handler in Spring MVC - * @RequestMapping methods. - * - * @author Dave Syer - * @author Tim Ysewyn - */ -@Configuration(proxyBeanMethods = false) -@ConditionalOnWebApplication -@ConditionalOnClass({ HandlerMethodReturnValueHandler.class }) -@EnableConfigurationProperties(ProxyProperties.class) -public class ProxyResponseAutoConfiguration implements WebMvcConfigurer { - - @Autowired - private ApplicationContext context; - - @Bean - @ConditionalOnMissingBean - public ProxyExchangeArgumentResolver proxyExchangeArgumentResolver(Optional optional, - ProxyProperties proxy) { - RestTemplateBuilder builder = optional.orElse(new RestTemplateBuilder()); - RestTemplate template = builder.build(); - template.setErrorHandler(new NoOpResponseErrorHandler()); - template.getMessageConverters().add(new ByteArrayHttpMessageConverter() { - @Override - public boolean supports(Class clazz) { - return true; - } - }); - ProxyExchangeArgumentResolver resolver = new ProxyExchangeArgumentResolver(template); - resolver.setHeaders(proxy.convertHeaders()); - resolver.setAutoForwardedHeaders(proxy.getAutoForward()); - resolver.setSensitive(proxy.getSensitive()); // can be null - return resolver; - } - - @Override - public void addArgumentResolvers(List argumentResolvers) { - argumentResolvers.add(context.getBean(ProxyExchangeArgumentResolver.class)); - } - - private static class NoOpResponseErrorHandler extends DefaultResponseErrorHandler { - - @Override - public void handleError(ClientHttpResponse response) throws IOException { - } - - } - -} diff --git a/spring-cloud-gateway-mvc/src/main/resources/META-INF/spring.factories b/spring-cloud-gateway-mvc/src/main/resources/META-INF/spring.factories deleted file mode 100644 index cad77eff..00000000 --- a/spring-cloud-gateway-mvc/src/main/resources/META-INF/spring.factories +++ /dev/null @@ -1,2 +0,0 @@ -org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureWebMvc=\ -org.springframework.cloud.gateway.mvc.config.ProxyResponseAutoConfiguration diff --git a/spring-cloud-gateway-mvc/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-cloud-gateway-mvc/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports deleted file mode 100644 index 6b27473e..00000000 --- a/spring-cloud-gateway-mvc/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ /dev/null @@ -1 +0,0 @@ -org.springframework.cloud.gateway.mvc.config.ProxyResponseAutoConfiguration diff --git a/spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/GetWithBodyRequestTests.java b/spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/GetWithBodyRequestTests.java deleted file mode 100644 index 5b2a0981..00000000 --- a/spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/GetWithBodyRequestTests.java +++ /dev/null @@ -1,203 +0,0 @@ -/* - * 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; - -import java.io.IOException; -import java.net.URI; -import java.util.Collections; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import org.junit.jupiter.api.BeforeEach; -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.client.TestRestTemplate; -import org.springframework.boot.test.web.server.LocalServerPort; -import org.springframework.boot.web.client.RestTemplateBuilder; -import org.springframework.cloud.gateway.mvc.GetWithBodyRequestTests.TestApplication.Foo; -import org.springframework.cloud.gateway.mvc.config.ProxyExchangeArgumentResolver; -import org.springframework.cloud.gateway.mvc.config.ProxyProperties; -import org.springframework.cloud.gateway.mvc.http.GetWithBodyRequestClientHttpRequestFactory; -import org.springframework.context.annotation.Bean; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.http.client.ClientHttpResponse; -import org.springframework.http.converter.ByteArrayHttpMessageConverter; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestHeader; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.client.DefaultResponseErrorHandler; -import org.springframework.web.client.RestTemplate; - -import static org.assertj.core.api.Assertions.assertThat; - -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -@ContextConfiguration(classes = GetWithBodyRequestTests.TestApplication.class) -public class GetWithBodyRequestTests { - - @Autowired - private TestRestTemplate rest; - - @Autowired - private TestApplication testApplication; - - @LocalServerPort - private int port; - - @BeforeEach - public void init() throws Exception { - testApplication.setHome(new URI("http://localhost:" + port)); - rest.getRestTemplate().setRequestFactory(new GetWithBodyRequestClientHttpRequestFactory()); - } - - @Test - public void get() { - assertThat(rest.getForObject("/proxy/0", Foo.class).getName()).isEqualTo("bye"); - } - - @Test - public void getWithBodyRequest() { - final HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_JSON); - headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); - - final Foo bodyRequest = new Foo("hello"); - final HttpEntity entity = new HttpEntity<>(bodyRequest, headers); - - final ResponseEntity response = rest.exchange("/proxy/get-with-body-request", HttpMethod.GET, entity, - Foo.class); - - assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); - assertThat(response.getBody()).isInstanceOfSatisfying(Foo.class, - foo -> assertThat(foo.getName()).isEqualTo("hello world")); - } - - @SpringBootApplication - static class TestApplication { - - @Autowired - private ProxyController proxyController; - - public void setHome(URI home) { - proxyController.setHome(home); - } - - @Bean - public ProxyExchangeArgumentResolver proxyExchangeArgumentResolver(final ProxyProperties proxy) { - ProxyExchangeArgumentResolver resolver = new ProxyExchangeArgumentResolver( - generateConfiguredRestTemplate()); - resolver.setHeaders(proxy.convertHeaders()); - resolver.setAutoForwardedHeaders(proxy.getAutoForward()); - resolver.setSensitive(proxy.getSensitive()); - return resolver; - } - - private RestTemplate generateConfiguredRestTemplate() { - final RestTemplateBuilder builder = new RestTemplateBuilder(); - final RestTemplate template = builder.build(); - - template.setRequestFactory(new GetWithBodyRequestClientHttpRequestFactory()); - template.setErrorHandler(new NoOpResponseErrorHandler()); - template.getMessageConverters().add(new ByteArrayHttpMessageConverter() { - @Override - public boolean supports(Class clazz) { - return true; - } - }); - - return template; - } - - @RestController - static class ProxyController { - - private URI home; - - public void setHome(URI home) { - this.home = home; - } - - @GetMapping("/proxy/{id}") - public ResponseEntity proxyFoos(@PathVariable Integer id, ProxyExchange proxy) throws Exception { - return proxy.uri(home.toString() + "/foos/" + id).get(); - } - - @GetMapping("/proxy/get-with-body-request") - public ResponseEntity proxyFooWithBody(@RequestBody Foo foo, ProxyExchange proxy) throws Exception { - return proxy.uri(home.toString() + "/foo/get-with-body-request").get(); - } - - } - - @RestController - static class TestController { - - @GetMapping("/foos/{id}") - public Foo foo(@PathVariable Integer id, @RequestHeader HttpHeaders headers) { - String custom = headers.getFirst("X-Custom"); - return new Foo(id == 1 ? "foo" : custom != null ? custom : "bye"); - } - - @GetMapping("/foo/get-with-body-request") - public Foo getWithBody(@RequestBody Foo foo) { - return new Foo(foo.getName() + " world"); - } - - } - - @JsonIgnoreProperties(ignoreUnknown = true) - static class Foo { - - private String name; - - Foo() { - } - - Foo(String name) { - this.name = name; - } - - public String getName() { - return name; - } - - public void setName(final String name) { - this.name = name; - } - - } - - private static class NoOpResponseErrorHandler extends DefaultResponseErrorHandler { - - @Override - public void handleError(ClientHttpResponse response) throws IOException { - } - - } - - } - -} diff --git a/spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/ProductionConfigurationTests.java b/spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/ProductionConfigurationTests.java deleted file mode 100644 index a0295e10..00000000 --- a/spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/ProductionConfigurationTests.java +++ /dev/null @@ -1,540 +0,0 @@ -/* - * 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; - -import java.net.URI; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import org.junit.jupiter.api.BeforeEach; -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.context.SpringBootTest.WebEnvironment; -import org.springframework.boot.test.web.client.TestRestTemplate; -import org.springframework.boot.test.web.server.LocalServerPort; -import org.springframework.cloud.gateway.mvc.ProductionConfigurationTests.TestApplication; -import org.springframework.cloud.gateway.mvc.ProductionConfigurationTests.TestApplication.Bar; -import org.springframework.cloud.gateway.mvc.ProductionConfigurationTests.TestApplication.Foo; -import org.springframework.core.ParameterizedTypeReference; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.http.RequestEntity; -import org.springframework.http.ResponseEntity; -import org.springframework.http.client.SimpleClientHttpRequestFactory; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestHeader; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.util.UriComponentsBuilder; - -import static org.assertj.core.api.Assertions.assertThat; - -@SpringBootTest(properties = { "spring.cloud.gateway.proxy.auto-forward=Baz" }, - webEnvironment = WebEnvironment.RANDOM_PORT) -@ContextConfiguration(classes = TestApplication.class) -public class ProductionConfigurationTests { - - @Autowired - private TestRestTemplate rest; - - @Autowired - private TestApplication application; - - @LocalServerPort - private int port; - - @BeforeEach - public void init() { - application.setHome(URI.create("http://localhost:" + port)); - rest.getRestTemplate().setRequestFactory(new SimpleClientHttpRequestFactory()); - } - - @Test - public void get() { - assertThat(rest.getForObject("/proxy/0", Foo.class).getName()).isEqualTo("bye"); - } - - @Test - public void path() { - assertThat(rest.getForObject("/proxy/path/1", Foo.class).getName()).isEqualTo("foo"); - } - - @Test - public void resource() { - assertThat(rest.getForObject("/proxy/html/test.html", String.class)).contains("Test"); - } - - @Test - public void resourceWithNoType() { - assertThat(rest.getForObject("/proxy/typeless/test.html", String.class)).contains("Test"); - } - - @Test - public void missing() { - assertThat(rest.getForEntity("/proxy/missing/0", Foo.class).getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND); - } - - @Test - public void uri() { - assertThat(rest.getForObject("/proxy/0", Foo.class).getName()).isEqualTo("bye"); - } - - @Test - public void post() { - assertThat(rest.postForObject("/proxy/0", Collections.singletonMap("name", "foo"), Bar.class).getName()) - .isEqualTo("host=localhost:" + port + ";foo"); - } - - @Test - public void forward() { - assertThat(rest.getForObject("/forward/foos/0", Foo.class).getName()).isEqualTo("bye"); - } - - @Test - public void forwardHeader() { - ResponseEntity result = rest.getForEntity("/forward/special/foos/0", Foo.class); - assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK); - assertThat(result.getBody().getName()).isEqualTo("FOO"); - } - - @Test - public void postForwardHeader() { - ResponseEntity> result = rest.exchange( - RequestEntity.post(rest.getRestTemplate().getUriTemplateHandler().expand("/forward/special/bars")) - .body(Collections.singletonList(Collections.singletonMap("name", "foo"))), - new ParameterizedTypeReference>() { - }); - assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK); - assertThat(result.getBody().iterator().next().getName()).isEqualTo("FOOfoo"); - } - - @Test - public void postForwardBody() { - ResponseEntity result = rest - .exchange( - RequestEntity.post(rest.getRestTemplate().getUriTemplateHandler().expand("/forward/body/bars")) - .body(Collections.singletonList(Collections.singletonMap("name", "foo"))), - String.class); - assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK); - assertThat(result.getBody()).contains("foo"); - } - - @Test - public void postForwardForgetBody() { - ResponseEntity result = rest.exchange( - RequestEntity.post(rest.getRestTemplate().getUriTemplateHandler().expand("/forward/forget/bars")) - .body(Collections.singletonList(Collections.singletonMap("name", "foo"))), - String.class); - assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK); - assertThat(result.getBody()).contains("foo"); - } - - @Test - public void postForwardBodyFoo() { - ResponseEntity> result = rest.exchange( - RequestEntity.post(rest.getRestTemplate().getUriTemplateHandler().expand("/forward/body/bars")) - .body(Collections.singletonList(Collections.singletonMap("name", "foo"))), - new ParameterizedTypeReference>() { - }); - assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK); - assertThat(result.getBody().iterator().next().getName()).isEqualTo("foo"); - } - - @Test - public void list() { - assertThat(rest.exchange( - RequestEntity.post(rest.getRestTemplate().getUriTemplateHandler().expand("/proxy")) - .body(Collections.singletonList(Collections.singletonMap("name", "foo"))), - new ParameterizedTypeReference>() { - }).getBody().iterator().next().getName()).isEqualTo("host=localhost:" + port + ";foo"); - } - - @Test - public void bodyless() { - assertThat(rest.postForObject("/proxy/0", Collections.singletonMap("name", "foo"), Bar.class).getName()) - .isEqualTo("host=localhost:" + port + ";foo"); - } - - @Test - public void entity() { - assertThat( - rest.exchange(RequestEntity.post(rest.getRestTemplate().getUriTemplateHandler().expand("/proxy/entity")) - .body(Collections.singletonMap("name", "foo")), new ParameterizedTypeReference>() { - }).getBody().iterator().next().getName()).isEqualTo("host=localhost:" + port + ";foo"); - } - - @Test - public void entityWithType() { - assertThat( - rest.exchange(RequestEntity.post(rest.getRestTemplate().getUriTemplateHandler().expand("/proxy/type")) - .body(Collections.singletonMap("name", "foo")), new ParameterizedTypeReference>() { - }).getBody().iterator().next().getName()).isEqualTo("host=localhost:" + port + ";foo"); - } - - @Test - public void single() { - assertThat(rest.postForObject("/proxy/single", Collections.singletonMap("name", "foobar"), Bar.class).getName()) - .isEqualTo("host=localhost:" + port + ";foobar"); - } - - @Test - public void converter() { - assertThat( - rest.postForObject("/proxy/converter", Collections.singletonMap("name", "foobar"), Bar.class).getName()) - .isEqualTo("host=localhost:" + port + ";foobar"); - } - - @Test - public void noBody() { - Foo foo = rest.postForObject("/proxy/no-body", null, Foo.class); - assertThat(foo.getName()).isEqualTo("hello"); - } - - @Test - public void deleteWithoutBody() { - ResponseEntity deleteResponse = rest.exchange("/proxy/{id}/no-body", HttpMethod.DELETE, null, Void.TYPE, - Collections.singletonMap("id", "123")); - assertThat(deleteResponse.getStatusCode()).isEqualTo(HttpStatus.OK); - } - - @Test - public void deleteWithBody() { - Foo foo = new Foo("to-be-deleted"); - ParameterizedTypeReference> returnType = new ParameterizedTypeReference>() { - }; - ResponseEntity> deleteResponse = rest.exchange("/proxy/{id}", HttpMethod.DELETE, - new HttpEntity(foo), returnType, Collections.singletonMap("id", "123")); - assertThat(deleteResponse.getStatusCode()).isEqualTo(HttpStatus.OK); - assertThat(deleteResponse.getBody().get("deleted")).usingRecursiveComparison().isEqualTo(foo); - } - - @Test - @SuppressWarnings({ "Duplicates", "unchecked" }) - public void testSensitiveHeadersOverride() { - RequestEntity request = RequestEntity - .get(rest.getRestTemplate().getUriTemplateHandler().expand("/proxy/headers")).header("foo", "bar") - .header("abc", "xyz").header("cookie", "monster").build(); - Map> headers = rest.exchange(request, Map.class).getBody(); - assertThat(headers).doesNotContainKey("foo").doesNotContainKey("hello").containsKeys("bar", "abc"); - - assertThat(headers.get("cookie")).containsOnly("monster"); - } - - @Test - @SuppressWarnings({ "Duplicates", "unchecked" }) - public void testSensitiveHeadersDefault() { - Map> headers = rest.exchange(RequestEntity - .get(rest.getRestTemplate().getUriTemplateHandler().expand("/proxy/sensitive-headers-default")) - .header("cookie", "monster").build(), Map.class).getBody(); - - assertThat(headers).doesNotContainKey("cookie"); - } - - @Test - @SuppressWarnings({ "Duplicates", "unchecked" }) - public void headers() { - Map> headers = rest - .exchange(RequestEntity.get(rest.getRestTemplate().getUriTemplateHandler().expand("/proxy/headers")) - .header("foo", "bar").header("abc", "xyz").header("baz", "fob").build(), Map.class) - .getBody(); - assertThat(headers).doesNotContainKey("foo").doesNotContainKey("hello").containsKeys("bar", "abc"); - - assertThat(headers.get("bar")).containsOnly("hello"); - assertThat(headers.get("abc")).containsOnly("123"); - assertThat(headers.get("baz")).containsOnly("fob"); - } - - @Test - public void forwardedHeaderUsesHost() { - Map> headers = rest - .exchange(RequestEntity.get(rest.getRestTemplate().getUriTemplateHandler().expand("/proxy/headers")) - .header("host", "foo:1234").build(), Map.class) - .getBody(); - - assertThat(headers).containsKey("forwarded"); - assertThat(headers.get("forwarded").size()).isEqualTo(1); - assertThat(headers.get("forwarded").get(0)).isEqualTo("host=localhost:" + port); - } - - @SpringBootApplication - static class TestApplication { - - @Autowired - private ProxyController controller; - - public void setHome(URI home) { - controller.setHome(home); - } - - @RestController - static class ProxyController { - - private URI home; - - public void setHome(URI home) { - this.home = home; - } - - @GetMapping("/proxy/{id}") - public ResponseEntity proxyFoos(@PathVariable Integer id, ProxyExchange proxy) { - return proxy.uri(home.toString() + "/foos/" + id).get(); - } - - @GetMapping("/proxy/path/**") - public ResponseEntity proxyPath(ProxyExchange proxy, UriComponentsBuilder uri) { - String path = proxy.path("/proxy/path/"); - return proxy.uri(home.toString() + "/foos/" + path).get(); - } - - @GetMapping("/proxy/html/**") - public ResponseEntity proxyHtml(ProxyExchange proxy, UriComponentsBuilder uri) { - String path = proxy.path("/proxy/html"); - return proxy.uri(home.toString() + path).get(); - } - - @GetMapping("/proxy/typeless/**") - public ResponseEntity proxyTypeless(ProxyExchange proxy, UriComponentsBuilder uri) { - String path = proxy.path("/proxy/typeless"); - return proxy.uri(home.toString() + path).get(); - } - - @GetMapping("/proxy/missing/{id}") - public ResponseEntity proxyMissing(@PathVariable Integer id, ProxyExchange proxy) { - return proxy.uri(home.toString() + "/missing/" + id).get(); - } - - @GetMapping("/proxy") - public ResponseEntity proxyUri(ProxyExchange proxy) { - return proxy.uri(home.toString() + "/foos").get(); - } - - @PostMapping("/proxy/{id}") - public ResponseEntity proxyBars(@PathVariable Integer id, @RequestBody Map body, - ProxyExchange> proxy) { - body.put("id", id); - return proxy.uri(home.toString() + "/bars").body(Arrays.asList(body)).post(this::first); - } - - @PostMapping("/proxy") - public ResponseEntity barsWithNoBody(ProxyExchange proxy) { - return proxy.uri(home.toString() + "/bars").post(); - } - - @PostMapping("/proxy/entity") - public ResponseEntity explicitEntity(@RequestBody Foo foo, ProxyExchange proxy) { - return proxy.uri(home.toString() + "/bars").body(Arrays.asList(foo)).post(); - } - - @PostMapping("/proxy/type") - public ResponseEntity> explicitEntityWithType(@RequestBody Foo foo, - ProxyExchange> proxy) { - return proxy.uri(home.toString() + "/bars").body(Arrays.asList(foo)).post(); - } - - @PostMapping("/proxy/single") - public ResponseEntity implicitEntity(@RequestBody Foo foo, ProxyExchange> proxy) { - return proxy.uri(home.toString() + "/bars").body(Arrays.asList(foo)).post(this::first); - } - - @PostMapping("/proxy/converter") - public ResponseEntity implicitEntityWithConverter(@RequestBody Foo foo, - ProxyExchange> proxy) { - return proxy.uri(home.toString() + "/bars").body(Arrays.asList(foo)) - .post(response -> ResponseEntity.status(response.getStatusCode()).headers(response.getHeaders()) - .body(response.getBody().iterator().next())); - } - - @PostMapping("/proxy/no-body") - public ResponseEntity noBody(ProxyExchange proxy) { - return proxy.uri(home.toString() + "/foos").post(); - } - - @DeleteMapping("/proxy/{id}/no-body") - public ResponseEntity deleteWithoutBody(@PathVariable Integer id, ProxyExchange proxy) { - return proxy.uri(home.toString() + "/foos/" + id + "/no-body").delete(); - } - - @DeleteMapping("/proxy/{id}") - public ResponseEntity deleteWithBody(@PathVariable Integer id, @RequestBody Foo foo, - ProxyExchange proxy) { - return proxy.uri(home.toString() + "/foos/" + id).body(foo).delete(response -> ResponseEntity - .status(response.getStatusCode()).headers(response.getHeaders()).body(response.getBody())); - } - - @GetMapping("/forward/**") - public void forward(ProxyExchange proxy) { - String path = proxy.path("/forward"); - if (path.startsWith("/special")) { - proxy.header("X-Custom", "FOO"); - path = proxy.path("/forward/special"); - } - proxy.forward(path); - } - - @PostMapping("/forward/**") - public void postForward(ProxyExchange proxy) { - String path = proxy.path("/forward"); - if (path.startsWith("/special")) { - proxy.header("X-Custom", "FOO"); - path = proxy.path("/forward/special"); - } - proxy.forward(path); - } - - @PostMapping("/forward/body/**") - public void postForwardBody(@RequestBody byte[] body, ProxyExchange proxy) { - String path = proxy.path("/forward/body"); - proxy.body(body).forward(path); - } - - @SuppressWarnings("unused") - @PostMapping("/forward/forget/**") - public void postForwardForgetBody(@RequestBody byte[] body, ProxyExchange proxy) { - String path = proxy.path("/forward/forget"); - proxy.forward(path); - } - - @GetMapping("/proxy/headers") - @SuppressWarnings("Duplicates") - public ResponseEntity>> headers(ProxyExchange>> proxy) { - proxy.sensitive("foo", "hello"); - proxy.header("bar", "hello"); - proxy.header("abc", "123"); - proxy.header("hello", "world"); - return proxy.uri(home.toString() + "/headers").get(); - } - - @GetMapping("/proxy/sensitive-headers-default") - public ResponseEntity>> defaultSensitiveHeaders( - ProxyExchange>> proxy) { - proxy.header("bar", "hello"); - proxy.header("abc", "123"); - proxy.header("hello", "world"); - return proxy.uri(home.toString() + "/headers").get(); - } - - private ResponseEntity first(ResponseEntity> response) { - return ResponseEntity.status(response.getStatusCode()).headers(response.getHeaders()) - .body(response.getBody().iterator().next()); - } - - } - - @RestController - static class TestController { - - @GetMapping("/foos") - public List foos() { - return Arrays.asList(new Foo("hello")); - } - - @PostMapping("/foos") - public Foo postFoos() { - return new Foo("hello"); - } - - @GetMapping("/foos/{id}") - public Foo foo(@PathVariable Integer id, @RequestHeader HttpHeaders headers) { - String custom = headers.getFirst("X-Custom"); - return new Foo(id == 1 ? "foo" : custom != null ? custom : "bye"); - } - - @DeleteMapping("/foos/{id}/no-body") - public ResponseEntity deleteFoo(@PathVariable Integer id) { - return ResponseEntity.ok().build(); - } - - @DeleteMapping("/foos/{id}") - public ResponseEntity deleteFoo(@PathVariable Integer id, @RequestBody Foo foo) { - return ResponseEntity.ok().body(Collections.singletonMap("deleted", foo)); - } - - @PostMapping("/bars") - public List bars(@RequestBody List foos, @RequestHeader HttpHeaders headers) { - String custom = headers.getFirst("X-Custom"); - custom = custom == null ? "" : custom; - custom = headers.getFirst("forwarded") == null ? custom : headers.getFirst("forwarded") + ";" + custom; - return Arrays.asList(new Bar(custom + foos.iterator().next().getName())); - } - - @GetMapping("/headers") - public Map> headers(@RequestHeader HttpHeaders headers) { - return new LinkedMultiValueMap<>(headers); - } - - } - - @JsonIgnoreProperties(ignoreUnknown = true) - static class Foo { - - private String name; - - Foo() { - } - - Foo(String name) { - this.name = name; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - } - - @JsonIgnoreProperties(ignoreUnknown = true) - static class Bar { - - private String name; - - Bar() { - } - - Bar(String name) { - this.name = name; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - } - - } - -} diff --git a/spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/config/ProxyExchangeArgumentResolverTest.java b/spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/config/ProxyExchangeArgumentResolverTest.java deleted file mode 100644 index fc11230d..00000000 --- a/spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/config/ProxyExchangeArgumentResolverTest.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * 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.net.URI; -import java.util.Collections; -import java.util.List; - -import org.junit.jupiter.api.BeforeEach; -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.context.SpringBootTest.WebEnvironment; -import org.springframework.boot.test.web.client.TestRestTemplate; -import org.springframework.boot.test.web.server.LocalServerPort; -import org.springframework.boot.web.client.RestTemplateBuilder; -import org.springframework.cloud.gateway.mvc.ProxyExchange; -import org.springframework.context.annotation.Bean; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.http.client.SimpleClientHttpRequestFactory; -import org.springframework.http.converter.ByteArrayHttpMessageConverter; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.client.DefaultResponseErrorHandler; - -import static org.assertj.core.api.Assertions.assertThat; - -@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) -@ContextConfiguration(classes = ProxyExchangeArgumentResolverTest.ProxyExchangeArgumentResolverTestApplication.class) -public class ProxyExchangeArgumentResolverTest { - - @Autowired - private TestRestTemplate rest; - - @Autowired - private ProxyExchangeArgumentResolverTestApplication application; - - @LocalServerPort - private int port; - - @BeforeEach - public void setUp() throws Exception { - application.setHome(new URI("http://localhost:" + port)); - rest.getRestTemplate().setRequestFactory(new SimpleClientHttpRequestFactory()); - } - - @Test - public void shouldProxyRequestWhenProxyExchangeArgumentResolverIsNotConfigured() { - final ResponseEntity response = rest.getForEntity("/proxy", String.class); - assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); - assertThat(response.getBody()).contains("Hello World"); - } - - @SpringBootApplication - static class ProxyExchangeArgumentResolverTestApplication { - - @Autowired - private ProxyController controller; - - @Bean - public ProxyExchangeArgumentResolver proxyExchangeArgumentResolver() { - RestTemplateBuilder builder = new RestTemplateBuilder(); - builder.errorHandler(new DefaultResponseErrorHandler()); - builder.messageConverters(new ByteArrayHttpMessageConverter()); - return new ProxyExchangeArgumentResolver(builder.build()); - } - - public void setHome(final URI home) { - controller.setHome(home); - } - - @RestController - static class ProxyController { - - private URI home; - - public void setHome(URI home) { - this.home = home; - } - - @GetMapping("/proxy") - public ResponseEntity proxyFoo(ProxyExchange proxy) { - return proxy.uri(home.toString() + "/foo").get(); - } - - } - - @RestController - static class TestController { - - @GetMapping("/foo") - public List foo() { - return Collections.singletonList("Hello World"); - } - - } - - } - -} diff --git a/spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/http/AbstractBufferingClientHttpRequest.java b/spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/http/AbstractBufferingClientHttpRequest.java deleted file mode 100644 index 64dfa458..00000000 --- a/spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/http/AbstractBufferingClientHttpRequest.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * 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.http; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStream; - -import org.springframework.http.HttpHeaders; -import org.springframework.http.client.AbstractClientHttpRequest; -import org.springframework.http.client.ClientHttpResponse; - -abstract class AbstractBufferingClientHttpRequest extends AbstractClientHttpRequest { - - private ByteArrayOutputStream bufferedOutput = new ByteArrayOutputStream(1024); - - protected OutputStream getBodyInternal(HttpHeaders headers) { - return bufferedOutput; - } - - protected abstract ClientHttpResponse executeInternal(HttpHeaders headers, byte[] body) throws IOException; - - protected ClientHttpResponse executeInternal(HttpHeaders headers) throws IOException { - final byte[] bytes = bufferedOutput.toByteArray(); - if (headers.getContentLength() < 0L) { - headers.setContentLength(bytes.length); - } - - final ClientHttpResponse response = executeInternal(headers, bytes); - bufferedOutput = new ByteArrayOutputStream(0); - return response; - } - -} diff --git a/spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/http/GetWithBodyRequestClientHttpRequestFactory.java b/spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/http/GetWithBodyRequestClientHttpRequestFactory.java deleted file mode 100644 index fd4a0653..00000000 --- a/spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/http/GetWithBodyRequestClientHttpRequestFactory.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * 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.http; - -import java.io.Closeable; -import java.io.IOException; -import java.net.URI; - -import org.apache.http.client.HttpClient; -import org.apache.http.client.config.RequestConfig; -import org.apache.http.client.methods.Configurable; -import org.apache.http.client.methods.HttpDelete; -import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; -import org.apache.http.client.methods.HttpHead; -import org.apache.http.client.methods.HttpOptions; -import org.apache.http.client.methods.HttpPatch; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.client.methods.HttpPut; -import org.apache.http.client.methods.HttpTrace; -import org.apache.http.client.methods.HttpUriRequest; -import org.apache.http.client.protocol.HttpClientContext; -import org.apache.http.impl.client.HttpClients; -import org.apache.http.protocol.HttpContext; - -import org.springframework.beans.factory.DisposableBean; -import org.springframework.http.HttpMethod; -import org.springframework.http.client.ClientHttpRequest; -import org.springframework.http.client.ClientHttpRequestFactory; - -public class GetWithBodyRequestClientHttpRequestFactory implements ClientHttpRequestFactory, DisposableBean { - - private final HttpClient httpClient; - - public GetWithBodyRequestClientHttpRequestFactory() { - this.httpClient = HttpClients.createSystem(); - } - - @Override - public ClientHttpRequest createRequest(final URI uri, final HttpMethod httpMethod) throws IOException { - final HttpUriRequest httpRequest = createHttpUriRequest(httpMethod, uri); - final HttpContext context = HttpClientContext.create(); - - if (context.getAttribute("http.request-config") == null) { - RequestConfig config = null; - if (httpRequest instanceof Configurable) { - config = ((Configurable) httpRequest).getConfig(); - } - if (config == null) { - config = createRequestConfig(httpClient); - } - if (config != null) { - context.setAttribute("http.request-config", config); - } - } - - return new HttpComponentsClientHttpRequest(httpClient, httpRequest, context); - } - - private HttpUriRequest createHttpUriRequest(final HttpMethod httpMethod, final URI uri) { - if (httpMethod.equals(HttpMethod.GET)) { - return new GetWithEntity(uri); - } - else if (httpMethod.equals(HttpMethod.HEAD)) { - return new HttpHead(uri); - } - else if (httpMethod.equals(HttpMethod.OPTIONS)) { - return new HttpOptions(uri); - } - else if (httpMethod.equals(HttpMethod.POST)) { - return new HttpPost(uri); - } - else if (httpMethod.equals(HttpMethod.DELETE)) { - return new HttpDelete(uri); - } - else if (httpMethod.equals(HttpMethod.PUT)) { - return new HttpPut(uri); - } - else if (httpMethod.equals(HttpMethod.PATCH)) { - return new HttpPatch(uri); - } - else if (httpMethod.equals(HttpMethod.TRACE)) { - return new HttpTrace(uri); - } - - throw new IllegalArgumentException("Invalid HTTP method: " + httpMethod); - } - - private RequestConfig createRequestConfig(final HttpClient client) { - if (client instanceof Configurable) { - return ((Configurable) client).getConfig(); - } - return null; - } - - @Override - public void destroy() throws Exception { - if (httpClient instanceof Closeable) { - ((Closeable) httpClient).close(); - } - } - - public static class GetWithEntity extends HttpEntityEnclosingRequestBase { - - public static final String METHOD_NAME = "GET"; - - public GetWithEntity(final URI uri) { - setURI(uri); - } - - @Override - public String getMethod() { - return METHOD_NAME; - } - - } - -} diff --git a/spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/http/HttpComponentsClientHttpRequest.java b/spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/http/HttpComponentsClientHttpRequest.java deleted file mode 100644 index 9d60ccf3..00000000 --- a/spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/http/HttpComponentsClientHttpRequest.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * 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.http; - -import java.io.IOException; -import java.net.URI; -import java.util.Iterator; - -import org.apache.http.HttpEntityEnclosingRequest; -import org.apache.http.HttpResponse; -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.HttpUriRequest; -import org.apache.http.entity.ByteArrayEntity; -import org.apache.http.protocol.HttpContext; - -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.client.ClientHttpResponse; -import org.springframework.util.StringUtils; - -final class HttpComponentsClientHttpRequest extends AbstractBufferingClientHttpRequest { - - private static final String COOKIE_HEADER_NAME = "Cookie"; - - private static final String CONTENT_LENGTH_HEADER_NAME = "Content-Length"; - - private static final String TRANSFER_ENCODING_HEADER_NAME = "Transfer-Encoding"; - - private final HttpClient httpClient; - - private final HttpUriRequest httpRequest; - - private final HttpContext httpContext; - - HttpComponentsClientHttpRequest(final HttpClient httpClient, final HttpUriRequest httpRequest, - final HttpContext httpContext) { - this.httpClient = httpClient; - this.httpRequest = httpRequest; - this.httpContext = httpContext; - } - - @Override - public HttpMethod getMethod() { - return HttpMethod.valueOf(httpRequest.getMethod()); - } - - @Override - public URI getURI() { - return httpRequest.getURI(); - } - - @Override - protected ClientHttpResponse executeInternal(final HttpHeaders headers, final byte[] bufferedOutput) - throws IOException { - addHeaders(headers); - attachBodyRequest(bufferedOutput); - - final HttpResponse response = httpClient.execute(httpRequest, httpContext); - return new HttpComponentsClientHttpResponse(response); - } - - private void addHeaders(final HttpHeaders headers) { - headers.forEach((headerName, headerValues) -> { - if (COOKIE_HEADER_NAME.equalsIgnoreCase(headerName)) { - String headerValue = StringUtils.collectionToDelimitedString(headerValues, ": "); - httpRequest.addHeader(headerName, headerValue); - } - else if (!CONTENT_LENGTH_HEADER_NAME.equalsIgnoreCase(headerName) - && !TRANSFER_ENCODING_HEADER_NAME.equalsIgnoreCase(headerName)) { - final Iterator it = headerValues.iterator(); - while (it.hasNext()) { - String value = it.next(); - httpRequest.addHeader(headerName, value); - } - } - }); - } - - private void attachBodyRequest(final byte[] bufferedOutput) { - if (httpRequest instanceof HttpEntityEnclosingRequest) { - ((HttpEntityEnclosingRequest) httpRequest).setEntity(new ByteArrayEntity(bufferedOutput)); - } - } - -} diff --git a/spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/http/HttpComponentsClientHttpResponse.java b/spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/http/HttpComponentsClientHttpResponse.java deleted file mode 100644 index 39f0e4b3..00000000 --- a/spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/http/HttpComponentsClientHttpResponse.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * 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.http; - -import java.io.Closeable; -import java.io.IOException; -import java.io.InputStream; - -import org.apache.http.Header; -import org.apache.http.HttpEntity; -import org.apache.http.HttpResponse; -import org.apache.http.util.EntityUtils; - -import org.springframework.http.HttpHeaders; -import org.springframework.http.client.AbstractClientHttpResponse; -import org.springframework.lang.Nullable; -import org.springframework.util.StreamUtils; - -final class HttpComponentsClientHttpResponse extends AbstractClientHttpResponse { - - private final HttpResponse response; - - @Nullable - private HttpHeaders headers; - - HttpComponentsClientHttpResponse(final HttpResponse response) { - this.response = response; - } - - @Override - public int getRawStatusCode() throws IOException { - return response.getStatusLine().getStatusCode(); - } - - @Override - public String getStatusText() throws IOException { - return response.getStatusLine().getReasonPhrase(); - } - - @Override - public HttpHeaders getHeaders() { - if (headers == null) { - headers = new HttpHeaders(); - final Header[] responseHeaders = response.getAllHeaders(); - for (Header header : responseHeaders) { - headers.add(header.getName(), header.getValue()); - } - } - return headers; - } - - @Override - public InputStream getBody() throws IOException { - final HttpEntity entity = response.getEntity(); - return entity != null ? entity.getContent() : StreamUtils.emptyInput(); - } - - @Override - public void close() { - try { - try { - EntityUtils.consume(response.getEntity()); - } - finally { - if (response instanceof Closeable) { - ((Closeable) response).close(); - } - } - } - catch (IOException ignored) { - } - } - -} diff --git a/spring-cloud-gateway-mvc/src/test/resources/static/test.html b/spring-cloud-gateway-mvc/src/test/resources/static/test.html deleted file mode 100644 index 0cfed139..00000000 --- a/spring-cloud-gateway-mvc/src/test/resources/static/test.html +++ /dev/null @@ -1,4 +0,0 @@ - -Test - - \ No newline at end of file diff --git a/spring-cloud-gateway-sample/.jdk8 b/spring-cloud-gateway-sample/.jdk8 deleted file mode 100644 index e69de29b..00000000 diff --git a/spring-cloud-gateway-sample/pom.xml b/spring-cloud-gateway-sample/pom.xml deleted file mode 100644 index 0af4d599..00000000 --- a/spring-cloud-gateway-sample/pom.xml +++ /dev/null @@ -1,177 +0,0 @@ - - - 4.0.0 - - spring-cloud-gateway-sample - jar - - Spring Cloud Gateway Sample - Spring Cloud Gateway Sample - - - - - - org.springframework.cloud - spring-cloud-gateway - 4.1.0-SNAPSHOT - .. - - - - - org.springframework.boot - spring-boot-starter-actuator - - - org.springframework.boot - spring-boot-starter-webflux - - - org.springframework.cloud - spring-cloud-starter-gateway - - - org.isomorphism - token-bucket - 1.7 - - - org.springframework.cloud - spring-cloud-gateway-server - ${project.version} - test-jar - test - - - org.springframework.boot - spring-boot-starter-test - test - - - org.junit.vintage - junit-vintage-engine - test - - - org.springframework.cloud - spring-cloud-test-support - test - - - org.springframework.cloud - spring-cloud-loadbalancer - test - - - io.projectreactor - reactor-test - test - - - org.assertj - assertj-core - test - - - org.jetbrains.kotlin - kotlin-stdlib - true - - - org.jetbrains.kotlin - kotlin-reflect - true - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - org.jetbrains.kotlin - kotlin-maven-plugin - - - -Xjsr305=strict - - 17 - - - - compile - compile - - compile - - - - src/main/java - src/main/kotlin - - - - - test-compile - test-compile - - test-compile - - - - src/test/java - src/test/kotlin - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - - -parameters - - - - - - default-compile - none - - - - default-testCompile - none - - - java-compile - compile - - compile - - - - java-test-compile - test-compile - - testCompile - - - - - - maven-deploy-plugin - - true - - - - - - diff --git a/spring-cloud-gateway-sample/src/main/java/org/springframework/cloud/gateway/sample/GatewaySampleApplication.java b/spring-cloud-gateway-sample/src/main/java/org/springframework/cloud/gateway/sample/GatewaySampleApplication.java deleted file mode 100644 index 15842cac..00000000 --- a/spring-cloud-gateway-sample/src/main/java/org/springframework/cloud/gateway/sample/GatewaySampleApplication.java +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Copyright 2013-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.sample; - -import java.util.Map; -import java.util.concurrent.TimeUnit; - -import reactor.core.publisher.Mono; - -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.gateway.route.RouteLocator; -import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; -import org.springframework.http.MediaType; -import org.springframework.web.reactive.function.BodyInserters; -import org.springframework.web.reactive.function.server.RequestPredicates; -import org.springframework.web.reactive.function.server.RouterFunction; -import org.springframework.web.reactive.function.server.RouterFunctions; -import org.springframework.web.reactive.function.server.ServerResponse; - -/** - * @author Spencer Gibb - */ -@SpringBootConfiguration -@EnableAutoConfiguration -@Import(AdditionalRoutes.class) -public class GatewaySampleApplication { - - public static final String HELLO_FROM_FAKE_ACTUATOR_METRICS_GATEWAY_REQUESTS = "hello from fake /actuator/metrics/spring.cloud.gateway.requests"; - - @Value("${test.uri:http://httpbin.org:80}") - String uri; - - public static void main(String[] args) { - SpringApplication.run(GatewaySampleApplication.class, args); - } - - @Bean - public RouteLocator customRouteLocator(RouteLocatorBuilder builder) { - //@formatter:off - // String uri = "http://httpbin.org:80"; - // String uri = "http://localhost:9080"; - return builder.routes() - .route(r -> r.host("**.abc.org").and().path("/anything/png") - .filters(f -> - f.prefixPath("/httpbin") - .addResponseHeader("X-TestHeader", "foobar")) - .uri(uri) - ) - .route("read_body_pred", r -> r.host("*.readbody.org") - .and().readBody(String.class, - s -> s.trim().equalsIgnoreCase("hi")) - .filters(f -> f.prefixPath("/httpbin") - .addResponseHeader("X-TestHeader", "read_body_pred") - ).uri(uri) - ) - .route("rewrite_request_obj", r -> r.host("*.rewriterequestobj.org") - .filters(f -> f.prefixPath("/httpbin") - .addResponseHeader("X-TestHeader", "rewrite_request") - .modifyRequestBody(String.class, Hello.class, MediaType.APPLICATION_JSON_VALUE, - (exchange, s) -> { - return Mono.just(new Hello(s.toUpperCase())); - }) - ).uri(uri) - ) - .route("rewrite_request_upper", r -> r.host("*.rewriterequestupper.org") - .filters(f -> f.prefixPath("/httpbin") - .addResponseHeader("X-TestHeader", "rewrite_request_upper") - .modifyRequestBody(String.class, String.class, - (exchange, s) -> { - return Mono.just(s.toUpperCase() + s.toUpperCase()); - }) - ).uri(uri) - ) - .route("rewrite_response_upper", r -> r.host("*.rewriteresponseupper.org") - .filters(f -> f.prefixPath("/httpbin") - .addResponseHeader("X-TestHeader", "rewrite_response_upper") - .modifyResponseBody(String.class, String.class, - (exchange, s) -> { - return Mono.just(s.toUpperCase()); - }) - ).uri(uri) - ) - .route("rewrite_empty_response", r -> r.host("*.rewriteemptyresponse.org") - .filters(f -> f.prefixPath("/httpbin") - .addResponseHeader("X-TestHeader", "rewrite_empty_response") - .modifyResponseBody(String.class, String.class, - (exchange, s) -> { - if (s == null) { - return Mono.just("emptybody"); - } - return Mono.just(s.toUpperCase()); - }) - - ).uri(uri) - ) - .route("rewrite_response_fail_supplier", r -> r.host("*.rewriteresponsewithfailsupplier.org") - .filters(f -> f.prefixPath("/httpbin") - .addResponseHeader("X-TestHeader", "rewrite_response_fail_supplier") - .modifyResponseBody(String.class, String.class, - (exchange, s) -> { - if (s == null) { - return Mono.error(new IllegalArgumentException("this should not happen")); - } - return Mono.just(s.toUpperCase()); - }) - ).uri(uri) - ) - .route("rewrite_response_obj", r -> r.host("*.rewriteresponseobj.org") - .filters(f -> f.prefixPath("/httpbin") - .addResponseHeader("X-TestHeader", "rewrite_response_obj") - .modifyResponseBody(Map.class, String.class, MediaType.TEXT_PLAIN_VALUE, - (exchange, map) -> { - Object data = map.get("data"); - return Mono.just(data.toString()); - }) - .setResponseHeader("Content-Type", MediaType.TEXT_PLAIN_VALUE) - ).uri(uri) - ) - .route(r -> r.path("/image/webp") - .filters(f -> - f.prefixPath("/httpbin") - .addResponseHeader("X-AnotherHeader", "baz")) - .uri(uri) - ) - .route(r -> r.order(-1) - .host("**.throttle.org").and().path("/get") - .filters(f -> f.prefixPath("/httpbin") - .filter(new ThrottleGatewayFilter() - .setCapacity(1) - .setRefillTokens(1) - .setRefillPeriod(10) - .setRefillUnit(TimeUnit.SECONDS))) - .uri(uri) - ) - .build(); - //@formatter:on - } - - @Bean - public RouterFunction testFunRouterFunction() { - RouterFunction route = RouterFunctions.route(RequestPredicates.path("/testfun"), - request -> ServerResponse.ok().body(BodyInserters.fromValue("hello"))); - return route; - } - - @Bean - public RouterFunction testWhenMetricPathIsNotMeet() { - RouterFunction route = RouterFunctions.route( - RequestPredicates.path("/actuator/metrics/spring.cloud.gateway.requests"), request -> ServerResponse - .ok().body(BodyInserters.fromValue(HELLO_FROM_FAKE_ACTUATOR_METRICS_GATEWAY_REQUESTS))); - return route; - } - - static class Hello { - - String message; - - Hello() { - } - - Hello(String message) { - this.message = message; - } - - public String getMessage() { - return message; - } - - public void setMessage(String message) { - this.message = message; - } - - } - -} diff --git a/spring-cloud-gateway-sample/src/main/java/org/springframework/cloud/gateway/sample/ThrottleGatewayFilter.java b/spring-cloud-gateway-sample/src/main/java/org/springframework/cloud/gateway/sample/ThrottleGatewayFilter.java deleted file mode 100644 index 47487c8e..00000000 --- a/spring-cloud-gateway-sample/src/main/java/org/springframework/cloud/gateway/sample/ThrottleGatewayFilter.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright 2013-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.sample; - -import java.util.concurrent.TimeUnit; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.isomorphism.util.TokenBucket; -import org.isomorphism.util.TokenBuckets; -import reactor.core.publisher.Mono; - -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.GatewayFilterChain; -import org.springframework.http.HttpStatus; -import org.springframework.web.server.ServerWebExchange; - -/** - * Sample throttling filter. See https://github.com/bbeck/token-bucket - */ -public class ThrottleGatewayFilter implements GatewayFilter { - - private static final Log log = LogFactory.getLog(ThrottleGatewayFilter.class); - - int capacity; - - int refillTokens; - - int refillPeriod; - - TimeUnit refillUnit; - - public int getCapacity() { - return capacity; - } - - public ThrottleGatewayFilter setCapacity(int capacity) { - this.capacity = capacity; - return this; - } - - public int getRefillTokens() { - return refillTokens; - } - - public ThrottleGatewayFilter setRefillTokens(int refillTokens) { - this.refillTokens = refillTokens; - return this; - } - - public int getRefillPeriod() { - return refillPeriod; - } - - public ThrottleGatewayFilter setRefillPeriod(int refillPeriod) { - this.refillPeriod = refillPeriod; - return this; - } - - public TimeUnit getRefillUnit() { - return refillUnit; - } - - public ThrottleGatewayFilter setRefillUnit(TimeUnit refillUnit) { - this.refillUnit = refillUnit; - return this; - } - - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - - TokenBucket tokenBucket = TokenBuckets.builder().withCapacity(capacity) - .withFixedIntervalRefillStrategy(refillTokens, refillPeriod, refillUnit).build(); - - // TODO: get a token bucket for a key - log.debug("TokenBucket capacity: " + tokenBucket.getCapacity()); - boolean consumed = tokenBucket.tryConsume(); - if (consumed) { - return chain.filter(exchange); - } - exchange.getResponse().setStatusCode(HttpStatus.TOO_MANY_REQUESTS); - return exchange.getResponse().setComplete(); - } - -} diff --git a/spring-cloud-gateway-sample/src/main/kotlin/org/springframework/cloud/gateway/sample/AdditionalRoutes.kt b/spring-cloud-gateway-sample/src/main/kotlin/org/springframework/cloud/gateway/sample/AdditionalRoutes.kt deleted file mode 100644 index d6321067..00000000 --- a/spring-cloud-gateway-sample/src/main/kotlin/org/springframework/cloud/gateway/sample/AdditionalRoutes.kt +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2017-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.sample - -import org.springframework.beans.factory.annotation.Value -import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder -import org.springframework.cloud.gateway.route.builder.filters -import org.springframework.cloud.gateway.route.builder.routes -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration - -@Configuration(proxyBeanMethods = false) -open class AdditionalRoutes { - - @Value("\${test.uri:http://httpbin.org:80}") - var uri: String? = null - - @Bean - open fun additionalRouteLocator(builder: RouteLocatorBuilder) = builder.routes { - route(id = "test-kotlin") { - host("kotlin.abc.org") and path("/anything/kotlinroute") - filters { - prefixPath("/httpbin") - addResponseHeader("X-TestHeader", "foobar") - } - uri(uri) - } - } - -} \ No newline at end of file diff --git a/spring-cloud-gateway-sample/src/main/resources/application.yml b/spring-cloud-gateway-sample/src/main/resources/application.yml deleted file mode 100644 index 4cf839df..00000000 --- a/spring-cloud-gateway-sample/src/main/resources/application.yml +++ /dev/null @@ -1,43 +0,0 @@ -test: - # hostport: httpbin.org:80 - # hostport: localhost:5000 - # uri: http://${test.hostport} - uri: lb://httpbin - - -spring: - jmx: - enabled: false - cloud: - gateway: - default-filters: - - PrefixPath=/httpbin - - AddResponseHeader=X-Response-Default-Foo, Default-Bar - - routes: - # ===================================== - # to run server - # $ wscat --listen 9000 - # to run client - # $ wscat --connect ws://localhost:8080/echo - - id: websocket_test - uri: ws://localhost:9000 - order: 9000 - predicates: - - Path=/echo - # ===================================== - - id: default_path_to_httpbin - uri: ${test.uri} - order: 10000 - predicates: - - Path=/** - -logging: - level: - org.springframework.cloud.gateway: TRACE - org.springframework.http.server.reactive: DEBUG - org.springframework.web.reactive: DEBUG - reactor.ipc.netty: DEBUG - reactor.netty: DEBUG - -management.endpoints.web.exposure.include: '*' diff --git a/spring-cloud-gateway-sample/src/test/java/org/springframework/cloud/gateway/sample/GatewaySampleApplicationTests.java b/spring-cloud-gateway-sample/src/test/java/org/springframework/cloud/gateway/sample/GatewaySampleApplicationTests.java deleted file mode 100644 index 60aa4fdf..00000000 --- a/spring-cloud-gateway-sample/src/test/java/org/springframework/cloud/gateway/sample/GatewaySampleApplicationTests.java +++ /dev/null @@ -1,217 +0,0 @@ -/* - * Copyright 2013-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.sample; - -import java.io.IOException; -import java.time.Duration; -import java.util.Map; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.web.server.LocalServerPort; -import org.springframework.cloud.client.DefaultServiceInstance; -import org.springframework.cloud.gateway.config.GatewayMetricsProperties; -import org.springframework.cloud.gateway.test.HttpBinCompatibleController; -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.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.core.env.Environment; -import org.springframework.test.util.TestSocketUtils; -import org.springframework.test.web.reactive.server.WebTestClient; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; - -/** - * @author Spencer Gibb - */ -@SpringBootTest(classes = { GatewaySampleApplicationTests.TestConfig.class }, webEnvironment = RANDOM_PORT, - properties = { "management.endpoint.gateway.enabled=true", "management.server.port=${test.port}" }) -public class GatewaySampleApplicationTests { - - protected static int managementPort; - - @Autowired - GatewayMetricsProperties metricsProperties; - - @LocalServerPort - protected int port = 0; - - protected WebTestClient webClient; - - protected String baseUri; - - @BeforeAll - public static void beforeClass() { - managementPort = TestSocketUtils.findAvailableTcpPort(); - - System.setProperty("test.port", String.valueOf(managementPort)); - } - - @AfterAll - public static void afterClass() { - System.clearProperty("test.port"); - } - - @BeforeEach - public void setup() { - baseUri = "http://localhost:" + port; - this.webClient = WebTestClient.bindToServer().responseTimeout(Duration.ofSeconds(10)).baseUrl(baseUri).build(); - } - - @Test - public void contextLoads() { - webClient.get().uri("/get").exchange().expectStatus().isOk(); - } - - @Test - @SuppressWarnings("unchecked") - public void readBodyPredicateStringWorks() { - webClient.post().uri("/post").header("Host", "www.readbody.org").bodyValue("hi").exchange().expectStatus() - .isOk().expectHeader().valueEquals("X-TestHeader", "read_body_pred").expectBody(Map.class) - .consumeWith(result -> assertThat(result.getResponseBody()).containsEntry("data", "hi")); - } - - @Test - @SuppressWarnings("unchecked") - public void rewriteRequestBodyStringWorks() { - webClient.post().uri("/post").header("Host", "www.rewriterequestupper.org").bodyValue("hello").exchange() - .expectStatus().isOk().expectHeader().valueEquals("X-TestHeader", "rewrite_request_upper") - .expectBody(Map.class) - .consumeWith(result -> assertThat(result.getResponseBody()).containsEntry("data", "HELLOHELLO")); - } - - @Test - @SuppressWarnings("unchecked") - public void rewriteRequestBodyObjectWorks() { - webClient.post().uri("/post").header("Host", "www.rewriterequestobj.org").bodyValue("hello").exchange() - .expectStatus().isOk().expectHeader().valueEquals("X-TestHeader", "rewrite_request") - .expectBody(Map.class).consumeWith(result -> assertThat(result.getResponseBody()).containsEntry("data", - "{\"message\":\"HELLO\"}")); - } - - @Test - @SuppressWarnings("unchecked") - public void rewriteResponseBodyStringWorks() { - webClient.post().uri("/post").header("Host", "www.rewriteresponseupper.org").bodyValue("hello").exchange() - .expectStatus().isOk().expectHeader().valueEquals("X-TestHeader", "rewrite_response_upper") - .expectBody(Map.class) - .consumeWith(result -> assertThat(result.getResponseBody()).containsEntry("DATA", "HELLO")); - } - - @Test - @SuppressWarnings("unchecked") - public void rewriteResponseEmptyBodyToStringWorks() { - webClient.post().uri("/post/empty").header("Host", "www.rewriteemptyresponse.org").exchange().expectStatus() - .isOk().expectHeader().valueEquals("X-TestHeader", "rewrite_empty_response").expectBody(String.class) - .consumeWith(result -> assertThat(result.getResponseBody()).isEqualTo("emptybody")); - } - - @Test - @SuppressWarnings("unchecked") - public void emptyBodySupplierNotCalledWhenBodyPresent() { - webClient.post().uri("/post").header("Host", "www.rewriteresponsewithfailsupplier.org").bodyValue("hello") - .exchange().expectStatus().isOk().expectHeader() - .valueEquals("X-TestHeader", "rewrite_response_fail_supplier").expectBody(Map.class) - .consumeWith(result -> assertThat(result.getResponseBody()).containsEntry("DATA", "HELLO")); - } - - @Test - @SuppressWarnings("unchecked") - public void rewriteResponeBodyObjectWorks() { - webClient.post().uri("/post").header("Host", "www.rewriteresponseobj.org").bodyValue("hello").exchange() - .expectStatus().isOk().expectHeader().valueEquals("X-TestHeader", "rewrite_response_obj") - .expectBody(String.class) - .consumeWith(result -> assertThat(result.getResponseBody()).isEqualTo("hello")); - } - - @Test - public void complexPredicate() { - webClient.get().uri("/anything/png").header("Host", "www.abc.org").exchange().expectHeader() - .valueEquals("X-TestHeader", "foobar").expectStatus().isOk(); - } - - @Test - public void routeFromKotlin() { - webClient.get().uri("/anything/kotlinroute").header("Host", "kotlin.abc.org").exchange().expectHeader() - .valueEquals("X-TestHeader", "foobar").expectStatus().isOk(); - } - - @Test - public void actuatorManagementPort() { - webClient.get().uri("http://localhost:" + managementPort + "/actuator/gateway/routes").exchange().expectStatus() - .isOk(); - } - - @Test - public void actuatorMetrics() { - contextLoads(); - String metricName = metricsProperties.getPrefix() + ".requests"; - webClient.get().uri("http://localhost:" + managementPort + "/actuator/metrics/" + metricName).exchange() - .expectStatus().isOk().expectBody().consumeWith(i -> { - String body = new String(i.getResponseBodyContent()); - ObjectMapper mapper = new ObjectMapper(); - try { - JsonNode actualObj = mapper.readTree(body); - JsonNode findValue = actualObj.findValue("name"); - assertThat(findValue.asText()).as("Expected to find metric with name gateway.requests") - .isEqualTo(metricName); - } - catch (IOException e) { - throw new IllegalStateException(e); - } - }); - } - - @Configuration(proxyBeanMethods = false) - @EnableAutoConfiguration - @LoadBalancerClient(name = "httpbin", configuration = LoadBalancerConfig.class) - @Import(GatewaySampleApplication.class) - protected static class TestConfig { - - @Bean - public HttpBinCompatibleController httpBinCompatibleController() { - return new HttpBinCompatibleController(); - } - - } - - protected static class LoadBalancerConfig { - - @LocalServerPort - int port; - - @Bean - public ServiceInstanceListSupplier fixedServiceInstanceListSupplier(Environment env) { - return ServiceInstanceListSuppliers.from("httpbin", - new DefaultServiceInstance("httpbin-1", "httpbin", "localhost", port, false)); - } - - } - -} diff --git a/spring-cloud-gateway-sample/src/test/java/org/springframework/cloud/gateway/sample/GatewaySampleApplicationWithoutMetricsTests.java b/spring-cloud-gateway-sample/src/test/java/org/springframework/cloud/gateway/sample/GatewaySampleApplicationWithoutMetricsTests.java deleted file mode 100644 index cc7009c1..00000000 --- a/spring-cloud-gateway-sample/src/test/java/org/springframework/cloud/gateway/sample/GatewaySampleApplicationWithoutMetricsTests.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2013-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.sample; - -import java.time.Duration; - -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.springframework.boot.WebApplicationType; -import org.springframework.boot.builder.SpringApplicationBuilder; -import org.springframework.cloud.gateway.sample.GatewaySampleApplicationTests.TestConfig; -import org.springframework.cloud.test.ClassPathExclusions; -import org.springframework.cloud.test.ModifiedClassPathRunner; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.util.TestSocketUtils; -import org.springframework.test.web.reactive.server.WebTestClient; - -@RunWith(ModifiedClassPathRunner.class) -@ClassPathExclusions({ "micrometer-core.jar", "spring-boot-actuator-*.jar", - "spring-boot-actuator-autoconfigure-*.jar" }) -@DirtiesContext -public class GatewaySampleApplicationWithoutMetricsTests { - - static protected int port; - - protected WebTestClient webClient; - - protected String baseUri; - - @BeforeClass - public static void beforeClass() { - port = TestSocketUtils.findAvailableTcpPort(); - System.setProperty("server.port", Integer.toString(port)); - } - - @AfterClass - public static void afterClass() { - System.clearProperty("server.port"); - } - - @Before - public void setup() { - baseUri = "http://localhost:" + port; - this.webClient = WebTestClient.bindToServer().responseTimeout(Duration.ofSeconds(10)).baseUrl(baseUri).build(); - } - - protected ConfigurableApplicationContext init(Class config) { - return new SpringApplicationBuilder().web(WebApplicationType.REACTIVE) - .sources(GatewaySampleApplication.class, config).run(); - } - - @Test - public void actuatorMetrics() { - init(TestConfig.class); - webClient.get().uri("/get").exchange().expectStatus().isOk(); - webClient.get().uri("http://localhost:" + port + "/actuator/metrics/spring.cloud.gateway.requests").exchange() - .expectStatus().isOk().expectBody(String.class) - .isEqualTo(GatewaySampleApplication.HELLO_FROM_FAKE_ACTUATOR_METRICS_GATEWAY_REQUESTS); - } - -} diff --git a/spring-cloud-gateway-server-mvc/pom.xml b/spring-cloud-gateway-server-mvc/pom.xml deleted file mode 100644 index 089ec6e8..00000000 --- a/spring-cloud-gateway-server-mvc/pom.xml +++ /dev/null @@ -1,104 +0,0 @@ - - - - - 4.0.0 - - - org.springframework.cloud - spring-cloud-gateway - 4.1.0-SNAPSHOT - .. - - spring-cloud-gateway-server-mvc - jar - Spring Cloud Gateway Server MVC - Spring Cloud Gateway Server MVC - - ${basedir}/.. - - - - - - org.springframework.boot - spring-boot-configuration-processor - true - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-validation - - - org.springframework.cloud - spring-cloud-commons - - - org.springframework.cloud - spring-cloud-loadbalancer - true - - - org.springframework.cloud - spring-cloud-starter-circuitbreaker-resilience4j - true - - - org.springframework.security - spring-security-oauth2-client - true - - - org.springframework.retry - spring-retry - true - - - - com.bucket4j - bucket4j-core - true - - - - org.springframework.boot - spring-boot-starter-test - test - - - - com.bucket4j - bucket4j-caffeine - test - - - com.github.ben-manes.caffeine - caffeine - test - - - org.testcontainers - junit-jupiter - test - - - \ No newline at end of file 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 deleted file mode 100644 index 883b078b..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/GatewayServerMvcAutoConfiguration.java +++ /dev/null @@ -1,164 +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; - -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.boot.autoconfigure.AutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration; -import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration; -import org.springframework.boot.ssl.SslBundle; -import org.springframework.boot.ssl.SslBundles; -import org.springframework.boot.web.client.ClientHttpRequestFactories; -import org.springframework.boot.web.client.ClientHttpRequestFactorySettings; -import org.springframework.boot.web.client.RestClientCustomizer; -import org.springframework.cloud.gateway.server.mvc.common.ArgumentSupplierBeanPostProcessor; -import org.springframework.cloud.gateway.server.mvc.config.GatewayMvcProperties; -import org.springframework.cloud.gateway.server.mvc.config.GatewayMvcPropertiesBeanDefinitionRegistrar; -import org.springframework.cloud.gateway.server.mvc.filter.ForwardedRequestHeadersFilter; -import org.springframework.cloud.gateway.server.mvc.filter.HttpHeadersFilter.RequestHttpHeadersFilter; -import org.springframework.cloud.gateway.server.mvc.filter.HttpHeadersFilter.ResponseHttpHeadersFilter; -import org.springframework.cloud.gateway.server.mvc.filter.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.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.ProxyExchange; -import org.springframework.cloud.gateway.server.mvc.handler.ProxyExchangeHandlerFunction; -import org.springframework.cloud.gateway.server.mvc.handler.RestClientProxyExchange; -import org.springframework.cloud.gateway.server.mvc.predicate.PredicateDiscoverer; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; -import org.springframework.http.client.ClientHttpRequestFactory; -import org.springframework.http.client.JdkClientHttpRequestFactory; -import org.springframework.util.StringUtils; -import org.springframework.web.client.RestClient; - -@AutoConfiguration(after = { RestTemplateAutoConfiguration.class, RestClientAutoConfiguration.class }) -@Import(GatewayMvcPropertiesBeanDefinitionRegistrar.class) -public class GatewayServerMvcAutoConfiguration { - - @Bean - public ArgumentSupplierBeanPostProcessor argumentSupplierBeanPostProcessor(ApplicationEventPublisher publisher) { - return new ArgumentSupplierBeanPostProcessor(publisher); - } - - @Bean - public RestClientCustomizer gatewayRestClientCustomizer(ClientHttpRequestFactory requestFactory) { - return restClientBuilder -> restClientBuilder.requestFactory(requestFactory); - } - - @Bean - @ConditionalOnMissingBean(ProxyExchange.class) - public RestClientProxyExchange restClientProxyExchange(RestClient.Builder restClientBuilder) { - return new RestClientProxyExchange(restClientBuilder.build()); - } - - @Bean - @ConditionalOnMissingBean - public ForwardedRequestHeadersFilter forwardedRequestHeadersFilter() { - return new ForwardedRequestHeadersFilter(); - } - - @Bean - @ConditionalOnMissingBean - public ClientHttpRequestFactory gatewayClientHttpRequestFactory(GatewayMvcProperties gatewayMvcProperties, - SslBundles sslBundles) { - GatewayMvcProperties.HttpClient properties = gatewayMvcProperties.getHttpClient(); - - SslBundle sslBundle = null; - if (StringUtils.hasText(properties.getSslBundle())) { - sslBundle = sslBundles.getBundle(properties.getSslBundle()); - } - ClientHttpRequestFactorySettings settings = new ClientHttpRequestFactorySettings(properties.getConnectTimeout(), - properties.getReadTimeout(), sslBundle); - - if (properties.getType() == GatewayMvcProperties.HttpClientType.JDK) { - // TODO: customize restricted headers - String restrictedHeaders = System.getProperty("jdk.httpclient.allowRestrictedHeaders"); - if (!StringUtils.hasText(restrictedHeaders)) { - System.setProperty("jdk.httpclient.allowRestrictedHeaders", "host"); - } - else if (StringUtils.hasText(restrictedHeaders) && !restrictedHeaders.contains("host")) { - System.setProperty("jdk.httpclient.allowRestrictedHeaders", restrictedHeaders + ",host"); - } - - return ClientHttpRequestFactories.get(JdkClientHttpRequestFactory.class, settings); - } - - // Autodetect - return ClientHttpRequestFactories.get(settings); - } - - @Bean - @ConditionalOnMissingBean - public GatewayMvcProperties gatewayMvcProperties() { - return new GatewayMvcProperties(); - } - - @Bean - public PredicateDiscoverer predicateDiscoverer() { - return new PredicateDiscoverer(); - } - - @Bean - @ConditionalOnMissingBean - public ProxyExchangeHandlerFunction proxyExchangeHandlerFunction(ProxyExchange proxyExchange, - ObjectProvider requestHttpHeadersFilters, - ObjectProvider responseHttpHeadersFilters) { - return new ProxyExchangeHandlerFunction(proxyExchange, requestHttpHeadersFilters, responseHttpHeadersFilters); - } - - @Bean - @ConditionalOnMissingBean - public RemoveContentLengthRequestHeadersFilter removeContentLengthRequestHeadersFilter() { - return new RemoveContentLengthRequestHeadersFilter(); - } - - @Bean - @ConditionalOnMissingBean - public RemoveHopByHopRequestHeadersFilter removeHopByHopRequestHeadersFilter() { - return new RemoveHopByHopRequestHeadersFilter(); - } - - @Bean - @ConditionalOnMissingBean - public RemoveHopByHopResponseHeadersFilter removeHopByHopResponseHeadersFilter() { - return new RemoveHopByHopResponseHeadersFilter(); - } - - @Bean - @ConditionalOnMissingBean - public TransferEncodingNormalizationRequestHeadersFilter transferEncodingNormalizationRequestHeadersFilter() { - return new TransferEncodingNormalizationRequestHeadersFilter(); - } - - @Bean - @ConditionalOnMissingBean - public WeightCalculatorFilter weightCalculatorFilter() { - return new WeightCalculatorFilter(); - } - - @Bean - @ConditionalOnMissingBean - public XForwardedRequestHeadersFilter xForwardedRequestHeadersFilter() { - return new XForwardedRequestHeadersFilter(); - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/AbstractGatewayDiscoverer.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/AbstractGatewayDiscoverer.java deleted file mode 100644 index f72ab2a8..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/AbstractGatewayDiscoverer.java +++ /dev/null @@ -1,80 +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.common; - -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.function.Supplier; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.cloud.gateway.server.mvc.invoke.reflect.DefaultOperationMethod; -import org.springframework.cloud.gateway.server.mvc.invoke.reflect.OperationMethod; -import org.springframework.core.io.support.SpringFactoriesLoader; -import org.springframework.core.log.LogMessage; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; - -public abstract class AbstractGatewayDiscoverer { - - protected final Log log = LogFactory.getLog(getClass()); - - protected volatile MultiValueMap operations = new LinkedMultiValueMap<>(); - - public >, R> void doDiscover(Class supplierClass, Class returnType) { - List suppliers = loadSuppliers(supplierClass); - - List methods = new ArrayList<>(); - for (Supplier> supplier : suppliers) { - methods.addAll(supplier.get()); - } - - for (Method method : methods) { - // TODO: replace with a BiPredicate of some kind - if (returnType.isAssignableFrom(method.getReturnType())) { - addOperationMethod(method); - } - } - } - - protected void addOperationMethod(Method method) { - OperationMethod operationMethod = new DefaultOperationMethod(method); - String key = method.getName(); - operations.add(key, operationMethod); - log.trace(LogMessage.format("Discovered %s", operationMethod)); - } - - public abstract void discover(); - - protected List loadSuppliers(Class supplierClass) { - ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); - - List suppliers = SpringFactoriesLoader.loadFactories(supplierClass, classLoader); - return suppliers; - } - - public MultiValueMap getOperations() { - if (operations == null || operations.isEmpty()) { - discover(); - } - return operations; - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/ArgumentSupplier.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/ArgumentSupplier.java deleted file mode 100644 index 46410d9d..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/ArgumentSupplier.java +++ /dev/null @@ -1,33 +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.common; - -public interface ArgumentSupplier { - - ArgumentSuppliedEvent getArgumentSuppliedEvent(); - - interface ArgumentSuppliedEvent { - - Class getType(); - - T getArgument(); - - Object getSource(); - - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/ArgumentSupplierBeanPostProcessor.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/ArgumentSupplierBeanPostProcessor.java deleted file mode 100644 index 49e441c6..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/ArgumentSupplierBeanPostProcessor.java +++ /dev/null @@ -1,188 +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.common; - -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Function; - -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.config.BeanPostProcessor; -import org.springframework.cloud.gateway.server.mvc.common.ArgumentSupplier.ArgumentSuppliedEvent; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.core.io.Resource; -import org.springframework.http.HttpMethod; -import org.springframework.web.servlet.function.HandlerFunction; -import org.springframework.web.servlet.function.RequestPredicate; -import org.springframework.web.servlet.function.RequestPredicates; -import org.springframework.web.servlet.function.RouterFunction; -import org.springframework.web.servlet.function.RouterFunctions; -import org.springframework.web.servlet.function.ServerRequest; - -// The RequestPredicate needs to add `visitor.unknown(this)` in accept(); -public class ArgumentSupplierBeanPostProcessor implements BeanPostProcessor { - - private final ApplicationEventPublisher publisher; - - public ArgumentSupplierBeanPostProcessor(ApplicationEventPublisher publisher) { - this.publisher = publisher; - } - - @Override - public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { - if (bean instanceof RouterFunction routerFunction) { - PredicateVisitor predicateVisitor = new PredicateVisitor(); - RouterFunctionVisitor routerFunctionVisitor = new RouterFunctionVisitor(predicateVisitor); - routerFunction.accept(routerFunctionVisitor); - if (predicateVisitor.argumentSupplier != null) { - ArgumentSuppliedEvent argumentSuppliedEvent = predicateVisitor.argumentSupplier - .getArgumentSuppliedEvent(); - if (predicateVisitor.attributes != null) { - argumentSuppliedEvent = new AttributedArugmentSuppliedEvent<>(argumentSuppliedEvent, - predicateVisitor.attributes); - } - publisher.publishEvent(argumentSuppliedEvent); - } - } - return bean; - } - - class RouterFunctionVisitor implements RouterFunctions.Visitor { - - private final PredicateVisitor predicateVisitor; - - RouterFunctionVisitor(PredicateVisitor predicateVisitor) { - this.predicateVisitor = predicateVisitor; - } - - @Override - public void route(RequestPredicate predicate, HandlerFunction handlerFunction) { - predicate.accept(predicateVisitor); - } - - @Override - public void attributes(Map attributes) { - this.predicateVisitor.attributesRef.set(attributes); - } - - @Override - public void startNested(RequestPredicate predicate) { - - } - - @Override - public void endNested(RequestPredicate predicate) { - - } - - @Override - public void resources(Function> lookupFunction) { - - } - - @Override - public void unknown(RouterFunction routerFunction) { - } - - } - - class PredicateVisitor implements RequestPredicates.Visitor { - - private final AtomicReference> attributesRef = new AtomicReference<>(); - - private ArgumentSupplier argumentSupplier; - - private Map attributes; - - @Override - public void unknown(RequestPredicate predicate) { - if (predicate instanceof ArgumentSupplier argumentSupplier) { - this.argumentSupplier = argumentSupplier; - this.attributes = attributesRef.get(); - } - } - - @Override - public void method(Set methods) { - - } - - @Override - public void path(String pattern) { - - } - - @Override - public void pathExtension(String extension) { - - } - - @Override - public void header(String name, String value) { - - } - - @Override - public void param(String name, String value) { - - } - - @Override - public void startAnd() { - - } - - @Override - public void and() { - - } - - @Override - public void endAnd() { - - } - - @Override - public void startOr() { - - } - - @Override - public void or() { - - } - - @Override - public void endOr() { - - } - - @Override - public void startNegate() { - - } - - @Override - public void endNegate() { - - } - - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/AttributedArugmentSuppliedEvent.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/AttributedArugmentSuppliedEvent.java deleted file mode 100644 index c6e6ce2c..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/AttributedArugmentSuppliedEvent.java +++ /dev/null @@ -1,50 +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.common; - -import java.util.Map; - -import org.springframework.cloud.gateway.server.mvc.common.ArgumentSupplier.ArgumentSuppliedEvent; -import org.springframework.context.ApplicationEvent; - -public class AttributedArugmentSuppliedEvent extends ApplicationEvent implements ArgumentSuppliedEvent { - - private final ArgumentSuppliedEvent event; - - private final Map attributes; - - public AttributedArugmentSuppliedEvent(ArgumentSuppliedEvent event, Map attributes) { - super(event.getSource()); - this.event = event; - this.attributes = attributes; - } - - @Override - public Class getType() { - return event.getType(); - } - - @Override - public T getArgument() { - return event.getArgument(); - } - - public Map getAttributes() { - return attributes; - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/DefaultArgumentSuppliedEvent.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/DefaultArgumentSuppliedEvent.java deleted file mode 100644 index 44c810fb..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/DefaultArgumentSuppliedEvent.java +++ /dev/null @@ -1,44 +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.common; - -import org.springframework.context.ApplicationEvent; - -public class DefaultArgumentSuppliedEvent extends ApplicationEvent - implements ArgumentSupplier.ArgumentSuppliedEvent { - - private final Class type; - - private final T argument; - - public DefaultArgumentSuppliedEvent(Object source, Class type, T argument) { - super(source); - this.type = type; - this.argument = argument; - } - - @Override - public Class getType() { - return type; - } - - @Override - public T getArgument() { - return argument; - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/HttpStatusHolder.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/HttpStatusHolder.java deleted file mode 100644 index c19f6d47..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/HttpStatusHolder.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright 2013-2020 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 org.springframework.core.style.ToStringCreator; -import org.springframework.http.HttpStatus; -import org.springframework.http.HttpStatusCode; -import org.springframework.util.Assert; - -public class HttpStatusHolder { - - private final HttpStatusCode httpStatus; - - private final Integer status; - - public HttpStatusHolder(HttpStatusCode httpStatus, Integer status) { - Assert.isTrue(httpStatus != null || status != null, "httpStatus and status may not both be null"); - this.httpStatus = httpStatus; - this.status = status; - } - - public static HttpStatusHolder valueOf(String status) { - HttpStatusCode httpStatus; - try { - httpStatus = HttpStatus.valueOf(status.toUpperCase()); - } - catch (IllegalArgumentException e) { - httpStatus = null; - } - final Integer intStatus; - if (httpStatus == null) { - intStatus = Integer.parseInt(status); - } - else { - intStatus = null; - } - - return new HttpStatusHolder(httpStatus, intStatus); - } - - /** - * Resolves an {@link HttpStatusCode}. - * @return either the httpStatus field if not null, or resolves an HttpStatusCode - * based on the int status. - */ - public HttpStatusCode resolve() { - if (httpStatus != null) { - return httpStatus; - } - return HttpStatusCode.valueOf(status); - } - - public HttpStatusCode getHttpStatus() { - return httpStatus; - } - - public Integer getStatus() { - return status; - } - - /** - * Whether this status code is in the HTTP series - * {@link HttpStatus.Series#INFORMATIONAL}. - * @return true if status code is in the INFORMATIONAL http series - */ - public boolean is1xxInformational() { - return HttpStatus.Series.INFORMATIONAL.equals(getSeries()); - } - - /** - * Whether this status code is in the HTTP series - * {@link HttpStatus.Series#SUCCESSFUL}. - * @return true if status code is in the SUCCESSFUL http series - */ - public boolean is2xxSuccessful() { - return HttpStatus.Series.SUCCESSFUL.equals(getSeries()); - } - - /** - * Whether this status code is in the HTTP series - * {@link HttpStatus.Series#REDIRECTION}. - * @return true if status code is in the REDIRECTION http series - */ - public boolean is3xxRedirection() { - return HttpStatus.Series.REDIRECTION.equals(getSeries()); - } - - /** - * Whether this status code is in the HTTP series - * {@link HttpStatus.Series#CLIENT_ERROR}. - * @return true if status code is in the CLIENT_ERROR http series - */ - public boolean is4xxClientError() { - return HttpStatus.Series.CLIENT_ERROR.equals(getSeries()); - } - - /** - * Whether this status code is in the HTTP series - * {@link HttpStatus.Series#SERVER_ERROR}. - * @return true if status code is in the SERVER_ERROR http series - */ - public boolean is5xxServerError() { - return HttpStatus.Series.SERVER_ERROR.equals(getSeries()); - } - - public HttpStatus.Series getSeries() { - if (httpStatus != null) { - return HttpStatus.Series.valueOf(httpStatus.value()); - } - if (status != null) { - return HttpStatus.Series.valueOf(status); - } - return null; - } - - /** - * Whether this status code is in the HTTP series - * {@link HttpStatus.Series#CLIENT_ERROR} or {@link HttpStatus.Series#SERVER_ERROR}. - * @return true if is either CLIENT_ERROR or SERVER_ERROR - */ - public boolean isError() { - return is4xxClientError() || is5xxServerError(); - } - - @Override - public String toString() { - return new ToStringCreator(this).append("httpStatus", httpStatus).append("status", status).toString(); - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/KeyValues.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/KeyValues.java deleted file mode 100644 index c560aa0f..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/KeyValues.java +++ /dev/null @@ -1,79 +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.common; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import org.springframework.core.style.ToStringCreator; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - -public class KeyValues { - - private List keyValues = new ArrayList<>(); - - public List getKeyValues() { - return keyValues; - } - - public void setKeyValues(List keyValues) { - this.keyValues = keyValues; - } - - public static KeyValues valueOf(String s) { - String[] tokens = StringUtils.tokenizeToStringArray(s, ",", true, true); - List parsedKeyValues = Arrays.stream(tokens).map(KeyValue::valueOf).toList(); - KeyValues keyValues = new KeyValues(); - keyValues.setKeyValues(parsedKeyValues); - return keyValues; - } - - public static class KeyValue { - - private final String key; - - private final String value; - - public KeyValue(String key, String value) { - this.key = key; - this.value = value; - } - - public String getKey() { - return key; - } - - public String getValue() { - return value; - } - - @Override - public String toString() { - return new ToStringCreator(this).append("name", key).append("value", value).toString(); - } - - public static KeyValue valueOf(String s) { - String[] tokens = StringUtils.tokenizeToStringArray(s, ":", true, true); - Assert.isTrue(tokens.length == 2, () -> "String must be two tokens delimited by colon, but was " + s); - return new KeyValue(tokens[0], tokens[1]); - } - - } - -} 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 deleted file mode 100644 index d93b9fb8..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/MvcUtils.java +++ /dev/null @@ -1,218 +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.common; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.UncheckedIOException; -import java.net.URI; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -import org.springframework.context.ApplicationContext; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpInputMessage; -import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.util.Assert; -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 static org.springframework.web.servlet.function.RouterFunctions.URI_TEMPLATE_VARIABLES_ATTRIBUTE; - -// TODO: maybe rename to ServerRequestUtils? -public abstract class MvcUtils { - - /** - * Cached raw request body key. - */ - public static final String CACHED_REQUEST_BODY_ATTR = qualify("cachedRequestBody"); - - /** - * CircuitBreaker execution exception attribute name. - */ - public static final String CIRCUITBREAKER_EXECUTION_EXCEPTION_ATTR = qualify("circuitBreakerExecutionException"); - - /** - * Gateway route ID attribute name. - */ - public static final String GATEWAY_ATTRIBUTES_ATTR = qualify("gatewayAttributes"); - - /** - * Gateway request URL attribute name. - */ - public static final String GATEWAY_REQUEST_URL_ATTR = qualify("gatewayRequestUrl"); - - /** - * Gateway route ID attribute name. - */ - public static final String GATEWAY_ROUTE_ID_ATTR = qualify("gatewayRouteId"); - - /** - * Preserve-Host header attribute name. - */ - public static final String PRESERVE_HOST_HEADER_ATTRIBUTE = qualify("preserveHostHeader"); - - /** - * Weight attribute name. - */ - public static final String WEIGHT_ATTR = qualify("routeWeight"); - - private MvcUtils() { - } - - private static String qualify(String attr) { - return "GatewayServerMvc." + attr; - } - - public static Optional cacheAndReadBody(ServerRequest request, Class toClass) { - ByteArrayInputStream rawBody = cacheBody(request); - return readBody(request, rawBody, toClass); - } - - public static ByteArrayInputStream cacheBody(ServerRequest request) { - try { - byte[] bytes = StreamUtils.copyToByteArray(request.servletRequest().getInputStream()); - ByteArrayInputStream body = new ByteArrayInputStream(bytes); - putAttribute(request, MvcUtils.CACHED_REQUEST_BODY_ATTR, body); - return body; - } - catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - public static String expand(ServerRequest request, String template) { - Assert.notNull(request, "request may not be null"); - Assert.notNull(template, "template may not be null"); - - if (template.indexOf('{') == -1) { // short circuit - return template; - } - Map variables = getUriTemplateVariables(request); - return UriComponentsBuilder.fromPath(template).build().expand(variables).getPath(); - } - - public static List expandMultiple(ServerRequest request, Collection templates) { - return templates.stream().map(value -> MvcUtils.expand(request, value)).toList(); - } - - public static String[] expandMultiple(ServerRequest request, String... templates) { - List expanded = Arrays.stream(templates).map(value -> MvcUtils.expand(request, value)).toList(); - return expanded.toArray(new String[0]); - } - - public static ApplicationContext getApplicationContext(ServerRequest request) { - WebApplicationContext webApplicationContext = RequestContextUtils - .findWebApplicationContext(request.servletRequest()); - if (webApplicationContext == null) { - throw new IllegalStateException("No Application Context in request attributes"); - } - return webApplicationContext; - } - - @SuppressWarnings("unchecked") - public static T getAttribute(ServerRequest request, String key) { - if (request.attributes().containsKey(key)) { - return (T) request.attributes().get(key); - } - return (T) getGatewayAttributes(request).get(key); - } - - @SuppressWarnings("unchecked") - public static Map getGatewayAttributes(ServerRequest request) { - // This map is made in GatewayDelegatingRouterFunction.route() and persists across - // attribute resetting in RequestPredicates - Map attributes = (Map) request.attributes().get(GATEWAY_ATTRIBUTES_ATTR); - return attributes; - } - - @SuppressWarnings("unchecked") - public static Map getUriTemplateVariables(ServerRequest request) { - return (Map) request.attributes().getOrDefault(URI_TEMPLATE_VARIABLES_ATTRIBUTE, - new HashMap<>()); - } - - public static void putAttribute(ServerRequest request, String key, Object value) { - request.attributes().put(key, value); - getGatewayAttributes(request).put(key, value); - } - - @SuppressWarnings("unchecked") - public static void putUriTemplateVariables(ServerRequest request, Map uriVariables) { - if (request.attributes().containsKey(URI_TEMPLATE_VARIABLES_ATTRIBUTE)) { - Map existingVariables = (Map) request.attributes() - .get(URI_TEMPLATE_VARIABLES_ATTRIBUTE); - HashMap newVariables = new HashMap<>(existingVariables); - newVariables.putAll(uriVariables); - request.attributes().put(URI_TEMPLATE_VARIABLES_ATTRIBUTE, newVariables); - } - else { - request.attributes().put(URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriVariables); - } - } - - @SuppressWarnings("unchecked") - public static Optional readBody(ServerRequest request, ByteArrayInputStream body, Class toClass) { - try { - HttpInputMessage inputMessage = new ByteArrayInputMessage(request, body); - List> httpMessageConverters = request.messageConverters(); - for (HttpMessageConverter messageConverter : httpMessageConverters) { - if (messageConverter.canRead(toClass, request.headers().contentType().orElse(null))) { - T convertedValue = (T) messageConverter.read((Class) toClass, inputMessage); - return Optional.of(convertedValue); - } - } - } - catch (IOException e) { - throw new UncheckedIOException(e); - } - return Optional.empty(); - } - - public static void setRouteId(ServerRequest request, String routeId) { - request.attributes().put(GATEWAY_ROUTE_ID_ATTR, routeId); - request.servletRequest().setAttribute(GATEWAY_ROUTE_ID_ATTR, routeId); - } - - public static void setRequestUrl(ServerRequest request, URI url) { - request.attributes().put(GATEWAY_REQUEST_URL_ATTR, url); - request.servletRequest().setAttribute(GATEWAY_REQUEST_URL_ATTR, url); - } - - private record ByteArrayInputMessage(ServerRequest request, ByteArrayInputStream body) implements HttpInputMessage { - - @Override - public InputStream getBody() { - return body; - } - - @Override - public HttpHeaders getHeaders() { - return request.headers().asHttpHeaders(); - } - - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/NameUtils.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/NameUtils.java deleted file mode 100644 index dee1f775..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/NameUtils.java +++ /dev/null @@ -1,34 +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.common; - -public abstract class NameUtils { - - /** - * Generated name prefix. - */ - public static final String GENERATED_NAME_PREFIX = "_genkey_"; - - private NameUtils() { - throw new AssertionError("Must not instantiate utility class."); - } - - public static String generateName(int i) { - return GENERATED_NAME_PREFIX + i; - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/Shortcut.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/Shortcut.java deleted file mode 100644 index d6c5caae..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/Shortcut.java +++ /dev/null @@ -1,77 +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.common; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.springframework.core.annotation.AliasFor; -import org.springframework.stereotype.Indexed; - -@Target({ ElementType.METHOD }) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@Indexed -public @interface Shortcut { - - /** - * The names and order of arguments for a shortcut. Synonym for {@link #fieldOrder()}. - * Defaults to the names and order of the method on which this is declared. - */ - @AliasFor("fieldOrder") - String[] value() default {}; - - /** - * The names and order of arguments for a shortcut. Synonym for {@link #value()}. - * Defaults to the names and order of the method on which this is declared. - */ - @AliasFor("value") - String[] fieldOrder() default {}; - - /** - * Strategy for parsing the shortcut String. - */ - Type type() default Type.DEFAULT; - - /** - * Optional property prefix to be appended to fields. - */ - String fieldPrefix() default ""; - - enum Type { - - /** - * Default shortcut type. - */ - DEFAULT, - - /** - * List shortcut type. - */ - LIST, - - /** - * List is all elements except last which is a boolean flag. - */ - LIST_TAIL_FLAG; - - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/WeightConfig.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/WeightConfig.java deleted file mode 100644 index f8533910..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/WeightConfig.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2013-2020 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 jakarta.validation.constraints.Min; -import jakarta.validation.constraints.NotEmpty; - -import org.springframework.core.style.ToStringCreator; -import org.springframework.validation.annotation.Validated; - -@Validated -public class WeightConfig { - - /** - * Configuration prefix for {@link WeightConfig}. - */ - public static final String CONFIG_PREFIX = "weight"; - - @NotEmpty - private String group; - - private String routeId; - - @Min(0) - private int weight; - - private WeightConfig() { - } - - public WeightConfig(String routeId, String group, int weight) { - this.routeId = routeId; - this.group = group; - this.weight = weight; - } - - public WeightConfig(String routeId) { - this.routeId = routeId; - } - - public String getGroup() { - return group; - } - - public WeightConfig setGroup(String group) { - this.group = group; - return this; - } - - public String getRouteId() { - return routeId; - } - - public WeightConfig setRouteId(String routeId) { - this.routeId = routeId; - return this; - } - - public int getWeight() { - return weight; - } - - public WeightConfig setWeight(int weight) { - this.weight = weight; - return this; - } - - @Override - public String toString() { - return new ToStringCreator(this).append("routeId", routeId).append("group", group).append("weight", weight) - .toString(); - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/FilterProperties.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/FilterProperties.java deleted file mode 100644 index b1c25559..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/FilterProperties.java +++ /dev/null @@ -1,105 +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.config; - -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Objects; - -import jakarta.validation.constraints.NotNull; - -import org.springframework.cloud.gateway.server.mvc.common.NameUtils; -import org.springframework.validation.annotation.Validated; - -import static org.springframework.util.StringUtils.tokenizeToStringArray; - -/** - * @author Spencer Gibb - */ -@Validated -public class FilterProperties { - - @NotNull - private String name; - - private Map args = new LinkedHashMap<>(); - - public FilterProperties() { - } - - public FilterProperties(String text) { - int eqIdx = text.indexOf('='); - if (eqIdx <= 0) { - setName(text); - return; - } - setName(text.substring(0, eqIdx)); - - String[] args = tokenizeToStringArray(text.substring(eqIdx + 1), ","); - - for (int i = 0; i < args.length; i++) { - this.args.put(NameUtils.generateName(i), args[i]); - } - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public Map getArgs() { - return args; - } - - public void setArgs(Map args) { - this.args = args; - } - - public void addArg(String key, String value) { - this.args.put(key, value); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - FilterProperties that = (FilterProperties) o; - return Objects.equals(name, that.name) && Objects.equals(args, that.args); - } - - @Override - public int hashCode() { - return Objects.hash(name, args); - } - - @Override - public String toString() { - final StringBuilder sb = new StringBuilder("FilterDefinition{"); - sb.append("name='").append(name).append('\''); - sb.append(", args=").append(args); - sb.append('}'); - return sb.toString(); - } - -} 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 deleted file mode 100644 index c54e1d39..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/GatewayMvcProperties.java +++ /dev/null @@ -1,144 +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.config; - -import java.time.Duration; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; - -import jakarta.validation.Valid; -import jakarta.validation.constraints.NotNull; - -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.core.style.ToStringCreator; - -@ConfigurationProperties(GatewayMvcProperties.PREFIX) -public class GatewayMvcProperties { - - /** - * Properties prefix. - */ - public static final String PREFIX = "spring.cloud.gateway.mvc"; - - /** - * List of Routes. - */ - @NotNull - @Valid - private List routes = new ArrayList<>(); - - /** - * List of Routes. - */ - @NotNull - @Valid - private LinkedHashMap routesMap = new LinkedHashMap<>(); - - private HttpClient httpClient = new HttpClient(); - - public List getRoutes() { - return routes; - } - - public void setRoutes(List routes) { - this.routes = routes; - } - - public LinkedHashMap getRoutesMap() { - return routesMap; - } - - public void setRoutesMap(LinkedHashMap routesMap) { - this.routesMap = routesMap; - } - - public HttpClient getHttpClient() { - return httpClient; - } - - @Override - public String toString() { - return new ToStringCreator(this).append("httpClient", httpClient).append("routes", routes) - .append("routesMap", routesMap).toString(); - } - - public static class HttpClient { - - private Duration connectTimeout; - - private Duration readTimeout; - - private String sslBundle; - - private HttpClientType type = HttpClientType.JDK; - - public Duration getConnectTimeout() { - return connectTimeout; - } - - public void setConnectTimeout(Duration connectTimeout) { - this.connectTimeout = connectTimeout; - } - - public Duration getReadTimeout() { - return readTimeout; - } - - public void setReadTimeout(Duration readTimeout) { - this.readTimeout = readTimeout; - } - - public String getSslBundle() { - return sslBundle; - } - - public void setSslBundle(String sslBundle) { - this.sslBundle = sslBundle; - } - - public HttpClientType getType() { - return type; - } - - public void setType(HttpClientType type) { - this.type = type; - } - - @Override - public String toString() { - return new ToStringCreator(this).append("connectTimeout", connectTimeout).append("readTimeout", readTimeout) - .append("sslBundle", sslBundle).append("type", type).toString(); - } - - } - - public enum HttpClientType { - - /** - * Use JDK HttpClient. - */ - JDK, - - /** - * Auto-detect the HttpClient. - */ - AUTODETECT - - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/GatewayMvcPropertiesBeanDefinitionRegistrar.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/GatewayMvcPropertiesBeanDefinitionRegistrar.java deleted file mode 100644 index 90b9c62f..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/GatewayMvcPropertiesBeanDefinitionRegistrar.java +++ /dev/null @@ -1,386 +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.config; - -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Consumer; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.aop.scope.ScopedProxyUtils; -import org.springframework.beans.factory.config.BeanDefinitionHolder; -import org.springframework.beans.factory.support.AbstractBeanDefinition; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.boot.context.properties.bind.Binder; -import org.springframework.cloud.gateway.server.mvc.common.MvcUtils; -import org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions; -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.invoke.InvocationContext; -import org.springframework.cloud.gateway.server.mvc.invoke.OperationArgumentResolver; -import org.springframework.cloud.gateway.server.mvc.invoke.OperationParameters; -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.PredicateDiscoverer; -import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; -import org.springframework.core.env.Environment; -import org.springframework.core.log.LogMessage; -import org.springframework.core.type.AnnotationMetadata; -import org.springframework.util.MultiValueMap; -import org.springframework.util.StringUtils; -import org.springframework.web.servlet.function.HandlerFilterFunction; -import org.springframework.web.servlet.function.HandlerFunction; -import org.springframework.web.servlet.function.RequestPredicate; -import org.springframework.web.servlet.function.RouterFunction; -import org.springframework.web.servlet.function.RouterFunctions; -import org.springframework.web.servlet.function.ServerRequest; -import org.springframework.web.servlet.function.ServerResponse; - -import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route; - -public class GatewayMvcPropertiesBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar { - - private static final RequestPredicate neverPredicate = new RequestPredicate() { - @Override - public boolean test(ServerRequest request) { - return false; - } - - @Override - public String toString() { - return "Never"; - } - }; - - private static final RouterFunction NEVER_ROUTE = RouterFunctions.route(neverPredicate, - request -> ServerResponse.notFound().build()); - - protected final Log log = LogFactory.getLog(getClass()); - - private final TrueNullOperationArgumentResolver trueNullOperationArgumentResolver = new TrueNullOperationArgumentResolver(); - - private final Environment env; - - private final FilterDiscoverer filterDiscoverer = new FilterDiscoverer(); - - private final HandlerDiscoverer handlerDiscoverer = new HandlerDiscoverer(); - - private final PredicateDiscoverer predicateDiscoverer = new PredicateDiscoverer(); - - private final ParameterValueMapper parameterValueMapper = new ConversionServiceParameterValueMapper(); - - public GatewayMvcPropertiesBeanDefinitionRegistrar(Environment env) { - this.env = env; - } - - @Override - public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { - // registers a RouterFunctionHolder that specifically isn't a RouterFunction since - // RouterFunctionMapping gets a list of RouterFunction and if you put - // RouterFunction in refresh scope, RouterFunctionMapping will end up with two. - // Uses this::routerFunctionHolderSupplier so when the bean is refreshed, that - // method is called again. - AbstractBeanDefinition routerFnProviderBeanDefinition = BeanDefinitionBuilder - .genericBeanDefinition(RouterFunctionHolder.class, this::routerFunctionHolderSupplier) - .getBeanDefinition(); - // TODO: opt out of refresh scope? - // Puts the RouterFunctionHolder in refresh scope - BeanDefinitionHolder holder = new BeanDefinitionHolder(routerFnProviderBeanDefinition, - "gatewayRouterFunctionHolder"); - BeanDefinitionHolder proxy = ScopedProxyUtils.createScopedProxy(holder, registry, true); - routerFnProviderBeanDefinition.setScope("refresh"); - if (registry.containsBeanDefinition(proxy.getBeanName())) { - registry.removeBeanDefinition(proxy.getBeanName()); - } - registry.registerBeanDefinition(proxy.getBeanName(), proxy.getBeanDefinition()); - - // registers a DelegatingRouterFunction(RouterFunctionHolder) bean this way the - // holder can be refreshed and all config based routes will be reloaded. - - AbstractBeanDefinition routerFunctionBeanDefinition = BeanDefinitionBuilder - .genericBeanDefinition(DelegatingRouterFunction.class).getBeanDefinition(); - registry.registerBeanDefinition("gatewayCompositeRouterFunction", routerFunctionBeanDefinition); - } - - @SuppressWarnings({ "unchecked", "rawtypes" }) - private RouterFunctionHolder routerFunctionHolderSupplier() { - GatewayMvcProperties properties = Binder.get(env).bindOrCreate(GatewayMvcProperties.PREFIX, - GatewayMvcProperties.class); - log.trace(LogMessage.format("RouterFunctionHolder initializing with %d map routes and %d list routes", - properties.getRoutesMap().size(), properties.getRoutes().size())); - - Map routerFunctions = new LinkedHashMap<>(); - properties.getRoutes().forEach(routeProperties -> { - routerFunctions.put(routeProperties.getId(), getRouterFunction(routeProperties, routeProperties.getId())); - }); - properties.getRoutesMap().forEach((routeId, routeProperties) -> { - String computedRouteId = routeId; - if (StringUtils.hasText(routeProperties.getId())) { - computedRouteId = routeProperties.getId(); - } - routerFunctions.put(computedRouteId, getRouterFunction(routeProperties, computedRouteId)); - }); - RouterFunction routerFunction; - if (routerFunctions.isEmpty()) { - // no properties routes, so a RouterFunction that will never match - routerFunction = NEVER_ROUTE; - } - else { - routerFunction = routerFunctions.values().stream().reduce(RouterFunction::andOther).orElse(null); - // puts the map of configured RouterFunctions in an attribute. Makes testing - // easy. - routerFunction = routerFunction.withAttribute("gatewayRouterFunctions", routerFunctions); - } - log.trace(LogMessage.format("RouterFunctionHolder initialized %s", routerFunction.toString())); - return new RouterFunctionHolder(routerFunction); - } - - @SuppressWarnings({ "unchecked", "rawtypes" }) - private RouterFunction getRouterFunction(RouteProperties routeProperties, String routeId) { - log.trace(LogMessage.format("Creating route for : %s", routeProperties)); - - RouterFunctions.Builder builder = route(routeId); - - // MVC.fn users won't need this anonymous filter as url will be set directly. - // Put this function first, so if a filter from a handler changes the url - // it is after this one. - builder.filter((request, next) -> { - MvcUtils.setRequestUrl(request, routeProperties.getUri()); - return next.handle(request); - }); - builder.before(BeforeFilterFunctions.routeId(routeId)); - - MultiValueMap handlerOperations = handlerDiscoverer.getOperations(); - // TODO: cache? - // translate handlerFunction - String scheme = routeProperties.getUri().getScheme(); - Map handlerArgs = new HashMap<>(); - // TODO: avoid hardcoded scheme/uri args - // maybe find empty args or single RouteProperties param? - if (scheme.equals("lb")) { - handlerArgs.put("uri", routeProperties.getUri().toString()); - } - Optional handlerOperationMethod = findOperation(handlerOperations, - scheme.toLowerCase(), 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; - if (response instanceof HandlerFunction) { - handlerFunction = (HandlerFunction) response; - } - else if (response instanceof HandlerDiscoverer.Result result) { - handlerFunction = result.getHandlerFunction(); - result.getFilters().forEach(builder::filter); - } - if (handlerFunction == null) { - throw new IllegalStateException( - "Unable to find HandlerFunction for scheme: " + scheme + " and response " + response); - } - - // translate predicates - MultiValueMap predicateOperations = predicateDiscoverer.getOperations(); - final AtomicReference predicate = new AtomicReference<>(); - - routeProperties.getPredicates() - .forEach(predicateProperties -> translate(predicateOperations, predicateProperties.getName(), - predicateProperties.getArgs(), RequestPredicate.class, requestPredicate -> { - log.trace(LogMessage.format("Adding predicate to route %s - %s", routeId, - predicateProperties)); - if (predicate.get() == null) { - predicate.set(requestPredicate); - } - else { - RequestPredicate combined = predicate.get().and(requestPredicate); - predicate.set(combined); - } - log.trace(LogMessage.format("Combined predicate for route %s - %s", routeId, - predicate.get())); - })); - - // combine predicate and handlerFunction - builder.route(predicate.get(), handlerFunction); - predicate.set(null); - - // translate filters - MultiValueMap filterOperations = filterDiscoverer.getOperations(); - routeProperties.getFilters().forEach(filterProperties -> translate(filterOperations, filterProperties.getName(), - filterProperties.getArgs(), HandlerFilterFunction.class, builder::filter)); - - builder.withAttribute(MvcUtils.GATEWAY_ROUTE_ID_ATTR, routeId); - - return builder.build(); - } - - private void translate(MultiValueMap operations, String operationName, - Map operationArgs, Class returnType, Consumer operationHandler) { - String normalizedName = StringUtils.uncapitalize(operationName); - Optional operationMethod = findOperation(operations, normalizedName, operationArgs); - if (operationMethod.isPresent()) { - NormalizedOperationMethod opMethod = operationMethod.get(); - T handlerFilterFunction = invokeOperation(opMethod, opMethod.getNormalizedArgs()); - if (handlerFilterFunction != null) { - operationHandler.accept(handlerFilterFunction); - } - } - else { - throw new IllegalArgumentException(String.format("Unable to find operation %s for %s with args %s", - returnType, normalizedName, operationArgs)); - } - } - - private Optional findOperation(MultiValueMap operations, - String operationName, Map operationArgs) { - return operations.getOrDefault(operationName, Collections.emptyList()).stream() - .map(operationMethod -> new NormalizedOperationMethod(operationMethod, operationArgs)) - .filter(opeMethod -> matchOperation(opeMethod, operationArgs)).findFirst(); - } - - private static boolean matchOperation(NormalizedOperationMethod operationMethod, Map args) { - Map normalizedArgs = operationMethod.getNormalizedArgs(); - OperationParameters parameters = operationMethod.getParameters(); - if (parameters.getParameterCount() != normalizedArgs.size()) { - return false; - } - for (int i = 0; i < parameters.getParameterCount(); i++) { - if (!normalizedArgs.containsKey(parameters.get(i).getName())) { - return false; - } - } - // args contains all parameter names - return true; - } - - private T invokeOperation(OperationMethod operationMethod, Map operationArgs) { - Map args = new HashMap<>(operationArgs); - ReflectiveOperationInvoker operationInvoker = new ReflectiveOperationInvoker(operationMethod, - this.parameterValueMapper); - InvocationContext context = new InvocationContext(args, trueNullOperationArgumentResolver); - return operationInvoker.invoke(context); - } - - static class TrueNullOperationArgumentResolver implements OperationArgumentResolver { - - @Override - public boolean canResolve(Class type) { - return true; - } - - @Override - public T resolve(Class type) { - return null; - } - - } - - /** - * Simply holds the composite gateway RouterFunction. This class can be refresh scope - * without fear of having multiple RouterFunction mappings. - */ - static class RouterFunctionHolder { - - private final RouterFunction routerFunction; - - RouterFunctionHolder(RouterFunction routerFunction) { - this.routerFunction = routerFunction; - } - - public RouterFunction getRouterFunction() { - return this.routerFunction; - } - - } - - /** - * Delegating RouterFunction impl that delegates to the refreshable - * RouterFunctionHolder. - */ - static class DelegatingRouterFunction implements RouterFunction { - - final RouterFunctionHolder provider; - - DelegatingRouterFunction(RouterFunctionHolder provider) { - this.provider = provider; - } - - @Override - public RouterFunction and(RouterFunction other) { - return this.provider.getRouterFunction().and(other); - } - - @Override - public RouterFunction andOther(RouterFunction other) { - return this.provider.getRouterFunction().andOther(other); - } - - @Override - public RouterFunction andRoute(RequestPredicate predicate, - HandlerFunction handlerFunction) { - return this.provider.getRouterFunction().andRoute(predicate, handlerFunction); - } - - @Override - public RouterFunction andNest(RequestPredicate predicate, - RouterFunction routerFunction) { - return this.provider.getRouterFunction().andNest(predicate, routerFunction); - } - - @Override - public RouterFunction filter( - HandlerFilterFunction filterFunction) { - return this.provider.getRouterFunction().filter(filterFunction); - } - - @Override - public void accept(RouterFunctions.Visitor visitor) { - this.provider.getRouterFunction().accept(visitor); - } - - @Override - public RouterFunction withAttribute(String name, Object value) { - return this.provider.getRouterFunction().withAttribute(name, value); - } - - @Override - public RouterFunction withAttributes(Consumer> attributesConsumer) { - return this.provider.getRouterFunction().withAttributes(attributesConsumer); - } - - @Override - public Optional> route(ServerRequest request) { - return this.provider.getRouterFunction().route(request); - } - - @Override - public String toString() { - return this.provider.getRouterFunction().toString(); - } - - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/NormalizedOperationMethod.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/NormalizedOperationMethod.java deleted file mode 100644 index 36c3d944..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/NormalizedOperationMethod.java +++ /dev/null @@ -1,121 +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.config; - -import java.lang.reflect.Method; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.springframework.cloud.gateway.server.mvc.common.NameUtils; -import org.springframework.cloud.gateway.server.mvc.common.Shortcut; -import org.springframework.cloud.gateway.server.mvc.invoke.OperationParameter; -import org.springframework.cloud.gateway.server.mvc.invoke.OperationParameters; -import org.springframework.cloud.gateway.server.mvc.invoke.reflect.DefaultOperationMethod; -import org.springframework.cloud.gateway.server.mvc.invoke.reflect.OperationMethod; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - -public class NormalizedOperationMethod implements OperationMethod { - - private final OperationMethod delegate; - - private final Map normalizedArgs; - - /** - * Create a new {@link DefaultOperationMethod} instance. - * @param method the source method - */ - public NormalizedOperationMethod(OperationMethod delegate, Map args) { - this.delegate = delegate; - normalizedArgs = normalizeArgs(args); - } - - @Override - public Method getMethod() { - return delegate.getMethod(); - } - - @Override - public OperationParameters getParameters() { - return delegate.getParameters(); - } - - public Map getNormalizedArgs() { - return normalizedArgs; - } - - private Map normalizeArgs(Map operationArgs) { - if (hasGeneratedKey(operationArgs)) { - Shortcut shortcut = getMethod().getAnnotation(Shortcut.class); - if (shortcut != null) { - String[] fieldOrder = getFieldOrder(shortcut); - return switch (shortcut.type()) { - case DEFAULT -> { - Map map = new HashMap<>(); - int entryIdx = 0; - for (Map.Entry entry : operationArgs.entrySet()) { - String key = normalizeKey(entry.getKey(), entryIdx, operationArgs, fieldOrder); - // TODO: support spel? - // getValue(parser, beanFactory, entry.getValue()); - String value = entry.getValue(); - map.put(key, value); - entryIdx++; - } - yield map; - } - case LIST -> { - Map map = new HashMap<>(); - // field order should be of size 1 - Assert.isTrue(fieldOrder != null && fieldOrder.length == 1, - "Shortcut Configuration Type GATHER_LIST must have shortcutFieldOrder of size 1"); - String fieldName = fieldOrder[0]; - // No need to serialize here since that happens on invoke - map.put(fieldName, StringUtils.collectionToCommaDelimitedString(operationArgs.values())); - yield map; - } - default -> throw new IllegalArgumentException("Unknown Shortcut type " + shortcut.type()); - }; - } - } - return operationArgs; - } - - private String[] getFieldOrder(Shortcut shortcut) { - String[] fieldOrder = shortcut.fieldOrder(); - if (fieldOrder.length == 0) { - List paramNames = getParameters().stream().map(OperationParameter::getName).toList(); - fieldOrder = paramNames.toArray(new String[0]); - } - return fieldOrder; - } - - private static boolean hasGeneratedKey(Map operationArgs) { - return operationArgs.keySet().stream().anyMatch(key -> key.startsWith(NameUtils.GENERATED_NAME_PREFIX)); - } - - static String normalizeKey(String key, int entryIdx, Map args, String[] fieldOrder) { - // RoutePredicateFactory has name hints and this has a fake key name - // replace with the matching key hint - if (key.startsWith(NameUtils.GENERATED_NAME_PREFIX) && fieldOrder.length > 0 && entryIdx < args.size() - && entryIdx < fieldOrder.length) { - key = fieldOrder[entryIdx]; - } - return key; - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/PredicateProperties.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/PredicateProperties.java deleted file mode 100644 index 15924376..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/PredicateProperties.java +++ /dev/null @@ -1,106 +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.config; - -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Objects; - -import jakarta.validation.ValidationException; -import jakarta.validation.constraints.NotNull; - -import org.springframework.cloud.gateway.server.mvc.common.NameUtils; -import org.springframework.validation.annotation.Validated; - -import static org.springframework.util.StringUtils.tokenizeToStringArray; - -/** - * @author Spencer Gibb - */ -@Validated -public class PredicateProperties { - - @NotNull - private String name; - - private Map args = new LinkedHashMap<>(); - - public PredicateProperties() { - } - - public PredicateProperties(String text) { - int eqIdx = text.indexOf('='); - if (eqIdx <= 0) { - throw new ValidationException( - "Unable to parse PredicateDefinition text '" + text + "'" + ", must be of the form name=value"); - } - setName(text.substring(0, eqIdx)); - - String[] args = tokenizeToStringArray(text.substring(eqIdx + 1), ","); - - for (int i = 0; i < args.length; i++) { - this.args.put(NameUtils.generateName(i), args[i]); - } - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public Map getArgs() { - return args; - } - - public void setArgs(Map args) { - this.args = args; - } - - public void addArg(String key, String value) { - this.args.put(key, value); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - PredicateProperties that = (PredicateProperties) o; - return Objects.equals(name, that.name) && Objects.equals(args, that.args); - } - - @Override - public int hashCode() { - return Objects.hash(name, args); - } - - @Override - public String toString() { - final StringBuilder sb = new StringBuilder("PredicateDefinition{"); - sb.append("name='").append(name).append('\''); - sb.append(", args=").append(args); - sb.append('}'); - return sb.toString(); - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/RouteProperties.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/RouteProperties.java deleted file mode 100644 index 902be008..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/config/RouteProperties.java +++ /dev/null @@ -1,151 +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.config; - -import java.net.URI; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; - -import jakarta.validation.Valid; -import jakarta.validation.ValidationException; -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; - -import org.springframework.validation.annotation.Validated; - -import static org.springframework.util.StringUtils.tokenizeToStringArray; - -/** - * @author Spencer Gibb - */ -@Validated -public class RouteProperties { - - private String id; - - @NotEmpty - @Valid - private List predicates = new ArrayList<>(); - - @Valid - private List filters = new ArrayList<>(); - - @NotNull - private URI uri; - - private Map metadata = new HashMap<>(); - - private int order = 0; - - public RouteProperties() { - } - - public RouteProperties(String text) { - int eqIdx = text.indexOf('='); - if (eqIdx <= 0) { - throw new ValidationException( - "Unable to parse RouteDefinition text '" + text + "'" + ", must be of the form name=value"); - } - - setId(text.substring(0, eqIdx)); - - String[] args = tokenizeToStringArray(text.substring(eqIdx + 1), ","); - - setUri(URI.create(args[0])); - - for (int i = 1; i < args.length; i++) { - this.predicates.add(new PredicateProperties(args[i])); - } - } - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public List getPredicates() { - return predicates; - } - - public void setPredicates(List predicates) { - this.predicates = predicates; - } - - public List getFilters() { - return filters; - } - - public void setFilters(List filters) { - this.filters = filters; - } - - public URI getUri() { - return uri; - } - - public void setUri(URI uri) { - this.uri = uri; - } - - public int getOrder() { - return order; - } - - public void setOrder(int order) { - this.order = order; - } - - public Map getMetadata() { - return metadata; - } - - public void setMetadata(Map metadata) { - this.metadata = metadata; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - RouteProperties that = (RouteProperties) o; - return this.order == that.order && Objects.equals(this.id, that.id) - && Objects.equals(this.predicates, that.predicates) && Objects.equals(this.filters, that.filters) - && Objects.equals(this.uri, that.uri) && Objects.equals(this.metadata, that.metadata); - } - - @Override - public int hashCode() { - return Objects.hash(this.id, this.predicates, this.filters, this.uri, this.metadata, this.order); - } - - @Override - public String toString() { - return "RouteDefinition{" + "id='" + id + '\'' + ", predicates=" + predicates + ", filters=" + filters - + ", uri=" + uri + ", order=" + order + ", metadata=" + metadata + '}'; - } - -} 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 deleted file mode 100644 index 2f54e3dd..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/AfterFilterFunctions.java +++ /dev/null @@ -1,171 +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.util.ArrayList; -import java.util.Arrays; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.function.BiFunction; -import java.util.function.Consumer; -import java.util.regex.Pattern; - -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; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatusCode; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; -import org.springframework.web.servlet.function.ServerRequest; -import org.springframework.web.servlet.function.ServerResponse; - -public abstract class AfterFilterFunctions { - - private AfterFilterFunctions() { - } - - public static BiFunction addResponseHeader(String name, - String... values) { - return (request, response) -> { - String[] expandedValues = MvcUtils.expandMultiple(request, values); - response.headers().addAll(name, Arrays.asList(expandedValues)); - return response; - }; - } - - public static BiFunction dedupeResponseHeader(String name) { - return dedupeResponseHeader(name, DedupeStrategy.RETAIN_FIRST); - } - - public static BiFunction dedupeResponseHeader(String name, - DedupeStrategy strategy) { - Assert.hasText(name, "name must not be null or empty"); - Assert.notNull(strategy, "strategy must not be null"); - return (request, response) -> { - dedupeHeaders(response.headers(), name, strategy); - return response; - }; - } - - private static void dedupeHeaders(HttpHeaders headers, String names, DedupeStrategy strategy) { - if (headers == null || names == null || strategy == null) { - return; - } - String[] tokens = StringUtils.tokenizeToStringArray(names, " ", true, true); - for (String name : tokens) { - dedupeHeader(headers, name.trim(), strategy); - } - } - - private static void dedupeHeader(HttpHeaders headers, String name, DedupeStrategy strategy) { - List values = headers.get(name); - if (values == null || values.size() <= 1) { - return; - } - switch (strategy) { - case RETAIN_FIRST: - headers.set(name, values.get(0)); - break; - case RETAIN_LAST: - headers.set(name, values.get(values.size() - 1)); - break; - case RETAIN_UNIQUE: - headers.put(name, new ArrayList<>(new LinkedHashSet<>(values))); - break; - default: - break; - } - } - - public static BiFunction removeResponseHeader(String name) { - return (request, response) -> { - response.headers().remove(name); - return response; - }; - } - - public static BiFunction rewriteLocationResponseHeader() { - return RewriteLocationResponseHeaderFilterFunctions.rewriteLocationResponseHeader(config -> { - }); - } - - public static BiFunction rewriteLocationResponseHeader( - Consumer configConsumer) { - return RewriteLocationResponseHeaderFilterFunctions.rewriteLocationResponseHeader(configConsumer); - } - - public static BiFunction rewriteResponseHeader(String name, - String regexp, String originalReplacement) { - String replacement = originalReplacement.replace("$\\", "$"); - Pattern pattern = Pattern.compile(regexp); - return (request, response) -> { - response.headers().computeIfPresent(name, (key, values) -> { - List rewrittenValues = values.stream() - .map(value -> pattern.matcher(value).replaceAll(replacement)).toList(); - return new ArrayList<>(rewrittenValues); - }); - return response; - }; - } - - public static BiFunction setResponseHeader(String name, - String value) { - return (request, response) -> { - String expandedValue = MvcUtils.expand(request, value); - response.headers().set(name, expandedValue); - return response; - }; - } - - public static BiFunction setStatus(int statusCode) { - return setStatus(new HttpStatusHolder(null, statusCode)); - } - - public static BiFunction setStatus(HttpStatusCode statusCode) { - return setStatus(new HttpStatusHolder(statusCode, null)); - } - - public static BiFunction setStatus(HttpStatusHolder statusCode) { - return (request, response) -> { - if (response instanceof GatewayServerResponse res) { - res.setStatusCode(statusCode.resolve()); - } - return response; - }; - } - - public enum DedupeStrategy { - - /** - * Default: Retain the first value only. - */ - RETAIN_FIRST, - - /** - * Retain the last value only. - */ - RETAIN_LAST, - - /** - * Retain all unique values in the order of their first encounter. - */ - RETAIN_UNIQUE - - } - -} 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 deleted file mode 100644 index 2617930d..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/BeforeFilterFunctions.java +++ /dev/null @@ -1,442 +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.net.URI; -import java.net.URISyntaxException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.regex.Pattern; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.cloud.gateway.server.mvc.common.KeyValues.KeyValue; -import org.springframework.cloud.gateway.server.mvc.common.MvcUtils; -import org.springframework.core.log.LogMessage; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.util.Assert; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; -import org.springframework.util.StringUtils; -import org.springframework.util.unit.DataSize; -import org.springframework.util.unit.DataUnit; -import org.springframework.web.server.ResponseStatusException; -import org.springframework.web.servlet.function.ServerRequest; -import org.springframework.web.util.UriComponentsBuilder; -import org.springframework.web.util.UriTemplate; - -import static org.springframework.cloud.gateway.server.mvc.common.MvcUtils.CIRCUITBREAKER_EXECUTION_EXCEPTION_ATTR; -import static org.springframework.util.CollectionUtils.unmodifiableMultiValueMap; - -// TODO: can TokenRelay be here and not cause CNFE? -public abstract class BeforeFilterFunctions { - - private static final Log log = LogFactory.getLog(BeforeFilterFunctions.class); - - private static final String REQUEST_HEADER_SIZE_ERROR_PREFIX = "Request Header/s size is larger than permissible limit (%s)."; - - private static final String REQUEST_HEADER_SIZE_ERROR = " Request Header/s size for '%s' is %s."; - - private static final String REQUEST_SIZE_ERROR_MSG = "Request size is larger than permissible limit. Request size is %s where permissible limit is %s"; - - /** - * Default CircuitBreaker Fallback Execution Exception Type header name. - */ - public static final String CB_EXECUTION_EXCEPTION_TYPE = "Execution-Exception-Type"; - - /** - * Default CircuitBreaker Fallback Execution Exception Message header name. - */ - public static final String CB_EXECUTION_EXCEPTION_MESSAGE = "Execution-Exception-Message"; - - /** - * Default CircuitBreaker Root Cause Execution Exception Type header name. - */ - public static final String CB_ROOT_CAUSE_EXCEPTION_TYPE = "Root-Cause-Exception-Type"; - - /** - * Default CircuitBreaker Root Cause Execution Exception Message header name. - */ - public static final String CB_ROOT_CAUSE_EXCEPTION_MESSAGE = "Root-Cause-Exception-Message"; - - private BeforeFilterFunctions() { - } - - public static Function adaptCachedBody() { - return BodyFilterFunctions.adaptCachedBody(); - } - - public static Function addRequestHeader(String name, String... values) { - return request -> { - String[] expandedValues = MvcUtils.expandMultiple(request, values); - return ServerRequest.from(request).header(name, expandedValues).build(); - }; - } - - public static Function addRequestHeadersIfNotPresent(String... values) { - List keyValues = Arrays.stream(values).map(KeyValue::valueOf).toList(); - return addRequestHeadersIfNotPresent(keyValues); - } - - public static Function addRequestHeadersIfNotPresent(List keyValues) { - HttpHeaders newHeaders = new HttpHeaders(); - keyValues.forEach(keyValue -> newHeaders.add(keyValue.getKey(), keyValue.getValue())); - return request -> { - ServerRequest.Builder requestBuilder = ServerRequest.from(request); - newHeaders.forEach((newHeaderName, newHeaderValues) -> { - boolean headerIsMissingOrBlank = request.headers().asHttpHeaders().getOrEmpty(newHeaderName).stream() - .allMatch(h -> !StringUtils.hasText(h)); - if (headerIsMissingOrBlank) { - requestBuilder.headers(httpHeaders -> { - List expandedValues = MvcUtils.expandMultiple(request, newHeaderValues); - httpHeaders.addAll(newHeaderName, expandedValues); - }); - } - }); - return requestBuilder.build(); - }; - } - - public static Function addRequestParameter(String name, String... values) { - return request -> { - String[] expandedValues = MvcUtils.expandMultiple(request, values); - return ServerRequest.from(request).param(name, expandedValues).build(); - }; - } - - public static Function fallbackHeaders() { - return fallbackHeaders(config -> { - }); - } - - public static Function fallbackHeaders( - Consumer configConsumer) { - FallbackHeadersConfig config = new FallbackHeadersConfig(); - configConsumer.accept(config); - return request -> request.attribute(CIRCUITBREAKER_EXECUTION_EXCEPTION_ATTR).map(Throwable.class::cast) - .map(throwable -> ServerRequest.from(request).headers(httpHeaders -> { - httpHeaders.add(config.getExecutionExceptionTypeHeaderName(), throwable.getClass().getName()); - if (throwable.getMessage() != null) { - httpHeaders.add(config.getExecutionExceptionMessageHeaderName(), throwable.getMessage()); - } - Throwable rootCause = getRootCause(throwable); - if (rootCause != null) { - httpHeaders.add(config.getRootCauseExceptionTypeHeaderName(), rootCause.getClass().getName()); - if (rootCause.getMessage() != null) { - httpHeaders.add(config.getRootCauseExceptionMessageHeaderName(), rootCause.getMessage()); - } - } - }).build()).orElse(request); - } - - private static Throwable getRootCause(Throwable throwable) { - List list = getThrowableList(throwable); - return list.isEmpty() ? null : list.get(list.size() - 1); - } - - private static List getThrowableList(Throwable throwable) { - List list = new ArrayList<>(); - while (throwable != null && !list.contains(throwable)) { - list.add(throwable); - throwable = throwable.getCause(); - } - return list; - } - - public static Function mapRequestHeader(String fromHeader, String toHeader) { - return request -> { - if (request.headers().asHttpHeaders().containsKey(fromHeader)) { - List values = request.headers().header(fromHeader); - return ServerRequest.from(request).header(toHeader, values.toArray(new String[0])).build(); - } - return request; - }; - } - - public static Function modifyRequestBody(Class inClass, Class outClass, - String newContentType, BodyFilterFunctions.RewriteFunction rewriteFunction) { - return BodyFilterFunctions.modifyRequestBody(inClass, outClass, newContentType, rewriteFunction); - } - - public static Function prefixPath(String prefix) { - final UriTemplate uriTemplate = new UriTemplate(prefix); - - return request -> { - Map uriVariables = MvcUtils.getUriTemplateVariables(request); - URI uri = uriTemplate.expand(uriVariables); - - String newPath = uri.getRawPath() + request.uri().getRawPath(); - - URI prefixedUri = UriComponentsBuilder.fromUri(request.uri()).replacePath(newPath).build().toUri(); - return ServerRequest.from(request).uri(prefixedUri).build(); - }; - } - - public static Function preserveHost() { - return request -> { - request.attributes().put(MvcUtils.PRESERVE_HOST_HEADER_ATTRIBUTE, true); - return request; - }; - } - - public static Function removeRequestHeader(String name) { - return request -> ServerRequest.from(request).headers(httpHeaders -> httpHeaders.remove(name)).build(); - } - - public static Function removeRequestParameter(String name) { - return request -> { - MultiValueMap queryParams = new LinkedMultiValueMap<>(request.params()); - queryParams.remove(name); - - // remove from uri - URI newUri = UriComponentsBuilder.fromUri(request.uri()) - .replaceQueryParams(unmodifiableMultiValueMap(queryParams)).build().toUri(); - - // remove resolved params from request - return ServerRequest.from(request).params(params -> params.remove(name)).uri(newUri).build(); - }; - } - - public static Function requestHeaderSize(String maxSize) { - return requestHeaderSize(DataSize.parse(maxSize)); - } - - public static Function requestHeaderSize(DataSize maxSize) { - Assert.notNull(maxSize, "maxSize may not be null"); - Assert.isTrue(maxSize.toBytes() > 0, "maxSize must be greater than 0"); - return request -> { - HashMap longHeaders = new HashMap<>(); - - request.headers().asHttpHeaders().forEach((key, values) -> { - long headerSizeInBytes = 0L; - headerSizeInBytes += key.getBytes().length; - for (String value : values) { - headerSizeInBytes += value.getBytes().length; - } - if (headerSizeInBytes > maxSize.toBytes()) { - longHeaders.put(key, headerSizeInBytes); - } - }); - - if (!longHeaders.isEmpty()) { - StringBuilder errorMessage = new StringBuilder( - String.format(REQUEST_HEADER_SIZE_ERROR_PREFIX, maxSize)); - longHeaders.forEach((header, size) -> errorMessage - .append(String.format(REQUEST_HEADER_SIZE_ERROR, header, DataSize.of(size, DataUnit.BYTES)))); - - throw new ResponseStatusException(HttpStatus.REQUEST_HEADER_FIELDS_TOO_LARGE, errorMessage.toString()) { - @Override - public HttpHeaders getHeaders() { - HttpHeaders httpHeaders = new HttpHeaders(); - // TODO: customize header name - httpHeaders.add("errorMessage", errorMessage.toString()); - return httpHeaders; - } - }; - - } - - return request; - }; - } - - public static Function requestHeaderToRequestUri(String name) { - return request -> { - if (request.headers().asHttpHeaders().containsKey(name)) { - String newUri = request.headers().firstHeader(name); - try { - MvcUtils.setRequestUrl(request, new URI(newUri)); - } - catch (URISyntaxException e) { - log.info(LogMessage.format("Request url is invalid : url=%s", newUri), e); - } - } - return request; - }; - } - - public static Function requestSize(String maxSize) { - return requestSize(DataSize.parse(maxSize)); - } - - public static Function requestSize(DataSize maxSize) { - Assert.notNull(maxSize, "maxSize may not be null"); - Assert.isTrue(maxSize.toBytes() > 0, "maxSize must be greater than 0"); - return request -> { - if (request.headers().asHttpHeaders().containsKey(HttpHeaders.CONTENT_LENGTH)) { - long contentLength = request.headers().asHttpHeaders().getContentLength(); - if (contentLength > maxSize.toBytes()) { - String errorMessage = String.format(REQUEST_SIZE_ERROR_MSG, DataSize.ofBytes(contentLength), - maxSize); - throw new ResponseStatusException(HttpStatus.PAYLOAD_TOO_LARGE, errorMessage) { - @Override - public HttpHeaders getHeaders() { - HttpHeaders httpHeaders = new HttpHeaders(); - // TODO: customize header name - httpHeaders.add("errorMessage", errorMessage); - return httpHeaders; - } - }; - } - } - return request; - }; - } - - public static Function rewritePath(String regexp, String replacement) { - String normalizedReplacement = replacement.replace("$\\", "$"); - Pattern pattern = Pattern.compile(regexp); - return request -> { - // TODO: original request url - String path = request.uri().getRawPath(); - String newPath = pattern.matcher(path).replaceAll(normalizedReplacement); - - URI rewrittenUri = UriComponentsBuilder.fromUri(request.uri()).replacePath(newPath).build().toUri(); - - ServerRequest modified = ServerRequest.from(request).uri(rewrittenUri).build(); - - MvcUtils.setRequestUrl(modified, modified.uri()); - return modified; - }; - } - - public static Function routeId(String routeId) { - return request -> { - MvcUtils.setRouteId(request, routeId); - return request; - }; - } - - public static Function setPath(String path) { - UriTemplate uriTemplate = new UriTemplate(path); - - return request -> { - Map uriVariables = MvcUtils.getUriTemplateVariables(request); - URI uri = uriTemplate.expand(uriVariables); - String newPath = uri.getRawPath(); - - URI prefixedUri = UriComponentsBuilder.fromUri(request.uri()).replacePath(newPath).build().toUri(); - return ServerRequest.from(request).uri(prefixedUri).build(); - }; - } - - public static Function setRequestHeader(String name, String value) { - return request -> { - String expandedValue = MvcUtils.expand(request, value); - return ServerRequest.from(request).headers(httpHeaders -> httpHeaders.set(name, expandedValue)).build(); - }; - } - - public static Function setRequestHostHeader(String host) { - return request -> { - String expandedValue = MvcUtils.expand(request, host); - ServerRequest modified = ServerRequest.from(request).headers(httpHeaders -> { - httpHeaders.remove(HttpHeaders.HOST); - httpHeaders.set(HttpHeaders.HOST, expandedValue); - }).build(); - - // Make sure the header we just set is preserved - modified.attributes().put(MvcUtils.PRESERVE_HOST_HEADER_ATTRIBUTE, true); - return modified; - }; - } - - public static Function stripPrefix() { - return stripPrefix(1); - } - - public static Function stripPrefix(int parts) { - return request -> { - // TODO: gateway url attributes - String path = request.uri().getRawPath(); - // TODO: begin duplicate code from StripPrefixGatewayFilterFactory - String[] originalParts = StringUtils.tokenizeToStringArray(path, "/"); - - // all new paths start with / - StringBuilder newPath = new StringBuilder("/"); - for (int i = 0; i < originalParts.length; i++) { - if (i >= parts) { - // only append slash if this is the second part or greater - if (newPath.length() > 1) { - newPath.append('/'); - } - newPath.append(originalParts[i]); - } - } - if (newPath.length() > 1 && path.endsWith("/")) { - newPath.append('/'); - } - // TODO: end duplicate code from StripPrefixGatewayFilterFactory - - URI prefixedUri = UriComponentsBuilder.fromUri(request.uri()).replacePath(newPath.toString()).build() - .toUri(); - return ServerRequest.from(request).uri(prefixedUri).build(); - }; - } - - public static class FallbackHeadersConfig { - - private String executionExceptionTypeHeaderName = CB_EXECUTION_EXCEPTION_TYPE; - - private String executionExceptionMessageHeaderName = CB_EXECUTION_EXCEPTION_MESSAGE; - - private String rootCauseExceptionTypeHeaderName = CB_ROOT_CAUSE_EXCEPTION_TYPE; - - private String rootCauseExceptionMessageHeaderName = CB_ROOT_CAUSE_EXCEPTION_MESSAGE; - - public String getExecutionExceptionTypeHeaderName() { - return executionExceptionTypeHeaderName; - } - - public void setExecutionExceptionTypeHeaderName(String executionExceptionTypeHeaderName) { - this.executionExceptionTypeHeaderName = executionExceptionTypeHeaderName; - } - - public String getExecutionExceptionMessageHeaderName() { - return executionExceptionMessageHeaderName; - } - - public void setExecutionExceptionMessageHeaderName(String executionExceptionMessageHeaderName) { - this.executionExceptionMessageHeaderName = executionExceptionMessageHeaderName; - } - - public String getRootCauseExceptionTypeHeaderName() { - return rootCauseExceptionTypeHeaderName; - } - - public void setRootCauseExceptionTypeHeaderName(String rootCauseExceptionTypeHeaderName) { - this.rootCauseExceptionTypeHeaderName = rootCauseExceptionTypeHeaderName; - } - - public String getRootCauseExceptionMessageHeaderName() { - return rootCauseExceptionMessageHeaderName; - } - - public void setRootCauseExceptionMessageHeaderName(String rootCauseExceptionMessageHeaderName) { - this.rootCauseExceptionMessageHeaderName = rootCauseExceptionMessageHeaderName; - } - - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/BodyFilterFunctions.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/BodyFilterFunctions.java deleted file mode 100644 index 0664f43a..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/BodyFilterFunctions.java +++ /dev/null @@ -1,367 +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.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.io.UncheckedIOException; -import java.net.InetSocketAddress; -import java.net.URI; -import java.security.Principal; -import java.time.Instant; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.function.BiFunction; -import java.util.function.Consumer; -import java.util.function.Function; - -import jakarta.servlet.ReadListener; -import jakarta.servlet.ServletException; -import jakarta.servlet.ServletInputStream; -import jakarta.servlet.http.Cookie; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletRequestWrapper; -import jakarta.servlet.http.HttpSession; -import jakarta.servlet.http.Part; - -import org.springframework.cloud.gateway.server.mvc.common.MvcUtils; -import org.springframework.core.ParameterizedTypeReference; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpOutputMessage; -import org.springframework.http.MediaType; -import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.http.server.PathContainer; -import org.springframework.http.server.RequestPath; -import org.springframework.util.MultiValueMap; -import org.springframework.util.StringUtils; -import org.springframework.validation.BindException; -import org.springframework.web.bind.WebDataBinder; -import org.springframework.web.servlet.function.ServerRequest; -import org.springframework.web.servlet.function.ServerResponse; -import org.springframework.web.util.UriBuilder; - -import static org.springframework.cloud.gateway.server.mvc.common.MvcUtils.cacheAndReadBody; -import static org.springframework.cloud.gateway.server.mvc.common.MvcUtils.getAttribute; - -public abstract class BodyFilterFunctions { - - private BodyFilterFunctions() { - } - - public static Function adaptCachedBody() { - return request -> { - Object o = getAttribute(request, MvcUtils.CACHED_REQUEST_BODY_ATTR); - if (o instanceof ByteArrayInputStream body) { - return wrapRequest(request, body); - } - - return request; - }; - } - - private static ServerRequestWrapper wrapRequest(ServerRequest request, byte[] body) { - return wrapRequest(request, new ByteArrayInputStream(body)); - } - - private static ServerRequestWrapper wrapRequest(ServerRequest request, ByteArrayInputStream body) { - ByteArrayServletInputStream inputStream = new ByteArrayServletInputStream(body); - HttpServletRequestWrapper wrapper = new HttpServletRequestWrapper(request.servletRequest()) { - @Override - public ServletInputStream getInputStream() { - return inputStream; - } - }; - - return new ServerRequestWrapper(request) { - @Override - public HttpServletRequest servletRequest() { - return wrapper; - } - }; - } - - @SuppressWarnings("unchecked") - public static Function modifyRequestBody(Class inClass, Class outClass, - String newContentType, RewriteFunction rewriteFunction) { - return request -> cacheAndReadBody(request, inClass).map(body -> { - R convertedBody = rewriteFunction.apply(request, body); - // TODO: cache converted body - - MediaType contentType = (StringUtils.hasText(newContentType)) ? MediaType.parseMediaType(newContentType) - : request.headers().contentType().orElse(null); - - List> httpMessageConverters = request.messageConverters(); - for (HttpMessageConverter messageConverter : httpMessageConverters) { - if (messageConverter.canWrite(outClass, contentType)) { - HttpHeaders headers = new HttpHeaders(); - headers.putAll(request.headers().asHttpHeaders()); - - // the new content type will be computed by converter - // and then set in the request decorator - headers.remove(HttpHeaders.CONTENT_LENGTH); - - // if the body is changing content types, set it here, to the - // bodyInserter - // will know about it - if (contentType != null) { - headers.setContentType(contentType); - } - try { - ByteArrayHttpOutputMessage outputMessage = new ByteArrayHttpOutputMessage(headers); - ((HttpMessageConverter) messageConverter).write(convertedBody, contentType, outputMessage); - ServerRequest modified = ServerRequest.from(request) - .headers(httpHeaders -> httpHeaders.putAll(headers)).build(); - return wrapRequest(modified, outputMessage.getBytes()); - } - catch (IOException e) { - throw new UncheckedIOException(e); - } - } - } - - return request; - }).orElse(request); - } - - private final static class ByteArrayHttpOutputMessage implements HttpOutputMessage { - - private final HttpHeaders headers; - - private final ByteArrayOutputStream body; - - private ByteArrayHttpOutputMessage(HttpHeaders headers) { - this.headers = headers; - this.body = new ByteArrayOutputStream(); - } - - @Override - public OutputStream getBody() throws IOException { - return this.body; - } - - @Override - public HttpHeaders getHeaders() { - return headers; - } - - public byte[] getBytes() { - return this.body.toByteArray(); - } - - } - - public interface RewriteFunction extends BiFunction { - - } - - private static class ByteArrayServletInputStream extends ServletInputStream { - - private final ByteArrayInputStream body; - - ByteArrayServletInputStream(ByteArrayInputStream body) { - body.reset(); - this.body = body; - } - - @Override - public boolean isFinished() { - return body.available() <= 0; - } - - @Override - public boolean isReady() { - return true; - } - - @Override - public void setReadListener(ReadListener listener) { - - } - - @Override - public int read() { - return body.read(); - } - - } - - private static class ServerRequestWrapper implements ServerRequest { - - private final ServerRequest delegate; - - protected ServerRequestWrapper(ServerRequest delegate) { - this.delegate = delegate; - } - - @Override - public T bind(Class bindType) throws BindException { - return delegate.bind(bindType); - } - - @Override - public T bind(Class bindType, Consumer dataBinderCustomizer) throws BindException { - return delegate.bind(bindType, dataBinderCustomizer); - } - - @Override - public HttpMethod method() { - return delegate.method(); - } - - @Override - @Deprecated - public String methodName() { - return delegate.methodName(); - } - - @Override - public URI uri() { - return delegate.uri(); - } - - @Override - public UriBuilder uriBuilder() { - return delegate.uriBuilder(); - } - - @Override - public String path() { - return delegate.path(); - } - - @Override - @Deprecated - public PathContainer pathContainer() { - return delegate.pathContainer(); - } - - @Override - public RequestPath requestPath() { - return delegate.requestPath(); - } - - @Override - public Headers headers() { - return delegate.headers(); - } - - @Override - public MultiValueMap cookies() { - return delegate.cookies(); - } - - @Override - public Optional remoteAddress() { - return delegate.remoteAddress(); - } - - @Override - public List> messageConverters() { - return delegate.messageConverters(); - } - - @Override - public T body(Class bodyType) throws ServletException, IOException { - return delegate.body(bodyType); - } - - @Override - public T body(ParameterizedTypeReference bodyType) throws ServletException, IOException { - return delegate.body(bodyType); - } - - @Override - public Optional attribute(String name) { - return delegate.attribute(name); - } - - @Override - public Map attributes() { - return delegate.attributes(); - } - - @Override - public Optional param(String name) { - return delegate.param(name); - } - - @Override - public MultiValueMap params() { - return delegate.params(); - } - - @Override - public MultiValueMap multipartData() throws IOException, ServletException { - return delegate.multipartData(); - } - - @Override - public String pathVariable(String name) { - return delegate.pathVariable(name); - } - - @Override - public Map pathVariables() { - return delegate.pathVariables(); - } - - @Override - public HttpSession session() { - return delegate.session(); - } - - @Override - public Optional principal() { - return delegate.principal(); - } - - @Override - public HttpServletRequest servletRequest() { - return delegate.servletRequest(); - } - - @Override - public Optional checkNotModified(Instant lastModified) { - return delegate.checkNotModified(lastModified); - } - - @Override - public Optional checkNotModified(String etag) { - return delegate.checkNotModified(etag); - } - - @Override - public Optional checkNotModified(Instant lastModified, String etag) { - return delegate.checkNotModified(lastModified, etag); - } - - public static ServerRequest create(HttpServletRequest servletRequest, - List> messageReaders) { - return ServerRequest.create(servletRequest, messageReaders); - } - - public static Builder from(ServerRequest other) { - return ServerRequest.from(other); - } - - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/Bucket4jFilterFunctions.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/Bucket4jFilterFunctions.java deleted file mode 100644 index 96ab2757..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/Bucket4jFilterFunctions.java +++ /dev/null @@ -1,194 +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.time.Duration; -import java.util.Arrays; -import java.util.Collection; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeUnit; -import java.util.function.Consumer; -import java.util.function.Function; - -import io.github.bucket4j.Bandwidth; -import io.github.bucket4j.BucketConfiguration; -import io.github.bucket4j.ConsumptionProbe; -import io.github.bucket4j.distributed.AsyncBucketProxy; -import io.github.bucket4j.distributed.proxy.AsyncProxyManager; - -import org.springframework.cloud.gateway.server.mvc.common.MvcUtils; -import org.springframework.http.HttpStatus; -import org.springframework.http.HttpStatusCode; -import org.springframework.util.Assert; -import org.springframework.web.servlet.function.HandlerFilterFunction; -import org.springframework.web.servlet.function.ServerRequest; -import org.springframework.web.servlet.function.ServerResponse; - -public abstract class Bucket4jFilterFunctions { - - /** - * Default Header Name. - */ - public static final String DEFAULT_HEADER_NAME = "X-RateLimit-Remaining"; - - private static final Function DEFAULT_CONFIGURATION_BUILDER = config -> BucketConfiguration - .builder().addLimit(Bandwidth.simple(config.getCapacity(), config.getPeriod())).build(); - - private Bucket4jFilterFunctions() { - } - - public static HandlerFilterFunction rateLimit(long capacity, Duration period, - Function keyResolver) { - return rateLimit(c -> c.setCapacity(capacity).setPeriod(period).setKeyResolver(keyResolver)); - } - - @SuppressWarnings({ "rawtypes", "unchecked" }) - public static HandlerFilterFunction rateLimit( - Consumer configConsumer) { - RateLimitConfig config = new RateLimitConfig(); - configConsumer.accept(config); - BucketConfiguration bucketConfiguration = config.getConfigurationBuilder().apply(config); - return (request, next) -> { - AsyncProxyManager proxyManager = MvcUtils.getApplicationContext(request).getBean(AsyncProxyManager.class); - AsyncBucketProxy bucket = proxyManager.builder().build(config.getKeyResolver().apply(request), - bucketConfiguration); - CompletableFuture bucketFuture = bucket.tryConsumeAndReturnRemaining(config.getTokens()); - ConsumptionProbe consumptionProbe; - if (config.getTimeout() != null) { - consumptionProbe = bucketFuture.get(config.getTimeout().toMillis(), TimeUnit.MILLISECONDS); - } - else { - consumptionProbe = bucketFuture.get(); - } - boolean allowed = consumptionProbe.isConsumed(); - long remainingTokens = consumptionProbe.getRemainingTokens(); - if (allowed) { - ServerResponse serverResponse = next.handle(request); - serverResponse.headers().add(config.getHeaderName(), String.valueOf(remainingTokens)); - return serverResponse; - } - return ServerResponse.status(config.getStatusCode()) - .header(config.getHeaderName(), String.valueOf(remainingTokens)).build(); - }; - } - - public static class RateLimitConfig { - - Function configurationBuilder = DEFAULT_CONFIGURATION_BUILDER; - - long capacity; - - Duration period; - - Function keyResolver; - - HttpStatusCode statusCode = HttpStatus.TOO_MANY_REQUESTS; - - Duration timeout; - - int tokens = 1; - - String headerName = DEFAULT_HEADER_NAME; - - public Function getConfigurationBuilder() { - return configurationBuilder; - } - - public void setConfigurationBuilder(Function configurationBuilder) { - Assert.notNull(configurationBuilder, "configurationBuilder may not be null"); - this.configurationBuilder = configurationBuilder; - } - - public long getCapacity() { - return capacity; - } - - public RateLimitConfig setCapacity(long capacity) { - this.capacity = capacity; - return this; - } - - public Duration getPeriod() { - return period; - } - - public RateLimitConfig setPeriod(Duration period) { - this.period = period; - return this; - } - - public Function getKeyResolver() { - return keyResolver; - } - - public RateLimitConfig setKeyResolver(Function keyResolver) { - Assert.notNull(keyResolver, "keyResolver may not be null"); - this.keyResolver = keyResolver; - return this; - } - - public HttpStatusCode getStatusCode() { - return statusCode; - } - - public RateLimitConfig setStatusCode(HttpStatusCode statusCode) { - this.statusCode = statusCode; - return this; - } - - public Duration getTimeout() { - return timeout; - } - - public RateLimitConfig setTimeout(Duration timeout) { - this.timeout = timeout; - return this; - } - - public int getTokens() { - return tokens; - } - - public RateLimitConfig setTokens(int tokens) { - Assert.isTrue(tokens > 0, "tokens must be greater than zero"); - this.tokens = tokens; - return this; - } - - public String getHeaderName() { - return headerName; - } - - public RateLimitConfig setHeaderName(String headerName) { - Assert.notNull(headerName, "headerName may not be null"); - this.headerName = headerName; - return this; - } - - } - - static class FilterSupplier implements org.springframework.cloud.gateway.server.mvc.filter.FilterSupplier { - - @Override - public Collection get() { - return Arrays.asList(Bucket4jFilterFunctions.class.getMethods()); - } - - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/CircuitBreakerFilterFunctions.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/CircuitBreakerFilterFunctions.java deleted file mode 100644 index 1dff0450..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/CircuitBreakerFilterFunctions.java +++ /dev/null @@ -1,174 +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.io.IOException; -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; -import java.util.concurrent.TimeoutException; -import java.util.function.Consumer; -import java.util.stream.Collectors; - -import jakarta.servlet.ServletException; - -import org.springframework.cloud.client.circuitbreaker.CircuitBreaker; -import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory; -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.common.Shortcut; -import org.springframework.cloud.gateway.server.mvc.handler.GatewayServerResponse; -import org.springframework.http.HttpStatus; -import org.springframework.http.HttpStatusCode; -import org.springframework.util.StringUtils; -import org.springframework.web.server.ResponseStatusException; -import org.springframework.web.servlet.function.HandlerFilterFunction; -import org.springframework.web.servlet.function.ServerResponse; - -public abstract class CircuitBreakerFilterFunctions { - - private CircuitBreakerFilterFunctions() { - } - - @Shortcut - public static HandlerFilterFunction circuitBreaker(String id) { - return circuitBreaker(config -> config.setId(id)); - } - - public static HandlerFilterFunction circuitBreaker(String id, String fallbackPath) { - return circuitBreaker(config -> config.setId(id).setFallbackPath(fallbackPath)); - } - - public static HandlerFilterFunction circuitBreaker( - Consumer configConsumer) { - CircuitBreakerConfig config = new CircuitBreakerConfig(); - configConsumer.accept(config); - Set failureStatuses = config.getStatusCodes().stream() - .map(status -> HttpStatusHolder.valueOf(status).resolve()).collect(Collectors.toSet()); - return (request, next) -> { - CircuitBreakerFactory circuitBreakerFactory = MvcUtils.getApplicationContext(request) - .getBean(CircuitBreakerFactory.class); - // TODO: cache - CircuitBreaker circuitBreaker = circuitBreakerFactory.create(config.getId()); - return circuitBreaker.run(() -> { - try { - ServerResponse serverResponse = next.handle(request); - // on configured status code, throw exception - if (failureStatuses.contains(serverResponse.statusCode())) { - throw new CircuitBreakerStatusCodeException(serverResponse.statusCode()); - } - return serverResponse; - } - catch (RuntimeException e) { - throw e; - } - catch (Exception e) { - throw new RuntimeException(e); - } - }, throwable -> { - // if no fallback - if (!StringUtils.hasText(config.getFallbackPath())) { - // if timeout exception, GATEWAY_TIMEOUT - if (throwable instanceof TimeoutException) { - throw new ResponseStatusException(HttpStatus.GATEWAY_TIMEOUT, throwable.getMessage(), - throwable); - } - // TODO: if not permitted (like circuit open), SERVICE_UNAVAILABLE - // TODO: if resume without error, return ok response? - throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, throwable.getMessage(), - throwable); - } - - // add the throwable as an attribute. That way, if the fallback is a - // different gateway route, it can use the fallbackHeaders() filter - // to convert it to headers. - request.attributes().put(MvcUtils.CIRCUITBREAKER_EXECUTION_EXCEPTION_ATTR, throwable); - - // handle fallback - // ok() is wrong, but will be overwritten by the forwarded request - return GatewayServerResponse.ok().build((httpServletRequest, httpServletResponse) -> { - try { - String expandedFallback = MvcUtils.expand(request, config.getFallbackPath()); - request.servletRequest().getServletContext().getRequestDispatcher(expandedFallback) - .forward(httpServletRequest, httpServletResponse); - return null; - } - catch (ServletException | IOException e) { - throw new RuntimeException(e); - } - }); - }); - }; - } - - public static class CircuitBreakerConfig { - - private String id; - - private String fallbackPath; - - private Set statusCodes = new HashSet<>(); - - public String getId() { - return id; - } - - public CircuitBreakerConfig setId(String id) { - this.id = id; - return this; - } - - public String getFallbackPath() { - return fallbackPath; - } - - public CircuitBreakerConfig setFallbackPath(String fallbackPath) { - this.fallbackPath = fallbackPath; - return this; - } - - public Set getStatusCodes() { - return statusCodes; - } - - public CircuitBreakerConfig setStatusCodes(Set statusCodes) { - this.statusCodes = statusCodes; - return this; - } - - } - - public static class CircuitBreakerStatusCodeException extends ResponseStatusException { - - public CircuitBreakerStatusCodeException(HttpStatusCode statusCode) { - super(statusCode); - } - - } - - public static class FilterSupplier implements org.springframework.cloud.gateway.server.mvc.filter.FilterSupplier { - - @Override - public Collection get() { - return Arrays.asList(CircuitBreakerFilterFunctions.class.getMethods()); - } - - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/FilterDiscoverer.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/FilterDiscoverer.java deleted file mode 100644 index 28b8526f..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/FilterDiscoverer.java +++ /dev/null @@ -1,29 +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 org.springframework.cloud.gateway.server.mvc.common.AbstractGatewayDiscoverer; -import org.springframework.web.servlet.function.HandlerFilterFunction; - -public class FilterDiscoverer extends AbstractGatewayDiscoverer { - - @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/FilterFunctions.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/FilterFunctions.java deleted file mode 100644 index 0021cd18..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/FilterFunctions.java +++ /dev/null @@ -1,236 +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.function.Consumer; - -import org.springframework.cloud.gateway.server.mvc.common.HttpStatusHolder; -import org.springframework.cloud.gateway.server.mvc.common.KeyValues; -import org.springframework.cloud.gateway.server.mvc.common.Shortcut; -import org.springframework.cloud.gateway.server.mvc.filter.AfterFilterFunctions.DedupeStrategy; -import org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.FallbackHeadersConfig; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatusCode; -import org.springframework.util.Assert; -import org.springframework.util.unit.DataSize; -import org.springframework.web.servlet.function.HandlerFilterFunction; -import org.springframework.web.servlet.function.ServerResponse; - -import static org.springframework.web.servlet.function.HandlerFilterFunction.ofRequestProcessor; -import static org.springframework.web.servlet.function.HandlerFilterFunction.ofResponseProcessor; - -// TODO: can Bucket4j, CircuitBreaker, Retry be here and not cause CNFE? -public interface FilterFunctions { - - @Shortcut - static HandlerFilterFunction adaptCachedBody() { - return ofRequestProcessor(BeforeFilterFunctions.adaptCachedBody()); - } - - @Shortcut - static HandlerFilterFunction addRequestHeader(String name, String... values) { - return ofRequestProcessor(BeforeFilterFunctions.addRequestHeader(name, values)); - } - - static HandlerFilterFunction addRequestHeadersIfNotPresent(String values) { - return addRequestHeadersIfNotPresent(KeyValues.valueOf(values)); - } - - @Shortcut - static HandlerFilterFunction addRequestHeadersIfNotPresent(KeyValues keyValues) { - return ofRequestProcessor(BeforeFilterFunctions.addRequestHeadersIfNotPresent(keyValues.getKeyValues())); - } - - @Shortcut - static HandlerFilterFunction addRequestParameter(String name, String... values) { - return ofRequestProcessor(BeforeFilterFunctions.addRequestParameter(name, values)); - } - - @Shortcut - static HandlerFilterFunction addResponseHeader(String name, String... values) { - return ofResponseProcessor(AfterFilterFunctions.addResponseHeader(name, values)); - } - - static HandlerFilterFunction dedupeResponseHeader(String name) { - return ofResponseProcessor(AfterFilterFunctions.dedupeResponseHeader(name)); - } - - @Shortcut - static HandlerFilterFunction dedupeResponseHeader(String name, - DedupeStrategy strategy) { - return ofResponseProcessor(AfterFilterFunctions.dedupeResponseHeader(name, strategy)); - } - - @Shortcut - static HandlerFilterFunction fallbackHeaders() { - return ofRequestProcessor(BeforeFilterFunctions.fallbackHeaders()); - } - - static HandlerFilterFunction fallbackHeaders( - Consumer configConsumer) { - return ofRequestProcessor(BeforeFilterFunctions.fallbackHeaders(configConsumer)); - } - - @Shortcut - static HandlerFilterFunction mapRequestHeader(String fromHeader, String toHeader) { - return ofRequestProcessor(BeforeFilterFunctions.mapRequestHeader(fromHeader, toHeader)); - } - - @Shortcut - static HandlerFilterFunction prefixPath(String prefix) { - return ofRequestProcessor(BeforeFilterFunctions.prefixPath(prefix)); - } - - @Shortcut - static HandlerFilterFunction preserveHost() { - return ofRequestProcessor(BeforeFilterFunctions.preserveHost()); - } - - static HandlerFilterFunction redirectTo(int status, URI uri) { - return redirectTo(new HttpStatusHolder(null, status), uri); - } - - static HandlerFilterFunction redirectTo(HttpStatusCode status, URI uri) { - return redirectTo(new HttpStatusHolder(status, null), uri); - } - - @Shortcut - static HandlerFilterFunction redirectTo(HttpStatusHolder status, URI uri) { - Assert.isTrue(status.is3xxRedirection(), "status must be a 3xx code, but was " + status); - - return (request, next) -> ServerResponse.status(status.resolve()).header(HttpHeaders.LOCATION, uri.toString()) - .build(); - } - - @Shortcut - static HandlerFilterFunction removeRequestHeader(String name) { - return ofRequestProcessor(BeforeFilterFunctions.removeRequestHeader(name)); - } - - @Shortcut - static HandlerFilterFunction removeRequestParameter(String name) { - return ofRequestProcessor(BeforeFilterFunctions.removeRequestParameter(name)); - } - - @Shortcut - static HandlerFilterFunction removeResponseHeader(String name) { - return ofResponseProcessor(AfterFilterFunctions.removeResponseHeader(name)); - } - - @Shortcut - static HandlerFilterFunction requestHeaderSize(String maxSize) { - return ofRequestProcessor(BeforeFilterFunctions.requestHeaderSize(maxSize)); - } - - static HandlerFilterFunction requestHeaderSize(DataSize maxSize) { - return ofRequestProcessor(BeforeFilterFunctions.requestHeaderSize(maxSize)); - } - - @Shortcut - static HandlerFilterFunction requestHeaderToRequestUri(String name) { - return ofRequestProcessor(BeforeFilterFunctions.requestHeaderToRequestUri(name)); - } - - @Shortcut - static HandlerFilterFunction requestSize(String maxSize) { - return ofRequestProcessor(BeforeFilterFunctions.requestSize(maxSize)); - } - - static HandlerFilterFunction requestSize(DataSize maxSize) { - return ofRequestProcessor(BeforeFilterFunctions.requestSize(maxSize)); - } - - @Shortcut - static HandlerFilterFunction rewriteLocationResponseHeader(String stripVersion, - String locationHeaderName, String hostValue, String protocols) { - return ofResponseProcessor(RewriteLocationResponseHeaderFilterFunctions - .rewriteLocationResponseHeader(config -> config.setStripVersion(stripVersion) - .setLocationHeaderName(locationHeaderName).setHostValue(hostValue).setProtocols(protocols))); - } - - @Shortcut - static HandlerFilterFunction rewritePath(String regexp, String replacement) { - return ofRequestProcessor(BeforeFilterFunctions.rewritePath(regexp, replacement)); - } - - @Shortcut - static HandlerFilterFunction rewriteResponseHeader(String name, String regexp, - String replacement) { - return ofResponseProcessor(AfterFilterFunctions.rewriteResponseHeader(name, regexp, replacement)); - } - - @Shortcut - static HandlerFilterFunction routeId(String routeId) { - return ofRequestProcessor(BeforeFilterFunctions.routeId(routeId)); - } - - @Shortcut - static HandlerFilterFunction setPath(String path) { - return ofRequestProcessor(BeforeFilterFunctions.setPath(path)); - } - - @Shortcut - static HandlerFilterFunction setRequestHeader(String name, String value) { - return ofRequestProcessor(BeforeFilterFunctions.setRequestHeader(name, value)); - } - - @Shortcut - static HandlerFilterFunction setRequestHostHeader(String host) { - return ofRequestProcessor(BeforeFilterFunctions.setRequestHostHeader(host)); - } - - @Shortcut - static HandlerFilterFunction setResponseHeader(String name, String value) { - return ofResponseProcessor(AfterFilterFunctions.setResponseHeader(name, value)); - } - - static HandlerFilterFunction stripPrefix() { - return stripPrefix(1); - } - - @Shortcut - static HandlerFilterFunction stripPrefix(int parts) { - return ofRequestProcessor(BeforeFilterFunctions.stripPrefix(parts)); - } - - static HandlerFilterFunction setStatus(int statusCode) { - return setStatus(new HttpStatusHolder(null, statusCode)); - } - - static HandlerFilterFunction setStatus(HttpStatusCode statusCode) { - return setStatus(new HttpStatusHolder(statusCode, null)); - } - - @Shortcut - static HandlerFilterFunction setStatus(HttpStatusHolder statusCode) { - return ofResponseProcessor(AfterFilterFunctions.setStatus(statusCode)); - } - - class FilterSupplier implements org.springframework.cloud.gateway.server.mvc.filter.FilterSupplier { - - @Override - public Collection get() { - return Arrays.asList(FilterFunctions.class.getMethods()); - } - - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/FilterSupplier.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/FilterSupplier.java deleted file mode 100644 index 7f144d2c..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/FilterSupplier.java +++ /dev/null @@ -1,25 +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.util.Collection; -import java.util.function.Supplier; - -public interface FilterSupplier extends Supplier> { - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/ForwardedRequestHeadersFilter.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/ForwardedRequestHeadersFilter.java deleted file mode 100644 index 7924c5ef..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/ForwardedRequestHeadersFilter.java +++ /dev/null @@ -1,194 +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.net.Inet6Address; -import java.net.InetAddress; -import java.net.URI; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.springframework.core.Ordered; -import org.springframework.http.HttpHeaders; -import org.springframework.util.CollectionUtils; -import org.springframework.util.LinkedCaseInsensitiveMap; -import org.springframework.util.ObjectUtils; -import org.springframework.util.StringUtils; -import org.springframework.web.servlet.function.ServerRequest; - -public class ForwardedRequestHeadersFilter implements HttpHeadersFilter.RequestHttpHeadersFilter, Ordered { - - /** - * Forwarded header. - */ - public static final String FORWARDED_HEADER = "Forwarded"; - - /* for testing */ - static List parse(List values) { - ArrayList forwardeds = new ArrayList<>(); - if (CollectionUtils.isEmpty(values)) { - return forwardeds; - } - for (String value : values) { - Forwarded forwarded = parse(value); - forwardeds.add(forwarded); - } - return forwardeds; - } - - /* for testing */ - static Forwarded parse(String value) { - String[] pairs = StringUtils.tokenizeToStringArray(value, ";"); - - LinkedCaseInsensitiveMap result = splitIntoCaseInsensitiveMap(pairs); - if (result == null) { - return null; - } - - Forwarded forwarded = new Forwarded(result); - - return forwarded; - } - - /* for testing */ static LinkedCaseInsensitiveMap splitIntoCaseInsensitiveMap(String[] pairs) { - if (ObjectUtils.isEmpty(pairs)) { - return null; - } - - LinkedCaseInsensitiveMap result = new LinkedCaseInsensitiveMap<>(); - for (String element : pairs) { - String[] splittedElement = StringUtils.split(element, "="); - if (splittedElement == null) { - continue; - } - result.put(splittedElement[0].trim(), splittedElement[1].trim()); - } - return result; - } - - @Override - public int getOrder() { - return 0; - } - - @Override - public HttpHeaders apply(HttpHeaders input, ServerRequest request) { - HttpHeaders original = input; - HttpHeaders updated = new HttpHeaders(); - - // copy all headers except Forwarded - for (Map.Entry> entry : original.entrySet()) { - if (!entry.getKey().equalsIgnoreCase(FORWARDED_HEADER)) { - updated.addAll(entry.getKey(), entry.getValue()); - } - } - - List forwardeds = parse(original.get(FORWARDED_HEADER)); - - for (Forwarded f : forwardeds) { - updated.add(FORWARDED_HEADER, f.toHeaderValue()); - } - - // TODO: add new forwarded - URI uri = request.uri(); - String host = original.getFirst(HttpHeaders.HOST); - Forwarded forwarded = new Forwarded().put("host", host).put("proto", uri.getScheme()); - - request.remoteAddress().ifPresent(remoteAddress -> { - // If remoteAddress is unresolved, calling getHostAddress() would cause a - // NullPointerException. - String forValue; - if (remoteAddress.isUnresolved()) { - forValue = remoteAddress.getHostName(); - } - else { - InetAddress address = remoteAddress.getAddress(); - forValue = remoteAddress.getAddress().getHostAddress(); - if (address instanceof Inet6Address) { - forValue = "[" + forValue + "]"; - } - } - int port = remoteAddress.getPort(); - if (port >= 0) { - forValue = forValue + ":" + port; - } - forwarded.put("for", forValue); - }); - // TODO: support by? - - updated.add(FORWARDED_HEADER, forwarded.toHeaderValue()); - - return updated; - } - - /* for testing */ static class Forwarded { - - private static final char EQUALS = '='; - - private static final char SEMICOLON = ';'; - - private final Map values; - - Forwarded() { - this.values = new HashMap<>(); - } - - Forwarded(Map values) { - this.values = values; - } - - public Forwarded put(String key, String value) { - this.values.put(key, quoteIfNeeded(value)); - return this; - } - - private String quoteIfNeeded(String s) { - if (s != null && s.contains(":")) { // TODO: broaded quote - return "\"" + s + "\""; - } - return s; - } - - public String get(String key) { - return this.values.get(key); - } - - /* for testing */ Map getValues() { - return this.values; - } - - @Override - public String toString() { - return "Forwarded{" + "values=" + this.values + '}'; - } - - public String toHeaderValue() { - StringBuilder builder = new StringBuilder(); - for (Map.Entry entry : this.values.entrySet()) { - if (builder.length() > 0) { - builder.append(SEMICOLON); - } - builder.append(entry.getKey()).append(EQUALS).append(entry.getValue()); - } - return builder.toString(); - } - - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/HttpHeadersFilter.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/HttpHeadersFilter.java deleted file mode 100644 index a66443af..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/HttpHeadersFilter.java +++ /dev/null @@ -1,35 +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.util.function.BiFunction; - -import org.springframework.http.HttpHeaders; -import org.springframework.web.servlet.function.ServerRequest; -import org.springframework.web.servlet.function.ServerResponse; - -public interface HttpHeadersFilter extends BiFunction { - - interface RequestHttpHeadersFilter extends HttpHeadersFilter { - - } - - interface ResponseHttpHeadersFilter extends HttpHeadersFilter { - - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/LoadBalancerFilterFunctions.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/LoadBalancerFilterFunctions.java deleted file mode 100644 index 477a33c7..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/LoadBalancerFilterFunctions.java +++ /dev/null @@ -1,198 +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.net.URI; -import java.util.Collections; -import java.util.Map; -import java.util.Set; -import java.util.function.BiFunction; - -import jakarta.servlet.http.Cookie; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.cloud.client.ServiceInstance; -import org.springframework.cloud.client.loadbalancer.CompletionContext; -import org.springframework.cloud.client.loadbalancer.DefaultRequest; -import org.springframework.cloud.client.loadbalancer.DefaultResponse; -import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; -import org.springframework.cloud.client.loadbalancer.LoadBalancerLifecycle; -import org.springframework.cloud.client.loadbalancer.LoadBalancerLifecycleValidator; -import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; -import org.springframework.cloud.client.loadbalancer.LoadBalancerUriTools; -import org.springframework.cloud.client.loadbalancer.RequestData; -import org.springframework.cloud.client.loadbalancer.RequestDataContext; -import org.springframework.cloud.client.loadbalancer.ResponseData; -import org.springframework.cloud.gateway.server.mvc.common.MvcUtils; -import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory; -import org.springframework.http.HttpHeaders; -import org.springframework.util.MultiValueMap; -import org.springframework.web.servlet.function.HandlerFilterFunction; -import org.springframework.web.servlet.function.ServerResponse; - -import static org.springframework.cloud.gateway.server.mvc.common.MvcUtils.getApplicationContext; - -public abstract class LoadBalancerFilterFunctions { - - private static final Log log = LogFactory.getLog(LoadBalancerFilterFunctions.class); - - private LoadBalancerFilterFunctions() { - } - - public static HandlerFilterFunction lb(String serviceId) { - return lb(serviceId, LoadBalancerUriTools::reconstructURI); - } - - public static HandlerFilterFunction lb(String serviceId, - BiFunction reconstructUriFunction) { - return (request, next) -> { - LoadBalancerClientFactory clientFactory = getApplicationContext(request) - .getBean(LoadBalancerClientFactory.class); - Set supportedLifecycleProcessors = LoadBalancerLifecycleValidator - .getSupportedLifecycleProcessors(clientFactory.getInstances(serviceId, LoadBalancerLifecycle.class), - RequestDataContext.class, ResponseData.class, ServiceInstance.class); - RequestData requestData = new RequestData(request.method(), request.uri(), - request.headers().asHttpHeaders(), buildCookies(request.cookies()), request.attributes()); - DefaultRequest lbRequest = new DefaultRequest<>( - new RequestDataContext(requestData, getHint(clientFactory, serviceId))); - - LoadBalancerClient loadBalancerClient = clientFactory.getInstance(serviceId, LoadBalancerClient.class); - if (loadBalancerClient == null) { - // TODO: type, return 500 error? - throw new RuntimeException("No loadbalancer available for " + serviceId); - } - supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStart(lbRequest)); - ServiceInstance retrievedInstance = loadBalancerClient.choose(serviceId, lbRequest); - if (retrievedInstance == null) { - supportedLifecycleProcessors.forEach(lifecycle -> lifecycle - .onComplete(new CompletionContext<>(CompletionContext.Status.DISCARD, lbRequest))); - // TODO: type, return 500 error? - throw new RuntimeException("Unable to find instance for " + serviceId); - // throw NotFoundException.create(properties.isUse404(), "Unable to find - // instance for " + serviceId); - } - - URI uri = request.uri(); - - // if the `lb:` mechanism was used, use `` as the default, - // if the loadbalancer doesn't provide one. - String scheme = retrievedInstance.isSecure() ? "https" : "http"; - - DelegatingServiceInstance serviceInstance = new DelegatingServiceInstance(retrievedInstance, scheme); - - URI requestUrl = reconstructUriFunction.apply(serviceInstance, uri); - - if (log.isTraceEnabled()) { - log.trace("LoadBalancerClientFilter url chosen: " + requestUrl); - } - MvcUtils.setRequestUrl(request, requestUrl); - // exchange.getAttributes().put(GATEWAY_LOADBALANCER_RESPONSE_ATTR, response); - DefaultResponse defaultResponse = new DefaultResponse(serviceInstance); - - supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStartRequest(lbRequest, defaultResponse)); - - try { - ServerResponse serverResponse = next.handle(request); - supportedLifecycleProcessors.forEach( - lifecycle -> lifecycle.onComplete(new CompletionContext<>(CompletionContext.Status.SUCCESS, - lbRequest, defaultResponse, serverResponse))); - return serverResponse; - } - catch (Exception e) { - supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onComplete( - new CompletionContext<>(CompletionContext.Status.FAILED, e, lbRequest, defaultResponse))); - - throw new RuntimeException(e); - } - }; - } - - private static String getHint(LoadBalancerClientFactory clientFactory, String serviceId) { - LoadBalancerProperties loadBalancerProperties = clientFactory.getProperties(serviceId); - Map hints = loadBalancerProperties.getHint(); - String defaultHint = hints.getOrDefault("default", "default"); - String hintPropertyValue = hints.get(serviceId); - return hintPropertyValue != null ? hintPropertyValue : defaultHint; - } - - private static MultiValueMap buildCookies(MultiValueMap cookies) { - HttpHeaders newCookies = new HttpHeaders(); - if (cookies != null) { - cookies.forEach((key, value) -> value - .forEach(cookie -> newCookies.put(cookie.getName(), Collections.singletonList(cookie.getValue())))); - } - return newCookies; - } - - static class DelegatingServiceInstance implements ServiceInstance { - - final ServiceInstance delegate; - - private String overrideScheme; - - DelegatingServiceInstance(ServiceInstance delegate, String overrideScheme) { - this.delegate = delegate; - this.overrideScheme = overrideScheme; - } - - @Override - public String getServiceId() { - return delegate.getServiceId(); - } - - @Override - public String getHost() { - return delegate.getHost(); - } - - @Override - public int getPort() { - return delegate.getPort(); - } - - @Override - public boolean isSecure() { - // TODO: move to map - if ("https".equals(this.overrideScheme) || "wss".equals(this.overrideScheme)) { - return true; - } - return delegate.isSecure(); - } - - @Override - public URI getUri() { - return delegate.getUri(); - } - - @Override - public Map getMetadata() { - return delegate.getMetadata(); - } - - @Override - public String getScheme() { - String scheme = delegate.getScheme(); - if (scheme != null) { - return scheme; - } - return this.overrideScheme; - } - - } - -} 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 6a3d3365..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/LoadBalancerHandlerSupplier.java +++ /dev/null @@ -1,42 +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.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(URI uri) { - // TODO: how to do something other than http - return new HandlerDiscoverer.Result(HandlerFunctions.http(), - Collections.singletonList(LoadBalancerFilterFunctions.lb(uri.getHost()))); - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/RemoveContentLengthRequestHeadersFilter.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/RemoveContentLengthRequestHeadersFilter.java deleted file mode 100644 index 1c6818bd..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/RemoveContentLengthRequestHeadersFilter.java +++ /dev/null @@ -1,45 +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 org.springframework.core.Ordered; -import org.springframework.http.HttpHeaders; -import org.springframework.web.servlet.function.ServerRequest; - -// jdk http clients seem to not like having this copied from the initial request. -// urlconnection fails with form url encoded -public class RemoveContentLengthRequestHeadersFilter implements HttpHeadersFilter.RequestHttpHeadersFilter, Ordered { - - @Override - public int getOrder() { - return 1000; - } - - @Override - public HttpHeaders apply(HttpHeaders input, ServerRequest request) { - if (input.containsKey(HttpHeaders.CONTENT_LENGTH)) { - - HttpHeaders filtered = new HttpHeaders(); - // avoids read only if input is read only - filtered.addAll(input); - filtered.remove(HttpHeaders.CONTENT_LENGTH); - return filtered; - } - return input; - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/RemoveHopByHopRequestHeadersFilter.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/RemoveHopByHopRequestHeadersFilter.java deleted file mode 100644 index 44f21f90..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/RemoveHopByHopRequestHeadersFilter.java +++ /dev/null @@ -1,71 +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.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.springframework.cloud.gateway.server.mvc.filter.HttpHeadersFilter.RequestHttpHeadersFilter; -import org.springframework.core.Ordered; -import org.springframework.http.HttpHeaders; -import org.springframework.web.servlet.function.ServerRequest; - -public class RemoveHopByHopRequestHeadersFilter implements RequestHttpHeadersFilter, Ordered { - - // TODO: configurable - /** - * Headers to remove as the result of applying the filter. - */ - public static final Set HEADERS_REMOVED_ON_REQUEST = new HashSet<>( - Arrays.asList("connection", "keep-alive", "transfer-encoding", "te", "trailer", "proxy-authorization", - "proxy-authenticate", "x-application-context", "upgrade" - // these two are not listed in - // https://tools.ietf.org/html/draft-ietf-httpbis-p1-messaging-14#section-7.1.3 - // "proxy-connection", - // "content-length", - )); - - private int order = Ordered.LOWEST_PRECEDENCE - 1; - - private Set headers = HEADERS_REMOVED_ON_REQUEST; - - @Override - public HttpHeaders apply(HttpHeaders input, ServerRequest serverRequest) { - return filter(input, headers); - } - - static HttpHeaders filter(HttpHeaders input, Set headersToRemove) { - HttpHeaders filtered = new HttpHeaders(); - - for (Map.Entry> entry : input.entrySet()) { - if (!headersToRemove.contains(entry.getKey().toLowerCase())) { - filtered.addAll(entry.getKey(), entry.getValue()); - } - } - - return filtered; - } - - @Override - public int getOrder() { - return order; - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/RemoveHopByHopResponseHeadersFilter.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/RemoveHopByHopResponseHeadersFilter.java deleted file mode 100644 index 4ab68a6e..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/RemoveHopByHopResponseHeadersFilter.java +++ /dev/null @@ -1,44 +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.util.Set; - -import org.springframework.cloud.gateway.server.mvc.filter.HttpHeadersFilter.ResponseHttpHeadersFilter; -import org.springframework.core.Ordered; -import org.springframework.http.HttpHeaders; -import org.springframework.web.servlet.function.ServerResponse; - -public class RemoveHopByHopResponseHeadersFilter implements ResponseHttpHeadersFilter, Ordered { - - // TODO: configurable - - private int order = Ordered.LOWEST_PRECEDENCE - 1; - - private Set headers = RemoveHopByHopRequestHeadersFilter.HEADERS_REMOVED_ON_REQUEST; - - @Override - public HttpHeaders apply(HttpHeaders input, ServerResponse serverResponse) { - return RemoveHopByHopRequestHeadersFilter.filter(input, headers); - } - - @Override - public int getOrder() { - return order; - } - -} 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 deleted file mode 100644 index 10ccd37b..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/RetryFilterFunctions.java +++ /dev/null @@ -1,149 +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.io.IOException; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.TimeoutException; -import java.util.function.Consumer; - -import org.springframework.http.HttpStatus; -import org.springframework.http.HttpStatusCode; -import org.springframework.retry.RetryContext; -import org.springframework.retry.RetryPolicy; -import org.springframework.retry.policy.CompositeRetryPolicy; -import org.springframework.retry.policy.NeverRetryPolicy; -import org.springframework.retry.policy.SimpleRetryPolicy; -import org.springframework.retry.support.RetryTemplate; -import org.springframework.retry.support.RetryTemplateBuilder; -import org.springframework.web.client.HttpServerErrorException; -import org.springframework.web.servlet.function.HandlerFilterFunction; -import org.springframework.web.servlet.function.ServerResponse; - -public abstract class RetryFilterFunctions { - - private RetryFilterFunctions() { - } - - public static HandlerFilterFunction retry(int retries) { - return retry(config -> config.setRetries(retries)); - } - - public static HandlerFilterFunction retry(Consumer configConsumer) { - RetryConfig config = new RetryConfig(); - configConsumer.accept(config); - RetryTemplateBuilder retryTemplateBuilder = RetryTemplate.builder(); - CompositeRetryPolicy compositeRetryPolicy = new CompositeRetryPolicy(); - Map, Boolean> retryableExceptions = new HashMap<>(); - config.getExceptions().forEach(exception -> retryableExceptions.put(exception, true)); - SimpleRetryPolicy simpleRetryPolicy = new SimpleRetryPolicy(config.getRetries(), retryableExceptions); - compositeRetryPolicy.setPolicies( - Arrays.asList(simpleRetryPolicy, new HttpStatusRetryPolicy(config)).toArray(new RetryPolicy[0])); - RetryTemplate retryTemplate = retryTemplateBuilder.customPolicy(compositeRetryPolicy).build(); - return (request, next) -> retryTemplate.execute(context -> { - ServerResponse serverResponse = next.handle(request); - - if (isRetryableStatusCode(serverResponse.statusCode(), config)) { - throw new HttpServerErrorException(serverResponse.statusCode()); - } - return serverResponse; - }); - } - - private static boolean isRetryableStatusCode(HttpStatusCode httpStatus, RetryConfig config) { - Optional seriesMatches = config.getSeries().stream() - .filter(series -> HttpStatus.Series.resolve(httpStatus.value()) == series).findFirst(); - return seriesMatches.isPresent(); - } - - public static class HttpStatusRetryPolicy extends NeverRetryPolicy { - - private final RetryConfig config; - - public HttpStatusRetryPolicy(RetryConfig config) { - this.config = config; - } - - @Override - public boolean canRetry(RetryContext context) { - // TODO: custom exception - if (context.getLastThrowable() instanceof HttpServerErrorException e) { - return isRetryableStatusCode(e.getStatusCode(), config); - } - return super.canRetry(context); - } - - } - - public static class RetryConfig { - - private int retries = 3; - - private Set series = new HashSet<>(List.of(HttpStatus.Series.SERVER_ERROR)); - - private Set> exceptions = new HashSet<>( - List.of(IOException.class, TimeoutException.class, HttpServerErrorException.class)); - - // TODO: individual statuses - // TODO: support more Spring Retry policies - - public int getRetries() { - return retries; - } - - public RetryConfig setRetries(int retries) { - this.retries = retries; - return this; - } - - public Set getSeries() { - return series; - } - - public RetryConfig setSeries(Set series) { - this.series = series; - return this; - } - - public RetryConfig addSeries(HttpStatus.Series... series) { - this.series.addAll(Arrays.asList(series)); - return this; - } - - public Set> getExceptions() { - return exceptions; - } - - public RetryConfig setExceptions(Set> exceptions) { - this.exceptions = exceptions; - return this; - } - - public RetryConfig addExceptions(Class... exceptions) { - this.exceptions.addAll(Arrays.asList(exceptions)); - return this; - } - - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/RewriteLocationResponseHeaderFilterFunctions.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/RewriteLocationResponseHeaderFilterFunctions.java deleted file mode 100644 index b9249346..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/RewriteLocationResponseHeaderFilterFunctions.java +++ /dev/null @@ -1,226 +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.util.function.BiFunction; -import java.util.function.Consumer; -import java.util.regex.Pattern; - -import org.springframework.http.HttpHeaders; -import org.springframework.web.servlet.function.ServerRequest; -import org.springframework.web.servlet.function.ServerResponse; - -/* -This filter intent is to modify the value of Location response header, ridding it of backend specific details. -Typical scenario: POST response from a legacy backend contains a Location header with backend's hostname. - You want to replace the backend's hostname:port with your documented gateway's hostname:port. -Additionally, sometimes backend returns a 'versioned' Location, and you may want to strip the version portion. - -Note that the protocol portion of the URL will not be replaced by this filter. If you need that too, -either follow this filter with a generic rewrite response header filter, or subclass this filter. - -Configuration parameters: -- stripVersion - NEVER_STRIP - Version will not be stripped, even if the original request path contains no version - AS_IN_REQUEST - Default. Version will be stripped only if the original request path contains no version - ALWAYS_STRIP - Version will be stripped, even if the original request path contains version -- locationHeaderName - String representing a custom location header name. Default - Location -- hostValue - A String. If provided, will be used to replace the host:port portion of the response Location header. - Default - The value of request Host header is used to replace. -- protocols - A valid regex String, against which the protocol name will be matched. If not matched, the filter will do nothing. - Default - https?|ftps? (which is the same as http|https|ftp|ftps) - -Example 1 - default-filters: - - RewriteLocationResponseHeader - -Host request header: api.example.com:443 -POST request path: /some/object/name -Location response header: https://object-service.prod.example.net/v2/some/object/id -Modified Location response header: https://api.example.com:443/some/object/id - -Example 2 - default-filters: - - name: RewriteLocationResponseHeader - args: - stripVersion: ALWAYS_STRIP - locationHeaderName: Link - hostValue: example-api.com - -Host request header (irrelevant): api.example.com:443 -POST request path: /v1/some/object/name -Link response header: https://object-service.prod.example.net/v1/some/object/id -Modified Link response header: https://example-api.com/some/object/id - -Example 3 - default-filters: - - name: RewriteLocationResponseHeader - args: - stripVersion: NEVER_STRIP - protocols: https|ftps # only replace host:port for https or ftps, but not http or ftp - -Host request header: api.example.com:443 -1. POST request path: /some/object/name -Location response header: https://object-service.prod.example.net/v1/some/object/id -Modified Location response header: https://api.example.com/v1/some/object/id -2. POST request path: /some/other/object/name -Location response header: http://object-service.prod.example.net/v1/some/object/id -Modified (not) Location response header: http://object-service.prod.example.net/v1/some/object/id - */ - -/** - * @author Vitaliy Pavlyuk - */ -public abstract class RewriteLocationResponseHeaderFilterFunctions { - - private static final Pattern VERSIONED_PATH = Pattern.compile("^/v\\d+/.*"); - - private static final String DEFAULT_PROTOCOLS = "https?|ftps?"; - - private static final Pattern DEFAULT_HOST_PORT = compileHostPortPattern(DEFAULT_PROTOCOLS); - - private static final Pattern DEFAULT_HOST_PORT_VERSION = compileHostPortVersionPattern(DEFAULT_PROTOCOLS); - - private RewriteLocationResponseHeaderFilterFunctions() { - } - - private static Pattern compileHostPortPattern(String protocols) { - return Pattern.compile("(?<=^(?:" + protocols + ")://)[^:/]+(?::\\d+)?(?=/)"); - } - - private static Pattern compileHostPortVersionPattern(String protocols) { - return Pattern.compile("(?<=^(?:" + protocols + ")://)[^:/]+(?::\\d+)?(?:/v\\d+)?(?=/)"); - } - - public static BiFunction rewriteLocationResponseHeader( - Consumer configConsumer) { - RewriteLocationResponseHeaderConfig config = new RewriteLocationResponseHeaderConfig(); - configConsumer.accept(config); - return (request, response) -> { - String location = response.headers().getFirst(config.getLocationHeaderName()); - String host = config.getHostValue() != null ? config.getHostValue() - : request.headers().firstHeader(HttpHeaders.HOST); - String path = request.uri().getPath(); - if (location != null && host != null) { - String fixedLocation = fixedLocation(location, host, path, config.getStripVersion(), - config.getHostPortPattern(), config.getHostPortVersionPattern()); - response.headers().set(config.getLocationHeaderName(), fixedLocation); - } - return response; - }; - } - - private static String fixedLocation(String location, String host, String path, StripVersion stripVersion, - Pattern hostPortPattern, Pattern hostPortVersionPattern) { - boolean doStrip = StripVersion.ALWAYS_STRIP.equals(stripVersion) - || (StripVersion.AS_IN_REQUEST.equals(stripVersion) && !VERSIONED_PATH.matcher(path).matches()); - Pattern pattern = doStrip ? hostPortVersionPattern : hostPortPattern; - return pattern.matcher(location).replaceFirst(host); - } - - public enum StripVersion { - - /** - * Version will not be stripped, even if the original request path contains no - * version. - */ - NEVER_STRIP, - - /** - * Version will be stripped only if the original request path contains no version. - * This is the Default. - */ - AS_IN_REQUEST, - - /** - * Version will be stripped, even if the original request path contains version. - */ - ALWAYS_STRIP - - } - - public static class RewriteLocationResponseHeaderConfig { - - private StripVersion stripVersion = StripVersion.AS_IN_REQUEST; - - private String locationHeaderName = HttpHeaders.LOCATION; - - private String hostValue; - - private String protocols = DEFAULT_PROTOCOLS; - - private Pattern hostPortPattern = DEFAULT_HOST_PORT; - - private Pattern hostPortVersionPattern = DEFAULT_HOST_PORT_VERSION; - - public StripVersion getStripVersion() { - return stripVersion; - } - - public RewriteLocationResponseHeaderConfig setStripVersion(String stripVersion) { - this.stripVersion = StripVersion.valueOf(stripVersion); - return this; - } - - public RewriteLocationResponseHeaderConfig setStripVersion(StripVersion stripVersion) { - this.stripVersion = stripVersion; - return this; - } - - public String getLocationHeaderName() { - return locationHeaderName; - } - - public RewriteLocationResponseHeaderConfig setLocationHeaderName(String locationHeaderName) { - this.locationHeaderName = locationHeaderName; - return this; - } - - public String getHostValue() { - return hostValue; - } - - public RewriteLocationResponseHeaderConfig setHostValue(String hostValue) { - this.hostValue = hostValue; - return this; - } - - public String getProtocols() { - return protocols; - } - - public RewriteLocationResponseHeaderConfig setProtocols(String protocols) { - this.protocols = protocols; - this.hostPortPattern = compileHostPortPattern(protocols); - this.hostPortVersionPattern = compileHostPortVersionPattern(protocols); - return this; - } - - public Pattern getHostPortPattern() { - return hostPortPattern; - } - - public Pattern getHostPortVersionPattern() { - return hostPortVersionPattern; - } - - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/TokenRelayFilterFunctions.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/TokenRelayFilterFunctions.java deleted file mode 100644 index aa6e8769..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/TokenRelayFilterFunctions.java +++ /dev/null @@ -1,56 +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.security.Principal; - -import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest; -import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; -import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager; -import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.web.servlet.function.HandlerFilterFunction; -import org.springframework.web.servlet.function.ServerRequest; -import org.springframework.web.servlet.function.ServerResponse; - -import static org.springframework.cloud.gateway.server.mvc.common.MvcUtils.getApplicationContext; - -public abstract class TokenRelayFilterFunctions { - - private TokenRelayFilterFunctions() { - } - - public static HandlerFilterFunction tokenRelay() { - return (request, next) -> { - Principal principle = request.servletRequest().getUserPrincipal(); - if (principle instanceof OAuth2AuthenticationToken token) { - String clientRegistrationId = token.getAuthorizedClientRegistrationId(); - OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest - .withClientRegistrationId(clientRegistrationId).principal(token).build(); - OAuth2AuthorizedClientManager clientManager = getApplicationContext(request) - .getBean(OAuth2AuthorizedClientManager.class); - OAuth2AuthorizedClient authorizedClient = clientManager.authorize(authorizeRequest); - OAuth2AccessToken accessToken = authorizedClient.getAccessToken(); - ServerRequest modified = ServerRequest.from(request) - .headers(httpHeaders -> httpHeaders.setBearerAuth(accessToken.getTokenValue())).build(); - return next.handle(modified); - } - return next.handle(request); - }; - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/TransferEncodingNormalizationRequestHeadersFilter.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/TransferEncodingNormalizationRequestHeadersFilter.java deleted file mode 100644 index dd55b72d..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/TransferEncodingNormalizationRequestHeadersFilter.java +++ /dev/null @@ -1,46 +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 org.springframework.core.Ordered; -import org.springframework.http.HttpHeaders; -import org.springframework.web.servlet.function.ServerRequest; - -public class TransferEncodingNormalizationRequestHeadersFilter - implements HttpHeadersFilter.RequestHttpHeadersFilter, Ordered { - - @Override - public int getOrder() { - return 1000; - } - - @Override - public HttpHeaders apply(HttpHeaders input, ServerRequest request) { - String transferEncoding = input.getFirst(HttpHeaders.TRANSFER_ENCODING); - if (transferEncoding != null && "chunked".equalsIgnoreCase(transferEncoding.trim()) - && input.containsKey(HttpHeaders.CONTENT_LENGTH)) { - - HttpHeaders filtered = new HttpHeaders(); - // avoids read only if input is read only - filtered.addAll(input); - filtered.remove(HttpHeaders.CONTENT_LENGTH); - return filtered; - } - return input; - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/WeightCalculatorFilter.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/WeightCalculatorFilter.java deleted file mode 100644 index 4acf4567..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/WeightCalculatorFilter.java +++ /dev/null @@ -1,263 +0,0 @@ -/* - * Copyright 2013-2020 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.io.IOException; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ThreadLocalRandom; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Supplier; - -import jakarta.servlet.Filter; -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.ServletRequest; -import jakarta.servlet.ServletResponse; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.cloud.gateway.server.mvc.common.ArgumentSupplier.ArgumentSuppliedEvent; -import org.springframework.cloud.gateway.server.mvc.common.AttributedArugmentSuppliedEvent; -import org.springframework.cloud.gateway.server.mvc.common.MvcUtils; -import org.springframework.cloud.gateway.server.mvc.common.WeightConfig; -import org.springframework.context.ApplicationEvent; -import org.springframework.context.event.SmartApplicationListener; -import org.springframework.core.Ordered; -import org.springframework.core.style.ToStringCreator; - -import static org.springframework.cloud.gateway.server.mvc.common.MvcUtils.WEIGHT_ATTR; - -/** - * @author Spencer Gibb - * @author Alexey Nakidkin - */ -@SuppressWarnings("unchecked") -public class WeightCalculatorFilter implements Filter, Ordered, SmartApplicationListener { - - /** - * Order of Weight Calculator Web filter. - */ - public static final int WEIGHT_CALC_FILTER_ORDER = 10001; - - private static final Log log = LogFactory.getLog(WeightCalculatorFilter.class); - - private Supplier randomSupplier = null; - - private int order = WEIGHT_CALC_FILTER_ORDER; - - private Map groupWeights = new ConcurrentHashMap<>(); - - public WeightCalculatorFilter() { - - } - - /* for testing */ - static Map getWeights(ServletRequest request) { - Map weights = (Map) request.getAttribute(WEIGHT_ATTR); - - if (weights == null) { - weights = new ConcurrentHashMap<>(); - request.setAttribute(WEIGHT_ATTR, weights); - } - return weights; - } - - @Override - public int getOrder() { - return order; - } - - public void setOrder(int order) { - this.order = order; - } - - public void setRandomSupplier(Supplier randomSupplier) { - this.randomSupplier = randomSupplier; - } - - @Override - public boolean supportsEventType(Class eventType) { - return ArgumentSuppliedEvent.class.isAssignableFrom(eventType); - } - - @Override - public boolean supportsSourceType(Class sourceType) { - return true; - } - - @Override - public void onApplicationEvent(ApplicationEvent event) { - if (event instanceof ArgumentSuppliedEvent argumentSuppliedEvent) { - if (argumentSuppliedEvent.getType() == WeightConfig.class) { - WeightConfig weightConfig = (WeightConfig) argumentSuppliedEvent.getArgument(); - if (weightConfig.getRouteId() == null - && argumentSuppliedEvent instanceof AttributedArugmentSuppliedEvent attributed) { - String routeId = (String) attributed.getAttributes().get(MvcUtils.GATEWAY_ROUTE_ID_ATTR); - weightConfig.setRouteId(routeId); - } - - addWeightConfig(weightConfig); - } - } - } - - /* for testing */ void addWeightConfig(WeightConfig weightConfig) { - // Assert.hasText(weightConfig.getRouteId(), "routeId is required"); - - String group = weightConfig.getGroup(); - GroupWeightConfig config; - // only create new GroupWeightConfig rather than modify - // and put at end of calculations. This avoids concurency problems - // later during filter execution. - if (groupWeights.containsKey(group)) { - config = new GroupWeightConfig(groupWeights.get(group)); - } - else { - config = new GroupWeightConfig(group); - } - - config.weights.put(weightConfig.getRouteId(), weightConfig.getWeight()); - - // recalculate - - // normalize weights - int weightsSum = 0; - - for (Integer weight : config.weights.values()) { - weightsSum += weight; - } - - final AtomicInteger index = new AtomicInteger(0); - for (Map.Entry entry : config.weights.entrySet()) { - String routeId = entry.getKey(); - Integer weight = entry.getValue(); - Double nomalizedWeight = weight / (double) weightsSum; - config.normalizedWeights.put(routeId, nomalizedWeight); - - // recalculate rangeIndexes - config.rangeIndexes.put(index.getAndIncrement(), routeId); - } - - // TODO: calculate ranges - config.ranges.clear(); - - config.ranges.add(0.0); - - List values = new ArrayList<>(config.normalizedWeights.values()); - for (int i = 0; i < values.size(); i++) { - Double currentWeight = values.get(i); - Double previousRange = config.ranges.get(i); - Double range = previousRange + currentWeight; - config.ranges.add(range); - } - - if (log.isTraceEnabled()) { - log.trace("Recalculated group weight config " + config); - } - // only update after all calculations - groupWeights.put(group, config); - } - - /* for testing */ Map getGroupWeights() { - return groupWeights; - } - - @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) - throws IOException, ServletException { - Map weights = getWeights(request); - - for (String group : groupWeights.keySet()) { - GroupWeightConfig config = groupWeights.get(group); - - if (config == null) { - if (log.isDebugEnabled()) { - log.debug("No GroupWeightConfig found for group: " + group); - } - continue; // nothing we can do, but this is odd - } - - /* - * Usually, multiple threads accessing the same random object will have some - * performance problems, so we can use ThreadLocalRandom by default - */ - double r; - if (this.randomSupplier != null) { - r = randomSupplier.get(); - } - else { - r = ThreadLocalRandom.current().nextDouble(); - } - - List ranges = config.ranges; - - if (log.isTraceEnabled()) { - log.trace("Weight for group: " + group + ", ranges: " + ranges + ", r: " + r); - } - - for (int i = 0; i < ranges.size() - 1; i++) { - if (r >= ranges.get(i) && r < ranges.get(i + 1)) { - String routeId = config.rangeIndexes.get(i); - weights.put(group, routeId); - break; - } - } - } - - if (log.isTraceEnabled()) { - log.trace("Weights attr: " + weights); - } - - chain.doFilter(request, response); - } - - /* for testing */ static class GroupWeightConfig { - - String group; - - LinkedHashMap weights = new LinkedHashMap<>(); - - LinkedHashMap normalizedWeights = new LinkedHashMap<>(); - - LinkedHashMap rangeIndexes = new LinkedHashMap<>(); - - List ranges = new ArrayList<>(); - - GroupWeightConfig(String group) { - this.group = group; - } - - GroupWeightConfig(GroupWeightConfig other) { - this.group = other.group; - this.weights = new LinkedHashMap<>(other.weights); - this.normalizedWeights = new LinkedHashMap<>(other.normalizedWeights); - this.rangeIndexes = new LinkedHashMap<>(other.rangeIndexes); - } - - @Override - public String toString() { - return new ToStringCreator(this).append("group", group).append("weights", weights) - .append("normalizedWeights", normalizedWeights).append("rangeIndexes", rangeIndexes).toString(); - } - - } - -} 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 deleted file mode 100644 index c77807ca..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/XForwardedRequestHeadersFilter.java +++ /dev/null @@ -1,331 +0,0 @@ -/* - * Copyright 2013-2020 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.net.InetSocketAddress; -import java.net.URI; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; - -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.core.Ordered; -import org.springframework.http.HttpHeaders; -import org.springframework.util.ObjectUtils; -import org.springframework.util.StringUtils; -import org.springframework.web.servlet.function.ServerRequest; - -@ConfigurationProperties("spring.cloud.gateway.x-forwarded") -public class XForwardedRequestHeadersFilter implements HttpHeadersFilter.RequestHttpHeadersFilter, Ordered { - - /** Default http port. */ - public static final int HTTP_PORT = 80; - - /** Default https port. */ - public static final int HTTPS_PORT = 443; - - /** Http url scheme. */ - public static final String HTTP_SCHEME = "http"; - - /** Https url scheme. */ - public static final String HTTPS_SCHEME = "https"; - - /** X-Forwarded-For Header. */ - public static final String X_FORWARDED_FOR_HEADER = "X-Forwarded-For"; - - /** X-Forwarded-Host Header. */ - public static final String X_FORWARDED_HOST_HEADER = "X-Forwarded-Host"; - - /** X-Forwarded-Port Header. */ - public static final String X_FORWARDED_PORT_HEADER = "X-Forwarded-Port"; - - /** X-Forwarded-Proto Header. */ - public static final String X_FORWARDED_PROTO_HEADER = "X-Forwarded-Proto"; - - /** X-Forwarded-Prefix Header. */ - public static final String X_FORWARDED_PREFIX_HEADER = "X-Forwarded-Prefix"; - - /** The order of the XForwardedHeadersFilter. */ - private int order = 0; - - /** 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; - - @Override - public int getOrder() { - return this.order; - } - - public void setOrder(int order) { - this.order = order; - } - - public boolean isEnabled() { - return enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - - public boolean isForEnabled() { - return forEnabled; - } - - public void setForEnabled(boolean forEnabled) { - this.forEnabled = forEnabled; - } - - public boolean isHostEnabled() { - return hostEnabled; - } - - public void setHostEnabled(boolean hostEnabled) { - this.hostEnabled = hostEnabled; - } - - public boolean isPortEnabled() { - return portEnabled; - } - - public void setPortEnabled(boolean portEnabled) { - this.portEnabled = portEnabled; - } - - public boolean isProtoEnabled() { - return protoEnabled; - } - - public void setProtoEnabled(boolean protoEnabled) { - this.protoEnabled = protoEnabled; - } - - public boolean isPrefixEnabled() { - return prefixEnabled; - } - - public void setPrefixEnabled(boolean prefixEnabled) { - this.prefixEnabled = prefixEnabled; - } - - public boolean isForAppend() { - return forAppend; - } - - public void setForAppend(boolean forAppend) { - this.forAppend = forAppend; - } - - public boolean isHostAppend() { - return hostAppend; - } - - public void setHostAppend(boolean hostAppend) { - this.hostAppend = hostAppend; - } - - public boolean isPortAppend() { - return portAppend; - } - - public void setPortAppend(boolean portAppend) { - this.portAppend = portAppend; - } - - public boolean isProtoAppend() { - return protoAppend; - } - - public void setProtoAppend(boolean protoAppend) { - this.protoAppend = protoAppend; - } - - public boolean isPrefixAppend() { - return prefixAppend; - } - - public void setPrefixAppend(boolean prefixAppend) { - this.prefixAppend = prefixAppend; - } - - @Override - public HttpHeaders apply(HttpHeaders input, ServerRequest request) { - HttpHeaders original = input; - HttpHeaders updated = new HttpHeaders(); - - for (Map.Entry> entry : original.entrySet()) { - updated.addAll(entry.getKey(), entry.getValue()); - } - - InetSocketAddress remoteAddress = request.remoteAddress().orElse(null); - if (isForEnabled() && remoteAddress != null && remoteAddress.getAddress() != null) { - String remoteAddr = remoteAddress.getAddress().getHostAddress(); - write(updated, X_FORWARDED_FOR_HEADER, remoteAddr, isForAppend()); - } - - String proto = request.uri().getScheme(); - if (isProtoEnabled()) { - write(updated, X_FORWARDED_PROTO_HEADER, proto, isProtoAppend()); - } - - if (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 - // to routedservice:8090/get then /prefix is the prefix - // - see XForwardedHeadersFilterTests, so first get uris, then extract paths - // and remove one from another if it's the ending part. - - LinkedHashSet originalUris = null; // TODO: - // exchange.getAttribute(GATEWAY_ORIGINAL_REQUEST_URL_ATTR); - URI requestUri = null; // TODO: - // exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR); - - if (originalUris != null && requestUri != null) { - - originalUris.forEach(originalUri -> { - - if (originalUri != null && originalUri.getPath() != null) { - String prefix = originalUri.getPath(); - - // strip trailing slashes before checking if request path is end - // of original path - String originalUriPath = stripTrailingSlash(originalUri); - String requestUriPath = stripTrailingSlash(requestUri); - - updateRequest(updated, originalUri, originalUriPath, requestUriPath); - - } - }); - } - } - - if (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()); - } - - if (isHostEnabled()) { - String host = toHostHeader(request); - write(updated, X_FORWARDED_HOST_HEADER, host, isHostAppend()); - } - - return updated; - } - - private void updateRequest(HttpHeaders updated, URI originalUri, String originalUriPath, String requestUriPath) { - String prefix; - 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()); - } - } - } - - private static String substringBeforeLast(String str, String separator) { - if (ObjectUtils.isEmpty(str) || ObjectUtils.isEmpty(separator)) { - return str; - } - int pos = str.lastIndexOf(separator); - if (pos == -1) { - return str; - } - return str.substring(0, pos); - } - - private void write(HttpHeaders headers, String name, String value, boolean append) { - if (value == null) { - return; - } - if (append) { - headers.add(name, value); - // these headers should be treated as a single comma separated header - List values = headers.get(name); - String delimitedValue = StringUtils.collectionToCommaDelimitedString(values); - headers.set(name, delimitedValue); - } - else { - headers.set(name, value); - } - } - - private int getDefaultPort(String scheme) { - return HTTPS_SCHEME.equals(scheme) ? HTTPS_PORT : HTTP_PORT; - } - - private boolean hasHeader(ServerRequest request, String name) { - HttpHeaders headers = request.headers().asHttpHeaders(); - return headers.containsKey(name) && StringUtils.hasLength(headers.getFirst(name)); - } - - private String toHostHeader(ServerRequest request) { - int port = request.uri().getPort(); - String host = request.uri().getHost(); - String scheme = request.uri().getScheme(); - if (port < 0 || (port == HTTP_PORT && HTTP_SCHEME.equals(scheme)) - || (port == HTTPS_PORT && HTTPS_SCHEME.equals(scheme))) { - return host; - } - else { - return host + ":" + port; - } - } - - private String stripTrailingSlash(URI uri) { - if (uri.getPath().endsWith("/")) { - return uri.getPath().substring(0, uri.getPath().length() - 1); - } - else { - return uri.getPath(); - } - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/AbstractGatewayServerResponse.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/AbstractGatewayServerResponse.java deleted file mode 100644 index 2f4e5b4c..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/AbstractGatewayServerResponse.java +++ /dev/null @@ -1,142 +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.handler; - -import java.io.IOException; -import java.util.Collection; -import java.util.Set; - -import jakarta.servlet.ServletException; -import jakarta.servlet.http.Cookie; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatusCode; -import org.springframework.lang.Nullable; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; -import org.springframework.web.context.request.ServletWebRequest; -import org.springframework.web.servlet.ModelAndView; - -/** - * Abstract base class for {@link ServerResponse} implementations. - * - * @author Arjen Poutsma - * @since 5.3.2 - */ -abstract class AbstractGatewayServerResponse extends GatewayErrorHandlingServerResponse - implements GatewayServerResponse { - - private static final Set SAFE_METHODS = Set.of(HttpMethod.GET, HttpMethod.HEAD); - - private HttpStatusCode statusCode; - - private final HttpHeaders headers; - - private final MultiValueMap cookies; - - protected AbstractGatewayServerResponse(HttpStatusCode statusCode, HttpHeaders headers, - MultiValueMap cookies) { - - this.statusCode = statusCode; - this.headers = HttpHeaders.writableHttpHeaders(headers); - this.cookies = new LinkedMultiValueMap<>(cookies); - } - - @Override - public final HttpStatusCode statusCode() { - return this.statusCode; - } - - @Override - public void setStatusCode(HttpStatusCode statusCode) { - this.statusCode = statusCode; - } - - @Override - @Deprecated - public int rawStatusCode() { - return this.statusCode.value(); - } - - @Override - public final HttpHeaders headers() { - return this.headers; - } - - @Override - public MultiValueMap cookies() { - return this.cookies; - } - - @Override - public ModelAndView writeTo(HttpServletRequest request, HttpServletResponse response, Context context) - throws ServletException, IOException { - - try { - writeStatusAndHeaders(response); - - long lastModified = headers().getLastModified(); - ServletWebRequest servletWebRequest = new ServletWebRequest(request, response); - HttpMethod httpMethod = HttpMethod.valueOf(request.getMethod()); - if (SAFE_METHODS.contains(httpMethod) - && servletWebRequest.checkNotModified(headers().getETag(), lastModified)) { - return null; - } - else { - return writeToInternal(request, response, context); - } - } - catch (Throwable throwable) { - return handleError(throwable, request, response, context); - } - } - - private void writeStatusAndHeaders(HttpServletResponse response) { - response.setStatus(this.statusCode.value()); - writeHeaders(response); - writeCookies(response); - } - - private void writeHeaders(HttpServletResponse servletResponse) { - this.headers.forEach((headerName, headerValues) -> { - for (String headerValue : headerValues) { - servletResponse.addHeader(headerName, headerValue); - } - }); - // HttpServletResponse exposes some headers as properties: we should include those - // if not already present - if (servletResponse.getContentType() == null && this.headers.getContentType() != null) { - servletResponse.setContentType(this.headers.getContentType().toString()); - } - if (servletResponse.getCharacterEncoding() == null && this.headers.getContentType() != null - && this.headers.getContentType().getCharset() != null) { - servletResponse.setCharacterEncoding(this.headers.getContentType().getCharset().name()); - } - } - - private void writeCookies(HttpServletResponse servletResponse) { - this.cookies.values().stream().flatMap(Collection::stream).forEach(servletResponse::addCookie); - } - - @Nullable - protected abstract ModelAndView writeToInternal(HttpServletRequest request, HttpServletResponse response, - Context context) throws Exception; - -} 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 deleted file mode 100644 index fd8b85fa..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/ClientHttpRequestFactoryProxyExchange.java +++ /dev/null @@ -1,62 +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.handler; - -import java.io.IOException; -import java.io.UncheckedIOException; - -import org.springframework.http.client.ClientHttpRequest; -import org.springframework.http.client.ClientHttpRequestFactory; -import org.springframework.http.client.ClientHttpResponse; -import org.springframework.util.StreamUtils; -import org.springframework.web.servlet.function.ServerResponse; - -public class ClientHttpRequestFactoryProxyExchange implements ProxyExchange { - - private final ClientHttpRequestFactory requestFactory; - - public ClientHttpRequestFactoryProxyExchange(ClientHttpRequestFactory requestFactory) { - this.requestFactory = requestFactory; - } - - @Override - public ServerResponse exchange(Request request) { - try { - ClientHttpRequest clientHttpRequest = requestFactory.createRequest(request.getUri(), request.getMethod()); - clientHttpRequest.getHeaders().putAll(request.getHeaders()); - // copy body from request to clientHttpRequest - StreamUtils.copy(request.getServerRequest().servletRequest().getInputStream(), clientHttpRequest.getBody()); - ClientHttpResponse clientHttpResponse = clientHttpRequest.execute(); - ServerResponse serverResponse = GatewayServerResponse.status(clientHttpResponse.getStatusCode()) - .build((req, httpServletResponse) -> { - try (clientHttpResponse) { - StreamUtils.copy(clientHttpResponse.getBody(), httpServletResponse.getOutputStream()); - } - return null; - }); - ClientHttpResponseAdapter proxyExchangeResponse = new ClientHttpResponseAdapter(clientHttpResponse); - request.getResponseConsumers() - .forEach(responseConsumer -> responseConsumer.accept(proxyExchangeResponse, serverResponse)); - return serverResponse; - } - catch (IOException e) { - // TODO: log error? - throw new UncheckedIOException(e); - } - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/ClientHttpResponseAdapter.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/ClientHttpResponseAdapter.java deleted file mode 100644 index 8d64b864..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/ClientHttpResponseAdapter.java +++ /dev/null @@ -1,48 +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.handler; - -import java.io.IOException; - -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatusCode; -import org.springframework.http.client.ClientHttpResponse; - -class ClientHttpResponseAdapter implements ProxyExchange.Response { - - private final ClientHttpResponse response; - - ClientHttpResponseAdapter(ClientHttpResponse response) { - this.response = response; - } - - @Override - public HttpStatusCode getStatusCode() { - try { - return response.getStatusCode(); - } - catch (IOException e) { - throw new RuntimeException(e); - } - } - - @Override - public HttpHeaders getHeaders() { - return response.getHeaders(); - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/GatewayAsyncServerResponse.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/GatewayAsyncServerResponse.java deleted file mode 100644 index 5e388040..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/GatewayAsyncServerResponse.java +++ /dev/null @@ -1,179 +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.handler; - -import java.io.IOException; -import java.time.Duration; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.function.Function; - -import jakarta.servlet.ServletException; -import jakarta.servlet.http.Cookie; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatusCode; -import org.springframework.lang.Nullable; -import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; -import org.springframework.util.MultiValueMap; -import org.springframework.web.context.request.async.AsyncWebRequest; -import org.springframework.web.context.request.async.DeferredResult; -import org.springframework.web.context.request.async.WebAsyncManager; -import org.springframework.web.context.request.async.WebAsyncUtils; -import org.springframework.web.servlet.ModelAndView; -import org.springframework.web.servlet.function.AsyncServerResponse; -import org.springframework.web.servlet.function.ServerResponse; - -/** - * Default {@link AsyncServerResponse} implementation. - * - * @author Arjen Poutsma - * @since 5.3.2 - */ -final class GatewayAsyncServerResponse extends GatewayErrorHandlingServerResponse implements AsyncServerResponse { - - static final boolean reactiveStreamsPresent = ClassUtils.isPresent("org.reactivestreams.Publisher", - GatewayAsyncServerResponse.class.getClassLoader()); - - private final CompletableFuture futureResponse; - - @Nullable - private final Duration timeout; - - private GatewayAsyncServerResponse(CompletableFuture futureResponse, @Nullable Duration timeout) { - this.futureResponse = futureResponse; - this.timeout = timeout; - } - - @Override - public ServerResponse block() { - try { - if (this.timeout != null) { - return this.futureResponse.get(this.timeout.toMillis(), TimeUnit.MILLISECONDS); - } - else { - return this.futureResponse.get(); - } - } - catch (InterruptedException | ExecutionException | TimeoutException ex) { - throw new IllegalStateException("Failed to get future response", ex); - } - } - - @Override - public HttpStatusCode statusCode() { - return delegate(ServerResponse::statusCode); - } - - @Override - @Deprecated - public int rawStatusCode() { - return delegate(ServerResponse::rawStatusCode); - } - - @Override - public HttpHeaders headers() { - return delegate(ServerResponse::headers); - } - - @Override - public MultiValueMap cookies() { - return delegate(ServerResponse::cookies); - } - - private R delegate(Function function) { - ServerResponse response = this.futureResponse.getNow(null); - if (response != null) { - return function.apply(response); - } - else { - throw new IllegalStateException("Future ServerResponse has not yet completed"); - } - } - - @Nullable - @Override - public ModelAndView writeTo(HttpServletRequest request, HttpServletResponse response, Context context) - throws ServletException, IOException { - - writeAsync(request, response, createDeferredResult(request)); - return null; - } - - static void writeAsync(HttpServletRequest request, HttpServletResponse response, DeferredResult deferredResult) - throws ServletException, IOException { - - WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); - AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response); - asyncManager.setAsyncWebRequest(asyncWebRequest); - try { - asyncManager.startDeferredResultProcessing(deferredResult); - } - catch (IOException | ServletException ex) { - throw ex; - } - catch (Exception ex) { - throw new ServletException("Async processing failed", ex); - } - - } - - private DeferredResult createDeferredResult(HttpServletRequest request) { - DeferredResult result; - if (this.timeout != null) { - result = new DeferredResult<>(this.timeout.toMillis()); - } - else { - result = new DeferredResult<>(); - } - this.futureResponse.whenComplete((value, ex) -> { - if (ex != null) { - if (ex instanceof CompletionException && ex.getCause() != null) { - ex = ex.getCause(); - } - ServerResponse errorResponse = errorResponse(ex, request); - if (errorResponse != null) { - result.setResult(errorResponse); - } - else { - result.setErrorResult(ex); - } - } - else { - result.setResult(value); - } - }); - return result; - } - - @SuppressWarnings({ "unchecked", "rawtypes" }) - public static AsyncServerResponse create(Object obj, @Nullable Duration timeout) { - Assert.notNull(obj, "Argument to async must not be null"); - - if (obj instanceof CompletableFuture futureResponse) { - return new GatewayAsyncServerResponse(futureResponse, timeout); - } - throw new IllegalArgumentException("Asynchronous type not supported: " + obj.getClass()); - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/GatewayDelegatingRouterFunction.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/GatewayDelegatingRouterFunction.java deleted file mode 100644 index 8cc9a2f2..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/GatewayDelegatingRouterFunction.java +++ /dev/null @@ -1,61 +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.handler; - -import java.util.HashMap; -import java.util.Optional; - -import org.springframework.cloud.gateway.server.mvc.common.MvcUtils; -import org.springframework.web.servlet.function.HandlerFunction; -import org.springframework.web.servlet.function.RouterFunction; -import org.springframework.web.servlet.function.RouterFunctions; -import org.springframework.web.servlet.function.ServerRequest; -import org.springframework.web.servlet.function.ServerResponse; - -// no need for all methods delegated since there are default implementations -// adds routeId to request attributes in route(ServerRequest) -public class GatewayDelegatingRouterFunction implements RouterFunction { - - private final RouterFunction delegate; - - private final String routeId; - - public GatewayDelegatingRouterFunction(RouterFunction delegate, String routeId) { - this.delegate = delegate; - this.routeId = routeId; - } - - @Override - public Optional> route(ServerRequest request) { - request.attributes().put(MvcUtils.GATEWAY_ROUTE_ID_ATTR, routeId); - request.attributes().computeIfAbsent(MvcUtils.GATEWAY_ATTRIBUTES_ATTR, s -> new HashMap()); - Optional> handlerFunction = delegate.route(request); - request.attributes().remove(MvcUtils.GATEWAY_ROUTE_ID_ATTR); - return handlerFunction; - } - - @Override - public void accept(RouterFunctions.Visitor visitor) { - delegate.accept(visitor); - } - - @Override - public String toString() { - return String.format("RouterFunction routeId=%s delegate=%s", routeId, delegate); - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/GatewayEntityResponseBuilder.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/GatewayEntityResponseBuilder.java deleted file mode 100644 index a1a8e8bf..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/GatewayEntityResponseBuilder.java +++ /dev/null @@ -1,380 +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.handler; - -import java.io.IOException; -import java.lang.reflect.Type; -import java.net.URI; -import java.time.Instant; -import java.time.ZonedDateTime; -import java.util.Arrays; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; -import java.util.concurrent.CompletionException; -import java.util.concurrent.CompletionStage; -import java.util.function.Consumer; - -import jakarta.servlet.ServletException; -import jakarta.servlet.http.Cookie; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -import org.springframework.core.ParameterizedTypeReference; -import org.springframework.core.io.InputStreamResource; -import org.springframework.core.io.Resource; -import org.springframework.core.io.support.ResourceRegion; -import org.springframework.http.CacheControl; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpRange; -import org.springframework.http.HttpStatus; -import org.springframework.http.HttpStatusCode; -import org.springframework.http.InvalidMediaTypeException; -import org.springframework.http.MediaType; -import org.springframework.http.converter.GenericHttpMessageConverter; -import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.http.server.ServletServerHttpResponse; -import org.springframework.lang.Nullable; -import org.springframework.util.Assert; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; -import org.springframework.web.HttpMediaTypeNotAcceptableException; -import org.springframework.web.context.request.async.DeferredResult; -import org.springframework.web.servlet.ModelAndView; -import org.springframework.web.servlet.function.EntityResponse; -import org.springframework.web.servlet.function.ServerResponse; - -/** - * Default {@link EntityResponse.Builder} implementation. - * - * @param the entity type - * @author Arjen Poutsma - * @since 5.2 - */ -final class GatewayEntityResponseBuilder implements EntityResponse.Builder { - - private static final Type RESOURCE_REGION_LIST_TYPE = new ParameterizedTypeReference>() { - }.getType(); - - private final T entity; - - private final Type entityType; - - private HttpStatusCode status = HttpStatus.OK; - - private final HttpHeaders headers = new HttpHeaders(); - - private final MultiValueMap cookies = new LinkedMultiValueMap<>(); - - private GatewayEntityResponseBuilder(T entity, @Nullable Type entityType) { - this.entity = entity; - this.entityType = (entityType != null) ? entityType : entity.getClass(); - } - - @Override - public EntityResponse.Builder status(HttpStatusCode status) { - Assert.notNull(status, "HttpStatusCode must not be null"); - this.status = status; - return this; - } - - @Override - public EntityResponse.Builder status(int status) { - return status(HttpStatusCode.valueOf(status)); - } - - @Override - public EntityResponse.Builder cookie(Cookie cookie) { - Assert.notNull(cookie, "Cookie must not be null"); - this.cookies.add(cookie.getName(), cookie); - return this; - } - - @Override - public EntityResponse.Builder cookies(Consumer> cookiesConsumer) { - cookiesConsumer.accept(this.cookies); - return this; - } - - @Override - public EntityResponse.Builder header(String headerName, String... headerValues) { - for (String headerValue : headerValues) { - this.headers.add(headerName, headerValue); - } - return this; - } - - @Override - public EntityResponse.Builder headers(Consumer headersConsumer) { - headersConsumer.accept(this.headers); - return this; - } - - @Override - public EntityResponse.Builder allow(HttpMethod... allowedMethods) { - this.headers.setAllow(new LinkedHashSet<>(Arrays.asList(allowedMethods))); - return this; - } - - @Override - public EntityResponse.Builder allow(Set allowedMethods) { - this.headers.setAllow(allowedMethods); - return this; - } - - @Override - public EntityResponse.Builder contentLength(long contentLength) { - this.headers.setContentLength(contentLength); - return this; - } - - @Override - public EntityResponse.Builder contentType(MediaType contentType) { - this.headers.setContentType(contentType); - return this; - } - - @Override - public EntityResponse.Builder eTag(String etag) { - if (!etag.startsWith("\"") && !etag.startsWith("W/\"")) { - etag = "\"" + etag; - } - if (!etag.endsWith("\"")) { - etag = etag + "\""; - } - this.headers.setETag(etag); - return this; - } - - @Override - public EntityResponse.Builder lastModified(ZonedDateTime lastModified) { - this.headers.setLastModified(lastModified); - return this; - } - - @Override - public EntityResponse.Builder lastModified(Instant lastModified) { - this.headers.setLastModified(lastModified); - return this; - } - - @Override - public EntityResponse.Builder location(URI location) { - this.headers.setLocation(location); - return this; - } - - @Override - public EntityResponse.Builder cacheControl(CacheControl cacheControl) { - this.headers.setCacheControl(cacheControl); - return this; - } - - @Override - public EntityResponse.Builder varyBy(String... requestHeaders) { - this.headers.setVary(Arrays.asList(requestHeaders)); - return this; - } - - @SuppressWarnings({ "rawtypes", "unchecked" }) - @Override - public EntityResponse build() { - if (this.entity instanceof CompletionStage completionStage) { - return new CompletionStageEntityResponse(this.status, this.headers, this.cookies, completionStage, - this.entityType); - } - return new GatewayEntityResponse<>(this.status, this.headers, this.cookies, this.entity, this.entityType); - } - - /** - * Return a new {@link EntityResponse.Builder} from the given object. - */ - public static EntityResponse.Builder fromObject(T t) { - return new GatewayEntityResponseBuilder<>(t, null); - } - - /** - * Return a new {@link EntityResponse.Builder} from the given object and type - * reference. - */ - public static EntityResponse.Builder fromObject(T t, ParameterizedTypeReference bodyType) { - return new GatewayEntityResponseBuilder<>(t, bodyType.getType()); - } - - /** - * Default {@link EntityResponse} implementation for synchronous bodies. - */ - private static class GatewayEntityResponse extends AbstractGatewayServerResponse implements EntityResponse { - - private final T entity; - - private final Type entityType; - - GatewayEntityResponse(HttpStatusCode statusCode, HttpHeaders headers, MultiValueMap cookies, - T entity, Type entityType) { - - super(statusCode, headers, cookies); - this.entity = entity; - this.entityType = entityType; - } - - @Override - public T entity() { - return this.entity; - } - - @Override - protected ModelAndView writeToInternal(HttpServletRequest servletRequest, HttpServletResponse servletResponse, - Context context) throws ServletException, IOException { - - writeEntityWithMessageConverters(this.entity, servletRequest, servletResponse, context); - return null; - } - - @SuppressWarnings({ "unchecked", "resource", "rawtypes" }) - protected void writeEntityWithMessageConverters(Object entity, HttpServletRequest request, - HttpServletResponse response, ServerResponse.Context context) throws ServletException, IOException { - - ServletServerHttpResponse serverResponse = new ServletServerHttpResponse(response); - MediaType contentType = getContentType(response); - Class entityClass = entity.getClass(); - Type entityType = this.entityType; - - if (entityClass != InputStreamResource.class && Resource.class.isAssignableFrom(entityClass)) { - serverResponse.getHeaders().set(HttpHeaders.ACCEPT_RANGES, "bytes"); - String rangeHeader = request.getHeader(HttpHeaders.RANGE); - if (rangeHeader != null) { - Resource resource = (Resource) entity; - try { - List httpRanges = HttpRange.parseRanges(rangeHeader); - serverResponse.getServletResponse().setStatus(HttpStatus.PARTIAL_CONTENT.value()); - entity = HttpRange.toResourceRegions(httpRanges, resource); - entityClass = entity.getClass(); - entityType = RESOURCE_REGION_LIST_TYPE; - } - catch (IllegalArgumentException ex) { - serverResponse.getHeaders().set(HttpHeaders.CONTENT_RANGE, - "bytes */" + resource.contentLength()); - serverResponse.getServletResponse() - .setStatus(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE.value()); - } - } - } - - for (HttpMessageConverter messageConverter : context.messageConverters()) { - if (messageConverter instanceof GenericHttpMessageConverter genericMessageConverter) { - if (genericMessageConverter.canWrite(entityType, entityClass, contentType)) { - genericMessageConverter.write(entity, entityType, contentType, serverResponse); - return; - } - } - if (messageConverter.canWrite(entityClass, contentType)) { - ((HttpMessageConverter) messageConverter).write(entity, contentType, serverResponse); - return; - } - } - - List producibleMediaTypes = producibleMediaTypes(context.messageConverters(), entityClass); - throw new HttpMediaTypeNotAcceptableException(producibleMediaTypes); - } - - @Nullable - private static MediaType getContentType(HttpServletResponse response) { - try { - return MediaType.parseMediaType(response.getContentType()).removeQualityValue(); - } - catch (InvalidMediaTypeException ex) { - return null; - } - } - - protected void tryWriteEntityWithMessageConverters(Object entity, HttpServletRequest request, - HttpServletResponse response, ServerResponse.Context context) throws ServletException, IOException { - try { - writeEntityWithMessageConverters(entity, request, response, context); - } - catch (IOException | ServletException ex) { - handleError(ex, request, response, context); - } - } - - private static List producibleMediaTypes(List> messageConverters, - Class entityClass) { - - return messageConverters.stream().filter(messageConverter -> messageConverter.canWrite(entityClass, null)) - .flatMap(messageConverter -> messageConverter.getSupportedMediaTypes(entityClass).stream()) - .toList(); - } - - } - - /** - * {@link EntityResponse} implementation for asynchronous {@link CompletionStage} - * bodies. - */ - private static class CompletionStageEntityResponse extends GatewayEntityResponse> { - - CompletionStageEntityResponse(HttpStatusCode statusCode, HttpHeaders headers, - MultiValueMap cookies, CompletionStage entity, Type entityType) { - - super(statusCode, headers, cookies, entity, entityType); - } - - @Override - protected ModelAndView writeToInternal(HttpServletRequest servletRequest, HttpServletResponse servletResponse, - Context context) throws ServletException, IOException { - - DeferredResult deferredResult = createDeferredResult(servletRequest, servletResponse, - context); - GatewayAsyncServerResponse.writeAsync(servletRequest, servletResponse, deferredResult); - return null; - } - - private DeferredResult createDeferredResult(HttpServletRequest request, - HttpServletResponse response, Context context) { - - DeferredResult result = new DeferredResult<>(); - entity().whenComplete((value, ex) -> { - if (ex != null) { - if (ex instanceof CompletionException && ex.getCause() != null) { - ex = ex.getCause(); - } - ServerResponse errorResponse = errorResponse(ex, request); - if (errorResponse != null) { - result.setResult(errorResponse); - } - else { - result.setErrorResult(ex); - } - } - else { - try { - tryWriteEntityWithMessageConverters(value, request, response, context); - result.setResult(null); - } - catch (ServletException | IOException writeException) { - result.setErrorResult(writeException); - } - } - }); - return result; - } - - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/GatewayErrorHandlingServerResponse.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/GatewayErrorHandlingServerResponse.java deleted file mode 100644 index dcd6a18b..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/GatewayErrorHandlingServerResponse.java +++ /dev/null @@ -1,112 +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.handler; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.function.BiFunction; -import java.util.function.Predicate; - -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.lang.Nullable; -import org.springframework.util.Assert; -import org.springframework.web.servlet.ModelAndView; -import org.springframework.web.servlet.function.RouterFunctions; -import org.springframework.web.servlet.function.ServerRequest; -import org.springframework.web.servlet.function.ServerResponse; - -/** - * Base class for {@link ServerResponse} implementations with error handling. - * - * @author Arjen Poutsma - * @since 5.3 - */ -abstract class GatewayErrorHandlingServerResponse implements ServerResponse { - - protected final Log logger = LogFactory.getLog(getClass()); - - private final List> errorHandlers = new ArrayList<>(); - - protected final void addErrorHandler(Predicate predicate, - BiFunction errorHandler) { - - Assert.notNull(predicate, "Predicate must not be null"); - Assert.notNull(errorHandler, "ErrorHandler must not be null"); - this.errorHandlers.add(new ErrorHandler<>(predicate, errorHandler)); - } - - @Nullable - protected final ModelAndView handleError(Throwable t, HttpServletRequest servletRequest, - HttpServletResponse servletResponse, Context context) throws ServletException, IOException { - - ServerResponse serverResponse = errorResponse(t, servletRequest); - if (serverResponse != null) { - return serverResponse.writeTo(servletRequest, servletResponse, context); - } - else if (t instanceof ServletException servletException) { - throw servletException; - } - else if (t instanceof IOException ioException) { - throw ioException; - } - else { - throw new ServletException(t); - } - } - - @Nullable - protected final ServerResponse errorResponse(Throwable t, HttpServletRequest servletRequest) { - for (ErrorHandler errorHandler : this.errorHandlers) { - if (errorHandler.test(t)) { - ServerRequest serverRequest = (ServerRequest) servletRequest - .getAttribute(RouterFunctions.REQUEST_ATTRIBUTE); - return errorHandler.handle(t, serverRequest); - } - } - return null; - } - - private static class ErrorHandler { - - private final Predicate predicate; - - private final BiFunction responseProvider; - - ErrorHandler(Predicate predicate, BiFunction responseProvider) { - Assert.notNull(predicate, "Predicate must not be null"); - Assert.notNull(responseProvider, "ResponseProvider must not be null"); - this.predicate = predicate; - this.responseProvider = responseProvider; - } - - public boolean test(Throwable t) { - return this.predicate.test(t); - } - - public T handle(Throwable t, ServerRequest serverRequest) { - return this.responseProvider.apply(t, serverRequest); - } - - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/GatewayMultipartAutoConfiguration.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/GatewayMultipartAutoConfiguration.java deleted file mode 100644 index 89e75af2..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/GatewayMultipartAutoConfiguration.java +++ /dev/null @@ -1,50 +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.handler; - -import jakarta.servlet.MultipartConfigElement; -import jakarta.servlet.Servlet; - -import org.springframework.boot.autoconfigure.AutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; -import org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration; -import org.springframework.boot.autoconfigure.web.servlet.MultipartProperties; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.web.multipart.MultipartResolver; -import org.springframework.web.multipart.support.StandardServletMultipartResolver; -import org.springframework.web.servlet.DispatcherServlet; - -@AutoConfiguration(before = MultipartAutoConfiguration.class) -@ConditionalOnClass({ Servlet.class, StandardServletMultipartResolver.class, MultipartConfigElement.class }) -@ConditionalOnProperty(prefix = "spring.servlet.multipart", name = "enabled", matchIfMissing = true) -@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) -@EnableConfigurationProperties(MultipartProperties.class) -public class GatewayMultipartAutoConfiguration { - - @Bean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME) - @ConditionalOnMissingBean(MultipartResolver.class) - public GatewayMvcMultipartResolver gatewayMvcMultipartResolver(MultipartProperties properties) { - GatewayMvcMultipartResolver multipartResolver = new GatewayMvcMultipartResolver(); - multipartResolver.setResolveLazily(properties.isResolveLazily()); - return multipartResolver; - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/GatewayMvcMultipartResolver.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/GatewayMvcMultipartResolver.java deleted file mode 100644 index 59c20e82..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/GatewayMvcMultipartResolver.java +++ /dev/null @@ -1,77 +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.handler; - -import java.util.Collections; -import java.util.Map; - -import jakarta.servlet.http.HttpServletRequest; - -import org.springframework.cloud.gateway.server.mvc.common.MvcUtils; -import org.springframework.web.multipart.MultipartException; -import org.springframework.web.multipart.MultipartHttpServletRequest; -import org.springframework.web.multipart.support.StandardMultipartHttpServletRequest; -import org.springframework.web.multipart.support.StandardServletMultipartResolver; - -/** - * A MultipartResolver that does not resolve if the current request is a Gateway request. - */ -public class GatewayMvcMultipartResolver extends StandardServletMultipartResolver { - - @Override - public boolean isMultipart(HttpServletRequest request) { - return super.isMultipart(request); - } - - @Override - public MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException { - return new GatewayMultipartHttpServletRequest(request); - } - - private static boolean isGatewayRequest(HttpServletRequest request) { - return request.getAttribute(MvcUtils.GATEWAY_ROUTE_ID_ATTR) != null - || request.getAttribute(MvcUtils.GATEWAY_REQUEST_URL_ATTR) != null; - } - - /** - * StandardMultipartHttpServletRequest wrapper that will not parse multipart if it is - * a gateway request. A gateway request has certain request attributes set. - */ - static class GatewayMultipartHttpServletRequest extends StandardMultipartHttpServletRequest { - - GatewayMultipartHttpServletRequest(HttpServletRequest request) { - super(request, true); - } - - @Override - protected void initializeMultipart() { - if (!isGatewayRequest(getRequest())) { - super.initializeMultipart(); - } - } - - @Override - public Map getParameterMap() { - if (isGatewayRequest(getRequest())) { - return Collections.emptyMap(); - } - return super.getParameterMap(); - } - - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/GatewayRenderingResponseBuilder.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/GatewayRenderingResponseBuilder.java deleted file mode 100644 index eca372ad..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/GatewayRenderingResponseBuilder.java +++ /dev/null @@ -1,186 +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.handler; - -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.function.Consumer; - -import jakarta.servlet.http.Cookie; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -import org.springframework.core.Conventions; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.HttpStatusCode; -import org.springframework.lang.Nullable; -import org.springframework.util.Assert; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; -import org.springframework.web.servlet.ModelAndView; -import org.springframework.web.servlet.function.RenderingResponse; - -/** - * Default {@link RenderingResponse.Builder} implementation. - * - * @author Arjen Poutsma - * @since 5.1 - */ -final class GatewayRenderingResponseBuilder implements RenderingResponse.Builder { - - private final String name; - - private HttpStatusCode status = HttpStatus.OK; - - private final HttpHeaders headers = new HttpHeaders(); - - private final MultiValueMap cookies = new LinkedMultiValueMap<>(); - - private final Map model = new LinkedHashMap<>(); - - GatewayRenderingResponseBuilder(RenderingResponse other) { - Assert.notNull(other, "RenderingResponse must not be null"); - this.name = other.name(); - this.status = other.statusCode(); - this.headers.putAll(other.headers()); - this.model.putAll(other.model()); - } - - GatewayRenderingResponseBuilder(String name) { - Assert.notNull(name, "Name must not be null"); - this.name = name; - } - - @Override - public RenderingResponse.Builder status(HttpStatusCode status) { - Assert.notNull(status, "HttpStatusCode must not be null"); - this.status = status; - return this; - } - - @Override - public RenderingResponse.Builder status(int status) { - return status(HttpStatusCode.valueOf(status)); - } - - @Override - public RenderingResponse.Builder cookie(Cookie cookie) { - Assert.notNull(cookie, "Cookie must not be null"); - this.cookies.add(cookie.getName(), cookie); - return this; - } - - @Override - public RenderingResponse.Builder cookies(Consumer> cookiesConsumer) { - cookiesConsumer.accept(this.cookies); - return this; - } - - @Override - public RenderingResponse.Builder modelAttribute(Object attribute) { - Assert.notNull(attribute, "Attribute must not be null"); - if (attribute instanceof Collection collection && collection.isEmpty()) { - return this; - } - return modelAttribute(Conventions.getVariableName(attribute), attribute); - } - - @Override - public RenderingResponse.Builder modelAttribute(String name, @Nullable Object value) { - Assert.notNull(name, "Name must not be null"); - this.model.put(name, value); - return this; - } - - @Override - public RenderingResponse.Builder modelAttributes(Object... attributes) { - modelAttributes(Arrays.asList(attributes)); - return this; - } - - @Override - public RenderingResponse.Builder modelAttributes(Collection attributes) { - attributes.forEach(this::modelAttribute); - return this; - } - - @Override - public RenderingResponse.Builder modelAttributes(Map attributes) { - this.model.putAll(attributes); - return this; - } - - @Override - public RenderingResponse.Builder header(String headerName, String... headerValues) { - for (String headerValue : headerValues) { - this.headers.add(headerName, headerValue); - } - return this; - } - - @Override - public RenderingResponse.Builder headers(Consumer headersConsumer) { - headersConsumer.accept(this.headers); - return this; - } - - @Override - public RenderingResponse build() { - return new GatewayRenderingResponse(this.status, this.headers, this.cookies, this.name, this.model); - } - - private static final class GatewayRenderingResponse extends AbstractGatewayServerResponse - implements RenderingResponse { - - private final String name; - - private final Map model; - - GatewayRenderingResponse(HttpStatusCode statusCode, HttpHeaders headers, MultiValueMap cookies, - String name, Map model) { - - super(statusCode, headers, cookies); - this.name = name; - this.model = Collections.unmodifiableMap(new LinkedHashMap<>(model)); - } - - @Override - public String name() { - return this.name; - } - - @Override - public Map model() { - return this.model; - } - - @Override - protected ModelAndView writeToInternal(HttpServletRequest request, HttpServletResponse response, - Context context) { - - ModelAndView mav = new ModelAndView(this.name, this.statusCode()); - mav.addAllObjects(this.model); - return mav; - } - - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/GatewayRouterFunctions.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/GatewayRouterFunctions.java deleted file mode 100644 index 74ad3436..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/GatewayRouterFunctions.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.handler; - -import org.springframework.web.servlet.function.HandlerFunction; -import org.springframework.web.servlet.function.RequestPredicate; -import org.springframework.web.servlet.function.RouterFunction; -import org.springframework.web.servlet.function.RouterFunctions; -import org.springframework.web.servlet.function.RouterFunctions.Builder; -import org.springframework.web.servlet.function.ServerResponse; - -import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.routeId; - -public abstract class GatewayRouterFunctions { - - private GatewayRouterFunctions() { - } - - public static Builder route() { - return RouterFunctions.route(); - } - - public static Builder route(String routeId) { - Builder builder = RouterFunctions.route().before(routeId(routeId)); - return new GatewayRouterFunctionsBuilder(builder, routeId); - } - - public static RouterFunction route(RequestPredicate predicate, - HandlerFunction handlerFunction) { - return RouterFunctions.route(predicate, handlerFunction); - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/GatewayRouterFunctionsBuilder.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/GatewayRouterFunctionsBuilder.java deleted file mode 100644 index 2e81fc57..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/GatewayRouterFunctionsBuilder.java +++ /dev/null @@ -1,326 +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.handler; - -import java.util.Map; -import java.util.Optional; -import java.util.function.BiFunction; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Predicate; -import java.util.function.Supplier; - -import org.springframework.cloud.gateway.server.mvc.common.MvcUtils; -import org.springframework.core.io.Resource; -import org.springframework.web.servlet.function.HandlerFilterFunction; -import org.springframework.web.servlet.function.HandlerFunction; -import org.springframework.web.servlet.function.RequestPredicate; -import org.springframework.web.servlet.function.RouterFunction; -import org.springframework.web.servlet.function.RouterFunctions; -import org.springframework.web.servlet.function.ServerRequest; -import org.springframework.web.servlet.function.ServerResponse; - -public class GatewayRouterFunctionsBuilder implements RouterFunctions.Builder { - - private final RouterFunctions.Builder builder; - - private final String routeId; - - public GatewayRouterFunctionsBuilder(RouterFunctions.Builder builder, String routeId) { - this.builder = builder; - this.routeId = routeId; - } - - @Override - public RouterFunction build() { - RouterFunction routerFunction = builder.build(); - RouterFunction withAttributes = routerFunction.withAttribute(MvcUtils.GATEWAY_ROUTE_ID_ATTR, - this.routeId); - return new GatewayDelegatingRouterFunction<>(withAttributes, this.routeId); - } - - @Override - public RouterFunctions.Builder GET(HandlerFunction handlerFunction) { - builder.GET(handlerFunction); - return this; - } - - @Override - public RouterFunctions.Builder GET(String pattern, HandlerFunction handlerFunction) { - builder.GET(pattern, handlerFunction); - return this; - } - - @Override - public RouterFunctions.Builder GET(RequestPredicate predicate, HandlerFunction handlerFunction) { - builder.GET(predicate, handlerFunction); - return this; - } - - @Override - public RouterFunctions.Builder GET(String pattern, RequestPredicate predicate, - HandlerFunction handlerFunction) { - builder.GET(pattern, predicate, handlerFunction); - return this; - } - - @Override - public RouterFunctions.Builder HEAD(HandlerFunction handlerFunction) { - builder.HEAD(handlerFunction); - return this; - } - - @Override - public RouterFunctions.Builder HEAD(String pattern, HandlerFunction handlerFunction) { - builder.HEAD(pattern, handlerFunction); - return this; - } - - @Override - public RouterFunctions.Builder HEAD(RequestPredicate predicate, HandlerFunction handlerFunction) { - builder.HEAD(predicate, handlerFunction); - return this; - } - - @Override - public RouterFunctions.Builder HEAD(String pattern, RequestPredicate predicate, - HandlerFunction handlerFunction) { - builder.HEAD(pattern, predicate, handlerFunction); - return this; - } - - @Override - public RouterFunctions.Builder POST(HandlerFunction handlerFunction) { - builder.POST(handlerFunction); - return this; - } - - @Override - public RouterFunctions.Builder POST(String pattern, HandlerFunction handlerFunction) { - builder.POST(pattern, handlerFunction); - return this; - } - - @Override - public RouterFunctions.Builder POST(RequestPredicate predicate, HandlerFunction handlerFunction) { - builder.POST(predicate, handlerFunction); - return this; - } - - @Override - public RouterFunctions.Builder POST(String pattern, RequestPredicate predicate, - HandlerFunction handlerFunction) { - builder.POST(pattern, predicate, handlerFunction); - return this; - } - - @Override - public RouterFunctions.Builder PUT(HandlerFunction handlerFunction) { - builder.PUT(handlerFunction); - return this; - } - - @Override - public RouterFunctions.Builder PUT(String pattern, HandlerFunction handlerFunction) { - builder.PUT(pattern, handlerFunction); - return this; - } - - @Override - public RouterFunctions.Builder PUT(RequestPredicate predicate, HandlerFunction handlerFunction) { - builder.PUT(predicate, handlerFunction); - return this; - } - - @Override - public RouterFunctions.Builder PUT(String pattern, RequestPredicate predicate, - HandlerFunction handlerFunction) { - builder.PUT(pattern, predicate, handlerFunction); - return this; - } - - @Override - public RouterFunctions.Builder PATCH(HandlerFunction handlerFunction) { - builder.PATCH(handlerFunction); - return this; - } - - @Override - public RouterFunctions.Builder PATCH(String pattern, HandlerFunction handlerFunction) { - builder.PATCH(pattern, handlerFunction); - return this; - } - - @Override - public RouterFunctions.Builder PATCH(RequestPredicate predicate, HandlerFunction handlerFunction) { - builder.PATCH(predicate, handlerFunction); - return this; - } - - @Override - public RouterFunctions.Builder PATCH(String pattern, RequestPredicate predicate, - HandlerFunction handlerFunction) { - builder.PATCH(pattern, predicate, handlerFunction); - return this; - } - - @Override - public RouterFunctions.Builder DELETE(HandlerFunction handlerFunction) { - builder.DELETE(handlerFunction); - return this; - } - - @Override - public RouterFunctions.Builder DELETE(String pattern, HandlerFunction handlerFunction) { - builder.DELETE(pattern, handlerFunction); - return this; - } - - @Override - public RouterFunctions.Builder DELETE(RequestPredicate predicate, HandlerFunction handlerFunction) { - builder.DELETE(predicate, handlerFunction); - return this; - } - - @Override - public RouterFunctions.Builder DELETE(String pattern, RequestPredicate predicate, - HandlerFunction handlerFunction) { - builder.DELETE(pattern, predicate, handlerFunction); - return this; - } - - @Override - public RouterFunctions.Builder OPTIONS(HandlerFunction handlerFunction) { - builder.OPTIONS(handlerFunction); - return this; - } - - @Override - public RouterFunctions.Builder OPTIONS(String pattern, HandlerFunction handlerFunction) { - builder.OPTIONS(pattern, handlerFunction); - return this; - } - - @Override - public RouterFunctions.Builder OPTIONS(RequestPredicate predicate, - HandlerFunction handlerFunction) { - builder.OPTIONS(predicate, handlerFunction); - return this; - } - - @Override - public RouterFunctions.Builder OPTIONS(String pattern, RequestPredicate predicate, - HandlerFunction handlerFunction) { - builder.OPTIONS(pattern, predicate, handlerFunction); - return this; - } - - @Override - public RouterFunctions.Builder route(RequestPredicate predicate, HandlerFunction handlerFunction) { - builder.route(predicate, handlerFunction); - return this; - } - - @Override - public RouterFunctions.Builder add(RouterFunction routerFunction) { - builder.add(routerFunction); - return this; - } - - @Override - public RouterFunctions.Builder resources(String pattern, Resource location) { - builder.resources(pattern, location); - return this; - } - - @Override - public RouterFunctions.Builder resources(Function> lookupFunction) { - builder.resources(lookupFunction); - return this; - } - - @Override - public RouterFunctions.Builder nest(RequestPredicate predicate, - Supplier> routerFunctionSupplier) { - builder.nest(predicate, routerFunctionSupplier); - return this; - } - - @Override - public RouterFunctions.Builder nest(RequestPredicate predicate, Consumer builderConsumer) { - builder.nest(predicate, builderConsumer); - return this; - } - - @Override - public RouterFunctions.Builder path(String pattern, - Supplier> routerFunctionSupplier) { - builder.path(pattern, routerFunctionSupplier); - return this; - } - - @Override - public RouterFunctions.Builder path(String pattern, Consumer builderConsumer) { - builder.path(pattern, builderConsumer); - return this; - } - - @Override - public RouterFunctions.Builder filter(HandlerFilterFunction filterFunction) { - builder.filter(filterFunction); - return this; - } - - @Override - public RouterFunctions.Builder before(Function requestProcessor) { - builder.before(requestProcessor); - return this; - } - - @Override - public RouterFunctions.Builder after(BiFunction responseProcessor) { - builder.after(responseProcessor); - return this; - } - - @Override - public RouterFunctions.Builder onError(Predicate predicate, - BiFunction responseProvider) { - builder.onError(predicate, responseProvider); - return this; - } - - @Override - public RouterFunctions.Builder onError(Class exceptionType, - BiFunction responseProvider) { - builder.onError(exceptionType, responseProvider); - return this; - } - - @Override - public RouterFunctions.Builder withAttribute(String name, Object value) { - builder.withAttribute(name, value); - return this; - } - - @Override - public RouterFunctions.Builder withAttributes(Consumer> attributesConsumer) { - builder.withAttributes(attributesConsumer); - return this; - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/GatewayServerResponse.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/GatewayServerResponse.java deleted file mode 100644 index b03f106e..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/GatewayServerResponse.java +++ /dev/null @@ -1,209 +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.handler; - -import java.net.URI; -import java.time.Duration; -import java.util.concurrent.CompletableFuture; - -import org.springframework.core.ReactiveAdapterRegistry; -import org.springframework.http.HttpStatus; -import org.springframework.http.HttpStatusCode; -import org.springframework.web.ErrorResponse; -import org.springframework.web.servlet.function.ServerResponse; - -public interface GatewayServerResponse extends ServerResponse { - - void setStatusCode(HttpStatusCode statusCode); - - // Static methods - - /** - * Create a builder with the status code and headers of the given response. - * @param other the response to copy the status and headers from - * @return the created builder - */ - static ServerResponse.BodyBuilder from(ServerResponse other) { - return new GatewayServerResponseBuilder(other); - } - - /** - * Create a {@code ServerResponse} from the given {@link ErrorResponse}. - * @param response the {@link ErrorResponse} to initialize from - * @return the built response - * @since 6.0 - */ - static ServerResponse from(ErrorResponse response) { - return status(response.getStatusCode()).headers(headers -> headers.putAll(response.getHeaders())) - .body(response.getBody()); - } - - /** - * Create a builder with the given HTTP status. - * @param status the response status - * @return the created builder - */ - static ServerResponse.BodyBuilder status(HttpStatusCode status) { - return new GatewayServerResponseBuilder(status); - } - - /** - * Create a builder with the given HTTP status. - * @param status the response status - * @return the created builder - */ - static ServerResponse.BodyBuilder status(int status) { - return new GatewayServerResponseBuilder(HttpStatusCode.valueOf(status)); - } - - /** - * Create a builder with the status set to {@linkplain HttpStatus#OK 200 OK}. - * @return the created builder - */ - static ServerResponse.BodyBuilder ok() { - return status(HttpStatus.OK); - } - - /** - * Create a builder with a {@linkplain HttpStatus#CREATED 201 Created} status and a - * location header set to the given URI. - * @param location the location URI - * @return the created builder - */ - static ServerResponse.BodyBuilder created(URI location) { - ServerResponse.BodyBuilder builder = status(HttpStatus.CREATED); - return builder.location(location); - } - - /** - * Create a builder with a {@linkplain HttpStatus#ACCEPTED 202 Accepted} status. - * @return the created builder - */ - static ServerResponse.BodyBuilder accepted() { - return status(HttpStatus.ACCEPTED); - } - - /** - * Create a builder with a {@linkplain HttpStatus#NO_CONTENT 204 No Content} status. - * @return the created builder - */ - static ServerResponse.HeadersBuilder noContent() { - return status(HttpStatus.NO_CONTENT); - } - - /** - * Create a builder with a {@linkplain HttpStatus#SEE_OTHER 303 See Other} status and - * a location header set to the given URI. - * @param location the location URI - * @return the created builder - */ - static ServerResponse.BodyBuilder seeOther(URI location) { - ServerResponse.BodyBuilder builder = status(HttpStatus.SEE_OTHER); - return builder.location(location); - } - - /** - * Create a builder with a {@linkplain HttpStatus#TEMPORARY_REDIRECT 307 Temporary - * Redirect} status and a location header set to the given URI. - * @param location the location URI - * @return the created builder - */ - static ServerResponse.BodyBuilder temporaryRedirect(URI location) { - ServerResponse.BodyBuilder builder = status(HttpStatus.TEMPORARY_REDIRECT); - return builder.location(location); - } - - /** - * Create a builder with a {@linkplain HttpStatus#PERMANENT_REDIRECT 308 Permanent - * Redirect} status and a location header set to the given URI. - * @param location the location URI - * @return the created builder - */ - static ServerResponse.BodyBuilder permanentRedirect(URI location) { - ServerResponse.BodyBuilder builder = status(HttpStatus.PERMANENT_REDIRECT); - return builder.location(location); - } - - /** - * Create a builder with a {@linkplain HttpStatus#BAD_REQUEST 400 Bad Request} status. - * @return the created builder - */ - static ServerResponse.BodyBuilder badRequest() { - return status(HttpStatus.BAD_REQUEST); - } - - /** - * Create a builder with a {@linkplain HttpStatus#NOT_FOUND 404 Not Found} status. - * @return the created builder - */ - static ServerResponse.HeadersBuilder notFound() { - return status(HttpStatus.NOT_FOUND); - } - - /** - * Create a builder with a {@linkplain HttpStatus#UNPROCESSABLE_ENTITY 422 - * Unprocessable Entity} status. - * @return the created builder - */ - static ServerResponse.BodyBuilder unprocessableEntity() { - return status(HttpStatus.UNPROCESSABLE_ENTITY); - } - - /** - * Create a (built) response with the given asynchronous response. Parameter - * {@code asyncResponse} can be a {@link CompletableFuture - * CompletableFuture<ServerResponse>} or {@link Publisher - * Publisher<ServerResponse>} (or any asynchronous producer of a single - * {@code ServerResponse} that can be adapted via the - * {@link ReactiveAdapterRegistry}). - * - *

- * This method can be used to set the response status code, headers, and body based on - * an asynchronous result. If only the body is asynchronous, - * {@link ServerResponse.BodyBuilder#body(Object)} can be used instead. - * @param asyncResponse a {@code CompletableFuture} or - * {@code Publisher} - * @return the asynchronous response - * @since 5.3 - */ - static ServerResponse async(Object asyncResponse) { - return GatewayAsyncServerResponse.create(asyncResponse, null); - } - - /** - * Create a (built) response with the given asynchronous response. Parameter - * {@code asyncResponse} can be a {@link CompletableFuture - * CompletableFuture<ServerResponse>} or {@link Publisher - * Publisher<ServerResponse>} (or any asynchronous producer of a single - * {@code ServerResponse} that can be adapted via the - * {@link ReactiveAdapterRegistry}). - * - *

- * This method can be used to set the response status code, headers, and body based on - * an asynchronous result. If only the body is asynchronous, - * {@link ServerResponse.BodyBuilder#body(Object)} can be used instead. - * @param asyncResponse a {@code CompletableFuture} or - * {@code Publisher} - * @param timeout maximum time period to wait for before timing out - * @return the asynchronous response - * @since 5.3.2 - */ - static ServerResponse async(Object asyncResponse, Duration timeout) { - return GatewayAsyncServerResponse.create(asyncResponse, timeout); - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/GatewayServerResponseBuilder.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/GatewayServerResponseBuilder.java deleted file mode 100644 index 5b921fcd..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/GatewayServerResponseBuilder.java +++ /dev/null @@ -1,223 +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.handler; - -import java.net.URI; -import java.time.Instant; -import java.time.ZonedDateTime; -import java.util.Arrays; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Set; -import java.util.function.Consumer; - -import jakarta.servlet.http.Cookie; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -import org.springframework.core.ParameterizedTypeReference; -import org.springframework.http.CacheControl; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatusCode; -import org.springframework.http.MediaType; -import org.springframework.util.Assert; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; -import org.springframework.web.servlet.ModelAndView; -import org.springframework.web.servlet.function.ServerResponse; - -/** - * Default {@link ServerResponse.BodyBuilder} implementation. - * - * @author Arjen Poutsma - * @since 5.2 - */ -class GatewayServerResponseBuilder implements ServerResponse.BodyBuilder { - - private final HttpStatusCode statusCode; - - private final HttpHeaders headers = new HttpHeaders(); - - private final MultiValueMap cookies = new LinkedMultiValueMap<>(); - - GatewayServerResponseBuilder(ServerResponse other) { - Assert.notNull(other, "ServerResponse must not be null"); - this.statusCode = other.statusCode(); - this.headers.addAll(other.headers()); - this.cookies.addAll(other.cookies()); - } - - GatewayServerResponseBuilder(HttpStatusCode status) { - Assert.notNull(status, "HttpStatusCode must not be null"); - this.statusCode = status; - } - - @Override - public ServerResponse.BodyBuilder header(String headerName, String... headerValues) { - for (String headerValue : headerValues) { - this.headers.add(headerName, headerValue); - } - return this; - } - - @Override - public ServerResponse.BodyBuilder headers(Consumer headersConsumer) { - headersConsumer.accept(this.headers); - return this; - } - - @Override - public ServerResponse.BodyBuilder cookie(Cookie cookie) { - Assert.notNull(cookie, "Cookie must not be null"); - this.cookies.add(cookie.getName(), cookie); - return this; - } - - @Override - public ServerResponse.BodyBuilder cookies(Consumer> cookiesConsumer) { - cookiesConsumer.accept(this.cookies); - return this; - } - - @Override - public ServerResponse.BodyBuilder allow(HttpMethod... allowedMethods) { - this.headers.setAllow(new LinkedHashSet<>(Arrays.asList(allowedMethods))); - return this; - } - - @Override - public ServerResponse.BodyBuilder allow(Set allowedMethods) { - this.headers.setAllow(allowedMethods); - return this; - } - - @Override - public ServerResponse.BodyBuilder contentLength(long contentLength) { - this.headers.setContentLength(contentLength); - return this; - } - - @Override - public ServerResponse.BodyBuilder contentType(MediaType contentType) { - this.headers.setContentType(contentType); - return this; - } - - @Override - public ServerResponse.BodyBuilder eTag(String etag) { - if (!etag.startsWith("\"") && !etag.startsWith("W/\"")) { - etag = "\"" + etag; - } - if (!etag.endsWith("\"")) { - etag = etag + "\""; - } - this.headers.setETag(etag); - return this; - } - - @Override - public ServerResponse.BodyBuilder lastModified(ZonedDateTime lastModified) { - this.headers.setLastModified(lastModified); - return this; - } - - @Override - public ServerResponse.BodyBuilder lastModified(Instant lastModified) { - this.headers.setLastModified(lastModified); - return this; - } - - @Override - public ServerResponse.BodyBuilder location(URI location) { - this.headers.setLocation(location); - return this; - } - - @Override - public ServerResponse.BodyBuilder cacheControl(CacheControl cacheControl) { - this.headers.setCacheControl(cacheControl); - return this; - } - - @Override - public ServerResponse.BodyBuilder varyBy(String... requestHeaders) { - this.headers.setVary(Arrays.asList(requestHeaders)); - return this; - } - - @Override - public ServerResponse build() { - return build((request, response) -> null); - } - - @Override - public ServerResponse build(WriteFunction writeFunction) { - - return new WriteFunctionResponse(this.statusCode, this.headers, this.cookies, writeFunction); - } - - @Override - public ServerResponse body(Object body) { - return GatewayEntityResponseBuilder.fromObject(body).status(this.statusCode) - .headers(headers -> headers.putAll(this.headers)).cookies(cookies -> cookies.addAll(this.cookies)) - .build(); - } - - @Override - public ServerResponse body(T body, ParameterizedTypeReference bodyType) { - return GatewayEntityResponseBuilder.fromObject(body, bodyType).status(this.statusCode) - .headers(headers -> headers.putAll(this.headers)).cookies(cookies -> cookies.addAll(this.cookies)) - .build(); - } - - @Override - public ServerResponse render(String name, Object... modelAttributes) { - return new GatewayRenderingResponseBuilder(name).status(this.statusCode) - .headers(headers -> headers.putAll(this.headers)).cookies(cookies -> cookies.addAll(this.cookies)) - .modelAttributes(modelAttributes).build(); - } - - @Override - public ServerResponse render(String name, Map model) { - return new GatewayRenderingResponseBuilder(name).status(this.statusCode) - .headers(headers -> headers.putAll(this.headers)).cookies(cookies -> cookies.addAll(this.cookies)) - .modelAttributes(model).build(); - } - - private static class WriteFunctionResponse extends AbstractGatewayServerResponse { - - private final WriteFunction writeFunction; - - WriteFunctionResponse(HttpStatusCode statusCode, HttpHeaders headers, MultiValueMap cookies, - WriteFunction writeFunction) { - - super(statusCode, headers, cookies); - Assert.notNull(writeFunction, "WriteFunction must not be null"); - this.writeFunction = writeFunction; - } - - @Override - protected ModelAndView writeToInternal(HttpServletRequest request, HttpServletResponse response, - Context context) throws Exception { - - return this.writeFunction.write(request, response); - } - - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/HandlerDiscoverer.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/HandlerDiscoverer.java deleted file mode 100644 index be0dd7cf..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/HandlerDiscoverer.java +++ /dev/null @@ -1,58 +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.handler; - -import java.util.Collections; -import java.util.List; -import java.util.Objects; - -import org.springframework.cloud.gateway.server.mvc.common.AbstractGatewayDiscoverer; -import org.springframework.web.servlet.function.HandlerFilterFunction; -import org.springframework.web.servlet.function.HandlerFunction; -import org.springframework.web.servlet.function.ServerResponse; - -public class HandlerDiscoverer extends AbstractGatewayDiscoverer { - - @Override - public void discover() { - doDiscover(HandlerSupplier.class, HandlerFunction.class); - doDiscover(HandlerSupplier.class, HandlerDiscoverer.Result.class); - } - - public static class Result { - - private final HandlerFunction handlerFunction; - - private final List> filters; - - public Result(HandlerFunction handlerFunction, - List> filters) { - this.handlerFunction = handlerFunction; - this.filters = Objects.requireNonNullElse(filters, Collections.emptyList()); - } - - public HandlerFunction getHandlerFunction() { - return handlerFunction; - } - - public List> getFilters() { - return filters; - } - - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/HandlerFunctions.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/HandlerFunctions.java deleted file mode 100644 index 6863a33c..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/HandlerFunctions.java +++ /dev/null @@ -1,122 +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.handler; - -import java.io.IOException; -import java.lang.reflect.Method; -import java.net.URI; -import java.util.Arrays; -import java.util.Collection; -import java.util.concurrent.atomic.AtomicReference; - -import jakarta.servlet.ServletException; - -import org.springframework.cloud.gateway.server.mvc.common.MvcUtils; -import org.springframework.web.servlet.function.HandlerFunction; -import org.springframework.web.servlet.function.ServerRequest; -import org.springframework.web.servlet.function.ServerResponse; - -public abstract class HandlerFunctions { - - private HandlerFunctions() { - - } - - public static HandlerFunction forward(String path) { - // ok() is wrong, but can be overridden by the forwarded request. - return request -> GatewayServerResponse.ok().build((httpServletRequest, httpServletResponse) -> { - try { - String expandedFallback = MvcUtils.expand(request, path); - request.servletRequest().getServletContext().getRequestDispatcher(expandedFallback) - .forward(httpServletRequest, httpServletResponse); - return null; - } - catch (ServletException | IOException e) { - throw new RuntimeException(e); - } - }); - } - - // TODO: current discovery only goes by method name - // so last one wins, so put parameterless last - public static HandlerFunction http(URI uri) { - return new LookupProxyExchangeHandlerFunction(uri); - } - - public static HandlerFunction https(URI uri) { - return new LookupProxyExchangeHandlerFunction(uri); - } - - public static HandlerFunction https() { - return http(); - } - - public static HandlerFunction http() { - return new LookupProxyExchangeHandlerFunction(); - } - - static class LookupProxyExchangeHandlerFunction implements HandlerFunction { - - private final URI uri; - - private AtomicReference proxyExchangeHandlerFunction = new AtomicReference<>(); - - LookupProxyExchangeHandlerFunction() { - this.uri = null; - } - - LookupProxyExchangeHandlerFunction(URI uri) { - this.uri = uri; - } - - @Override - public ServerResponse handle(ServerRequest serverRequest) { - if (uri != null) { - // TODO: in 2 places now, here and - // GatewayMvcPropertiesBeanDefinitionRegistrar - serverRequest.attributes().put(MvcUtils.GATEWAY_REQUEST_URL_ATTR, uri); - } - this.proxyExchangeHandlerFunction.compareAndSet(null, lookup(serverRequest)); - return proxyExchangeHandlerFunction.get().handle(serverRequest); - } - - private static ProxyExchangeHandlerFunction lookup(ServerRequest request) { - return MvcUtils.getApplicationContext(request).getBean(ProxyExchangeHandlerFunction.class); - } - - @Override - public String toString() { - ProxyExchangeHandlerFunction handlerFunction = this.proxyExchangeHandlerFunction.get(); - if (handlerFunction != null) { - return handlerFunction.toString(); - } - return ProxyExchangeHandlerFunction.class.getSimpleName(); - } - - } - - public static class HandlerSupplier - implements org.springframework.cloud.gateway.server.mvc.handler.HandlerSupplier { - - @Override - public Collection get() { - return Arrays.asList(HandlerFunctions.class.getMethods()); - } - - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/HandlerSupplier.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/HandlerSupplier.java deleted file mode 100644 index 2d7ac79b..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/HandlerSupplier.java +++ /dev/null @@ -1,25 +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.handler; - -import java.lang.reflect.Method; -import java.util.Collection; -import java.util.function.Supplier; - -public interface HandlerSupplier extends Supplier> { - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/ProxyExchange.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/ProxyExchange.java deleted file mode 100644 index 7b81ab28..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/ProxyExchange.java +++ /dev/null @@ -1,149 +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.handler; - -import java.net.URI; -import java.util.ArrayList; -import java.util.Collection; -import java.util.function.BiConsumer; - -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMessage; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatusCode; -import org.springframework.web.servlet.function.ServerRequest; -import org.springframework.web.servlet.function.ServerResponse; - -public interface ProxyExchange { - - default RequestBuilder request(ServerRequest serverRequest) { - return new DefaultRequestBuilder(serverRequest); - } - - ServerResponse exchange(Request request); - - interface Request extends HttpMessage { - - HttpMethod getMethod(); - - URI getUri(); - - ServerRequest getServerRequest(); - - Collection getResponseConsumers(); - - } - - interface Response extends HttpMessage { - - HttpStatusCode getStatusCode(); - - } - - interface ResponseConsumer extends BiConsumer { - - } - - interface RequestBuilder { - - RequestBuilder headers(HttpHeaders httpHeaders); - - RequestBuilder method(HttpMethod method); - - RequestBuilder uri(URI uri); - - RequestBuilder responseConsumer(ResponseConsumer responseConsumer); - - Request build(); - - } - - class DefaultRequestBuilder implements RequestBuilder, Request { - - final ServerRequest serverRequest; - - private HttpHeaders httpHeaders; - - private HttpMethod method; - - private URI uri; - - private ArrayList responseConsumers = new ArrayList<>(); - - DefaultRequestBuilder(ServerRequest serverRequest) { - this.serverRequest = serverRequest; - this.method = serverRequest.method(); - } - - @Override - public RequestBuilder headers(HttpHeaders httpHeaders) { - this.httpHeaders = httpHeaders; - return this; - } - - @Override - public RequestBuilder method(HttpMethod method) { - this.method = method; - return this; - } - - @Override - public RequestBuilder uri(URI uri) { - this.uri = uri; - return this; - } - - @Override - public RequestBuilder responseConsumer(ResponseConsumer responseConsumer) { - responseConsumers.add(responseConsumer); - return this; - } - - @Override - public Request build() { - // TODO: validation - return this; - } - - @Override - public HttpHeaders getHeaders() { - return httpHeaders; - } - - @Override - public HttpMethod getMethod() { - return method; - } - - @Override - public URI getUri() { - return uri; - } - - @Override - public ServerRequest getServerRequest() { - return serverRequest; - } - - @Override - public Collection getResponseConsumers() { - return responseConsumers; - } - - } - -} 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 deleted file mode 100644 index e28bad1a..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/ProxyExchangeHandlerFunction.java +++ /dev/null @@ -1,143 +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.handler; - -import java.net.URI; -import java.util.function.Function; -import java.util.stream.Stream; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.cloud.gateway.server.mvc.common.MvcUtils; -import org.springframework.cloud.gateway.server.mvc.filter.HttpHeadersFilter; -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.web.servlet.function.HandlerFunction; -import org.springframework.web.servlet.function.ServerRequest; -import org.springframework.web.servlet.function.ServerResponse; -import org.springframework.web.util.UriComponentsBuilder; - -public class ProxyExchangeHandlerFunction implements HandlerFunction { - - private static final Log log = LogFactory.getLog(ProxyExchangeHandlerFunction.class); - - private final ProxyExchange proxyExchange; - - private final ObjectProvider requestHttpHeadersFilters; - - private final ObjectProvider responseHttpHeadersFilters; - - private final URIResolver uriResolver; - - public ProxyExchangeHandlerFunction(ProxyExchange proxyExchange, - ObjectProvider requestHttpHeadersFilters, - ObjectProvider responseHttpHeadersFilters) { - this(proxyExchange, requestHttpHeadersFilters, responseHttpHeadersFilters, - request -> (URI) request.attribute(MvcUtils.GATEWAY_REQUEST_URL_ATTR) - .orElseThrow(() -> new IllegalStateException("No routeUri resolved"))); - } - - public ProxyExchangeHandlerFunction(ProxyExchange proxyExchange, - ObjectProvider requestHttpHeadersFilters, - ObjectProvider responseHttpHeadersFilters, URIResolver uriResolver) { - this.proxyExchange = proxyExchange; - this.requestHttpHeadersFilters = requestHttpHeadersFilters; - this.responseHttpHeadersFilters = responseHttpHeadersFilters; - this.uriResolver = uriResolver; - } - - @Override - public ServerResponse handle(ServerRequest serverRequest) { - URI uri = uriResolver.apply(serverRequest); - boolean encoded = containsEncodedQuery(serverRequest.uri()); - // @formatter:off - URI url = UriComponentsBuilder.fromUri(serverRequest.uri()) - // .uri(routeUri) - .scheme(uri.getScheme()) - .host(uri.getHost()) - .port(uri.getPort()) - .queryParams(serverRequest.params()) - .build(encoded) - .toUri(); - // @formatter:on - - // TODO: Streams.collect()? - HttpHeaders filteredRequestHeaders = filterHeaders( - this.requestHttpHeadersFilters.orderedStream().map(Function.identity()), - serverRequest.headers().asHttpHeaders(), serverRequest); - - boolean preserveHost = (boolean) serverRequest.attributes() - .getOrDefault(MvcUtils.PRESERVE_HOST_HEADER_ATTRIBUTE, false); - if (preserveHost) { - filteredRequestHeaders.set(HttpHeaders.HOST, serverRequest.headers().firstHeader(HttpHeaders.HOST)); - } - else { - filteredRequestHeaders.remove(HttpHeaders.HOST); - } - - // @formatter:off - ProxyExchange.Request proxyRequest = proxyExchange.request(serverRequest).uri(url) - .headers(filteredRequestHeaders) - // TODO: allow injection of ResponseConsumer - .responseConsumer((response, serverResponse) -> { - HttpHeaders httpHeaders = filterHeaders(this.responseHttpHeadersFilters.orderedStream() - .map(Function.identity()), response.getHeaders(), serverResponse); - serverResponse.headers().putAll(httpHeaders); - }) - .build(); - // @formatter:on - return proxyExchange.exchange(proxyRequest); - } - - private HttpHeaders filterHeaders(Stream> filters, HttpHeaders original, TYPE type) { - HttpHeaders filtered = original; - for (var filter : filters.toList()) { - filtered = filter.apply(filtered, type); - } - return filtered; - } - - private static boolean containsEncodedQuery(URI uri) { - boolean encoded = (uri.getRawQuery() != null && uri.getRawQuery().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).build(true); - return true; - } - catch (IllegalArgumentException ignored) { - if (log.isTraceEnabled()) { - log.trace("Error in containsEncodedParts", ignored); - } - } - - return false; - } - - return encoded; - } - - 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 deleted file mode 100644 index 1f249924..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/RestClientProxyExchange.java +++ /dev/null @@ -1,62 +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.handler; - -import java.io.IOException; -import java.io.OutputStream; - -import org.springframework.http.client.ClientHttpResponse; -import org.springframework.util.StreamUtils; -import org.springframework.web.client.RestClient; -import org.springframework.web.servlet.function.ServerResponse; - -public class RestClientProxyExchange implements ProxyExchange { - - private final RestClient restClient; - - public RestClientProxyExchange(RestClient restClient) { - this.restClient = restClient; - } - - @Override - public ServerResponse exchange(Request request) { - return restClient.method(request.getMethod()).uri(request.getUri()) - .headers(httpHeaders -> httpHeaders.putAll(request.getHeaders())) - .body(outputStream -> copyBody(request, outputStream)) - .exchange((clientRequest, clientResponse) -> doExchange(request, clientResponse), false); - } - - private static int copyBody(Request request, OutputStream outputStream) throws IOException { - return StreamUtils.copy(request.getServerRequest().servletRequest().getInputStream(), outputStream); - } - - private static ServerResponse doExchange(Request request, ClientHttpResponse clientResponse) throws IOException { - ServerResponse serverResponse = GatewayServerResponse.status(clientResponse.getStatusCode()) - .build((req, httpServletResponse) -> { - try (clientResponse) { - // copy body from request to clientHttpRequest - StreamUtils.copy(clientResponse.getBody(), httpServletResponse.getOutputStream()); - } - return null; - }); - ClientHttpResponseAdapter proxyExchangeResponse = new ClientHttpResponseAdapter(clientResponse); - request.getResponseConsumers() - .forEach(responseConsumer -> responseConsumer.accept(proxyExchangeResponse, serverResponse)); - return serverResponse; - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/invoke/InvocationContext.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/invoke/InvocationContext.java deleted file mode 100644 index 2f12edda..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/invoke/InvocationContext.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright 2012-2022 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.invoke; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; - -import org.springframework.util.Assert; - -/** - * The context for the {@link OperationInvoker invocation of an operation}. - * - * @author Andy Wilkinson - * @author Phillip Webb - * @since 2.0.0 - */ -public class InvocationContext { - - private final Map arguments; - - private final List argumentResolvers; - - /** - * Creates a new context for an operation being invoked by the given - * {@code securityContext} with the given available {@code arguments}. - * @param arguments the arguments available to the operation. Never {@code null} - * @param argumentResolvers resolvers for additional arguments should be available to - * the operation. - */ - public InvocationContext(Map arguments, OperationArgumentResolver... argumentResolvers) { - Assert.notNull(arguments, "Arguments must not be null"); - this.arguments = arguments; - this.argumentResolvers = new ArrayList<>(); - if (argumentResolvers != null) { - this.argumentResolvers.addAll(Arrays.asList(argumentResolvers)); - } - } - - /** - * Return the invocation arguments. - * @return the arguments - */ - public Map getArguments() { - return this.arguments; - } - - /** - * Resolves an argument with the given {@code argumentType}. - * @param type of the argument - * @param argumentType type of the argument - * @return resolved argument of the required type or {@code null} - * @since 2.5.0 - * @see #canResolve(Class) - */ - public T resolveArgument(Class argumentType) { - for (OperationArgumentResolver argumentResolver : this.argumentResolvers) { - if (argumentResolver.canResolve(argumentType)) { - T result = argumentResolver.resolve(argumentType); - if (result != null) { - return result; - } - } - } - return null; - } - - /** - * Returns whether the context is capable of resolving an argument of the given - * {@code type}. Note that, even when {@code true} is returned, - * {@link #resolveArgument argument resolution} will return {@code null} if no - * argument of the required type is available. - * @param type argument type - * @return {@code true} if resolution of arguments of the given type is possible, - * otherwise {@code false}. - * @since 2.5.0 - * @see #resolveArgument(Class) - */ - public boolean canResolve(Class type) { - for (OperationArgumentResolver argumentResolver : this.argumentResolvers) { - if (argumentResolver.canResolve(type)) { - return true; - } - } - return false; - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/invoke/MissingParametersException.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/invoke/MissingParametersException.java deleted file mode 100644 index acae0fd1..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/invoke/MissingParametersException.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2012-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.server.mvc.invoke; - -import java.util.Set; -import java.util.stream.Collectors; - -import org.springframework.util.StringUtils; - -/** - * {@link RuntimeException} thrown when an endpoint invocation does not contain required - * parameters. - * - * @author Madhura Bhave - * @author Phillip Webb - * @since 2.0.0 - */ -public final class MissingParametersException extends RuntimeException { - - private final Set missingParameters; - - private final String reason; - - public MissingParametersException(Set missingParameters) { - super("Failed to invoke operation because the following required parameters were missing: " - + StringUtils.collectionToCommaDelimitedString(missingParameters)); - this.reason = "Missing parameters: " - + missingParameters.stream().map(OperationParameter::getName).collect(Collectors.joining(",")); - this.missingParameters = missingParameters; - } - - /** - * Returns the parameters that were missing. - * @return the parameters - */ - public Set getMissingParameters() { - return this.missingParameters; - } - - public String getReason() { - return reason; - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/invoke/OperationArgumentResolver.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/invoke/OperationArgumentResolver.java deleted file mode 100644 index 822f7175..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/invoke/OperationArgumentResolver.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2012-2021 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.invoke; - -import java.util.function.Supplier; - -import org.springframework.util.Assert; - -/** - * Resolver for an argument of an {@link Operation}. - * - * @author Andy Wilkinson - * @since 2.5.0 - */ -public interface OperationArgumentResolver { - - /** - * Return whether an argument of the given {@code type} can be resolved. - * @param type argument type - * @return {@code true} if an argument of the required type can be resolved, otherwise - * {@code false} - */ - boolean canResolve(Class type); - - /** - * Resolves an argument of the given {@code type}. - * @param required type of the argument - * @param type argument type - * @return an argument of the required type, or {@code null} - */ - T resolve(Class type); - - /** - * Factory method that creates an {@link OperationArgumentResolver} for a specific - * type using a {@link Supplier}. - * @param the resolvable type - * @param type the resolvable type - * @param supplier the value supplier - * @return an {@link OperationArgumentResolver} instance - */ - static OperationArgumentResolver of(Class type, Supplier supplier) { - Assert.notNull(type, "Type must not be null"); - Assert.notNull(supplier, "Supplier must not be null"); - return new OperationArgumentResolver() { - - @Override - public boolean canResolve(Class actualType) { - return actualType.equals(type); - } - - @Override - @SuppressWarnings("unchecked") - public R resolve(Class argumentType) { - return (R) supplier.get(); - } - - }; - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/invoke/OperationInvoker.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/invoke/OperationInvoker.java deleted file mode 100644 index 540c3bda..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/invoke/OperationInvoker.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2012-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.server.mvc.invoke; - -/** - * Interface to perform an operation invocation. - * - * @author Andy Wilkinson - * @author Phillip Webb - * @since 2.0.0 - */ -@FunctionalInterface -public interface OperationInvoker { - - /** - * Invoke the underlying operation using the given {@code context}. - * @param context the context to use to invoke the operation - * @return the result of the operation, may be {@code null} - * @throws MissingParametersException if parameters are missing - */ - T invoke(InvocationContext context) throws MissingParametersException; - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/invoke/OperationInvokerAdvisor.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/invoke/OperationInvokerAdvisor.java deleted file mode 100644 index fbf41933..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/invoke/OperationInvokerAdvisor.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2012-2021 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.invoke; - -//import org.springframework.boot.actuate.endpoint.EndpointId; -//import org.springframework.boot.actuate.endpoint.OperationType; - -/** - * Allows additional functionality to be applied to an {@link OperationInvoker}. - * - * @author Phillip Webb - * @since 2.0.0 - */ -// @FunctionalInterface -public interface OperationInvokerAdvisor { - - /** - * Apply additional functionality to the given invoker. - * @param endpointId the endpoint ID - * @param operationType the operation type - * @param parameters the operation parameters - * @param invoker the invoker to advise - * @return a potentially new operation invoker with support for additional features - */ - // OperationInvoker apply(EndpointId endpointId, OperationType operationType, - // OperationParameters parameters, - // OperationInvoker invoker); - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/invoke/OperationParameter.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/invoke/OperationParameter.java deleted file mode 100644 index b39619ac..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/invoke/OperationParameter.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2012-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.invoke; - -import java.lang.annotation.Annotation; - -/** - * A single operation parameter. - * - * @author Phillip Webb - * @author Moritz Halbritter - * @since 2.0.0 - */ -public interface OperationParameter { - - /** - * Returns the parameter name. - * @return the name - */ - String getName(); - - /** - * Returns the parameter type. - * @return the type - */ - Class getType(); - - /** - * Return if the parameter is mandatory (does not accept null values). - * @return if the parameter is mandatory - */ - boolean isMandatory(); - - /** - * Returns this element's annotation for the specified type if such an annotation is - * present, else null. - * @param annotation class of the annotation - * @return annotation value - * @param type of the annotation - * @since 2.7.8 - */ - T getAnnotation(Class annotation); - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/invoke/OperationParameters.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/invoke/OperationParameters.java deleted file mode 100644 index eb4484b2..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/invoke/OperationParameters.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2012-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.server.mvc.invoke; - -import java.util.stream.Stream; - -/** - * A collection of {@link OperationParameter operation parameters}. - * - * @author Phillip Webb - * @since 2.0.0 - */ -public interface OperationParameters extends Iterable { - - /** - * Return {@code true} if there is at least one parameter. - * @return if there are parameters - */ - default boolean hasParameters() { - return getParameterCount() > 0; - } - - /** - * Return the total number of parameters. - * @return the total number of parameters - */ - int getParameterCount(); - - /** - * Return if any of the contained parameters are - * {@link OperationParameter#isMandatory() mandatory}. - * @return if any parameters are mandatory - */ - default boolean hasMandatoryParameter() { - return stream().anyMatch(OperationParameter::isMandatory); - } - - /** - * Return the parameter at the specified index. - * @param index the parameter index - * @return the parameter - */ - OperationParameter get(int index); - - /** - * Return a stream of the contained parameters. - * @return a stream of the parameters - */ - Stream stream(); - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/invoke/ParameterMappingException.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/invoke/ParameterMappingException.java deleted file mode 100644 index 05619c86..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/invoke/ParameterMappingException.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2012-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.server.mvc.invoke; - -/** - * A {@code ParameterMappingException} is thrown when a failure occurs during - * {@link ParameterValueMapper#mapParameterValue operation parameter mapping}. - * - * @author Andy Wilkinson - * @since 2.0.0 - */ -public final class ParameterMappingException extends RuntimeException { - - private final OperationParameter parameter; - - private final String reason = "Parameter mapping failure"; - - private final Object value; - - /** - * Creates a new {@code ParameterMappingException} for a failure that occurred when - * trying to map the given {@code input} to the given {@code type}. - * @param parameter the parameter being mapping - * @param value the value being mapped - * @param cause the cause of the mapping failure - */ - public ParameterMappingException(OperationParameter parameter, Object value, Throwable cause) { - super("Failed to map " + value + " of type " + value.getClass() + " to " + parameter, cause); - this.parameter = parameter; - this.value = value; - } - - /** - * Return the parameter being mapped. - * @return the parameter - */ - public OperationParameter getParameter() { - return this.parameter; - } - - /** - * Return the value being mapped. - * @return the value - */ - public Object getValue() { - return this.value; - } - - public String getReason() { - return reason; - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/invoke/ParameterValueMapper.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/invoke/ParameterValueMapper.java deleted file mode 100644 index a4a9bc23..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/invoke/ParameterValueMapper.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2012-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.server.mvc.invoke; - -/** - * Maps parameter values to the required type when invoking an endpoint. - * - * @author Stephane Nicoll - * @since 2.0.0 - */ -@FunctionalInterface -public interface ParameterValueMapper { - - /** - * A {@link ParameterValueMapper} that does nothing. - */ - ParameterValueMapper NONE = (parameter, value) -> value; - - /** - * Map the specified {@code input} parameter to the given {@code parameterType}. - * @param parameter the parameter to map - * @param value a parameter value - * @return a value suitable for that parameter - * @throws ParameterMappingException when a mapping failure occurs - */ - Object mapParameterValue(OperationParameter parameter, Object value) throws ParameterMappingException; - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/invoke/convert/ConversionServiceParameterValueMapper.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/invoke/convert/ConversionServiceParameterValueMapper.java deleted file mode 100644 index 0e2a71d6..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/invoke/convert/ConversionServiceParameterValueMapper.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2012-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.server.mvc.invoke.convert; - -import org.springframework.boot.convert.ApplicationConversionService; -import org.springframework.cloud.gateway.server.mvc.invoke.OperationParameter; -import org.springframework.cloud.gateway.server.mvc.invoke.ParameterMappingException; -import org.springframework.cloud.gateway.server.mvc.invoke.ParameterValueMapper; -import org.springframework.core.convert.ConversionService; -import org.springframework.util.Assert; - -/** - * {@link ParameterValueMapper} backed by a {@link ConversionService}. - * - * @author Stephane Nicoll - * @author Phillip Webb - * @since 2.0.0 - */ -public class ConversionServiceParameterValueMapper implements ParameterValueMapper { - - private final ConversionService conversionService; - - /** - * Create a new {@link ConversionServiceParameterValueMapper} instance. - */ - public ConversionServiceParameterValueMapper() { - this(ApplicationConversionService.getSharedInstance()); - } - - /** - * Create a new {@link ConversionServiceParameterValueMapper} instance backed by a - * specific conversion service. - * @param conversionService the conversion service - */ - public ConversionServiceParameterValueMapper(ConversionService conversionService) { - Assert.notNull(conversionService, "ConversionService must not be null"); - this.conversionService = conversionService; - } - - @Override - public Object mapParameterValue(OperationParameter parameter, Object value) throws ParameterMappingException { - try { - return this.conversionService.convert(value, parameter.getType()); - } - catch (Exception ex) { - throw new ParameterMappingException(parameter, value, ex); - } - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/invoke/convert/IsoOffsetDateTimeConverter.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/invoke/convert/IsoOffsetDateTimeConverter.java deleted file mode 100644 index ea0d589a..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/invoke/convert/IsoOffsetDateTimeConverter.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2012-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.boot.actuate.endpoint.invoke.convert; - -import java.time.OffsetDateTime; -import java.time.format.DateTimeFormatter; - -import org.springframework.core.convert.converter.Converter; -import org.springframework.core.convert.converter.ConverterRegistry; -import org.springframework.util.StringUtils; - -/** - * A {@link String} to {@link OffsetDateTime} {@link Converter} that uses - * {@link DateTimeFormatter#ISO_OFFSET_DATE_TIME ISO offset} parsing. - * - * @author Andy Wilkinson - * @author Stephane Nicoll - * @since 2.0.0 - */ -public class IsoOffsetDateTimeConverter implements Converter { - - @Override - public OffsetDateTime convert(String source) { - if (StringUtils.hasLength(source)) { - return OffsetDateTime.parse(source, DateTimeFormatter.ISO_OFFSET_DATE_TIME); - } - return null; - } - - public static void registerConverter(ConverterRegistry registry) { - registry.addConverter(new IsoOffsetDateTimeConverter()); - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/invoke/reflect/DefaultOperationMethod.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/invoke/reflect/DefaultOperationMethod.java deleted file mode 100644 index 811c7ca5..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/invoke/reflect/DefaultOperationMethod.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2012-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.server.mvc.invoke.reflect; - -import java.lang.reflect.Method; - -import org.springframework.cloud.gateway.server.mvc.invoke.OperationParameters; -import org.springframework.core.DefaultParameterNameDiscoverer; -import org.springframework.core.ParameterNameDiscoverer; -import org.springframework.util.Assert; - -/** - * Information describing an operation method on an endpoint method. - * - * @author Phillip Webb - * @since 2.0.0 - * @see ReflectiveOperationInvoker - */ -public class DefaultOperationMethod implements OperationMethod { - - private static final ParameterNameDiscoverer DEFAULT_PARAMETER_NAME_DISCOVERER = new DefaultParameterNameDiscoverer(); - - private final Method method; - - private final OperationParameters operationParameters; - - /** - * Create a new {@link DefaultOperationMethod} instance. - * @param method the source method - */ - public DefaultOperationMethod(Method method) { - Assert.notNull(method, "Method must not be null"); - this.method = method; - this.operationParameters = new OperationMethodParameters(method, DEFAULT_PARAMETER_NAME_DISCOVERER); - } - - /** - * Return the source Java method. - * @return the method - */ - @Override - public Method getMethod() { - return this.method; - } - - /** - * Return the operation parameters. - * @return the operation parameters - */ - @Override - public OperationParameters getParameters() { - return this.operationParameters; - } - - @Override - public String toString() { - return "Operation method " + this.method; - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/invoke/reflect/OperationMethod.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/invoke/reflect/OperationMethod.java deleted file mode 100644 index 344993fa..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/invoke/reflect/OperationMethod.java +++ /dev/null @@ -1,29 +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.invoke.reflect; - -import java.lang.reflect.Method; - -import org.springframework.cloud.gateway.server.mvc.invoke.OperationParameters; - -public interface OperationMethod { - - Method getMethod(); - - OperationParameters getParameters(); - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/invoke/reflect/OperationMethodParameter.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/invoke/reflect/OperationMethodParameter.java deleted file mode 100644 index 0c136f66..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/invoke/reflect/OperationMethodParameter.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright 2012-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.invoke.reflect; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Parameter; - -import jakarta.annotation.Nonnull; - -import org.springframework.cloud.gateway.server.mvc.invoke.OperationParameter; -import org.springframework.core.annotation.MergedAnnotation; -import org.springframework.core.annotation.MergedAnnotations; -import org.springframework.lang.Nullable; -import org.springframework.util.ClassUtils; -import org.springframework.util.ObjectUtils; - -/** - * {@link OperationParameter} created from an {@link OperationMethod}. - * - * @author Phillip Webb - * @author Moritz Halbritter - */ -class OperationMethodParameter implements OperationParameter { - - private static final boolean jsr305Present = ClassUtils.isPresent("jakarta.annotation.Nonnull", null); - - private final String name; - - private final Parameter parameter; - - /** - * Create a new {@link OperationMethodParameter} instance. - * @param name the parameter name - * @param parameter the parameter - */ - OperationMethodParameter(String name, Parameter parameter) { - this.name = name; - this.parameter = parameter; - } - - @Override - public String getName() { - return this.name; - } - - @Override - public Class getType() { - return this.parameter.getType(); - } - - @Override - public boolean isMandatory() { - if (!ObjectUtils.isEmpty(this.parameter.getAnnotationsByType(Nullable.class))) { - return false; - } - return (jsr305Present) ? new Jsr305().isMandatory(this.parameter) : true; - } - - @Override - public T getAnnotation(Class annotation) { - return this.parameter.getAnnotation(annotation); - } - - @Override - public String toString() { - return this.name + " of type " + this.parameter.getType().getName(); - } - - private static class Jsr305 { - - boolean isMandatory(Parameter parameter) { - MergedAnnotation annotation = MergedAnnotations.from(parameter).get(Nonnull.class); - return !annotation.isPresent()/* - * || annotation.getEnum("when", When.class) - * == When.ALWAYS - */; - } - - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/invoke/reflect/OperationMethodParameters.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/invoke/reflect/OperationMethodParameters.java deleted file mode 100644 index c0d776dd..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/invoke/reflect/OperationMethodParameters.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2012-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.server.mvc.invoke.reflect; - -import java.lang.reflect.Method; -import java.lang.reflect.Parameter; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import java.util.stream.Stream; - -import org.springframework.cloud.gateway.server.mvc.invoke.OperationParameter; -import org.springframework.cloud.gateway.server.mvc.invoke.OperationParameters; -import org.springframework.core.ParameterNameDiscoverer; -import org.springframework.util.Assert; - -/** - * {@link OperationParameters} created from an {@link OperationMethod}. - * - * @author Phillip Webb - */ -class OperationMethodParameters implements OperationParameters { - - private final List operationParameters; - - /** - * Create a new {@link OperationMethodParameters} instance. - * @param method the source method - * @param parameterNameDiscoverer the parameter name discoverer - */ - OperationMethodParameters(Method method, ParameterNameDiscoverer parameterNameDiscoverer) { - Assert.notNull(method, "Method must not be null"); - Assert.notNull(parameterNameDiscoverer, "ParameterNameDiscoverer must not be null"); - String[] parameterNames = parameterNameDiscoverer.getParameterNames(method); - Parameter[] parameters = method.getParameters(); - Assert.state(parameterNames != null, () -> "Failed to extract parameter names for " + method); - this.operationParameters = getOperationParameters(parameters, parameterNames); - } - - private List getOperationParameters(Parameter[] parameters, String[] names) { - List operationParameters = new ArrayList<>(parameters.length); - for (int i = 0; i < names.length; i++) { - operationParameters.add(new OperationMethodParameter(names[i], parameters[i])); - } - return Collections.unmodifiableList(operationParameters); - } - - @Override - public int getParameterCount() { - return this.operationParameters.size(); - } - - @Override - public OperationParameter get(int index) { - return this.operationParameters.get(index); - } - - @Override - public Iterator iterator() { - return this.operationParameters.iterator(); - } - - @Override - public Stream stream() { - return this.operationParameters.stream(); - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/invoke/reflect/ReflectiveOperationInvoker.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/invoke/reflect/ReflectiveOperationInvoker.java deleted file mode 100644 index a1b921c2..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/invoke/reflect/ReflectiveOperationInvoker.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright 2012-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.invoke.reflect; - -import java.lang.reflect.Method; -import java.util.Set; -import java.util.stream.Collectors; - -import org.springframework.cloud.gateway.server.mvc.invoke.InvocationContext; -import org.springframework.cloud.gateway.server.mvc.invoke.MissingParametersException; -import org.springframework.cloud.gateway.server.mvc.invoke.OperationInvoker; -import org.springframework.cloud.gateway.server.mvc.invoke.OperationParameter; -import org.springframework.cloud.gateway.server.mvc.invoke.ParameterValueMapper; -import org.springframework.core.style.ToStringCreator; -import org.springframework.util.Assert; -import org.springframework.util.ReflectionUtils; - -/** - * An {@code OperationInvoker} that invokes an operation using reflection. - * - * @author Andy Wilkinson - * @author Stephane Nicoll - * @author Phillip Webb - * @since 2.0.0 - */ -public class ReflectiveOperationInvoker implements OperationInvoker { - - private final Object target; - - private final OperationMethod operationMethod; - - private final ParameterValueMapper parameterValueMapper; - - /** - * Creates a new {@code ReflectiveOperationInvoker} that will invoke the given static - * {@code method}. The given {@code parameterMapper} will be used to map parameters to - * the required types and the given {@code parameterNameMapper} will be used map - * parameters by name. - * @param operationMethod the method info - * @param parameterValueMapper the parameter mapper - */ - public ReflectiveOperationInvoker(OperationMethod operationMethod, ParameterValueMapper parameterValueMapper) { - this(null, operationMethod, parameterValueMapper); - } - - /** - * Creates a new {@code ReflectiveOperationInvoker} that will invoke the given - * {@code method} on the given {@code target}. The given {@code parameterMapper} will - * be used to map parameters to the required types and the given - * {@code parameterNameMapper} will be used map parameters by name. - * @param target the target of the reflective call - * @param operationMethod the method info - * @param parameterValueMapper the parameter mapper - */ - public ReflectiveOperationInvoker(Object target, OperationMethod operationMethod, - ParameterValueMapper parameterValueMapper) { - // Assert.notNull(target, "Target must not be null"); - Assert.notNull(operationMethod, "OperationMethod must not be null"); - Assert.notNull(parameterValueMapper, "ParameterValueMapper must not be null"); - ReflectionUtils.makeAccessible(operationMethod.getMethod()); - this.target = target; - this.operationMethod = operationMethod; - this.parameterValueMapper = parameterValueMapper; - } - - @Override - @SuppressWarnings("unchecked") - public T invoke(InvocationContext context) { - validateRequiredParameters(context); - Method method = this.operationMethod.getMethod(); - Object[] resolvedArguments = resolveArguments(context); - ReflectionUtils.makeAccessible(method); - return (T) ReflectionUtils.invokeMethod(method, this.target, resolvedArguments); - } - - private void validateRequiredParameters(InvocationContext context) { - Set missing = this.operationMethod.getParameters().stream() - .filter((parameter) -> isMissing(context, parameter)).collect(Collectors.toSet()); - if (!missing.isEmpty()) { - throw new MissingParametersException(missing); - } - } - - private boolean isMissing(InvocationContext context, OperationParameter parameter) { - if (!parameter.isMandatory()) { - return false; - } - if (context.canResolve(parameter.getType())) { - return false; - } - return context.getArguments().get(parameter.getName()) == null; - } - - private Object[] resolveArguments(InvocationContext context) { - return this.operationMethod.getParameters().stream().map((parameter) -> resolveArgument(parameter, context)) - .toArray(); - } - - private Object resolveArgument(OperationParameter parameter, InvocationContext context) { - Object resolvedByType = context.resolveArgument(parameter.getType()); - if (resolvedByType != null) { - return resolvedByType; - } - Object value = context.getArguments().get(parameter.getName()); - return this.parameterValueMapper.mapParameterValue(parameter, value); - } - - @Override - public String toString() { - return new ToStringCreator(this).append("target", this.target).append("method", this.operationMethod) - .toString(); - } - -} 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 deleted file mode 100644 index 989a28f8..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/predicate/GatewayRequestPredicates.java +++ /dev/null @@ -1,539 +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.predicate; - -import java.lang.reflect.Method; -import java.time.ZonedDateTime; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.function.Function; -import java.util.function.Predicate; -import java.util.regex.Pattern; - -import jakarta.servlet.http.Cookie; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.cloud.gateway.server.mvc.common.ArgumentSupplier; -import org.springframework.cloud.gateway.server.mvc.common.DefaultArgumentSuppliedEvent; -import org.springframework.cloud.gateway.server.mvc.common.MvcUtils; -import org.springframework.cloud.gateway.server.mvc.common.Shortcut; -import org.springframework.cloud.gateway.server.mvc.common.WeightConfig; -import org.springframework.core.io.Resource; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.server.PathContainer; -import org.springframework.lang.Nullable; -import org.springframework.util.Assert; -import org.springframework.util.ObjectUtils; -import org.springframework.util.StringUtils; -import org.springframework.web.cors.CorsUtils; -import org.springframework.web.servlet.function.HandlerFunction; -import org.springframework.web.servlet.function.RequestPredicate; -import org.springframework.web.servlet.function.RequestPredicates; -import org.springframework.web.servlet.function.RouterFunction; -import org.springframework.web.servlet.function.RouterFunctions; -import org.springframework.web.servlet.function.ServerRequest; -import org.springframework.web.util.pattern.PathPattern; -import org.springframework.web.util.pattern.PathPatternParser; - -import static org.springframework.cloud.gateway.server.mvc.common.MvcUtils.GATEWAY_ROUTE_ID_ATTR; -import static org.springframework.cloud.gateway.server.mvc.common.MvcUtils.WEIGHT_ATTR; -import static org.springframework.cloud.gateway.server.mvc.common.MvcUtils.cacheAndReadBody; -import static org.springframework.cloud.gateway.server.mvc.common.MvcUtils.getAttribute; -import static org.springframework.cloud.gateway.server.mvc.common.MvcUtils.putAttribute; - -public abstract class GatewayRequestPredicates { - - private static final Log log = LogFactory.getLog(GatewayRequestPredicates.class); - - private static final String X_CF_FORWARDED_URL = "X-CF-Forwarded-Url"; - - private static final String X_CF_PROXY_SIGNATURE = "X-CF-Proxy-Signature"; - - private static final String X_CF_PROXY_METADATA = "X-CF-Proxy-Metadata"; - - private static final PathPatternParser DEFAULT_HOST_INSTANCE = new HostReadOnlyPathPatternParser(); - - private static final String READ_BODY_CACHE_OBJECT_KEY = "cachedRequestBodyObject"; - - private GatewayRequestPredicates() { - } - - @Shortcut - public static RequestPredicate after(ZonedDateTime dateTime) { - return request -> ZonedDateTime.now().isAfter(dateTime); - } - - @Shortcut - public static RequestPredicate before(ZonedDateTime dateTime) { - return request -> ZonedDateTime.now().isBefore(dateTime); - } - - // TODO: accept and test datetime predicates (including yaml config) - @Shortcut - public static RequestPredicate between(ZonedDateTime dateTime1, ZonedDateTime dateTime2) { - return request -> { - ZonedDateTime now = ZonedDateTime.now(); - return now.isAfter(dateTime1) && now.isBefore(dateTime2); - }; - } - - public static RequestPredicate cloudFoundryRouteService() { - return header(X_CF_FORWARDED_URL).and(header(X_CF_PROXY_METADATA)).and(header(X_CF_PROXY_SIGNATURE)); - } - - public static RequestPredicate cookie(String name) { - return cookie(name, null); - } - - @Shortcut - public static RequestPredicate cookie(String name, String regexp) { - return new CookieRequestPredicate(name, regexp); - } - - public static RequestPredicate header(String header) { - return header(header, null); - } - - @Shortcut - public static RequestPredicate header(String header, String regexp) { - return new HeaderRequestPredicate(header, regexp); - } - - // TODO: implement parameter aliases for predicates in RequestPredicates for webflux - // compatibility? - @Shortcut(type = Shortcut.Type.LIST) - public static RequestPredicate method(HttpMethod... methods) { - return RequestPredicates.methods(methods); - } - - @Shortcut - public static RequestPredicate host(String pattern) { - Assert.notNull(pattern, "'pattern' must not be null"); - return hostPredicates(DEFAULT_HOST_INSTANCE).apply(pattern); - } - - /** - * Return a function that creates new host-matching {@code RequestPredicates} from - * pattern Strings using the given {@link PathPatternParser}. - *

- * This method can be used to specify a non-default, customized - * {@code PathPatternParser} when resolving path patterns. - * @param patternParser the parser used to parse patterns given to the returned - * function - * @return a function that resolves a pattern String into a path-matching - * {@code RequestPredicates} instance - */ - public static Function hostPredicates(PathPatternParser patternParser) { - Assert.notNull(patternParser, "PathPatternParser must not be null"); - return pattern -> new HostPatternPredicate(patternParser.parse(pattern)); - } - - /** - * Return a {@code RequestPredicate} that tests the request path against the given - * path pattern. - * @param pattern the pattern to match to - * @return a predicate that tests against the given path pattern - */ - // TODO: find a different way to add shortcut to RequestPredicates.* - @Shortcut - public static RequestPredicate path(String pattern) { - return RequestPredicates.path(pattern); - } - - public static RequestPredicate readBody(Class inClass, Predicate predicate) { - return new ReadBodyPredicate<>(inClass, predicate); - } - - /** - * A predicate which will select a route based on its assigned weight. - * @param group the group the route belongs to - * @param weight the weight for the route - * @return a predicate that tests against the given group and weight. - */ - @Shortcut - public static RequestPredicate weight(String group, int weight) { - return new WeightPredicate(group, weight); - } - - private static void traceMatch(String prefix, Object desired, @Nullable Object actual, boolean match) { - if (log.isTraceEnabled()) { - log.trace(String.format("%s \"%s\" %s against value \"%s\"", prefix, desired, - match ? "matches" : "does not match", actual)); - } - } - - private static class CookieRequestPredicate implements RequestPredicate { - - private final String name; - - private final Pattern pattern; - - CookieRequestPredicate(String name, String regexp) { - this.name = name; - this.pattern = (StringUtils.hasText(regexp)) ? Pattern.compile(regexp) : null; - } - - @Override - public boolean test(ServerRequest request) { - if (CorsUtils.isPreFlightRequest(request.servletRequest())) { - return true; - } - List cookies = request.cookies().get(name); - if (ObjectUtils.isEmpty(cookies)) { - return false; - } - // values is now guaranteed to not be empty - if (pattern != null) { - // check if a header value matches - for (Cookie cookie : cookies) { - if (pattern.asMatchPredicate().test(cookie.getValue())) { - return true; - } - } - return false; - } - - // there is a value and since regexp is empty, we only check existence. - return true; - } - - @Override - public void accept(RequestPredicates.Visitor visitor) { - visitor.header(name, pattern.pattern()); - } - - @Override - public String toString() { - return String.format("Cookie: %s regexp=%s", name, pattern); - } - - } - - private static class HeaderRequestPredicate implements RequestPredicate { - - private final String header; - - private final Pattern pattern; - - HeaderRequestPredicate(String header, String regexp) { - this.header = header; - this.pattern = (StringUtils.hasText(regexp)) ? Pattern.compile(regexp) : null; - } - - @Override - public boolean test(ServerRequest request) { - if (CorsUtils.isPreFlightRequest(request.servletRequest())) { - return true; - } - List values = request.headers().header(header); - if (values.isEmpty()) { - return false; - } - // values is now guaranteed to not be empty - if (pattern != null) { - // check if a header value matches - for (String value : values) { - if (pattern.asMatchPredicate().test(value)) { - return true; - } - } - return false; - } - - // there is a value and since regexp is empty, we only check existence. - return true; - } - - @Override - public void accept(RequestPredicates.Visitor visitor) { - if (pattern != null) { - visitor.header(header, pattern.pattern()); - } - else { - visitor.header(header, ""); - } - } - - @Override - public String toString() { - return String.format("Header: %s regexp=%s", header, pattern); - } - - } - - private static class HostReadOnlyPathPatternParser extends PathPatternParser { - - HostReadOnlyPathPatternParser() { - super.setPathOptions(PathContainer.Options.MESSAGE_ROUTE); - } - - @SuppressWarnings("deprecation") - @Override - public void setMatchOptionalTrailingSeparator(boolean matchOptionalTrailingSeparator) { - raiseError(); - } - - @Override - public void setCaseSensitive(boolean caseSensitive) { - raiseError(); - } - - @Override - public void setPathOptions(PathContainer.Options pathOptions) { - raiseError(); - } - - private void raiseError() { - throw new UnsupportedOperationException("This is a read-only, shared instance that cannot be modified"); - } - - } - - private static class HostPatternPredicate implements RequestPredicate, ChangePathPatternParserVisitor.Target { - - private PathPattern pattern; - - HostPatternPredicate(PathPattern pattern) { - Assert.notNull(pattern, "'pattern' must not be null"); - this.pattern = pattern; - } - - @Override - public boolean test(ServerRequest request) { - String host = request.headers().firstHeader(HttpHeaders.HOST); - PathContainer pathContainer = PathContainer.parsePath(host, PathContainer.Options.MESSAGE_ROUTE); - PathPattern.PathMatchInfo info = this.pattern.matchAndExtract(pathContainer); - traceMatch("Pattern", this.pattern.getPatternString(), host, info != null); - if (info != null) { - MvcUtils.putUriTemplateVariables(request, info.getUriVariables()); - return true; - } - else { - return false; - } - } - - @Override - public Optional nest(ServerRequest request) { - throw new UnsupportedOperationException("nest is not supported"); - // return - // Optional.ofNullable(this.pattern.matchStartOfPath(request.requestPath().pathWithinApplication())) - // .map(info -> new RequestPredicates.SubPathServerRequestWrapper(request, - // info, this.pattern)); - } - - @Override - public void accept(RequestPredicates.Visitor visitor) { - visitor.header(HttpHeaders.HOST, this.pattern.getPatternString()); - } - - @Override - public void changeParser(PathPatternParser parser) { - String patternString = this.pattern.getPatternString(); - this.pattern = parser.parse(patternString); - } - - @Override - public String toString() { - return this.pattern.getPatternString(); - } - - } - - /** - * Implementation of {@link RouterFunctions.Visitor} that changes the - * {@link PathPatternParser} on path-related request predicates (i.e. - * {@code RequestPredicates.PathPatternPredicate}. - * - * @author Arjen Poutsma - * @since 5.3 - */ - private static class ChangePathPatternParserVisitor implements RouterFunctions.Visitor { - - private final PathPatternParser parser; - - ChangePathPatternParserVisitor(PathPatternParser parser) { - Assert.notNull(parser, "Parser must not be null"); - this.parser = parser; - } - - @Override - public void startNested(RequestPredicate predicate) { - changeParser(predicate); - } - - @Override - public void endNested(RequestPredicate predicate) { - } - - @Override - public void route(RequestPredicate predicate, HandlerFunction handlerFunction) { - changeParser(predicate); - } - - @Override - public void resources(Function> lookupFunction) { - } - - @Override - public void attributes(Map attributes) { - } - - @Override - public void unknown(RouterFunction routerFunction) { - } - - private void changeParser(RequestPredicate predicate) { - if (predicate instanceof ChangePathPatternParserVisitor.Target target) { - target.changeParser(this.parser); - } - } - - /** - * Interface implemented by predicates that can change the parser. - */ - public interface Target { - - void changeParser(PathPatternParser parser); - - } - - } - - private static final class ReadBodyPredicate implements RequestPredicate { - - private final Class toRead; - - private final Predicate predicate; - - ReadBodyPredicate(Class toRead, Predicate predicate) { - this.toRead = toRead; - this.predicate = predicate; - } - - @Override - @SuppressWarnings("unchecked") - public boolean test(ServerRequest request) { - try { - Object cachedBody = getAttribute(request, READ_BODY_CACHE_OBJECT_KEY); - - if (cachedBody != null) { - return predicate.test((T) cachedBody); - } - } - catch (ClassCastException e) { - if (log.isDebugEnabled()) { - log.debug("Predicate test failed because class in predicate " - + "does not match the cached body object", e); - } - } - - return cacheAndReadBody(request, toRead).map(body -> { - putAttribute(request, READ_BODY_CACHE_OBJECT_KEY, body); - return predicate.test(body); - }).orElse(false); - } - - @Override - public void accept(RequestPredicates.Visitor visitor) { - visitor.unknown(this); - } - - @Override - public String toString() { - return String.format("ReadBody=%s predicate=%s", toRead.getSimpleName(), predicate); - } - - } - - private static final class WeightPredicate implements RequestPredicate, ArgumentSupplier { - - final String group; - - final int weight; - - private WeightPredicate(String group, int weight) { - this.group = group; - this.weight = weight; - } - - @SuppressWarnings("unchecked") - @Override - public boolean test(ServerRequest request) { - Map weights = (Map) request.attributes().getOrDefault(WEIGHT_ATTR, - Collections.emptyMap()); - - String routeId = (String) request.attributes().get(GATEWAY_ROUTE_ID_ATTR); - if (ObjectUtils.isEmpty(routeId)) { - // no routeId to test against - // TODO: maybe log a warning - return false; - } - - // all calculations and comparison against random num happened in - // WeightCalculatorHandlerInterceptor - if (weights.containsKey(group)) { - - String chosenRoute = weights.get(group); - if (log.isTraceEnabled()) { - log.trace("in group weight: " + group + ", current route: " + routeId + ", chosen route: " - + chosenRoute); - } - - return routeId.equals(chosenRoute); - } - else if (log.isTraceEnabled()) { - log.trace("no weights found for group: " + group + ", current route: " + routeId); - } - - return false; - } - - @Override - public void accept(RequestPredicates.Visitor visitor) { - visitor.unknown(this); - } - - @Override - public ArgumentSuppliedEvent getArgumentSuppliedEvent() { - return new DefaultArgumentSuppliedEvent<>(this, WeightConfig.class, new WeightConfig(null, group, weight)); - } - - @Override - public String toString() { - return String.format("Weight=%d group=%s", weight, group); - } - - } - - public static class PredicateSupplier - implements org.springframework.cloud.gateway.server.mvc.predicate.PredicateSupplier { - - @Override - public Collection get() { - return Arrays.asList(GatewayRequestPredicates.class.getMethods()); - } - - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/predicate/MvcPredicateSupplier.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/predicate/MvcPredicateSupplier.java deleted file mode 100644 index 7b7f80dd..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/predicate/MvcPredicateSupplier.java +++ /dev/null @@ -1,32 +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.predicate; - -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.Collection; - -import org.springframework.web.servlet.function.RequestPredicates; - -public class MvcPredicateSupplier implements PredicateSupplier { - - @Override - public Collection get() { - return Arrays.asList(RequestPredicates.class.getMethods()); - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/predicate/PredicateDiscoverer.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/predicate/PredicateDiscoverer.java deleted file mode 100644 index f7e63e41..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/predicate/PredicateDiscoverer.java +++ /dev/null @@ -1,29 +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.predicate; - -import org.springframework.cloud.gateway.server.mvc.common.AbstractGatewayDiscoverer; -import org.springframework.web.servlet.function.RequestPredicate; - -public class PredicateDiscoverer extends AbstractGatewayDiscoverer { - - @Override - public void discover() { - doDiscover(PredicateSupplier.class, RequestPredicate.class); - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/predicate/PredicateSupplier.java b/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/predicate/PredicateSupplier.java deleted file mode 100644 index 23976d09..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/java/org/springframework/cloud/gateway/server/mvc/predicate/PredicateSupplier.java +++ /dev/null @@ -1,25 +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.predicate; - -import java.lang.reflect.Method; -import java.util.Collection; -import java.util.function.Supplier; - -public interface PredicateSupplier extends Supplier> { - -} 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 deleted file mode 100644 index fd424eac..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/resources/META-INF/spring.factories +++ /dev/null @@ -1,12 +0,0 @@ -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.FilterFunctions.FilterSupplier - -org.springframework.cloud.gateway.server.mvc.handler.HandlerSupplier=\ - org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.HandlerSupplier,\ - 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 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 deleted file mode 100644 index bbc80f73..00000000 --- a/spring-cloud-gateway-server-mvc/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ /dev/null @@ -1,3 +0,0 @@ -org.springframework.cloud.gateway.server.mvc.GatewayServerMvcAutoConfiguration -org.springframework.cloud.gateway.server.mvc.handler.GatewayMultipartAutoConfiguration -org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration \ No newline at end of file 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 deleted file mode 100644 index 9bb2c113..00000000 --- a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/ServerMvcIntegrationTests.java +++ /dev/null @@ -1,1188 +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; - -import java.net.URI; -import java.nio.charset.StandardCharsets; -import java.time.Duration; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Predicate; - -import com.github.benmanes.caffeine.cache.Caffeine; -import io.github.bucket4j.caffeine.CaffeineProxyManager; -import io.github.bucket4j.distributed.proxy.AsyncProxyManager; -import io.github.bucket4j.distributed.remote.RemoteBucketState; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -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.boot.test.web.client.TestRestTemplate; -import org.springframework.boot.test.web.server.LocalServerPort; -import org.springframework.cloud.gateway.server.mvc.common.MvcUtils; -import org.springframework.cloud.gateway.server.mvc.filter.ForwardedRequestHeadersFilter; -import org.springframework.cloud.gateway.server.mvc.filter.XForwardedRequestHeadersFilter; -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.LocalServerPortUriResolver; -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.core.io.ClassPathResource; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.RequestEntity; -import org.springframework.http.ResponseEntity; -import org.springframework.http.client.MultipartBodyBuilder; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.servlet.function.HandlerFunction; -import org.springframework.web.servlet.function.RouterFunction; -import org.springframework.web.servlet.function.ServerRequest; -import org.springframework.web.servlet.function.ServerResponse; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.cloud.gateway.server.mvc.filter.AfterFilterFunctions.DedupeStrategy.RETAIN_FIRST; -import static org.springframework.cloud.gateway.server.mvc.filter.AfterFilterFunctions.DedupeStrategy.RETAIN_LAST; -import static org.springframework.cloud.gateway.server.mvc.filter.AfterFilterFunctions.DedupeStrategy.RETAIN_UNIQUE; -import static org.springframework.cloud.gateway.server.mvc.filter.AfterFilterFunctions.addResponseHeader; -import static org.springframework.cloud.gateway.server.mvc.filter.AfterFilterFunctions.dedupeResponseHeader; -import static org.springframework.cloud.gateway.server.mvc.filter.AfterFilterFunctions.removeResponseHeader; -import static org.springframework.cloud.gateway.server.mvc.filter.AfterFilterFunctions.rewriteLocationResponseHeader; -import static org.springframework.cloud.gateway.server.mvc.filter.AfterFilterFunctions.rewriteResponseHeader; -import static org.springframework.cloud.gateway.server.mvc.filter.AfterFilterFunctions.setResponseHeader; -import static org.springframework.cloud.gateway.server.mvc.filter.AfterFilterFunctions.setStatus; -import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.CB_EXECUTION_EXCEPTION_MESSAGE; -import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.CB_EXECUTION_EXCEPTION_TYPE; -import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.adaptCachedBody; -import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.fallbackHeaders; -import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.mapRequestHeader; -import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.modifyRequestBody; -import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.preserveHost; -import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.removeRequestParameter; -import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.requestHeaderSize; -import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.requestHeaderToRequestUri; -import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.requestSize; -import static org.springframework.cloud.gateway.server.mvc.filter.BeforeFilterFunctions.routeId; -import static org.springframework.cloud.gateway.server.mvc.filter.Bucket4jFilterFunctions.rateLimit; -import static org.springframework.cloud.gateway.server.mvc.filter.CircuitBreakerFilterFunctions.circuitBreaker; -import static org.springframework.cloud.gateway.server.mvc.filter.FilterFunctions.addRequestHeader; -import static org.springframework.cloud.gateway.server.mvc.filter.FilterFunctions.addRequestHeadersIfNotPresent; -import static org.springframework.cloud.gateway.server.mvc.filter.FilterFunctions.addRequestParameter; -import static org.springframework.cloud.gateway.server.mvc.filter.FilterFunctions.prefixPath; -import static org.springframework.cloud.gateway.server.mvc.filter.FilterFunctions.redirectTo; -import static org.springframework.cloud.gateway.server.mvc.filter.FilterFunctions.removeRequestHeader; -import static org.springframework.cloud.gateway.server.mvc.filter.FilterFunctions.rewritePath; -import static org.springframework.cloud.gateway.server.mvc.filter.FilterFunctions.setPath; -import static org.springframework.cloud.gateway.server.mvc.filter.FilterFunctions.setRequestHeader; -import static org.springframework.cloud.gateway.server.mvc.filter.FilterFunctions.setRequestHostHeader; -import static org.springframework.cloud.gateway.server.mvc.filter.FilterFunctions.stripPrefix; -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.forward; -import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http; -import static org.springframework.cloud.gateway.server.mvc.predicate.GatewayRequestPredicates.cloudFoundryRouteService; -import static org.springframework.cloud.gateway.server.mvc.predicate.GatewayRequestPredicates.cookie; -import static org.springframework.cloud.gateway.server.mvc.predicate.GatewayRequestPredicates.header; -import static org.springframework.cloud.gateway.server.mvc.predicate.GatewayRequestPredicates.host; -import static org.springframework.cloud.gateway.server.mvc.predicate.GatewayRequestPredicates.readBody; -import static org.springframework.cloud.gateway.server.mvc.test.TestUtils.getMap; -import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED; -import static org.springframework.http.MediaType.MULTIPART_FORM_DATA; -import static org.springframework.web.servlet.function.RequestPredicates.GET; -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" }, - webEnvironment = WebEnvironment.RANDOM_PORT) -@ContextConfiguration(initializers = HttpbinTestcontainers.class) -public class ServerMvcIntegrationTests { - - static { - // if set type to autodetect above - System.setProperty("sun.net.http.allowRestrictedHeaders", "true"); - } - - @LocalServerPort - int port; - - @Autowired - TestRestTemplate restTemplate; - - @Autowired - TestRestClient restClient; - - @Test - public void nonGatewayRouterFunctionWorks() { - restClient.get().uri("/hello").exchange().expectStatus().isOk().expectBody(String.class).isEqualTo("Hello"); - } - - @SuppressWarnings("rawtypes") - @Test - public void addRequestHeaderWorks() { - restClient.get().uri("/get").exchange().expectStatus().isOk().expectBody(Map.class).consumeWith(res -> { - Map headers = getMap(res.getResponseBody(), "headers"); - assertThat(headers).containsEntry("X-Foo", "Bar"); - }); - } - - @Test - public void addRequestParameterWorks() { - restClient.get().uri("/anything/addrequestparam").exchange().expectStatus().isOk().expectBody(Map.class) - .consumeWith(res -> { - Map map = res.getResponseBody(); - Map args = getMap(map, "args"); - assertThat(args).containsEntry("param1", "param1val"); - }); - } - - @Test - public void removeHopByHopRequestHeadersFilterWorks() { - restClient.get().uri("/anything/removehopbyhoprequestheaders").exchange().expectStatus().isOk() - .expectBody(Map.class).consumeWith(res -> { - Map map = res.getResponseBody(); - Map headers = getMap(map, "headers"); - assertThat(headers).doesNotContainKeys("x-application-context"); - }); - } - - @Test - public void setPathWorks() { - restClient.get().uri("/mycustompathextra1").exchange().expectStatus().isOk().expectBody(Map.class) - .consumeWith(res -> { - Map map = res.getResponseBody(); - Map args = getMap(map, "args"); - assertThat(args).containsEntry("param1", "param1valextra1"); - }); - } - - @Test - public void stripPathWorks() { - restClient.get().uri("/long/path/to/get").exchange().expectStatus().isOk().expectBody(Map.class) - .consumeWith(res -> { - Map map = res.getResponseBody(); - Map headers = getMap(map, "headers"); - assertThat(headers).containsEntry("X-Test", "stripPrefix"); - }); - } - - @Test - public void setStatusGatewayRouterFunctionWorks() { - restClient.get().uri("/status/201").exchange().expectStatus().isEqualTo(HttpStatus.TOO_MANY_REQUESTS) - .expectHeader().valueEquals("x-status", "201"); // .expectBody(String.class).isEqualTo("Failed - // with 201"); - } - - @Test - public void addResponseHeaderWorks() { - restClient.get().uri("/anything/addresheader").exchange().expectStatus().isOk().expectBody(Map.class) - .consumeWith(res -> { - Map map = res.getResponseBody(); - Map headers = getMap(map, "headers"); - assertThat(headers).doesNotContainKey("x-bar"); - assertThat(res.getResponseHeaders()).containsEntry("x-bar", Collections.singletonList("val1")); - }); - } - - @Test - public void postWorks() { - restClient.post().uri("/post").bodyValue("Post Value").header("test", "post").exchange().expectStatus().isOk() - .expectBody(Map.class).consumeWith(res -> { - Map map = res.getResponseBody(); - assertThat(map).isNotEmpty().containsEntry("data", "Post Value"); - }); - } - - @Test - public void loadbalancerWorks() { - restClient.get().uri("/anything/loadbalancer").exchange().expectStatus().isOk().expectBody(Map.class) - .consumeWith(res -> { - Map map = res.getResponseBody(); - Map headers = getMap(map, "headers"); - assertThat(headers).containsEntry("X-Test", "loadbalancer"); - }); - } - - @Test - public void hostPredicateWorks() { - String host = "www1.myjavadslhost.com"; - restClient.get().uri("/anything/hostpredicate").header("Host", host).exchange().expectStatus().isOk() - .expectHeader().valueEquals("X-SubDomain", "www1").expectBody(Map.class).consumeWith(res -> { - Map map = res.getResponseBody(); - Map headers = getMap(map, "headers"); - assertThat(headers).containsEntry("Host", host); - }); - } - - @Test - public void circuitBreakerFallbackWorks() { - restClient.get().uri("/anything/circuitbreakerfallback").exchange().expectStatus().isOk() - .expectBody(String.class).isEqualTo("Hello"); - } - - @Test - public void circuitBreakerGatewayFallbackWorks() { - restClient.get().uri("/anything/circuitbreakergatewayfallback").exchange().expectStatus().isOk() - .expectBody(Map.class).consumeWith(res -> { - Map headers = getMap(res.getResponseBody(), "headers"); - assertThat(headers).containsKeys(CB_EXECUTION_EXCEPTION_TYPE, CB_EXECUTION_EXCEPTION_MESSAGE); - }); - } - - @Test - public void circuitBreakerNoFallbackWorks() { - restClient.get().uri("/anything/circuitbreakernofallback").exchange().expectStatus() - .isEqualTo(HttpStatus.GATEWAY_TIMEOUT); - } - - @Test - public void retryWorks() { - restClient.get().uri("/retry?key=get").exchange().expectStatus().isOk().expectBody(String.class).isEqualTo("3"); - // test for: java.lang.IllegalArgumentException: You have already selected another - // retry policy - restClient.get().uri("/retry?key=get2").exchange().expectStatus().isOk().expectBody(String.class) - .isEqualTo("3"); - } - - @Test - public void rateLimitWorks() { - restClient.get().uri("/anything/ratelimit").exchange().expectStatus().isOk(); - restClient.get().uri("/anything/ratelimit").exchange().expectStatus().isEqualTo(HttpStatus.TOO_MANY_REQUESTS); - } - - @Test - public void headerRegexWorks() { - restClient.get().uri("/headerregex").exchange().expectStatus().isNotFound(); - restClient.get().uri("/headerregex").header("X-MyHeader", "foo").exchange().expectStatus().isOk() - .expectBody(Map.class).consumeWith(res -> { - Map map = res.getResponseBody(); - Map headers = getMap(map, "headers"); - assertThat(headers).containsEntry("X-Myheader", "foo"); - }); - } - - @Test - public void cookieRegexWorks() { - restClient.get().uri("/cookieregex").exchange().expectStatus().isNotFound(); - restClient.get().uri("/cookieregex").cookie("mycookie", "foo").exchange().expectStatus().isOk() - .expectBody(Map.class).consumeWith(res -> { - Map map = res.getResponseBody(); - Map headers = getMap(map, "headers"); - assertThat(headers).containsEntry("Cookie", "mycookie=foo"); - }); - } - - @Test - public void rewritePathWorks() { - restClient.get().uri("/foo/get").header("Host", "www.rewritepath.org").exchange().expectStatus().isOk() - .expectBody(Map.class).consumeWith(res -> { - Map map = res.getResponseBody(); - Map headers = getMap(map, "headers"); - assertThat(headers).containsEntry("X-Test", "rewritepath"); - }); - } - - @Test - public void forwardedHeadersWork() { - restClient.get().uri("/headers").header("test", "forwarded").exchange().expectStatus().isOk() - .expectBody(Map.class).consumeWith(res -> { - Map map = res.getResponseBody(); - Map headers = getMap(map, "headers"); - assertThat(headers).containsKeys(ForwardedRequestHeadersFilter.FORWARDED_HEADER, - XForwardedRequestHeadersFilter.X_FORWARDED_FOR_HEADER, - XForwardedRequestHeadersFilter.X_FORWARDED_HOST_HEADER, - XForwardedRequestHeadersFilter.X_FORWARDED_PORT_HEADER, - XForwardedRequestHeadersFilter.X_FORWARDED_PROTO_HEADER); - assertThat(headers.get(ForwardedRequestHeadersFilter.FORWARDED_HEADER)).asString() - .contains("proto=http").contains("host=\"localhost:").contains("for=\"127.0.0.1:"); - assertThat(headers.get(XForwardedRequestHeadersFilter.X_FORWARDED_HOST_HEADER)).asString() - .isEqualTo("localhost:" + this.port); - assertThat(headers.get(XForwardedRequestHeadersFilter.X_FORWARDED_PORT_HEADER)).asString() - .isEqualTo(String.valueOf(this.port)); - assertThat(headers.get(XForwardedRequestHeadersFilter.X_FORWARDED_PROTO_HEADER).toString()) - .asString().isEqualTo("http"); - }); - } - - @Test - public void requestSizeWorks() { - restClient.post().uri("/post").bodyValue("123456").header("test", "requestsize").exchange().expectStatus() - .isEqualTo(HttpStatus.PAYLOAD_TOO_LARGE).expectHeader().valueMatches("errormessage", - "Request size is larger than permissible limit. Request size is .* where permissible limit is .*"); - } - - @Test - public void requestHeaderSizeWorks() { - restClient.get().uri("/headers").header("test", "requestheadersize") - .header("X-AnyHeader", - "11111111112222222222333333333344444444445555555555666666666677777777778888888888") - .exchange().expectStatus().isEqualTo(HttpStatus.REQUEST_HEADER_FIELDS_TOO_LARGE).expectHeader() - .valueMatches("errormessage", - "Request Header/s size is larger than permissible limit (.*). Request Header/s size for 'x-anyheader' is .*"); - restClient.get().uri("/headers").header("test", "requestheadersize") - .header("X-AnyHeader", "111111111122222222223333333333444444444455555555556666666666").exchange() - .expectStatus().isOk(); - } - - public static final MediaType FORM_URL_ENCODED_CONTENT_TYPE = new MediaType(APPLICATION_FORM_URLENCODED, - StandardCharsets.UTF_8); - - @Test - void formUrlencodedWorks() { - LinkedMultiValueMap formData = new LinkedMultiValueMap<>(); - formData.add("foo", "bar"); - formData.add("baz", "bam"); - - // @formatter:off - restClient.post().uri("/post").header("test", "form").contentType(FORM_URL_ENCODED_CONTENT_TYPE) - .bodyValue(formData) - .exchange() - .expectStatus().isOk() - .expectBody(Map.class).consumeWith(result -> { - Map map = result.getResponseBody(); - Map form = getMap(map, "form"); - assertThat(form).containsEntry("foo", "bar"); - assertThat(form).containsEntry("baz", "bam"); - }); - // @formatter:on - } - - @Test - void multipartFormDataWorks() { - MultiValueMap> formData = createMultipartData(); - // @formatter:off - restClient.post().uri("/post").contentType(MULTIPART_FORM_DATA) - .header("test", "form") - .bodyValue(formData) - .exchange() - .expectStatus().isOk() - .expectBody(Map.class) - .consumeWith(result -> { - assertMultipartData(result.getResponseBody()); - }); - // @formatter:on - } - - @Test - void multipartFormDataRestTemplateWorks() { - MultiValueMap> formData = createMultipartData(); - RequestEntity>> request = RequestEntity.post("/post") - .contentType(MULTIPART_FORM_DATA).header("test", "form").body(formData); - ResponseEntity response = restTemplate.exchange(request, Map.class); - assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); - assertMultipartData(response.getBody()); - } - - @Test - public void redirectToWorks() { - restClient.get().uri("/anything/redirect").exchange().expectStatus().isEqualTo(HttpStatus.MOVED_PERMANENTLY) - .expectHeader().valueEquals(HttpHeaders.LOCATION, "https://exampleredirect.com"); - } - - private MultiValueMap> createMultipartData() { - ClassPathResource part = new ClassPathResource("test/1x1.png"); - MultipartBodyBuilder builder = new MultipartBodyBuilder(); - builder.part("imgpart", part, MediaType.IMAGE_PNG); - return builder.build(); - } - - private void assertMultipartData(Map responseBody) { - Map files = (Map) responseBody.get("files"); - assertThat(files).containsKey("imgpart"); - String file = (String) files.get("imgpart"); - assertThat(file).startsWith("data:").contains(";base64,"); - } - - @Test - public void removeRequestHeaderWorks() { - restClient.get().uri("/anything/removerequestheader").header("X-Request-Foo", "Bar").exchange().expectStatus() - .isOk().expectBody(Map.class).consumeWith(res -> { - Map map = res.getResponseBody(); - Map headers = getMap(map, "headers"); - assertThat(headers).doesNotContainKey("X-Request-Foo"); - }); - } - - @Test - public void setRequestHeaderWorks() { - restClient.get().uri("/headers").header("test", "setrequestheader").exchange().expectStatus().isOk() - .expectBody(Map.class).consumeWith(res -> { - Map headers = getMap(res.getResponseBody(), "headers"); - assertThat(headers).doesNotContainEntry("X-Test", "value1"); - assertThat(headers).containsEntry("X-Test", "value2"); - }); - } - - @Test - public void setRequestHeaderHostWorks() { - restClient.get().uri("/headers").header("Host", "www.setrequesthostheader.org").exchange().expectStatus().isOk() - .expectBody(Map.class).consumeWith(res -> { - Map headers = getMap(res.getResponseBody(), "headers"); - assertThat(headers).containsEntry("Host", "otherhost.io"); - }); - } - - @Test - public void setResponseHeaderWorks() { - restClient.get().uri("/anything/setresponseheader").header("test", "setresponseheader").exchange() - .expectStatus().isOk().expectBody(Map.class).consumeWith(res -> { - HttpHeaders headers = res.getResponseHeaders(); - assertThat(headers).doesNotContainEntry("X-Test", List.of("value1")); - assertThat(headers).containsEntry("X-Test", List.of("value2")); - }); - } - - @Test - public void nestedRouteWorks() { - testNestedRoute("nested1"); - testNestedRoute("nested2"); - } - - private void testNestedRoute(String nestedPath) { - restClient.get().uri("/anything/nested/" + nestedPath).header("test", "nested").exchange().expectStatus().isOk() - .expectBody(Map.class).consumeWith(res -> { - Map headers = getMap(res.getResponseBody(), "headers"); - assertThat(headers).containsEntry("X-Test", nestedPath); - }); - } - - @Test - public void removeRequestParameterWorks() { - restClient.get().uri("/anything/removerequestparameter?foo=bar").header("test", "removerequestparam").exchange() - .expectStatus().isOk().expectHeader().doesNotExist("foo"); - } - - @Test - public void removeResponseHeaderWorks() { - restClient.get().uri("/anything/removeresponseheader").header("test", "removeresponseheader").exchange() - .expectStatus().isOk().expectHeader().doesNotExist("X-Test"); - } - - @Test - public void rewriteResponseHeaderWorks() { - restClient.get().uri("/headers").header("test", "rewriteresponseheader").exchange().expectStatus().isOk() - .expectHeader().valueEquals("X-Request-Foo", "/42?user=ford&password=***&flag=true"); - } - - @Test - public void requestHeaderToRequestUriWorks() { - // @formatter:off - restClient.get().uri("/anything/requestheadertorequesturi") - .header("Host", "www.requestheadertorequesturi.org") - .header("X-CF-Forwarded-Url", "http://localhost:" + port) - .header("X-CF-Proxy-Signature", "fakeproxysignature") - .header("X-CF-Proxy-Metadata", "fakeproxymetadata") - .exchange().expectStatus().isOk().expectBody(String.class).isEqualTo("Hello"); - // @formatter:on - } - - @Test - public void mapRequestHeaderWorks() { - restClient.get().uri("/anything/maprequestheader").header("X-Foo", "fooval").exchange().expectStatus().isOk() - .expectBody(Map.class).consumeWith(res -> { - Map headers = getMap(res.getResponseBody(), "headers"); - assertThat(headers).containsEntry("X-Bar", "fooval"); - }); - } - - @Test - public void dedupeResponseHeaderWorks() { - restClient.get().uri("/headers").header("Host", "www.deduperesponseheader.org").exchange().expectStatus().isOk() - .expectHeader().valueEquals("Access-Control-Allow-Credentials", "true").expectHeader() - .valueEquals("Access-Control-Allow-Origin", "https://example.org").expectHeader() - .valueEquals("Scout-Cookie", "S'mores").expectHeader() - .valueEquals("Next-Week-Lottery-Numbers", "4", "2", "42"); - } - - @Test - public void addRequestHeadersIfNotPresentWorks() { - restClient.get().uri("/headers").header("Host", "www.addrequestheadersifnotpresent.org") - .header("X-Request-Beta", "Value1").exchange().expectStatus().isOk().expectBody(Map.class) - .consumeWith(res -> { - Map headers = getMap(res.getResponseBody(), "headers"); - // this asserts that Value2 was not added - assertThat(headers).containsEntry("X-Request-Beta", "Value1"); - assertThat(headers).containsKey("X-Request-Acme"); - List values = (List) headers.get("X-Request-Acme"); - assertThat(values).hasSize(4).containsOnly("ValueX", "ValueY", "ValueZ", "www"); - }); - } - - @Test - public void rewriteLocationResponseHeaderWorks() { - restClient.get().uri("/anything/rewritelocationresponseheader") - .header("Host", "test1.rewritelocationresponseheader.org").exchange().expectStatus().isOk() - .expectHeader() - .valueEquals("Location", "https://test1.rewritelocationresponseheader.org/some/object/id"); - } - - @Test - public void readBodyWorks() { - Event messageEvent = new Event("message", "bar"); - - restClient.post().uri("/events").bodyValue(messageEvent).exchange().expectStatus().isOk().expectHeader() - .valueEquals("X-Foo", "message").expectBody(Event.class) - .consumeWith(res -> assertThat(res.getResponseBody()).isEqualTo(messageEvent)); - - Event messageChannelEvent = new Event("message.channel", "baz"); - - restClient.post().uri("/events").bodyValue(messageChannelEvent).exchange().expectStatus().isOk().expectHeader() - .valueEquals("X-Channel-Foo", "message.channel").expectBody(Event.class) - .consumeWith(res -> assertThat(res.getResponseBody()).isEqualTo(messageChannelEvent)); - - } - - @Test - @SuppressWarnings("unchecked") - public void rewriteRequestBodyStringWorks() { - restClient.post().uri("/post").header("Host", "www.modifyrequestbodystring.org").bodyValue("hello").exchange() - .expectStatus().isOk().expectBody(Map.class) - .consumeWith(result -> assertThat(result.getResponseBody()).containsEntry("data", "HELLOHELLO")); - } - - @Test - @SuppressWarnings("unchecked") - public void rewriteRequestBodyObjectWorks() { - restClient.post().uri("/post").header("Host", "www.modifyrequestbodyobject.org").bodyValue("hello world") - .exchange().expectStatus().isOk().expectBody(Map.class) - .consumeWith(result -> assertThat(result.getResponseBody()).containsEntry("data", - "{\"message\":\"HELLO WORLD\"}")); - } - - @Test - public void forwardWorks() { - restClient.get().uri("/doforward").exchange().expectStatus().isOk().expectBody(String.class).isEqualTo("Hello"); - } - - @Test - public void forwardNon200StatusWorks() { - restClient.get().uri("/doforward2").exchange().expectStatus().isCreated().expectBody(String.class) - .isEqualTo("hello2"); - } - - @SpringBootConfiguration - @EnableAutoConfiguration - @LoadBalancerClient(name = "httpbin", configuration = TestLoadBalancerConfig.Httpbin.class) - protected static class TestConfiguration { - - @Bean - TestHandler testHandler() { - return new TestHandler(); - } - - @Bean - RetryController retryController() { - return new RetryController(); - } - - @Bean - EventController eventController() { - return new EventController(); - } - - @Bean - public AsyncProxyManager caffeineProxyManager() { - Caffeine builder = (Caffeine) Caffeine.newBuilder().maximumSize(100); - return new CaffeineProxyManager<>(builder, Duration.ofMinutes(1)).asAsync(); - } - - @Bean - public RouterFunction nonGatewayRouterFunctions(TestHandler testHandler) { - return route(GET("/hello"), testHandler).withAttribute(MvcUtils.GATEWAY_ROUTE_ID_ATTR, "hello"); - } - - @Bean - public RouterFunction nonGatewayRouterFunctions2() { - return route(GET("/hello2"), request -> ServerResponse.status(HttpStatus.CREATED).body("hello2")) - .withAttribute(MvcUtils.GATEWAY_ROUTE_ID_ATTR, "hello2"); - } - - @Bean - public RouterFunction gatewayRouterFunctionsAddReqHeader() { - // @formatter:off - return route(GET("/get"), http()) - .filter(new HttpbinUriResolver()) - .filter(addRequestHeader("X-Foo", "Bar")) - .withAttribute(MvcUtils.GATEWAY_ROUTE_ID_ATTR, "testaddreqheader"); - // @formatter:on - } - - @Bean - public RouterFunction gatewayRouterFunctionsSetStatusAndAddRespHeader() { - // @formatter:off - return route("testsetstatus") - .GET("/status/{status}", http()) - .before(new HttpbinUriResolver()) - .after(setStatus(HttpStatus.TOO_MANY_REQUESTS)) - .after(addResponseHeader("X-Status", "{status}")) - .build().and(route() - .GET("/anything/addresheader", http()) - .before(routeId("testaddresponseheader")) - .before(new HttpbinUriResolver()) - .after(addResponseHeader("X-Bar", "val1")) - .build()); - // @formatter:on - } - - @Bean - public RouterFunction gatewayRouterFunctionsAddRequestParam() { - // @formatter:off - return route(GET("/anything/addrequestparam"), http()) - .filter(new HttpbinUriResolver()) - .filter(addRequestParameter("param1", "param1val")) - .withAttribute(MvcUtils.GATEWAY_ROUTE_ID_ATTR, "testaddrequestparam"); - // @formatter:on - } - - @Bean - public RouterFunction gatewayRouterFunctionsSetPath() { - // @formatter:off - return route(GET("/mycustompath{extra}"), http()) - .filter(new HttpbinUriResolver()) - .filter(setPath("/anything/mycustompath{extra}")) - .filter(addRequestParameter("param1", "param1val{extra}")) - .withAttribute(MvcUtils.GATEWAY_ROUTE_ID_ATTR, "testsetpath"); - // @formatter:on - } - - @Bean - public RouterFunction gatewayRouterFunctionsStripPrefix() { - // @formatter:off - return route(GET("/long/path/to/get"), http()) - .filter(new HttpbinUriResolver()) - .filter(stripPrefix(3)) - .filter(addRequestHeader("X-Test", "stripPrefix")) - .withAttribute(MvcUtils.GATEWAY_ROUTE_ID_ATTR, "teststripprefix"); - // @formatter:on - } - - @Bean - public RouterFunction gatewayRouterFunctionsRemoveHopByHopRequestHeaders() { - // @formatter:off - return route(GET("/anything/removehopbyhoprequestheaders"), http()) - .filter(new HttpbinUriResolver()) - .filter(addRequestHeader("x-application-context", "context-id1")) - .withAttribute(MvcUtils.GATEWAY_ROUTE_ID_ATTR, "testremovehopbyhopheaders"); - // @formatter:on - } - - @Bean - public RouterFunction gatewayRouterFunctionsPost() { - // @formatter:off - return route(POST("/post").and(header("test", "post")), http()) - .filter(new HttpbinUriResolver()) - .withAttribute(MvcUtils.GATEWAY_ROUTE_ID_ATTR, "testpost"); - // @formatter:on - } - - @Bean - public RouterFunction gatewayRouterFunctionsLoadBalancer() { - // @formatter:off - return route() - .GET("/anything/loadbalancer", http()) - .filter(lb("httpbin")) - .filter(addRequestHeader("X-Test", "loadbalancer")) - .withAttribute(MvcUtils.GATEWAY_ROUTE_ID_ATTR, "testloadbalancer") - .build(); - // @formatter:on - } - - @Bean - public RouterFunction gatewayRouterFunctionsHost() { - // @formatter:off - return route("testhostpredicate") - .route(host("{sub}.myjavadslhost.com").and(path("/anything/hostpredicate")), http()) - .before(new HttpbinUriResolver()) - .before(preserveHost()) - .after(addResponseHeader("X-SubDomain", "{sub}")) - .build(); - // @formatter:on - } - - @Bean - public RouterFunction gatewayRouterFunctionsCircuitBreakerFallback() { - // @formatter:off - return route("testcircuitbreakerfallback") - .route(path("/anything/circuitbreakerfallback"), http(URI.create("https://nonexistantdomain.com1234"))) - .filter(circuitBreaker("mycb1", "/hello")) - .build(); - // @formatter:on - } - - @Bean - public RouterFunction gatewayRouterFunctionsCircuitBreakerFallbackToGatewayRoute() { - // @formatter:off - return route("testcircuitbreakergatewayfallback") - .route(path("/anything/circuitbreakergatewayfallback"), http(URI.create("https://nonexistantdomain.com1234"))) - .filter(circuitBreaker("mycb2", "/anything/gatewayfallback")) - .build() - .and(route("testgatewayfallback") - .route(path("/anything/gatewayfallback"), http()) - .before(new HttpbinUriResolver()) - .before(fallbackHeaders()) - .build()); - // @formatter:on - } - - @Bean - public RouterFunction gatewayRouterFunctionsCircuitBreakerNoFallback() { - // @formatter:off - return route(path("/anything/circuitbreakernofallback"), http()) - .filter(new HttpbinUriResolver()) - .filter(circuitBreaker("mycb3", null)) - .filter(setPath("/delay/5")) - .withAttribute(MvcUtils.GATEWAY_ROUTE_ID_ATTR, "testcircuitbreakernofallback"); - // @formatter:on - } - - @Bean - public RouterFunction gatewayRouterFunctionsRetry() { - // @formatter:off - return route("testretry") - .route(path("/retry"), http()) - .before(new LocalServerPortUriResolver()) - .filter(retry(3)) - .filter(prefixPath("/do")) - .build(); - // @formatter:on - } - - @Bean - public RouterFunction gatewayRouterFunctionsRateLimit() { - // @formatter:off - return route(GET("/anything/ratelimit"), http()) - .filter(new HttpbinUriResolver()) - //.filter(rateLimit(1, Duration.ofMinutes(1), request -> "ratelimittest1min")) - .filter(rateLimit(c -> c.setCapacity(1) - .setPeriod(Duration.ofMinutes(1)) - .setKeyResolver(request -> "ratelimitttest1min"))) - .withAttribute(MvcUtils.GATEWAY_ROUTE_ID_ATTR, "testratelimit"); - // @formatter:on - } - - @Bean - public RouterFunction gatewayRouterFunctionsHeaderPredicate() { - // @formatter:off - return route(path("/headerregex").and(header("X-MyHeader", "fo.")), http()) - .filter(new HttpbinUriResolver()) - .filter(setPath("/headers")) - .withAttribute(MvcUtils.GATEWAY_ROUTE_ID_ATTR, "testheaderpredicate"); - // @formatter:on - } - - @Bean - public RouterFunction gatewayRouterFunctionsCookiePredicate() { - // @formatter:off - return route(path("/cookieregex").and(cookie("mycookie", "fo.")), http()) - .filter(new HttpbinUriResolver()) - .filter(setPath("/headers")) - .withAttribute(MvcUtils.GATEWAY_ROUTE_ID_ATTR, "testcookiepredicate"); - // @formatter:on - } - - @Bean - public RouterFunction gatewayRouterFunctionsRewritePath() { - // @formatter:off - return route(path("/foo/**").and(host("**.rewritepath.org")), http()) - .filter(new HttpbinUriResolver()) - .filter(rewritePath("/foo/(?.*)", "/${segment}")) - .filter(addRequestHeader("X-Test", "rewritepath")) - .withAttribute(MvcUtils.GATEWAY_ROUTE_ID_ATTR, "testrewritepath"); - // @formatter:on - } - - @Bean - public RouterFunction gatewayRouterFunctionsForwardedHeaders() { - // @formatter:off - return route(path("/headers").and(header("test", "forwarded")), http()) - .filter(new HttpbinUriResolver()) - .filter(addRequestHeader("X-Test", "forwarded")) - .withAttribute(MvcUtils.GATEWAY_ROUTE_ID_ATTR, "testforwardedheaders"); - // @formatter:on - } - - @Bean - public RouterFunction gatewayRouterFunctionsForm() { - // @formatter:off - return route("testform") - .POST("/post", header("test", "form"), http()) - .before(new LocalServerPortUriResolver()) - .filter(prefixPath("/test")) - .filter(addRequestHeader("X-Test", "form")) - .build(); - // @formatter:on - } - - @Bean - public RouterFunction gatewayRouterFunctionsRedirectTo() { - // @formatter:off - return route(path("/anything/redirect"), http()) - .filter(redirectTo(HttpStatus.MOVED_PERMANENTLY, URI.create("https://exampleredirect.com"))) - .withAttribute(MvcUtils.GATEWAY_ROUTE_ID_ATTR, "testredirectto"); - // @formatter:on - } - - @Bean - public RouterFunction gatewayRouterFunctionsRemoveRequestHeader() { - // @formatter:off - return route(path("/anything/removerequestheader"), http()) - .filter(new HttpbinUriResolver()) - .filter(removeRequestHeader("X-Request-Foo")) - .withAttribute(MvcUtils.GATEWAY_ROUTE_ID_ATTR, "testremoverequestheader"); - // @formatter:on - } - - @Bean - public RouterFunction gatewayRouterFunctionsSetRequestHeader() { - // @formatter:off - return route("testsetrequestheader") - .GET("/headers", header("test", "setrequestheader"), http()) - .filter(new HttpbinUriResolver()) - .filter(addRequestHeader("X-Test", "value1")) - .filter(setRequestHeader("X-Test", "value2")) - .build(); - // @formatter:on - } - - @Bean - public RouterFunction gatewayRouterFunctionsSetRequestHostHeader() { - // @formatter:off - return route("testsetrequesthostheader") - .route(host("**.setrequesthostheader.org"), http()) - .filter(new HttpbinUriResolver()) - .filter(setRequestHostHeader("otherhost.io")) - .build(); - // @formatter:on - } - - @Bean - public RouterFunction gatewayRouterFunctionsSetResponseHeader() { - // @formatter:off - return route("testsetresponseheader") - .GET("/anything/setresponseheader", header("test", "setresponseheader"), http()) - .before(new HttpbinUriResolver()) - // reverse order for "post" filters - .after(setResponseHeader("X-Test", "value2")) - .after(addResponseHeader("X-Test", "value1")) - .build(); - // @formatter:on - } - - @Bean - public RouterFunction gatewayRouterFunctionsNested() { - // @formatter:off - return route() - .nest(path("/anything/nested").and(header("test", "nested")), () -> - route("nested1").GET("/nested1", http()) - .before(new HttpbinUriResolver()) - .filter(addRequestHeader("X-Test", "nested1")) - .build().and( - route("nested2").GET("/nested2", http()) - .before(new HttpbinUriResolver()) - .filter(addRequestHeader("X-Test", "nested2")).build())) - .build(); - // @formatter:on - } - - @Bean - public RouterFunction gatewayRouterFunctionsRemoveRequestParam() { - // @formatter:off - return route("removerequestparam") - .route(header("test", "removerequestparam"), http()) - .filter(new HttpbinUriResolver()) - .before(removeRequestParameter("foo")) - .build(); - // @formatter:on - } - - @Bean - public RouterFunction gatewayRouterFunctionsRemoveResponseHeader() { - // @formatter:off - return route("testremoveresponseheader") - .GET("/anything/removeresponseheader", header("test", "removeresponseheader"), http()) - .before(new HttpbinUriResolver()) - // reverse order for "post" filters - .after(removeResponseHeader("X-Test")) - .after(addResponseHeader("X-Test", "value1")) - .build(); - // @formatter:on - } - - @Bean - public RouterFunction gatewayRouterFunctionsRewriteResponseHeader() { - // @formatter:off - return route("testrewriteresponseheader") - .route(header("test", "rewriteresponseheader"), http()) - .before(new HttpbinUriResolver()) - // reverse order for "post" filters - .after(rewriteResponseHeader("X-Request-Foo", "password=[^&]+", "password=***")) - .after(addResponseHeader("X-Request-Foo", "/42?user=ford&password=omg!what&flag=true")) - .build(); - // @formatter:on - } - - @Bean - public RouterFunction gatewayRouterFunctionsRequestSize() { - // @formatter:off - return route("requestsize") - .POST(path("/post").and(header("test", "requestsize")), http()) - .before(new HttpbinUriResolver()) - .before(requestSize("5B")) - .build(); - // @formatter:on - } - - @Bean - public RouterFunction gatewayRouterFunctionsRequestHeaderSize() { - // @formatter:off - return route("requestheadersize") - .GET(path("/headers").and(header("test", "requestheadersize")), http()) - .before(new HttpbinUriResolver()) - .before(requestHeaderSize("79B")) - .build(); - // @formatter:on - } - - @Bean - public RouterFunction gatewayRouterFunctionsRequestHeaderToRequestUri() { - // @formatter:off - return route("requestheadertorequesturi") - .route(cloudFoundryRouteService().and(host("**.requestheadertorequesturi.org")), http()) - //.before(new HttpbinUriResolver()) NO URI RESOLVER! - .before(requestHeaderToRequestUri("X-CF-Forwarded-Url")) - .filter(setPath("/hello")) - .build(); - // @formatter:on - } - - @Bean - public RouterFunction gatewayRouterFunctionsMapRequestHeader() { - // @formatter:off - return route("testmaprequestheader") - .GET("/anything/maprequestheader", http()) - .before(new HttpbinUriResolver()) - .before(mapRequestHeader("X-Foo", "X-Bar")) - .build(); - // @formatter:on - } - - @Bean - public RouterFunction gatewayRouterFunctionsAddRequestHeadersIfNotPresent() { - // @formatter:off - return route("testaddrequestheadersifnotpresent") - .route(GET("/headers").and(host("{sub}.addrequestheadersifnotpresent.org")), http()) - .filter(new HttpbinUriResolver()) - // normally use BeforeFilterFunctions version, but wanted to test parsing for config - .filter(addRequestHeadersIfNotPresent("X-Request-Acme:ValueX, X-Request-Acme:ValueY,X-Request-Acme:ValueZ, X-Request-Acme:{sub},X-Request-Beta:Value2")) - .build(); - // @formatter:on - } - - @Bean - public RouterFunction gatewayRouterFunctionsDedupeResponseHeader() { - // @formatter:off - return route("testdeduperesponseheader") - .route(GET("/headers").and(host("{sub}.deduperesponseheader.org")), http()) - .filter(new HttpbinUriResolver()) - .after(dedupeResponseHeader("Access-Control-Allow-Credentials Access-Control-Allow-Origin", RETAIN_FIRST)) - .after(dedupeResponseHeader("Scout-Cookie", RETAIN_LAST)) - .after(dedupeResponseHeader("Next-Week-Lottery-Numbers", RETAIN_UNIQUE)) - .after(addResponseHeader("Access-Control-Allow-Credentials", "false")) - .after(setResponseHeader("Access-Control-Allow-Credentials", "true")) - .after(addResponseHeader("Access-Control-Allow-Origin", "*")) - .after(setResponseHeader("Access-Control-Allow-Origin", "https://example.org")) - .after(addResponseHeader("Scout-Cookie", "S'mores")) - .after(setResponseHeader("Scout-Cookie", "Thin Mints")) - .after(addResponseHeader("Next-Week-Lottery-Numbers", "42")) - .after(addResponseHeader("Next-Week-Lottery-Numbers", "2")) - .after(addResponseHeader("Next-Week-Lottery-Numbers", "2")) - .after(setResponseHeader("Next-Week-Lottery-Numbers", "4")) - .build(); - // @formatter:on - } - - @Bean - public RouterFunction gatewayRouterFunctionsRewriteLocationResponseHeader() { - // @formatter:off - return route("testrewritelocationresponseheader") - .GET("/anything/rewritelocationresponseheader", host("**.rewritelocationresponseheader.org"), http()) - .before(new HttpbinUriResolver()) - // reverse order for "post" filters - .after(rewriteLocationResponseHeader()) - .after(addResponseHeader("Location", "https://backend.org:443/v1/some/object/id")) - .build(); - // @formatter:on - } - - @Bean - public RouterFunction gatewayRouterFunctionsReadBodyPredicate() { - // @formatter:off - return route("testreadbodypredicate") - .POST("/events", readBody(Event.class, eventPredicate("message")), http()) - .before(new LocalServerPortUriResolver()) - .filter(setPath("/do/events")) - .before(adaptCachedBody()) - .build().and( - route("testreadbodypredicate2") - .POST("/events", readBody(Event.class, eventPredicate("message.channel")), http()) - .before(new LocalServerPortUriResolver()) - .filter(setPath("/do/events/channel")) - .before(adaptCachedBody()) - .build()); - // @formatter:on - } - - @Bean - public RouterFunction gatewayRouterFunctionsModifyRequestBody() { - // @formatter:off - return route("testmodifyrequestbodystring") - .POST("/post", host("**.modifyrequestbodystring.org"), http()) - .before(new HttpbinUriResolver()) - .before(modifyRequestBody(String.class, String.class, null, (request, s) -> s.toUpperCase() + s.toUpperCase())) - .build().and( - route("testmodifyrequestbodyobject") - .POST("/post", host("**.modifyrequestbodyobject.org"), http()) - .before(new HttpbinUriResolver()) - .before(modifyRequestBody(String.class, Hello.class, MediaType.APPLICATION_JSON_VALUE, (request, s) -> new Hello(s.toUpperCase()))) - .build()); - // @formatter:on - } - - @Bean - public RouterFunction gatewayRouterFunctionsForward() { - // @formatter:off - return route("testforward") - .GET("/doforward", forward("/hello")) - .before(new LocalServerPortUriResolver()) - .build(); - // @formatter:on - } - - @Bean - public RouterFunction gatewayRouterFunctionsForwardNon200Status() { - // @formatter:off - return route("testforwardnon200status") - .GET("/doforward2", forward("/hello2")) - .before(new LocalServerPortUriResolver()) - .build(); - // @formatter:on - } - - private Predicate eventPredicate(String foo) { - return new Predicate<>() { - @Override - public boolean test(Event event) { - return event.foo().equals(foo); - } - - @Override - public String toString() { - return "Event.foo == " + foo; - } - }; - } - - } - - protected record Hello(String message) { - - } - - protected record Event(String foo, String bar) { - - } - - @RestController - protected static class EventController { - - @PostMapping(path = "/do/events", produces = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity messageEvents(@RequestBody Event e) { - return ResponseEntity.ok().header("X-Foo", e.foo()).body(e); - } - - @PostMapping(path = "/do/events/channel", produces = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity messageChannelEvents(@RequestBody Event e) { - return ResponseEntity.ok().header("X-Channel-Foo", e.foo()).body(e); - } - - } - - @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 = getCount(key); - 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); - } - - AtomicInteger getCount(String key) { - return map.computeIfAbsent(key, s -> new AtomicInteger()); - } - - } - - protected static class TestHandler implements HandlerFunction { - - @Override - public ServerResponse handle(ServerRequest request) { - return ServerResponse.ok().body("Hello"); - } - - @Override - public String toString() { - return "TestHandler Hello"; - } - - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/TokenRelayFilterFunctionsTests.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/TokenRelayFilterFunctionsTests.java deleted file mode 100644 index a0647581..00000000 --- a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/TokenRelayFilterFunctionsTests.java +++ /dev/null @@ -1,114 +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; - -import java.util.Collections; -import java.util.List; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.boot.autoconfigure.http.HttpMessageConverters; -import org.springframework.cloud.gateway.server.mvc.filter.TokenRelayFilterFunctions; -import org.springframework.http.HttpHeaders; -import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockServletContext; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest; -import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; -import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager; -import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; -import org.springframework.security.oauth2.client.registration.ClientRegistration; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.user.OAuth2User; -import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; -import org.springframework.web.context.WebApplicationContext; -import org.springframework.web.servlet.DispatcherServlet; -import org.springframework.web.servlet.function.HandlerFilterFunction; -import org.springframework.web.servlet.function.ServerRequest; -import org.springframework.web.servlet.function.ServerResponse; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class TokenRelayFilterFunctionsTests { - - private final List> converters = new HttpMessageConverters().getConverters(); - - private MockHttpServletRequest request; - - private OAuth2AuthorizedClientManager authorizedClientManager; - - private WebApplicationContext applicationContext; - - private HandlerFilterFunction filter; - - @BeforeEach - @SuppressWarnings("unchecked") - public void init() { - request = MockMvcRequestBuilders.get("/hello").buildRequest(new MockServletContext()); - authorizedClientManager = mock(OAuth2AuthorizedClientManager.class); - applicationContext = mock(WebApplicationContext.class); - request.setAttribute(DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE, applicationContext); - when(applicationContext.getBean(OAuth2AuthorizedClientManager.class)).thenReturn(authorizedClientManager); - filter = TokenRelayFilterFunctions.tokenRelay(); - } - - @Test - public void emptyPrincipal() throws Exception { - filter.filter(ServerRequest.create(request, converters), req -> { - assertThat(req.headers().asHttpHeaders().containsKey(HttpHeaders.AUTHORIZATION)).isFalse(); - return null; - }); - } - - @Test - public void whenPrincipalExistsAuthorizationHeaderAdded() throws Exception { - OAuth2AccessToken accessToken = mock(OAuth2AccessToken.class); - when(accessToken.getTokenValue()).thenReturn("mytoken"); - - ClientRegistration clientRegistration = ClientRegistration.withRegistrationId("myregistrationid") - .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS).clientId("myclientid") - .tokenUri("mytokenuri").build(); - OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(clientRegistration, "joe", accessToken); - - when(authorizedClientManager.authorize(any(OAuth2AuthorizeRequest.class))).thenReturn(authorizedClient); - - OAuth2AuthenticationToken authenticationToken = new OAuth2AuthenticationToken(mock(OAuth2User.class), - Collections.emptyList(), "myId"); - request.setUserPrincipal(authenticationToken); - - filter.filter(ServerRequest.create(request, converters), req -> { - assertThat(req.headers().firstHeader(HttpHeaders.AUTHORIZATION)).isEqualTo("Bearer mytoken"); - return null; - }); - } - - @Test - public void principalIsNotOAuth2AuthenticationToken() throws Exception { - request.setUserPrincipal(new TestingAuthenticationToken("my", null)); - filter.filter(ServerRequest.create(request, converters), req -> { - assertThat(req.headers().asHttpHeaders().containsKey(HttpHeaders.AUTHORIZATION)).isFalse(); - return null; - }); - } - -} 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 deleted file mode 100644 index d8f2bef7..00000000 --- a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/config/GatewayMvcPropertiesBeanDefinitionRegistrarTests.java +++ /dev/null @@ -1,317 +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.config; - -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Function; - -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.boot.test.util.TestPropertyValues; -import org.springframework.cloud.context.refresh.ContextRefresher; -import org.springframework.cloud.gateway.server.mvc.common.MvcUtils; -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.context.ApplicationContext; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.core.io.Resource; -import org.springframework.http.HttpMethod; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.web.servlet.function.HandlerFunction; -import org.springframework.web.servlet.function.RequestPredicate; -import org.springframework.web.servlet.function.RequestPredicates; -import org.springframework.web.servlet.function.RouterFunction; -import org.springframework.web.servlet.function.RouterFunctions; -import org.springframework.web.servlet.function.ServerRequest; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.cloud.gateway.server.mvc.test.TestUtils.getMap; - -@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) -@ActiveProfiles("propertiesbeandefinitionregistrartests") -@ContextConfiguration(initializers = HttpbinTestcontainers.class) -public class GatewayMvcPropertiesBeanDefinitionRegistrarTests { - - @Autowired - TestRestClient restClient; - - @Test - void contextLoads(ApplicationContext context) { - Map routerFunctions = getRouterFunctions(context); - - assertThat(routerFunctions).hasSizeGreaterThanOrEqualTo(5).containsKeys("listRoute1", "route1", - "route2CustomId", "listRoute2", "listRoute3"); - RouterFunction listRoute1RouterFunction = routerFunctions.get("listRoute1"); - listRoute1RouterFunction.accept(new AbstractRouterFunctionsVisitor() { - @Override - public void route(RequestPredicate predicate, HandlerFunction handlerFunction) { - predicate.accept(new AbstractRequestPredicatesVisitor() { - @Override - public void method(Set methods) { - assertThat(methods).containsOnly(HttpMethod.GET); - } - - @Override - public void path(String pattern) { - assertThat(pattern).isEqualTo("/anything/listRoute1"); - } - }); - } - - @Override - public void attributes(Map attributes) { - assertThat(attributes).containsEntry(MvcUtils.GATEWAY_ROUTE_ID_ATTR, "listRoute1"); - } - }); - RouterFunction listRoute2RouterFunction = routerFunctions.get("listRoute2"); - listRoute2RouterFunction.accept(new AbstractRouterFunctionsVisitor() { - @Override - public void route(RequestPredicate predicate, HandlerFunction handlerFunction) { - predicate.accept(new AbstractRequestPredicatesVisitor() { - @Override - public void method(Set methods) { - assertThat(methods).containsOnly(HttpMethod.GET, HttpMethod.POST); - } - }); - } - - @Override - public void attributes(Map attributes) { - assertThat(attributes).containsEntry(MvcUtils.GATEWAY_ROUTE_ID_ATTR, "listRoute2"); - } - }); - RouterFunction listRoute3RouterFunction = routerFunctions.get("listRoute3"); - listRoute3RouterFunction.accept(new AbstractRouterFunctionsVisitor() { - @Override - public void route(RequestPredicate predicate, HandlerFunction handlerFunction) { - predicate.accept(new AbstractRequestPredicatesVisitor() { - @Override - public void path(String pattern) { - assertThat(pattern).isEqualTo("/anything/listRoute3"); - } - - @Override - public void header(String name, String value) { - assertThat(name).isEqualTo("MyHeaderName"); - assertThat(value).isEqualTo("MyHeader.*"); - } - }); - } - - @Override - public void attributes(Map attributes) { - assertThat(attributes).containsEntry(MvcUtils.GATEWAY_ROUTE_ID_ATTR, "listRoute3"); - } - }); - } - - private static Map getRouterFunctions(ApplicationContext context) { - Map allRouterFunctions = context.getBeansOfType(RouterFunction.class); - assertThat(allRouterFunctions).hasSize(1).containsOnlyKeys("gatewayCompositeRouterFunction"); - AtomicReference> routerFunctionsRef = new AtomicReference<>(); - RouterFunction gatewayCompositeRouterFunction = allRouterFunctions.get("gatewayCompositeRouterFunction"); - gatewayCompositeRouterFunction.accept(new AbstractRouterFunctionsVisitor() { - - @Override - @SuppressWarnings("unchecked") - public void attributes(Map attributes) { - if (attributes.containsKey("gatewayRouterFunctions")) { - Map map = (Map) attributes - .get("gatewayRouterFunctions"); - routerFunctionsRef.compareAndSet(null, map); - } - } - - }); - Map routerFunctions = routerFunctionsRef.get(); - return routerFunctions; - } - - @Test - @SuppressWarnings("unchecked") - public void configuredRouteWorks() { - restClient.get().uri("/anything/listRoute1").exchange().expectStatus().isOk().expectBody(Map.class) - .consumeWith(res -> { - Map headers = getMap(res.getResponseBody(), "headers"); - assertThat(headers).containsEntry("X-Test", "listRoute1"); - }); - } - - @Test - @SuppressWarnings("unchecked") - public void lbRouteWorks() { - restClient.get().uri("/anything/listRoute3").header("MyHeaderName", "MyHeaderVal").exchange().expectStatus() - .isOk().expectBody(Map.class).consumeWith(res -> { - Map headers = getMap(res.getResponseBody(), "headers"); - assertThat(headers).containsEntry("X-Test", "listRoute3"); - }); - } - - @Test - @SuppressWarnings({ "rawtypes", "unchecked" }) - void refreshWorks(ConfigurableApplicationContext context) { - Map routerFunctions = getRouterFunctions(context); - assertThat(routerFunctions).hasSize(5); - 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").applyTo(context); - ContextRefresher contextRefresher = context.getBean(ContextRefresher.class); - contextRefresher.refresh(); - // make http call before getRouterFunction() - restClient.get().uri("/anything/mapRoute3").exchange().expectStatus().isOk().expectBody(Map.class) - .consumeWith(res -> { - Map headers = getMap(res.getResponseBody(), "headers"); - assertThat(headers).containsEntry("X-Test", "mapRoute3"); - }); - - GatewayMvcProperties properties = context.getBean(GatewayMvcProperties.class); - assertThat(properties.getRoutesMap()).hasSize(3).containsKey("route3"); - routerFunctions = getRouterFunctions(context); - assertThat(routerFunctions).hasSize(6); - } - - @SpringBootConfiguration - @EnableAutoConfiguration - @LoadBalancerClient(name = "httpbin", configuration = TestLoadBalancerConfig.Httpbin.class) - static class Config { - - } - - static abstract class AbstractRequestPredicatesVisitor implements RequestPredicates.Visitor { - - @Override - public void method(Set methods) { - - } - - @Override - public void path(String pattern) { - - } - - @Override - public void pathExtension(String extension) { - - } - - @Override - public void header(String name, String value) { - - } - - @Override - public void param(String name, String value) { - - } - - @Override - public void unknown(RequestPredicate predicate) { - - } - - @Override - public void startAnd() { - - } - - @Override - public void and() { - - } - - @Override - public void endAnd() { - - } - - @Override - public void startOr() { - - } - - @Override - public void or() { - - } - - @Override - public void endOr() { - - } - - @Override - public void startNegate() { - - } - - @Override - public void endNegate() { - - } - - } - - static abstract class AbstractRouterFunctionsVisitor implements RouterFunctions.Visitor { - - @Override - public void startNested(RequestPredicate predicate) { - - } - - @Override - public void endNested(RequestPredicate predicate) { - - } - - @Override - public void route(RequestPredicate predicate, HandlerFunction handlerFunction) { - - } - - @Override - public void resources(Function> lookupFunction) { - - } - - @Override - public void attributes(Map attributes) { - - } - - @Override - public void unknown(RouterFunction routerFunction) { - - } - - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/filter/ForwardedRequestHeadersFilterTests.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/filter/ForwardedRequestHeadersFilterTests.java deleted file mode 100644 index e3117e53..00000000 --- a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/filter/ForwardedRequestHeadersFilterTests.java +++ /dev/null @@ -1,207 +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.net.UnknownHostException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.junit.jupiter.api.Test; - -import org.springframework.cloud.gateway.server.mvc.filter.ForwardedRequestHeadersFilter.Forwarded; -import org.springframework.http.HttpHeaders; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; -import org.springframework.util.StringUtils; -import org.springframework.web.servlet.function.ServerRequest; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.cloud.gateway.server.mvc.filter.ForwardedRequestHeadersFilter.FORWARDED_HEADER; - -/** - * @author Spencer Gibb - */ -public class ForwardedRequestHeadersFilterTests { - - static Map map(String... values) { - if (values.length % 2 != 0) { - throw new IllegalArgumentException("values must have even number of items: " + Arrays.asList(values)); - } - HashMap map = new HashMap<>(); - for (int i = 0; i < values.length; i++) { - map.put(values[i], values[++i]); - } - return map; - } - - @Test - public void forwardedHeaderDoesNotExist() { - MockHttpServletRequest servletRequest = MockMvcRequestBuilders.get("http://localhost/get") - .remoteAddress("10.0.0.1:80").header(HttpHeaders.HOST, "myhost").buildRequest(null); - servletRequest.setRemoteHost("10.0.0.1"); - ServerRequest request = ServerRequest.create(servletRequest, Collections.emptyList()); - - ForwardedRequestHeadersFilter filter = new ForwardedRequestHeadersFilter(); - - HttpHeaders headers = filter.apply(request.headers().asHttpHeaders(), request); - - assertThat(headers.get(FORWARDED_HEADER)).hasSize(1); - - List forwardeds = ForwardedRequestHeadersFilter.parse(headers.get(FORWARDED_HEADER)); - - assertThat(forwardeds).hasSize(1); - Forwarded forwarded = forwardeds.get(0); - - assertThat(forwarded.getValues()).containsEntry("host", "myhost").containsEntry("proto", "http") - .containsEntry("for", "\"10.0.0.1:80\""); - } - - @Test - public void forwardedHeaderExists() { - MockHttpServletRequest servletRequest = MockMvcRequestBuilders.get("http://localhost/get") - .remoteAddress("10.0.0.1:80") - .header(FORWARDED_HEADER, "for=12.34.56.78;host=example.com;proto=https; for=23.45.67.89") - .buildRequest(null); - servletRequest.setRemoteHost("10.0.0.1"); - ServerRequest request = ServerRequest.create(servletRequest, Collections.emptyList()); - - ForwardedRequestHeadersFilter filter = new ForwardedRequestHeadersFilter(); - - HttpHeaders headers = filter.apply(request.headers().asHttpHeaders(), request); - - assertThat(headers.get(FORWARDED_HEADER)).hasSize(2); - - List forwardeds = ForwardedRequestHeadersFilter.parse(headers.get(FORWARDED_HEADER)); - - assertThat(forwardeds).hasSize(2); - Forwarded addedForwardedHeader = forwardeds.get(0); - Forwarded existingForwardedHeader = forwardeds.get(1); - - assertThat(existingForwardedHeader.getValues()).containsEntry("proto", "http").containsEntry("for", - "\"10.0.0.1:80\""); - - assertThat(addedForwardedHeader.getValues()).containsEntry("proto", "https").containsEntry("for", - "23.45.67.89"); - } - - @Test - public void noHostHeader() throws UnknownHostException { - MockHttpServletRequest servletRequest = MockMvcRequestBuilders.get("http://localhost/get") - .remoteAddress("10.0.0.1:80").buildRequest(null); - servletRequest.setRemoteHost("10.0.0.1"); - ServerRequest request = ServerRequest.create(servletRequest, Collections.emptyList()); - - ForwardedRequestHeadersFilter filter = new ForwardedRequestHeadersFilter(); - - HttpHeaders headers = filter.apply(request.headers().asHttpHeaders(), request); - - assertThat(headers.get(FORWARDED_HEADER)).hasSize(1); - - List forwardeds = ForwardedRequestHeadersFilter.parse(headers.get(FORWARDED_HEADER)); - - assertThat(forwardeds).hasSize(1); - Forwarded forwarded = forwardeds.get(0); - - assertThat(forwarded.getValues()).containsEntry("proto", "http").containsEntry("for", "\"10.0.0.1:80\""); - } - - @Test - public void correctIPv6RemoteAddressMapping() throws UnknownHostException { - MockHttpServletRequest servletRequest = MockMvcRequestBuilders.get("http://localhost/get") - .remoteAddress("2001:db8:cafe:0:0:0:0:17:80").header(HttpHeaders.HOST, "myhost").buildRequest(null); - servletRequest.setRemoteHost("2001:db8:cafe:0:0:0:0:17"); - ServerRequest request = ServerRequest.create(servletRequest, Collections.emptyList()); - - ForwardedRequestHeadersFilter filter = new ForwardedRequestHeadersFilter(); - - HttpHeaders headers = filter.apply(request.headers().asHttpHeaders(), request); - - assertThat(headers.get(FORWARDED_HEADER)).hasSize(1); - - List forwardeds = ForwardedRequestHeadersFilter.parse(headers.get(FORWARDED_HEADER)); - - assertThat(forwardeds).hasSize(1); - Forwarded forwarded = forwardeds.get(0); - - assertThat(forwarded.getValues()).containsEntry("for", "\"[2001:db8:cafe:0:0:0:0:17]:80\""); - } - - @Test - public void unresolvedRemoteAddressFallsBackToHostName() throws UnknownHostException { - MockHttpServletRequest servletRequest = MockMvcRequestBuilders.get("http://localhost/get") - .remoteAddress("unresolvable-hostname:80").buildRequest(null); - servletRequest.setRemoteHost("unresolvable-hostname"); - ServerRequest request = ServerRequest.create(servletRequest, Collections.emptyList()); - - ForwardedRequestHeadersFilter filter = new ForwardedRequestHeadersFilter(); - - HttpHeaders headers = filter.apply(request.headers().asHttpHeaders(), request); - - assertThat(headers.get(FORWARDED_HEADER)).hasSize(1); - - List forwardeds = ForwardedRequestHeadersFilter.parse(headers.get(FORWARDED_HEADER)); - - assertThat(forwardeds).hasSize(1); - Forwarded forwarded = forwardeds.get(0); - - assertThat(forwarded.getValues()).containsEntry("proto", "http").containsEntry("for", - "\"unresolvable-hostname:80\""); - } - - @Test - public void forwardedParsedCorrectly() { - String[] valid = new String[] { "for=\"_gazonk\"", "for=192.0.2.60;proto=http;by=203.0.113.43", - "for=192.0.2.43, for=198.51.100.17", "for=12.34.56.78;host=example.com;proto=https, for=23.45.67.89", - "for=12.34.56.78, for=23.45.67.89;secret=egah2CGj55fSJFs, for=10.1.2.3", - "For=\"[2001:db8:cafe::17]:4711\"", }; - - List>> expectedFor = new ArrayList<>(); - expectedFor.add(Arrays.asList(map("for", "\"_gazonk\""))); - expectedFor.add(Arrays.asList(map("for", "192.0.2.60", "proto", "http", "by", "203.0.113.43"))); - expectedFor.add(Arrays.asList(map("for", "192.0.2.43"), map("for", "198.51.100.17"))); - expectedFor.add(Arrays.asList(map("for", "12.34.56.78", "host", "example.com", "proto", "https"), - map("for", "23.45.67.89"))); - expectedFor.add(Arrays.asList(map("for", "12.34.56.78"), map("for", "23.45.67.89", "secret", "egah2CGj55fSJFs"), - map("for", "10.1.2.3"))); - expectedFor.add(Arrays.asList(map("for", "\"[2001:db8:cafe::17]:4711\""))); - - for (int i = 0; i < valid.length; i++) { - String value = valid[i]; - // simulate spring's parsed headers - String[] values = StringUtils.tokenizeToStringArray(value, ","); - List results = ForwardedRequestHeadersFilter.parse(Arrays.asList(values)); - // System.out.println("results: "+results); - - assertThat(results).hasSize(values.length); - assertThat(results.get(0)).isNotNull(); - - List> expected = expectedFor.get(i); - // System.out.println("expected: "+Arrays.asList(expected)); - assertThat(expected).hasSize(results.size()); - - for (int j = 0; j < results.size(); j++) { - Forwarded forwarded = results.get(j); - assertThat(forwarded.getValues()).hasSize(expected.get(j).size()).containsAllEntriesOf(expected.get(j)); - } - } - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/filter/TransferEncodingNormalizationRequestHeadersFilterTests.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/filter/TransferEncodingNormalizationRequestHeadersFilterTests.java deleted file mode 100644 index b9a8703c..00000000 --- a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/filter/TransferEncodingNormalizationRequestHeadersFilterTests.java +++ /dev/null @@ -1,66 +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.util.Collections; - -import org.junit.jupiter.api.Test; - -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 static org.assertj.core.api.Assertions.assertThat; - -public class TransferEncodingNormalizationRequestHeadersFilterTests { - - @Test - public void noTransferEncodingWithContentLength() { - MockHttpServletRequest servletRequest = MockMvcRequestBuilders.post("http://localhost/post") - .header(HttpHeaders.CONTENT_LENGTH, "6").buildRequest(null); - - HttpHeaders headers = testFilter(ServerRequest.create(servletRequest, Collections.emptyList())); - assertThat(headers).containsKey(HttpHeaders.CONTENT_LENGTH).doesNotContainKey(HttpHeaders.TRANSFER_ENCODING); - } - - @Test - public void transferEncodingWithContentLength() { - MockHttpServletRequest servletRequest = MockMvcRequestBuilders.post("http://localhost/post") - .header(HttpHeaders.CONTENT_LENGTH, "6").header(HttpHeaders.TRANSFER_ENCODING, "chunked") - .buildRequest(null); - - HttpHeaders headers = testFilter(ServerRequest.create(servletRequest, Collections.emptyList())); - assertThat(headers).doesNotContainKey(HttpHeaders.CONTENT_LENGTH).containsKey(HttpHeaders.TRANSFER_ENCODING); - } - - @Test - public void transferEncodingCaseInsensitiveWithContentLength() { - MockHttpServletRequest servletRequest = MockMvcRequestBuilders.post("http://localhost/post") - .header(HttpHeaders.CONTENT_LENGTH, "6").header(HttpHeaders.TRANSFER_ENCODING, "Chunked ") - .buildRequest(null); - - HttpHeaders headers = testFilter(ServerRequest.create(servletRequest, Collections.emptyList())); - assertThat(headers).doesNotContainKey(HttpHeaders.CONTENT_LENGTH).containsKey(HttpHeaders.TRANSFER_ENCODING); - } - - private HttpHeaders testFilter(ServerRequest request) { - TransferEncodingNormalizationRequestHeadersFilter filter = new TransferEncodingNormalizationRequestHeadersFilter(); - return filter.apply(request.headers().asHttpHeaders(), request); - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/filter/WeightCalculatorFilterTests.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/filter/WeightCalculatorFilterTests.java deleted file mode 100644 index d952d0e4..00000000 --- a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/filter/WeightCalculatorFilterTests.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright 2013-2020 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 java.util.function.Supplier; - -import org.junit.jupiter.api.Test; - -import org.springframework.cloud.gateway.server.mvc.common.WeightConfig; -import org.springframework.cloud.gateway.server.mvc.filter.WeightCalculatorFilter.GroupWeightConfig; -import org.springframework.mock.web.MockFilterChain; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; - -import static java.util.Arrays.asList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class WeightCalculatorFilterTests { - - @Test - public void testWeightCalculation() { - WeightCalculatorFilter filter = createFilter(); - - String grp1 = "group1"; - String grp2 = "group2"; - int grp1idx = 1; - int grp2idx = 1; - - assertWeightCalculation(filter, grp1, grp1idx++, 1, asList(1.0)); - assertWeightCalculation(filter, grp2, grp2idx++, 1, asList(1.0)); - assertWeightCalculation(filter, grp1, grp1idx++, 3, asList(0.25, 0.75), 0.25); - assertWeightCalculation(filter, grp2, grp2idx++, 1, asList(0.5, 0.5), 0.5); - assertWeightCalculation(filter, grp1, grp1idx++, 6, asList(0.1, 0.3, 0.6), 0.1, 0.4); - assertWeightCalculation(filter, grp2, grp2idx++, 2, asList(0.25, 0.25, 0.5), 0.25, 0.5); - assertWeightCalculation(filter, grp2, grp2idx++, 4, asList(0.125, 0.125, 0.25, 0.5), 0.125, 0.25, 0.5); - } - - private WeightCalculatorFilter createFilter() { - return new WeightCalculatorFilter(); - } - - private void assertWeightCalculation(WeightCalculatorFilter filter, String group, int item, int weight, - List normalized, Double... middleRanges) { - String routeId = route(item); - - filter.addWeightConfig(new WeightConfig(routeId, group, weight)); - - Map groupWeights = filter.getGroupWeights(); - assertThat(groupWeights).containsKey(group); - - GroupWeightConfig config = groupWeights.get(group); - assertThat(config.group).isEqualTo(group); - assertThat(config.weights).hasSize(item).containsEntry(routeId, weight); - assertThat(config.normalizedWeights).hasSize(item); - - for (int i = 0; i < normalized.size(); i++) { - assertThat(config.normalizedWeights).containsEntry(route(i + 1), normalized.get(i)); - } - - for (int i = 0; i < normalized.size(); i++) { - assertThat(config.rangeIndexes).containsEntry(i, route(i + 1)); - } - - assertThat(config.ranges).hasSize(item + 1).startsWith(0.0).endsWith(1.0); - - if (middleRanges.length > 0) { - assertThat(config.ranges).contains(middleRanges); - } - } - - private String route(int i) { - return "route" + i; - } - - @Test - public void testChooseRouteWithRandom() throws Exception { - WeightCalculatorFilter filter = createFilter(); - filter.addWeightConfig(new WeightConfig("route1", "groupa", 1)); - filter.addWeightConfig(new WeightConfig("route2", "groupa", 3)); - filter.addWeightConfig(new WeightConfig("route3", "groupa", 6)); - - Supplier random = mock(Supplier.class); - - when(random.get()).thenReturn(0.05).thenReturn(0.2).thenReturn(0.6); - - filter.setRandomSupplier(random); - - MockHttpServletRequest request = MockMvcRequestBuilders.get("http://localhost").buildRequest(null); - - MockHttpServletResponse response = new MockHttpServletResponse(); - filter.doFilter(request, response, new MockFilterChain()); - Map weights = WeightCalculatorFilter.getWeights(request); - assertThat(weights).containsEntry("groupa", "route1"); - - filter.doFilter(request, response, new MockFilterChain()); - weights = WeightCalculatorFilter.getWeights(request); - assertThat(weights).containsEntry("groupa", "route2"); - - filter.doFilter(request, response, new MockFilterChain()); - weights = WeightCalculatorFilter.getWeights(request); - assertThat(weights).containsEntry("groupa", "route3"); - } - - // @Test - // public void receivesPredicateArgsEvent() { - // TestWeightCalculatorHandlerInterceptor filter = new - // TestWeightCalculatorHandlerInterceptor(); - // - // HashMap args = new HashMap<>(); - // args.put("weight.group", "group1"); - // args.put("weight.weight", "1"); - // PredicateArgsEvent event = new PredicateArgsEvent(this, "routeA", args); - // filter.handle(event); - // - // WeightConfig weightConfig = filter.weightConfig; - // assertThat(weightConfig.getGroup()).isEqualTo("group1"); - // assertThat(weightConfig.getRouteId()).isEqualTo("routeA"); - // assertThat(weightConfig.getWeight()).isEqualTo(1); - // } - - class TestWeightCalculatorFilter extends WeightCalculatorFilter { - - private WeightConfig weightConfig; - - TestWeightCalculatorFilter() { - super(); - } - - @Override - void addWeightConfig(WeightConfig weightConfig) { - this.weightConfig = weightConfig; - } - - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/filter/WeightRequestPredicateIntegrationTests.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/filter/WeightRequestPredicateIntegrationTests.java deleted file mode 100644 index d33babbd..00000000 --- a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/filter/WeightRequestPredicateIntegrationTests.java +++ /dev/null @@ -1,102 +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.util.function.Supplier; - -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.client.TestRestClient; -import org.springframework.context.annotation.Bean; -import org.springframework.http.HttpHeaders; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.web.servlet.function.RouterFunction; -import org.springframework.web.servlet.function.ServerResponse; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; -import static org.springframework.cloud.gateway.server.mvc.filter.AfterFilterFunctions.addResponseHeader; -import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route; -import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http; -import static org.springframework.cloud.gateway.server.mvc.predicate.GatewayRequestPredicates.host; -import static org.springframework.cloud.gateway.server.mvc.predicate.GatewayRequestPredicates.weight; - -@SpringBootTest(webEnvironment = RANDOM_PORT) -@ContextConfiguration(initializers = HttpbinTestcontainers.class) -@ActiveProfiles("weightrequestpredicateintegrationtests") -public class WeightRequestPredicateIntegrationTests { - - @Autowired - TestRestClient testClient; - - @Autowired - private WeightCalculatorFilter filter; - - private static Supplier getRandom(double value) { - Supplier random = mock(Supplier.class); - when(random.get()).thenReturn(value); - return random; - } - - @Test - public void highWeight() { - filter.setRandomSupplier(getRandom(0.9)); - - testClient.get().uri("/get").header(HttpHeaders.HOST, "www.weight-high.org").exchange().expectStatus().isOk() - .expectHeader().valueEquals("X-Route", "weight_high_test"); - } - - @Test - public void lowWeight() { - filter.setRandomSupplier(getRandom(0.1)); - - testClient.get().uri("/get").header(HttpHeaders.HOST, "www.weight-low.org").exchange().expectStatus().isOk() - .expectHeader().valueEquals("X-Route", "weight_low_test"); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - public static class TestConfig { - - public TestConfig(WeightCalculatorFilter filter) { - Supplier random = getRandom(0.4); - - filter.setRandomSupplier(random); - } - - @Bean - public RouterFunction weightLowRouterFunction() { - // @formatter:off - return route("weight_low_test") - .route(weight("group1", 2).and(host("**.weight-low.org")), http()) - .before(new HttpbinUriResolver()) - .after(addResponseHeader("X-Route", "weight_low_test")) - .build(); - // @formatter:on - } - - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/predicate/PredicateDiscovererTests.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/predicate/PredicateDiscovererTests.java deleted file mode 100644 index 9f330dae..00000000 --- a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/predicate/PredicateDiscovererTests.java +++ /dev/null @@ -1,49 +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.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 PredicateDiscovererTests { - - @Autowired - PredicateDiscoverer 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/HttpbinTestcontainers.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/HttpbinTestcontainers.java deleted file mode 100644 index 8b1f7be9..00000000 --- a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/HttpbinTestcontainers.java +++ /dev/null @@ -1,99 +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.test; - -import java.util.HashMap; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.testcontainers.containers.GenericContainer; -import org.testcontainers.containers.wait.strategy.HttpWaitStrategy; -import org.testcontainers.utility.DockerImageName; - -import org.springframework.context.ApplicationContextInitializer; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.core.env.MapPropertySource; -import org.springframework.core.env.MutablePropertySources; - -public class HttpbinTestcontainers implements ApplicationContextInitializer { - - // https://github.com/mccutchen/go-httpbin - // https://hub.docker.com/r/mccutchen/go-httpbin - private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("mccutchen/go-httpbin"); - - static final Logger logger = LoggerFactory.getLogger(HttpbinTestcontainers.class); - - /** - * Default httpbin port. - */ - public static final int DEFAULT_PORT = 8080; - - /** - * Shared httpbin container. - */ - public static GenericContainer container = createContainer(); - - public static GenericContainer createContainer() { - return new GenericContainer<>(DEFAULT_IMAGE_NAME).withExposedPorts(DEFAULT_PORT) - .waitingFor(new HttpWaitStrategy().forPort(DEFAULT_PORT)); - } - - @Override - public void initialize(ConfigurableApplicationContext context) { - start(); - - MutablePropertySources sources = context.getEnvironment().getPropertySources(); - - if (!sources.contains("httpbinTestcontainer")) { - boolean running = container.isRunning(); - Integer mappedPort = container.getMappedPort(DEFAULT_PORT); - HashMap map = new HashMap<>(); - map.put("httpbin.port", String.valueOf(mappedPort)); - map.put("httpbin.host", container.getHost()); - - sources.addFirst(new MapPropertySource("httpbinTestcontainer", map)); - } - } - - public static void start() { - if (!container.isRunning()) { - container.start(); - } - } - - public static Integer getPort() { - if (!container.isRunning()) { - throw new IllegalStateException("httpbin Testcontainer is not running"); - } - return container.getMappedPort(DEFAULT_PORT); - } - - public static String getHost() { - if (!container.isRunning()) { - throw new IllegalStateException("httpbin Testcontainer is not running"); - } - return container.getHost(); - } - - public static void initializeSystemProperties() { - start(); - - System.setProperty("httpbin.port", getPort().toString()); - System.setProperty("httpbin.host", getHost()); - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/HttpbinUriResolver.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/HttpbinUriResolver.java deleted file mode 100644 index c46e1f25..00000000 --- a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/HttpbinUriResolver.java +++ /dev/null @@ -1,54 +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.test; - -import java.net.URI; -import java.util.function.Function; - -import org.springframework.cloud.gateway.server.mvc.common.MvcUtils; -import org.springframework.context.ApplicationContext; -import org.springframework.util.Assert; -import org.springframework.web.servlet.function.HandlerFilterFunction; -import org.springframework.web.servlet.function.HandlerFunction; -import org.springframework.web.servlet.function.ServerRequest; -import org.springframework.web.servlet.function.ServerResponse; - -public class HttpbinUriResolver - implements Function, HandlerFilterFunction { - - protected URI uri(ServerRequest request) { - ApplicationContext context = MvcUtils.getApplicationContext(request); - Integer port = context.getEnvironment().getProperty("httpbin.port", Integer.class); - String host = context.getEnvironment().getProperty("httpbin.host"); - Assert.hasText(host, "httpbin.host is not set, did you initialize HttpbinTestcontainers?"); - Assert.notNull(port, "httpbin.port is not set, did you initialize HttpbinTestcontainers?"); - return URI.create(String.format("http://%s:%d", host, port)); - } - - @Override - public ServerResponse filter(ServerRequest request, HandlerFunction next) throws Exception { - return next.handle(apply(request)); - } - - @Override - public ServerRequest apply(ServerRequest request) { - URI uri = uri(request); - MvcUtils.setRequestUrl(request, uri); - return request; - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/LocalHostUriBuilderFactory.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/LocalHostUriBuilderFactory.java deleted file mode 100644 index bc05a879..00000000 --- a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/LocalHostUriBuilderFactory.java +++ /dev/null @@ -1,356 +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.test; - -import java.net.URI; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; - -import org.springframework.core.env.Environment; -import org.springframework.lang.Nullable; -import org.springframework.util.Assert; -import org.springframework.util.MultiValueMap; -import org.springframework.util.ObjectUtils; -import org.springframework.web.util.DefaultUriBuilderFactory; -import org.springframework.web.util.UriBuilder; -import org.springframework.web.util.UriBuilderFactory; -import org.springframework.web.util.UriComponents; -import org.springframework.web.util.UriComponentsBuilder; -import org.springframework.web.util.UriUtils; - -public class LocalHostUriBuilderFactory implements UriBuilderFactory { - - private static final String PREFIX = "server.servlet."; - - private final Environment environment; - - private final String scheme; - - private DefaultUriBuilderFactory.EncodingMode encodingMode = DefaultUriBuilderFactory.EncodingMode.TEMPLATE_AND_VALUES; - - private final Map defaultUriVariables = new HashMap<>(); - - private boolean parsePath = true; - - /** - * Create a new {@code LocalHostUriTemplateHandler} that will generate {@code http} - * URIs using the given {@code environment} to determine the context path and port. - * @param environment the environment used to determine the port - */ - public LocalHostUriBuilderFactory(Environment environment) { - this(environment, "http"); - } - - /** - * Create a new {@code LocalHostUriTemplateHandler} that will generate URIs with the - * given {@code scheme} and use the given {@code environment} to determine the - * context-path and port. - * @param environment the environment used to determine the port - * @param scheme the scheme of the root uri - * @since 1.4.1 - */ - public LocalHostUriBuilderFactory(Environment environment, String scheme) { - Assert.notNull(environment, "Environment must not be null"); - Assert.notNull(scheme, "Scheme must not be null"); - this.environment = environment; - this.scheme = scheme; - } - - public String getPort() { - return this.environment.getProperty("local.server.port", "8080"); - } - - public String getContextPath() { - return this.environment.getProperty(PREFIX + "context-path", ""); - } - - public String getScheme() { - return scheme; - } - - /** - * Set the {@link DefaultUriBuilderFactory.EncodingMode encoding mode} to use. - *

- * By default this is set to - * {@link DefaultUriBuilderFactory.EncodingMode#TEMPLATE_AND_VALUES - * EncodingMode.TEMPLATE_AND_VALUES}. - *

- * Note: Prior to 5.1 the default was - * {@link DefaultUriBuilderFactory.EncodingMode#URI_COMPONENT - * EncodingMode.URI_COMPONENT} therefore the {@code WebClient} {@code RestTemplate} - * have switched their default behavior. - * @param encodingMode the encoding mode to use - */ - public void setEncodingMode(DefaultUriBuilderFactory.EncodingMode encodingMode) { - this.encodingMode = encodingMode; - } - - /** - * Return the configured encoding mode. - */ - public DefaultUriBuilderFactory.EncodingMode getEncodingMode() { - return this.encodingMode; - } - - /** - * Provide default URI variable values to use when expanding URI templates with a Map - * of variables. - * @param defaultUriVariables default URI variable values - */ - public void setDefaultUriVariables(@Nullable Map defaultUriVariables) { - this.defaultUriVariables.clear(); - if (defaultUriVariables != null) { - this.defaultUriVariables.putAll(defaultUriVariables); - } - } - - /** - * Return the configured default URI variable values. - */ - public Map getDefaultUriVariables() { - return Collections.unmodifiableMap(this.defaultUriVariables); - } - - /** - * Whether to parse the input path into path segments if the encoding mode is set to - * {@link DefaultUriBuilderFactory.EncodingMode#URI_COMPONENT - * EncodingMode.URI_COMPONENT}, which ensures that URI variables in the path are - * encoded according to path segment rules and for example a '/' is encoded. - *

- * By default this is set to {@code true}. - * @param parsePath whether to parse the path into path segments - */ - public void setParsePath(boolean parsePath) { - this.parsePath = parsePath; - } - - /** - * Whether to parse the path into path segments if the encoding mode is set to - * {@link DefaultUriBuilderFactory.EncodingMode#URI_COMPONENT - * EncodingMode.URI_COMPONENT}. - */ - public boolean shouldParsePath() { - return this.parsePath; - } - - @Override - public UriBuilder uriString(String uriTemplate) { - return new DefaultUriBuilder(uriTemplate); - } - - @Override - public UriBuilder builder() { - return new DefaultUriBuilder(""); - } - - @Override - public URI expand(String uriTemplate, Map uriVars) { - return uriString(uriTemplate).build(uriVars); - - } - - @Override - public URI expand(String uriTemplate, Object... uriVars) { - return uriString(uriTemplate).build(uriVars); - } - - /** - * {@link DefaultUriBuilderFactory} specific implementation of UriBuilder. - */ - private class DefaultUriBuilder implements UriBuilder { - - private final UriComponentsBuilder uriComponentsBuilder; - - DefaultUriBuilder(String uriTemplate) { - this.uriComponentsBuilder = initUriComponentsBuilder(uriTemplate); - } - - private UriComponentsBuilder initUriComponentsBuilder(String uriTemplate) { - UriComponentsBuilder result = UriComponentsBuilder.fromUriString(uriTemplate); - if (encodingMode.equals(DefaultUriBuilderFactory.EncodingMode.TEMPLATE_AND_VALUES)) { - result.encode(); - } - parsePathIfNecessary(result); - return result; - } - - private void parsePathIfNecessary(UriComponentsBuilder result) { - if (parsePath && encodingMode.equals(DefaultUriBuilderFactory.EncodingMode.URI_COMPONENT)) { - UriComponents uric = result.build(); - String path = uric.getPath(); - result.replacePath(null); - for (String segment : uric.getPathSegments()) { - result.pathSegment(segment); - } - if (path != null && path.endsWith("/")) { - result.path("/"); - } - } - } - - @Override - public DefaultUriBuilder scheme(@Nullable String scheme) { - this.uriComponentsBuilder.scheme(scheme); - return this; - } - - @Override - public DefaultUriBuilder userInfo(@Nullable String userInfo) { - this.uriComponentsBuilder.userInfo(userInfo); - return this; - } - - @Override - public DefaultUriBuilder host(@Nullable String host) { - this.uriComponentsBuilder.host(host); - return this; - } - - @Override - public DefaultUriBuilder port(int port) { - this.uriComponentsBuilder.port(port); - return this; - } - - @Override - public DefaultUriBuilder port(@Nullable String port) { - this.uriComponentsBuilder.port(port); - return this; - } - - @Override - public DefaultUriBuilder path(String path) { - this.uriComponentsBuilder.path(path); - return this; - } - - @Override - public DefaultUriBuilder replacePath(@Nullable String path) { - this.uriComponentsBuilder.replacePath(path); - return this; - } - - @Override - public DefaultUriBuilder pathSegment(String... pathSegments) { - this.uriComponentsBuilder.pathSegment(pathSegments); - return this; - } - - @Override - public DefaultUriBuilder query(String query) { - this.uriComponentsBuilder.query(query); - return this; - } - - @Override - public DefaultUriBuilder replaceQuery(@Nullable String query) { - this.uriComponentsBuilder.replaceQuery(query); - return this; - } - - @Override - public DefaultUriBuilder queryParam(String name, Object... values) { - this.uriComponentsBuilder.queryParam(name, values); - return this; - } - - @Override - public DefaultUriBuilder queryParam(String name, @Nullable Collection values) { - this.uriComponentsBuilder.queryParam(name, values); - return this; - } - - @Override - public DefaultUriBuilder queryParamIfPresent(String name, Optional value) { - this.uriComponentsBuilder.queryParamIfPresent(name, value); - return this; - } - - @Override - public DefaultUriBuilder queryParams(MultiValueMap params) { - this.uriComponentsBuilder.queryParams(params); - return this; - } - - @Override - public DefaultUriBuilder replaceQueryParam(String name, Object... values) { - this.uriComponentsBuilder.replaceQueryParam(name, values); - return this; - } - - @Override - public DefaultUriBuilder replaceQueryParam(String name, @Nullable Collection values) { - this.uriComponentsBuilder.replaceQueryParam(name, values); - return this; - } - - @Override - public DefaultUriBuilder replaceQueryParams(MultiValueMap params) { - this.uriComponentsBuilder.replaceQueryParams(params); - return this; - } - - @Override - public DefaultUriBuilder fragment(@Nullable String fragment) { - this.uriComponentsBuilder.fragment(fragment); - return this; - } - - @Override - public URI build(Map uriVars) { - if (!defaultUriVariables.isEmpty()) { - Map map = new HashMap<>(); - map.putAll(defaultUriVariables); - map.putAll(uriVars); - uriVars = map; - } - if (encodingMode.equals(DefaultUriBuilderFactory.EncodingMode.VALUES_ONLY)) { - uriVars = UriUtils.encodeUriVariables(uriVars); - } - - this.uriComponentsBuilder.scheme(getScheme()); - this.uriComponentsBuilder.port(getPort()); - this.uriComponentsBuilder.replacePath(getContextPath()); - UriComponents uric = this.uriComponentsBuilder.build().expand(uriVars); - return createUri(uric); - } - - @Override - public URI build(Object... uriVars) { - if (ObjectUtils.isEmpty(uriVars) && !defaultUriVariables.isEmpty()) { - return build(Collections.emptyMap()); - } - if (encodingMode.equals(DefaultUriBuilderFactory.EncodingMode.VALUES_ONLY)) { - uriVars = UriUtils.encodeUriVariables(uriVars); - } - UriComponents uric = this.uriComponentsBuilder.build().expand(uriVars); - return createUri(uric); - } - - private URI createUri(UriComponents uric) { - if (encodingMode.equals(DefaultUriBuilderFactory.EncodingMode.URI_COMPONENT)) { - uric = uric.encode(); - } - return URI.create(uric.toString()); - } - - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/LocalServerPortUriResolver.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/LocalServerPortUriResolver.java deleted file mode 100644 index 1dac429e..00000000 --- a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/LocalServerPortUriResolver.java +++ /dev/null @@ -1,50 +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.test; - -import java.net.URI; -import java.util.function.Function; - -import org.springframework.cloud.gateway.server.mvc.common.MvcUtils; -import org.springframework.context.ApplicationContext; -import org.springframework.web.servlet.function.HandlerFilterFunction; -import org.springframework.web.servlet.function.HandlerFunction; -import org.springframework.web.servlet.function.ServerRequest; -import org.springframework.web.servlet.function.ServerResponse; - -public class LocalServerPortUriResolver - implements Function, HandlerFilterFunction { - - protected URI uri(ServerRequest request) { - ApplicationContext context = MvcUtils.getApplicationContext(request); - Integer port = context.getEnvironment().getProperty("local.server.port", Integer.class); - return URI.create("http://localhost:" + port); - } - - @Override - public ServerResponse filter(ServerRequest request, HandlerFunction next) throws Exception { - return next.handle(apply(request)); - } - - @Override - public ServerRequest apply(ServerRequest request) { - URI uri = uri(request); - MvcUtils.setRequestUrl(request, uri); - return request; - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/TestAutoConfiguration.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/TestAutoConfiguration.java deleted file mode 100644 index aeadee62..00000000 --- a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/TestAutoConfiguration.java +++ /dev/null @@ -1,59 +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.test; - -import java.util.List; - -import org.springframework.boot.autoconfigure.AutoConfiguration; -import org.springframework.boot.test.web.client.TestRestTemplate; -import org.springframework.boot.web.client.RestTemplateCustomizer; -import org.springframework.cloud.gateway.server.mvc.GatewayServerMvcAutoConfiguration; -import org.springframework.cloud.gateway.server.mvc.test.client.DefaultTestRestClient; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.core.env.Environment; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; -import org.springframework.http.client.ClientHttpRequestFactory; - -@AutoConfiguration(after = GatewayServerMvcAutoConfiguration.class) -public class TestAutoConfiguration { - - @Bean - RestTemplateCustomizer testRestClientRestTemplateCustomizer(ApplicationContext context) { - return restTemplate -> { - restTemplate.setRequestFactory(context.getBean(ClientHttpRequestFactory.class)); - restTemplate.setClientHttpRequestInitializers(List.of(request -> { - if (!request.getHeaders().containsKey(HttpHeaders.ACCEPT)) { - request.getHeaders().setAccept(List.of(MediaType.ALL)); - } - })); - }; - } - - @Bean - public DefaultTestRestClient testRestClient(TestRestTemplate testRestTemplate, Environment env) { - return new DefaultTestRestClient(testRestTemplate, new LocalHostUriBuilderFactory(env), result -> { - }); - } - - @Bean - public TestController testController() { - return new TestController(); - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/TestController.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/TestController.java deleted file mode 100644 index d65d50f9..00000000 --- a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/TestController.java +++ /dev/null @@ -1,131 +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.test; - -import java.io.IOException; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; - -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.util.MultiValueMap; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestHeader; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.multipart.MultipartFile; -import org.springframework.web.server.ServerWebExchange; - -@RestController -@RequestMapping("/test") -public class TestController { - - private static final String HEADER_REQ_VARY = "X-Request-Vary"; - - @GetMapping("/") - public String home() { - return "test controller home"; - } - - @RequestMapping(path = "/multivalueheaders", method = { RequestMethod.GET, RequestMethod.POST }, - produces = MediaType.APPLICATION_JSON_VALUE) - public Map multiValueHeaders(ServerWebExchange exchange) { - Map result = new HashMap<>(); - result.put("headers", exchange.getRequest().getHeaders()); - return result; - } - - @PostMapping(value = "/post", consumes = MediaType.MULTIPART_FORM_DATA_VALUE, - produces = MediaType.APPLICATION_JSON_VALUE) - public Map postFormData(HttpServletRequest request, - @RequestParam MultiValueMap parts) throws ServletException, IOException { - HashMap ret = new HashMap<>(); - ret.put("headers", getHeaders(request)); - HashMap files = new HashMap<>(); - ret.put("files", files); - - parts.values().stream().flatMap(List::stream).forEach(part -> { - String contentType = part.getContentType(); - long contentLength = part.getSize(); - // TODO: get part data - files.put(part.getName(), "data:" + contentType + ";base64," + contentLength); - }); - return ret; - } - - @PostMapping(path = "/post", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, - produces = MediaType.APPLICATION_JSON_VALUE) - public Map postUrlEncoded(HttpServletRequest request, - @RequestBody(required = false) MultiValueMap form) throws IOException { - HashMap ret = new HashMap<>(); - ret.put("headers", getHeaders(request)); - ret.put("form", form); - return ret; - } - - @PostMapping(path = "/post", produces = MediaType.APPLICATION_JSON_VALUE) - public Map post(HttpServletRequest request, @RequestBody(required = false) String body) { - HashMap ret = new HashMap<>(); - ret.put("headers", getHeaders(request)); - ret.put("data", body); - HashMap form = new HashMap<>(); - ret.put("form", form); - return ret; - } - - @GetMapping("/vary-on-header/**") - public ResponseEntity> varyOnAccept(HttpServletRequest request, - @RequestHeader(name = HEADER_REQ_VARY, required = false) String headerToVary) { - if (headerToVary == null) { - return ResponseEntity.badRequest().body(Map.of("error", HEADER_REQ_VARY + " header is mandatory")); - } - else { - var builder = ResponseEntity.ok(); - builder.varyBy(headerToVary); - Map result = new HashMap<>(); - result.put("headers", getHeaders(request)); - return builder.body(result); - } - } - - public Map getHeaders(HttpServletRequest req) { - HashMap headers = new HashMap<>(); - Enumeration headerNames = req.getHeaderNames(); - - while (headerNames.hasMoreElements()) { - String name = headerNames.nextElement(); - String value = null; - - Enumeration values = req.getHeaders(name); - if (values.hasMoreElements()) { - value = values.nextElement(); - } - headers.put(name, value); - } - return headers; - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/TestFilterSupplier.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/TestFilterSupplier.java deleted file mode 100644 index 41ad6a76..00000000 --- a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/TestFilterSupplier.java +++ /dev/null @@ -1,42 +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.test; - -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.Collection; - -import org.springframework.cloud.gateway.server.mvc.filter.FilterSupplier; -import org.springframework.web.servlet.function.HandlerFilterFunction; -import org.springframework.web.servlet.function.ServerResponse; - -public class TestFilterSupplier implements FilterSupplier { - - @Override - public Collection get() { - return Arrays.asList(TestFilterSupplier.class.getMethods()); - } - - public static HandlerFilterFunction localServerPortUriResolver() { - return new LocalServerPortUriResolver(); - } - - public static HandlerFilterFunction httpbinUriResolver() { - return new HttpbinUriResolver(); - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/TestLoadBalancerConfig.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/TestLoadBalancerConfig.java deleted file mode 100644 index 463d1d63..00000000 --- a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/TestLoadBalancerConfig.java +++ /dev/null @@ -1,50 +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.test; - -import org.springframework.boot.test.web.server.LocalServerPort; -import org.springframework.cloud.client.DefaultServiceInstance; -import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier; -import org.springframework.cloud.loadbalancer.support.ServiceInstanceListSuppliers; -import org.springframework.context.annotation.Bean; - -public class TestLoadBalancerConfig { - - public static class Httpbin { - - @Bean - public ServiceInstanceListSupplier staticServiceInstanceListSupplier() { - return ServiceInstanceListSuppliers.from("httpbin", new DefaultServiceInstance("httpbin" + "-1", "httpbin", - HttpbinTestcontainers.getHost(), HttpbinTestcontainers.getPort(), false)); - } - - } - - public static class Local { - - @LocalServerPort - protected int port = 0; - - @Bean - public ServiceInstanceListSupplier staticServiceInstanceListSupplier() { - return ServiceInstanceListSuppliers.from("testservice", - new DefaultServiceInstance("testservice" + "-1", "testservice", "localhost", port, false)); - } - - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/TestUtils.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/TestUtils.java deleted file mode 100644 index 9aef6ac5..00000000 --- a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/TestUtils.java +++ /dev/null @@ -1,49 +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.test; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static org.assertj.core.api.Assertions.assertThat; - -public class TestUtils { - - public static Map getMap(Map map, String mapKey) { - assertThat(map).isNotEmpty().containsKey(mapKey); - Map headers = (Map) map.get(mapKey); - Map newHeaders = new HashMap<>(); - - // flatten map as mccutchen/go-httpbin only gets lists - headers.forEach((key, value) -> { - if (value instanceof List values) { - if (values.size() == 1) { - newHeaders.put(key, values.get(0)); - } - else { - newHeaders.put(key, values); - } - } - else { - newHeaders.put(key, value); - } - }); - return newHeaders; - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/client/CookieAssertions.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/client/CookieAssertions.java deleted file mode 100644 index 3634f96c..00000000 --- a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/client/CookieAssertions.java +++ /dev/null @@ -1,220 +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.test.client; - -import java.time.Duration; -import java.util.Objects; -import java.util.function.Consumer; - -import org.hamcrest.Matcher; -import org.hamcrest.MatcherAssert; - -import org.springframework.http.ResponseCookie; -import org.springframework.test.util.AssertionErrors; - -import static org.hamcrest.MatcherAssert.assertThat; - -/** - * Assertions on cookies of the response. - * - * @author Rossen Stoyanchev - * @since 5.3 - */ -public class CookieAssertions { - - private final ExchangeResult exchangeResult; - - private final TestRestClient.ResponseSpec responseSpec; - - public CookieAssertions(ExchangeResult exchangeResult, TestRestClient.ResponseSpec responseSpec) { - this.exchangeResult = exchangeResult; - this.responseSpec = responseSpec; - } - - /** - * Expect a header with the given name to match the specified values. - */ - public TestRestClient.ResponseSpec valueEquals(String name, String value) { - this.exchangeResult.assertWithDiagnostics(() -> { - String message = getMessage(name); - AssertionErrors.assertEquals(message, value, getCookie(name).getValue()); - }); - return this.responseSpec; - } - - /** - * Assert the first value of the response cookie with a Hamcrest {@link Matcher}. - */ - public TestRestClient.ResponseSpec value(String name, Matcher matcher) { - String value = getCookie(name).getValue(); - this.exchangeResult.assertWithDiagnostics(() -> { - String message = getMessage(name); - MatcherAssert.assertThat(message, value, matcher); - }); - return this.responseSpec; - } - - /** - * Consume the value of the response cookie. - */ - public TestRestClient.ResponseSpec value(String name, Consumer consumer) { - String value = getCookie(name).getValue(); - this.exchangeResult.assertWithDiagnostics(() -> consumer.accept(value)); - return this.responseSpec; - } - - /** - * Expect that the cookie with the given name is present. - */ - public TestRestClient.ResponseSpec exists(String name) { - getCookie(name); - return this.responseSpec; - } - - /** - * Expect that the cookie with the given name is not present. - */ - public TestRestClient.ResponseSpec doesNotExist(String name) { - ResponseCookie cookie = this.exchangeResult.getResponseCookies().getFirst(name); - if (cookie != null) { - String message = getMessage(name) + " exists with value=[" + cookie.getValue() + "]"; - this.exchangeResult.assertWithDiagnostics(() -> AssertionErrors.fail(message)); - } - return this.responseSpec; - } - - /** - * Assert a cookie's maxAge attribute. - */ - public TestRestClient.ResponseSpec maxAge(String name, Duration expected) { - Duration maxAge = getCookie(name).getMaxAge(); - this.exchangeResult.assertWithDiagnostics(() -> { - String message = getMessage(name) + " maxAge"; - AssertionErrors.assertEquals(message, expected, maxAge); - }); - return this.responseSpec; - } - - /** - * Assert a cookie's maxAge attribute with a Hamcrest {@link Matcher}. - */ - public TestRestClient.ResponseSpec maxAge(String name, Matcher matcher) { - long maxAge = getCookie(name).getMaxAge().getSeconds(); - this.exchangeResult.assertWithDiagnostics(() -> { - String message = getMessage(name) + " maxAge"; - assertThat(message, maxAge, matcher); - }); - return this.responseSpec; - } - - /** - * Assert a cookie's path attribute. - */ - public TestRestClient.ResponseSpec path(String name, String expected) { - String path = getCookie(name).getPath(); - this.exchangeResult.assertWithDiagnostics(() -> { - String message = getMessage(name) + " path"; - AssertionErrors.assertEquals(message, expected, path); - }); - return this.responseSpec; - } - - /** - * Assert a cookie's path attribute with a Hamcrest {@link Matcher}. - */ - public TestRestClient.ResponseSpec path(String name, Matcher matcher) { - String path = getCookie(name).getPath(); - this.exchangeResult.assertWithDiagnostics(() -> { - String message = getMessage(name) + " path"; - assertThat(message, path, matcher); - }); - return this.responseSpec; - } - - /** - * Assert a cookie's domain attribute. - */ - public TestRestClient.ResponseSpec domain(String name, String expected) { - String path = getCookie(name).getDomain(); - this.exchangeResult.assertWithDiagnostics(() -> { - String message = getMessage(name) + " domain"; - AssertionErrors.assertEquals(message, expected, path); - }); - return this.responseSpec; - } - - /** - * Assert a cookie's domain attribute with a Hamcrest {@link Matcher}. - */ - public TestRestClient.ResponseSpec domain(String name, Matcher matcher) { - String domain = getCookie(name).getDomain(); - this.exchangeResult.assertWithDiagnostics(() -> { - String message = getMessage(name) + " domain"; - assertThat(message, domain, matcher); - }); - return this.responseSpec; - } - - /** - * Assert a cookie's secure attribute. - */ - public TestRestClient.ResponseSpec secure(String name, boolean expected) { - boolean isSecure = getCookie(name).isSecure(); - this.exchangeResult.assertWithDiagnostics(() -> { - String message = getMessage(name) + " secure"; - AssertionErrors.assertEquals(message, expected, isSecure); - }); - return this.responseSpec; - } - - /** - * Assert a cookie's httpOnly attribute. - */ - public TestRestClient.ResponseSpec httpOnly(String name, boolean expected) { - boolean isHttpOnly = getCookie(name).isHttpOnly(); - this.exchangeResult.assertWithDiagnostics(() -> { - String message = getMessage(name) + " httpOnly"; - AssertionErrors.assertEquals(message, expected, isHttpOnly); - }); - return this.responseSpec; - } - - /** - * Assert a cookie's sameSite attribute. - */ - public TestRestClient.ResponseSpec sameSite(String name, String expected) { - String sameSite = getCookie(name).getSameSite(); - this.exchangeResult.assertWithDiagnostics(() -> { - String message = getMessage(name) + " sameSite"; - AssertionErrors.assertEquals(message, expected, sameSite); - }); - return this.responseSpec; - } - - private ResponseCookie getCookie(String name) { - ResponseCookie cookie = this.exchangeResult.getResponseCookies().getFirst(name); - if (cookie == null) { - this.exchangeResult.assertWithDiagnostics(() -> AssertionErrors.fail("No cookie with name '" + name + "'")); - } - return Objects.requireNonNull(cookie); - } - - private String getMessage(String cookie) { - return "Response cookie '" + cookie + "'"; - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/client/DefaultTestRestClient.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/client/DefaultTestRestClient.java deleted file mode 100644 index 0f2db1ec..00000000 --- a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/client/DefaultTestRestClient.java +++ /dev/null @@ -1,589 +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.test.client; - -import java.io.IOException; -import java.net.URI; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.time.ZonedDateTime; -import java.util.Arrays; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.atomic.AtomicLong; -import java.util.function.Consumer; -import java.util.function.Function; - -import org.hamcrest.Matcher; -import org.hamcrest.MatcherAssert; - -import org.springframework.boot.test.web.client.TestRestTemplate; -import org.springframework.core.ParameterizedTypeReference; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.MediaType; -import org.springframework.http.RequestEntity; -import org.springframework.http.ResponseEntity; -import org.springframework.http.client.reactive.ClientHttpRequest; -import org.springframework.lang.Nullable; -import org.springframework.mock.http.client.MockClientHttpResponse; -import org.springframework.test.util.AssertionErrors; -import org.springframework.test.util.ExceptionCollector; -import org.springframework.test.util.JsonExpectationsHelper; -import org.springframework.test.util.XmlExpectationsHelper; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MimeType; -import org.springframework.util.MultiValueMap; -import org.springframework.util.ObjectUtils; -import org.springframework.web.client.HttpMessageConverterExtractor; -import org.springframework.web.util.UriBuilder; -import org.springframework.web.util.UriBuilderFactory; - -public class DefaultTestRestClient implements TestRestClient { - - private final TestRestTemplate testRestTemplate; - - private final UriBuilderFactory uriBuilderFactory; - - private final Consumer> entityResultConsumer; - - private final AtomicLong requestIndex = new AtomicLong(); - - public DefaultTestRestClient(TestRestTemplate testRestTemplate, UriBuilderFactory uriBuilderFactory, - Consumer> entityResultConsumer) { - this.uriBuilderFactory = uriBuilderFactory; - this.testRestTemplate = testRestTemplate; - this.entityResultConsumer = entityResultConsumer; - } - - @Override - public RequestHeadersUriSpec get() { - return methodInternal(HttpMethod.GET); - } - - @Override - public RequestHeadersUriSpec head() { - return methodInternal(HttpMethod.HEAD); - } - - @Override - public RequestBodyUriSpec post() { - return methodInternal(HttpMethod.POST); - } - - @Override - public RequestBodyUriSpec put() { - return methodInternal(HttpMethod.PUT); - } - - @Override - public RequestBodyUriSpec patch() { - return methodInternal(HttpMethod.PATCH); - } - - @Override - public RequestHeadersUriSpec delete() { - return methodInternal(HttpMethod.DELETE); - } - - @Override - public RequestHeadersUriSpec options() { - return methodInternal(HttpMethod.OPTIONS); - } - - private RequestBodyUriSpec methodInternal(HttpMethod httpMethod) { - return new DefaultRequestBodyUriSpec(httpMethod); - } - - private class DefaultRequestBodyUriSpec implements RequestBodyUriSpec { - - private final HttpMethod httpMethod; - - @Nullable - private URI uri; - - private final HttpHeaders headers; - - @Nullable - private MultiValueMap cookies; - - private final Map attributes = new LinkedHashMap<>(4); - - @Nullable - private Consumer httpRequestConsumer; - - @Nullable - private String uriTemplate; - - private final String requestId; - - private Object body; - - private Class type; - - DefaultRequestBodyUriSpec(HttpMethod httpMethod) { - this.httpMethod = httpMethod; - this.requestId = String.valueOf(requestIndex.incrementAndGet()); - this.headers = new HttpHeaders(); - this.headers.add(TESTRESTCLIENT_REQUEST_ID, this.requestId); - } - - @Override - public RequestBodySpec uri(String uriTemplate, Object... uriVariables) { - this.uriTemplate = uriTemplate; - return uri(uriBuilderFactory.expand(uriTemplate, uriVariables)); - } - - @Override - public RequestBodySpec uri(String uriTemplate, Map uriVariables) { - this.uriTemplate = uriTemplate; - return uri(uriBuilderFactory.expand(uriTemplate, uriVariables)); - } - - @Override - public RequestBodySpec uri(Function uriFunction) { - this.uriTemplate = null; - return uri(uriFunction.apply(uriBuilderFactory.builder())); - } - - @Override - public RequestBodySpec uri(URI uri) { - this.uri = uri; - return this; - } - - private HttpHeaders getHeaders() { - return this.headers; - } - - private MultiValueMap getCookies() { - if (this.cookies == null) { - this.cookies = new LinkedMultiValueMap<>(3); - } - return this.cookies; - } - - @Override - public RequestBodySpec header(String headerName, String... headerValues) { - for (String headerValue : headerValues) { - getHeaders().add(headerName, headerValue); - } - return this; - } - - @Override - public RequestBodySpec headers(Consumer headersConsumer) { - headersConsumer.accept(getHeaders()); - return this; - } - - @Override - public RequestBodySpec attribute(String name, Object value) { - this.attributes.put(name, value); - return this; - } - - @Override - public RequestBodySpec attributes(Consumer> attributesConsumer) { - attributesConsumer.accept(this.attributes); - return this; - } - - @Override - public RequestBodySpec accept(MediaType... acceptableMediaTypes) { - getHeaders().setAccept(Arrays.asList(acceptableMediaTypes)); - return this; - } - - @Override - public RequestBodySpec acceptCharset(Charset... acceptableCharsets) { - getHeaders().setAcceptCharset(Arrays.asList(acceptableCharsets)); - return this; - } - - @Override - public RequestBodySpec contentType(MediaType contentType) { - getHeaders().setContentType(contentType); - return this; - } - - @Override - public RequestBodySpec contentLength(long contentLength) { - getHeaders().setContentLength(contentLength); - return this; - } - - @Override - public RequestBodySpec cookie(String name, String value) { - getCookies().add(name, value); - return this; - } - - @Override - public RequestBodySpec cookies(Consumer> cookiesConsumer) { - cookiesConsumer.accept(getCookies()); - return this; - } - - @Override - public RequestBodySpec ifModifiedSince(ZonedDateTime ifModifiedSince) { - getHeaders().setIfModifiedSince(ifModifiedSince); - return this; - } - - @Override - public RequestBodySpec ifNoneMatch(String... ifNoneMatches) { - getHeaders().setIfNoneMatch(Arrays.asList(ifNoneMatches)); - return this; - } - - @Override - public RequestHeadersSpec bodyValue(Object body) { - this.body = body; - return this; - } - - @Override - public RequestHeadersSpec body(Object body, Class elementClass) { - this.body = body; - this.type = elementClass; - return this; - } - - @Override - public RequestHeadersSpec body(Object body, ParameterizedTypeReference elementTypeRef) { - this.body = body; - return this; - } - - @Override - public ResponseSpec exchange() { - HttpHeaders combinedHeaders = new HttpHeaders(); - combinedHeaders.putAll(getHeaders()); - if (!ObjectUtils.isEmpty(cookies)) { - cookies.forEach((name, values) -> { - values.forEach(value -> { - combinedHeaders.add("Cookie", name + "=" + value); - }); - }); - } - RequestEntity.BodyBuilder builder = RequestEntity.method(httpMethod, uri).headers(combinedHeaders); - RequestEntity request = builder.body(body, type); - ResponseEntity response = testRestTemplate.exchange(request, byte[].class); - ExchangeResult exchangeResult = new ExchangeResult(request, response); - return new DefaultResponseSpec(exchangeResult, response, DefaultTestRestClient.this.entityResultConsumer); - } - - } - - private class DefaultResponseSpec implements ResponseSpec { - - private final ExchangeResult exchangeResult; - - private final ResponseEntity responseEntity; - - private final Consumer> entityResultConsumer; - - DefaultResponseSpec(ExchangeResult exchangeResult, ResponseEntity responseEntity, - Consumer> entityResultConsumer) { - this.exchangeResult = exchangeResult; - this.responseEntity = responseEntity; - this.entityResultConsumer = entityResultConsumer; - } - - @Override - public StatusAssertions expectStatus() { - return new StatusAssertions(this.exchangeResult, this); - } - - @Override - public HeaderAssertions expectHeader() { - return new HeaderAssertions(this.exchangeResult, this); - } - - @Override - public CookieAssertions expectCookie() { - return new CookieAssertions(this.exchangeResult, this); - } - - @Override - public BodySpec expectBody(Class bodyType) { - HttpMessageConverterExtractor httpMessageConverterExtractor = new HttpMessageConverterExtractor<>( - bodyType, DefaultTestRestClient.this.testRestTemplate.getRestTemplate().getMessageConverters()); - try { - MockClientHttpResponse mockResponse = new MockClientHttpResponse(this.responseEntity.getBody(), - this.responseEntity.getStatusCode()); - mockResponse.getHeaders().putAll(this.responseEntity.getHeaders()); - B body = httpMessageConverterExtractor.extractData(mockResponse); - EntityExchangeResult entityResult = initEntityExchangeResult(body); - return new DefaultBodySpec<>(entityResult); - } - catch (IOException e) { - throw new RuntimeException(e); - } - } - - @Override - public BodySpec expectBody(ParameterizedTypeReference bodyType) { - throw new UnsupportedOperationException("expectBody(ParameterizedTypeReference bodyType)"); - /* - * B body = - * DefaultConversionService.getSharedInstance().convert(this.responseEntity. - * getBody(), bodyType); EntityExchangeResult entityResult = - * initEntityExchangeResult(body); return new DefaultBodySpec<>(entityResult); - */ - } - - private EntityExchangeResult initEntityExchangeResult(@Nullable B body) { - EntityExchangeResult result = new EntityExchangeResult<>(this.exchangeResult, body); - result.assertWithDiagnostics(() -> this.entityResultConsumer.accept(result)); - return result; - } - - @Override - public ListBodySpec expectBodyList(Class elementType) { - return null; - } - - @Override - public ListBodySpec expectBodyList(ParameterizedTypeReference elementType) { - return null; - } - - @Override - public BodyContentSpec expectBody() { - return new DefaultBodyContentSpec(null); - } - - /* - * @Override public FluxExchangeResult returnResult(Class elementClass) - * { return null; } - * - * @Override public FluxExchangeResult - * returnResult(ParameterizedTypeReference elementTypeRef) { return null; } - */ - - @Override - public TestRestClient.ResponseSpec expectAll(TestRestClient.ResponseSpec.ResponseSpecConsumer... consumers) { - ExceptionCollector exceptionCollector = new ExceptionCollector(); - for (TestRestClient.ResponseSpec.ResponseSpecConsumer consumer : consumers) { - exceptionCollector.execute(() -> consumer.accept(this)); - } - try { - exceptionCollector.assertEmpty(); - } - catch (RuntimeException ex) { - throw ex; - } - catch (Exception ex) { - // In theory, a ResponseSpecConsumer should never throw an Exception - // that is not a RuntimeException, but since ExceptionCollector may - // throw a checked Exception, we handle this to appease the compiler - // and in case someone uses a "sneaky throws" technique. - AssertionError assertionError = new AssertionError(ex.getMessage()); - assertionError.initCause(ex); - throw assertionError; - } - return this; - } - - } - - private static class DefaultBodySpec> - implements TestRestClient.BodySpec { - - private final EntityExchangeResult result; - - DefaultBodySpec(EntityExchangeResult result) { - this.result = result; - } - - protected EntityExchangeResult getResult() { - return this.result; - } - - @Override - public T isEqualTo(B expected) { - this.result.assertWithDiagnostics( - () -> AssertionErrors.assertEquals("Response body", expected, this.result.getResponseBody())); - return self(); - } - - @Override - public T value(Matcher matcher) { - this.result.assertWithDiagnostics(() -> MatcherAssert.assertThat(this.result.getResponseBody(), matcher)); - return self(); - } - - @Override - public T value(Function bodyMapper, Matcher matcher) { - this.result.assertWithDiagnostics(() -> { - B body = this.result.getResponseBody(); - MatcherAssert.assertThat(bodyMapper.apply(body), matcher); - }); - return self(); - } - - @Override - public T value(Consumer consumer) { - this.result.assertWithDiagnostics(() -> consumer.accept(this.result.getResponseBody())); - return self(); - } - - @Override - public T consumeWith(Consumer> consumer) { - this.result.assertWithDiagnostics(() -> consumer.accept(this.result)); - return self(); - } - - @SuppressWarnings("unchecked") - private T self() { - return (T) this; - } - - @Override - public EntityExchangeResult returnResult() { - return this.result; - } - - } - - private static class DefaultListBodySpec - extends DefaultTestRestClient.DefaultBodySpec, TestRestClient.ListBodySpec> - implements TestRestClient.ListBodySpec { - - DefaultListBodySpec(EntityExchangeResult> result) { - super(result); - } - - @Override - public TestRestClient.ListBodySpec hasSize(int size) { - List actual = getResult().getResponseBody(); - String message = "Response body does not contain " + size + " elements"; - getResult().assertWithDiagnostics( - () -> AssertionErrors.assertEquals(message, size, (actual != null ? actual.size() : 0))); - return this; - } - - @Override - @SuppressWarnings("unchecked") - public TestRestClient.ListBodySpec contains(E... elements) { - List expected = Arrays.asList(elements); - List actual = getResult().getResponseBody(); - String message = "Response body does not contain " + expected; - getResult().assertWithDiagnostics( - () -> AssertionErrors.assertTrue(message, (actual != null && actual.containsAll(expected)))); - return this; - } - - @Override - @SuppressWarnings("unchecked") - public TestRestClient.ListBodySpec doesNotContain(E... elements) { - List expected = Arrays.asList(elements); - List actual = getResult().getResponseBody(); - String message = "Response body should not have contained " + expected; - getResult().assertWithDiagnostics( - () -> AssertionErrors.assertTrue(message, (actual == null || !actual.containsAll(expected)))); - return this; - } - - @Override - public EntityExchangeResult> returnResult() { - return getResult(); - } - - } - - private static class DefaultBodyContentSpec implements TestRestClient.BodyContentSpec { - - private final EntityExchangeResult result; - - private final boolean isEmpty; - - DefaultBodyContentSpec(EntityExchangeResult result) { - this.result = result; - this.isEmpty = (result.getResponseBody() == null || result.getResponseBody().length == 0); - } - - @Override - public EntityExchangeResult isEmpty() { - this.result.assertWithDiagnostics(() -> AssertionErrors.assertTrue("Expected empty body", this.isEmpty)); - return new EntityExchangeResult<>(this.result, null); - } - - @Override - public TestRestClient.BodyContentSpec json(String json, boolean strict) { - this.result.assertWithDiagnostics(() -> { - try { - new JsonExpectationsHelper().assertJsonEqual(json, getBodyAsString(), strict); - } - catch (Exception ex) { - throw new AssertionError("JSON parsing error", ex); - } - }); - return this; - } - - @Override - public TestRestClient.BodyContentSpec xml(String expectedXml) { - this.result.assertWithDiagnostics(() -> { - try { - new XmlExpectationsHelper().assertXmlEqual(expectedXml, getBodyAsString()); - } - catch (Exception ex) { - throw new AssertionError("XML parsing error", ex); - } - }); - return this; - } - - @Override - public JsonPathAssertions jsonPath(String expression, Object... args) { - return new JsonPathAssertions(this, getBodyAsString(), expression, args); - } - - @Override - public XpathAssertions xpath(String expression, @Nullable Map namespaces, Object... args) { - return new XpathAssertions(this, expression, namespaces, args); - } - - private String getBodyAsString() { - byte[] body = this.result.getResponseBody(); - if (body == null || body.length == 0) { - return ""; - } - Charset charset = Optional.ofNullable(this.result.getResponseHeaders().getContentType()) - .map(MimeType::getCharset).orElse(StandardCharsets.UTF_8); - return new String(body, charset); - } - - @Override - public TestRestClient.BodyContentSpec consumeWith(Consumer> consumer) { - this.result.assertWithDiagnostics(() -> consumer.accept(this.result)); - return this; - } - - @Override - public EntityExchangeResult returnResult() { - return this.result; - } - - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/client/EntityExchangeResult.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/client/EntityExchangeResult.java deleted file mode 100644 index cde5a4a0..00000000 --- a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/client/EntityExchangeResult.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.test.client; - -import org.springframework.lang.Nullable; - -/** - * {@code ExchangeResult} sub-class that exposes the response body fully extracted to a - * representation of type {@code }. - * - * @param the response body type - * @author Rossen Stoyanchev - * @since 5.0 - */ -public class EntityExchangeResult extends ExchangeResult { - - @Nullable - private final T body; - - EntityExchangeResult(ExchangeResult result, @Nullable T body) { - super(result); - this.body = body; - } - - /** - * Return the entity extracted from the response body. - */ - @Nullable - public T getResponseBody() { - return this.body; - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/client/ExchangeResult.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/client/ExchangeResult.java deleted file mode 100644 index 694a7e02..00000000 --- a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/client/ExchangeResult.java +++ /dev/null @@ -1,296 +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.test.client; - -import java.net.URI; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.time.Duration; -import java.util.List; -import java.util.stream.Collectors; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.http.HttpStatusCode; -import org.springframework.http.MediaType; -import org.springframework.http.RequestEntity; -import org.springframework.http.ResponseCookie; -import org.springframework.http.ResponseEntity; -import org.springframework.lang.Nullable; -import org.springframework.util.Assert; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; - -/** - * Container for request and response details for exchanges performed through - * {@link WebTestClient}. - * - *

- * Note that a decoded response body is not exposed at this level since the body may not - * have been decoded and consumed yet. Subtypes {@link EntityExchangeResult} and - * {@link FluxExchangeResult} provide access to a decoded response entity and a decoded - * (but not consumed) response body respectively. - * - * @author Rossen Stoyanchev - * @author Sam Brannen - * @since 5.0 - * @see EntityExchangeResult - * @see FluxExchangeResult - */ -public class ExchangeResult { - - private static final Log logger = LogFactory.getLog(ExchangeResult.class); - - private static final List PRINTABLE_MEDIA_TYPES = List.of(MediaType.parseMediaType("application/*+json"), - MediaType.APPLICATION_XML, MediaType.parseMediaType("text/*"), MediaType.APPLICATION_FORM_URLENCODED); - - private final RequestEntity request; - - private final ResponseEntity response; - - private final byte[] requestBody; - - private final byte[] responseBody; - - private final Duration timeout; - - @Nullable - private final String uriTemplate; - - @Nullable - private final Object mockServerResult; - - /** Ensure single logging, e.g. for expectAll. */ - private boolean diagnosticsLogged; - - /** - * Create an instance with an HTTP request and response along with promises for the - * serialized request and response body content. - * @param request the HTTP request - * @param response the HTTP response - */ - ExchangeResult(RequestEntity request, ResponseEntity response) { - this(request, response, new byte[0], new byte[0], null, null, null); - } - - /** - * Create an instance with an HTTP request and response along with promises for the - * serialized request and response body content. - * @param request the HTTP request - * @param response the HTTP response - * @param requestBody capture of serialized request body content - * @param responseBody capture of serialized response body content - * @param timeout how long to wait for content to materialize - * @param uriTemplate the URI template used to set up the request, if any - * @param serverResult the result of a mock server exchange if applicable. - */ - ExchangeResult(RequestEntity request, ResponseEntity response, byte[] requestBody, byte[] responseBody, - Duration timeout, @Nullable String uriTemplate, @Nullable Object serverResult) { - - Assert.notNull(request, "ClientHttpRequest is required"); - Assert.notNull(response, "ClientHttpResponse is required"); - Assert.notNull(requestBody, "'requestBody' is required"); - Assert.notNull(responseBody, "'responseBody' is required"); - - this.request = request; - this.response = response; - this.requestBody = requestBody; - this.responseBody = responseBody; - this.timeout = timeout; - this.uriTemplate = uriTemplate; - this.mockServerResult = serverResult; - } - - /** - * Copy constructor to use after body is decoded and/or consumed. - */ - ExchangeResult(ExchangeResult other) { - this.request = other.request; - this.response = other.response; - this.requestBody = other.requestBody; - this.responseBody = other.responseBody; - this.timeout = other.timeout; - this.uriTemplate = other.uriTemplate; - this.mockServerResult = other.mockServerResult; - this.diagnosticsLogged = other.diagnosticsLogged; - } - - /** - * Return the method of the request. - */ - public HttpMethod getMethod() { - return this.request.getMethod(); - } - - /** - * Return the URI of the request. - */ - public URI getUrl() { - return this.request.getUrl(); - } - - /** - * Return the original URI template used to prepare the request, if any. - */ - @Nullable - public String getUriTemplate() { - return this.uriTemplate; - } - - /** - * Return the request headers sent to the server. - */ - public HttpHeaders getRequestHeaders() { - return this.request.getHeaders(); - } - - /** - * Return the raw request body content written through the request. - *

- * Note: If the request content has not been consumed for any reason - * yet, use of this method will trigger consumption. - * @throws IllegalStateException if the request body has not been fully written. - */ - @Nullable - public byte[] getRequestBodyContent() { - return this.requestBody; - } - - /** - * Return the HTTP status code as an {@link HttpStatusCode} value. - */ - public HttpStatusCode getStatus() { - return this.response.getStatusCode(); - } - - /** - * Return the HTTP status code as an integer. - * @since 5.1.10 - * @deprecated as of 6.0, in favor of {@link #getStatus()} - */ - @Deprecated(since = "6.0", forRemoval = true) - public int getRawStatusCode() { - return getStatus().value(); - } - - /** - * Return the response headers received from the server. - */ - public HttpHeaders getResponseHeaders() { - return this.response.getHeaders(); - } - - /** - * Return response cookies received from the server. - */ - public MultiValueMap getResponseCookies() { - return new LinkedMultiValueMap<>(); // TODO: getResponseCookies - } - - /** - * Return the raw request body content written to the response. - *

- * Note: If the response content has not been consumed yet, use of - * this method will trigger consumption. - * @throws IllegalStateException if the response has not been fully read. - */ - @Nullable - public byte[] getResponseBodyContent() { - return this.responseBody; - } - - /** - * Return the result from the mock server exchange, if applicable, for further - * assertions on the state of the server response. - * @since 5.3 - * @see org.springframework.test.web.servlet.client.MockMvcWebTestClient#resultActionsFor(ExchangeResult) - */ - @Nullable - public Object getMockServerResult() { - return this.mockServerResult; - } - - /** - * Execute the given Runnable, catch any {@link AssertionError}, log details about the - * request and response at ERROR level under the class log category, and after that - * re-throw the error. - */ - public void assertWithDiagnostics(Runnable assertion) { - try { - assertion.run(); - } - catch (AssertionError ex) { - if (!this.diagnosticsLogged && logger.isErrorEnabled()) { - this.diagnosticsLogged = true; - logger.error("Request details for assertion failure:\n" + this); - } - throw ex; - } - } - - @Override - public String toString() { - return "\n" + "> " + getMethod() + " " + getUrl() + "\n" + "> " + formatHeaders(getRequestHeaders(), "\n> ") - + "\n" + "\n" + formatBody(getRequestHeaders().getContentType(), this.requestBody) + "\n" + "\n" + "< " - + formatStatus(getStatus()) + "\n" + "< " + formatHeaders(getResponseHeaders(), "\n< ") + "\n" + "\n" - + formatBody(getResponseHeaders().getContentType(), this.responseBody) + "\n" - + formatMockServerResult(); - } - - private String formatStatus(HttpStatusCode statusCode) { - String result = statusCode.toString(); - if (statusCode instanceof HttpStatus status) { - result += " " + status.getReasonPhrase(); - } - return result; - } - - private String formatHeaders(HttpHeaders headers, String delimiter) { - return headers.entrySet().stream().map(entry -> entry.getKey() + ": " + entry.getValue()) - .collect(Collectors.joining(delimiter)); - } - - @Nullable - private String formatBody(@Nullable MediaType contentType, byte[] bytes) { - if (bytes == null) { - return "No content"; - } - if (contentType == null) { - return bytes.length + " bytes of content (unknown content-type)."; - } - Charset charset = contentType.getCharset(); - if (charset != null) { - return new String(bytes, charset); - } - if (PRINTABLE_MEDIA_TYPES.stream().anyMatch(contentType::isCompatibleWith)) { - return new String(bytes, StandardCharsets.UTF_8); - } - return bytes.length + " bytes of content."; - } - - private String formatMockServerResult() { - return (this.mockServerResult != null - ? "\n====================== MockMvc (Server) ===============================\n" + this.mockServerResult - + "\n" - : ""); - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/client/HeaderAssertions.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/client/HeaderAssertions.java deleted file mode 100644 index 4d640a4d..00000000 --- a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/client/HeaderAssertions.java +++ /dev/null @@ -1,322 +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.test.client; - -import java.net.URI; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; -import java.util.function.Consumer; - -import org.hamcrest.Matcher; -import org.hamcrest.MatcherAssert; - -import org.springframework.http.CacheControl; -import org.springframework.http.ContentDisposition; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; -import org.springframework.lang.Nullable; -import org.springframework.test.util.AssertionErrors; -import org.springframework.util.CollectionUtils; - -import static org.springframework.test.util.AssertionErrors.assertEquals; -import static org.springframework.test.util.AssertionErrors.assertNotNull; -import static org.springframework.test.util.AssertionErrors.assertTrue; - -/** - * Assertions on headers of the response. - * - * @author Rossen Stoyanchev - * @author Brian Clozel - * @author Sam Brannen - * @since 5.0 - * @see TestRestClient.ResponseSpec#expectHeader() - */ -public class HeaderAssertions { - - private final ExchangeResult exchangeResult; - - private final TestRestClient.ResponseSpec responseSpec; - - HeaderAssertions(ExchangeResult result, TestRestClient.ResponseSpec spec) { - this.exchangeResult = result; - this.responseSpec = spec; - } - - /** - * Expect a header with the given name to match the specified values. - */ - public TestRestClient.ResponseSpec valueEquals(String headerName, String... values) { - return assertHeader(headerName, Arrays.asList(values), getHeaders().getOrEmpty(headerName)); - } - - /** - * Expect a header with the given name to match the given long value. - * @since 5.3 - */ - public TestRestClient.ResponseSpec valueEquals(String headerName, long value) { - String actual = getHeaders().getFirst(headerName); - this.exchangeResult.assertWithDiagnostics( - () -> assertTrue("Response does not contain header '" + headerName + "'", actual != null)); - return assertHeader(headerName, value, Long.parseLong(Objects.requireNonNull(actual))); - } - - /** - * Expect a header with the given name to match the specified long value parsed into a - * date using the preferred date format described in RFC 7231. - *

- * An {@link AssertionError} is thrown if the response does not contain the specified - * header, or if the supplied {@code value} does not match the primary header value. - * @since 5.3 - */ - public TestRestClient.ResponseSpec valueEqualsDate(String headerName, long value) { - this.exchangeResult.assertWithDiagnostics(() -> { - String headerValue = getHeaders().getFirst(headerName); - assertNotNull("Response does not contain header '" + headerName + "'", headerValue); - - HttpHeaders headers = new HttpHeaders(); - headers.setDate("expected", value); - headers.set("actual", headerValue); - - assertEquals( - "Response header '" + headerName + "'='" + headerValue + "' " + "does not match expected value '" - + headers.getFirst("expected") + "'", - headers.getFirstDate("expected"), headers.getFirstDate("actual")); - }); - return this.responseSpec; - } - - /** - * Match the first value of the response header with a regex. - * @param name the header name - * @param pattern the regex pattern - */ - public TestRestClient.ResponseSpec valueMatches(String name, String pattern) { - String value = getRequiredValue(name); - String message = getMessage(name) + "=[" + value + "] does not match [" + pattern + "]"; - this.exchangeResult.assertWithDiagnostics(() -> AssertionErrors.assertTrue(message, value.matches(pattern))); - return this.responseSpec; - } - - /** - * Match all values of the response header with the given regex patterns which are - * applied to the values of the header in the same order. Note that the number of - * patterns must match the number of actual values. - * @param name the header name - * @param patterns one or more regex patterns, one per expected value - * @since 5.3 - */ - public TestRestClient.ResponseSpec valuesMatch(String name, String... patterns) { - this.exchangeResult.assertWithDiagnostics(() -> { - List values = getRequiredValues(name); - AssertionErrors - .assertTrue( - getMessage(name) + " has fewer or more values " + values - + " than number of patterns to match with " + Arrays.toString(patterns), - values.size() == patterns.length); - for (int i = 0; i < values.size(); i++) { - String value = values.get(i); - String pattern = patterns[i]; - AssertionErrors.assertTrue( - getMessage(name) + "[" + i + "]='" + value + "' does not match '" + pattern + "'", - value.matches(pattern)); - } - }); - return this.responseSpec; - } - - /** - * Assert the first value of the response header with a Hamcrest {@link Matcher}. - * @param name the header name - * @param matcher the matcher to use - * @since 5.1 - */ - public TestRestClient.ResponseSpec value(String name, Matcher matcher) { - String value = getHeaders().getFirst(name); - this.exchangeResult.assertWithDiagnostics(() -> { - String message = getMessage(name); - MatcherAssert.assertThat(message, value, matcher); - }); - return this.responseSpec; - } - - /** - * Assert all values of the response header with a Hamcrest {@link Matcher}. - * @param name the header name - * @param matcher the matcher to use - * @since 5.3 - */ - public TestRestClient.ResponseSpec values(String name, Matcher> matcher) { - List values = getHeaders().get(name); - this.exchangeResult.assertWithDiagnostics(() -> { - String message = getMessage(name); - MatcherAssert.assertThat(message, values, matcher); - }); - return this.responseSpec; - } - - /** - * Consume the first value of the named response header. - * @param name the header name - * @param consumer the consumer to use - * @since 5.1 - */ - public TestRestClient.ResponseSpec value(String name, Consumer consumer) { - String value = getRequiredValue(name); - this.exchangeResult.assertWithDiagnostics(() -> consumer.accept(value)); - return this.responseSpec; - } - - /** - * Consume all values of the named response header. - * @param name the header name - * @param consumer the consumer to use - * @since 5.3 - */ - public TestRestClient.ResponseSpec values(String name, Consumer> consumer) { - List values = getRequiredValues(name); - this.exchangeResult.assertWithDiagnostics(() -> consumer.accept(values)); - return this.responseSpec; - } - - private String getRequiredValue(String name) { - return getRequiredValues(name).get(0); - } - - private List getRequiredValues(String name) { - List values = getHeaders().get(name); - if (CollectionUtils.isEmpty(values)) { - this.exchangeResult.assertWithDiagnostics(() -> AssertionErrors.fail(getMessage(name) + " not found")); - } - return Objects.requireNonNull(values); - } - - /** - * Expect that the header with the given name is present. - * @since 5.0.3 - */ - public TestRestClient.ResponseSpec exists(String name) { - if (!getHeaders().containsKey(name)) { - String message = getMessage(name) + " does not exist"; - this.exchangeResult.assertWithDiagnostics(() -> AssertionErrors.fail(message)); - } - return this.responseSpec; - } - - /** - * Expect that the header with the given name is not present. - */ - public TestRestClient.ResponseSpec doesNotExist(String name) { - if (getHeaders().containsKey(name)) { - String message = getMessage(name) + " exists with value=[" + getHeaders().getFirst(name) + "]"; - this.exchangeResult.assertWithDiagnostics(() -> AssertionErrors.fail(message)); - } - return this.responseSpec; - } - - /** - * Expect a "Cache-Control" header with the given value. - */ - public TestRestClient.ResponseSpec cacheControl(CacheControl cacheControl) { - return assertHeader("Cache-Control", cacheControl.getHeaderValue(), getHeaders().getCacheControl()); - } - - /** - * Expect a "Content-Disposition" header with the given value. - */ - public TestRestClient.ResponseSpec contentDisposition(ContentDisposition contentDisposition) { - return assertHeader("Content-Disposition", contentDisposition, getHeaders().getContentDisposition()); - } - - /** - * Expect a "Content-Length" header with the given value. - */ - public TestRestClient.ResponseSpec contentLength(long contentLength) { - return assertHeader("Content-Length", contentLength, getHeaders().getContentLength()); - } - - /** - * Expect a "Content-Type" header with the given value. - */ - public TestRestClient.ResponseSpec contentType(MediaType mediaType) { - return assertHeader("Content-Type", mediaType, getHeaders().getContentType()); - } - - /** - * Expect a "Content-Type" header with the given value. - */ - public TestRestClient.ResponseSpec contentType(String mediaType) { - return contentType(MediaType.parseMediaType(mediaType)); - } - - /** - * Expect a "Content-Type" header compatible with the given value. - */ - public TestRestClient.ResponseSpec contentTypeCompatibleWith(MediaType mediaType) { - MediaType actual = getHeaders().getContentType(); - String message = getMessage("Content-Type") + "=[" + actual + "] is not compatible with [" + mediaType + "]"; - this.exchangeResult.assertWithDiagnostics( - () -> AssertionErrors.assertTrue(message, (actual != null && actual.isCompatibleWith(mediaType)))); - return this.responseSpec; - } - - /** - * Expect a "Content-Type" header compatible with the given value. - */ - public TestRestClient.ResponseSpec contentTypeCompatibleWith(String mediaType) { - return contentTypeCompatibleWith(MediaType.parseMediaType(mediaType)); - } - - /** - * Expect an "Expires" header with the given value. - */ - public TestRestClient.ResponseSpec expires(long expires) { - return assertHeader("Expires", expires, getHeaders().getExpires()); - } - - /** - * Expect a "Last-Modified" header with the given value. - */ - public TestRestClient.ResponseSpec lastModified(long lastModified) { - return assertHeader("Last-Modified", lastModified, getHeaders().getLastModified()); - } - - /** - * Expect a "Location" header with the given value. - * @since 5.3 - */ - public TestRestClient.ResponseSpec location(String location) { - return assertHeader("Location", URI.create(location), getHeaders().getLocation()); - } - - private HttpHeaders getHeaders() { - return this.exchangeResult.getResponseHeaders(); - } - - private String getMessage(String headerName) { - return "Response header '" + headerName + "'"; - } - - private TestRestClient.ResponseSpec assertHeader(String name, @Nullable Object expected, @Nullable Object actual) { - this.exchangeResult.assertWithDiagnostics(() -> { - String message = getMessage(name); - AssertionErrors.assertEquals(message, expected, actual); - }); - return this.responseSpec; - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/client/JsonPathAssertions.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/client/JsonPathAssertions.java deleted file mode 100644 index 6db7a602..00000000 --- a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/client/JsonPathAssertions.java +++ /dev/null @@ -1,191 +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.test.client; - -import java.util.function.Consumer; - -import org.hamcrest.Matcher; - -import org.springframework.lang.Nullable; -import org.springframework.test.util.JsonPathExpectationsHelper; - -/** - * JsonPath assertions. - * - * @author Rossen Stoyanchev - * @since 5.0 - * @see https://github.com/jayway/JsonPath - * @see JsonPathExpectationsHelper - */ -public class JsonPathAssertions { - - private final TestRestClient.BodyContentSpec bodySpec; - - private final String content; - - private final JsonPathExpectationsHelper pathHelper; - - JsonPathAssertions(TestRestClient.BodyContentSpec spec, String content, String expression, Object... args) { - this.bodySpec = spec; - this.content = content; - this.pathHelper = new JsonPathExpectationsHelper(expression, args); - } - - /** - * Applies {@link JsonPathExpectationsHelper#assertValue(String, Object)}. - */ - public TestRestClient.BodyContentSpec isEqualTo(Object expectedValue) { - this.pathHelper.assertValue(this.content, expectedValue); - return this.bodySpec; - } - - /** - * Applies {@link JsonPathExpectationsHelper#exists(String)}. - */ - public TestRestClient.BodyContentSpec exists() { - this.pathHelper.exists(this.content); - return this.bodySpec; - } - - /** - * Applies {@link JsonPathExpectationsHelper#doesNotExist(String)}. - */ - public TestRestClient.BodyContentSpec doesNotExist() { - this.pathHelper.doesNotExist(this.content); - return this.bodySpec; - } - - /** - * Applies {@link JsonPathExpectationsHelper#assertValueIsEmpty(String)}. - */ - public TestRestClient.BodyContentSpec isEmpty() { - this.pathHelper.assertValueIsEmpty(this.content); - return this.bodySpec; - } - - /** - * Applies {@link JsonPathExpectationsHelper#assertValueIsNotEmpty(String)}. - */ - public TestRestClient.BodyContentSpec isNotEmpty() { - this.pathHelper.assertValueIsNotEmpty(this.content); - return this.bodySpec; - } - - /** - * Applies {@link JsonPathExpectationsHelper#hasJsonPath}. - * @since 5.0.3 - */ - public TestRestClient.BodyContentSpec hasJsonPath() { - this.pathHelper.hasJsonPath(this.content); - return this.bodySpec; - } - - /** - * Applies {@link JsonPathExpectationsHelper#doesNotHaveJsonPath}. - * @since 5.0.3 - */ - public TestRestClient.BodyContentSpec doesNotHaveJsonPath() { - this.pathHelper.doesNotHaveJsonPath(this.content); - return this.bodySpec; - } - - /** - * Applies {@link JsonPathExpectationsHelper#assertValueIsBoolean(String)}. - */ - public TestRestClient.BodyContentSpec isBoolean() { - this.pathHelper.assertValueIsBoolean(this.content); - return this.bodySpec; - } - - /** - * Applies {@link JsonPathExpectationsHelper#assertValueIsNumber(String)}. - */ - public TestRestClient.BodyContentSpec isNumber() { - this.pathHelper.assertValueIsNumber(this.content); - return this.bodySpec; - } - - /** - * Applies {@link JsonPathExpectationsHelper#assertValueIsArray(String)}. - */ - public TestRestClient.BodyContentSpec isArray() { - this.pathHelper.assertValueIsArray(this.content); - return this.bodySpec; - } - - /** - * Applies {@link JsonPathExpectationsHelper#assertValueIsMap(String)}. - */ - public TestRestClient.BodyContentSpec isMap() { - this.pathHelper.assertValueIsMap(this.content); - return this.bodySpec; - } - - /** - * Delegates to {@link JsonPathExpectationsHelper#assertValue(String, Matcher)}. - * @since 5.1 - */ - public TestRestClient.BodyContentSpec value(Matcher matcher) { - this.pathHelper.assertValue(this.content, matcher); - return this.bodySpec; - } - - /** - * Delegates to - * {@link JsonPathExpectationsHelper#assertValue(String, Matcher, Class)}. - * @since 5.1 - */ - public TestRestClient.BodyContentSpec value(Matcher matcher, Class targetType) { - this.pathHelper.assertValue(this.content, matcher, targetType); - return this.bodySpec; - } - - /** - * Consume the result of the JSONPath evaluation. - * @since 5.1 - */ - @SuppressWarnings("unchecked") - public TestRestClient.BodyContentSpec value(Consumer consumer) { - Object value = this.pathHelper.evaluateJsonPath(this.content); - consumer.accept((T) value); - return this.bodySpec; - } - - /** - * Consume the result of the JSONPath evaluation and provide a target class. - * @since 5.1 - */ - @SuppressWarnings("unchecked") - public TestRestClient.BodyContentSpec value(Consumer consumer, Class targetType) { - Object value = this.pathHelper.evaluateJsonPath(this.content, targetType); - consumer.accept((T) value); - return this.bodySpec; - } - - @Override - public boolean equals(@Nullable Object obj) { - throw new AssertionError("Object#equals is disabled " - + "to avoid being used in error instead of JsonPathAssertions#isEqualTo(String)."); - } - - @Override - public int hashCode() { - return super.hashCode(); - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/client/StatusAssertions.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/client/StatusAssertions.java deleted file mode 100644 index 41b526ed..00000000 --- a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/client/StatusAssertions.java +++ /dev/null @@ -1,237 +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.test.client; - -import java.util.function.Consumer; - -import org.hamcrest.Matcher; -import org.hamcrest.MatcherAssert; - -import org.springframework.http.HttpStatus; -import org.springframework.http.HttpStatusCode; -import org.springframework.test.util.AssertionErrors; - -public class StatusAssertions { - - private final ExchangeResult exchangeResult; - - private final TestRestClient.ResponseSpec responseSpec; - - StatusAssertions(ExchangeResult result, TestRestClient.ResponseSpec spec) { - this.exchangeResult = result; - this.responseSpec = spec; - } - - /** - * Assert the response status as an {@link HttpStatusCode}. - */ - public TestRestClient.ResponseSpec isEqualTo(HttpStatusCode status) { - HttpStatusCode actual = this.exchangeResult.getStatus(); - this.exchangeResult.assertWithDiagnostics(() -> AssertionErrors.assertEquals("Status", status, actual)); - return this.responseSpec; - } - - /** - * Assert the response status as an integer. - */ - public TestRestClient.ResponseSpec isEqualTo(int status) { - return isEqualTo(HttpStatusCode.valueOf(status)); - } - - /** - * Assert the response status code is {@code HttpStatus.OK} (200). - */ - public TestRestClient.ResponseSpec isOk() { - return assertStatusAndReturn(HttpStatus.OK); - } - - /** - * Assert the response status code is {@code HttpStatus.CREATED} (201). - */ - public TestRestClient.ResponseSpec isCreated() { - return assertStatusAndReturn(HttpStatus.CREATED); - } - - /** - * Assert the response status code is {@code HttpStatus.ACCEPTED} (202). - */ - public TestRestClient.ResponseSpec isAccepted() { - return assertStatusAndReturn(HttpStatus.ACCEPTED); - } - - /** - * Assert the response status code is {@code HttpStatus.NO_CONTENT} (204). - */ - public TestRestClient.ResponseSpec isNoContent() { - return assertStatusAndReturn(HttpStatus.NO_CONTENT); - } - - /** - * Assert the response status code is {@code HttpStatus.FOUND} (302). - */ - public TestRestClient.ResponseSpec isFound() { - return assertStatusAndReturn(HttpStatus.FOUND); - } - - /** - * Assert the response status code is {@code HttpStatus.SEE_OTHER} (303). - */ - public TestRestClient.ResponseSpec isSeeOther() { - return assertStatusAndReturn(HttpStatus.SEE_OTHER); - } - - /** - * Assert the response status code is {@code HttpStatus.NOT_MODIFIED} (304). - */ - public TestRestClient.ResponseSpec isNotModified() { - return assertStatusAndReturn(HttpStatus.NOT_MODIFIED); - } - - /** - * Assert the response status code is {@code HttpStatus.TEMPORARY_REDIRECT} (307). - */ - public TestRestClient.ResponseSpec isTemporaryRedirect() { - return assertStatusAndReturn(HttpStatus.TEMPORARY_REDIRECT); - } - - /** - * Assert the response status code is {@code HttpStatus.PERMANENT_REDIRECT} (308). - */ - public TestRestClient.ResponseSpec isPermanentRedirect() { - return assertStatusAndReturn(HttpStatus.PERMANENT_REDIRECT); - } - - /** - * Assert the response status code is {@code HttpStatus.BAD_REQUEST} (400). - */ - public TestRestClient.ResponseSpec isBadRequest() { - return assertStatusAndReturn(HttpStatus.BAD_REQUEST); - } - - /** - * Assert the response status code is {@code HttpStatus.UNAUTHORIZED} (401). - */ - public TestRestClient.ResponseSpec isUnauthorized() { - return assertStatusAndReturn(HttpStatus.UNAUTHORIZED); - } - - /** - * Assert the response status code is {@code HttpStatus.FORBIDDEN} (403). - * @since 5.0.2 - */ - public TestRestClient.ResponseSpec isForbidden() { - return assertStatusAndReturn(HttpStatus.FORBIDDEN); - } - - /** - * Assert the response status code is {@code HttpStatus.NOT_FOUND} (404). - */ - public TestRestClient.ResponseSpec isNotFound() { - return assertStatusAndReturn(HttpStatus.NOT_FOUND); - } - - /** - * Assert the response error message. - */ - public TestRestClient.ResponseSpec reasonEquals(String reason) { - String actual = getReasonPhrase(this.exchangeResult.getStatus()); - this.exchangeResult - .assertWithDiagnostics(() -> AssertionErrors.assertEquals("Response status reason", reason, actual)); - return this.responseSpec; - } - - private static String getReasonPhrase(HttpStatusCode statusCode) { - if (statusCode instanceof HttpStatus status) { - return status.getReasonPhrase(); - } - else { - return ""; - } - } - - /** - * Assert the response status code is in the 1xx range. - */ - public TestRestClient.ResponseSpec is1xxInformational() { - return assertSeriesAndReturn(HttpStatus.Series.INFORMATIONAL); - } - - /** - * Assert the response status code is in the 2xx range. - */ - public TestRestClient.ResponseSpec is2xxSuccessful() { - return assertSeriesAndReturn(HttpStatus.Series.SUCCESSFUL); - } - - /** - * Assert the response status code is in the 3xx range. - */ - public TestRestClient.ResponseSpec is3xxRedirection() { - return assertSeriesAndReturn(HttpStatus.Series.REDIRECTION); - } - - /** - * Assert the response status code is in the 4xx range. - */ - public TestRestClient.ResponseSpec is4xxClientError() { - return assertSeriesAndReturn(HttpStatus.Series.CLIENT_ERROR); - } - - /** - * Assert the response status code is in the 5xx range. - */ - public TestRestClient.ResponseSpec is5xxServerError() { - return assertSeriesAndReturn(HttpStatus.Series.SERVER_ERROR); - } - - /** - * Match the response status value with a Hamcrest matcher. - * @param matcher the matcher to use - * @since 5.1 - */ - public TestRestClient.ResponseSpec value(Matcher matcher) { - int actual = this.exchangeResult.getStatus().value(); - this.exchangeResult.assertWithDiagnostics(() -> MatcherAssert.assertThat("Response status", actual, matcher)); - return this.responseSpec; - } - - /** - * Consume the response status value as an integer. - * @param consumer the consumer to use - * @since 5.1 - */ - public TestRestClient.ResponseSpec value(Consumer consumer) { - int actual = this.exchangeResult.getStatus().value(); - this.exchangeResult.assertWithDiagnostics(() -> consumer.accept(actual)); - return this.responseSpec; - } - - private TestRestClient.ResponseSpec assertStatusAndReturn(HttpStatusCode expected) { - HttpStatusCode actual = this.exchangeResult.getStatus(); - this.exchangeResult.assertWithDiagnostics(() -> AssertionErrors.assertEquals("Status", expected, actual)); - return this.responseSpec; - } - - private TestRestClient.ResponseSpec assertSeriesAndReturn(HttpStatus.Series expected) { - HttpStatusCode status = this.exchangeResult.getStatus(); - HttpStatus.Series series = HttpStatus.Series.resolve(status.value()); - this.exchangeResult.assertWithDiagnostics( - () -> AssertionErrors.assertEquals("Range for response status value " + status, expected, series)); - return this.responseSpec; - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/client/TestRestClient.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/client/TestRestClient.java deleted file mode 100644 index ca3a9ac6..00000000 --- a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/client/TestRestClient.java +++ /dev/null @@ -1,665 +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.test.client; - -import java.net.URI; -import java.nio.charset.Charset; -import java.time.ZonedDateTime; -import java.util.List; -import java.util.Map; -import java.util.function.Consumer; -import java.util.function.Function; - -import org.hamcrest.Matcher; - -import org.springframework.core.ParameterizedTypeReference; -import org.springframework.core.ReactiveAdapterRegistry; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; -import org.springframework.lang.Nullable; -import org.springframework.util.MultiValueMap; -import org.springframework.web.util.UriBuilder; -import org.springframework.web.util.UriBuilderFactory; - -public interface TestRestClient { - - /** - * The name of a request header used to assign a unique id to every request performed - * through the {@code WebTestClient}. This can be useful for storing contextual - * information at all phases of request processing (e.g. from a server-side component) - * under that id and later to look up that information once an {@link ExchangeResult} - * is available. - */ - String TESTRESTCLIENT_REQUEST_ID = "TestRestClient-Request-Id"; - - /** - * Prepare an HTTP GET request. - * @return a spec for specifying the target URL - */ - RequestHeadersUriSpec get(); - - /** - * Prepare an HTTP HEAD request. - * @return a spec for specifying the target URL - */ - RequestHeadersUriSpec head(); - - /** - * Prepare an HTTP POST request. - * @return a spec for specifying the target URL - */ - RequestBodyUriSpec post(); - - /** - * Prepare an HTTP PUT request. - * @return a spec for specifying the target URL - */ - RequestBodyUriSpec put(); - - /** - * Prepare an HTTP PATCH request. - * @return a spec for specifying the target URL - */ - RequestBodyUriSpec patch(); - - /** - * Prepare an HTTP DELETE request. - * @return a spec for specifying the target URL - */ - RequestHeadersUriSpec delete(); - - /** - * Prepare an HTTP OPTIONS request. - * @return a spec for specifying the target URL - */ - RequestHeadersUriSpec options(); - - /** - * Prepare a request for the specified {@code HttpMethod}. - * - * @return a spec for specifying the target URL - */ - // RequestBodyUriSpec method(HttpMethod method); - - interface UriSpec> { - - /** - * Specify the URI using an absolute, fully constructed {@link java.net.URI}. - *

- * If a {@link UriBuilderFactory} was configured for the client with a base URI, - * that base URI will not be applied to the supplied - * {@code java.net.URI}. If you wish to have a base URI applied to a - * {@code java.net.URI} you must invoke either {@link #uri(String, Object...)} or - * {@link #uri(String, Map)} — for example, {@code uri(myUri.toString())}. - * @return spec to add headers or perform the exchange - */ - S uri(URI uri); - - /** - * Specify the URI for the request using a URI template and URI variables. - *

- * If a {@link UriBuilderFactory} was configured for the client (e.g. with a base - * URI) it will be used to expand the URI template. - * @return spec to add headers or perform the exchange - */ - S uri(String uri, Object... uriVariables); - - /** - * Specify the URI for the request using a URI template and URI variables. - *

- * If a {@link UriBuilderFactory} was configured for the client (e.g. with a base - * URI) it will be used to expand the URI template. - * @return spec to add headers or perform the exchange - */ - S uri(String uri, Map uriVariables); - - /** - * Build the URI for the request with a {@link UriBuilder} obtained through the - * {@link UriBuilderFactory} configured for this client. - * @return spec to add headers or perform the exchange - */ - S uri(Function uriFunction); - - } - - /** - * Specification for adding request headers and performing an exchange. - * - * @param a self reference to the spec type - */ - interface RequestHeadersSpec> { - - /** - * Set the list of acceptable {@linkplain MediaType media types}, as specified by - * the {@code Accept} header. - * @param acceptableMediaTypes the acceptable media types - * @return the same instance - */ - S accept(MediaType... acceptableMediaTypes); - - /** - * Set the list of acceptable {@linkplain Charset charsets}, as specified by the - * {@code Accept-Charset} header. - * @param acceptableCharsets the acceptable charsets - * @return the same instance - */ - S acceptCharset(Charset... acceptableCharsets); - - /** - * Add a cookie with the given name and value. - * @param name the cookie name - * @param value the cookie value - * @return the same instance - */ - S cookie(String name, String value); - - /** - * Manipulate this request's cookies with the given consumer. The map provided to - * the consumer is "live", so that the consumer can be used to - * {@linkplain MultiValueMap#set(Object, Object) overwrite} existing header - * values, {@linkplain MultiValueMap#remove(Object) remove} values, or use any of - * the other {@link MultiValueMap} methods. - * @param cookiesConsumer a function that consumes the cookies map - * @return this builder - */ - S cookies(Consumer> cookiesConsumer); - - /** - * Set the value of the {@code If-Modified-Since} header. - *

- * The date should be specified as the number of milliseconds since January 1, - * 1970 GMT. - * @param ifModifiedSince the new value of the header - * @return the same instance - */ - S ifModifiedSince(ZonedDateTime ifModifiedSince); - - /** - * Set the values of the {@code If-None-Match} header. - * @param ifNoneMatches the new value of the header - * @return the same instance - */ - S ifNoneMatch(String... ifNoneMatches); - - /** - * Add the given, single header value under the given name. - * @param headerName the header name - * @param headerValues the header value(s) - * @return the same instance - */ - S header(String headerName, String... headerValues); - - /** - * Manipulate the request's headers with the given consumer. The headers provided - * to the consumer are "live", so that the consumer can be used to - * {@linkplain HttpHeaders#set(String, String) overwrite} existing header values, - * {@linkplain HttpHeaders#remove(Object) remove} values, or use any of the other - * {@link HttpHeaders} methods. - * @param headersConsumer a function that consumes the {@code HttpHeaders} - * @return this builder - */ - S headers(Consumer headersConsumer); - - /** - * Set the attribute with the given name to the given value. - * @param name the name of the attribute to add - * @param value the value of the attribute to add - * @return this builder - */ - S attribute(String name, Object value); - - /** - * Manipulate the request attributes with the given consumer. The attributes - * provided to the consumer are "live", so that the consumer can be used to - * inspect attributes, remove attributes, or use any of the other map-provided - * methods. - * @param attributesConsumer a function that consumes the attributes - * @return this builder - */ - S attributes(Consumer> attributesConsumer); - - /** - * Perform the exchange without a request body. - * @return spec for decoding the response - */ - ResponseSpec exchange(); - - } - - /** - * Specification for providing body of a request. - */ - interface RequestBodySpec extends RequestHeadersSpec { - - /** - * Set the length of the body in bytes, as specified by the {@code Content-Length} - * header. - * @param contentLength the content length - * @return the same instance - * @see HttpHeaders#setContentLength(long) - */ - RequestBodySpec contentLength(long contentLength); - - /** - * Set the {@linkplain MediaType media type} of the body, as specified by the - * {@code Content-Type} header. - * @param contentType the content type - * @return the same instance - * @see HttpHeaders#setContentType(MediaType) - */ - RequestBodySpec contentType(MediaType contentType); - - /** - * Set the body to the given {@code Object} value. This method invokes the - * {@link org.springframework.web.reactive.function.client.WebClient.RequestBodySpec#bodyValue(Object) - * bodyValue} method on the underlying {@code WebClient}. - * @param body the value to write to the request body - * @return spec for further declaration of the request - * @since 5.2 - */ - RequestHeadersSpec bodyValue(Object body); - - /** - * Set the body from the given {@code Publisher}. Shortcut for - * {@link #body(BodyInserter)} with a {@linkplain BodyInserters#fromPublisher - * Publisher inserter}. - * @param publisher the request body data - * @param elementClass the class of elements contained in the publisher - * @param the type of the elements contained in the publisher - * @param the type of the {@code Publisher} - * @return spec for further declaration of the request - */ - // > RequestHeadersSpec body(S publisher, Class - // elementClass); - - /** - * Variant of {@link #body(Publisher, Class)} that allows providing element type - * information with generics. - * @param publisher the request body data - * @param elementTypeRef the type reference of elements contained in the publisher - * @param the type of the elements contained in the publisher - * @param the type of the {@code Publisher} - * @return spec for further declaration of the request - * @since 5.2 - */ - // > RequestHeadersSpec body( - // S publisher, ParameterizedTypeReference elementTypeRef); - - /** - * Set the body from the given producer. This method invokes the - * {@link org.springframework.web.reactive.function.client.WebClient.RequestBodySpec#body(Object, Class) - * body(Object, Class)} method on the underlying {@code WebClient}. - * @param producer the producer to write to the request. This must be a - * {@link Publisher} or another producer adaptable to a {@code Publisher} via - * {@link ReactiveAdapterRegistry} - * @param elementClass the class of elements contained in the producer - * @return spec for further declaration of the request - * @since 5.2 - */ - RequestHeadersSpec body(Object producer, Class elementClass); - - /** - * Set the body from the given producer. This method invokes the - * {@link org.springframework.web.reactive.function.client.WebClient.RequestBodySpec#body(Object, ParameterizedTypeReference) - * body(Object, ParameterizedTypeReference)} method on the underlying - * {@code WebClient}. - * @param producer the producer to write to the request. This must be a - * {@link Publisher} or another producer adaptable to a {@code Publisher} via - * {@link ReactiveAdapterRegistry} - * @param elementTypeRef the type reference of elements contained in the producer - * @return spec for further declaration of the request - * @since 5.2 - */ - RequestHeadersSpec body(Object producer, ParameterizedTypeReference elementTypeRef); - - /** - * Set the body of the request to the given {@code BodyInserter}. This method - * invokes the - * {@link org.springframework.web.reactive.function.client.WebClient.RequestBodySpec#body(BodyInserter) - * body(BodyInserter)} method on the underlying {@code WebClient}. - * @param inserter the body inserter to use - * @return spec for further declaration of the request - * @see org.springframework.web.reactive.function.BodyInserters - */ - // RequestHeadersSpec body(BodyInserter - // inserter); - - } - - /** - * Specification for providing request headers and the URI of a request. - * - * @param a self reference to the spec type - */ - interface RequestHeadersUriSpec> extends UriSpec, RequestHeadersSpec { - - } - - /** - * Specification for providing the body and the URI of a request. - */ - interface RequestBodyUriSpec extends RequestBodySpec, RequestHeadersUriSpec { - - } - - /** - * Chained API for applying assertions to a response. - */ - interface ResponseSpec { - - /** - * Apply multiple assertions to a response with the given - * {@linkplain ResponseSpec.ResponseSpecConsumer consumers}, with the guarantee - * that all assertions will be applied even if one or more assertions fails with - * an exception. - *

- * If a single {@link Error} or {@link RuntimeException} is thrown, it will be - * rethrown. - *

- * If multiple exceptions are thrown, this method will throw an - * {@link AssertionError} whose error message is a summary of all the exceptions. - * In addition, each exception will be added as a - * {@linkplain Throwable#addSuppressed(Throwable) suppressed exception} to the - * {@code AssertionError}. - *

- * This feature is similar to the {@code SoftAssertions} support in AssertJ and - * the {@code assertAll()} support in JUnit Jupiter. - * - *

Example

-		 * get().uri("/hello").exchange()
-		 *     .expectAll(
-		 *         responseSpec -> responseSpec.expectStatus().isOk(),
-		 *         responseSpec -> responseSpec.expectBody(String.class).isEqualTo("Hello, World!")
-		 *     );
-		 * 
- * @param consumers the list of {@code ResponseSpec} consumers - * @since 5.3.10 - */ - ResponseSpec expectAll(ResponseSpec.ResponseSpecConsumer... consumers); - - /** - * Assertions on the response status. - */ - StatusAssertions expectStatus(); - - /** - * Assertions on the headers of the response. - */ - HeaderAssertions expectHeader(); - - /** - * Assertions on the cookies of the response. - * @since 5.3 - */ - CookieAssertions expectCookie(); - - /** - * Consume and decode the response body to a single object of type {@code } and - * then apply assertions. - * @param bodyType the expected body type - */ - BodySpec expectBody(Class bodyType); - - /** - * Alternative to {@link #expectBody(Class)} that accepts information about a - * target type with generics. - */ - BodySpec expectBody(ParameterizedTypeReference bodyType); - - /** - * Consume and decode the response body to {@code List} and then apply - * List-specific assertions. - * @param elementType the expected List element type - */ - ListBodySpec expectBodyList(Class elementType); - - /** - * Alternative to {@link #expectBodyList(Class)} that accepts information about a - * target type with generics. - */ - ListBodySpec expectBodyList(ParameterizedTypeReference elementType); - - /** - * Consume and decode the response body to {@code byte[]} and then apply - * assertions on the raw content (e.g. isEmpty, JSONPath, etc.) - */ - BodyContentSpec expectBody(); - - /** - * Exit the chained flow in order to consume the response body externally, e.g. - * via {@link reactor.test.StepVerifier}. - *

- * Note that when {@code Void.class} is passed in, the response body is consumed - * and released. If no content is expected, then consider using - * {@code .expectBody().isEmpty()} instead which asserts that there is no content. - */ - // FluxExchangeResult returnResult(Class elementClass); - - /** - * Alternative to {@link #returnResult(Class)} that accepts information about a - * target type with generics. - */ - // FluxExchangeResult returnResult(ParameterizedTypeReference - // elementTypeRef); - - /** - * {@link Consumer} of a {@link ResponseSpec}. - * - * @since 5.3.10 - * @see ResponseSpec#expectAll(ResponseSpec.ResponseSpecConsumer...) - */ - @FunctionalInterface - interface ResponseSpecConsumer extends Consumer { - - } - - } - - /** - * Spec for expectations on the response body decoded to a single Object. - * - * @param a self reference to the spec type - * @param the body type - */ - interface BodySpec> { - - /** - * Assert the extracted body is equal to the given value. - */ - T isEqualTo(B expected); - - /** - * Assert the extracted body with a {@link Matcher}. - * @since 5.1 - */ - T value(Matcher matcher); - - /** - * Transform the extracted the body with a function, e.g. extracting a property, - * and assert the mapped value with a {@link Matcher}. - * @since 5.1 - */ - T value(Function bodyMapper, Matcher matcher); - - /** - * Assert the extracted body with a {@link Consumer}. - * @since 5.1 - */ - T value(Consumer consumer); - - /** - * Assert the exchange result with the given {@link Consumer}. - */ - T consumeWith(Consumer> consumer); - - /** - * Exit the chained API and return an {@code ExchangeResult} with the decoded - * response content. - */ - EntityExchangeResult returnResult(); - - } - - /** - * Spec for expectations on the response body decoded to a List. - * - * @param the body list element type - */ - interface ListBodySpec extends BodySpec, ListBodySpec> { - - /** - * Assert the extracted list of values is of the given size. - * @param size the expected size - */ - ListBodySpec hasSize(int size); - - /** - * Assert the extracted list of values contains the given elements. - * @param elements the elements to check - */ - @SuppressWarnings("unchecked") - ListBodySpec contains(E... elements); - - /** - * Assert the extracted list of values doesn't contain the given elements. - * @param elements the elements to check - */ - @SuppressWarnings("unchecked") - ListBodySpec doesNotContain(E... elements); - - } - - /** - * Spec for expectations on the response body content. - */ - interface BodyContentSpec { - - /** - * Assert the response body is empty and return the exchange result. - */ - EntityExchangeResult isEmpty(); - - /** - * Parse the expected and actual response content as JSON and perform a comparison - * verifying that they contain the same attribute-value pairs regardless of - * formatting with lenient checking (extensible and non-strict array - * ordering). - *

- * Use of this method requires the - * JSONassert library to be on - * the classpath. - * @param expectedJson the expected JSON content - * @see #json(String, boolean) - */ - default BodyContentSpec json(String expectedJson) { - return json(expectedJson, false); - } - - /** - * Parse the expected and actual response content as JSON and perform a comparison - * verifying that they contain the same attribute-value pairs regardless of - * formatting. - *

- * Can compare in two modes, depending on the {@code strict} parameter value: - *

    - *
  • {@code true}: strict checking. Not extensible and strict array - * ordering.
  • - *
  • {@code false}: lenient checking. Extensible and non-strict array - * ordering.
  • - *
- *

- * Use of this method requires the - * JSONassert library to be on - * the classpath. - * @param expectedJson the expected JSON content - * @param strict enables strict checking if {@code true} - * @since 5.3.16 - * @see #json(String) - */ - BodyContentSpec json(String expectedJson, boolean strict); - - /** - * Parse expected and actual response content as XML and assert that the two are - * "similar", i.e. they contain the same elements and attributes regardless of - * order. - *

- * Use of this method requires the - * XMLUnit library on the - * classpath. - * @param expectedXml the expected JSON content. - * @since 5.1 - * @see org.springframework.test.util.XmlExpectationsHelper#assertXmlEqual(String, - * String) - */ - BodyContentSpec xml(String expectedXml); - - /** - * Access to response body assertions using a - * JsonPath expression to inspect - * a specific subset of the body. - *

- * The JSON path expression can be a parameterized string using formatting - * specifiers as defined in {@link String#format}. - * @param expression the JsonPath expression - * @param args arguments to parameterize the expression - */ - JsonPathAssertions jsonPath(String expression, Object... args); - - /** - * Access to response body assertions using an XPath expression to inspect a - * specific subset of the body. - *

- * The XPath expression can be a parameterized string using formatting specifiers - * as defined in {@link String#format}. - * @param expression the XPath expression - * @param args arguments to parameterize the expression - * @since 5.1 - * @see #xpath(String, Map, Object...) - */ - default XpathAssertions xpath(String expression, Object... args) { - return xpath(expression, null, args); - } - - /** - * Access to response body assertions with specific namespaces using an XPath - * expression to inspect a specific subset of the body. - *

- * The XPath expression can be a parameterized string using formatting specifiers - * as defined in {@link String#format}. - * @param expression the XPath expression - * @param namespaces the namespaces to use - * @param args arguments to parameterize the expression - * @since 5.1 - */ - XpathAssertions xpath(String expression, @Nullable Map namespaces, Object... args); - - /** - * Assert the response body content with the given {@link Consumer}. - * @param consumer the consumer for the response body; the input {@code byte[]} - * may be {@code null} if there was no response body. - */ - BodyContentSpec consumeWith(Consumer> consumer); - - /** - * Exit the chained API and return an {@code ExchangeResult} with the raw response - * content. - */ - EntityExchangeResult returnResult(); - - } - -} diff --git a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/client/XpathAssertions.java b/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/client/XpathAssertions.java deleted file mode 100644 index 1612d2f5..00000000 --- a/spring-cloud-gateway-server-mvc/src/test/java/org/springframework/cloud/gateway/server/mvc/test/client/XpathAssertions.java +++ /dev/null @@ -1,208 +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.test.client; - -import java.nio.charset.StandardCharsets; -import java.util.Map; -import java.util.Optional; -import java.util.function.Consumer; - -import javax.xml.xpath.XPathExpressionException; - -import org.hamcrest.Matcher; - -import org.springframework.http.HttpHeaders; -import org.springframework.lang.Nullable; -import org.springframework.test.util.XpathExpectationsHelper; -import org.springframework.util.Assert; -import org.springframework.util.MimeType; - -/** - * XPath assertions for the {@link TestRestClient}. - * - * @author Eric Deandrea - * @author Rossen Stoyanchev - * @since 5.1 - */ -public class XpathAssertions { - - private final TestRestClient.BodyContentSpec bodySpec; - - private final XpathExpectationsHelper xpathHelper; - - XpathAssertions(TestRestClient.BodyContentSpec spec, String expression, @Nullable Map namespaces, - Object... args) { - - this.bodySpec = spec; - this.xpathHelper = initXpathHelper(expression, namespaces, args); - } - - private static XpathExpectationsHelper initXpathHelper(String expression, @Nullable Map namespaces, - Object[] args) { - - try { - return new XpathExpectationsHelper(expression, namespaces, args); - } - catch (XPathExpressionException ex) { - throw new AssertionError("XML parsing error", ex); - } - } - - /** - * Delegates to {@link XpathExpectationsHelper#assertString(byte[], String, String)}. - */ - public TestRestClient.BodyContentSpec isEqualTo(String expectedValue) { - return assertWith(() -> this.xpathHelper.assertString(getContent(), getCharset(), expectedValue)); - } - - /** - * Delegates to {@link XpathExpectationsHelper#assertNumber(byte[], String, Double)}. - */ - public TestRestClient.BodyContentSpec isEqualTo(Double expectedValue) { - return assertWith(() -> this.xpathHelper.assertNumber(getContent(), getCharset(), expectedValue)); - } - - /** - * Delegates to - * {@link XpathExpectationsHelper#assertBoolean(byte[], String, boolean)}. - */ - public TestRestClient.BodyContentSpec isEqualTo(boolean expectedValue) { - return assertWith(() -> this.xpathHelper.assertBoolean(getContent(), getCharset(), expectedValue)); - } - - /** - * Delegates to {@link XpathExpectationsHelper#exists(byte[], String)}. - */ - public TestRestClient.BodyContentSpec exists() { - return assertWith(() -> this.xpathHelper.exists(getContent(), getCharset())); - } - - /** - * Delegates to {@link XpathExpectationsHelper#doesNotExist(byte[], String)}. - */ - public TestRestClient.BodyContentSpec doesNotExist() { - return assertWith(() -> this.xpathHelper.doesNotExist(getContent(), getCharset())); - } - - /** - * Delegates to {@link XpathExpectationsHelper#assertNodeCount(byte[], String, int)}. - */ - public TestRestClient.BodyContentSpec nodeCount(int expectedCount) { - return assertWith(() -> this.xpathHelper.assertNodeCount(getContent(), getCharset(), expectedCount)); - } - - /** - * Delegates to {@link XpathExpectationsHelper#assertString(byte[], String, Matcher)}. - * @since 5.1 - */ - public TestRestClient.BodyContentSpec string(Matcher matcher) { - return assertWith(() -> this.xpathHelper.assertString(getContent(), getCharset(), matcher)); - } - - /** - * Delegates to {@link XpathExpectationsHelper#assertNumber(byte[], String, Matcher)}. - * @since 5.1 - */ - public TestRestClient.BodyContentSpec number(Matcher matcher) { - return assertWith(() -> this.xpathHelper.assertNumber(getContent(), getCharset(), matcher)); - } - - /** - * Delegates to - * {@link XpathExpectationsHelper#assertNodeCount(byte[], String, Matcher)}. - * @since 5.1 - */ - public TestRestClient.BodyContentSpec nodeCount(Matcher matcher) { - return assertWith(() -> this.xpathHelper.assertNodeCount(getContent(), getCharset(), matcher)); - } - - /** - * Consume the result of the XPath evaluation as a String. - * @since 5.1 - */ - public TestRestClient.BodyContentSpec string(Consumer consumer) { - return assertWith(() -> { - String value = this.xpathHelper.evaluateXpath(getContent(), getCharset(), String.class); - consumer.accept(value); - }); - } - - /** - * Consume the result of the XPath evaluation as a Double. - * @since 5.1 - */ - public TestRestClient.BodyContentSpec number(Consumer consumer) { - return assertWith(() -> { - Double value = this.xpathHelper.evaluateXpath(getContent(), getCharset(), Double.class); - consumer.accept(value); - }); - } - - /** - * Consume the count of nodes as result of the XPath evaluation. - * @since 5.1 - */ - public TestRestClient.BodyContentSpec nodeCount(Consumer consumer) { - return assertWith(() -> { - Integer value = this.xpathHelper.evaluateXpath(getContent(), getCharset(), Integer.class); - consumer.accept(value); - }); - } - - private TestRestClient.BodyContentSpec assertWith(CheckedExceptionTask task) { - try { - task.run(); - } - catch (Exception ex) { - throw new AssertionError("XML parsing error", ex); - } - return this.bodySpec; - } - - private byte[] getContent() { - byte[] body = this.bodySpec.returnResult().getResponseBody(); - Assert.notNull(body, "Expected body content"); - return body; - } - - private String getCharset() { - return Optional.of(this.bodySpec.returnResult()).map(EntityExchangeResult::getResponseHeaders) - .map(HttpHeaders::getContentType).map(MimeType::getCharset).orElse(StandardCharsets.UTF_8).name(); - } - - @Override - public boolean equals(@Nullable Object obj) { - throw new AssertionError("Object#equals is disabled " - + "to avoid being used in error instead of XPathAssertions#isEqualTo(String)."); - } - - @Override - public int hashCode() { - return super.hashCode(); - } - - /** - * Lets us be able to use lambda expressions that could throw checked exceptions, - * since {@link XpathExpectationsHelper} throws {@link Exception} on its methods. - */ - private interface CheckedExceptionTask { - - void run() throws Exception; - - } - -} 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 deleted file mode 100644 index bca78c94..00000000 --- a/spring-cloud-gateway-server-mvc/src/test/resources/META-INF/spring.factories +++ /dev/null @@ -1,2 +0,0 @@ -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/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-cloud-gateway-server-mvc/src/test/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports deleted file mode 100644 index 5509d4e7..00000000 --- a/spring-cloud-gateway-server-mvc/src/test/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ /dev/null @@ -1 +0,0 @@ -org.springframework.cloud.gateway.server.mvc.test.TestAutoConfiguration \ No newline at end of file diff --git a/spring-cloud-gateway-server-mvc/src/test/resources/application-propertiesbeandefinitionregistrartests.yml b/spring-cloud-gateway-server-mvc/src/test/resources/application-propertiesbeandefinitionregistrartests.yml deleted file mode 100644 index f58d2691..00000000 --- a/spring-cloud-gateway-server-mvc/src/test/resources/application-propertiesbeandefinitionregistrartests.yml +++ /dev/null @@ -1,48 +0,0 @@ -spring.cloud.gateway.mvc: - routesMap: - route1: - uri: https://example1.com - predicates: - - Path=/anything/example1 - route2: - id: route2CustomId - uri: https://example2.com - predicates: - - Path=/anything/example2 - filters: - - SetStatus=200 - routes: - - id: listRoute1 - uri: https://examplel1.com - predicates: - - name: Method - args: - # TODO: implement aliases? - methods: GET - - name: Path - args: - pattern: /anything/listRoute1 - filters: - - HttpbinUriResolver= - - AddRequestHeader=X-Test,listRoute1 - - id: listRoute2 - uri: https://examplel2.com - predicates: - - Method=GET,POST - - Path=/anything/listRoute2 - filters: - - HttpbinUriResolver= - - AddRequestHeader=X-Test,listRoute2 - - id: listRoute3 - uri: lb://httpbin - predicates: - - Path=/anything/listRoute3 - - Header=MyHeaderName,MyHeader.* - filters: - - name: AddRequestHeader - args: - name: X-Test - values: listRoute3 -logging: - level: - org.springframework.cloud.gateway.server.mvc: TRACE diff --git a/spring-cloud-gateway-server-mvc/src/test/resources/application-weightrequestpredicateintegrationtests.yml b/spring-cloud-gateway-server-mvc/src/test/resources/application-weightrequestpredicateintegrationtests.yml deleted file mode 100644 index 69d927b8..00000000 --- a/spring-cloud-gateway-server-mvc/src/test/resources/application-weightrequestpredicateintegrationtests.yml +++ /dev/null @@ -1,20 +0,0 @@ -spring.cloud.gateway.mvc: - routes: - - id: weight_high_test - uri: https://examplel1.com - predicates: - - Host=**.weight-high.org - - Weight=group1, 8 - filters: - - HttpbinUriResolver= - - AddResponseHeader=X-Route,weight_high_test - - id: default_route - uri: https://examplel2.com - predicates: - - Path=/** - filters: - - HttpbinUriResolver= - - AddResponseHeader=X-Route,default_route -logging: - level: - org.springframework.cloud.gateway.server.mvc: TRACE diff --git a/spring-cloud-gateway-server-mvc/src/test/resources/application.yml b/spring-cloud-gateway-server-mvc/src/test/resources/application.yml deleted file mode 100644 index 9bc1759e..00000000 --- a/spring-cloud-gateway-server-mvc/src/test/resources/application.yml +++ /dev/null @@ -1,9 +0,0 @@ -logging: - level: - org.apache.hc.client5.http: DEBUG - org.apache.hc.client5.http.wire: DEBUG - org.springframework.web: TRACE - org.springframework.retry: TRACE -spring: - mvc: - log-request-details: true \ No newline at end of file diff --git a/spring-cloud-gateway-server-mvc/src/test/resources/test/1x1.png b/spring-cloud-gateway-server-mvc/src/test/resources/test/1x1.png deleted file mode 100644 index 134aa5b71e4f334e720c798450f264ccfe1d028b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1047 zcmZ{jJ#W)M7{{+gNX5{J1tE|uGuvmoZ_3GW)yB|9A|*_|Kw;>d#nKfHoM2cK)l!Fy|FHy zAM?{+a=->hhv4eRliy-+FFhVJ0Pa7O_Zs;0`4IqjzLMdHk9sfBgvM4lrDxbG;#9Z+ z@Cqb8W6VRnh@*s|qHQP#65mpWhOR99UE3J-`g(_ESl_X>E!*%lUDv&AIzxltMTLt| z+jz@)iXhDM+{(8snw>+Z)oMZ8g|2G~hsiDz9u{W8o|TKt*9kD2WF+N;Cc0cVJfj!9 zZ5T4p?N#kFPHzJxtjbE{2a7O;j%CAT5GS+6_Wq&d%t{OwWCbQ?N=y}_+@mKdih6WP zW|u+8vG3Z>o@u+Ly*qST$ac}T>%oR+k)fhH84fwkhLlErpAsr%4z>G#0a{1Q@;l9X4g=ol4YP^{D_vTDp zYyzWYKwYc>@)(e+((Gd1r)fCBvRn$ONlmwkGF}m-rdON?<$qlJ zugj)Us^}VD6X!YaKwsPcdGJj%<%4c;Fx+@L`Ezr#5AI&CUw`>-iiQTD`}`!hI(+l~ EFQ~UWDF6Tf diff --git a/spring-cloud-gateway-server/pom.xml b/spring-cloud-gateway-server/pom.xml deleted file mode 100644 index 3a38e491..00000000 --- a/spring-cloud-gateway-server/pom.xml +++ /dev/null @@ -1,384 +0,0 @@ - - - 4.0.0 - - - org.springframework.cloud - spring-cloud-gateway - 4.1.0-SNAPSHOT - .. - - spring-cloud-gateway-server - jar - Spring Cloud Gateway Server - Spring Cloud Gateway Server - - ${basedir}/.. - 1.47.0 - 1.0.0 - - - - - org.springframework.boot - spring-boot-starter - - - org.springframework.boot - spring-boot-starter-validation - - - org.springframework.boot - spring-boot-starter-oauth2-client - true - - - org.springframework.boot - spring-boot-starter-actuator - true - - - io.micrometer - micrometer-tracing - true - - - org.springframework.boot - spring-boot-starter-webflux - true - - - org.springframework.boot - spring-boot-configuration-processor - true - - - org.springframework.cloud - spring-cloud-loadbalancer - true - - - org.springframework.boot - spring-boot-devtools - true - - - org.springframework.boot - spring-boot-starter-data-redis - true - - - org.springframework.boot - spring-boot-starter-security - true - - - org.jetbrains.kotlin - kotlin-stdlib - true - - - org.jetbrains.kotlin - kotlin-reflect - true - - - io.projectreactor.addons - reactor-extra - - - io.projectreactor.kotlin - reactor-kotlin-extensions - true - - - com.fasterxml.jackson.dataformat - jackson-dataformat-protobuf - true - - - com.fasterxml.jackson.core - jackson-databind - true - - - io.grpc - grpc-netty - true - ${grpc.version} - - - io.grpc - grpc-protobuf - true - ${grpc.version} - - - io.grpc - grpc-stub - true - ${grpc.version} - - - org.springframework.boot - spring-boot-autoconfigure-processor - true - - - org.springframework.cloud - spring-cloud-starter-circuitbreaker-reactor-resilience4j - true - - - com.github.ben-manes.caffeine - caffeine - true - - - io.micrometer - context-propagation - ${context-propagation.version} - test - - - io.micrometer - micrometer-observation-test - test - - - io.micrometer - micrometer-test - test - - - io.micrometer - micrometer-tracing-integration-test - test - - - - io.micrometer - micrometer-tracing-bridge-otel - - - - io.micrometer - micrometer-tracing-reporter-wavefront - - - com.wavefront - * - - - - - org.springframework.boot - spring-boot-starter-test - test - - - org.junit.vintage - junit-vintage-engine - test - - - org.junit-pioneer - junit-pioneer - test - - - org.springframework.cloud - spring-cloud-test-support - test - - - io.projectreactor - reactor-test - test - - - io.projectreactor.tools - blockhound - test - - - org.assertj - assertj-core - test - - - com.fasterxml.jackson.module - jackson-module-kotlin - test - - - org.testcontainers - junit-jupiter - test - - - org.openjdk.jmh - jmh-core - 1.20 - test - - - org.openjdk.jmh - jmh-generator-annprocess - 1.20 - test - - - org.springframework - spring-core-test - test - - - - - - - kotlin-maven-plugin - org.jetbrains.kotlin - - 17 - - - - compile - - compile - - - - ${project.basedir}/src/main/kotlin - ${project.basedir}/src/main/java - - - - - test-compile - - test-compile - - - - ${project.basedir}/src/test/kotlin - ${project.basedir}/src/test/java - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - - -parameters - - - - - - default-compile - none - - - - default-testCompile - none - - - java-compile - compile - - compile - - - - java-test-compile - test-compile - - testCompile - - - - - - org.apache.maven.plugins - maven-jar-plugin - 3.2.0 - - - - test-jar - - - - - - - - - java17 - - [17,18) - - - - io.projectreactor.tools - blockhound-junit-platform - test - - - - - java13plus - - [13,) - - - - - org.apache.maven.plugins - maven-surefire-plugin - ${maven-surefire-plugin.version} - - - **/*Tests.java - **/*Test.java - - - **/Abstract*.java - - - -XX:+AllowRedefinitionToAddDeleteMethods - - - - - - - java8plus - - [1.8,2.0) - - - - - org.apache.maven.plugins - maven-compiler-plugin - - - -parameters - - - - - - - - diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/actuate/AbstractGatewayControllerEndpoint.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/actuate/AbstractGatewayControllerEndpoint.java deleted file mode 100644 index fd31bf68..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/actuate/AbstractGatewayControllerEndpoint.java +++ /dev/null @@ -1,251 +0,0 @@ -/* - * Copyright 2013-2020 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.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -import org.springframework.cloud.gateway.event.RefreshRoutesEvent; -import org.springframework.cloud.gateway.filter.FilterDefinition; -import org.springframework.cloud.gateway.filter.GlobalFilter; -import org.springframework.cloud.gateway.filter.factory.GatewayFilterFactory; -import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition; -import org.springframework.cloud.gateway.handler.predicate.RoutePredicateFactory; -import org.springframework.cloud.gateway.route.RouteDefinition; -import org.springframework.cloud.gateway.route.RouteDefinitionLocator; -import org.springframework.cloud.gateway.route.RouteDefinitionWriter; -import org.springframework.cloud.gateway.route.RouteLocator; -import org.springframework.cloud.gateway.support.NotFoundException; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.context.ApplicationEventPublisherAware; -import org.springframework.core.Ordered; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.util.CollectionUtils; -import org.springframework.util.StringUtils; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.server.ResponseStatusException; - -/** - * @author Spencer Gibb - */ -public class AbstractGatewayControllerEndpoint implements ApplicationEventPublisherAware { - - private static final Log log = LogFactory.getLog(GatewayControllerEndpoint.class); - - protected RouteDefinitionLocator routeDefinitionLocator; - - protected List globalFilters; - - // TODO change casing in next major release - protected List GatewayFilters; - - protected List routePredicates; - - protected RouteDefinitionWriter routeDefinitionWriter; - - protected RouteLocator routeLocator; - - protected ApplicationEventPublisher publisher; - - public AbstractGatewayControllerEndpoint(RouteDefinitionLocator routeDefinitionLocator, - List globalFilters, List gatewayFilters, - List routePredicates, RouteDefinitionWriter routeDefinitionWriter, - RouteLocator routeLocator) { - this.routeDefinitionLocator = routeDefinitionLocator; - this.globalFilters = globalFilters; - this.GatewayFilters = gatewayFilters; - this.routePredicates = routePredicates; - this.routeDefinitionWriter = routeDefinitionWriter; - this.routeLocator = routeLocator; - } - - @Override - public void setApplicationEventPublisher(ApplicationEventPublisher publisher) { - this.publisher = publisher; - } - - // TODO: Add uncommited or new but not active routes endpoint - - @PostMapping("/refresh") - public Mono refresh(@RequestParam(value = "metadata", required = false) List byMetadata) { - publishRefreshEvent(byMetadata); - return Mono.empty(); - } - - private void publishRefreshEvent(List byMetadata) { - RefreshRoutesEvent event; - if (!CollectionUtils.isEmpty(byMetadata)) { - event = new RefreshRoutesEvent(this, convertToMap(byMetadata)); - } - else { - event = new RefreshRoutesEvent(this); - } - - this.publisher.publishEvent(event); - } - - private Map convertToMap(List byMetadata) { - return byMetadata.stream().map(keyValueStr -> keyValueStr.split(":")) - .collect(Collectors.toMap(kv -> kv[0], kv -> kv.length > 1 ? kv[1] : null)); - } - - @GetMapping("/globalfilters") - public Mono> globalfilters() { - return getNamesToOrders(this.globalFilters); - } - - @GetMapping("/routefilters") - public Mono> routefilers() { - return getNamesToOrders(this.GatewayFilters); - } - - @GetMapping("/routepredicates") - public Mono> routepredicates() { - return getNamesToOrders(this.routePredicates); - } - - private Mono> getNamesToOrders(List list) { - return Flux.fromIterable(list).reduce(new HashMap<>(), this::putItem); - } - - private HashMap putItem(HashMap map, Object o) { - Integer order = null; - if (o instanceof Ordered) { - order = ((Ordered) o).getOrder(); - } - // filters.put(o.getClass().getName(), order); - map.put(o.toString(), order); - return map; - } - - /* - * http POST :8080/admin/gateway/routes/apiaddreqhead uri=http://httpbin.org:80 - * predicates:='["Host=**.apiaddrequestheader.org", "Path=/headers"]' - * filters:='["AddRequestHeader=X-Request-ApiFoo, ApiBar"]' - */ - @PostMapping("/routes/{id}") - @SuppressWarnings("unchecked") - public Mono> save(@PathVariable String id, @RequestBody RouteDefinition route) { - - return Mono.just(route).doOnNext(this::validateRouteDefinition) - .flatMap(routeDefinition -> this.routeDefinitionWriter.save(Mono.just(routeDefinition).map(r -> { - r.setId(id); - log.debug("Saving route: " + route); - return r; - })).then(Mono.defer(() -> Mono.just(ResponseEntity.created(URI.create("/routes/" + id)).build())))) - .switchIfEmpty(Mono.defer(() -> Mono.just(ResponseEntity.badRequest().build()))); - } - - @PostMapping("/routes") - @SuppressWarnings("unchecked") - public Mono> save(@RequestBody List routes) { - routes.stream().forEach(routeDef -> { - validateRouteDefinition(routeDef); - validateRouteId(routeDef); - }); - - return Flux.fromIterable(routes) - .flatMap(routeDefinition -> this.routeDefinitionWriter.save(Mono.just(routeDefinition).map(r -> { - log.debug("Saving route: " + routeDefinition); - return r; - }))).then(Mono.defer(() -> Mono.just(ResponseEntity.ok().build()))) - .switchIfEmpty(Mono.defer(() -> Mono.just(ResponseEntity.badRequest().build()))); - } - - private void validateRouteId(RouteDefinition routeDefinition) { - if (routeDefinition.getId() == null) { - handleError("Saving multiple routes require specifying the ID for every route"); - } - } - - private void validateRouteDefinition(RouteDefinition routeDefinition) { - Set unavailableFilterDefinitions = routeDefinition.getFilters().stream().filter(rd -> !isAvailable(rd)) - .map(FilterDefinition::getName).collect(Collectors.toSet()); - - Set unavailablePredicatesDefinitions = routeDefinition.getPredicates().stream() - .filter(rd -> !isAvailable(rd)).map(PredicateDefinition::getName).collect(Collectors.toSet()); - if (!unavailableFilterDefinitions.isEmpty()) { - handleUnavailableDefinition(FilterDefinition.class.getSimpleName(), unavailableFilterDefinitions); - } - else if (!unavailablePredicatesDefinitions.isEmpty()) { - handleUnavailableDefinition(PredicateDefinition.class.getSimpleName(), unavailablePredicatesDefinitions); - } - - validateRouteUri(routeDefinition.getUri()); - } - - private void validateRouteUri(URI uri) { - if (uri == null) { - handleError("The URI can not be empty"); - } - - if (!StringUtils.hasText(uri.getScheme())) { - handleError(String.format("The URI format [%s] is incorrect, scheme can not be empty", uri)); - } - } - - private void handleUnavailableDefinition(String simpleName, Set unavailableDefinitions) { - final String errorMessage = String.format("Invalid %s: %s", simpleName, unavailableDefinitions); - log.warn(errorMessage); - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, errorMessage); - } - - private void handleError(String errorMessage) { - log.warn(errorMessage); - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, errorMessage); - } - - private boolean isAvailable(FilterDefinition filterDefinition) { - return GatewayFilters.stream() - .anyMatch(gatewayFilterFactory -> filterDefinition.getName().equals(gatewayFilterFactory.name())); - } - - private boolean isAvailable(PredicateDefinition predicateDefinition) { - return routePredicates.stream() - .anyMatch(routePredicate -> predicateDefinition.getName().equals(routePredicate.name())); - } - - @DeleteMapping("/routes/{id}") - public Mono> delete(@PathVariable String id) { - return this.routeDefinitionWriter.delete(Mono.just(id)) - .then(Mono.defer(() -> Mono.just(ResponseEntity.ok().build()))) - .onErrorResume(t -> t instanceof NotFoundException, t -> Mono.just(ResponseEntity.notFound().build())); - } - - @GetMapping("/routes/{id}/combinedfilters") - public Mono> combinedfilters(@PathVariable String id) { - // TODO: missing global filters - return this.routeLocator.getRoutes().filter(route -> route.getId().equals(id)).reduce(new HashMap<>(), - this::putItem); - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/actuate/GatewayControllerEndpoint.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/actuate/GatewayControllerEndpoint.java deleted file mode 100644 index 109f2888..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/actuate/GatewayControllerEndpoint.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright 2013-2020 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.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -import org.springframework.boot.actuate.endpoint.web.annotation.RestControllerEndpoint; -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.GlobalFilter; -import org.springframework.cloud.gateway.filter.factory.GatewayFilterFactory; -import org.springframework.cloud.gateway.handler.predicate.RoutePredicateFactory; -import org.springframework.cloud.gateway.route.Route; -import org.springframework.cloud.gateway.route.RouteDefinition; -import org.springframework.cloud.gateway.route.RouteDefinitionLocator; -import org.springframework.cloud.gateway.route.RouteDefinitionWriter; -import org.springframework.cloud.gateway.route.RouteLocator; -import org.springframework.http.ResponseEntity; -import org.springframework.util.CollectionUtils; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; - -/** - * @author Spencer Gibb - */ -@RestControllerEndpoint(id = "gateway", enableByDefault = false) -public class GatewayControllerEndpoint extends AbstractGatewayControllerEndpoint { - - public GatewayControllerEndpoint(List globalFilters, List gatewayFilters, - List routePredicates, RouteDefinitionWriter routeDefinitionWriter, - RouteLocator routeLocator, RouteDefinitionLocator routeDefinitionLocator) { - super(routeDefinitionLocator, globalFilters, gatewayFilters, routePredicates, routeDefinitionWriter, - routeLocator); - } - - @GetMapping("/routedefinitions") - public Flux routesdef() { - return this.routeDefinitionLocator.getRouteDefinitions(); - } - - // TODO: Flush out routes without a definition - @GetMapping("/routes") - public Flux> routes() { - return this.routeLocator.getRoutes().map(this::serialize); - } - - Map serialize(Route route) { - HashMap r = new HashMap<>(); - r.put("route_id", route.getId()); - r.put("uri", route.getUri().toString()); - r.put("order", route.getOrder()); - r.put("predicate", route.getPredicate().toString()); - if (!CollectionUtils.isEmpty(route.getMetadata())) { - r.put("metadata", route.getMetadata()); - } - - ArrayList filters = new ArrayList<>(); - - for (int i = 0; i < route.getFilters().size(); i++) { - GatewayFilter gatewayFilter = route.getFilters().get(i); - filters.add(gatewayFilter.toString()); - } - - r.put("filters", filters); - return r; - } - - @GetMapping("/routes/{id}") - public Mono>> route(@PathVariable String id) { - // @formatter:off - return this.routeLocator.getRoutes() - .filter(route -> route.getId().equals(id)) - .singleOrEmpty() - .map(this::serialize) - .map(ResponseEntity::ok) - .switchIfEmpty(Mono.just(ResponseEntity.notFound().build())); - // @formatter:on - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/actuate/GatewayLegacyControllerEndpoint.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/actuate/GatewayLegacyControllerEndpoint.java deleted file mode 100644 index 5e2064cb..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/actuate/GatewayLegacyControllerEndpoint.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2013-2020 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.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import reactor.core.publisher.Mono; - -import org.springframework.boot.actuate.endpoint.web.annotation.RestControllerEndpoint; -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.GlobalFilter; -import org.springframework.cloud.gateway.filter.factory.GatewayFilterFactory; -import org.springframework.cloud.gateway.handler.predicate.RoutePredicateFactory; -import org.springframework.cloud.gateway.route.Route; -import org.springframework.cloud.gateway.route.RouteDefinition; -import org.springframework.cloud.gateway.route.RouteDefinitionLocator; -import org.springframework.cloud.gateway.route.RouteDefinitionWriter; -import org.springframework.cloud.gateway.route.RouteLocator; -import org.springframework.http.ResponseEntity; -import org.springframework.util.CollectionUtils; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; - -/** - * @author Spencer Gibb - */ -@RestControllerEndpoint(id = "gateway", enableByDefault = false) -public class GatewayLegacyControllerEndpoint extends AbstractGatewayControllerEndpoint { - - public GatewayLegacyControllerEndpoint(RouteDefinitionLocator routeDefinitionLocator, - List globalFilters, List gatewayFilterFactories, - List routePredicates, RouteDefinitionWriter routeDefinitionWriter, - RouteLocator routeLocator) { - super(routeDefinitionLocator, globalFilters, gatewayFilterFactories, routePredicates, routeDefinitionWriter, - routeLocator); - } - - @GetMapping("/routes") - public Mono>> routes() { - Mono> routeDefs = this.routeDefinitionLocator.getRouteDefinitions() - .collectMap(RouteDefinition::getId); - Mono> routes = this.routeLocator.getRoutes().collectList(); - return Mono.zip(routeDefs, routes).map(tuple -> { - Map defs = tuple.getT1(); - List routeList = tuple.getT2(); - List> allRoutes = new ArrayList<>(); - - routeList.forEach(route -> { - HashMap r = new HashMap<>(); - r.put("route_id", route.getId()); - r.put("order", route.getOrder()); - - if (defs.containsKey(route.getId())) { - r.put("route_definition", defs.get(route.getId())); - } - else { - HashMap obj = new HashMap<>(); - - obj.put("predicate", route.getPredicate().toString()); - - if (!route.getFilters().isEmpty()) { - ArrayList filters = new ArrayList<>(); - for (GatewayFilter filter : route.getFilters()) { - filters.add(filter.toString()); - } - - obj.put("filters", filters); - } - - if (!CollectionUtils.isEmpty(route.getMetadata())) { - obj.put("metadata", route.getMetadata()); - } - - if (!obj.isEmpty()) { - r.put("route_object", obj); - } - } - allRoutes.add(r); - }); - - return allRoutes; - }); - } - - @GetMapping("/routes/{id}") - public Mono> route(@PathVariable String id) { - // TODO: missing RouteLocator - return this.routeDefinitionLocator.getRouteDefinitions().filter(route -> route.getId().equals(id)) - .singleOrEmpty().map(ResponseEntity::ok).switchIfEmpty(Mono.just(ResponseEntity.notFound().build())); - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/AbstractSslConfigurer.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/AbstractSslConfigurer.java deleted file mode 100644 index 5f310d3f..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/AbstractSslConfigurer.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright 2013-2020 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.io.IOException; -import java.net.URL; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchProviderException; -import java.security.cert.Certificate; -import java.security.cert.CertificateException; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.Collection; - -import javax.net.ssl.KeyManagerFactory; -import javax.net.ssl.SSLException; -import javax.net.ssl.TrustManagerFactory; - -import io.netty.handler.ssl.SslContextBuilder; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.util.ResourceUtils; - -/** - * Base class to configure SSL for component T. Returns an instance S with the resulting - * configuration (can be the same as T). - * - * @author Abel Salgado Romero - */ -public abstract class AbstractSslConfigurer { - - protected final Log logger = LogFactory.getLog(this.getClass()); - - private final HttpClientProperties.Ssl ssl; - - protected AbstractSslConfigurer(HttpClientProperties.Ssl sslProperties) { - this.ssl = sslProperties; - } - - abstract public S configureSsl(T client) throws SSLException; - - protected HttpClientProperties.Ssl getSslProperties() { - return ssl; - } - - protected X509Certificate[] getTrustedX509CertificatesForTrustManager() { - - try { - CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); - ArrayList allCerts = new ArrayList<>(); - for (String trustedCert : ssl.getTrustedX509Certificates()) { - try { - URL url = ResourceUtils.getURL(trustedCert); - Collection certs = certificateFactory.generateCertificates(url.openStream()); - allCerts.addAll(certs); - } - catch (IOException e) { - throw new RuntimeException("Could not load certificate '" + trustedCert + "'", e); - } - } - return allCerts.toArray(new X509Certificate[allCerts.size()]); - } - catch (CertificateException e1) { - throw new RuntimeException("Could not load CertificateFactory X.509", e1); - } - } - - protected KeyManagerFactory getKeyManagerFactory() { - - try { - if (ssl.getKeyStore() != null && ssl.getKeyStore().length() > 0) { - KeyManagerFactory keyManagerFactory = KeyManagerFactory - .getInstance(KeyManagerFactory.getDefaultAlgorithm()); - char[] keyPassword = ssl.getKeyPassword() != null ? ssl.getKeyPassword().toCharArray() : null; - - if (keyPassword == null && ssl.getKeyStorePassword() != null) { - keyPassword = ssl.getKeyStorePassword().toCharArray(); - } - - keyManagerFactory.init(this.createKeyStore(), keyPassword); - - return keyManagerFactory; - } - - return null; - } - catch (Exception e) { - throw new IllegalStateException(e); - } - } - - protected KeyStore createKeyStore() { - - try { - KeyStore store = ssl.getKeyStoreProvider() != null - ? KeyStore.getInstance(ssl.getKeyStoreType(), ssl.getKeyStoreProvider()) - : KeyStore.getInstance(ssl.getKeyStoreType()); - try { - URL url = ResourceUtils.getURL(ssl.getKeyStore()); - store.load(url.openStream(), - ssl.getKeyStorePassword() != null ? ssl.getKeyStorePassword().toCharArray() : null); - } - catch (Exception e) { - throw new RuntimeException("Could not load key store ' " + ssl.getKeyStore() + "'", e); - } - - return store; - } - catch (KeyStoreException | NoSuchProviderException e) { - throw new RuntimeException("Could not load KeyStore for given type and provider", e); - } - } - - protected void setTrustManager(SslContextBuilder sslContextBuilder, X509Certificate... trustedX509Certificates) { - sslContextBuilder.trustManager(trustedX509Certificates); - } - - protected void setTrustManager(SslContextBuilder sslContextBuilder, TrustManagerFactory factory) { - sslContextBuilder.trustManager(factory); - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/ConfigurableHintsRegistrationProcessor.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/ConfigurableHintsRegistrationProcessor.java deleted file mode 100644 index a2d525c1..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/ConfigurableHintsRegistrationProcessor.java +++ /dev/null @@ -1,160 +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.config; - -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.stream.Collectors; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.aot.hint.MemberCategory; -import org.springframework.aot.hint.ReflectionHints; -import org.springframework.aot.hint.TypeReference; -import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution; -import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.cloud.gateway.filter.factory.FallbackHeadersGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.JsonToGrpcGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.SpringCloudCircuitBreakerResilience4JFilterFactory; -import org.springframework.cloud.gateway.filter.factory.TokenRelayGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.cache.LocalResponseCacheGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.ratelimit.RedisRateLimiter; -import org.springframework.cloud.gateway.support.Configurable; -import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; -import org.springframework.core.ResolvableType; -import org.springframework.core.type.filter.AssignableTypeFilter; - -/** - * A {@link BeanFactoryInitializationAotProcessor} responsible for registering reflection - * hints for {@link Configurable} beans. - * - * @author Josh Long - * @author Olga Maciaszek-Sharma - * @since 4.0.0 - */ -class ConfigurableHintsRegistrationProcessor implements BeanFactoryInitializationAotProcessor { - - private static final Log LOG = LogFactory.getLog(ConfigurableHintsRegistrationProcessor.class); - - private static final String ROOT_GATEWAY_PACKAGE_NAME = "org.springframework.cloud.gateway"; - - private static final Set circuitBreakerConditionalClasses = Set.of( - "org.springframework.web.reactive.DispatcherHandler", - "org.springframework.cloud.circuitbreaker.resilience4j.ReactiveResilience4JAutoConfiguration", - "org.springframework.cloud.client.circuitbreaker.ReactiveCircuitBreakerFactory", - "org.springframework.cloud.circuitbreaker.resilience4j.ReactiveResilience4JCircuitBreakerFactory"); - - private static final Map, Set> beansConditionalOnClasses = Map.of( - TokenRelayGatewayFilterFactory.class, - Set.of("org.springframework.security.oauth2.client.OAuth2AuthorizedClient", - "org.springframework.security.web.server.SecurityWebFilterChain", - "org.springframework.boot.autoconfigure.security.SecurityProperties"), - JsonToGrpcGatewayFilterFactory.class, Set.of("io.grpc.Channel"), RedisRateLimiter.class, - Set.of("org.springframework.data.redis.core.RedisTemplate", - "org.springframework.web.reactive.DispatcherHandler"), - SpringCloudCircuitBreakerResilience4JFilterFactory.class, circuitBreakerConditionalClasses, - FallbackHeadersGatewayFilterFactory.class, circuitBreakerConditionalClasses, - LocalResponseCacheGatewayFilterFactory.class, - Set.of("com.github.benmanes.caffeine.cache.Weigher", "com.github.benmanes.caffeine.cache.Caffeine", - "org.springframework.cache.caffeine.CaffeineCacheManager")); - - @Override - public BeanFactoryInitializationAotContribution processAheadOfTime(ConfigurableListableBeanFactory beanFactory) { - return (generationContext, beanFactoryInitializationCode) -> { - ReflectionHints hints = generationContext.getRuntimeHints().reflection(); - getConfigurableTypes().forEach(clazz -> hints.registerType(TypeReference.of(clazz), - hint -> hint.withMembers(MemberCategory.DECLARED_FIELDS, MemberCategory.INVOKE_DECLARED_METHODS, - MemberCategory.INVOKE_DECLARED_CONSTRUCTORS))); - }; - } - - private static Set> getConfigurableTypes() { - Set> classesToAdd = getClassesToAdd(); - Set> genericsToAdd = new HashSet<>(); - Set> superTypes = new HashSet<>(); - for (Class clazz : classesToAdd) { - ResolvableType resolvableType = ResolvableType.forType(clazz); - addGenericsForClass(genericsToAdd, resolvableType); - addSuperTypesForClass(resolvableType, superTypes, genericsToAdd); - } - classesToAdd.addAll(genericsToAdd); - classesToAdd.addAll(superTypes); - return classesToAdd.stream().filter(Objects::nonNull).collect(Collectors.toSet()); - - } - - private static void addGenericsForClass(Set> genericsToAdd, ResolvableType resolvableType) { - if (resolvableType.getSuperType().hasGenerics()) { - genericsToAdd.addAll(Arrays.stream(resolvableType.getSuperType().getGenerics()).map(ResolvableType::toClass) - .collect(Collectors.toSet())); - } - } - - private static void addSuperTypesForClass(ResolvableType resolvableType, Set> supertypesToAdd, - Set> genericsToAdd) { - ResolvableType superType = resolvableType.getSuperType(); - if (!ResolvableType.NONE.equals(superType)) { - addGenericsForClass(genericsToAdd, superType); - supertypesToAdd.add(superType.toClass()); - addSuperTypesForClass(superType, supertypesToAdd, genericsToAdd); - } - } - - @SuppressWarnings({ "rawtypes" }) - private static Set> getClassesToAdd() { - Set> classesToAdd = new HashSet<>(); - ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false); - provider.addIncludeFilter(new AssignableTypeFilter(Configurable.class)); - Set components = provider.findCandidateComponents(ROOT_GATEWAY_PACKAGE_NAME); - for (BeanDefinition component : components) { - Class clazz; - try { - clazz = Class.forName(component.getBeanClassName()); - if (shouldRegisterClass(clazz)) { - classesToAdd.add(clazz); - } - } - catch (NoClassDefFoundError | ClassNotFoundException exception) { - if (LOG.isDebugEnabled()) { - LOG.debug(exception); - } - } - } - return classesToAdd; - } - - private static boolean shouldRegisterClass(Class clazz) { - Set conditionClasses = beansConditionalOnClasses.getOrDefault(clazz, Collections.emptySet()); - for (String conditionClass : conditionClasses) { - try { - ConfigurableHintsRegistrationProcessor.class.getClassLoader().loadClass(conditionClass); - } - catch (ClassNotFoundException e) { - return false; - } - } - return true; - } - -} 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 deleted file mode 100644 index ca2b7a14..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java +++ /dev/null @@ -1,882 +0,0 @@ -/* - * Copyright 2013-2022 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.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.util.List; -import java.util.Set; -import java.util.function.Supplier; - -import javax.net.ssl.TrustManagerFactory; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import reactor.core.publisher.Flux; -import reactor.netty.http.client.HttpClient; -import reactor.netty.http.client.WebsocketClientSpec; -import reactor.netty.http.server.WebsocketServerSpec; - -import org.springframework.aot.hint.MemberCategory; -import org.springframework.aot.hint.RuntimeHints; -import org.springframework.aot.hint.RuntimeHintsRegistrar; -import org.springframework.aot.hint.TypeReference; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint; -import org.springframework.boot.actuate.health.Health; -import org.springframework.boot.autoconfigure.AutoConfigureAfter; -import org.springframework.boot.autoconfigure.AutoConfigureBefore; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.autoconfigure.condition.NoneNestedConditions; -import org.springframework.boot.autoconfigure.security.SecurityProperties; -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.context.properties.EnableConfigurationProperties; -import org.springframework.boot.context.properties.PropertyMapper; -import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory; -import org.springframework.cloud.gateway.actuate.GatewayControllerEndpoint; -import org.springframework.cloud.gateway.actuate.GatewayLegacyControllerEndpoint; -import org.springframework.cloud.gateway.config.conditional.ConditionalOnEnabledFilter; -import org.springframework.cloud.gateway.config.conditional.ConditionalOnEnabledGlobalFilter; -import org.springframework.cloud.gateway.config.conditional.ConditionalOnEnabledPredicate; -import org.springframework.cloud.gateway.filter.AdaptCachedBodyGlobalFilter; -import org.springframework.cloud.gateway.filter.FilterDefinition; -import org.springframework.cloud.gateway.filter.ForwardPathFilter; -import org.springframework.cloud.gateway.filter.ForwardRoutingFilter; -import org.springframework.cloud.gateway.filter.GlobalFilter; -import org.springframework.cloud.gateway.filter.NettyRoutingFilter; -import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter; -import org.springframework.cloud.gateway.filter.RemoveCachedBodyFilter; -import org.springframework.cloud.gateway.filter.RouteToRequestUrlFilter; -import org.springframework.cloud.gateway.filter.WebsocketRoutingFilter; -import org.springframework.cloud.gateway.filter.WeightCalculatorWebFilter; -import org.springframework.cloud.gateway.filter.cors.CorsGatewayFilterApplicationListener; -import org.springframework.cloud.gateway.filter.factory.AbstractNameValueGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.AddRequestHeaderGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.AddRequestHeadersIfNotPresentGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.AddRequestParameterGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.AddResponseHeaderGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.CacheRequestBodyGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.DedupeResponseHeaderGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.GatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.JsonToGrpcGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.MapRequestHeaderGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.PrefixPathGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.PreserveHostHeaderGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.RedirectToGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.RemoveJsonAttributesResponseBodyGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.RemoveRequestHeaderGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.RemoveRequestParameterGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.RemoveResponseHeaderGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.RequestHeaderSizeGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.RequestHeaderToRequestUriGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.RequestRateLimiterGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.RequestSizeGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.RetryGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.RewriteLocationResponseHeaderGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.RewritePathGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.RewriteResponseHeaderGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.SaveSessionGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.SecureHeadersGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.SecureHeadersProperties; -import org.springframework.cloud.gateway.filter.factory.SetPathGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.SetRequestHeaderGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.SetRequestHostHeaderGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.SetResponseHeaderGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.SetStatusGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.StripPrefixGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.TokenRelayGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.rewrite.GzipMessageBodyResolver; -import org.springframework.cloud.gateway.filter.factory.rewrite.MessageBodyDecoder; -import org.springframework.cloud.gateway.filter.factory.rewrite.MessageBodyEncoder; -import org.springframework.cloud.gateway.filter.factory.rewrite.ModifyRequestBodyGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.rewrite.ModifyResponseBodyGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.headers.ForwardedHeadersFilter; -import org.springframework.cloud.gateway.filter.headers.GRPCRequestHeadersFilter; -import org.springframework.cloud.gateway.filter.headers.GRPCResponseHeadersFilter; -import org.springframework.cloud.gateway.filter.headers.HttpHeadersFilter; -import org.springframework.cloud.gateway.filter.headers.RemoveHopByHopHeadersFilter; -import org.springframework.cloud.gateway.filter.headers.TransferEncodingNormalizationHeadersFilter; -import org.springframework.cloud.gateway.filter.headers.XForwardedHeadersFilter; -import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver; -import org.springframework.cloud.gateway.filter.ratelimit.PrincipalNameKeyResolver; -import org.springframework.cloud.gateway.filter.ratelimit.RateLimiter; -import org.springframework.cloud.gateway.handler.FilteringWebHandler; -import org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping; -import org.springframework.cloud.gateway.handler.predicate.AfterRoutePredicateFactory; -import org.springframework.cloud.gateway.handler.predicate.BeforeRoutePredicateFactory; -import org.springframework.cloud.gateway.handler.predicate.BetweenRoutePredicateFactory; -import org.springframework.cloud.gateway.handler.predicate.CloudFoundryRouteServiceRoutePredicateFactory; -import org.springframework.cloud.gateway.handler.predicate.CookieRoutePredicateFactory; -import org.springframework.cloud.gateway.handler.predicate.HeaderRoutePredicateFactory; -import org.springframework.cloud.gateway.handler.predicate.HostRoutePredicateFactory; -import org.springframework.cloud.gateway.handler.predicate.MethodRoutePredicateFactory; -import org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory; -import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition; -import org.springframework.cloud.gateway.handler.predicate.QueryRoutePredicateFactory; -import org.springframework.cloud.gateway.handler.predicate.ReadBodyRoutePredicateFactory; -import org.springframework.cloud.gateway.handler.predicate.RemoteAddrRoutePredicateFactory; -import org.springframework.cloud.gateway.handler.predicate.RoutePredicateFactory; -import org.springframework.cloud.gateway.handler.predicate.WeightRoutePredicateFactory; -import org.springframework.cloud.gateway.handler.predicate.XForwardedRemoteAddrRoutePredicateFactory; -import org.springframework.cloud.gateway.route.CachingRouteLocator; -import org.springframework.cloud.gateway.route.CompositeRouteDefinitionLocator; -import org.springframework.cloud.gateway.route.CompositeRouteLocator; -import org.springframework.cloud.gateway.route.InMemoryRouteDefinitionRepository; -import org.springframework.cloud.gateway.route.RouteDefinitionLocator; -import org.springframework.cloud.gateway.route.RouteDefinitionRepository; -import org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator; -import org.springframework.cloud.gateway.route.RouteDefinitionWriter; -import org.springframework.cloud.gateway.route.RouteLocator; -import org.springframework.cloud.gateway.route.RouteRefreshListener; -import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; -import org.springframework.cloud.gateway.support.ConfigurationService; -import org.springframework.cloud.gateway.support.KeyValueConverter; -import org.springframework.cloud.gateway.support.StringToZonedDateTimeConverter; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Conditional; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.DependsOn; -import org.springframework.context.annotation.Primary; -import org.springframework.core.convert.ConversionService; -import org.springframework.core.env.Environment; -import org.springframework.core.io.ResourceLoader; -import org.springframework.http.codec.ServerCodecConfigurer; -import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; -import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientManager; -import org.springframework.security.web.server.SecurityWebFilterChain; -import org.springframework.util.ClassUtils; -import org.springframework.validation.Validator; -import org.springframework.web.reactive.DispatcherHandler; -import org.springframework.web.reactive.socket.client.ReactorNettyWebSocketClient; -import org.springframework.web.reactive.socket.client.WebSocketClient; -import org.springframework.web.reactive.socket.server.RequestUpgradeStrategy; -import org.springframework.web.reactive.socket.server.WebSocketService; -import org.springframework.web.reactive.socket.server.support.HandshakeWebSocketService; -import org.springframework.web.reactive.socket.server.upgrade.ReactorNettyRequestUpgradeStrategy; - -/** - * @author Spencer Gibb - * @author Ziemowit Stolarczyk - * @author Mete Alpaslan Katırcıoğlu - * @author Alberto C. Ríos - * @author Olga Maciaszek-Sharma - */ -@Configuration(proxyBeanMethods = false) -@ConditionalOnProperty(name = "spring.cloud.gateway.enabled", matchIfMissing = true) -@EnableConfigurationProperties -@AutoConfigureBefore({ HttpHandlerAutoConfiguration.class, WebFluxAutoConfiguration.class }) -@AutoConfigureAfter({ GatewayReactiveLoadBalancerClientAutoConfiguration.class, - GatewayClassPathWarningAutoConfiguration.class }) -@ConditionalOnClass(DispatcherHandler.class) -public class GatewayAutoConfiguration { - - @Bean - public StringToZonedDateTimeConverter stringToZonedDateTimeConverter() { - return new StringToZonedDateTimeConverter(); - } - - @Bean - public KeyValueConverter keyValueConverter() { - return new KeyValueConverter(); - } - - @Bean - public RouteLocatorBuilder routeLocatorBuilder(ConfigurableApplicationContext context) { - return new RouteLocatorBuilder(context); - } - - @Bean - @ConditionalOnMissingBean - public PropertiesRouteDefinitionLocator propertiesRouteDefinitionLocator(GatewayProperties properties) { - return new PropertiesRouteDefinitionLocator(properties); - } - - @Bean - @ConditionalOnMissingBean(RouteDefinitionRepository.class) - public InMemoryRouteDefinitionRepository inMemoryRouteDefinitionRepository() { - return new InMemoryRouteDefinitionRepository(); - } - - @Bean - @Primary - public RouteDefinitionLocator routeDefinitionLocator(List routeDefinitionLocators) { - return new CompositeRouteDefinitionLocator(Flux.fromIterable(routeDefinitionLocators)); - } - - @Bean - public ConfigurationService gatewayConfigurationService(BeanFactory beanFactory, - @Qualifier("webFluxConversionService") ObjectProvider conversionService, - ObjectProvider validator) { - return new ConfigurationService(beanFactory, conversionService, validator); - } - - @Bean - public RouteLocator routeDefinitionRouteLocator(GatewayProperties properties, - List gatewayFilters, List predicates, - RouteDefinitionLocator routeDefinitionLocator, ConfigurationService configurationService) { - return new RouteDefinitionRouteLocator(routeDefinitionLocator, predicates, gatewayFilters, properties, - configurationService); - } - - @Bean - @Primary - @ConditionalOnMissingBean(name = "cachedCompositeRouteLocator") - // TODO: property to disable composite? - public RouteLocator cachedCompositeRouteLocator(List routeLocators) { - return new CachingRouteLocator(new CompositeRouteLocator(Flux.fromIterable(routeLocators))); - } - - @Bean - @ConditionalOnClass(name = "org.springframework.cloud.client.discovery.event.HeartbeatMonitor") - public RouteRefreshListener routeRefreshListener(ApplicationEventPublisher publisher) { - return new RouteRefreshListener(publisher); - } - - @Bean - @ConditionalOnMissingBean - public FilteringWebHandler filteringWebHandler(List globalFilters) { - return new FilteringWebHandler(globalFilters); - } - - @Bean - public GlobalCorsProperties globalCorsProperties() { - return new GlobalCorsProperties(); - } - - @Bean - @ConditionalOnProperty(name = "spring.cloud.gateway.globalcors.enabled", matchIfMissing = true) - public CorsGatewayFilterApplicationListener corsGatewayFilterApplicationListener( - GlobalCorsProperties globalCorsProperties, RoutePredicateHandlerMapping routePredicateHandlerMapping, - RouteLocator routeLocator) { - return new CorsGatewayFilterApplicationListener(globalCorsProperties, routePredicateHandlerMapping, - routeLocator); - } - - @Bean - @ConditionalOnMissingBean - public RoutePredicateHandlerMapping routePredicateHandlerMapping(FilteringWebHandler webHandler, - RouteLocator routeLocator, GlobalCorsProperties globalCorsProperties, Environment environment) { - return new RoutePredicateHandlerMapping(webHandler, routeLocator, globalCorsProperties, environment); - } - - @Bean - public GatewayProperties gatewayProperties() { - return new GatewayProperties(); - } - - // ConfigurationProperty beans - - @Bean - public SecureHeadersProperties secureHeadersProperties() { - return new SecureHeadersProperties(); - } - - @Bean - @ConditionalOnProperty(name = "spring.cloud.gateway.forwarded.enabled", matchIfMissing = true) - public ForwardedHeadersFilter forwardedHeadersFilter() { - return new ForwardedHeadersFilter(); - } - - // HttpHeaderFilter beans - - @Bean - public RemoveHopByHopHeadersFilter removeHopByHopHeadersFilter() { - return new RemoveHopByHopHeadersFilter(); - } - - @Bean - @ConditionalOnProperty(name = "spring.cloud.gateway.x-forwarded.enabled", matchIfMissing = true) - public XForwardedHeadersFilter xForwardedHeadersFilter() { - return new XForwardedHeadersFilter(); - } - - @Bean - @ConditionalOnProperty(name = "server.http2.enabled", matchIfMissing = true) - public GRPCRequestHeadersFilter gRPCRequestHeadersFilter() { - return new GRPCRequestHeadersFilter(); - } - - @Bean - @ConditionalOnProperty(name = "server.http2.enabled", matchIfMissing = true) - public GRPCResponseHeadersFilter gRPCResponseHeadersFilter() { - return new GRPCResponseHeadersFilter(); - } - - @Bean - @ConditionalOnEnabledFilter - @ConditionalOnProperty(name = "server.http2.enabled", matchIfMissing = true) - @ConditionalOnClass(name = "io.grpc.Channel") - public JsonToGrpcGatewayFilterFactory jsonToGRPCFilterFactory(GrpcSslConfigurer gRPCSSLContext, - ResourceLoader resourceLoader) { - return new JsonToGrpcGatewayFilterFactory(gRPCSSLContext, resourceLoader); - } - - @Bean - @ConditionalOnEnabledFilter(JsonToGrpcGatewayFilterFactory.class) - @ConditionalOnMissingBean(GrpcSslConfigurer.class) - @ConditionalOnClass(name = "io.grpc.Channel") - public GrpcSslConfigurer grpcSslConfigurer(HttpClientProperties properties) - throws KeyStoreException, NoSuchAlgorithmException { - TrustManagerFactory trustManagerFactory = TrustManagerFactory - .getInstance(TrustManagerFactory.getDefaultAlgorithm()); - trustManagerFactory.init(KeyStore.getInstance(KeyStore.getDefaultType())); - - return new GrpcSslConfigurer(properties.getSsl()); - } - - @Bean - public TransferEncodingNormalizationHeadersFilter transferEncodingNormalizationHeadersFilter() { - return new TransferEncodingNormalizationHeadersFilter(); - } - - // GlobalFilter beans - - @Bean - @ConditionalOnEnabledGlobalFilter - public AdaptCachedBodyGlobalFilter adaptCachedBodyGlobalFilter() { - return new AdaptCachedBodyGlobalFilter(); - } - - @Bean - @ConditionalOnEnabledGlobalFilter - public RemoveCachedBodyFilter removeCachedBodyFilter() { - return new RemoveCachedBodyFilter(); - } - - @Bean - @ConditionalOnEnabledGlobalFilter - public RouteToRequestUrlFilter routeToRequestUrlFilter() { - return new RouteToRequestUrlFilter(); - } - - @Bean - @ConditionalOnEnabledGlobalFilter - public ForwardRoutingFilter forwardRoutingFilter(ObjectProvider dispatcherHandler) { - return new ForwardRoutingFilter(dispatcherHandler); - } - - @Bean - @ConditionalOnEnabledGlobalFilter - public ForwardPathFilter forwardPathFilter() { - return new ForwardPathFilter(); - } - - @Bean - @ConditionalOnEnabledGlobalFilter(WebsocketRoutingFilter.class) - public WebSocketService webSocketService(RequestUpgradeStrategy requestUpgradeStrategy) { - return new HandshakeWebSocketService(requestUpgradeStrategy); - } - - @Bean - @ConditionalOnEnabledGlobalFilter - public WebsocketRoutingFilter websocketRoutingFilter(WebSocketClient webSocketClient, - WebSocketService webSocketService, ObjectProvider> headersFilters) { - return new WebsocketRoutingFilter(webSocketClient, webSocketService, headersFilters); - } - - @Bean - @ConditionalOnEnabledPredicate(WeightRoutePredicateFactory.class) - public WeightCalculatorWebFilter weightCalculatorWebFilter(ConfigurationService configurationService, - ObjectProvider routeLocator) { - return new WeightCalculatorWebFilter(routeLocator, configurationService); - } - - // Predicate Factory beans - - @Bean - @ConditionalOnEnabledPredicate - public AfterRoutePredicateFactory afterRoutePredicateFactory() { - return new AfterRoutePredicateFactory(); - } - - @Bean - @ConditionalOnEnabledPredicate - public BeforeRoutePredicateFactory beforeRoutePredicateFactory() { - return new BeforeRoutePredicateFactory(); - } - - @Bean - @ConditionalOnEnabledPredicate - public BetweenRoutePredicateFactory betweenRoutePredicateFactory() { - return new BetweenRoutePredicateFactory(); - } - - @Bean - @ConditionalOnEnabledPredicate - public CookieRoutePredicateFactory cookieRoutePredicateFactory() { - return new CookieRoutePredicateFactory(); - } - - @Bean - @ConditionalOnEnabledPredicate - public HeaderRoutePredicateFactory headerRoutePredicateFactory() { - return new HeaderRoutePredicateFactory(); - } - - @Bean - @ConditionalOnEnabledPredicate - public HostRoutePredicateFactory hostRoutePredicateFactory() { - return new HostRoutePredicateFactory(); - } - - @Bean - @ConditionalOnEnabledPredicate - public MethodRoutePredicateFactory methodRoutePredicateFactory() { - return new MethodRoutePredicateFactory(); - } - - @Bean - @ConditionalOnEnabledPredicate - public PathRoutePredicateFactory pathRoutePredicateFactory() { - return new PathRoutePredicateFactory(); - } - - @Bean - @ConditionalOnEnabledPredicate - public QueryRoutePredicateFactory queryRoutePredicateFactory() { - return new QueryRoutePredicateFactory(); - } - - @Bean - @ConditionalOnEnabledPredicate - public ReadBodyRoutePredicateFactory readBodyPredicateFactory(ServerCodecConfigurer codecConfigurer) { - return new ReadBodyRoutePredicateFactory(codecConfigurer.getReaders()); - } - - @Bean - @ConditionalOnEnabledPredicate - public RemoteAddrRoutePredicateFactory remoteAddrRoutePredicateFactory() { - return new RemoteAddrRoutePredicateFactory(); - } - - @Bean - @ConditionalOnEnabledPredicate - public XForwardedRemoteAddrRoutePredicateFactory xForwardedRemoteAddrRoutePredicateFactory() { - return new XForwardedRemoteAddrRoutePredicateFactory(); - } - - @Bean - @DependsOn("weightCalculatorWebFilter") - @ConditionalOnEnabledPredicate - public WeightRoutePredicateFactory weightRoutePredicateFactory() { - return new WeightRoutePredicateFactory(); - } - - @Bean - @ConditionalOnEnabledPredicate - public CloudFoundryRouteServiceRoutePredicateFactory cloudFoundryRouteServiceRoutePredicateFactory() { - return new CloudFoundryRouteServiceRoutePredicateFactory(); - } - - // GatewayFilter Factory beans - - @Bean - @ConditionalOnEnabledFilter - public AddRequestHeaderGatewayFilterFactory addRequestHeaderGatewayFilterFactory() { - return new AddRequestHeaderGatewayFilterFactory(); - } - - @Bean - @ConditionalOnEnabledFilter - public AddRequestHeadersIfNotPresentGatewayFilterFactory addRequestHeadersIfNotPresentGatewayFilterFactory() { - return new AddRequestHeadersIfNotPresentGatewayFilterFactory(); - } - - @Bean - @ConditionalOnEnabledFilter - public MapRequestHeaderGatewayFilterFactory mapRequestHeaderGatewayFilterFactory() { - return new MapRequestHeaderGatewayFilterFactory(); - } - - @Bean - @ConditionalOnEnabledFilter - public AddRequestParameterGatewayFilterFactory addRequestParameterGatewayFilterFactory() { - return new AddRequestParameterGatewayFilterFactory(); - } - - @Bean - @ConditionalOnEnabledFilter - public AddResponseHeaderGatewayFilterFactory addResponseHeaderGatewayFilterFactory() { - return new AddResponseHeaderGatewayFilterFactory(); - } - - @Bean - @ConditionalOnEnabledFilter - public ModifyRequestBodyGatewayFilterFactory modifyRequestBodyGatewayFilterFactory( - ServerCodecConfigurer codecConfigurer) { - return new ModifyRequestBodyGatewayFilterFactory(codecConfigurer.getReaders()); - } - - @Bean - @ConditionalOnEnabledFilter - public DedupeResponseHeaderGatewayFilterFactory dedupeResponseHeaderGatewayFilterFactory() { - return new DedupeResponseHeaderGatewayFilterFactory(); - } - - @Bean - @ConditionalOnEnabledFilter - public ModifyResponseBodyGatewayFilterFactory modifyResponseBodyGatewayFilterFactory( - ServerCodecConfigurer codecConfigurer, Set bodyDecoders, - Set bodyEncoders) { - return new ModifyResponseBodyGatewayFilterFactory(codecConfigurer.getReaders(), bodyDecoders, bodyEncoders); - } - - @Bean - @ConditionalOnEnabledFilter - public CacheRequestBodyGatewayFilterFactory cacheRequestBodyGatewayFilterFactory() { - return new CacheRequestBodyGatewayFilterFactory(); - } - - @Bean - @ConditionalOnEnabledFilter - public PrefixPathGatewayFilterFactory prefixPathGatewayFilterFactory() { - return new PrefixPathGatewayFilterFactory(); - } - - @Bean - @ConditionalOnEnabledFilter - public PreserveHostHeaderGatewayFilterFactory preserveHostHeaderGatewayFilterFactory() { - return new PreserveHostHeaderGatewayFilterFactory(); - } - - @Bean - @ConditionalOnEnabledFilter - public RedirectToGatewayFilterFactory redirectToGatewayFilterFactory() { - return new RedirectToGatewayFilterFactory(); - } - - @Bean - @ConditionalOnEnabledFilter - public RemoveJsonAttributesResponseBodyGatewayFilterFactory removeJsonAttributesResponseBodyGatewayFilterFactory( - ServerCodecConfigurer codecConfigurer, Set bodyDecoders, - Set bodyEncoders) { - return new RemoveJsonAttributesResponseBodyGatewayFilterFactory( - new ModifyResponseBodyGatewayFilterFactory(codecConfigurer.getReaders(), bodyDecoders, bodyEncoders)); - } - - @Bean - @ConditionalOnEnabledFilter - public RemoveRequestHeaderGatewayFilterFactory removeRequestHeaderGatewayFilterFactory() { - return new RemoveRequestHeaderGatewayFilterFactory(); - } - - @Bean - @ConditionalOnEnabledFilter - public RemoveRequestParameterGatewayFilterFactory removeRequestParameterGatewayFilterFactory() { - return new RemoveRequestParameterGatewayFilterFactory(); - } - - @Bean - @ConditionalOnEnabledFilter - public RemoveResponseHeaderGatewayFilterFactory removeResponseHeaderGatewayFilterFactory() { - return new RemoveResponseHeaderGatewayFilterFactory(); - } - - @Bean(name = PrincipalNameKeyResolver.BEAN_NAME) - @ConditionalOnBean(RateLimiter.class) - @ConditionalOnMissingBean(KeyResolver.class) - @ConditionalOnEnabledFilter(RequestRateLimiterGatewayFilterFactory.class) - public PrincipalNameKeyResolver principalNameKeyResolver() { - return new PrincipalNameKeyResolver(); - } - - @Bean - @ConditionalOnBean({ RateLimiter.class, KeyResolver.class }) - @ConditionalOnEnabledFilter - public RequestRateLimiterGatewayFilterFactory requestRateLimiterGatewayFilterFactory(RateLimiter rateLimiter, - KeyResolver resolver) { - return new RequestRateLimiterGatewayFilterFactory(rateLimiter, resolver); - } - - @Bean - @ConditionalOnEnabledFilter - public RewritePathGatewayFilterFactory rewritePathGatewayFilterFactory() { - return new RewritePathGatewayFilterFactory(); - } - - @Bean - @ConditionalOnEnabledFilter - public RetryGatewayFilterFactory retryGatewayFilterFactory() { - return new RetryGatewayFilterFactory(); - } - - @Bean - @ConditionalOnEnabledFilter - public SetPathGatewayFilterFactory setPathGatewayFilterFactory() { - return new SetPathGatewayFilterFactory(); - } - - @Bean - @ConditionalOnEnabledFilter - public SecureHeadersGatewayFilterFactory secureHeadersGatewayFilterFactory(SecureHeadersProperties properties) { - return new SecureHeadersGatewayFilterFactory(properties); - } - - @Bean - @ConditionalOnEnabledFilter - public SetRequestHeaderGatewayFilterFactory setRequestHeaderGatewayFilterFactory() { - return new SetRequestHeaderGatewayFilterFactory(); - } - - @Bean - @ConditionalOnEnabledFilter - public SetRequestHostHeaderGatewayFilterFactory setRequestHostHeaderGatewayFilterFactory() { - return new SetRequestHostHeaderGatewayFilterFactory(); - } - - @Bean - @ConditionalOnEnabledFilter - public SetResponseHeaderGatewayFilterFactory setResponseHeaderGatewayFilterFactory() { - return new SetResponseHeaderGatewayFilterFactory(); - } - - @Bean - @ConditionalOnEnabledFilter - public RewriteResponseHeaderGatewayFilterFactory rewriteResponseHeaderGatewayFilterFactory() { - return new RewriteResponseHeaderGatewayFilterFactory(); - } - - @Bean - @ConditionalOnEnabledFilter - public RewriteLocationResponseHeaderGatewayFilterFactory rewriteLocationResponseHeaderGatewayFilterFactory() { - return new RewriteLocationResponseHeaderGatewayFilterFactory(); - } - - @Bean - @ConditionalOnEnabledFilter - public SetStatusGatewayFilterFactory setStatusGatewayFilterFactory() { - return new SetStatusGatewayFilterFactory(); - } - - @Bean - @ConditionalOnEnabledFilter - public SaveSessionGatewayFilterFactory saveSessionGatewayFilterFactory() { - return new SaveSessionGatewayFilterFactory(); - } - - @Bean - @ConditionalOnEnabledFilter - public StripPrefixGatewayFilterFactory stripPrefixGatewayFilterFactory() { - return new StripPrefixGatewayFilterFactory(); - } - - @Bean - @ConditionalOnEnabledFilter - public RequestHeaderToRequestUriGatewayFilterFactory requestHeaderToRequestUriGatewayFilterFactory() { - return new RequestHeaderToRequestUriGatewayFilterFactory(); - } - - @Bean - @ConditionalOnEnabledFilter - public RequestSizeGatewayFilterFactory requestSizeGatewayFilterFactory() { - return new RequestSizeGatewayFilterFactory(); - } - - @Bean - @ConditionalOnEnabledFilter - public RequestHeaderSizeGatewayFilterFactory requestHeaderSizeGatewayFilterFactory() { - return new RequestHeaderSizeGatewayFilterFactory(); - } - - @Bean - public GzipMessageBodyResolver gzipMessageBodyResolver() { - return new GzipMessageBodyResolver(); - } - - @Bean - static ConfigurableHintsRegistrationProcessor configurableHintsRegistrationProcessor() { - return new ConfigurableHintsRegistrationProcessor(); - } - - @Configuration(proxyBeanMethods = false) - @ConditionalOnClass(HttpClient.class) - protected static class NettyConfiguration { - - protected final Log logger = LogFactory.getLog(getClass()); - - @Bean - @ConditionalOnProperty(name = "spring.cloud.gateway.httpserver.wiretap") - public NettyWebServerFactoryCustomizer nettyServerWiretapCustomizer(Environment environment, - ServerProperties serverProperties) { - return new NettyWebServerFactoryCustomizer(environment, serverProperties) { - @Override - public void customize(NettyReactiveWebServerFactory factory) { - factory.addServerCustomizers(httpServer -> httpServer.wiretap(true)); - super.customize(factory); - } - }; - } - - @Bean - public HttpClientSslConfigurer httpClientSslConfigurer(ServerProperties serverProperties, - HttpClientProperties httpClientProperties) { - return new HttpClientSslConfigurer(httpClientProperties.getSsl(), serverProperties) { - }; - } - - @Bean - @ConditionalOnMissingBean({ HttpClient.class, HttpClientFactory.class }) - public HttpClientFactory gatewayHttpClientFactory(HttpClientProperties properties, - ServerProperties serverProperties, List customizers, - HttpClientSslConfigurer sslConfigurer) { - return new HttpClientFactory(properties, serverProperties, sslConfigurer, customizers); - } - - @Bean - public HttpClientProperties httpClientProperties() { - return new HttpClientProperties(); - } - - @Bean - @ConditionalOnEnabledGlobalFilter - public NettyRoutingFilter routingFilter(HttpClient httpClient, - ObjectProvider> headersFilters, HttpClientProperties properties) { - return new NettyRoutingFilter(httpClient, headersFilters, properties); - } - - @Bean - @ConditionalOnEnabledGlobalFilter(NettyRoutingFilter.class) - public NettyWriteResponseFilter nettyWriteResponseFilter(GatewayProperties properties) { - return new NettyWriteResponseFilter(properties.getStreamingMediaTypes()); - } - - @Bean - @ConditionalOnEnabledGlobalFilter(WebsocketRoutingFilter.class) - public ReactorNettyWebSocketClient reactorNettyWebSocketClient(HttpClientProperties properties, - HttpClient httpClient) { - Supplier builderSupplier = () -> { - WebsocketClientSpec.Builder builder = WebsocketClientSpec.builder() - .handlePing(properties.getWebsocket().isProxyPing()); - if (properties.getWebsocket().getMaxFramePayloadLength() != null) { - builder.maxFramePayloadLength(properties.getWebsocket().getMaxFramePayloadLength()); - } - return builder; - }; - return new ReactorNettyWebSocketClient(httpClient, builderSupplier); - } - - @Bean - @ConditionalOnEnabledGlobalFilter(WebsocketRoutingFilter.class) - public ReactorNettyRequestUpgradeStrategy reactorNettyRequestUpgradeStrategy( - HttpClientProperties httpClientProperties) { - - Supplier builderSupplier = () -> { - WebsocketServerSpec.Builder builder = WebsocketServerSpec.builder(); - HttpClientProperties.Websocket websocket = httpClientProperties.getWebsocket(); - PropertyMapper map = PropertyMapper.get(); - map.from(websocket::getMaxFramePayloadLength).whenNonNull().to(builder::maxFramePayloadLength); - map.from(websocket::isProxyPing).to(builder::handlePing); - return builder; - }; - - return new ReactorNettyRequestUpgradeStrategy(builderSupplier); - } - - } - - @Configuration(proxyBeanMethods = false) - @ConditionalOnClass(Health.class) - protected static class GatewayActuatorConfiguration { - - @Bean - @ConditionalOnProperty(name = "spring.cloud.gateway.actuator.verbose.enabled", matchIfMissing = true) - @ConditionalOnAvailableEndpoint - public GatewayControllerEndpoint gatewayControllerEndpoint(List globalFilters, - List gatewayFilters, List routePredicates, - RouteDefinitionWriter routeDefinitionWriter, RouteLocator routeLocator, - RouteDefinitionLocator routeDefinitionLocator) { - return new GatewayControllerEndpoint(globalFilters, gatewayFilters, routePredicates, routeDefinitionWriter, - routeLocator, routeDefinitionLocator); - } - - @Bean - @Conditional(OnVerboseDisabledCondition.class) - @ConditionalOnAvailableEndpoint - public GatewayLegacyControllerEndpoint gatewayLegacyControllerEndpoint( - RouteDefinitionLocator routeDefinitionLocator, List globalFilters, - List gatewayFilters, List routePredicates, - RouteDefinitionWriter routeDefinitionWriter, RouteLocator routeLocator) { - return new GatewayLegacyControllerEndpoint(routeDefinitionLocator, globalFilters, gatewayFilters, - routePredicates, routeDefinitionWriter, routeLocator); - } - - } - - private static class OnVerboseDisabledCondition extends NoneNestedConditions { - - OnVerboseDisabledCondition() { - super(ConfigurationPhase.REGISTER_BEAN); - } - - @ConditionalOnProperty(name = "spring.cloud.gateway.actuator.verbose.enabled", matchIfMissing = true) - static class VerboseDisabled { - - } - - } - - @Configuration(proxyBeanMethods = false) - @ConditionalOnProperty(name = "spring.cloud.gateway.enabled", matchIfMissing = true) - @ConditionalOnClass({ OAuth2AuthorizedClient.class, SecurityWebFilterChain.class, SecurityProperties.class }) - @ConditionalOnEnabledFilter(TokenRelayGatewayFilterFactory.class) - protected static class TokenRelayConfiguration { - - @Bean - public TokenRelayGatewayFilterFactory tokenRelayGatewayFilterFactory( - ObjectProvider clientManager) { - return new TokenRelayGatewayFilterFactory(clientManager); - } - - } - -} - -class GatewayHints implements RuntimeHintsRegistrar { - - @Override - public void registerHints(RuntimeHints hints, ClassLoader classLoader) { - if (!ClassUtils.isPresent("org.springframework.cloud.gateway.route.RouteLocator", classLoader)) { - return; - } - hints.reflection() - .registerType(TypeReference.of(FilterDefinition.class), - hint -> hint.withMembers(MemberCategory.DECLARED_FIELDS, MemberCategory.INVOKE_DECLARED_METHODS, - MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)) - .registerType(TypeReference.of(PredicateDefinition.class), - hint -> hint.withMembers(MemberCategory.DECLARED_FIELDS, MemberCategory.INVOKE_DECLARED_METHODS, - MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)) - .registerType(TypeReference.of(AbstractNameValueGatewayFilterFactory.NameValueConfig.class), - hint -> hint.withMembers(MemberCategory.DECLARED_FIELDS, MemberCategory.INVOKE_DECLARED_METHODS, - MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)) - .registerType(TypeReference.of( - "org.springframework.cloud.gateway.discovery.DiscoveryClientRouteDefinitionLocator$DelegatingServiceInstance"), - hint -> hint.withMembers(MemberCategory.DECLARED_FIELDS, MemberCategory.INVOKE_DECLARED_METHODS, - MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)); - } - -} 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 deleted file mode 100644 index 2fd3e441..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayClassPathWarningAutoConfiguration.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2013-2020 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.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.boot.autoconfigure.AutoConfigureBefore; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; -import org.springframework.cloud.gateway.support.MvcFoundOnClasspathException; -import org.springframework.context.annotation.Configuration; - -@Configuration(proxyBeanMethods = false) -@AutoConfigureBefore(GatewayAutoConfiguration.class) -@ConditionalOnProperty(name = "spring.cloud.gateway.enabled", matchIfMissing = true) -public class GatewayClassPathWarningAutoConfiguration { - - private static final Log log = LogFactory.getLog(GatewayClassPathWarningAutoConfiguration.class); - - private static final String BORDER = "\n\n**********************************************************\n\n"; - - @Configuration(proxyBeanMethods = false) - @ConditionalOnClass(name = "org.springframework.web.servlet.DispatcherServlet") - @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) - protected static class SpringMvcFoundOnClasspathConfiguration { - - public SpringMvcFoundOnClasspathConfiguration() { - throw new MvcFoundOnClasspathException(); - } - - } - - @Configuration(proxyBeanMethods = false) - @ConditionalOnMissingClass("org.springframework.web.reactive.DispatcherHandler") - protected static class WebfluxMissingFromClasspathConfiguration { - - public WebfluxMissingFromClasspathConfiguration() { - log.warn(BORDER + "Spring Webflux is missing from the classpath, " - + "which is required for Spring Cloud Gateway at this time. " - + "Please add spring-boot-starter-webflux dependency." + BORDER); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayEnvironmentPostProcessor.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayEnvironmentPostProcessor.java deleted file mode 100644 index 62ace259..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayEnvironmentPostProcessor.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2013-2020 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.Collections; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.env.EnvironmentPostProcessor; -import org.springframework.core.env.ConfigurableEnvironment; -import org.springframework.core.env.MapPropertySource; - -public class GatewayEnvironmentPostProcessor implements EnvironmentPostProcessor { - - @Override - public void postProcessEnvironment(ConfigurableEnvironment env, SpringApplication application) { - env.getPropertySources().addFirst(new MapPropertySource("gateway-properties", - Collections.singletonMap("spring.webflux.hiddenmethod.filter.enabled", "false"))); - } - -} 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 deleted file mode 100644 index b2be55c9..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayLoadBalancerProperties.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2013-2020 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.springframework.boot.context.properties.ConfigurationProperties; - -/** - * @author Ryan Baxter - */ -@ConfigurationProperties("spring.cloud.gateway.loadbalancer") -public class GatewayLoadBalancerProperties { - - private boolean use404; - - public boolean isUse404() { - return use404; - } - - public void setUse404(boolean use404) { - this.use404 = use404; - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayMetricsAutoConfiguration.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayMetricsAutoConfiguration.java deleted file mode 100644 index ac02e075..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayMetricsAutoConfiguration.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright 2013-2020 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.List; - -import io.micrometer.core.instrument.MeterRegistry; -import io.micrometer.observation.ObservationRegistry; -import io.micrometer.tracing.Tracer; -import io.micrometer.tracing.propagation.Propagator; - -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.boot.actuate.autoconfigure.metrics.CompositeMeterRegistryAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.tracing.TracingProperties; -import org.springframework.boot.autoconfigure.AutoConfigureAfter; -import org.springframework.boot.autoconfigure.AutoConfigureBefore; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.cloud.gateway.filter.GatewayMetricsFilter; -import org.springframework.cloud.gateway.filter.headers.observation.GatewayObservationConvention; -import org.springframework.cloud.gateway.filter.headers.observation.GatewayPropagatingSenderTracingObservationHandler; -import org.springframework.cloud.gateway.filter.headers.observation.ObservationClosingWebExceptionHandler; -import org.springframework.cloud.gateway.filter.headers.observation.ObservedRequestHttpHeadersFilter; -import org.springframework.cloud.gateway.filter.headers.observation.ObservedResponseHttpHeadersFilter; -import org.springframework.cloud.gateway.route.RouteDefinitionLocator; -import org.springframework.cloud.gateway.route.RouteDefinitionMetrics; -import org.springframework.cloud.gateway.support.tagsprovider.GatewayHttpTagsProvider; -import org.springframework.cloud.gateway.support.tagsprovider.GatewayPathTagsProvider; -import org.springframework.cloud.gateway.support.tagsprovider.GatewayRouteTagsProvider; -import org.springframework.cloud.gateway.support.tagsprovider.GatewayTagsProvider; -import org.springframework.cloud.gateway.support.tagsprovider.PropertiesTagsProvider; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.Ordered; -import org.springframework.core.annotation.Order; -import org.springframework.web.reactive.DispatcherHandler; - -@Configuration(proxyBeanMethods = false) -@ConditionalOnProperty(name = GatewayProperties.PREFIX + ".enabled", matchIfMissing = true) -@EnableConfigurationProperties(GatewayMetricsProperties.class) -@AutoConfigureBefore(HttpHandlerAutoConfiguration.class) -@AutoConfigureAfter({ MetricsAutoConfiguration.class, CompositeMeterRegistryAutoConfiguration.class, - ObservationAutoConfiguration.class }) -@ConditionalOnClass({ DispatcherHandler.class, MeterRegistry.class, MetricsAutoConfiguration.class }) -public class GatewayMetricsAutoConfiguration { - - @Bean - public GatewayHttpTagsProvider gatewayHttpTagsProvider() { - return new GatewayHttpTagsProvider(); - } - - @Bean - @ConditionalOnProperty(name = GatewayProperties.PREFIX + ".metrics.tags.path.enabled") - public GatewayPathTagsProvider gatewayPathTagsProvider() { - return new GatewayPathTagsProvider(); - } - - @Bean - public GatewayRouteTagsProvider gatewayRouteTagsProvider() { - return new GatewayRouteTagsProvider(); - } - - @Bean - public PropertiesTagsProvider propertiesTagsProvider(GatewayMetricsProperties properties) { - return new PropertiesTagsProvider(properties.getTags()); - } - - @Bean - @ConditionalOnBean(MeterRegistry.class) - @ConditionalOnProperty(name = GatewayProperties.PREFIX + ".metrics.enabled", matchIfMissing = true) - // don't use @ConditionalOnEnabledGlobalFilter as the above property may - // encompass more than just the filter - public GatewayMetricsFilter gatewayMetricFilter(MeterRegistry meterRegistry, - List tagsProviders, GatewayMetricsProperties properties) { - return new GatewayMetricsFilter(meterRegistry, tagsProviders, properties.getPrefix()); - } - - @Bean - @ConditionalOnBean(MeterRegistry.class) - @ConditionalOnProperty(name = GatewayProperties.PREFIX + ".metrics.enabled", matchIfMissing = true) - public RouteDefinitionMetrics routeDefinitionMetrics(MeterRegistry meterRegistry, - RouteDefinitionLocator routeDefinitionLocator, GatewayMetricsProperties properties) { - return new RouteDefinitionMetrics(meterRegistry, routeDefinitionLocator, properties.getPrefix()); - } - - @Configuration(proxyBeanMethods = false) - @ConditionalOnBean(ObservationRegistry.class) - @ConditionalOnProperty(name = GatewayProperties.PREFIX + ".observability.enabled", matchIfMissing = true) - static class ObservabilityConfiguration { - - @Bean - @ConditionalOnMissingBean - ObservedRequestHttpHeadersFilter observedRequestHttpHeadersFilter(ObservationRegistry observationRegistry, - ObjectProvider gatewayObservationConvention) { - return new ObservedRequestHttpHeadersFilter(observationRegistry, - gatewayObservationConvention.getIfAvailable(() -> null)); - } - - @Bean - @ConditionalOnMissingBean - ObservedResponseHttpHeadersFilter observedResponseHttpHeadersFilter() { - return new ObservedResponseHttpHeadersFilter(); - } - - @Bean - @Order(Ordered.HIGHEST_PRECEDENCE) - ObservationClosingWebExceptionHandler observationClosingWebExceptionHandler() { - return new ObservationClosingWebExceptionHandler(); - } - - @Configuration(proxyBeanMethods = false) - @ConditionalOnClass(Tracer.class) - @ConditionalOnBean(Tracer.class) - static class GatewayTracingConfiguration { - - @Bean - @ConditionalOnMissingBean - @ConditionalOnBean({ Propagator.class, TracingProperties.class }) - @Order(Ordered.HIGHEST_PRECEDENCE + 5) - GatewayPropagatingSenderTracingObservationHandler gatewayPropagatingSenderTracingObservationHandler( - Tracer tracer, Propagator propagator, TracingProperties tracingProperties) { - return new GatewayPropagatingSenderTracingObservationHandler(tracer, propagator, - tracingProperties.getBaggage().getRemoteFields()); - } - - } - - } - -} 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 deleted file mode 100644 index 87a505a3..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayMetricsProperties.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2013-2020 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.HashMap; -import java.util.Map; - -import jakarta.validation.constraints.NotNull; - -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.core.style.ToStringCreator; -import org.springframework.validation.annotation.Validated; - -/** - * @author Ingyu Hwang - */ -@ConfigurationProperties("spring.cloud.gateway.metrics") -@Validated -public class GatewayMetricsProperties { - - /** - * Default metrics prefix. - */ - public static final String DEFAULT_PREFIX = "spring.cloud.gateway"; - - /** - * Enables the collection of metrics data. - */ - private boolean enabled; - - /** - * The prefix of all metrics emitted by gateway. - */ - private String prefix = DEFAULT_PREFIX; - - /** - * Tags map that added to metrics. - */ - @NotNull - private Map tags = new HashMap<>(); - - public boolean isEnabled() { - return enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - - public String getPrefix() { - return prefix; - } - - public void setPrefix(String prefix) { - this.prefix = prefix; - } - - public Map getTags() { - return tags; - } - - public void setTags(Map tags) { - this.tags = tags; - } - - @Override - public String toString() { - return new ToStringCreator(this).append("enabled", enabled).append("prefix", prefix).append("tags", tags) - .toString(); - - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayNoLoadBalancerClientAutoConfiguration.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayNoLoadBalancerClientAutoConfiguration.java deleted file mode 100644 index a57c3042..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayNoLoadBalancerClientAutoConfiguration.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2013-2020 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.net.URI; - -import reactor.core.publisher.Mono; - -import org.springframework.boot.autoconfigure.AutoConfigureAfter; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer; -import org.springframework.cloud.gateway.filter.GatewayFilterChain; -import org.springframework.cloud.gateway.filter.GlobalFilter; -import org.springframework.cloud.gateway.filter.ReactiveLoadBalancerClientFilter; -import org.springframework.cloud.gateway.support.NotFoundException; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.Ordered; -import org.springframework.web.server.ServerWebExchange; - -import static org.springframework.cloud.gateway.filter.ReactiveLoadBalancerClientFilter.LOAD_BALANCER_CLIENT_FILTER_ORDER; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR; - -/** - * @author Spencer Gibb - */ -@Configuration(proxyBeanMethods = false) -@ConditionalOnMissingClass("org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer") -@ConditionalOnMissingBean(ReactiveLoadBalancer.class) -@EnableConfigurationProperties(GatewayLoadBalancerProperties.class) -@AutoConfigureAfter(GatewayReactiveLoadBalancerClientAutoConfiguration.class) -public class GatewayNoLoadBalancerClientAutoConfiguration { - - @Bean - @ConditionalOnMissingBean(ReactiveLoadBalancerClientFilter.class) - public NoLoadBalancerClientFilter noLoadBalancerClientFilter(GatewayLoadBalancerProperties properties) { - return new NoLoadBalancerClientFilter(properties.isUse404()); - } - - protected static class NoLoadBalancerClientFilter implements GlobalFilter, Ordered { - - private final boolean use404; - - public NoLoadBalancerClientFilter(boolean use404) { - this.use404 = use404; - } - - @Override - public int getOrder() { - return LOAD_BALANCER_CLIENT_FILTER_ORDER; - } - - @Override - @SuppressWarnings("Duplicates") - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - URI url = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR); - String schemePrefix = exchange.getAttribute(GATEWAY_SCHEME_PREFIX_ATTR); - if (url == null || (!"lb".equals(url.getScheme()) && !"lb".equals(schemePrefix))) { - return chain.filter(exchange); - } - - throw NotFoundException.create(use404, "Unable to find instance for " + url.getHost()); - } - - } - -} 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 deleted file mode 100644 index 13d44243..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayProperties.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright 2013-2020 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.Arrays; -import java.util.List; - -import jakarta.validation.Valid; -import jakarta.validation.constraints.NotNull; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.cloud.gateway.filter.FilterDefinition; -import org.springframework.cloud.gateway.route.RouteDefinition; -import org.springframework.core.style.ToStringCreator; -import org.springframework.http.MediaType; -import org.springframework.validation.annotation.Validated; - -/** - * @author Spencer Gibb - */ -@ConfigurationProperties(GatewayProperties.PREFIX) -@Validated -public class GatewayProperties { - - /** - * Properties prefix. - */ - public static final String PREFIX = "spring.cloud.gateway"; - - private final Log logger = LogFactory.getLog(getClass()); - - /** - * List of Routes. - */ - @NotNull - @Valid - private List routes = new ArrayList<>(); - - /** - * List of filter definitions that are applied to every route. - */ - private List defaultFilters = new ArrayList<>(); - - private List streamingMediaTypes = Arrays.asList(MediaType.TEXT_EVENT_STREAM, - new MediaType("application", "stream+json"), new MediaType("application", "grpc"), - new MediaType("application", "grpc+protobuf"), new MediaType("application", "grpc+json")); - - /** - * Option to fail on route definition errors, defaults to true. Otherwise, a warning - * is logged. - */ - private boolean failOnRouteDefinitionError = true; - - public List getRoutes() { - return routes; - } - - public void setRoutes(List routes) { - this.routes = routes; - if (routes != null && routes.size() > 0 && logger.isDebugEnabled()) { - logger.debug("Routes supplied from Gateway Properties: " + routes); - } - } - - public List getDefaultFilters() { - return defaultFilters; - } - - public void setDefaultFilters(List defaultFilters) { - this.defaultFilters = defaultFilters; - } - - public List getStreamingMediaTypes() { - return streamingMediaTypes; - } - - public void setStreamingMediaTypes(List streamingMediaTypes) { - this.streamingMediaTypes = streamingMediaTypes; - } - - public boolean isFailOnRouteDefinitionError() { - return failOnRouteDefinitionError; - } - - public void setFailOnRouteDefinitionError(boolean failOnRouteDefinitionError) { - this.failOnRouteDefinitionError = failOnRouteDefinitionError; - } - - @Override - public String toString() { - return new ToStringCreator(this).append("routes", routes).append("defaultFilters", defaultFilters) - .append("streamingMediaTypes", streamingMediaTypes) - .append("failOnRouteDefinitionError", failOnRouteDefinitionError).toString(); - - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayReactiveLoadBalancerClientAutoConfiguration.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayReactiveLoadBalancerClientAutoConfiguration.java deleted file mode 100644 index 4ac6890d..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayReactiveLoadBalancerClientAutoConfiguration.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2013-2020 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.springframework.boot.autoconfigure.AutoConfigureAfter; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer; -import org.springframework.cloud.gateway.config.conditional.ConditionalOnEnabledGlobalFilter; -import org.springframework.cloud.gateway.filter.LoadBalancerServiceInstanceCookieFilter; -import org.springframework.cloud.gateway.filter.ReactiveLoadBalancerClientFilter; -import org.springframework.cloud.loadbalancer.config.LoadBalancerAutoConfiguration; -import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.reactive.DispatcherHandler; - -/** - * AutoConfiguration for {@link ReactiveLoadBalancerClientFilter}. - * - * @author Spencer Gibb - * @author Olga Maciaszek-Sharma - */ -@Configuration(proxyBeanMethods = false) -@ConditionalOnClass({ ReactiveLoadBalancer.class, LoadBalancerAutoConfiguration.class, DispatcherHandler.class }) -@AutoConfigureAfter(LoadBalancerAutoConfiguration.class) -@EnableConfigurationProperties(GatewayLoadBalancerProperties.class) -public class GatewayReactiveLoadBalancerClientAutoConfiguration { - - @Bean - @ConditionalOnBean(LoadBalancerClientFactory.class) - @ConditionalOnMissingBean(ReactiveLoadBalancerClientFilter.class) - @ConditionalOnEnabledGlobalFilter - public ReactiveLoadBalancerClientFilter gatewayLoadBalancerClientFilter(LoadBalancerClientFactory clientFactory, - GatewayLoadBalancerProperties properties) { - return new ReactiveLoadBalancerClientFilter(clientFactory, properties); - } - - @Bean - @ConditionalOnBean({ ReactiveLoadBalancerClientFilter.class, LoadBalancerClientFactory.class }) - @ConditionalOnMissingBean - @ConditionalOnEnabledGlobalFilter - public LoadBalancerServiceInstanceCookieFilter loadBalancerServiceInstanceCookieFilter( - LoadBalancerClientFactory loadBalancerClientFactory) { - return new LoadBalancerServiceInstanceCookieFilter(loadBalancerClientFactory); - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayReactiveOAuth2AutoConfiguration.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayReactiveOAuth2AutoConfiguration.java deleted file mode 100644 index 76f454b2..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayReactiveOAuth2AutoConfiguration.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2013-2021 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.springframework.boot.autoconfigure.AutoConfigureAfter; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.autoconfigure.security.SecurityProperties; -import org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration; -import org.springframework.cloud.gateway.config.conditional.ConditionalOnEnabledFilter; -import org.springframework.cloud.gateway.filter.factory.TokenRelayGatewayFilterFactory; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; -import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientManager; -import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientProvider; -import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientProviderBuilder; -import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; -import org.springframework.security.oauth2.client.web.DefaultReactiveOAuth2AuthorizedClientManager; -import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository; -import org.springframework.security.web.server.SecurityWebFilterChain; - -@Configuration(proxyBeanMethods = false) -@ConditionalOnProperty(name = "spring.cloud.gateway.enabled", matchIfMissing = true) -@ConditionalOnClass({ OAuth2AuthorizedClient.class, SecurityWebFilterChain.class, SecurityProperties.class }) -@ConditionalOnEnabledFilter(TokenRelayGatewayFilterFactory.class) -@AutoConfigureAfter(ReactiveSecurityAutoConfiguration.class) -public class GatewayReactiveOAuth2AutoConfiguration { - - @Bean - @ConditionalOnMissingBean - @ConditionalOnBean(ReactiveClientRegistrationRepository.class) - public ReactiveOAuth2AuthorizedClientManager gatewayReactiveOAuth2AuthorizedClientManager( - ReactiveClientRegistrationRepository clientRegistrationRepository, - ServerOAuth2AuthorizedClientRepository authorizedClientRepository) { - ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder - .builder().authorizationCode().refreshToken().build(); - DefaultReactiveOAuth2AuthorizedClientManager authorizedClientManager = new DefaultReactiveOAuth2AuthorizedClientManager( - clientRegistrationRepository, authorizedClientRepository); - authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider); - return authorizedClientManager; - } - -} 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 deleted file mode 100644 index d4624591..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayRedisAutoConfiguration.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright 2013-2020 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.List; - -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.boot.autoconfigure.AutoConfigureAfter; -import org.springframework.boot.autoconfigure.AutoConfigureBefore; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration; -import org.springframework.cloud.gateway.filter.ratelimit.RedisRateLimiter; -import org.springframework.cloud.gateway.route.RedisRouteDefinitionRepository; -import org.springframework.cloud.gateway.route.RouteDefinition; -import org.springframework.cloud.gateway.support.ConfigurationService; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.io.ClassPathResource; -import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory; -import org.springframework.data.redis.core.ReactiveRedisTemplate; -import org.springframework.data.redis.core.ReactiveStringRedisTemplate; -import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.core.script.DefaultRedisScript; -import org.springframework.data.redis.core.script.RedisScript; -import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; -import org.springframework.data.redis.serializer.RedisSerializationContext; -import org.springframework.data.redis.serializer.StringRedisSerializer; -import org.springframework.scripting.support.ResourceScriptSource; -import org.springframework.web.reactive.DispatcherHandler; - -@Configuration(proxyBeanMethods = false) -@AutoConfigureAfter(RedisReactiveAutoConfiguration.class) -@AutoConfigureBefore(GatewayAutoConfiguration.class) -@ConditionalOnBean(ReactiveRedisTemplate.class) -@ConditionalOnClass({ RedisTemplate.class, DispatcherHandler.class }) -@ConditionalOnProperty(name = "spring.cloud.gateway.redis.enabled", matchIfMissing = true) -class GatewayRedisAutoConfiguration { - - @Bean - @SuppressWarnings("unchecked") - public RedisScript redisRequestRateLimiterScript() { - DefaultRedisScript redisScript = new DefaultRedisScript<>(); - redisScript.setScriptSource( - new ResourceScriptSource(new ClassPathResource("META-INF/scripts/request_rate_limiter.lua"))); - redisScript.setResultType(List.class); - return redisScript; - } - - @Bean - @ConditionalOnMissingBean - public RedisRateLimiter redisRateLimiter(ReactiveStringRedisTemplate redisTemplate, - @Qualifier(RedisRateLimiter.REDIS_SCRIPT_NAME) RedisScript> redisScript, - ConfigurationService configurationService) { - return new RedisRateLimiter(redisTemplate, redisScript, configurationService); - } - - @Bean - @ConditionalOnProperty(value = "spring.cloud.gateway.redis-route-definition-repository.enabled", - havingValue = "true") - @ConditionalOnClass(ReactiveRedisTemplate.class) - public RedisRouteDefinitionRepository redisRouteDefinitionRepository( - ReactiveRedisTemplate reactiveRedisTemplate) { - return new RedisRouteDefinitionRepository(reactiveRedisTemplate); - } - - @Bean - public ReactiveRedisTemplate reactiveRedisRouteDefinitionTemplate( - ReactiveRedisConnectionFactory factory) { - StringRedisSerializer keySerializer = new StringRedisSerializer(); - Jackson2JsonRedisSerializer valueSerializer = new Jackson2JsonRedisSerializer<>( - RouteDefinition.class); - RedisSerializationContext.RedisSerializationContextBuilder builder = RedisSerializationContext - .newSerializationContext(keySerializer); - RedisSerializationContext context = builder.value(valueSerializer).build(); - - return new ReactiveRedisTemplate<>(factory, context); - } - -} 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 deleted file mode 100644 index 598b1ef2..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayResilience4JCircuitBreakerAutoConfiguration.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2013-2020 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.springframework.beans.factory.ObjectProvider; -import org.springframework.boot.autoconfigure.AutoConfigureAfter; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.cloud.circuitbreaker.resilience4j.ReactiveResilience4JAutoConfiguration; -import org.springframework.cloud.circuitbreaker.resilience4j.ReactiveResilience4JCircuitBreakerFactory; -import org.springframework.cloud.client.circuitbreaker.ReactiveCircuitBreakerFactory; -import org.springframework.cloud.gateway.config.conditional.ConditionalOnEnabledFilter; -import org.springframework.cloud.gateway.filter.factory.FallbackHeadersGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.SpringCloudCircuitBreakerResilience4JFilterFactory; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.reactive.DispatcherHandler; - -/** - * @author Ryan Baxter - */ -@Configuration(proxyBeanMethods = false) -@ConditionalOnProperty(name = "spring.cloud.gateway.enabled", matchIfMissing = true) -@AutoConfigureAfter({ ReactiveResilience4JAutoConfiguration.class }) -@ConditionalOnClass({ DispatcherHandler.class, ReactiveResilience4JAutoConfiguration.class, - ReactiveCircuitBreakerFactory.class, ReactiveResilience4JCircuitBreakerFactory.class }) -public class GatewayResilience4JCircuitBreakerAutoConfiguration { - - @Bean - @ConditionalOnBean(ReactiveResilience4JCircuitBreakerFactory.class) - @ConditionalOnEnabledFilter - public SpringCloudCircuitBreakerResilience4JFilterFactory springCloudCircuitBreakerResilience4JFilterFactory( - ReactiveResilience4JCircuitBreakerFactory reactiveCircuitBreakerFactory, - ObjectProvider dispatcherHandler) { - return new SpringCloudCircuitBreakerResilience4JFilterFactory(reactiveCircuitBreakerFactory, dispatcherHandler); - } - - @Bean - @ConditionalOnMissingBean - @ConditionalOnEnabledFilter - public FallbackHeadersGatewayFilterFactory fallbackHeadersGatewayFilterFactory() { - return new FallbackHeadersGatewayFilterFactory(); - } - -} 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 deleted file mode 100644 index db102fd8..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GlobalCorsProperties.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2013-2020 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.LinkedHashMap; -import java.util.Map; - -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping; -import org.springframework.web.cors.CorsConfiguration; - -/** - * Configuration properties for global configuration of cors. See - * {@link RoutePredicateHandlerMapping} - */ -@ConfigurationProperties("spring.cloud.gateway.globalcors") -public class GlobalCorsProperties { - - private final Map corsConfigurations = new LinkedHashMap<>(); - - public Map getCorsConfigurations() { - return corsConfigurations; - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GrpcSslConfigurer.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GrpcSslConfigurer.java deleted file mode 100644 index f3b6907e..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GrpcSslConfigurer.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2013-2020 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 javax.net.ssl.SSLException; - -import io.grpc.ManagedChannel; -import io.grpc.netty.GrpcSslContexts; -import io.grpc.netty.NettyChannelBuilder; -import io.netty.handler.ssl.SslContext; -import io.netty.handler.ssl.SslContextBuilder; -import io.netty.handler.ssl.util.InsecureTrustManagerFactory; - -/** - * @author Alberto C. Ríos - */ -public class GrpcSslConfigurer extends AbstractSslConfigurer { - - public GrpcSslConfigurer(HttpClientProperties.Ssl sslProperties) { - super(sslProperties); - } - - @Override - public ManagedChannel configureSsl(NettyChannelBuilder NettyChannelBuilder) throws SSLException { - return NettyChannelBuilder.useTransportSecurity().sslContext(getSslContext()).build(); - } - - private SslContext getSslContext() throws SSLException { - - final SslContextBuilder sslContextBuilder = GrpcSslContexts.forClient(); - - final HttpClientProperties.Ssl ssl = getSslProperties(); - boolean useInsecureTrustManager = ssl.isUseInsecureTrustManager(); - if (useInsecureTrustManager) { - sslContextBuilder.trustManager(InsecureTrustManagerFactory.INSTANCE.getTrustManagers()[0]); - } - - if (!useInsecureTrustManager && ssl.getTrustedX509Certificates().size() > 0) { - sslContextBuilder.trustManager(getTrustedX509CertificatesForTrustManager()); - } - - return sslContextBuilder.keyManager(getKeyManagerFactory()).build(); - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/HttpClientCustomizer.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/HttpClientCustomizer.java deleted file mode 100644 index e10ecb7e..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/HttpClientCustomizer.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2013-2020 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 reactor.netty.http.client.HttpClient; - -@FunctionalInterface -public interface HttpClientCustomizer { - - /** - * Customize the specified {@link HttpClient}. - * @param httpClient the http client to customize. - * @return the customized HttpClient. - */ - HttpClient customize(HttpClient httpClient); - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/HttpClientFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/HttpClientFactory.java deleted file mode 100644 index 0aaa9f52..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/HttpClientFactory.java +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright 2013-2022 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.time.Duration; -import java.util.List; - -import io.netty.channel.ChannelOption; -import reactor.netty.http.HttpProtocol; -import reactor.netty.http.client.HttpClient; -import reactor.netty.http.client.HttpResponseDecoderSpec; -import reactor.netty.resources.ConnectionProvider; -import reactor.netty.transport.ProxyProvider; - -import org.springframework.beans.factory.config.AbstractFactoryBean; -import org.springframework.boot.autoconfigure.web.ServerProperties; -import org.springframework.boot.context.properties.PropertyMapper; -import org.springframework.core.annotation.AnnotationAwareOrderComparator; -import org.springframework.util.CollectionUtils; -import org.springframework.util.StringUtils; - -import static org.springframework.cloud.gateway.config.HttpClientProperties.Pool.PoolType.DISABLED; -import static org.springframework.cloud.gateway.config.HttpClientProperties.Pool.PoolType.FIXED; - -/** - * Factory Bean that allows users to extend and customize parts of the HttpClient. Also - * allows for testing the configuration of the HttpClient. - * - * @author Spencer Gibb - * @since 3.1.1 - */ -public class HttpClientFactory extends AbstractFactoryBean { - - protected final HttpClientProperties properties; - - protected final ServerProperties serverProperties; - - protected final HttpClientSslConfigurer sslConfigurer; - - protected final List customizers; - - public HttpClientFactory(HttpClientProperties properties, ServerProperties serverProperties, - List customizers) { - this.properties = properties; - this.serverProperties = serverProperties; - this.sslConfigurer = null; - this.customizers = customizers; - } - - public HttpClientFactory(HttpClientProperties properties, ServerProperties serverProperties, - HttpClientSslConfigurer sslConfigurer, List customizers) { - this.properties = properties; - this.serverProperties = serverProperties; - this.sslConfigurer = sslConfigurer; - this.customizers = customizers; - } - - @Override - public Class getObjectType() { - return HttpClient.class; - } - - @Override - protected HttpClient createInstance() { - // configure pool resources - ConnectionProvider connectionProvider = buildConnectionProvider(properties); - - HttpClient httpClient = HttpClient.create(connectionProvider) - // TODO: move customizations to HttpClientCustomizers - .httpResponseDecoder(this::httpResponseDecoder); - - if (serverProperties.getHttp2().isEnabled()) { - httpClient = httpClient.protocol(HttpProtocol.HTTP11, HttpProtocol.H2); - } - - if (properties.getConnectTimeout() != null) { - httpClient = httpClient.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, properties.getConnectTimeout()); - } - - httpClient = configureProxy(httpClient); - - httpClient = configureSsl(httpClient); - - if (properties.isWiretap()) { - httpClient = httpClient.wiretap(true); - } - - if (properties.isCompression()) { - httpClient = httpClient.compress(true); - } - - httpClient = applyCustomizers(httpClient); - - return httpClient; - } - - protected HttpClient configureSsl(HttpClient httpClient) { - return sslConfigurer.configureSsl(httpClient); - } - - private HttpClient applyCustomizers(HttpClient httpClient) { - if (!CollectionUtils.isEmpty(customizers)) { - customizers.sort(AnnotationAwareOrderComparator.INSTANCE); - for (HttpClientCustomizer customizer : customizers) { - httpClient = customizer.customize(httpClient); - } - } - return httpClient; - } - - protected HttpClient configureProxy(HttpClient httpClient) { - // configure proxy if proxy host is set. - if (StringUtils.hasText(properties.getProxy().getHost())) { - HttpClientProperties.Proxy proxy = properties.getProxy(); - - httpClient = httpClient.proxy(proxySpec -> { - configureProxyProvider(proxy, proxySpec); - }); - } - return httpClient; - } - - protected ProxyProvider.Builder configureProxyProvider(HttpClientProperties.Proxy proxy, - ProxyProvider.TypeSpec proxySpec) { - ProxyProvider.Builder builder = proxySpec.type(proxy.getType()).host(proxy.getHost()); - - PropertyMapper map = PropertyMapper.get(); - - map.from(proxy::getPort).whenNonNull().to(builder::port); - map.from(proxy::getUsername).whenHasText().to(builder::username); - map.from(proxy::getPassword).whenHasText().to(password -> builder.password(s -> password)); - map.from(proxy::getNonProxyHostsPattern).whenHasText().to(builder::nonProxyHosts); - return builder; - } - - protected HttpResponseDecoderSpec httpResponseDecoder(HttpResponseDecoderSpec spec) { - if (properties.getMaxHeaderSize() != null) { - // cast to int is ok, since @Max is Integer.MAX_VALUE - spec.maxHeaderSize((int) properties.getMaxHeaderSize().toBytes()); - } - if (properties.getMaxInitialLineLength() != null) { - // cast to int is ok, since @Max is Integer.MAX_VALUE - spec.maxInitialLineLength((int) properties.getMaxInitialLineLength().toBytes()); - } - return spec; - } - - protected ConnectionProvider buildConnectionProvider(HttpClientProperties properties) { - HttpClientProperties.Pool pool = properties.getPool(); - - ConnectionProvider connectionProvider; - if (pool.getType() == DISABLED) { - connectionProvider = ConnectionProvider.newConnection(); - } - else { - // create either Fixed or Elastic pool - ConnectionProvider.Builder builder = ConnectionProvider.builder(pool.getName()); - if (pool.getType() == FIXED) { - builder.maxConnections(pool.getMaxConnections()).pendingAcquireMaxCount(-1) - .pendingAcquireTimeout(Duration.ofMillis(pool.getAcquireTimeout())); - } - else { - // Elastic - builder.maxConnections(Integer.MAX_VALUE).pendingAcquireTimeout(Duration.ofMillis(0)) - .pendingAcquireMaxCount(-1); - } - - if (pool.getMaxIdleTime() != null) { - builder.maxIdleTime(pool.getMaxIdleTime()); - } - if (pool.getMaxLifeTime() != null) { - builder.maxLifeTime(pool.getMaxLifeTime()); - } - builder.evictInBackground(pool.getEvictionInterval()); - builder.metrics(pool.isMetrics()); - connectionProvider = builder.build(); - } - return connectionProvider; - } - -} 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 deleted file mode 100644 index 1071931b..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/HttpClientProperties.java +++ /dev/null @@ -1,542 +0,0 @@ -/* - * Copyright 2013-2020 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.time.Duration; -import java.util.ArrayList; -import java.util.List; - -import jakarta.validation.constraints.Max; -import reactor.netty.resources.ConnectionProvider; -import reactor.netty.transport.ProxyProvider; - -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.core.style.ToStringCreator; -import org.springframework.util.unit.DataSize; -import org.springframework.validation.annotation.Validated; - -/** - * Configuration properties for the Netty {@link reactor.netty.http.client.HttpClient}. - */ -@ConfigurationProperties("spring.cloud.gateway.httpclient") -@Validated -public class HttpClientProperties { - - /** The connect timeout in millis, the default is 30s. */ - private Integer connectTimeout; - - /** The response timeout. */ - private Duration responseTimeout; - - /** The max response header size. */ - private DataSize maxHeaderSize; - - /** The max initial line length. */ - private DataSize maxInitialLineLength; - - /** Pool configuration for Netty HttpClient. */ - private Pool pool = new Pool(); - - /** Proxy configuration for Netty HttpClient. */ - private Proxy proxy = new Proxy(); - - /** SSL configuration for Netty HttpClient. */ - private Ssl ssl = new Ssl(); - - /** Websocket configuration for Netty HttpClient. */ - private Websocket websocket = new Websocket(); - - /** Enables wiretap debugging for Netty HttpClient. */ - private boolean wiretap; - - /** Enables compression for Netty HttpClient. */ - private boolean compression; - - public Integer getConnectTimeout() { - return connectTimeout; - } - - public void setConnectTimeout(Integer connectTimeout) { - this.connectTimeout = connectTimeout; - } - - public Duration getResponseTimeout() { - return responseTimeout; - } - - public void setResponseTimeout(Duration responseTimeout) { - this.responseTimeout = responseTimeout; - } - - @Max(Integer.MAX_VALUE) - public DataSize getMaxHeaderSize() { - return maxHeaderSize; - } - - public void setMaxHeaderSize(DataSize maxHeaderSize) { - this.maxHeaderSize = maxHeaderSize; - } - - @Max(Integer.MAX_VALUE) - public DataSize getMaxInitialLineLength() { - return maxInitialLineLength; - } - - public void setMaxInitialLineLength(DataSize maxInitialLineLength) { - this.maxInitialLineLength = maxInitialLineLength; - } - - public Pool getPool() { - return pool; - } - - public void setPool(Pool pool) { - this.pool = pool; - } - - public Proxy getProxy() { - return proxy; - } - - public void setProxy(Proxy proxy) { - this.proxy = proxy; - } - - public Ssl getSsl() { - return ssl; - } - - public void setSsl(Ssl ssl) { - this.ssl = ssl; - } - - public Websocket getWebsocket() { - return this.websocket; - } - - public void setWebsocket(Websocket websocket) { - this.websocket = websocket; - } - - public boolean isWiretap() { - return this.wiretap; - } - - public void setWiretap(boolean wiretap) { - this.wiretap = wiretap; - } - - public boolean isCompression() { - return compression; - } - - public void setCompression(boolean compression) { - this.compression = compression; - } - - @Override - public String toString() { - // @formatter:off - return new ToStringCreator(this) - .append("connectTimeout", connectTimeout) - .append("responseTimeout", responseTimeout) - .append("maxHeaderSize", maxHeaderSize) - .append("maxInitialLineLength", maxInitialLineLength) - .append("pool", pool) - .append("proxy", proxy) - .append("ssl", ssl) - .append("websocket", websocket) - .append("wiretap", wiretap) - .append("compression", compression) - .toString(); - // @formatter:on - - } - - public static class Pool { - - /** Type of pool for HttpClient to use, defaults to ELASTIC. */ - private PoolType type = PoolType.ELASTIC; - - /** The channel pool map name, defaults to proxy. */ - private String name = "proxy"; - - /** - * Only for type FIXED, the maximum number of connections before starting pending - * acquisition on existing ones. - */ - private Integer maxConnections = ConnectionProvider.DEFAULT_POOL_MAX_CONNECTIONS; - - /** Only for type FIXED, the maximum time in millis to wait for acquiring. */ - private Long acquireTimeout = ConnectionProvider.DEFAULT_POOL_ACQUIRE_TIMEOUT; - - /** - * Time in millis after which the channel will be closed. If NULL, there is no max - * idle time. - */ - private Duration maxIdleTime = null; - - /** - * Duration after which the channel will be closed. If NULL, there is no max life - * time. - */ - private Duration maxLifeTime = null; - - /** - * Perform regular eviction checks in the background at a specified interval. - * Disabled by default ({@link Duration#ZERO}) - */ - private Duration evictionInterval = Duration.ZERO; - - /** - * Enables channel pools metrics to be collected and registered in Micrometer. - * Disabled by default. - */ - private boolean metrics = false; - - public PoolType getType() { - return type; - } - - public void setType(PoolType type) { - this.type = type; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public Integer getMaxConnections() { - return maxConnections; - } - - public void setMaxConnections(Integer maxConnections) { - this.maxConnections = maxConnections; - } - - public Long getAcquireTimeout() { - return acquireTimeout; - } - - public void setAcquireTimeout(Long acquireTimeout) { - this.acquireTimeout = acquireTimeout; - } - - public Duration getMaxIdleTime() { - return maxIdleTime; - } - - public void setMaxIdleTime(Duration maxIdleTime) { - this.maxIdleTime = maxIdleTime; - } - - public Duration getMaxLifeTime() { - return maxLifeTime; - } - - public void setMaxLifeTime(Duration maxLifeTime) { - this.maxLifeTime = maxLifeTime; - } - - public Duration getEvictionInterval() { - return evictionInterval; - } - - public void setEvictionInterval(Duration evictionInterval) { - this.evictionInterval = evictionInterval; - } - - public boolean isMetrics() { - return metrics; - } - - public void setMetrics(boolean metrics) { - this.metrics = metrics; - } - - @Override - public String toString() { - return "Pool{" + "type=" + type + ", name='" + name + '\'' + ", maxConnections=" + maxConnections - + ", acquireTimeout=" + acquireTimeout + ", maxIdleTime=" + maxIdleTime + ", maxLifeTime=" - + maxLifeTime + ", evictionInterval=" + evictionInterval + ", metrics=" + metrics + '}'; - } - - public enum PoolType { - - /** - * Elastic pool type. - */ - ELASTIC, - - /** - * Fixed pool type. - */ - FIXED, - - /** - * Disabled pool type. - */ - DISABLED - - } - - } - - public static class Proxy { - - /** proxyType for proxy configuration of Netty HttpClient. */ - private ProxyProvider.Proxy type = ProxyProvider.Proxy.HTTP; - - /** Hostname for proxy configuration of Netty HttpClient. */ - private String host; - - /** Port for proxy configuration of Netty HttpClient. */ - private Integer port; - - /** Username for proxy configuration of Netty HttpClient. */ - private String username; - - /** Password for proxy configuration of Netty HttpClient. */ - private String password; - - /** - * Regular expression (Java) for a configured list of hosts. that should be - * reached directly, bypassing the proxy - */ - private String nonProxyHostsPattern; - - public ProxyProvider.Proxy getType() { - return type; - } - - public void setType(ProxyProvider.Proxy type) { - this.type = type; - } - - public String getHost() { - return host; - } - - public void setHost(String host) { - this.host = host; - } - - public Integer getPort() { - return port; - } - - public void setPort(Integer port) { - this.port = port; - } - - public String getUsername() { - return username; - } - - public void setUsername(String username) { - this.username = username; - } - - public String getPassword() { - return password; - } - - public void setPassword(String password) { - this.password = password; - } - - public String getNonProxyHostsPattern() { - return nonProxyHostsPattern; - } - - public void setNonProxyHostsPattern(String nonProxyHostsPattern) { - this.nonProxyHostsPattern = nonProxyHostsPattern; - } - - @Override - public String toString() { - return "Proxy{" + "type='" + type + '\'' + "host='" + host + '\'' + ", port=" + port + ", username='" - + username + '\'' + ", password='" + password + '\'' + ", nonProxyHostsPattern='" - + nonProxyHostsPattern + '\'' + '}'; - } - - } - - public static class Ssl { - - /** - * Installs the netty InsecureTrustManagerFactory. This is insecure and not - * suitable for production. - */ - private boolean useInsecureTrustManager = false; - - /** Trusted certificates for verifying the remote endpoint's certificate. */ - private List trustedX509Certificates = new ArrayList<>(); - - // use netty default SSL timeouts - /** SSL handshake timeout. Default to 10000 ms */ - private Duration handshakeTimeout = Duration.ofMillis(10000); - - /** SSL close_notify flush timeout. Default to 3000 ms. */ - private Duration closeNotifyFlushTimeout = Duration.ofMillis(3000); - - /** SSL close_notify read timeout. Default to 0 ms. */ - private Duration closeNotifyReadTimeout = Duration.ZERO; - - /** Keystore path for Netty HttpClient. */ - private String keyStore; - - /** Keystore type for Netty HttpClient, default is JKS. */ - private String keyStoreType = "JKS"; - - /** Keystore provider for Netty HttpClient, optional field. */ - private String keyStoreProvider; - - /** Keystore password. */ - private String keyStorePassword; - - /** Key password, default is same as keyStorePassword. */ - private String keyPassword; - - public String getKeyStorePassword() { - return keyStorePassword; - } - - public void setKeyStorePassword(String keyStorePassword) { - this.keyStorePassword = keyStorePassword; - } - - public String getKeyStoreType() { - return keyStoreType; - } - - public void setKeyStoreType(String keyStoreType) { - this.keyStoreType = keyStoreType; - } - - public String getKeyStoreProvider() { - return keyStoreProvider; - } - - public void setKeyStoreProvider(String keyStoreProvider) { - this.keyStoreProvider = keyStoreProvider; - } - - public String getKeyStore() { - return keyStore; - } - - public void setKeyStore(String keyStore) { - this.keyStore = keyStore; - } - - public String getKeyPassword() { - return keyPassword; - } - - public void setKeyPassword(String keyPassword) { - this.keyPassword = keyPassword; - } - - public List getTrustedX509Certificates() { - return trustedX509Certificates; - } - - public void setTrustedX509Certificates(List trustedX509) { - this.trustedX509Certificates = trustedX509; - } - - public boolean isUseInsecureTrustManager() { - return useInsecureTrustManager; - } - - public void setUseInsecureTrustManager(boolean useInsecureTrustManager) { - this.useInsecureTrustManager = useInsecureTrustManager; - } - - public Duration getHandshakeTimeout() { - return handshakeTimeout; - } - - public void setHandshakeTimeout(Duration handshakeTimeout) { - this.handshakeTimeout = handshakeTimeout; - } - - public Duration getCloseNotifyFlushTimeout() { - return closeNotifyFlushTimeout; - } - - public void setCloseNotifyFlushTimeout(Duration closeNotifyFlushTimeout) { - this.closeNotifyFlushTimeout = closeNotifyFlushTimeout; - } - - public Duration getCloseNotifyReadTimeout() { - return closeNotifyReadTimeout; - } - - public void setCloseNotifyReadTimeout(Duration closeNotifyReadTimeout) { - this.closeNotifyReadTimeout = closeNotifyReadTimeout; - } - - @Override - public String toString() { - return new ToStringCreator(this).append("useInsecureTrustManager", useInsecureTrustManager) - .append("trustedX509Certificates", trustedX509Certificates) - .append("handshakeTimeout", handshakeTimeout) - .append("closeNotifyFlushTimeout", closeNotifyFlushTimeout) - .append("closeNotifyReadTimeout", closeNotifyReadTimeout).toString(); - } - - } - - public static class Websocket { - - /** Max frame payload length. */ - private Integer maxFramePayloadLength; - - /** Proxy ping frames to downstream services, defaults to true. */ - private boolean proxyPing = true; - - public Integer getMaxFramePayloadLength() { - return this.maxFramePayloadLength; - } - - public void setMaxFramePayloadLength(Integer maxFramePayloadLength) { - this.maxFramePayloadLength = maxFramePayloadLength; - } - - public boolean isProxyPing() { - return proxyPing; - } - - public void setProxyPing(boolean proxyPing) { - this.proxyPing = proxyPing; - } - - @Override - public String toString() { - return new ToStringCreator(this).append("maxFramePayloadLength", maxFramePayloadLength) - .append("proxyPing", proxyPing).toString(); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/HttpClientSslConfigurer.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/HttpClientSslConfigurer.java deleted file mode 100644 index 0938a891..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/HttpClientSslConfigurer.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2013-2020 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.security.cert.X509Certificate; - -import io.netty.handler.ssl.util.InsecureTrustManagerFactory; -import reactor.netty.http.Http11SslContextSpec; -import reactor.netty.http.Http2SslContextSpec; -import reactor.netty.http.client.HttpClient; -import reactor.netty.tcp.SslProvider; - -import org.springframework.boot.autoconfigure.web.ServerProperties; - -public class HttpClientSslConfigurer extends AbstractSslConfigurer { - - private final ServerProperties serverProperties; - - public HttpClientSslConfigurer(HttpClientProperties.Ssl sslProperties, ServerProperties serverProperties) { - super(sslProperties); - this.serverProperties = serverProperties; - } - - public HttpClient configureSsl(HttpClient client) { - final HttpClientProperties.Ssl ssl = getSslProperties(); - - if ((ssl.getKeyStore() != null && ssl.getKeyStore().length() > 0) - || getTrustedX509CertificatesForTrustManager().length > 0 || ssl.isUseInsecureTrustManager()) { - client = client.secure(sslContextSpec -> { - // configure ssl - configureSslContext(ssl, sslContextSpec); - }); - } - return client; - } - - protected void configureSslContext(HttpClientProperties.Ssl ssl, SslProvider.SslContextSpec sslContextSpec) { - SslProvider.ProtocolSslContextSpec clientSslContext = (serverProperties.getHttp2().isEnabled()) - ? Http2SslContextSpec.forClient() : Http11SslContextSpec.forClient(); - clientSslContext.configure(sslContextBuilder -> { - X509Certificate[] trustedX509Certificates = getTrustedX509CertificatesForTrustManager(); - if (trustedX509Certificates.length > 0) { - setTrustManager(sslContextBuilder, trustedX509Certificates); - } - else if (ssl.isUseInsecureTrustManager()) { - setTrustManager(sslContextBuilder, InsecureTrustManagerFactory.INSTANCE); - } - - try { - sslContextBuilder.keyManager(getKeyManagerFactory()); - } - catch (Exception e) { - logger.error(e); - } - }); - - sslContextSpec.sslContext(clientSslContext).handshakeTimeout(ssl.getHandshakeTimeout()) - .closeNotifyFlushTimeout(ssl.getCloseNotifyFlushTimeout()) - .closeNotifyReadTimeout(ssl.getCloseNotifyReadTimeout()); - } - -} 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 deleted file mode 100644 index 233e93e8..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/LocalResponseCacheAutoConfiguration.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright 2013-2020 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.time.Duration; - -import com.github.benmanes.caffeine.cache.Caffeine; -import com.github.benmanes.caffeine.cache.Weigher; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.boot.autoconfigure.condition.AllNestedConditions; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.cache.Cache; -import org.springframework.cache.CacheManager; -import org.springframework.cache.caffeine.CaffeineCacheManager; -import org.springframework.cloud.gateway.config.conditional.ConditionalOnEnabledFilter; -import org.springframework.cloud.gateway.filter.factory.cache.GlobalLocalResponseCacheGatewayFilter; -import org.springframework.cloud.gateway.filter.factory.cache.LocalResponseCacheGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.cache.LocalResponseCacheProperties; -import org.springframework.cloud.gateway.filter.factory.cache.ResponseCacheManagerFactory; -import org.springframework.cloud.gateway.filter.factory.cache.ResponseCacheSizeWeigher; -import org.springframework.cloud.gateway.filter.factory.cache.keygenerator.CacheKeyGenerator; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Conditional; -import org.springframework.context.annotation.Configuration; - -/** - * @author Ignacio Lozano - * @author Marta Medio - */ -@Configuration(proxyBeanMethods = false) -@EnableConfigurationProperties({ LocalResponseCacheProperties.class }) -@ConditionalOnClass({ Weigher.class, Caffeine.class, CaffeineCacheManager.class }) -@ConditionalOnEnabledFilter(LocalResponseCacheGatewayFilterFactory.class) -public class LocalResponseCacheAutoConfiguration { - - private static final Log LOGGER = LogFactory.getLog(LocalResponseCacheAutoConfiguration.class); - - private static final String RESPONSE_CACHE_NAME = "response-cache"; - - /* for testing */ static final String RESPONSE_CACHE_MANAGER_NAME = "gatewayCacheManager"; - - @Bean - @Conditional(LocalResponseCacheAutoConfiguration.OnGlobalLocalResponseCacheCondition.class) - public GlobalLocalResponseCacheGatewayFilter globalLocalResponseCacheGatewayFilter( - ResponseCacheManagerFactory responseCacheManagerFactory, - @Qualifier(RESPONSE_CACHE_MANAGER_NAME) CacheManager cacheManager, - LocalResponseCacheProperties properties) { - return new GlobalLocalResponseCacheGatewayFilter(responseCacheManagerFactory, responseCache(cacheManager), - properties.getTimeToLive()); - } - - @Bean(name = RESPONSE_CACHE_MANAGER_NAME) - @Conditional(LocalResponseCacheAutoConfiguration.OnGlobalLocalResponseCacheCondition.class) - public CacheManager gatewayCacheManager(LocalResponseCacheProperties cacheProperties) { - return createGatewayCacheManager(cacheProperties); - } - - @Bean - public LocalResponseCacheGatewayFilterFactory localResponseCacheGatewayFilterFactory( - ResponseCacheManagerFactory responseCacheManagerFactory, LocalResponseCacheProperties properties) { - return new LocalResponseCacheGatewayFilterFactory(responseCacheManagerFactory, properties.getTimeToLive(), - properties.getSize()); - } - - @Bean - public ResponseCacheManagerFactory responseCacheManagerFactory(CacheKeyGenerator cacheKeyGenerator) { - return new ResponseCacheManagerFactory(cacheKeyGenerator); - } - - @Bean - public CacheKeyGenerator cacheKeyGenerator() { - return new CacheKeyGenerator(); - } - - @SuppressWarnings({ "unchecked", "rawtypes" }) - public static CaffeineCacheManager createGatewayCacheManager(LocalResponseCacheProperties cacheProperties) { - Caffeine caffeine = Caffeine.newBuilder(); - LOGGER.info("Initializing Caffeine"); - Duration ttlSeconds = cacheProperties.getTimeToLive(); - caffeine.expireAfterWrite(ttlSeconds); - - if (cacheProperties.getSize() != null) { - caffeine.maximumWeight(cacheProperties.getSize().toBytes()).weigher(responseCacheSizeWeigher()); - } - CaffeineCacheManager caffeineCacheManager = new CaffeineCacheManager(); - caffeineCacheManager.setCaffeine(caffeine); - return caffeineCacheManager; - } - - private static ResponseCacheSizeWeigher responseCacheSizeWeigher() { - return new ResponseCacheSizeWeigher(); - } - - Cache responseCache(CacheManager cacheManager) { - return cacheManager.getCache(RESPONSE_CACHE_NAME); - } - - public static class OnGlobalLocalResponseCacheCondition extends AllNestedConditions { - - OnGlobalLocalResponseCacheCondition() { - super(ConfigurationPhase.REGISTER_BEAN); - } - - @ConditionalOnProperty(value = "spring.cloud.gateway.enabled", havingValue = "true", matchIfMissing = true) - static class OnGatewayPropertyEnabled { - - } - - @ConditionalOnProperty(value = "spring.cloud.gateway.filter.local-response-cache.enabled", havingValue = "true") - static class OnLocalResponseCachePropertyEnabled { - - } - - @ConditionalOnProperty(name = "spring.cloud.gateway.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/MaxDataSizeValidator.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/MaxDataSizeValidator.java deleted file mode 100644 index 54c94f71..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/MaxDataSizeValidator.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2013-2020 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 jakarta.validation.ConstraintValidator; -import jakarta.validation.ConstraintValidatorContext; -import jakarta.validation.constraints.Max; - -import org.springframework.util.unit.DataSize; - -// https://in.relation.to/2017/03/02/adding-custom-constraint-definitions-via-the-java-service-loader/ -public class MaxDataSizeValidator implements ConstraintValidator { - - private long maxValue; - - @Override - public boolean isValid(DataSize value, ConstraintValidatorContext context) { - // null values are valid - if (value == null) { - return true; - } - return value.toBytes() <= maxValue; - } - - @Override - public void initialize(Max maxValue) { - this.maxValue = maxValue.value(); - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/PropertiesRouteDefinitionLocator.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/PropertiesRouteDefinitionLocator.java deleted file mode 100644 index e7b038ac..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/PropertiesRouteDefinitionLocator.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2013-2020 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 reactor.core.publisher.Flux; - -import org.springframework.cloud.gateway.route.RouteDefinition; -import org.springframework.cloud.gateway.route.RouteDefinitionLocator; - -/** - * @author Spencer Gibb - */ -public class PropertiesRouteDefinitionLocator implements RouteDefinitionLocator { - - private final GatewayProperties properties; - - public PropertiesRouteDefinitionLocator(GatewayProperties properties) { - this.properties = properties; - } - - @Override - public Flux getRouteDefinitions() { - return Flux.fromIterable(this.properties.getRoutes()); - } - -} 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 deleted file mode 100644 index 62c7ffad..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/SimpleUrlHandlerMappingGlobalCorsAutoConfiguration.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2013-2020 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 jakarta.annotation.PostConstruct; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping; - -/** - * This is useful for PreFlight CORS requests. We can add a "global" configuration here so - * we don't have to modify existing predicates to allow the "options" HTTP method. - */ -@Configuration(proxyBeanMethods = false) -@ConditionalOnClass(SimpleUrlHandlerMapping.class) -@ConditionalOnProperty(name = "spring.cloud.gateway.globalcors.add-to-simple-url-handler-mapping", - matchIfMissing = false) -public class SimpleUrlHandlerMappingGlobalCorsAutoConfiguration { - - @Autowired - private GlobalCorsProperties globalCorsProperties; - - @Autowired - private SimpleUrlHandlerMapping simpleUrlHandlerMapping; - - @PostConstruct - void config() { - simpleUrlHandlerMapping.setCorsConfigurations(globalCorsProperties.getCorsConfigurations()); - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/conditional/ConditionalOnEnabledFilter.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/conditional/ConditionalOnEnabledFilter.java deleted file mode 100644 index a84bd9e9..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/conditional/ConditionalOnEnabledFilter.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2013-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.config.conditional; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.springframework.cloud.gateway.filter.factory.GatewayFilterFactory; -import org.springframework.context.annotation.Conditional; - -@Retention(RetentionPolicy.RUNTIME) -@Target({ ElementType.TYPE, ElementType.METHOD }) -@Documented -@Conditional(OnEnabledFilter.class) -public @interface ConditionalOnEnabledFilter { - - /** - * The class component to check for. - * @return the class that must be enabled - */ - Class> value() default OnEnabledFilter.DefaultValue.class; - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/conditional/ConditionalOnEnabledGlobalFilter.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/conditional/ConditionalOnEnabledGlobalFilter.java deleted file mode 100644 index cfbf71f8..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/conditional/ConditionalOnEnabledGlobalFilter.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2013-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.config.conditional; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.springframework.cloud.gateway.filter.GlobalFilter; -import org.springframework.context.annotation.Conditional; - -@Retention(RetentionPolicy.RUNTIME) -@Target({ ElementType.TYPE, ElementType.METHOD }) -@Documented -@Conditional(OnEnabledGlobalFilter.class) -public @interface ConditionalOnEnabledGlobalFilter { - - /** - * The class component to check for. - * @return the class that must be enabled - */ - Class value() default OnEnabledGlobalFilter.DefaultValue.class; - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/conditional/ConditionalOnEnabledPredicate.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/conditional/ConditionalOnEnabledPredicate.java deleted file mode 100644 index 9c6fdc47..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/conditional/ConditionalOnEnabledPredicate.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2013-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.config.conditional; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.springframework.cloud.gateway.handler.predicate.RoutePredicateFactory; -import org.springframework.context.annotation.Conditional; - -@Retention(RetentionPolicy.RUNTIME) -@Target({ ElementType.TYPE, ElementType.METHOD }) -@Documented -@Conditional(OnEnabledPredicate.class) -public @interface ConditionalOnEnabledPredicate { - - /** - * The class components to check for. - * @return the class that must be enabled - */ - Class> value() default OnEnabledPredicate.DefaultValue.class; - -} 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 deleted file mode 100644 index b3211dd5..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/conditional/OnEnabledComponent.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 2013-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.config.conditional; - -import java.util.Map; - -import org.springframework.boot.autoconfigure.condition.ConditionMessage; -import org.springframework.boot.autoconfigure.condition.ConditionOutcome; -import org.springframework.boot.autoconfigure.condition.SpringBootCondition; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.ConditionContext; -import org.springframework.context.annotation.ConfigurationCondition; -import org.springframework.core.env.PropertyResolver; -import org.springframework.core.type.AnnotatedTypeMetadata; -import org.springframework.core.type.MethodMetadata; -import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; - -import static org.springframework.boot.autoconfigure.condition.ConditionMessage.forCondition; - -public abstract class OnEnabledComponent extends SpringBootCondition implements ConfigurationCondition { - - private static final String PREFIX = "spring.cloud.gateway."; - - private static final String SUFFIX = ".enabled"; - - @Override - public ConfigurationPhase getConfigurationPhase() { - return ConfigurationPhase.REGISTER_BEAN; - } - - @Override - public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { - Class candidate = getComponentType(annotationClass(), context, metadata); - return determineOutcome(candidate, context.getEnvironment()); - } - - @SuppressWarnings("unchecked") - protected Class getComponentType(Class annotationClass, ConditionContext context, - AnnotatedTypeMetadata metadata) { - Map attributes = metadata.getAnnotationAttributes(annotationClass.getName()); - if (attributes != null && attributes.containsKey("value")) { - Class target = (Class) attributes.get("value"); - if (target != defaultValueClass()) { - return (Class) target; - } - } - Assert.state(metadata instanceof MethodMetadata && metadata.isAnnotated(Bean.class.getName()), - getClass().getSimpleName() + " must be used on @Bean methods when the value is not specified"); - MethodMetadata methodMetadata = (MethodMetadata) metadata; - try { - return (Class) ClassUtils.forName(methodMetadata.getReturnTypeName(), - context.getClassLoader()); - } - catch (Throwable ex) { - throw new IllegalStateException("Failed to extract component class for " - + methodMetadata.getDeclaringClassName() + "." + methodMetadata.getMethodName(), ex); - } - } - - private ConditionOutcome determineOutcome(Class componentClass, PropertyResolver resolver) { - String key = PREFIX + normalizeComponentName(componentClass) + SUFFIX; - ConditionMessage.Builder messageBuilder = forCondition(annotationClass().getName(), componentClass.getName()); - if ("false".equalsIgnoreCase(resolver.getProperty(key))) { - return ConditionOutcome.noMatch(messageBuilder.because("bean is not available")); - } - return ConditionOutcome.match(); - } - - protected abstract String normalizeComponentName(Class componentClass); - - protected abstract Class annotationClass(); - - protected abstract Class defaultValueClass(); - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/conditional/OnEnabledFilter.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/conditional/OnEnabledFilter.java deleted file mode 100644 index d24e3650..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/conditional/OnEnabledFilter.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2013-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.config.conditional; - -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.factory.GatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.SpringCloudCircuitBreakerFilterFactory; -import org.springframework.cloud.gateway.support.NameUtils; - -public class OnEnabledFilter extends OnEnabledComponent> { - - @Override - protected String normalizeComponentName(Class> filterClass) { - if (SpringCloudCircuitBreakerFilterFactory.class.isAssignableFrom(filterClass)) { - return "filter." - + NameUtils.normalizeToCanonicalPropertyFormat(SpringCloudCircuitBreakerFilterFactory.NAME); - } - else { - return "filter." + NameUtils.normalizeFilterFactoryNameAsProperty(filterClass); - } - } - - @Override - protected Class annotationClass() { - return ConditionalOnEnabledFilter.class; - } - - @Override - protected Class> defaultValueClass() { - return DefaultValue.class; - } - - static class DefaultValue implements GatewayFilterFactory { - - @Override - public GatewayFilter apply(Object config) { - throw new UnsupportedOperationException("class DefaultValue is never meant to be intantiated"); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/conditional/OnEnabledGlobalFilter.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/conditional/OnEnabledGlobalFilter.java deleted file mode 100644 index 513a8709..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/conditional/OnEnabledGlobalFilter.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2013-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.config.conditional; - -import reactor.core.publisher.Mono; - -import org.springframework.cloud.gateway.filter.GatewayFilterChain; -import org.springframework.cloud.gateway.filter.GlobalFilter; -import org.springframework.cloud.gateway.support.NameUtils; -import org.springframework.web.server.ServerWebExchange; - -public class OnEnabledGlobalFilter extends OnEnabledComponent { - - @Override - protected String normalizeComponentName(Class filterClass) { - return "global-filter." + NameUtils.normalizeGlobalFilterNameAsProperty(filterClass); - } - - @Override - protected Class annotationClass() { - return ConditionalOnEnabledGlobalFilter.class; - } - - @Override - protected Class defaultValueClass() { - return DefaultValue.class; - } - - static class DefaultValue implements GlobalFilter { - - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - throw new UnsupportedOperationException("class DefaultValue is never meant to be intantiated"); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/conditional/OnEnabledPredicate.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/conditional/OnEnabledPredicate.java deleted file mode 100644 index f95ad386..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/conditional/OnEnabledPredicate.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2013-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.config.conditional; - -import java.util.function.Consumer; -import java.util.function.Predicate; - -import org.springframework.cloud.gateway.handler.predicate.RoutePredicateFactory; -import org.springframework.cloud.gateway.support.NameUtils; -import org.springframework.web.server.ServerWebExchange; - -public class OnEnabledPredicate extends OnEnabledComponent> { - - @Override - protected String normalizeComponentName(Class> predicateClass) { - return "predicate." + NameUtils.normalizeRoutePredicateNameAsProperty(predicateClass); - } - - @Override - protected Class annotationClass() { - return ConditionalOnEnabledPredicate.class; - } - - @Override - protected Class> defaultValueClass() { - return DefaultValue.class; - } - - static class DefaultValue implements RoutePredicateFactory { - - @Override - public Predicate apply(Consumer consumer) { - throw new UnsupportedOperationException("class DefaultValue is never meant to be intantiated"); - } - - @Override - public Predicate apply(Object config) { - throw new UnsupportedOperationException("class DefaultValue is never meant to be intantiated"); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/discovery/DiscoveryClientRouteDefinitionLocator.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/discovery/DiscoveryClientRouteDefinitionLocator.java deleted file mode 100644 index 82e746b8..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/discovery/DiscoveryClientRouteDefinitionLocator.java +++ /dev/null @@ -1,212 +0,0 @@ -/* - * Copyright 2013-2020 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.discovery; - -import java.net.URI; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.function.Predicate; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import reactor.core.publisher.Flux; - -import org.springframework.cloud.client.ServiceInstance; -import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient; -import org.springframework.cloud.gateway.filter.FilterDefinition; -import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition; -import org.springframework.cloud.gateway.route.RouteDefinition; -import org.springframework.cloud.gateway.route.RouteDefinitionLocator; -import org.springframework.core.style.ToStringCreator; -import org.springframework.expression.EvaluationException; -import org.springframework.expression.Expression; -import org.springframework.expression.ParseException; -import org.springframework.expression.spel.standard.SpelExpressionParser; -import org.springframework.expression.spel.support.SimpleEvaluationContext; -import org.springframework.util.StringUtils; - -/** - * TODO: change to RouteLocator? use java dsl - * - * @author Spencer Gibb - */ -public class DiscoveryClientRouteDefinitionLocator implements RouteDefinitionLocator { - - private static final Log log = LogFactory.getLog(DiscoveryClientRouteDefinitionLocator.class); - - private final DiscoveryLocatorProperties properties; - - private final String routeIdPrefix; - - private final SimpleEvaluationContext evalCtxt; - - private Flux> serviceInstances; - - public DiscoveryClientRouteDefinitionLocator(ReactiveDiscoveryClient discoveryClient, - DiscoveryLocatorProperties properties) { - this(discoveryClient.getClass().getSimpleName(), properties); - serviceInstances = discoveryClient.getServices() - .flatMap(service -> discoveryClient.getInstances(service).collectList()); - } - - private DiscoveryClientRouteDefinitionLocator(String discoveryClientName, DiscoveryLocatorProperties properties) { - this.properties = properties; - if (StringUtils.hasText(properties.getRouteIdPrefix())) { - routeIdPrefix = properties.getRouteIdPrefix(); - } - else { - routeIdPrefix = discoveryClientName + "_"; - } - evalCtxt = SimpleEvaluationContext.forReadOnlyDataBinding().withInstanceMethods().build(); - } - - @Override - public Flux getRouteDefinitions() { - - SpelExpressionParser parser = new SpelExpressionParser(); - Expression includeExpr = parser.parseExpression(properties.getIncludeExpression()); - Expression urlExpr = parser.parseExpression(properties.getUrlExpression()); - - Predicate includePredicate; - if (properties.getIncludeExpression() == null || "true".equalsIgnoreCase(properties.getIncludeExpression())) { - includePredicate = instance -> true; - } - else { - includePredicate = instance -> { - Boolean include = includeExpr.getValue(evalCtxt, instance, Boolean.class); - if (include == null) { - return false; - } - return include; - }; - } - - return serviceInstances.filter(instances -> !instances.isEmpty()).flatMap(Flux::fromIterable) - .filter(includePredicate).collectMap(ServiceInstance::getServiceId) - // remove duplicates - .flatMapMany(map -> Flux.fromIterable(map.values())).map(instance -> { - RouteDefinition routeDefinition = buildRouteDefinition(urlExpr, instance); - - final ServiceInstance instanceForEval = new DelegatingServiceInstance(instance, properties); - - for (PredicateDefinition original : this.properties.getPredicates()) { - PredicateDefinition predicate = new PredicateDefinition(); - predicate.setName(original.getName()); - for (Map.Entry entry : original.getArgs().entrySet()) { - String value = getValueFromExpr(evalCtxt, parser, instanceForEval, entry); - predicate.addArg(entry.getKey(), value); - } - routeDefinition.getPredicates().add(predicate); - } - - for (FilterDefinition original : this.properties.getFilters()) { - FilterDefinition filter = new FilterDefinition(); - filter.setName(original.getName()); - for (Map.Entry entry : original.getArgs().entrySet()) { - String value = getValueFromExpr(evalCtxt, parser, instanceForEval, entry); - filter.addArg(entry.getKey(), value); - } - routeDefinition.getFilters().add(filter); - } - - return routeDefinition; - }); - } - - protected RouteDefinition buildRouteDefinition(Expression urlExpr, ServiceInstance serviceInstance) { - String serviceId = serviceInstance.getServiceId(); - RouteDefinition routeDefinition = new RouteDefinition(); - routeDefinition.setId(this.routeIdPrefix + serviceId); - String uri = urlExpr.getValue(this.evalCtxt, serviceInstance, String.class); - routeDefinition.setUri(URI.create(uri)); - // add instance metadata - routeDefinition.setMetadata(new LinkedHashMap<>(serviceInstance.getMetadata())); - return routeDefinition; - } - - String getValueFromExpr(SimpleEvaluationContext evalCtxt, SpelExpressionParser parser, ServiceInstance instance, - Map.Entry entry) { - try { - Expression valueExpr = parser.parseExpression(entry.getValue()); - return valueExpr.getValue(evalCtxt, instance, String.class); - } - catch (ParseException | EvaluationException e) { - if (log.isDebugEnabled()) { - log.debug("Unable to parse " + entry.getValue(), e); - } - throw e; - } - } - - private static class DelegatingServiceInstance implements ServiceInstance { - - final ServiceInstance delegate; - - private final DiscoveryLocatorProperties properties; - - private DelegatingServiceInstance(ServiceInstance delegate, DiscoveryLocatorProperties properties) { - this.delegate = delegate; - this.properties = properties; - } - - @Override - public String getServiceId() { - if (properties.isLowerCaseServiceId()) { - return delegate.getServiceId().toLowerCase(); - } - return delegate.getServiceId(); - } - - @Override - public String getHost() { - return delegate.getHost(); - } - - @Override - public int getPort() { - return delegate.getPort(); - } - - @Override - public boolean isSecure() { - return delegate.isSecure(); - } - - @Override - public URI getUri() { - return delegate.getUri(); - } - - @Override - public Map getMetadata() { - return delegate.getMetadata(); - } - - @Override - public String getScheme() { - return delegate.getScheme(); - } - - @Override - public String toString() { - return new ToStringCreator(this).append("delegate", delegate).append("properties", properties).toString(); - } - - } - -} 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 deleted file mode 100644 index b13f71cb..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/discovery/DiscoveryLocatorProperties.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright 2013-2020 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.discovery; - -import java.util.ArrayList; -import java.util.List; - -import org.springframework.boot.context.properties.ConfigurationProperties; -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") -public class DiscoveryLocatorProperties { - - /** Flag that enables DiscoveryClient gateway integration. */ - private boolean enabled = false; - - /** - * The prefix for the routeId, defaults to discoveryClient.getClass().getSimpleName() - * + "_". Service Id will be appended to create the routeId. - */ - private String routeIdPrefix; - - /** - * SpEL expression that will evaluate whether to include a service in gateway - * integration or not, defaults to: true. - */ - private String includeExpression = "true"; - - /** - * SpEL expression that create the uri for each route, defaults to: 'lb://'+serviceId. - */ - private String urlExpression = "'lb://'+serviceId"; - - /** - * 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/** - */ - private boolean lowerCaseServiceId = false; - - private List predicates = new ArrayList<>(); - - private List filters = new ArrayList<>(); - - public boolean isEnabled() { - return enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - - public String getRouteIdPrefix() { - return routeIdPrefix; - } - - public void setRouteIdPrefix(String routeIdPrefix) { - this.routeIdPrefix = routeIdPrefix; - } - - public String getIncludeExpression() { - return includeExpression; - } - - public void setIncludeExpression(String includeExpression) { - this.includeExpression = includeExpression; - } - - public String getUrlExpression() { - return urlExpression; - } - - public void setUrlExpression(String urlExpression) { - this.urlExpression = urlExpression; - } - - public boolean isLowerCaseServiceId() { - return lowerCaseServiceId; - } - - public void setLowerCaseServiceId(boolean lowerCaseServiceId) { - this.lowerCaseServiceId = lowerCaseServiceId; - } - - public List getPredicates() { - return predicates; - } - - public void setPredicates(List predicates) { - this.predicates = predicates; - } - - public List getFilters() { - return filters; - } - - public void setFilters(List filters) { - this.filters = filters; - } - - @Override - public String toString() { - return new ToStringCreator(this).append("enabled", enabled).append("routeIdPrefix", routeIdPrefix) - .append("includeExpression", includeExpression).append("urlExpression", urlExpression) - .append("lowerCaseServiceId", lowerCaseServiceId).append("predicates", predicates) - .append("filters", filters).toString(); - } - -} 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 deleted file mode 100644 index 94cc64e2..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/discovery/GatewayDiscoveryClientAutoConfiguration.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright 2013-2020 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.discovery; - -import java.util.ArrayList; -import java.util.List; - -import org.springframework.boot.autoconfigure.AutoConfigureAfter; -import org.springframework.boot.autoconfigure.AutoConfigureBefore; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient; -import org.springframework.cloud.client.discovery.composite.CompositeDiscoveryClientAutoConfiguration; -import org.springframework.cloud.gateway.config.GatewayAutoConfiguration; -import org.springframework.cloud.gateway.filter.FilterDefinition; -import org.springframework.cloud.gateway.filter.factory.RewritePathGatewayFilterFactory; -import org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory; -import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.reactive.DispatcherHandler; - -import static org.springframework.cloud.gateway.filter.factory.RewritePathGatewayFilterFactory.REGEXP_KEY; -import static org.springframework.cloud.gateway.filter.factory.RewritePathGatewayFilterFactory.REPLACEMENT_KEY; -import static org.springframework.cloud.gateway.handler.predicate.RoutePredicateFactory.PATTERN_KEY; -import static org.springframework.cloud.gateway.support.NameUtils.normalizeFilterFactoryName; -import static org.springframework.cloud.gateway.support.NameUtils.normalizeRoutePredicateName; - -/** - * @author Spencer Gibb - */ -@Configuration(proxyBeanMethods = false) -@ConditionalOnProperty(name = "spring.cloud.gateway.enabled", matchIfMissing = true) -@AutoConfigureBefore(GatewayAutoConfiguration.class) -@AutoConfigureAfter(CompositeDiscoveryClientAutoConfiguration.class) -@ConditionalOnClass({ DispatcherHandler.class, CompositeDiscoveryClientAutoConfiguration.class }) -@EnableConfigurationProperties -public class GatewayDiscoveryClientAutoConfiguration { - - public static List initPredicates() { - ArrayList definitions = new ArrayList<>(); - // TODO: add a predicate that matches the url at /serviceId? - - // add a predicate that matches the url at /serviceId/** - PredicateDefinition predicate = new PredicateDefinition(); - predicate.setName(normalizeRoutePredicateName(PathRoutePredicateFactory.class)); - predicate.addArg(PATTERN_KEY, "'/'+serviceId+'/**'"); - definitions.add(predicate); - return definitions; - } - - public static List initFilters() { - ArrayList definitions = new ArrayList<>(); - - // add a filter that removes /serviceId by default - FilterDefinition filter = new FilterDefinition(); - filter.setName(normalizeFilterFactoryName(RewritePathGatewayFilterFactory.class)); - String regex = "'/' + serviceId + '/?(?.*)'"; - String replacement = "'/${remaining}'"; - filter.addArg(REGEXP_KEY, regex); - filter.addArg(REPLACEMENT_KEY, replacement); - definitions.add(filter); - - return definitions; - } - - @Bean - public DiscoveryLocatorProperties discoveryLocatorProperties() { - DiscoveryLocatorProperties properties = new DiscoveryLocatorProperties(); - properties.setPredicates(initPredicates()); - properties.setFilters(initFilters()); - return properties; - } - - @Configuration(proxyBeanMethods = false) - @ConditionalOnProperty(value = "spring.cloud.discovery.reactive.enabled", matchIfMissing = true) - public static class ReactiveDiscoveryClientRouteDefinitionLocatorConfiguration { - - @Bean - @ConditionalOnProperty(name = "spring.cloud.gateway.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/event/EnableBodyCachingEvent.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/event/EnableBodyCachingEvent.java deleted file mode 100644 index 12ae4d25..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/event/EnableBodyCachingEvent.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2013-2020 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.event; - -import org.springframework.context.ApplicationEvent; - -public class EnableBodyCachingEvent extends ApplicationEvent { - - private final String routeId; - - public EnableBodyCachingEvent(Object source, String routeId) { - super(source); - this.routeId = routeId; - } - - public String getRouteId() { - return this.routeId; - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/event/FilterArgsEvent.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/event/FilterArgsEvent.java deleted file mode 100644 index 29ea8f34..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/event/FilterArgsEvent.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2013-2020 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.event; - -import java.util.Map; - -import org.springframework.context.ApplicationEvent; - -public class FilterArgsEvent extends ApplicationEvent { - - private final Map args; - - private String routeId; - - public FilterArgsEvent(Object source, String routeId, Map args) { - super(source); - this.routeId = routeId; - this.args = args; - } - - public String getRouteId() { - return routeId; - } - - public Map getArgs() { - return args; - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/event/PredicateArgsEvent.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/event/PredicateArgsEvent.java deleted file mode 100644 index c8322f82..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/event/PredicateArgsEvent.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2013-2020 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.event; - -import java.util.Map; - -import org.springframework.context.ApplicationEvent; - -public class PredicateArgsEvent extends ApplicationEvent { - - private final Map args; - - private String routeId; - - public PredicateArgsEvent(Object source, String routeId, Map args) { - super(source); - this.routeId = routeId; - this.args = args; - } - - public String getRouteId() { - return routeId; - } - - public Map getArgs() { - return args; - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/event/RefreshRoutesEvent.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/event/RefreshRoutesEvent.java deleted file mode 100644 index bd1dd720..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/event/RefreshRoutesEvent.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2013-2020 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.event; - -import java.util.Map; - -import org.springframework.context.ApplicationEvent; -import org.springframework.util.CollectionUtils; - -/** - * @author Spencer Gibb - */ -public class RefreshRoutesEvent extends ApplicationEvent { - - private final Map metadata; - - /** - * Create a new ApplicationEvent. - * @param source the object on which the event initially occurred (never {@code null}) - */ - public RefreshRoutesEvent(Object source) { - this(source, null); - } - - /** - * Create a new ApplicationEvent that should refresh filtering by {@link #metadata}. - * @param source the object on which the event initially occurred (never {@code null}) - * @param metadata map of metadata the routes should match ({code null} is considered - * a global refresh) - */ - public RefreshRoutesEvent(Object source, Map metadata) { - super(source); - this.metadata = metadata; - } - - public boolean isScoped() { - return !CollectionUtils.isEmpty(getMetadata()); - } - - public Map getMetadata() { - return metadata; - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/event/RefreshRoutesResultEvent.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/event/RefreshRoutesResultEvent.java deleted file mode 100644 index 5e561582..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/event/RefreshRoutesResultEvent.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2013-2020 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.event; - -import org.springframework.context.ApplicationEvent; - -/** - * @author alvin - */ -public class RefreshRoutesResultEvent extends ApplicationEvent { - - private Throwable throwable; - - public RefreshRoutesResultEvent(Object source, Throwable throwable) { - super(source); - this.throwable = throwable; - } - - public RefreshRoutesResultEvent(Object source) { - super(source); - } - - public Throwable getThrowable() { - return throwable; - } - - public boolean isSuccess() { - return throwable == null; - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/event/WeightDefinedEvent.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/event/WeightDefinedEvent.java deleted file mode 100644 index e681ecf4..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/event/WeightDefinedEvent.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2013-2020 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.event; - -import org.springframework.cloud.gateway.support.WeightConfig; -import org.springframework.context.ApplicationEvent; - -public class WeightDefinedEvent extends ApplicationEvent { - - private final WeightConfig weightConfig; - - public WeightDefinedEvent(Object source, WeightConfig weightConfig) { - super(source); - this.weightConfig = weightConfig; - } - - public WeightConfig getWeightConfig() { - return weightConfig; - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/AdaptCachedBodyGlobalFilter.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/AdaptCachedBodyGlobalFilter.java deleted file mode 100644 index 48a125bd..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/AdaptCachedBodyGlobalFilter.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2013-2020 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.filter; - -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; - -import reactor.core.publisher.Mono; - -import org.springframework.cloud.gateway.event.EnableBodyCachingEvent; -import org.springframework.cloud.gateway.route.Route; -import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; -import org.springframework.context.ApplicationListener; -import org.springframework.core.Ordered; -import org.springframework.core.io.buffer.DataBuffer; -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.web.server.ServerWebExchange; - -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.CACHED_REQUEST_BODY_ATTR; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.CACHED_SERVER_HTTP_REQUEST_DECORATOR_ATTR; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR; - -public class AdaptCachedBodyGlobalFilter implements GlobalFilter, Ordered, ApplicationListener { - - private ConcurrentMap routesToCache = new ConcurrentHashMap<>(); - - @Override - public void onApplicationEvent(EnableBodyCachingEvent event) { - this.routesToCache.putIfAbsent(event.getRouteId(), true); - } - - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - // the cached ServerHttpRequest is used when the ServerWebExchange can not be - // mutated, for example, during a predicate where the body is read, but still - // needs to be cached. - ServerHttpRequest cachedRequest = exchange.getAttributeOrDefault(CACHED_SERVER_HTTP_REQUEST_DECORATOR_ATTR, - null); - if (cachedRequest != null) { - exchange.getAttributes().remove(CACHED_SERVER_HTTP_REQUEST_DECORATOR_ATTR); - return chain.filter(exchange.mutate().request(cachedRequest).build()); - } - - // - DataBuffer body = exchange.getAttributeOrDefault(CACHED_REQUEST_BODY_ATTR, null); - Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR); - - if (body != null || !this.routesToCache.containsKey(route.getId())) { - return chain.filter(exchange); - } - - return ServerWebExchangeUtils.cacheRequestBody(exchange, (serverHttpRequest) -> { - // don't mutate and build if same request object - if (serverHttpRequest == exchange.getRequest()) { - return chain.filter(exchange); - } - return chain.filter(exchange.mutate().request(serverHttpRequest).build()); - }); - } - - @Override - public int getOrder() { - return Ordered.HIGHEST_PRECEDENCE + 1000; - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/FilterDefinition.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/FilterDefinition.java deleted file mode 100644 index c4afa629..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/FilterDefinition.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright 2013-2020 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.filter; - -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Objects; - -import jakarta.validation.constraints.NotNull; - -import org.springframework.cloud.gateway.support.NameUtils; -import org.springframework.validation.annotation.Validated; - -import static org.springframework.util.StringUtils.tokenizeToStringArray; - -/** - * @author Spencer Gibb - */ -@Validated -public class FilterDefinition { - - @NotNull - private String name; - - private Map args = new LinkedHashMap<>(); - - public FilterDefinition() { - } - - public FilterDefinition(String text) { - int eqIdx = text.indexOf('='); - if (eqIdx <= 0) { - setName(text); - return; - } - setName(text.substring(0, eqIdx)); - - String[] args = tokenizeToStringArray(text.substring(eqIdx + 1), ","); - - for (int i = 0; i < args.length; i++) { - this.args.put(NameUtils.generateName(i), args[i]); - } - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public Map getArgs() { - return args; - } - - public void setArgs(Map args) { - this.args = args; - } - - public void addArg(String key, String value) { - this.args.put(key, value); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - FilterDefinition that = (FilterDefinition) o; - return Objects.equals(name, that.name) && Objects.equals(args, that.args); - } - - @Override - public int hashCode() { - return Objects.hash(name, args); - } - - @Override - public String toString() { - final StringBuilder sb = new StringBuilder("FilterDefinition{"); - sb.append("name='").append(name).append('\''); - sb.append(", args=").append(args); - sb.append('}'); - return sb.toString(); - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/ForwardPathFilter.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/ForwardPathFilter.java deleted file mode 100644 index 16b472b0..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/ForwardPathFilter.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2013-2020 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.filter; - -import java.net.URI; - -import reactor.core.publisher.Mono; - -import org.springframework.cloud.gateway.route.Route; -import org.springframework.core.Ordered; -import org.springframework.web.server.ServerWebExchange; - -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.isAlreadyRouted; - -/** - * Filter to set the path in the request URI if the {@link Route} URI has the scheme - * forward. - * - * @author Ryan Baxter - */ -public class ForwardPathFilter implements GlobalFilter, Ordered { - - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR); - URI routeUri = route.getUri(); - String scheme = routeUri.getScheme(); - if (isAlreadyRouted(exchange) || !"forward".equals(scheme)) { - return chain.filter(exchange); - } - exchange = exchange.mutate().request(exchange.getRequest().mutate().path(routeUri.getPath()).build()).build(); - return chain.filter(exchange); - } - - @Override - public int getOrder() { - return 0; - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/ForwardRoutingFilter.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/ForwardRoutingFilter.java deleted file mode 100644 index 4191a264..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/ForwardRoutingFilter.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2013-2020 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.filter; - -import java.net.URI; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import reactor.core.publisher.Mono; - -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.core.Ordered; -import org.springframework.web.reactive.DispatcherHandler; -import org.springframework.web.server.ServerWebExchange; - -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.handle; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.isAlreadyRouted; - -public class ForwardRoutingFilter implements GlobalFilter, Ordered { - - private static final Log log = LogFactory.getLog(ForwardRoutingFilter.class); - - private final ObjectProvider dispatcherHandlerProvider; - - // do not use this dispatcherHandler directly, use getDispatcherHandler() instead. - private volatile DispatcherHandler dispatcherHandler; - - public ForwardRoutingFilter(ObjectProvider dispatcherHandlerProvider) { - this.dispatcherHandlerProvider = dispatcherHandlerProvider; - } - - private DispatcherHandler getDispatcherHandler() { - if (dispatcherHandler == null) { - dispatcherHandler = dispatcherHandlerProvider.getIfAvailable(); - } - - return dispatcherHandler; - } - - @Override - public int getOrder() { - return Ordered.LOWEST_PRECEDENCE; - } - - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - URI requestUrl = exchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR); - - String scheme = requestUrl.getScheme(); - if (isAlreadyRouted(exchange) || !"forward".equals(scheme)) { - return chain.filter(exchange); - } - - // TODO: translate url? - - if (log.isTraceEnabled()) { - log.trace("Forwarding to URI: " + requestUrl); - } - - return handle(this.getDispatcherHandler(), exchange); - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/GatewayFilter.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/GatewayFilter.java deleted file mode 100644 index 77158784..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/GatewayFilter.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2013-2020 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.filter; - -import reactor.core.publisher.Mono; - -import org.springframework.cloud.gateway.support.ShortcutConfigurable; -import org.springframework.web.server.ServerWebExchange; - -/** - * Contract for interception-style, chained processing of Web requests that may be used to - * implement cross-cutting, application-agnostic requirements such as security, timeouts, - * and others. Specific to a Gateway - * - * Copied from WebFilter - * - * @author Rossen Stoyanchev - * @since 5.0 - */ -public interface GatewayFilter extends ShortcutConfigurable { - - /** - * Name key. - */ - String NAME_KEY = "name"; - - /** - * Value key. - */ - String VALUE_KEY = "value"; - - /** - * Process the Web request and (optionally) delegate to the next {@code WebFilter} - * through the given {@link GatewayFilterChain}. - * @param exchange the current server exchange - * @param chain provides a way to delegate to the next filter - * @return {@code Mono} to indicate when request processing is complete - */ - Mono filter(ServerWebExchange exchange, GatewayFilterChain chain); - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/GatewayFilterChain.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/GatewayFilterChain.java deleted file mode 100644 index 9af1507e..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/GatewayFilterChain.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2013-2020 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.filter; - -import reactor.core.publisher.Mono; - -import org.springframework.web.server.ServerWebExchange; - -/** - * Contract to allow a {@link GatewayFilter} to delegate to the next in the chain. - * - * Copied from framework WebFilterChain - * - * @author Rossen Stoyanchev - * @since 5.0 - */ -public interface GatewayFilterChain { - - /** - * Delegate to the next {@code GatewayFilter} in the chain. - * @param exchange the current server exchange - * @return {@code Mono} to indicate when request handling is complete - */ - Mono filter(ServerWebExchange exchange); - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/GatewayMetricsFilter.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/GatewayMetricsFilter.java deleted file mode 100644 index 298547b7..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/GatewayMetricsFilter.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright 2013-2020 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.filter; - -import java.util.List; - -import io.micrometer.core.instrument.MeterRegistry; -import io.micrometer.core.instrument.Tags; -import io.micrometer.core.instrument.Timer; -import io.micrometer.core.instrument.Timer.Sample; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import reactor.core.publisher.Mono; - -import org.springframework.cloud.gateway.support.tagsprovider.GatewayTagsProvider; -import org.springframework.core.Ordered; -import org.springframework.http.server.reactive.ServerHttpResponse; -import org.springframework.web.server.ServerWebExchange; - -/** - * @author Tony Clarke - * @author Ingyu Hwang - */ -public class GatewayMetricsFilter implements GlobalFilter, Ordered { - - private static final Log log = LogFactory.getLog(GatewayMetricsFilter.class); - - private final MeterRegistry meterRegistry; - - private GatewayTagsProvider compositeTagsProvider; - - private final String metricsPrefix; - - public GatewayMetricsFilter(MeterRegistry meterRegistry, List tagsProviders, - String metricsPrefix) { - this.meterRegistry = meterRegistry; - this.compositeTagsProvider = tagsProviders.stream().reduce(exchange -> Tags.empty(), GatewayTagsProvider::and); - if (metricsPrefix.endsWith(".")) { - this.metricsPrefix = metricsPrefix.substring(0, metricsPrefix.length() - 1); - } - else { - this.metricsPrefix = metricsPrefix; - } - } - - public String getMetricsPrefix() { - return metricsPrefix; - } - - @Override - public int getOrder() { - // start the timer as soon as possible and report the metric event before we write - // response to client - return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER + 1; - } - - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - Sample sample = Timer.start(meterRegistry); - - return chain.filter(exchange).doOnSuccess(aVoid -> endTimerRespectingCommit(exchange, sample)) - .doOnError(throwable -> endTimerRespectingCommit(exchange, sample)); - } - - private void endTimerRespectingCommit(ServerWebExchange exchange, Sample sample) { - - ServerHttpResponse response = exchange.getResponse(); - if (response.isCommitted()) { - endTimerInner(exchange, sample); - } - else { - response.beforeCommit(() -> { - endTimerInner(exchange, sample); - return Mono.empty(); - }); - } - } - - private void endTimerInner(ServerWebExchange exchange, Sample sample) { - Tags tags = compositeTagsProvider.apply(exchange); - - if (log.isTraceEnabled()) { - log.trace(metricsPrefix + ".requests tags: " + tags); - } - sample.stop(meterRegistry.timer(metricsPrefix + ".requests", tags)); - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/GlobalFilter.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/GlobalFilter.java deleted file mode 100644 index c1db2c68..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/GlobalFilter.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2013-2020 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.filter; - -import reactor.core.publisher.Mono; - -import org.springframework.web.server.ServerWebExchange; - -/** - * Contract for interception-style, chained processing of gateway requests that may be - * used to implement cross-cutting, application-agnostic requirements such as security, - * timeouts, and others. - * - * Only applies to matched gateway routes. - * - * Copied from framework WebFilter - * - * @author Rossen Stoyanchev - * @since 5.0 - */ -public interface GlobalFilter { - - /** - * Process the Web request and (optionally) delegate to the next {@code GatewayFilter} - * through the given {@link GatewayFilterChain}. - * @param exchange the current server exchange - * @param chain provides a way to delegate to the next filter - * @return {@code Mono} to indicate when request processing is complete - */ - Mono filter(ServerWebExchange exchange, GatewayFilterChain chain); - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/LoadBalancerServiceInstanceCookieFilter.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/LoadBalancerServiceInstanceCookieFilter.java deleted file mode 100644 index 4874a563..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/LoadBalancerServiceInstanceCookieFilter.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2013-2021 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.filter; - -import java.util.ArrayList; -import java.util.List; - -import reactor.core.publisher.Mono; - -import org.springframework.cloud.client.ServiceInstance; -import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; -import org.springframework.cloud.client.loadbalancer.Response; -import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer; -import org.springframework.core.Ordered; -import org.springframework.http.HttpCookie; -import org.springframework.http.HttpHeaders; -import org.springframework.util.StringUtils; -import org.springframework.web.server.ServerWebExchange; - -import static org.springframework.cloud.gateway.filter.ReactiveLoadBalancerClientFilter.LOAD_BALANCER_CLIENT_FILTER_ORDER; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_LOADBALANCER_RESPONSE_ATTR; - -/** - * A {@link GlobalFilter} that allows passing the {@code} instanceId) of the - * {@link ServiceInstance} selected by the {@link ReactiveLoadBalancerClientFilter} in a - * cookie. - * - * @author Olga Maciaszek-Sharma - * @since 3.0.2 - */ -public class LoadBalancerServiceInstanceCookieFilter implements GlobalFilter, Ordered { - - private LoadBalancerProperties loadBalancerProperties; - - private ReactiveLoadBalancer.Factory loadBalancerClientFactory; - - LoadBalancerServiceInstanceCookieFilter(LoadBalancerProperties loadBalancerProperties) { - this.loadBalancerProperties = loadBalancerProperties; - } - - public LoadBalancerServiceInstanceCookieFilter( - ReactiveLoadBalancer.Factory loadBalancerClientFactory) { - this.loadBalancerClientFactory = loadBalancerClientFactory; - } - - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - Response serviceInstanceResponse = exchange.getAttribute(GATEWAY_LOADBALANCER_RESPONSE_ATTR); - if (serviceInstanceResponse == null || !serviceInstanceResponse.hasServer()) { - return chain.filter(exchange); - } - LoadBalancerProperties properties = loadBalancerClientFactory != null - ? loadBalancerClientFactory.getProperties(serviceInstanceResponse.getServer().getServiceId()) - : loadBalancerProperties; - if (!properties.getStickySession().isAddServiceInstanceCookie()) { - return chain.filter(exchange); - } - String instanceIdCookieName = properties.getStickySession().getInstanceIdCookieName(); - if (!StringUtils.hasText(instanceIdCookieName)) { - return chain.filter(exchange); - } - ServerWebExchange newExchange = exchange.mutate().request(exchange.getRequest().mutate().headers((headers) -> { - List cookieHeaders = new ArrayList<>(headers.getOrEmpty(HttpHeaders.COOKIE)); - String serviceInstanceCookie = new HttpCookie(instanceIdCookieName, - serviceInstanceResponse.getServer().getInstanceId()).toString(); - cookieHeaders.add(serviceInstanceCookie); - headers.put(HttpHeaders.COOKIE, cookieHeaders); - }).build()).build(); - return chain.filter(newExchange); - } - - @Override - public int getOrder() { - return LOAD_BALANCER_CLIENT_FILTER_ORDER + 1; - } - -} 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 deleted file mode 100644 index d9acd324..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/NettyRoutingFilter.java +++ /dev/null @@ -1,291 +0,0 @@ -/* - * Copyright 2013-2020 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.filter; - -import java.net.URI; -import java.time.Duration; -import java.util.List; - -import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; -import io.netty.channel.ChannelOption; -import io.netty.handler.codec.http.DefaultHttpHeaders; -import io.netty.handler.codec.http.HttpMethod; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import reactor.netty.http.client.HttpClient; -import reactor.netty.http.client.HttpClientResponse; - -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.cloud.gateway.config.HttpClientProperties; -import org.springframework.cloud.gateway.filter.headers.HttpHeadersFilter; -import org.springframework.cloud.gateway.filter.headers.HttpHeadersFilter.Type; -import org.springframework.cloud.gateway.route.Route; -import org.springframework.cloud.gateway.support.TimeoutException; -import org.springframework.core.Ordered; -import org.springframework.core.io.buffer.DataBuffer; -import org.springframework.core.io.buffer.DefaultDataBuffer; -import org.springframework.core.io.buffer.NettyDataBuffer; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.server.reactive.AbstractServerHttpResponse; -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.http.server.reactive.ServerHttpResponse; -import org.springframework.http.server.reactive.ServerHttpResponseDecorator; -import org.springframework.util.StringUtils; -import org.springframework.web.server.ResponseStatusException; -import org.springframework.web.server.ServerWebExchange; - -import static org.springframework.cloud.gateway.filter.headers.HttpHeadersFilter.filterRequest; -import static org.springframework.cloud.gateway.support.RouteMetadataUtils.CONNECT_TIMEOUT_ATTR; -import static org.springframework.cloud.gateway.support.RouteMetadataUtils.RESPONSE_TIMEOUT_ATTR; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.CLIENT_RESPONSE_ATTR; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.CLIENT_RESPONSE_CONN_ATTR; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.CLIENT_RESPONSE_HEADER_NAMES; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.PRESERVE_HOST_HEADER_ATTRIBUTE; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.isAlreadyRouted; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.setAlreadyRouted; - -/** - * @author Spencer Gibb - * @author Biju Kunjummen - */ -public class NettyRoutingFilter implements GlobalFilter, Ordered { - - /** - * The order of the NettyRoutingFilter. See {@link Ordered#LOWEST_PRECEDENCE}. - */ - public static final int ORDER = Ordered.LOWEST_PRECEDENCE; - - private static final Log log = LogFactory.getLog(NettyRoutingFilter.class); - - private final HttpClient httpClient; - - private final ObjectProvider> headersFiltersProvider; - - private final HttpClientProperties properties; - - // do not use this headersFilters directly, use getHeadersFilters() instead. - private volatile List headersFilters; - - public NettyRoutingFilter(HttpClient httpClient, ObjectProvider> headersFiltersProvider, - HttpClientProperties properties) { - this.httpClient = httpClient; - this.headersFiltersProvider = headersFiltersProvider; - this.properties = properties; - } - - public List getHeadersFilters() { - if (headersFilters == null) { - headersFilters = headersFiltersProvider.getIfAvailable(); - } - return headersFilters; - } - - @Override - public int getOrder() { - return ORDER; - } - - @Override - @SuppressWarnings("Duplicates") - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - URI requestUrl = exchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR); - - String scheme = requestUrl.getScheme(); - if (isAlreadyRouted(exchange) || (!"http".equalsIgnoreCase(scheme) && !"https".equalsIgnoreCase(scheme))) { - return chain.filter(exchange); - } - setAlreadyRouted(exchange); - - ServerHttpRequest request = exchange.getRequest(); - - final HttpMethod method = HttpMethod.valueOf(request.getMethod().name()); - final String url = requestUrl.toASCIIString(); - - HttpHeaders filtered = filterRequest(getHeadersFilters(), exchange); - - final DefaultHttpHeaders httpHeaders = new DefaultHttpHeaders(); - filtered.forEach(httpHeaders::set); - - boolean preserveHost = exchange.getAttributeOrDefault(PRESERVE_HOST_HEADER_ATTRIBUTE, false); - Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR); - - Flux responseFlux = getHttpClient(route, exchange).headers(headers -> { - headers.add(httpHeaders); - // Will either be set below, or later by Netty - headers.remove(HttpHeaders.HOST); - if (preserveHost) { - String host = request.getHeaders().getFirst(HttpHeaders.HOST); - headers.add(HttpHeaders.HOST, host); - } - }).request(method).uri(url).send((req, nettyOutbound) -> { - if (log.isTraceEnabled()) { - nettyOutbound.withConnection(connection -> log.trace("outbound route: " - + connection.channel().id().asShortText() + ", inbound: " + exchange.getLogPrefix())); - } - return nettyOutbound.send(request.getBody().map(this::getByteBuf)); - }).responseConnection((res, connection) -> { - - // Defer committing the response until all route filters have run - // Put client response as ServerWebExchange attribute and write - // response later NettyWriteResponseFilter - exchange.getAttributes().put(CLIENT_RESPONSE_ATTR, res); - exchange.getAttributes().put(CLIENT_RESPONSE_CONN_ATTR, connection); - - ServerHttpResponse response = exchange.getResponse(); - // put headers and status so filters can modify the response - HttpHeaders headers = new HttpHeaders(); - - res.responseHeaders().forEach(entry -> headers.add(entry.getKey(), entry.getValue())); - - String contentTypeValue = headers.getFirst(HttpHeaders.CONTENT_TYPE); - if (StringUtils.hasLength(contentTypeValue)) { - exchange.getAttributes().put(ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR, contentTypeValue); - } - - setResponseStatus(res, response); - - // make sure headers filters run after setting status so it is - // available in response - HttpHeaders filteredResponseHeaders = HttpHeadersFilter.filter(getHeadersFilters(), headers, exchange, - Type.RESPONSE); - - if (!filteredResponseHeaders.containsKey(HttpHeaders.TRANSFER_ENCODING) - && filteredResponseHeaders.containsKey(HttpHeaders.CONTENT_LENGTH)) { - // It is not valid to have both the transfer-encoding header and - // the content-length header. - // Remove the transfer-encoding header in the response if the - // content-length header is present. - response.getHeaders().remove(HttpHeaders.TRANSFER_ENCODING); - } - - exchange.getAttributes().put(CLIENT_RESPONSE_HEADER_NAMES, filteredResponseHeaders.keySet()); - - response.getHeaders().addAll(filteredResponseHeaders); - - return Mono.just(res); - }); - - Duration responseTimeout = getResponseTimeout(route); - if (responseTimeout != null) { - responseFlux = responseFlux - .timeout(responseTimeout, - Mono.error(new TimeoutException("Response took longer than timeout: " + responseTimeout))) - .onErrorMap(TimeoutException.class, - th -> new ResponseStatusException(HttpStatus.GATEWAY_TIMEOUT, th.getMessage(), th)); - } - - return responseFlux.then(chain.filter(exchange)); - } - - protected ByteBuf getByteBuf(DataBuffer dataBuffer) { - if (dataBuffer instanceof NettyDataBuffer) { - NettyDataBuffer buffer = (NettyDataBuffer) dataBuffer; - return buffer.getNativeBuffer(); - } - // MockServerHttpResponse creates these - else if (dataBuffer instanceof DefaultDataBuffer) { - DefaultDataBuffer buffer = (DefaultDataBuffer) dataBuffer; - return Unpooled.wrappedBuffer(buffer.getNativeBuffer()); - } - throw new IllegalArgumentException("Unable to handle DataBuffer of type " + dataBuffer.getClass()); - } - - private void setResponseStatus(HttpClientResponse clientResponse, ServerHttpResponse response) { - HttpStatus status = HttpStatus.resolve(clientResponse.status().code()); - if (status != null) { - response.setStatusCode(status); - } - else { - while (response instanceof ServerHttpResponseDecorator) { - response = ((ServerHttpResponseDecorator) response).getDelegate(); - } - if (response instanceof AbstractServerHttpResponse) { - ((AbstractServerHttpResponse) response).setRawStatusCode(clientResponse.status().code()); - } - else { - // TODO: log warning here, not throw error? - throw new IllegalStateException("Unable to set status code " + clientResponse.status().code() - + " on response of type " + response.getClass().getName()); - } - } - } - - /** - * Creates a new HttpClient with per route timeout configuration. Sub-classes that - * override, should call super.getHttpClient() if they want to honor the per route - * timeout configuration. - * @param route the current route. - * @param exchange the current ServerWebExchange. - * @return the configured HttpClient. - */ - protected HttpClient getHttpClient(Route route, ServerWebExchange exchange) { - Object connectTimeoutAttr = route.getMetadata().get(CONNECT_TIMEOUT_ATTR); - if (connectTimeoutAttr != null) { - Integer connectTimeout = getInteger(connectTimeoutAttr); - return this.httpClient.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectTimeout); - } - return httpClient; - } - - static Integer getInteger(Object connectTimeoutAttr) { - Integer connectTimeout; - if (connectTimeoutAttr instanceof Integer) { - connectTimeout = (Integer) connectTimeoutAttr; - } - else { - connectTimeout = Integer.parseInt(connectTimeoutAttr.toString()); - } - return connectTimeout; - } - - private Duration getResponseTimeout(Route route) { - try { - if (route.getMetadata().containsKey(RESPONSE_TIMEOUT_ATTR)) { - Long routeResponseTimeout = getLong(route.getMetadata().get(RESPONSE_TIMEOUT_ATTR)); - if (routeResponseTimeout != null && routeResponseTimeout >= 0) { - return Duration.ofMillis(routeResponseTimeout); - } - else { - return null; - } - } - } - catch (NumberFormatException e) { - // ignore number format and use global default - } - return properties.getResponseTimeout(); - } - - static Long getLong(Object responseTimeoutAttr) { - Long responseTimeout = null; - if (responseTimeoutAttr instanceof Number) { - responseTimeout = ((Number) responseTimeoutAttr).longValue(); - } - else if (responseTimeoutAttr != null) { - responseTimeout = Long.parseLong(responseTimeoutAttr.toString()); - } - return responseTimeout; - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/NettyWriteResponseFilter.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/NettyWriteResponseFilter.java deleted file mode 100644 index c06c175b..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/NettyWriteResponseFilter.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright 2013-2020 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.filter; - -import java.util.List; - -import io.netty.buffer.ByteBuf; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import reactor.netty.Connection; - -import org.springframework.core.Ordered; -import org.springframework.core.io.buffer.DataBuffer; -import org.springframework.core.io.buffer.DataBufferFactory; -import org.springframework.core.io.buffer.DefaultDataBufferFactory; -import org.springframework.core.io.buffer.NettyDataBufferFactory; -import org.springframework.http.MediaType; -import org.springframework.http.server.reactive.ServerHttpResponse; -import org.springframework.lang.Nullable; -import org.springframework.web.server.ServerWebExchange; - -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.CLIENT_RESPONSE_CONN_ATTR; - -/** - * @author Spencer Gibb - */ -public class NettyWriteResponseFilter implements GlobalFilter, Ordered { - - /** - * Order for write response filter. - */ - public static final int WRITE_RESPONSE_FILTER_ORDER = -1; - - private static final Log log = LogFactory.getLog(NettyWriteResponseFilter.class); - - private final List streamingMediaTypes; - - public NettyWriteResponseFilter(List streamingMediaTypes) { - this.streamingMediaTypes = streamingMediaTypes; - } - - @Override - public int getOrder() { - return WRITE_RESPONSE_FILTER_ORDER; - } - - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - // NOTICE: nothing in "pre" filter stage as CLIENT_RESPONSE_CONN_ATTR is not added - // until the NettyRoutingFilter is run - // @formatter:off - return chain.filter(exchange) - .then(Mono.defer(() -> { - Connection connection = exchange.getAttribute(CLIENT_RESPONSE_CONN_ATTR); - - if (connection == null) { - return Mono.empty(); - } - if (log.isTraceEnabled()) { - log.trace("NettyWriteResponseFilter start inbound: " - + connection.channel().id().asShortText() + ", outbound: " - + exchange.getLogPrefix()); - } - ServerHttpResponse response = exchange.getResponse(); - - // TODO: needed? - final Flux body = connection - .inbound() - .receive() - .retain() - .map(byteBuf -> wrap(byteBuf, response)); - - MediaType contentType = null; - try { - contentType = response.getHeaders().getContentType(); - } - catch (Exception e) { - if (log.isTraceEnabled()) { - log.trace("invalid media type", e); - } - } - return (isStreamingMediaType(contentType) - ? response.writeAndFlushWith(body.map(Flux::just)) - : response.writeWith(body)); - })).doOnCancel(() -> cleanup(exchange)) - .doOnError(throwable -> cleanup(exchange)); - // @formatter:on - } - - protected DataBuffer wrap(ByteBuf byteBuf, ServerHttpResponse response) { - DataBufferFactory bufferFactory = response.bufferFactory(); - if (bufferFactory instanceof NettyDataBufferFactory) { - NettyDataBufferFactory factory = (NettyDataBufferFactory) bufferFactory; - return factory.wrap(byteBuf); - } - // MockServerHttpResponse creates these - else if (bufferFactory instanceof DefaultDataBufferFactory) { - DataBuffer buffer = ((DefaultDataBufferFactory) bufferFactory).allocateBuffer(byteBuf.readableBytes()); - buffer.write(byteBuf.nioBuffer()); - byteBuf.release(); - return buffer; - } - throw new IllegalArgumentException("Unkown DataBufferFactory type " + bufferFactory.getClass()); - } - - private void cleanup(ServerWebExchange exchange) { - Connection connection = exchange.getAttribute(CLIENT_RESPONSE_CONN_ATTR); - if (connection != null && connection.channel().isActive()) { - connection.dispose(); - } - } - - // TODO: use framework if possible - private boolean isStreamingMediaType(@Nullable MediaType contentType) { - if (contentType != null) { - for (int i = 0; i < streamingMediaTypes.size(); i++) { - if (streamingMediaTypes.get(i).isCompatibleWith(contentType)) { - return true; - } - } - } - return false; - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/OrderedGatewayFilter.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/OrderedGatewayFilter.java deleted file mode 100644 index 0c797272..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/OrderedGatewayFilter.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2013-2020 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.filter; - -import reactor.core.publisher.Mono; - -import org.springframework.core.Ordered; -import org.springframework.web.server.ServerWebExchange; - -/** - * @author Spencer Gibb - */ -public class OrderedGatewayFilter implements GatewayFilter, Ordered { - - private final GatewayFilter delegate; - - private final int order; - - public OrderedGatewayFilter(GatewayFilter delegate, int order) { - this.delegate = delegate; - this.order = order; - } - - public GatewayFilter getDelegate() { - return delegate; - } - - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - return this.delegate.filter(exchange, chain); - } - - @Override - public int getOrder() { - return this.order; - } - - @Override - public String toString() { - return new StringBuilder("[").append(delegate).append(", order = ").append(order).append("]").toString(); - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/ReactiveLoadBalancerClientFilter.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/ReactiveLoadBalancerClientFilter.java deleted file mode 100644 index 3de22769..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/ReactiveLoadBalancerClientFilter.java +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright 2013-2020 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.filter; - -import java.net.URI; -import java.util.Map; -import java.util.Set; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import reactor.core.publisher.Mono; - -import org.springframework.cloud.client.ServiceInstance; -import org.springframework.cloud.client.loadbalancer.CompletionContext; -import org.springframework.cloud.client.loadbalancer.DefaultRequest; -import org.springframework.cloud.client.loadbalancer.LoadBalancerLifecycle; -import org.springframework.cloud.client.loadbalancer.LoadBalancerLifecycleValidator; -import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; -import org.springframework.cloud.client.loadbalancer.LoadBalancerUriTools; -import org.springframework.cloud.client.loadbalancer.Request; -import org.springframework.cloud.client.loadbalancer.RequestData; -import org.springframework.cloud.client.loadbalancer.RequestDataContext; -import org.springframework.cloud.client.loadbalancer.Response; -import org.springframework.cloud.client.loadbalancer.ResponseData; -import org.springframework.cloud.gateway.config.GatewayLoadBalancerProperties; -import org.springframework.cloud.gateway.support.DelegatingServiceInstance; -import org.springframework.cloud.gateway.support.NotFoundException; -import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer; -import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer; -import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory; -import org.springframework.core.Ordered; -import org.springframework.web.server.ServerWebExchange; - -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_LOADBALANCER_RESPONSE_ATTR; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.addOriginalRequestUrl; - -/** - * A {@link GlobalFilter} implementation that routes requests using reactive Spring Cloud - * LoadBalancer. - * - * @author Spencer Gibb - * @author Tim Ysewyn - * @author Olga Maciaszek-Sharma - */ -@SuppressWarnings({ "rawtypes", "unchecked" }) -public class ReactiveLoadBalancerClientFilter implements GlobalFilter, Ordered { - - private static final Log log = LogFactory.getLog(ReactiveLoadBalancerClientFilter.class); - - /** - * Order of filter. - */ - public static final int LOAD_BALANCER_CLIENT_FILTER_ORDER = 10150; - - private final LoadBalancerClientFactory clientFactory; - - private final GatewayLoadBalancerProperties properties; - - /** - * @deprecated in favour of - * {@link ReactiveLoadBalancerClientFilter#ReactiveLoadBalancerClientFilter(LoadBalancerClientFactory, GatewayLoadBalancerProperties)} - */ - @Deprecated - public ReactiveLoadBalancerClientFilter(LoadBalancerClientFactory clientFactory, - GatewayLoadBalancerProperties properties, LoadBalancerProperties loadBalancerProperties) { - this.clientFactory = clientFactory; - this.properties = properties; - } - - public ReactiveLoadBalancerClientFilter(LoadBalancerClientFactory clientFactory, - GatewayLoadBalancerProperties properties) { - this.clientFactory = clientFactory; - this.properties = properties; - } - - @Override - public int getOrder() { - return LOAD_BALANCER_CLIENT_FILTER_ORDER; - } - - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - URI url = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR); - String schemePrefix = exchange.getAttribute(GATEWAY_SCHEME_PREFIX_ATTR); - if (url == null || (!"lb".equals(url.getScheme()) && !"lb".equals(schemePrefix))) { - return chain.filter(exchange); - } - // preserve the original url - addOriginalRequestUrl(exchange, url); - - if (log.isTraceEnabled()) { - log.trace(ReactiveLoadBalancerClientFilter.class.getSimpleName() + " url before: " + url); - } - - URI requestUri = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR); - String serviceId = requestUri.getHost(); - Set supportedLifecycleProcessors = LoadBalancerLifecycleValidator - .getSupportedLifecycleProcessors(clientFactory.getInstances(serviceId, LoadBalancerLifecycle.class), - RequestDataContext.class, ResponseData.class, ServiceInstance.class); - DefaultRequest lbRequest = new DefaultRequest<>( - new RequestDataContext(new RequestData(exchange.getRequest()), getHint(serviceId))); - return choose(lbRequest, serviceId, supportedLifecycleProcessors).doOnNext(response -> { - - if (!response.hasServer()) { - supportedLifecycleProcessors.forEach(lifecycle -> lifecycle - .onComplete(new CompletionContext<>(CompletionContext.Status.DISCARD, lbRequest, response))); - throw NotFoundException.create(properties.isUse404(), "Unable to find instance for " + url.getHost()); - } - - ServiceInstance retrievedInstance = response.getServer(); - - URI uri = exchange.getRequest().getURI(); - - // if the `lb:` mechanism was used, use `` as the default, - // if the loadbalancer doesn't provide one. - String overrideScheme = retrievedInstance.isSecure() ? "https" : "http"; - if (schemePrefix != null) { - overrideScheme = url.getScheme(); - } - - DelegatingServiceInstance serviceInstance = new DelegatingServiceInstance(retrievedInstance, - overrideScheme); - - URI requestUrl = reconstructURI(serviceInstance, uri); - - if (log.isTraceEnabled()) { - log.trace("LoadBalancerClientFilter url chosen: " + requestUrl); - } - exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUrl); - exchange.getAttributes().put(GATEWAY_LOADBALANCER_RESPONSE_ATTR, response); - supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStartRequest(lbRequest, response)); - }).then(chain.filter(exchange)) - .doOnError(throwable -> supportedLifecycleProcessors.forEach(lifecycle -> lifecycle - .onComplete(new CompletionContext( - CompletionContext.Status.FAILED, throwable, lbRequest, - exchange.getAttribute(GATEWAY_LOADBALANCER_RESPONSE_ATTR))))) - .doOnSuccess(aVoid -> supportedLifecycleProcessors.forEach(lifecycle -> lifecycle - .onComplete(new CompletionContext( - CompletionContext.Status.SUCCESS, lbRequest, - exchange.getAttribute(GATEWAY_LOADBALANCER_RESPONSE_ATTR), - new ResponseData(exchange.getResponse(), new RequestData(exchange.getRequest())))))); - } - - protected URI reconstructURI(ServiceInstance serviceInstance, URI original) { - return LoadBalancerUriTools.reconstructURI(serviceInstance, original); - } - - private Mono> choose(Request lbRequest, String serviceId, - Set supportedLifecycleProcessors) { - ReactorLoadBalancer loadBalancer = this.clientFactory.getInstance(serviceId, - ReactorServiceInstanceLoadBalancer.class); - if (loadBalancer == null) { - throw new NotFoundException("No loadbalancer available for " + serviceId); - } - supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStart(lbRequest)); - return loadBalancer.choose(lbRequest); - } - - private String getHint(String serviceId) { - LoadBalancerProperties loadBalancerProperties = clientFactory.getProperties(serviceId); - Map hints = loadBalancerProperties.getHint(); - String defaultHint = hints.getOrDefault("default", "default"); - String hintPropertyValue = hints.get(serviceId); - return hintPropertyValue != null ? hintPropertyValue : defaultHint; - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/RemoveCachedBodyFilter.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/RemoveCachedBodyFilter.java deleted file mode 100644 index 488ff9bf..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/RemoveCachedBodyFilter.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2013-2020 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.filter; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import reactor.core.publisher.Mono; - -import org.springframework.core.Ordered; -import org.springframework.core.io.buffer.PooledDataBuffer; -import org.springframework.web.server.ServerWebExchange; - -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.CACHED_REQUEST_BODY_ATTR; - -public class RemoveCachedBodyFilter implements GlobalFilter, Ordered { - - private static final Log log = LogFactory.getLog(RemoveCachedBodyFilter.class); - - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - return chain.filter(exchange).doFinally(s -> { - Object attribute = exchange.getAttributes().remove(CACHED_REQUEST_BODY_ATTR); - if (attribute != null && attribute instanceof PooledDataBuffer) { - PooledDataBuffer dataBuffer = (PooledDataBuffer) attribute; - if (dataBuffer.isAllocated()) { - if (log.isTraceEnabled()) { - log.trace("releasing cached body in exchange attribute"); - } - dataBuffer.release(); - } - } - }); - } - - @Override - public int getOrder() { - return HIGHEST_PRECEDENCE; - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/RouteToRequestUrlFilter.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/RouteToRequestUrlFilter.java deleted file mode 100644 index 35c1a77c..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/RouteToRequestUrlFilter.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright 2013-2020 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.filter; - -import java.net.URI; -import java.util.regex.Pattern; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import reactor.core.publisher.Mono; - -import org.springframework.cloud.gateway.route.Route; -import org.springframework.core.Ordered; -import org.springframework.web.server.ServerWebExchange; -import org.springframework.web.util.UriComponentsBuilder; - -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.containsEncodedParts; - -/** - * @author Spencer Gibb - */ -public class RouteToRequestUrlFilter implements GlobalFilter, Ordered { - - /** - * Order of Route to URL. - */ - public static final int ROUTE_TO_URL_FILTER_ORDER = 10000; - - private static final Log log = LogFactory.getLog(RouteToRequestUrlFilter.class); - - private static final String SCHEME_REGEX = "[a-zA-Z]([a-zA-Z]|\\d|\\+|\\.|-)*:.*"; - static final Pattern schemePattern = Pattern.compile(SCHEME_REGEX); - - /* for testing */ - static boolean hasAnotherScheme(URI uri) { - return schemePattern.matcher(uri.getSchemeSpecificPart()).matches() && uri.getHost() == null - && uri.getRawPath() == null; - } - - @Override - public int getOrder() { - return ROUTE_TO_URL_FILTER_ORDER; - } - - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR); - if (route == null) { - return chain.filter(exchange); - } - log.trace("RouteToRequestUrlFilter start"); - URI uri = exchange.getRequest().getURI(); - boolean encoded = containsEncodedParts(uri); - URI routeUri = route.getUri(); - - if (hasAnotherScheme(routeUri)) { - // this is a special url, save scheme to special attribute - // replace routeUri with schemeSpecificPart - exchange.getAttributes().put(GATEWAY_SCHEME_PREFIX_ATTR, routeUri.getScheme()); - routeUri = URI.create(routeUri.getSchemeSpecificPart()); - } - - if ("lb".equalsIgnoreCase(routeUri.getScheme()) && routeUri.getHost() == null) { - // Load balanced URIs should always have a host. If the host is null it is - // most - // likely because the host name was invalid (for example included an - // underscore) - throw new IllegalStateException("Invalid host: " + routeUri.toString()); - } - - URI mergedUrl = UriComponentsBuilder.fromUri(uri) - // .uri(routeUri) - .scheme(routeUri.getScheme()).host(routeUri.getHost()).port(routeUri.getPort()).build(encoded).toUri(); - exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, mergedUrl); - return chain.filter(exchange); - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/WebClientHttpRoutingFilter.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/WebClientHttpRoutingFilter.java deleted file mode 100644 index 24e6cd33..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/WebClientHttpRoutingFilter.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright 2013-2020 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.filter; - -import java.net.URI; -import java.util.List; - -import reactor.core.publisher.Mono; - -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.cloud.gateway.filter.headers.HttpHeadersFilter; -import org.springframework.core.Ordered; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.http.server.reactive.ServerHttpResponse; -import org.springframework.web.reactive.function.BodyInserters; -import org.springframework.web.reactive.function.client.WebClient; -import org.springframework.web.reactive.function.client.WebClient.RequestBodySpec; -import org.springframework.web.reactive.function.client.WebClient.RequestHeadersSpec; -import org.springframework.web.server.ServerWebExchange; - -import static org.springframework.cloud.gateway.filter.headers.HttpHeadersFilter.filterRequest; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.CLIENT_RESPONSE_ATTR; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.PRESERVE_HOST_HEADER_ATTRIBUTE; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.isAlreadyRouted; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.setAlreadyRouted; - -/** - * @author Spencer Gibb - */ -public class WebClientHttpRoutingFilter implements GlobalFilter, Ordered { - - private final WebClient webClient; - - private final ObjectProvider> headersFiltersProvider; - - // do not use this headersFilters directly, use getHeadersFilters() instead. - private volatile List headersFilters; - - public WebClientHttpRoutingFilter(WebClient webClient, - ObjectProvider> headersFiltersProvider) { - this.webClient = webClient; - this.headersFiltersProvider = headersFiltersProvider; - } - - public List getHeadersFilters() { - if (headersFilters == null) { - headersFilters = headersFiltersProvider.getIfAvailable(); - } - return headersFilters; - } - - @Override - public int getOrder() { - return Ordered.LOWEST_PRECEDENCE; - } - - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - URI requestUrl = exchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR); - - String scheme = requestUrl.getScheme(); - if (isAlreadyRouted(exchange) || (!"http".equals(scheme) && !"https".equals(scheme))) { - return chain.filter(exchange); - } - setAlreadyRouted(exchange); - - ServerHttpRequest request = exchange.getRequest(); - - HttpMethod method = request.getMethod(); - - HttpHeaders filteredHeaders = filterRequest(getHeadersFilters(), exchange); - - boolean preserveHost = exchange.getAttributeOrDefault(PRESERVE_HOST_HEADER_ATTRIBUTE, false); - - RequestBodySpec bodySpec = this.webClient.method(method).uri(requestUrl).headers(httpHeaders -> { - httpHeaders.addAll(filteredHeaders); - // TODO: can this support preserviceHostHeader? - if (!preserveHost) { - httpHeaders.remove(HttpHeaders.HOST); - } - }); - - RequestHeadersSpec headersSpec; - if (requiresBody(method)) { - headersSpec = bodySpec.body(BodyInserters.fromDataBuffers(request.getBody())); - } - else { - headersSpec = bodySpec; - } - - return headersSpec.exchangeToMono(Mono::just) - // .log("webClient route") - .flatMap(res -> { - ServerHttpResponse response = exchange.getResponse(); - response.getHeaders().putAll(res.headers().asHttpHeaders()); - response.setStatusCode(res.statusCode()); - // Defer committing the response until all route filters have run - // Put client response as ServerWebExchange attribute and write - // response later NettyWriteResponseFilter - exchange.getAttributes().put(CLIENT_RESPONSE_ATTR, res); - return chain.filter(exchange); - }); - } - - private boolean requiresBody(HttpMethod method) { - return method.equals(HttpMethod.PUT) || method.equals(HttpMethod.POST) || method.equals(HttpMethod.PATCH); - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/WebClientWriteResponseFilter.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/WebClientWriteResponseFilter.java deleted file mode 100644 index 28b44db8..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/WebClientWriteResponseFilter.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2013-2020 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.filter; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import reactor.core.publisher.Mono; - -import org.springframework.core.Ordered; -import org.springframework.http.server.reactive.ServerHttpResponse; -import org.springframework.web.reactive.function.BodyExtractors; -import org.springframework.web.reactive.function.client.ClientResponse; -import org.springframework.web.server.ServerWebExchange; - -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.CLIENT_RESPONSE_ATTR; - -/** - * @author Spencer Gibb - */ -public class WebClientWriteResponseFilter implements GlobalFilter, Ordered { - - /** - * Order of Write Response Filter. - */ - public static final int WRITE_RESPONSE_FILTER_ORDER = -1; - - private static final Log log = LogFactory.getLog(WebClientWriteResponseFilter.class); - - @Override - public int getOrder() { - return WRITE_RESPONSE_FILTER_ORDER; - } - - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - // NOTICE: nothing in "pre" filter stage as CLIENT_RESPONSE_ATTR is not added - // until the WebHandler is run - return chain.filter(exchange).doOnError(throwable -> cleanup(exchange)).then(Mono.defer(() -> { - ClientResponse clientResponse = exchange.getAttribute(CLIENT_RESPONSE_ATTR); - if (clientResponse == null) { - return Mono.empty(); - } - log.trace("WebClientWriteResponseFilter start"); - ServerHttpResponse response = exchange.getResponse(); - - return response.writeWith(clientResponse.body(BodyExtractors.toDataBuffers())) - // .log("webClient response") - .doOnCancel(() -> cleanup(exchange)); - })); - } - - private void cleanup(ServerWebExchange exchange) { - ClientResponse clientResponse = exchange.getAttribute(CLIENT_RESPONSE_ATTR); - if (clientResponse != null) { - clientResponse.bodyToMono(Void.class).subscribe(); - } - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/WebsocketRoutingFilter.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/WebsocketRoutingFilter.java deleted file mode 100644 index 6a4fe3b8..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/WebsocketRoutingFilter.java +++ /dev/null @@ -1,289 +0,0 @@ -/* - * Copyright 2013-2020 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.filter; - -import java.net.URI; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import reactor.core.publisher.Mono; - -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.cloud.gateway.filter.headers.HttpHeadersFilter; -import org.springframework.core.Ordered; -import org.springframework.http.HttpHeaders; -import org.springframework.util.StringUtils; -import org.springframework.web.reactive.socket.CloseStatus; -import org.springframework.web.reactive.socket.WebSocketHandler; -import org.springframework.web.reactive.socket.WebSocketMessage; -import org.springframework.web.reactive.socket.WebSocketSession; -import org.springframework.web.reactive.socket.client.WebSocketClient; -import org.springframework.web.reactive.socket.server.WebSocketService; -import org.springframework.web.server.ServerWebExchange; -import org.springframework.web.util.UriComponentsBuilder; - -import static org.springframework.cloud.gateway.filter.headers.HttpHeadersFilter.filterRequest; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.PRESERVE_HOST_HEADER_ATTRIBUTE; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.containsEncodedParts; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.isAlreadyRouted; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.setAlreadyRouted; - -/** - * @author Spencer Gibb - * @author Nikita Konev - */ -public class WebsocketRoutingFilter implements GlobalFilter, Ordered { - - /** - * Sec-Websocket protocol. - */ - public static final String SEC_WEBSOCKET_PROTOCOL = "Sec-WebSocket-Protocol"; - - private static final Log log = LogFactory.getLog(WebsocketRoutingFilter.class); - - private final WebSocketClient webSocketClient; - - private final WebSocketService webSocketService; - - private final ObjectProvider> headersFiltersProvider; - - // do not use this headersFilters directly, use getHeadersFilters() instead. - private volatile List headersFilters; - - public WebsocketRoutingFilter(WebSocketClient webSocketClient, WebSocketService webSocketService, - ObjectProvider> headersFiltersProvider) { - this.webSocketClient = webSocketClient; - this.webSocketService = webSocketService; - this.headersFiltersProvider = headersFiltersProvider; - } - - /* for testing */ - static String convertHttpToWs(String scheme) { - scheme = scheme.toLowerCase(); - return "http".equals(scheme) ? "ws" : "https".equals(scheme) ? "wss" : scheme; - } - - @Override - public int getOrder() { - // Before NettyRoutingFilter since this routes certain http requests - return Ordered.LOWEST_PRECEDENCE - 1; - } - - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - changeSchemeIfIsWebSocketUpgrade(exchange); - - URI requestUrl = exchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR); - String scheme = requestUrl.getScheme(); - - if (isAlreadyRouted(exchange) || (!"ws".equals(scheme) && !"wss".equals(scheme))) { - return chain.filter(exchange); - } - setAlreadyRouted(exchange); - - HttpHeaders headers = exchange.getRequest().getHeaders(); - HttpHeaders filtered = filterRequest(getHeadersFilters(), exchange); - - List protocols = getProtocols(headers); - - return this.webSocketService.handleRequest(exchange, - new ProxyWebSocketHandler(requestUrl, this.webSocketClient, filtered, protocols)); - } - - /* for testing */ List getProtocols(HttpHeaders headers) { - List protocols = headers.get(SEC_WEBSOCKET_PROTOCOL); - if (protocols != null) { - ArrayList updatedProtocols = new ArrayList<>(); - for (int i = 0; i < protocols.size(); i++) { - String protocol = protocols.get(i); - updatedProtocols.addAll(Arrays.asList(StringUtils.tokenizeToStringArray(protocol, ","))); - } - protocols = updatedProtocols; - } - return protocols; - } - - /* for testing */ List getHeadersFilters() { - if (this.headersFilters == null) { - this.headersFilters = this.headersFiltersProvider.getIfAvailable(ArrayList::new); - - // remove host header unless specifically asked not to - headersFilters.add((headers, exchange) -> { - HttpHeaders filtered = new HttpHeaders(); - filtered.addAll(headers); - filtered.remove(HttpHeaders.HOST); - boolean preserveHost = exchange.getAttributeOrDefault(PRESERVE_HOST_HEADER_ATTRIBUTE, false); - if (preserveHost) { - String host = exchange.getRequest().getHeaders().getFirst(HttpHeaders.HOST); - filtered.add(HttpHeaders.HOST, host); - } - return filtered; - }); - - headersFilters.add((headers, exchange) -> { - HttpHeaders filtered = new HttpHeaders(); - for (Map.Entry> entry : headers.entrySet()) { - if (!entry.getKey().toLowerCase().startsWith("sec-websocket")) { - filtered.addAll(entry.getKey(), entry.getValue()); - } - } - return filtered; - }); - } - - return this.headersFilters; - } - - static void changeSchemeIfIsWebSocketUpgrade(ServerWebExchange exchange) { - // Check the Upgrade - URI requestUrl = exchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR); - String scheme = requestUrl.getScheme().toLowerCase(); - String upgrade = exchange.getRequest().getHeaders().getUpgrade(); - // change the scheme if the socket client send a "http" or "https" - if ("WebSocket".equalsIgnoreCase(upgrade) && ("http".equals(scheme) || "https".equals(scheme))) { - String wsScheme = convertHttpToWs(scheme); - boolean encoded = containsEncodedParts(requestUrl); - URI wsRequestUrl = UriComponentsBuilder.fromUri(requestUrl).scheme(wsScheme).build(encoded).toUri(); - exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, wsRequestUrl); - if (log.isTraceEnabled()) { - log.trace("changeSchemeTo:[" + wsRequestUrl + "]"); - } - } - } - - private static class ProxyWebSocketHandler implements WebSocketHandler { - - private final WebSocketClient client; - - private final URI url; - - private final HttpHeaders headers; - - private final List subProtocols; - - ProxyWebSocketHandler(URI url, WebSocketClient client, HttpHeaders headers, List protocols) { - this.client = client; - this.url = url; - this.headers = headers; - if (protocols != null) { - this.subProtocols = protocols; - } - else { - this.subProtocols = Collections.emptyList(); - } - } - - @Override - public List getSubProtocols() { - return this.subProtocols; - } - - @Override - public Mono handle(WebSocketSession session) { - // pass headers along so custom headers can be sent through - return client.execute(url, this.headers, new WebSocketHandler() { - - private CloseStatus adaptCloseStatus(CloseStatus closeStatus) { - int code = closeStatus.getCode(); - if (code > 2999 && code < 5000) { - return closeStatus; - } - switch (code) { - case 1000: - case 1001: - case 1002: - case 1003: - case 1007: - case 1008: - case 1009: - case 1010: - case 1011: - return closeStatus; - case 1004: - // Should not be used in a close frame - // RESERVED; - case 1005: - // Should not be used in a close frame - // return CloseStatus.NO_STATUS_CODE; - case 1006: - // Should not be used in a close frame - // return CloseStatus.NO_CLOSE_FRAME; - case 1012: - // Not in RFC6455 - // return CloseStatus.SERVICE_RESTARTED; - case 1013: - // Not in RFC6455 - // return CloseStatus.SERVICE_OVERLOAD; - case 1015: - // Should not be used in a close frame - // return CloseStatus.TLS_HANDSHAKE_FAILURE; - default: - return CloseStatus.PROTOCOL_ERROR; - } - } - - @Override - public Mono handle(WebSocketSession proxySession) { - Mono serverClose = proxySession.closeStatus().filter(__ -> session.isOpen()) - .map(this::adaptCloseStatus).flatMap(session::close); - Mono proxyClose = session.closeStatus().filter(__ -> proxySession.isOpen()) - .map(this::adaptCloseStatus).flatMap(proxySession::close); - // Use retain() for Reactor Netty - Mono proxySessionSend = proxySession - .send(session.receive().doOnNext(WebSocketMessage::retain).doOnNext(webSocketMessage -> { - if (log.isTraceEnabled()) { - log.trace("proxySession(send from client): " + proxySession.getId() - + ", corresponding session:" + session.getId() + ", packet: " - + webSocketMessage.getPayloadAsText()); - } - })); - // .log("proxySessionSend", Level.FINE); - Mono serverSessionSend = session.send( - proxySession.receive().doOnNext(WebSocketMessage::retain).doOnNext(webSocketMessage -> { - if (log.isTraceEnabled()) { - log.trace("session(send from backend): " + session.getId() - + ", corresponding proxySession:" + proxySession.getId() + " packet: " - + webSocketMessage.getPayloadAsText()); - } - })); - // .log("sessionSend", Level.FINE); - // Ensure closeStatus from one propagates to the other - Mono.when(serverClose, proxyClose).subscribe(); - // Complete when both sessions are done - return Mono.zip(proxySessionSend, serverSessionSend).then(); - } - - /** - * Copy subProtocols so they are available downstream. - * @return available subProtocols. - */ - @Override - public List getSubProtocols() { - return ProxyWebSocketHandler.this.subProtocols; - } - }); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/WeightCalculatorWebFilter.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/WeightCalculatorWebFilter.java deleted file mode 100644 index 977a64fb..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/WeightCalculatorWebFilter.java +++ /dev/null @@ -1,306 +0,0 @@ -/* - * Copyright 2013-2020 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.filter; - -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Random; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ThreadLocalRandom; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Supplier; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import reactor.core.publisher.Mono; - -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.cloud.gateway.event.PredicateArgsEvent; -import org.springframework.cloud.gateway.event.RefreshRoutesEvent; -import org.springframework.cloud.gateway.event.WeightDefinedEvent; -import org.springframework.cloud.gateway.route.RouteLocator; -import org.springframework.cloud.gateway.support.ConfigurationService; -import org.springframework.cloud.gateway.support.WeightConfig; -import org.springframework.context.ApplicationEvent; -import org.springframework.context.event.SmartApplicationListener; -import org.springframework.core.Ordered; -import org.springframework.core.style.ToStringCreator; -import org.springframework.web.server.ServerWebExchange; -import org.springframework.web.server.WebFilter; -import org.springframework.web.server.WebFilterChain; - -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.WEIGHT_ATTR; - -/** - * @author Spencer Gibb - * @author Alexey Nakidkin - */ -public class WeightCalculatorWebFilter implements WebFilter, Ordered, SmartApplicationListener { - - /** - * Order of Weight Calculator Web filter. - */ - public static final int WEIGHT_CALC_FILTER_ORDER = 10001; - - private static final Log log = LogFactory.getLog(WeightCalculatorWebFilter.class); - - private final ObjectProvider routeLocator; - - private final ConfigurationService configurationService; - - private Supplier randomSupplier = null; - - private int order = WEIGHT_CALC_FILTER_ORDER; - - private Map groupWeights = new ConcurrentHashMap<>(); - - private final AtomicBoolean routeLocatorInitialized = new AtomicBoolean(); - - public WeightCalculatorWebFilter(ObjectProvider routeLocator, - ConfigurationService configurationService) { - this.routeLocator = routeLocator; - this.configurationService = configurationService; - } - - /* for testing */ - static Map getWeights(ServerWebExchange exchange) { - Map weights = exchange.getAttribute(WEIGHT_ATTR); - - if (weights == null) { - weights = new ConcurrentHashMap<>(); - exchange.getAttributes().put(WEIGHT_ATTR, weights); - } - return weights; - } - - @Override - public int getOrder() { - return order; - } - - public void setOrder(int order) { - this.order = order; - } - - @Deprecated - public void setRandom(Random random) { - this.randomSupplier = random::nextDouble; - } - - public void setRandomSupplier(Supplier randomSupplier) { - this.randomSupplier = randomSupplier; - } - - @Override - public boolean supportsEventType(Class eventType) { - // from config file - return PredicateArgsEvent.class.isAssignableFrom(eventType) || - // from java dsl - WeightDefinedEvent.class.isAssignableFrom(eventType) || - // force initialization - RefreshRoutesEvent.class.isAssignableFrom(eventType); - } - - @Override - public boolean supportsSourceType(Class sourceType) { - return true; - } - - @Override - public void onApplicationEvent(ApplicationEvent event) { - if (event instanceof PredicateArgsEvent) { - handle((PredicateArgsEvent) event); - } - else if (event instanceof WeightDefinedEvent) { - addWeightConfig(((WeightDefinedEvent) event).getWeightConfig()); - } - else if (event instanceof RefreshRoutesEvent && routeLocator != null) { - // forces initialization - if (routeLocatorInitialized.compareAndSet(false, true)) { - // on first time, block so that app fails to start if there are errors in - // routes - // see gh-1574 - routeLocator.ifAvailable(locator -> locator.getRoutes().blockLast()); - } - else { - // this preserves previous behaviour on refresh, this could likely go away - routeLocator.ifAvailable(locator -> locator.getRoutes().subscribe()); - } - } - - } - - public void handle(PredicateArgsEvent event) { - Map args = event.getArgs(); - - if (args.isEmpty() || !hasRelevantKey(args)) { - return; - } - - WeightConfig config = new WeightConfig(event.getRouteId()); - - this.configurationService.with(config).name(WeightConfig.CONFIG_PREFIX).normalizedProperties(args).bind(); - - addWeightConfig(config); - } - - private boolean hasRelevantKey(Map args) { - return args.keySet().stream().anyMatch(key -> key.startsWith(WeightConfig.CONFIG_PREFIX + ".")); - } - - /* for testing */ void addWeightConfig(WeightConfig weightConfig) { - String group = weightConfig.getGroup(); - GroupWeightConfig config; - // only create new GroupWeightConfig rather than modify - // and put at end of calculations. This avoids concurency problems - // later during filter execution. - if (groupWeights.containsKey(group)) { - config = new GroupWeightConfig(groupWeights.get(group)); - } - else { - config = new GroupWeightConfig(group); - } - - config.weights.put(weightConfig.getRouteId(), weightConfig.getWeight()); - - // recalculate - - // normalize weights - int weightsSum = 0; - - for (Integer weight : config.weights.values()) { - weightsSum += weight; - } - - final AtomicInteger index = new AtomicInteger(0); - for (Map.Entry entry : config.weights.entrySet()) { - String routeId = entry.getKey(); - Integer weight = entry.getValue(); - Double nomalizedWeight = weight / (double) weightsSum; - config.normalizedWeights.put(routeId, nomalizedWeight); - - // recalculate rangeIndexes - config.rangeIndexes.put(index.getAndIncrement(), routeId); - } - - // TODO: calculate ranges - config.ranges.clear(); - - config.ranges.add(0.0); - - List values = new ArrayList<>(config.normalizedWeights.values()); - for (int i = 0; i < values.size(); i++) { - Double currentWeight = values.get(i); - Double previousRange = config.ranges.get(i); - Double range = previousRange + currentWeight; - config.ranges.add(range); - } - - if (log.isTraceEnabled()) { - log.trace("Recalculated group weight config " + config); - } - // only update after all calculations - groupWeights.put(group, config); - } - - /* for testing */ Map getGroupWeights() { - return groupWeights; - } - - @Override - public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { - Map weights = getWeights(exchange); - - for (String group : groupWeights.keySet()) { - GroupWeightConfig config = groupWeights.get(group); - - if (config == null) { - if (log.isDebugEnabled()) { - log.debug("No GroupWeightConfig found for group: " + group); - } - continue; // nothing we can do, but this is odd - } - - /* - * Usually, multiple threads accessing the same random object will have some - * performance problems, so we can use ThreadLocalRandom by default - */ - double r; - if (this.randomSupplier != null) { - r = randomSupplier.get(); - } - else { - r = ThreadLocalRandom.current().nextDouble(); - } - - List ranges = config.ranges; - - if (log.isTraceEnabled()) { - log.trace("Weight for group: " + group + ", ranges: " + ranges + ", r: " + r); - } - - for (int i = 0; i < ranges.size() - 1; i++) { - if (r >= ranges.get(i) && r < ranges.get(i + 1)) { - String routeId = config.rangeIndexes.get(i); - weights.put(group, routeId); - break; - } - } - } - - if (log.isTraceEnabled()) { - log.trace("Weights attr: " + weights); - } - - return chain.filter(exchange); - } - - /* for testing */ static class GroupWeightConfig { - - String group; - - LinkedHashMap weights = new LinkedHashMap<>(); - - LinkedHashMap normalizedWeights = new LinkedHashMap<>(); - - LinkedHashMap rangeIndexes = new LinkedHashMap<>(); - - List ranges = new ArrayList<>(); - - GroupWeightConfig(String group) { - this.group = group; - } - - GroupWeightConfig(GroupWeightConfig other) { - this.group = other.group; - this.weights = new LinkedHashMap<>(other.weights); - this.normalizedWeights = new LinkedHashMap<>(other.normalizedWeights); - this.rangeIndexes = new LinkedHashMap<>(other.rangeIndexes); - } - - @Override - public String toString() { - return new ToStringCreator(this).append("group", group).append("weights", weights) - .append("normalizedWeights", normalizedWeights).append("rangeIndexes", rangeIndexes).toString(); - } - - } - -} 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 deleted file mode 100644 index 54fa2e60..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/cors/CorsGatewayFilterApplicationListener.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.cors; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -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.handler.RoutePredicateHandlerMapping; -import org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory; -import org.springframework.cloud.gateway.route.Route; -import org.springframework.cloud.gateway.route.RouteLocator; -import org.springframework.context.ApplicationListener; -import org.springframework.web.cors.CorsConfiguration; - -/** - * This class updates Cors configuration each time a {@link RefreshRoutesEvent} 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 { - - private final GlobalCorsProperties globalCorsProperties; - - private final RoutePredicateHandlerMapping routePredicateHandlerMapping; - - private final RouteLocator routeLocator; - - private static final String METADATA_KEY = "cors"; - - private static final String ALL_PATHS = "/**"; - - public CorsGatewayFilterApplicationListener(GlobalCorsProperties globalCorsProperties, - RoutePredicateHandlerMapping routePredicateHandlerMapping, RouteLocator routeLocator) { - this.globalCorsProperties = globalCorsProperties; - this.routePredicateHandlerMapping = routePredicateHandlerMapping; - this.routeLocator = routeLocator; - } - - @Override - public void onApplicationEvent(RefreshRoutesEvent event) { - routeLocator.getRoutes().collectList().subscribe(routes -> { - // pre-populate with pre-existing global cors configurations to combine with. - var corsConfigurations = new HashMap<>(globalCorsProperties.getCorsConfigurations()); - - routes.forEach(route -> { - var corsConfiguration = getCorsConfiguration(route); - corsConfiguration.ifPresent(configuration -> { - var pathPredicate = getPathPredicate(route); - corsConfigurations.put(pathPredicate, configuration); - }); - }); - - routePredicateHandlerMapping.setCorsConfigurations(corsConfigurations); - }); - } - - /** - * Finds the first path predicate and first pattern in the config. - * @param route The Route to use. - * @return the first path predicate pattern or /**. - */ - private String getPathPredicate(Route route) { - var predicate = route.getPredicate(); - var pathPatterns = new AtomicReference(); - predicate.accept(p -> { - if (p.getConfig() instanceof PathRoutePredicateFactory.Config pathConfig) { - if (!pathConfig.getPatterns().isEmpty()) { - pathPatterns.compareAndSet(null, pathConfig.getPatterns().get(0)); - } - } - }); - if (pathPatterns.get() != null) { - return pathPatterns.get(); - } - return ALL_PATHS; - } - - @SuppressWarnings("unchecked") - private Optional getCorsConfiguration(Route route) { - Map corsMetadata = (Map) route.getMetadata().get(METADATA_KEY); - if (corsMetadata != null) { - final CorsConfiguration corsConfiguration = new CorsConfiguration(); - - findValue(corsMetadata, "allowCredentials") - .ifPresent(value -> corsConfiguration.setAllowCredentials((Boolean) value)); - findValue(corsMetadata, "allowedHeaders") - .ifPresent(value -> corsConfiguration.setAllowedHeaders(asList(value))); - findValue(corsMetadata, "allowedMethods") - .ifPresent(value -> corsConfiguration.setAllowedMethods(asList(value))); - findValue(corsMetadata, "allowedOriginPatterns") - .ifPresent(value -> corsConfiguration.setAllowedOriginPatterns(asList(value))); - findValue(corsMetadata, "allowedOrigins") - .ifPresent(value -> corsConfiguration.setAllowedOrigins(asList(value))); - findValue(corsMetadata, "exposedHeaders") - .ifPresent(value -> corsConfiguration.setExposedHeaders(asList(value))); - findValue(corsMetadata, "maxAge").ifPresent(value -> corsConfiguration.setMaxAge(asLong(value))); - - return Optional.of(corsConfiguration); - } - - return Optional.empty(); - } - - private Optional findValue(Map metadata, String key) { - Object value = metadata.get(key); - return Optional.ofNullable(value); - } - - private List asList(Object value) { - if (value instanceof String) { - return Arrays.asList((String) value); - } - if (value instanceof Map) { - return new ArrayList<>(((Map) value).values()); - } - else { - return (List) value; - } - } - - private Long asLong(Object value) { - if (value instanceof Integer) { - return ((Integer) value).longValue(); - } - else { - return (Long) value; - } - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/AbstractChangeRequestUriGatewayFilterFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/AbstractChangeRequestUriGatewayFilterFactory.java deleted file mode 100644 index a383f1cd..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/AbstractChangeRequestUriGatewayFilterFactory.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import java.net.URI; -import java.util.Map; -import java.util.Optional; - -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.OrderedGatewayFilter; -import org.springframework.cloud.gateway.filter.RouteToRequestUrlFilter; -import org.springframework.web.server.ServerWebExchange; - -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR; - -/** - * This filter changes the request uri by - * {@link #determineRequestUri(ServerWebExchange, T)} logic. - * - * @author Toshiaki Maki - */ -public abstract class AbstractChangeRequestUriGatewayFilterFactory extends AbstractGatewayFilterFactory { - - private final int order; - - public AbstractChangeRequestUriGatewayFilterFactory(Class clazz, int order) { - super(clazz); - this.order = order; - } - - public AbstractChangeRequestUriGatewayFilterFactory(Class clazz) { - this(clazz, RouteToRequestUrlFilter.ROUTE_TO_URL_FILTER_ORDER + 1); - } - - protected abstract Optional determineRequestUri(ServerWebExchange exchange, T config); - - @Override - public GatewayFilter apply(T config) { - return new OrderedGatewayFilter((exchange, chain) -> { - Optional uri = this.determineRequestUri(exchange, config); - uri.ifPresent(u -> { - Map attributes = exchange.getAttributes(); - attributes.put(GATEWAY_REQUEST_URL_ATTR, u); - }); - return chain.filter(exchange); - }, this.order); - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/AbstractGatewayFilterFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/AbstractGatewayFilterFactory.java deleted file mode 100644 index e94a2ede..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/AbstractGatewayFilterFactory.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import org.springframework.cloud.gateway.support.AbstractConfigurable; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.context.ApplicationEventPublisherAware; - -/** - * This class is BETA and may be subject to change in a future release. - * - * @param {@link AbstractConfigurable} subtype - */ -public abstract class AbstractGatewayFilterFactory extends AbstractConfigurable - implements GatewayFilterFactory, ApplicationEventPublisherAware { - - private ApplicationEventPublisher publisher; - - @SuppressWarnings("unchecked") - public AbstractGatewayFilterFactory() { - super((Class) Object.class); - } - - public AbstractGatewayFilterFactory(Class configClass) { - super(configClass); - } - - protected ApplicationEventPublisher getPublisher() { - return this.publisher; - } - - @Override - public void setApplicationEventPublisher(ApplicationEventPublisher publisher) { - this.publisher = publisher; - } - - public static class NameConfig { - - private String name; - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/AbstractNameValueGatewayFilterFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/AbstractNameValueGatewayFilterFactory.java deleted file mode 100644 index 9ffdd0ac..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/AbstractNameValueGatewayFilterFactory.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import java.util.Arrays; -import java.util.List; - -import jakarta.validation.constraints.NotEmpty; - -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.core.style.ToStringCreator; -import org.springframework.validation.annotation.Validated; - -public abstract class AbstractNameValueGatewayFilterFactory - extends AbstractGatewayFilterFactory { - - public AbstractNameValueGatewayFilterFactory() { - super(NameValueConfig.class); - } - - @Override - public List shortcutFieldOrder() { - return Arrays.asList(GatewayFilter.NAME_KEY, GatewayFilter.VALUE_KEY); - } - - @Validated - public static class NameValueConfig { - - @NotEmpty - protected String name; - - @NotEmpty - protected String value; - - public String getName() { - return name; - } - - public NameValueConfig setName(String name) { - this.name = name; - return this; - } - - public String getValue() { - return value; - } - - public NameValueConfig setValue(String value) { - this.value = value; - return this; - } - - @Override - public String toString() { - return new ToStringCreator(this).append("name", name).append("value", value).toString(); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/AddRequestHeaderGatewayFilterFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/AddRequestHeaderGatewayFilterFactory.java deleted file mode 100644 index 1e2a32e4..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/AddRequestHeaderGatewayFilterFactory.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import reactor.core.publisher.Mono; - -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.GatewayFilterChain; -import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.web.server.ServerWebExchange; - -import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator; - -/** - * @author Spencer Gibb - */ -public class AddRequestHeaderGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory { - - @Override - public GatewayFilter apply(NameValueConfig config) { - return new GatewayFilter() { - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - String value = ServerWebExchangeUtils.expand(exchange, config.getValue()); - ServerHttpRequest request = exchange.getRequest().mutate() - .headers(httpHeaders -> httpHeaders.add(config.getName(), value)).build(); - - return chain.filter(exchange.mutate().request(request).build()); - } - - @Override - public String toString() { - return filterToStringCreator(AddRequestHeaderGatewayFilterFactory.this) - .append(config.getName(), config.getValue()).toString(); - } - }; - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/AddRequestHeadersIfNotPresentGatewayFilterFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/AddRequestHeadersIfNotPresentGatewayFilterFactory.java deleted file mode 100644 index f912b928..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/AddRequestHeadersIfNotPresentGatewayFilterFactory.java +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Copyright 2013-2022 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.filter.factory; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -import reactor.core.publisher.Mono; - -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.GatewayFilterChain; -import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; -import org.springframework.core.style.ToStringCreator; -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.util.StringUtils; -import org.springframework.web.server.ServerWebExchange; - -import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator; - -/** - * Adds one or more headers to the downstream request’s headers without overriding - * previous values. If the header is are already present, value(s) will not be set. - * - * @author Abel Salgado Romero - */ -public class AddRequestHeadersIfNotPresentGatewayFilterFactory - extends AbstractGatewayFilterFactory { - - @Override - public GatewayFilter apply(KeyValueConfig config) { - return new GatewayFilter() { - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - ServerHttpRequest.Builder requestBuilder = null; - - Map> aggregatedHeaders = new HashMap<>(); - - for (KeyValue keyValue : config.getKeyValues()) { - String key = keyValue.getKey(); - List candidateValue = aggregatedHeaders.get(key); - if (candidateValue == null) { - candidateValue = new ArrayList<>(); - candidateValue.add(keyValue.getValue()); - } - else { - candidateValue.add(keyValue.getValue()); - } - aggregatedHeaders.put(key, candidateValue); - } - - for (Map.Entry> kv : aggregatedHeaders.entrySet()) { - String headerName = kv.getKey(); - - boolean headerIsMissingOrBlank = exchange.getRequest().getHeaders().getOrEmpty(headerName).stream() - .allMatch(h -> !StringUtils.hasText(h)); - - if (headerIsMissingOrBlank) { - if (requestBuilder == null) { - requestBuilder = exchange.getRequest().mutate(); - } - ServerWebExchange finalExchange = exchange; - requestBuilder.headers(httpHeaders -> { - List replacedValues = kv.getValue().stream() - .map(value -> ServerWebExchangeUtils.expand(finalExchange, value)) - .collect(Collectors.toList()); - httpHeaders.addAll(headerName, replacedValues); - }); - } - } - if (requestBuilder != null) { - exchange = exchange.mutate().request(requestBuilder.build()).build(); - } - return chain.filter(exchange); - } - - @Override - public String toString() { - ToStringCreator toStringCreator = filterToStringCreator( - AddRequestHeadersIfNotPresentGatewayFilterFactory.this); - for (KeyValue keyValue : config.getKeyValues()) { - toStringCreator.append(keyValue.getKey(), keyValue.getValue()); - } - return toStringCreator.toString(); - } - }; - } - - public ShortcutType shortcutType() { - return ShortcutType.GATHER_LIST; - } - - @Override - public List shortcutFieldOrder() { - return Collections.singletonList("keyValues"); - } - - @Override - public KeyValueConfig newConfig() { - return new KeyValueConfig(); - } - - @Override - public Class getConfigClass() { - return KeyValueConfig.class; - } - - public static class KeyValueConfig { - - private KeyValue[] keyValues; - - public KeyValue[] getKeyValues() { - return keyValues; - } - - public void setKeyValues(KeyValue[] keyValues) { - this.keyValues = keyValues; - } - - } - - public static class KeyValue { - - private final String key; - - private final String value; - - public KeyValue(String key, String value) { - this.key = key; - this.value = value; - } - - public String getKey() { - return key; - } - - public String getValue() { - return value; - } - - @Override - public String toString() { - return new ToStringCreator(this).append("name", key).append("value", value).toString(); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/AddRequestParameterGatewayFilterFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/AddRequestParameterGatewayFilterFactory.java deleted file mode 100644 index d99c46a7..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/AddRequestParameterGatewayFilterFactory.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import java.net.URI; - -import reactor.core.publisher.Mono; - -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.GatewayFilterChain; -import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.util.StringUtils; -import org.springframework.web.server.ServerWebExchange; -import org.springframework.web.util.UriComponentsBuilder; - -import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator; - -/** - * @author Spencer Gibb - */ -public class AddRequestParameterGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory { - - @Override - public GatewayFilter apply(NameValueConfig config) { - return new GatewayFilter() { - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - URI uri = exchange.getRequest().getURI(); - StringBuilder query = new StringBuilder(); - String originalQuery = uri.getRawQuery(); - - if (StringUtils.hasText(originalQuery)) { - query.append(originalQuery); - if (originalQuery.charAt(originalQuery.length() - 1) != '&') { - query.append('&'); - } - } - - String value = ServerWebExchangeUtils.expand(exchange, config.getValue()); - // TODO urlencode? - query.append(config.getName()); - query.append('='); - query.append(value); - - try { - URI newUri = UriComponentsBuilder.fromUri(uri).replaceQuery(query.toString()).build(true).toUri(); - - ServerHttpRequest request = exchange.getRequest().mutate().uri(newUri).build(); - - return chain.filter(exchange.mutate().request(request).build()); - } - catch (RuntimeException ex) { - throw new IllegalStateException("Invalid URI query: \"" + query.toString() + "\""); - } - } - - @Override - public String toString() { - return filterToStringCreator(AddRequestParameterGatewayFilterFactory.this) - .append(config.getName(), config.getValue()).toString(); - } - }; - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/AddResponseHeaderGatewayFilterFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/AddResponseHeaderGatewayFilterFactory.java deleted file mode 100644 index 699a6f7f..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/AddResponseHeaderGatewayFilterFactory.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import reactor.core.publisher.Mono; - -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.GatewayFilterChain; -import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; -import org.springframework.http.HttpHeaders; -import org.springframework.web.server.ServerWebExchange; - -import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator; - -/** - * @author Spencer Gibb - */ -public class AddResponseHeaderGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory { - - @Override - public GatewayFilter apply(NameValueConfig config) { - return new GatewayFilter() { - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - return chain.filter(exchange).then(Mono.fromRunnable(() -> addHeader(exchange, config))); - } - - @Override - public String toString() { - return filterToStringCreator(AddResponseHeaderGatewayFilterFactory.this) - .append(config.getName(), config.getValue()).toString(); - } - }; - } - - void addHeader(ServerWebExchange exchange, NameValueConfig config) { - final String value = ServerWebExchangeUtils.expand(exchange, config.getValue()); - HttpHeaders headers = exchange.getResponse().getHeaders(); - // if response has been commited, no more response headers will bee added. - if (!exchange.getResponse().isCommitted()) { - headers.add(config.getName(), value); - } - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/CacheRequestBodyGatewayFilterFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/CacheRequestBodyGatewayFilterFactory.java deleted file mode 100644 index 625f6ff9..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/CacheRequestBodyGatewayFilterFactory.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import java.net.URI; -import java.util.List; - -import reactor.core.publisher.Mono; - -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.GatewayFilterChain; -import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; -import org.springframework.core.io.buffer.DataBuffer; -import org.springframework.core.io.buffer.DataBufferUtils; -import org.springframework.http.codec.HttpMessageReader; -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.util.Assert; -import org.springframework.web.reactive.function.server.HandlerStrategies; -import org.springframework.web.reactive.function.server.ServerRequest; -import org.springframework.web.server.ServerWebExchange; - -import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.CACHED_SERVER_HTTP_REQUEST_DECORATOR_ATTR; - -/** - * @author weizibin - */ -public class CacheRequestBodyGatewayFilterFactory - extends AbstractGatewayFilterFactory { - - static final String CACHED_ORIGINAL_REQUEST_BODY_BACKUP_ATTR = "cachedOriginalRequestBodyBackup"; - - private final List> messageReaders; - - public CacheRequestBodyGatewayFilterFactory() { - super(CacheRequestBodyGatewayFilterFactory.Config.class); - this.messageReaders = HandlerStrategies.withDefaults().messageReaders(); - } - - @Override - public GatewayFilter apply(CacheRequestBodyGatewayFilterFactory.Config config) { - return new GatewayFilter() { - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - ServerHttpRequest request = exchange.getRequest(); - URI requestUri = request.getURI(); - String scheme = requestUri.getScheme(); - - // Record only http requests (including https) - if ((!"http".equals(scheme) && !"https".equals(scheme))) { - return chain.filter(exchange); - } - - Object cachedBody = exchange.getAttribute(ServerWebExchangeUtils.CACHED_REQUEST_BODY_ATTR); - if (cachedBody != null) { - return chain.filter(exchange); - } - - return ServerWebExchangeUtils.cacheRequestBodyAndRequest(exchange, (serverHttpRequest) -> { - final ServerRequest serverRequest = ServerRequest - .create(exchange.mutate().request(serverHttpRequest).build(), messageReaders); - return serverRequest.bodyToMono((config.getBodyClass())).doOnNext(objectValue -> { - Object previousCachedBody = exchange.getAttributes() - .put(ServerWebExchangeUtils.CACHED_REQUEST_BODY_ATTR, objectValue); - if (previousCachedBody != null) { - // store previous cached body - exchange.getAttributes().put(CACHED_ORIGINAL_REQUEST_BODY_BACKUP_ATTR, previousCachedBody); - } - }).then(Mono.defer(() -> { - ServerHttpRequest cachedRequest = exchange - .getAttribute(CACHED_SERVER_HTTP_REQUEST_DECORATOR_ATTR); - Assert.notNull(cachedRequest, "cache request shouldn't be null"); - exchange.getAttributes().remove(CACHED_SERVER_HTTP_REQUEST_DECORATOR_ATTR); - return chain.filter(exchange.mutate().request(cachedRequest).build()).doFinally(s -> { - // - Object backupCachedBody = exchange.getAttributes() - .get(CACHED_ORIGINAL_REQUEST_BODY_BACKUP_ATTR); - if (backupCachedBody instanceof DataBuffer dataBuffer) { - DataBufferUtils.release(dataBuffer); - } - }); - })); - }); - } - - @Override - public String toString() { - return filterToStringCreator(CacheRequestBodyGatewayFilterFactory.this) - .append("Body class", config.getBodyClass()).toString(); - } - }; - } - - public static class Config { - - private Class bodyClass; - - public Class getBodyClass() { - return bodyClass; - } - - public void setBodyClass(Class bodyClass) { - this.bodyClass = bodyClass; - } - - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/DedupeResponseHeaderGatewayFilterFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/DedupeResponseHeaderGatewayFilterFactory.java deleted file mode 100644 index dd159d31..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/DedupeResponseHeaderGatewayFilterFactory.java +++ /dev/null @@ -1,169 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.LinkedHashSet; -import java.util.List; - -import reactor.core.publisher.Mono; - -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.GatewayFilterChain; -import org.springframework.http.HttpHeaders; -import org.springframework.web.server.ServerWebExchange; - -import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator; - -/* -Use case: Both your legacy backend and your API gateway add CORS header values. So, your consumer ends up with - Access-Control-Allow-Credentials: true, true - Access-Control-Allow-Origin: https://musk.mars, https://musk.mars -(The one from the gateway will be the first of the two.) To fix, add - DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin - -Configuration parameters: -- name - String representing response header names, space separated. Required. -- strategy - RETAIN_FIRST - Default. Retain the first value only. - RETAIN_LAST - Retain the last value only. - RETAIN_UNIQUE - Retain all unique values in the order of their first encounter. - -Example 1 - default-filters: - - DedupeResponseHeader=Access-Control-Allow-Credentials - -Response header Access-Control-Allow-Credentials: true, false -Modified response header Access-Control-Allow-Credentials: true - -Example 2 - default-filters: - - DedupeResponseHeader=Access-Control-Allow-Credentials, RETAIN_LAST - -Response header Access-Control-Allow-Credentials: true, false -Modified response header Access-Control-Allow-Credentials: false - -Example 3 - default-filters: - - DedupeResponseHeader=Access-Control-Allow-Credentials, RETAIN_UNIQUE - -Response header Access-Control-Allow-Credentials: true, true -Modified response header Access-Control-Allow-Credentials: true - */ - -/** - * @author Vitaliy Pavlyuk - */ -public class DedupeResponseHeaderGatewayFilterFactory - extends AbstractGatewayFilterFactory { - - private static final String STRATEGY_KEY = "strategy"; - - public DedupeResponseHeaderGatewayFilterFactory() { - super(Config.class); - } - - @Override - public List shortcutFieldOrder() { - return Arrays.asList(NAME_KEY, STRATEGY_KEY); - } - - @Override - public GatewayFilter apply(Config config) { - return new GatewayFilter() { - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - return chain.filter(exchange) - .then(Mono.fromRunnable(() -> dedupe(exchange.getResponse().getHeaders(), config))); - } - - @Override - public String toString() { - return filterToStringCreator(DedupeResponseHeaderGatewayFilterFactory.this) - .append(config.getName(), config.getStrategy()).toString(); - } - }; - } - - public enum Strategy { - - /** - * Default: Retain the first value only. - */ - RETAIN_FIRST, - - /** - * Retain the last value only. - */ - RETAIN_LAST, - - /** - * Retain all unique values in the order of their first encounter. - */ - RETAIN_UNIQUE - - } - - void dedupe(HttpHeaders headers, Config config) { - String names = config.getName(); - Strategy strategy = config.getStrategy(); - if (headers == null || names == null || strategy == null) { - return; - } - for (String name : names.split(" ")) { - dedupe(headers, name.trim(), strategy); - } - } - - private void dedupe(HttpHeaders headers, String name, Strategy strategy) { - List values = headers.get(name); - if (values == null || values.size() <= 1) { - return; - } - switch (strategy) { - case RETAIN_FIRST: - headers.set(name, values.get(0)); - break; - case RETAIN_LAST: - headers.set(name, values.get(values.size() - 1)); - break; - case RETAIN_UNIQUE: - headers.put(name, new ArrayList<>(new LinkedHashSet<>(values))); - break; - default: - break; - } - } - - public static class Config extends AbstractGatewayFilterFactory.NameConfig { - - private Strategy strategy = Strategy.RETAIN_FIRST; - - public Strategy getStrategy() { - return strategy; - } - - public Config setStrategy(Strategy strategy) { - this.strategy = strategy; - return this; - } - - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/FallbackHeadersGatewayFilterFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/FallbackHeadersGatewayFilterFactory.java deleted file mode 100644 index dccc448e..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/FallbackHeadersGatewayFilterFactory.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import java.util.ArrayList; -import java.util.List; - -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.web.server.ServerWebExchange; - -import static java.util.Collections.singletonList; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.CIRCUITBREAKER_EXECUTION_EXCEPTION_ATTR; - -/** - * @author Olga Maciaszek-Sharma - * @author Ryan Baxter - */ -public class FallbackHeadersGatewayFilterFactory - extends AbstractGatewayFilterFactory { - - public FallbackHeadersGatewayFilterFactory() { - super(Config.class); - } - - @Override - public List shortcutFieldOrder() { - return singletonList(NAME_KEY); - } - - @Override - public GatewayFilter apply(Config config) { - return (exchange, chain) -> { - Throwable exception = exchange.getAttribute(CIRCUITBREAKER_EXECUTION_EXCEPTION_ATTR); - ServerWebExchange filteredExchange; - if (exception == null) { - filteredExchange = exchange; - } - else { - filteredExchange = addFallbackHeaders(config, exchange, exception); - } - return chain.filter(filteredExchange); - }; - } - - private ServerWebExchange addFallbackHeaders(Config config, ServerWebExchange exchange, - Throwable executionException) { - ServerHttpRequest.Builder requestBuilder = exchange.getRequest().mutate(); - requestBuilder.header(config.executionExceptionTypeHeaderName, executionException.getClass().getName()); - requestBuilder.header(config.executionExceptionMessageHeaderName, executionException.getMessage()); - Throwable rootCause = getRootCause(executionException); - if (rootCause != null) { - requestBuilder.header(config.rootCauseExceptionTypeHeaderName, rootCause.getClass().getName()); - requestBuilder.header(config.rootCauseExceptionMessageHeaderName, rootCause.getMessage()); - } - return exchange.mutate().request(requestBuilder.build()).build(); - } - - private static Throwable getRootCause(final Throwable throwable) { - final List list = getThrowableList(throwable); - return list.isEmpty() ? null : list.get(list.size() - 1); - } - - private static List getThrowableList(Throwable throwable) { - final List list = new ArrayList<>(); - while (throwable != null && !list.contains(throwable)) { - list.add(throwable); - throwable = throwable.getCause(); - } - return list; - } - - public static class Config { - - private static final String EXECUTION_EXCEPTION_TYPE = "Execution-Exception-Type"; - - private static final String EXECUTION_EXCEPTION_MESSAGE = "Execution-Exception-Message"; - - private static final String ROOT_CAUSE_EXCEPTION_TYPE = "Root-Cause-Exception-Type"; - - private static final String ROOT_CAUSE_EXCEPTION_MESSAGE = "Root-Cause-Exception-Message"; - - private String executionExceptionTypeHeaderName = EXECUTION_EXCEPTION_TYPE; - - private String executionExceptionMessageHeaderName = EXECUTION_EXCEPTION_MESSAGE; - - private String rootCauseExceptionTypeHeaderName = ROOT_CAUSE_EXCEPTION_TYPE; - - private String rootCauseExceptionMessageHeaderName = ROOT_CAUSE_EXCEPTION_MESSAGE; - - public String getExecutionExceptionTypeHeaderName() { - return executionExceptionTypeHeaderName; - } - - public void setExecutionExceptionTypeHeaderName(String executionExceptionTypeHeaderName) { - this.executionExceptionTypeHeaderName = executionExceptionTypeHeaderName; - } - - public String getExecutionExceptionMessageHeaderName() { - return executionExceptionMessageHeaderName; - } - - public void setExecutionExceptionMessageHeaderName(String executionExceptionMessageHeaderName) { - this.executionExceptionMessageHeaderName = executionExceptionMessageHeaderName; - } - - public String getRootCauseExceptionTypeHeaderName() { - return rootCauseExceptionTypeHeaderName; - } - - public void setRootCauseExceptionTypeHeaderName(String rootCauseExceptionTypeHeaderName) { - this.rootCauseExceptionTypeHeaderName = rootCauseExceptionTypeHeaderName; - } - - public String getRootCauseExceptionMessageHeaderName() { - return rootCauseExceptionMessageHeaderName; - } - - public void setRootCauseExceptionMessageHeaderName(String rootCauseExceptionMessageHeaderName) { - this.rootCauseExceptionMessageHeaderName = rootCauseExceptionMessageHeaderName; - } - - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/GatewayFilterFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/GatewayFilterFactory.java deleted file mode 100644 index 1b2dc0cf..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/GatewayFilterFactory.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import java.util.function.Consumer; - -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.support.Configurable; -import org.springframework.cloud.gateway.support.HasRouteId; -import org.springframework.cloud.gateway.support.NameUtils; -import org.springframework.cloud.gateway.support.ShortcutConfigurable; - -/** - * @author Spencer Gibb - */ -@FunctionalInterface -public interface GatewayFilterFactory extends ShortcutConfigurable, Configurable { - - /** - * Name key. - */ - String NAME_KEY = "name"; - - /** - * Value key. - */ - String VALUE_KEY = "value"; - - // useful for javadsl - default GatewayFilter apply(String routeId, Consumer consumer) { - C config = newConfig(); - consumer.accept(config); - return apply(routeId, config); - } - - default GatewayFilter apply(Consumer consumer) { - C config = newConfig(); - consumer.accept(config); - return apply(config); - } - - default Class getConfigClass() { - throw new UnsupportedOperationException("getConfigClass() not implemented"); - } - - @Override - default C newConfig() { - throw new UnsupportedOperationException("newConfig() not implemented"); - } - - GatewayFilter apply(C config); - - default GatewayFilter apply(String routeId, C config) { - if (config instanceof HasRouteId) { - HasRouteId hasRouteId = (HasRouteId) config; - hasRouteId.setRouteId(routeId); - } - return apply(config); - } - - default String name() { - // TODO: deal with proxys - return NameUtils.normalizeFilterFactoryName(getClass()); - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/JsonToGrpcGatewayFilterFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/JsonToGrpcGatewayFilterFactory.java deleted file mode 100644 index 2b0286e0..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/JsonToGrpcGatewayFilterFactory.java +++ /dev/null @@ -1,320 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import java.io.IOException; -import java.io.InputStream; -import java.net.URI; -import java.util.Arrays; -import java.util.List; -import java.util.NoSuchElementException; -import java.util.Objects; -import java.util.function.Function; - -import javax.net.ssl.SSLException; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.ObjectReader; -import com.fasterxml.jackson.databind.ObjectWriter; -import com.fasterxml.jackson.databind.SerializationFeature; -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.fasterxml.jackson.dataformat.protobuf.ProtobufFactory; -import com.fasterxml.jackson.dataformat.protobuf.schema.ProtobufSchema; -import com.fasterxml.jackson.dataformat.protobuf.schema.ProtobufSchemaLoader; -import com.google.protobuf.DescriptorProtos; -import com.google.protobuf.Descriptors; -import com.google.protobuf.DynamicMessage; -import io.grpc.CallOptions; -import io.grpc.Channel; -import io.grpc.ClientCall; -import io.grpc.ManagedChannel; -import io.grpc.MethodDescriptor; -import io.grpc.netty.NettyChannelBuilder; -import io.grpc.protobuf.ProtoUtils; -import io.grpc.stub.ClientCalls; -import io.netty.buffer.PooledByteBufAllocator; -import org.reactivestreams.Publisher; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -import org.springframework.cloud.gateway.config.GrpcSslConfigurer; -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.GatewayFilterChain; -import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter; -import org.springframework.cloud.gateway.filter.OrderedGatewayFilter; -import org.springframework.cloud.gateway.route.Route; -import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; -import org.springframework.core.ResolvableType; -import org.springframework.core.io.Resource; -import org.springframework.core.io.ResourceLoader; -import org.springframework.core.io.buffer.DataBuffer; -import org.springframework.core.io.buffer.NettyDataBufferFactory; -import org.springframework.http.codec.json.Jackson2JsonDecoder; -import org.springframework.http.server.reactive.ServerHttpResponseDecorator; -import org.springframework.web.server.ServerWebExchange; - -import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator; - -/** - * This filter takes a JSON payload, transform it into a protobuf object, send it to a - * given gRPC channel, and transform the response back to JSON. - * - * Making it transparent for the consumer that the service under the gateway is a gRPC - * one. - * - * @author Alberto C. Ríos - */ -public class JsonToGrpcGatewayFilterFactory - extends AbstractGatewayFilterFactory { - - private final GrpcSslConfigurer grpcSslConfigurer; - - private final ResourceLoader resourceLoader; - - public JsonToGrpcGatewayFilterFactory(GrpcSslConfigurer grpcSslConfigurer, ResourceLoader resourceLoader) { - super(Config.class); - this.grpcSslConfigurer = grpcSslConfigurer; - this.resourceLoader = resourceLoader; - } - - @Override - public List shortcutFieldOrder() { - return Arrays.asList("protoDescriptor", "protoFile", "service", "method"); - } - - @Override - public GatewayFilter apply(Config config) { - GatewayFilter filter = new GatewayFilter() { - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - GRPCResponseDecorator modifiedResponse = new GRPCResponseDecorator(exchange, config); - - ServerWebExchangeUtils.setAlreadyRouted(exchange); - return modifiedResponse.writeWith(exchange.getRequest().getBody()) - .then(chain.filter(exchange.mutate().response(modifiedResponse).build())); - } - - @Override - public String toString() { - return filterToStringCreator(JsonToGrpcGatewayFilterFactory.this).toString(); - } - }; - - int order = NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 1; - return new OrderedGatewayFilter(filter, order); - } - - public static class Config { - - private String protoDescriptor; - - private String protoFile; - - private String service; - - private String method; - - public String getProtoDescriptor() { - return protoDescriptor; - } - - public Config setProtoDescriptor(String protoDescriptor) { - this.protoDescriptor = protoDescriptor; - return this; - } - - public String getProtoFile() { - return protoFile; - } - - public Config setProtoFile(String protoFile) { - this.protoFile = protoFile; - return this; - } - - public String getService() { - return service; - } - - public Config setService(String service) { - this.service = service; - return this; - } - - public String getMethod() { - return method; - } - - public Config setMethod(String method) { - this.method = method; - return this; - } - - } - - class GRPCResponseDecorator extends ServerHttpResponseDecorator { - - private final ServerWebExchange exchange; - - private final Descriptors.Descriptor descriptor; - - private final ObjectWriter objectWriter; - - private final ObjectReader objectReader; - - private final ClientCall clientCall; - - private final ObjectNode objectNode; - - GRPCResponseDecorator(ServerWebExchange exchange, Config config) { - super(exchange.getResponse()); - this.exchange = exchange; - try { - Resource descriptorFile = resourceLoader.getResource(config.getProtoDescriptor()); - Resource protoFile = resourceLoader.getResource(config.getProtoFile()); - - descriptor = DescriptorProtos.FileDescriptorProto.parseFrom(descriptorFile.getInputStream()) - .getDescriptorForType(); - - Descriptors.MethodDescriptor methodDescriptor = getMethodDescriptor(config, - descriptorFile.getInputStream()); - Descriptors.ServiceDescriptor serviceDescriptor = methodDescriptor.getService(); - Descriptors.Descriptor outputType = methodDescriptor.getOutputType(); - - clientCall = createClientCallForType(config, serviceDescriptor, outputType); - - ProtobufSchema schema = ProtobufSchemaLoader.std.load(protoFile.getInputStream()); - ProtobufSchema responseType = schema.withRootType(outputType.getName()); - - ObjectMapper objectMapper = new ObjectMapper(new ProtobufFactory()); - objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); - objectWriter = objectMapper.writer(schema); - objectReader = objectMapper.readerFor(JsonNode.class).with(responseType); - objectNode = objectMapper.createObjectNode(); - - } - catch (IOException | Descriptors.DescriptorValidationException e) { - throw new RuntimeException(e); - } - } - - @Override - public Mono writeWith(Publisher body) { - exchange.getResponse().getHeaders().set("Content-Type", "application/json"); - - return getDelegate().writeWith(deserializeJSONRequest().map(callGRPCServer()).map(serialiseGRPCResponse()) - .map(wrapGRPCResponse()).cast(DataBuffer.class).last()); - } - - private ClientCall createClientCallForType(Config config, - Descriptors.ServiceDescriptor serviceDescriptor, Descriptors.Descriptor outputType) { - MethodDescriptor.Marshaller marshaller = ProtoUtils - .marshaller(DynamicMessage.newBuilder(outputType).build()); - MethodDescriptor methodDescriptor = MethodDescriptor - .newBuilder().setType(MethodDescriptor.MethodType.UNKNOWN) - .setFullMethodName(MethodDescriptor.generateFullMethodName(serviceDescriptor.getFullName(), - config.getMethod())) - .setRequestMarshaller(marshaller).setResponseMarshaller(marshaller).build(); - Channel channel = createChannel(); - return channel.newCall(methodDescriptor, CallOptions.DEFAULT); - } - - private Descriptors.MethodDescriptor getMethodDescriptor(Config config, InputStream descriptorFile) - throws IOException, Descriptors.DescriptorValidationException { - DescriptorProtos.FileDescriptorSet fileDescriptorSet = DescriptorProtos.FileDescriptorSet - .parseFrom(descriptorFile); - DescriptorProtos.FileDescriptorProto fileProto = fileDescriptorSet.getFile(0); - Descriptors.FileDescriptor fileDescriptor = Descriptors.FileDescriptor.buildFrom(fileProto, - new Descriptors.FileDescriptor[0]); - - Descriptors.ServiceDescriptor serviceDescriptor = fileDescriptor.findServiceByName(config.getService()); - if (serviceDescriptor == null) { - throw new NoSuchElementException("No Service found"); - } - - List methods = serviceDescriptor.getMethods(); - - return methods.stream().filter(method -> method.getName().equals(config.getMethod())).findFirst() - .orElseThrow(() -> new NoSuchElementException("No Method found")); - } - - private ManagedChannel createChannel() { - URI requestURI = ((Route) exchange.getAttributes().get(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR)).getUri(); - return createChannelChannel(requestURI.getHost(), requestURI.getPort()); - } - - private Function callGRPCServer() { - return jsonRequest -> { - try { - byte[] request = objectWriter.writeValueAsBytes(jsonRequest); - return ClientCalls.blockingUnaryCall(clientCall, DynamicMessage.parseFrom(descriptor, request)); - } - catch (IOException e) { - throw new RuntimeException(e); - } - }; - } - - private Function serialiseGRPCResponse() { - return gRPCResponse -> { - try { - return objectReader.readValue(gRPCResponse.toByteArray()); - } - catch (IOException e) { - throw new RuntimeException(e); - } - }; - } - - private Flux deserializeJSONRequest() { - return exchange.getRequest().getBody().mapNotNull(dataBufferBody -> { - if (dataBufferBody.capacity() == 0) { - return objectNode; - } - ResolvableType targetType = ResolvableType.forType(JsonNode.class); - return new Jackson2JsonDecoder().decode(dataBufferBody, targetType, null, null); - }).cast(JsonNode.class); - } - - private Function wrapGRPCResponse() { - return jsonResponse -> { - try { - return new NettyDataBufferFactory(new PooledByteBufAllocator()) - .wrap(Objects.requireNonNull(new ObjectMapper().writeValueAsBytes(jsonResponse))); - } - catch (JsonProcessingException e) { - return new NettyDataBufferFactory(new PooledByteBufAllocator()).allocateBuffer(); - } - }; - } - - // We are creating this on every call, should optimize? - private ManagedChannel createChannelChannel(String host, int port) { - NettyChannelBuilder nettyChannelBuilder = NettyChannelBuilder.forAddress(host, port); - try { - return grpcSslConfigurer.configureSsl(nettyChannelBuilder); - } - catch (SSLException e) { - throw new RuntimeException(e); - } - } - - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/MapRequestHeaderGatewayFilterFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/MapRequestHeaderGatewayFilterFactory.java deleted file mode 100644 index cedc14cf..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/MapRequestHeaderGatewayFilterFactory.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import java.util.Arrays; -import java.util.List; - -import reactor.core.publisher.Mono; - -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.GatewayFilterChain; -import org.springframework.core.style.ToStringCreator; -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.web.server.ServerWebExchange; - -import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator; - -/** - * @author Tony Clarke - */ -public class MapRequestHeaderGatewayFilterFactory - extends AbstractGatewayFilterFactory { - - /** - * From Header key. - */ - public static final String FROM_HEADER_KEY = "fromHeader"; - - /** - * To Header key. - */ - public static final String TO_HEADER_KEY = "toHeader"; - - public MapRequestHeaderGatewayFilterFactory() { - super(Config.class); - } - - @Override - public List shortcutFieldOrder() { - return Arrays.asList(FROM_HEADER_KEY, TO_HEADER_KEY); - } - - @Override - public GatewayFilter apply(MapRequestHeaderGatewayFilterFactory.Config config) { - return new GatewayFilter() { - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - if (!exchange.getRequest().getHeaders().containsKey(config.getFromHeader())) { - return chain.filter(exchange); - } - List headerValues = exchange.getRequest().getHeaders().get(config.getFromHeader()); - - ServerHttpRequest request = exchange.getRequest().mutate() - .headers(i -> i.addAll(config.getToHeader(), headerValues)).build(); - - return chain.filter(exchange.mutate().request(request).build()); - } - - @Override - public String toString() { - // @formatter:off - return filterToStringCreator(MapRequestHeaderGatewayFilterFactory.this) - .append(FROM_HEADER_KEY, config.getFromHeader()) - .append(TO_HEADER_KEY, config.getToHeader()) - .toString(); - // @formatter:on - } - }; - } - - public static class Config { - - private String fromHeader; - - private String toHeader; - - public String getFromHeader() { - return this.fromHeader; - } - - public Config setFromHeader(String fromHeader) { - this.fromHeader = fromHeader; - return this; - } - - public String getToHeader() { - return this.toHeader; - } - - public Config setToHeader(String toHeader) { - this.toHeader = toHeader; - return this; - } - - @Override - public String toString() { - // @formatter:off - return new ToStringCreator(this) - .append("fromHeader", fromHeader) - .append("toHeader", toHeader) - .toString(); - // @formatter:on - } - - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/PrefixPathGatewayFilterFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/PrefixPathGatewayFilterFactory.java deleted file mode 100644 index a71829c0..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/PrefixPathGatewayFilterFactory.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright 2013-2022 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.filter.factory; - -import java.net.URI; -import java.util.Arrays; -import java.util.List; -import java.util.Map; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import reactor.core.publisher.Mono; - -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.GatewayFilterChain; -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.web.server.ServerWebExchange; -import org.springframework.web.util.UriTemplate; - -import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ALREADY_PREFIXED_ATTR; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.addOriginalRequestUrl; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.getUriTemplateVariables; - -/** - * @author Spencer Gibb - */ -public class PrefixPathGatewayFilterFactory - extends AbstractGatewayFilterFactory { - - /** - * Prefix key. - */ - public static final String PREFIX_KEY = "prefix"; - - private static final Log log = LogFactory.getLog(PrefixPathGatewayFilterFactory.class); - - public PrefixPathGatewayFilterFactory() { - super(Config.class); - } - - @Override - public List shortcutFieldOrder() { - return Arrays.asList(PREFIX_KEY); - } - - @Override - public GatewayFilter apply(Config config) { - return new GatewayFilter() { - final UriTemplate uriTemplate = new UriTemplate(config.prefix); - - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - boolean alreadyPrefixed = exchange.getAttributeOrDefault(GATEWAY_ALREADY_PREFIXED_ATTR, false); - if (alreadyPrefixed) { - return chain.filter(exchange); - } - exchange.getAttributes().put(GATEWAY_ALREADY_PREFIXED_ATTR, true); - - ServerHttpRequest req = exchange.getRequest(); - addOriginalRequestUrl(exchange, req.getURI()); - - Map uriVariables = getUriTemplateVariables(exchange); - URI uri = uriTemplate.expand(uriVariables); - - String newPath = uri.getRawPath() + req.getURI().getRawPath(); - exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, uri); - ServerHttpRequest request = req.mutate().path(newPath).build(); - - if (log.isTraceEnabled()) { - log.trace("Prefixed URI with: " + config.prefix + " -> " + request.getURI()); - } - - return chain.filter(exchange.mutate().request(request).build()); - } - - @Override - public String toString() { - return filterToStringCreator(PrefixPathGatewayFilterFactory.this).append("prefix", config.getPrefix()) - .toString(); - } - }; - } - - public static class Config { - - private String prefix; - - public String getPrefix() { - return prefix; - } - - public void setPrefix(String prefix) { - this.prefix = prefix; - } - - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/PreserveHostHeaderGatewayFilterFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/PreserveHostHeaderGatewayFilterFactory.java deleted file mode 100644 index e1e63cff..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/PreserveHostHeaderGatewayFilterFactory.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import reactor.core.publisher.Mono; - -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.GatewayFilterChain; -import org.springframework.web.server.ServerWebExchange; - -import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.PRESERVE_HOST_HEADER_ATTRIBUTE; - -/** - * @author Spencer Gibb - */ -public class PreserveHostHeaderGatewayFilterFactory extends AbstractGatewayFilterFactory { - - public GatewayFilter apply() { - return apply(o -> { - }); - } - - @Override - public GatewayFilter apply(Object config) { - return new GatewayFilter() { - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - exchange.getAttributes().put(PRESERVE_HOST_HEADER_ATTRIBUTE, true); - return chain.filter(exchange); - } - - @Override - public String toString() { - return filterToStringCreator(PreserveHostHeaderGatewayFilterFactory.this).toString(); - } - }; - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RedirectToGatewayFilterFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RedirectToGatewayFilterFactory.java deleted file mode 100644 index 87f7f7d0..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RedirectToGatewayFilterFactory.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import java.net.URI; -import java.util.Arrays; -import java.util.List; - -import reactor.core.publisher.Mono; - -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.GatewayFilterChain; -import org.springframework.cloud.gateway.support.HttpStatusHolder; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.server.reactive.ServerHttpResponse; -import org.springframework.util.Assert; -import org.springframework.web.server.ServerWebExchange; - -import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.setResponseStatus; - -/** - * @author Spencer Gibb - */ -public class RedirectToGatewayFilterFactory - extends AbstractGatewayFilterFactory { - - /** - * Status key. - */ - public static final String STATUS_KEY = "status"; - - /** - * URL key. - */ - public static final String URL_KEY = "url"; - - public RedirectToGatewayFilterFactory() { - super(Config.class); - } - - @Override - public List shortcutFieldOrder() { - return Arrays.asList(STATUS_KEY, URL_KEY); - } - - @Override - public GatewayFilter apply(Config config) { - return apply(config.status, config.url); - } - - public GatewayFilter apply(String statusString, String urlString) { - HttpStatusHolder httpStatus = HttpStatusHolder.parse(statusString); - Assert.isTrue(httpStatus.is3xxRedirection(), "status must be a 3xx code, but was " + statusString); - final URI url = URI.create(urlString); - return apply(httpStatus, url); - } - - public GatewayFilter apply(HttpStatus httpStatus, URI uri) { - return apply(new HttpStatusHolder(httpStatus, null), uri); - } - - public GatewayFilter apply(HttpStatusHolder httpStatus, URI uri) { - return new GatewayFilter() { - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - if (!exchange.getResponse().isCommitted()) { - setResponseStatus(exchange, httpStatus); - - final ServerHttpResponse response = exchange.getResponse(); - response.getHeaders().set(HttpHeaders.LOCATION, uri.toString()); - return response.setComplete(); - } - return Mono.empty(); - } - - @Override - public String toString() { - String status; - if (httpStatus.getHttpStatus() != null) { - status = String.valueOf(httpStatus.getHttpStatus().value()); - } - else { - status = httpStatus.getStatus().toString(); - } - return filterToStringCreator(RedirectToGatewayFilterFactory.this).append(status, uri).toString(); - } - }; - } - - public static class Config { - - String status; - - String url; - - public String getStatus() { - return status; - } - - public void setStatus(String status) { - this.status = status; - } - - public String getUrl() { - return url; - } - - public void setUrl(String url) { - this.url = url; - } - - } - -} 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 deleted file mode 100644 index 09558702..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RemoveJsonAttributesResponseBodyGatewayFilterFactory.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import java.util.Arrays; -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; - -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.factory.rewrite.ModifyResponseBodyGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.rewrite.RewriteFunction; -import org.springframework.core.style.ToStringCreator; -import org.springframework.http.MediaType; - -/** - * @author Marta Medio - */ -public class RemoveJsonAttributesResponseBodyGatewayFilterFactory extends - AbstractGatewayFilterFactory { - - public RemoveJsonAttributesResponseBodyGatewayFilterFactory( - ModifyResponseBodyGatewayFilterFactory modifyResponseBodyGatewayFilterFactory) { - this.modifyResponseBodyGatewayFilterFactory = modifyResponseBodyGatewayFilterFactory; - } - - @Override - public ShortcutType shortcutType() { - return ShortcutType.GATHER_LIST_TAIL_FLAG; - } - - @Override - public List shortcutFieldOrder() { - return Arrays.asList("fieldList", "deleteRecursively"); - } - - @Override - public FieldListConfiguration newConfig() { - return new FieldListConfiguration(); - } - - @Override - public Class getConfigClass() { - return FieldListConfiguration.class; - } - - @Override - public GatewayFilter apply(FieldListConfiguration config) { - ModifyResponseBodyGatewayFilterFactory.Config modifyResponseBodyConfig = new ModifyResponseBodyGatewayFilterFactory.Config(); - modifyResponseBodyConfig.setInClass(String.class); - modifyResponseBodyConfig.setOutClass(String.class); - - RewriteFunction rewriteFunction = (exchange, body) -> { - if (MediaType.APPLICATION_JSON.isCompatibleWith(exchange.getResponse().getHeaders().getContentType())) { - try { - JsonNode jsonBodyContent = mapper.readValue(body, JsonNode.class); - - removeJsonAttribute(jsonBodyContent, config.getFieldList(), config.isDeleteRecursively()); - - body = mapper.writeValueAsString(jsonBodyContent); - } - catch (JsonProcessingException e) { - throw new RuntimeException(e); - } - } - return Mono.just(body); - }; - modifyResponseBodyConfig.setRewriteFunction(rewriteFunction); - - return modifyResponseBodyGatewayFilterFactory.apply(modifyResponseBodyConfig); - } - - private final ModifyResponseBodyGatewayFilterFactory modifyResponseBodyGatewayFilterFactory; - - 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); - } - } - } - } - if (jsonBodyContent instanceof ObjectNode) { - ((ObjectNode) jsonBodyContent).remove(fieldsToRemove); - } - } - - public static class FieldListConfiguration { - - private List fieldList; - - private boolean deleteRecursively; - - public boolean isDeleteRecursively() { - return deleteRecursively; - } - - public FieldListConfiguration setDeleteRecursively(boolean deleteRecursively) { - this.deleteRecursively = deleteRecursively; - return this; - } - - List getFieldList() { - return fieldList; - } - - public FieldListConfiguration setFieldList(List fieldList) { - this.fieldList = fieldList; - return this; - } - - @Override - public String toString() { - return new ToStringCreator(this).append("fieldList", fieldList) - .append("deleteRecursively", deleteRecursively).toString(); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RemoveRequestHeaderGatewayFilterFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RemoveRequestHeaderGatewayFilterFactory.java deleted file mode 100644 index 9a772491..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RemoveRequestHeaderGatewayFilterFactory.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import java.util.Arrays; -import java.util.List; - -import reactor.core.publisher.Mono; - -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.GatewayFilterChain; -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.web.server.ServerWebExchange; - -import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator; - -/** - * @author Spencer Gibb - */ -public class RemoveRequestHeaderGatewayFilterFactory - extends AbstractGatewayFilterFactory { - - public RemoveRequestHeaderGatewayFilterFactory() { - super(NameConfig.class); - } - - @Override - public List shortcutFieldOrder() { - return Arrays.asList(NAME_KEY); - } - - @Override - public GatewayFilter apply(NameConfig config) { - return new GatewayFilter() { - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - ServerHttpRequest request = exchange.getRequest().mutate() - .headers(httpHeaders -> httpHeaders.remove(config.getName())).build(); - - return chain.filter(exchange.mutate().request(request).build()); - } - - @Override - public String toString() { - return filterToStringCreator(RemoveRequestHeaderGatewayFilterFactory.this) - .append("name", config.getName()).toString(); - } - }; - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RemoveRequestParameterGatewayFilterFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RemoveRequestParameterGatewayFilterFactory.java deleted file mode 100644 index 8d09d331..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RemoveRequestParameterGatewayFilterFactory.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import java.net.URI; -import java.util.Arrays; -import java.util.List; - -import reactor.core.publisher.Mono; - -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.GatewayFilterChain; -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; -import org.springframework.web.server.ServerWebExchange; -import org.springframework.web.util.UriComponentsBuilder; - -import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator; -import static org.springframework.util.CollectionUtils.unmodifiableMultiValueMap; - -/** - * @author Thirunavukkarasu Ravichandran - */ -public class RemoveRequestParameterGatewayFilterFactory - extends AbstractGatewayFilterFactory { - - public RemoveRequestParameterGatewayFilterFactory() { - super(NameConfig.class); - } - - @Override - public List shortcutFieldOrder() { - return Arrays.asList(NAME_KEY); - } - - @Override - public GatewayFilter apply(NameConfig config) { - return new GatewayFilter() { - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - ServerHttpRequest request = exchange.getRequest(); - MultiValueMap queryParams = new LinkedMultiValueMap<>(request.getQueryParams()); - queryParams.remove(config.getName()); - - URI newUri = UriComponentsBuilder.fromUri(request.getURI()) - .replaceQueryParams(unmodifiableMultiValueMap(queryParams)).build().toUri(); - - ServerHttpRequest updatedRequest = exchange.getRequest().mutate().uri(newUri).build(); - - return chain.filter(exchange.mutate().request(updatedRequest).build()); - } - - @Override - public String toString() { - return filterToStringCreator(RemoveRequestParameterGatewayFilterFactory.this) - .append("name", config.getName()).toString(); - } - }; - } - -} 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 deleted file mode 100644 index 92efcdf4..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RemoveResponseHeaderGatewayFilterFactory.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import java.util.Arrays; -import java.util.List; - -import reactor.core.publisher.Mono; - -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.GatewayFilterChain; -import org.springframework.web.server.ServerWebExchange; - -import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator; - -/** - * @author Spencer Gibb - */ -public class RemoveResponseHeaderGatewayFilterFactory - extends AbstractGatewayFilterFactory { - - public RemoveResponseHeaderGatewayFilterFactory() { - super(NameConfig.class); - } - - @Override - public List shortcutFieldOrder() { - return Arrays.asList(NAME_KEY); - } - - @Override - public GatewayFilter apply(NameConfig config) { - return new GatewayFilter() { - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - return chain.filter(exchange) - .then(Mono.fromRunnable(() -> exchange.getResponse().getHeaders().remove(config.getName()))); - } - - @Override - public String toString() { - return filterToStringCreator(RemoveResponseHeaderGatewayFilterFactory.this) - .append("name", config.getName()).toString(); - } - }; - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RequestHeaderSizeGatewayFilterFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RequestHeaderSizeGatewayFilterFactory.java deleted file mode 100644 index 3817dca7..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RequestHeaderSizeGatewayFilterFactory.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import reactor.core.publisher.Mono; - -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.GatewayFilterChain; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.util.unit.DataSize; -import org.springframework.util.unit.DataUnit; -import org.springframework.web.server.ServerWebExchange; - -import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator; - -/** - * This filter validates the size of each Request Header in the request, including the - * key. If size of the request header is greater than the configured maxSize, it blocks - * the request. Default max size of request header is 16KB. - * - * @author Sakalya Deshpande - * @author Marta Medio - */ - -public class RequestHeaderSizeGatewayFilterFactory - extends AbstractGatewayFilterFactory { - - private static String ERROR_PREFIX = "Request Header/s size is larger than permissible limit (%s)."; - - private static String ERROR = " Request Header/s size for '%s' is %s."; - - public RequestHeaderSizeGatewayFilterFactory() { - super(RequestHeaderSizeGatewayFilterFactory.Config.class); - } - - @Override - public List shortcutFieldOrder() { - return Collections.singletonList("maxSize"); - } - - @Override - public GatewayFilter apply(RequestHeaderSizeGatewayFilterFactory.Config config) { - String errorHeaderName = config.getErrorHeaderName() != null ? config.getErrorHeaderName() : "errorMessage"; - return new GatewayFilter() { - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - ServerHttpRequest request = exchange.getRequest(); - HttpHeaders headers = request.getHeaders(); - HashMap longHeaders = new HashMap<>(); - - for (Map.Entry> headerEntry : headers.entrySet()) { - long headerSizeInBytes = 0L; - headerSizeInBytes += headerEntry.getKey().getBytes().length; - List values = headerEntry.getValue(); - for (String value : values) { - headerSizeInBytes += value.getBytes().length; - } - if (headerSizeInBytes > config.getMaxSize().toBytes()) { - longHeaders.put(headerEntry.getKey(), headerSizeInBytes); - } - } - - if (!longHeaders.isEmpty()) { - exchange.getResponse().setStatusCode(HttpStatus.REQUEST_HEADER_FIELDS_TOO_LARGE); - exchange.getResponse().getHeaders().add(errorHeaderName, - getErrorMessage(longHeaders, config.getMaxSize())); - return exchange.getResponse().setComplete(); - - } - - return chain.filter(exchange); - } - - @Override - public String toString() { - return filterToStringCreator(RequestHeaderSizeGatewayFilterFactory.this) - .append("maxSize", config.getMaxSize()).toString(); - } - }; - } - - private static String getErrorMessage(HashMap longHeaders, DataSize maxSize) { - StringBuilder msg = new StringBuilder(String.format(ERROR_PREFIX, maxSize)); - longHeaders - .forEach((header, size) -> msg.append(String.format(ERROR, header, DataSize.of(size, DataUnit.BYTES)))); - return msg.toString(); - } - - public static class Config { - - private DataSize maxSize = DataSize.ofBytes(16000L); - - private String errorHeaderName; - - public DataSize getMaxSize() { - return maxSize; - } - - public void setMaxSize(DataSize maxSize) { - this.maxSize = maxSize; - } - - public String getErrorHeaderName() { - return errorHeaderName; - } - - public void setErrorHeaderName(String errorHeaderName) { - this.errorHeaderName = errorHeaderName; - } - - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RequestHeaderToRequestUriGatewayFilterFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RequestHeaderToRequestUriGatewayFilterFactory.java deleted file mode 100644 index 371b01a6..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RequestHeaderToRequestUriGatewayFilterFactory.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import java.net.MalformedURLException; -import java.net.URI; -import java.util.Arrays; -import java.util.List; -import java.util.Optional; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.OrderedGatewayFilter; -import org.springframework.web.server.ServerWebExchange; - -import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator; - -/** - * This filter changes the request uri by a request header. - * - * @author Toshiaki Maki - */ -public class RequestHeaderToRequestUriGatewayFilterFactory - extends AbstractChangeRequestUriGatewayFilterFactory { - - private final Logger log = LoggerFactory.getLogger(RequestHeaderToRequestUriGatewayFilterFactory.class); - - public RequestHeaderToRequestUriGatewayFilterFactory() { - super(NameConfig.class); - } - - @Override - public List shortcutFieldOrder() { - return Arrays.asList(NAME_KEY); - } - - @Override - public GatewayFilter apply(NameConfig config) { - // AbstractChangeRequestUriGatewayFilterFactory.apply() returns - // OrderedGatewayFilter - OrderedGatewayFilter gatewayFilter = (OrderedGatewayFilter) super.apply(config); - return new OrderedGatewayFilter(gatewayFilter, gatewayFilter.getOrder()) { - @Override - public String toString() { - return filterToStringCreator(RequestHeaderToRequestUriGatewayFilterFactory.this) - .append("name", config.getName()).toString(); - } - }; - } - - @Override - protected Optional determineRequestUri(ServerWebExchange exchange, NameConfig config) { - String requestUrl = exchange.getRequest().getHeaders().getFirst(config.getName()); - return Optional.ofNullable(requestUrl).map(url -> { - try { - URI uri = URI.create(url); - uri.toURL(); // validate url - return uri; - } - catch (IllegalArgumentException | MalformedURLException e) { - log.info("Request url is invalid : url={}, error={}", requestUrl, e.getMessage()); - return null; - } - }); - } - -} 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 deleted file mode 100644 index b524b572..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RequestRateLimiterGatewayFilterFactory.java +++ /dev/null @@ -1,203 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import java.util.Map; - -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver; -import org.springframework.cloud.gateway.filter.ratelimit.RateLimiter; -import org.springframework.cloud.gateway.route.Route; -import org.springframework.cloud.gateway.support.HasRouteId; -import org.springframework.cloud.gateway.support.HttpStatusHolder; -import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; -import org.springframework.http.HttpStatus; - -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.setResponseStatus; - -/** - * 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") -public class RequestRateLimiterGatewayFilterFactory - extends AbstractGatewayFilterFactory { - - /** - * Key-Resolver key. - */ - public static final String KEY_RESOLVER_KEY = "keyResolver"; - - private static final String EMPTY_KEY = "____EMPTY_KEY__"; - - private final RateLimiter defaultRateLimiter; - - private final KeyResolver defaultKeyResolver; - - /** - * Switch to deny requests if the Key Resolver returns an empty key, defaults to true. - */ - private boolean denyEmptyKey = true; - - /** HttpStatus to return when denyEmptyKey is true, defaults to FORBIDDEN. */ - private String emptyKeyStatusCode = HttpStatus.FORBIDDEN.name(); - - public RequestRateLimiterGatewayFilterFactory(RateLimiter defaultRateLimiter, KeyResolver defaultKeyResolver) { - super(Config.class); - this.defaultRateLimiter = defaultRateLimiter; - this.defaultKeyResolver = defaultKeyResolver; - } - - public KeyResolver getDefaultKeyResolver() { - return defaultKeyResolver; - } - - public RateLimiter getDefaultRateLimiter() { - return defaultRateLimiter; - } - - public boolean isDenyEmptyKey() { - return denyEmptyKey; - } - - public void setDenyEmptyKey(boolean denyEmptyKey) { - this.denyEmptyKey = denyEmptyKey; - } - - public String getEmptyKeyStatusCode() { - return emptyKeyStatusCode; - } - - public void setEmptyKeyStatusCode(String emptyKeyStatusCode) { - this.emptyKeyStatusCode = emptyKeyStatusCode; - } - - @SuppressWarnings("unchecked") - @Override - public GatewayFilter apply(Config config) { - KeyResolver resolver = getOrDefault(config.keyResolver, defaultKeyResolver); - RateLimiter limiter = getOrDefault(config.rateLimiter, defaultRateLimiter); - boolean denyEmpty = getOrDefault(config.denyEmptyKey, this.denyEmptyKey); - HttpStatusHolder emptyKeyStatus = HttpStatusHolder - .parse(getOrDefault(config.emptyKeyStatus, this.emptyKeyStatusCode)); - - return (exchange, chain) -> resolver.resolve(exchange).defaultIfEmpty(EMPTY_KEY).flatMap(key -> { - if (EMPTY_KEY.equals(key)) { - if (denyEmpty) { - setResponseStatus(exchange, emptyKeyStatus); - return exchange.getResponse().setComplete(); - } - return chain.filter(exchange); - } - String routeId = config.getRouteId(); - if (routeId == null) { - Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR); - routeId = route.getId(); - } - return limiter.isAllowed(routeId, key).flatMap(response -> { - - for (Map.Entry header : response.getHeaders().entrySet()) { - exchange.getResponse().getHeaders().add(header.getKey(), header.getValue()); - } - - if (response.isAllowed()) { - return chain.filter(exchange); - } - - setResponseStatus(exchange, config.getStatusCode()); - return exchange.getResponse().setComplete(); - }); - }); - } - - private T getOrDefault(T configValue, T defaultValue) { - return (configValue != null) ? configValue : defaultValue; - } - - public static class Config implements HasRouteId { - - private KeyResolver keyResolver; - - private RateLimiter rateLimiter; - - private HttpStatus statusCode = HttpStatus.TOO_MANY_REQUESTS; - - private Boolean denyEmptyKey; - - private String emptyKeyStatus; - - private String routeId; - - public KeyResolver getKeyResolver() { - return keyResolver; - } - - public Config setKeyResolver(KeyResolver keyResolver) { - this.keyResolver = keyResolver; - return this; - } - - public RateLimiter getRateLimiter() { - return rateLimiter; - } - - public Config setRateLimiter(RateLimiter rateLimiter) { - this.rateLimiter = rateLimiter; - return this; - } - - public HttpStatus getStatusCode() { - return statusCode; - } - - public Config setStatusCode(HttpStatus statusCode) { - this.statusCode = statusCode; - return this; - } - - public Boolean getDenyEmptyKey() { - return denyEmptyKey; - } - - public Config setDenyEmptyKey(Boolean denyEmptyKey) { - this.denyEmptyKey = denyEmptyKey; - return this; - } - - public String getEmptyKeyStatus() { - return emptyKeyStatus; - } - - public Config setEmptyKeyStatus(String emptyKeyStatus) { - this.emptyKeyStatus = emptyKeyStatus; - return this; - } - - @Override - public void setRouteId(String routeId) { - this.routeId = routeId; - } - - @Override - public String getRouteId() { - return this.routeId; - } - - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RequestSizeGatewayFilterFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RequestSizeGatewayFilterFactory.java deleted file mode 100644 index 3d798130..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RequestSizeGatewayFilterFactory.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import reactor.core.publisher.Mono; - -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.GatewayFilterChain; -import org.springframework.http.HttpStatus; -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.util.Assert; -import org.springframework.util.ObjectUtils; -import org.springframework.util.unit.DataSize; -import org.springframework.web.server.ServerWebExchange; - -import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator; - -/** - * This filter blocks the request, if the request size is more than the permissible size. - * The default request size is 5 MB. - * - * @author Arpan - */ -public class RequestSizeGatewayFilterFactory - extends AbstractGatewayFilterFactory { - - private static String PREFIX = "kMGTPE"; - - private static String ERROR = "Request size is larger than permissible limit." - + " Request size is %s where permissible limit is %s"; - - public RequestSizeGatewayFilterFactory() { - super(RequestSizeGatewayFilterFactory.RequestSizeConfig.class); - } - - private static String getErrorMessage(Long currentRequestSize, Long maxSize) { - return String.format(ERROR, getReadableByteCount(currentRequestSize), getReadableByteCount(maxSize)); - } - - private static String getReadableByteCount(long bytes) { - int unit = 1000; - if (bytes < unit) { - return bytes + " B"; - } - int exp = (int) (Math.log(bytes) / Math.log(unit)); - String pre = Character.toString(PREFIX.charAt(exp - 1)); - return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre); - } - - @Override - public GatewayFilter apply(RequestSizeGatewayFilterFactory.RequestSizeConfig requestSizeConfig) { - requestSizeConfig.validate(); - return new GatewayFilter() { - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - ServerHttpRequest request = exchange.getRequest(); - String contentLength = request.getHeaders().getFirst("content-length"); - if (!ObjectUtils.isEmpty(contentLength)) { - Long currentRequestSize = Long.valueOf(contentLength); - if (currentRequestSize > requestSizeConfig.getMaxSize().toBytes()) { - exchange.getResponse().setStatusCode(HttpStatus.PAYLOAD_TOO_LARGE); - if (!exchange.getResponse().isCommitted()) { - exchange.getResponse().getHeaders().add("errorMessage", - getErrorMessage(currentRequestSize, requestSizeConfig.getMaxSize().toBytes())); - } - return exchange.getResponse().setComplete(); - } - } - return chain.filter(exchange); - } - - @Override - public String toString() { - return filterToStringCreator(RequestSizeGatewayFilterFactory.this) - .append("max", requestSizeConfig.getMaxSize()).toString(); - } - }; - } - - public static class RequestSizeConfig { - - // TODO: use boot data size type - private DataSize maxSize = DataSize.ofBytes(5000000L); - - public DataSize getMaxSize() { - return maxSize; - } - - public RequestSizeGatewayFilterFactory.RequestSizeConfig setMaxSize(DataSize maxSize) { - this.maxSize = maxSize; - return this; - } - - // TODO: use validator annotation - public void validate() { - Assert.notNull(this.maxSize, "maxSize may not be null"); - Assert.isTrue(this.maxSize.toBytes() > 0, "maxSize must be greater than 0"); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RetryGatewayFilterFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RetryGatewayFilterFactory.java deleted file mode 100644 index 55896fc7..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RetryGatewayFilterFactory.java +++ /dev/null @@ -1,437 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import java.io.IOException; -import java.time.Duration; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.function.Predicate; -import java.util.function.Supplier; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.reactivestreams.Publisher; -import reactor.core.publisher.Mono; -import reactor.netty.Connection; -import reactor.retry.Backoff; -import reactor.retry.Repeat; -import reactor.retry.RepeatContext; -import reactor.retry.Retry; -import reactor.retry.RetryContext; - -import org.springframework.cloud.gateway.event.EnableBodyCachingEvent; -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.GatewayFilterChain; -import org.springframework.cloud.gateway.support.HasRouteId; -import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; -import org.springframework.cloud.gateway.support.TimeoutException; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.http.HttpStatus.Series; -import org.springframework.http.HttpStatusCode; -import org.springframework.util.Assert; -import org.springframework.web.server.ServerWebExchange; - -import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator; - -public class RetryGatewayFilterFactory extends AbstractGatewayFilterFactory { - - /** - * Retry iteration key. - */ - public static final String RETRY_ITERATION_KEY = "retry_iteration"; - - private static final Log log = LogFactory.getLog(RetryGatewayFilterFactory.class); - - public RetryGatewayFilterFactory() { - super(RetryConfig.class); - } - - private static List toList(T... items) { - return new ArrayList<>(Arrays.asList(items)); - } - - @Override - public List shortcutFieldOrder() { - return Arrays.asList("retries", "statuses", "methods", "backoff.firstBackoff", "backoff.maxBackoff", - "backoff.factor", "backoff.basedOnPreviousValue"); - } - - @Override - public GatewayFilter apply(RetryConfig retryConfig) { - retryConfig.validate(); - - Repeat statusCodeRepeat = null; - if (!retryConfig.getStatuses().isEmpty() || !retryConfig.getSeries().isEmpty()) { - Predicate> repeatPredicate = context -> { - ServerWebExchange exchange = context.applicationContext(); - if (exceedsMaxIterations(exchange, retryConfig)) { - return false; - } - - HttpStatusCode statusCode = exchange.getResponse().getStatusCode(); - - boolean retryableStatusCode = retryConfig.getStatuses().contains(statusCode); - - // null status code might mean a network exception? - if (!retryableStatusCode && statusCode != null) { - // try the series - retryableStatusCode = false; - for (int i = 0; i < retryConfig.getSeries().size(); i++) { - if (statusCode instanceof HttpStatus) { - HttpStatus httpStatus = (HttpStatus) statusCode; - if (httpStatus.series().equals(retryConfig.getSeries().get(i))) { - retryableStatusCode = true; - break; - } - } - } - } - - final boolean finalRetryableStatusCode = retryableStatusCode; - trace("retryableStatusCode: %b, statusCode %s, configured statuses %s, configured series %s", - () -> finalRetryableStatusCode, () -> statusCode, retryConfig::getStatuses, - retryConfig::getSeries); - - HttpMethod httpMethod = exchange.getRequest().getMethod(); - boolean retryableMethod = retryConfig.getMethods().contains(httpMethod); - - trace("retryableMethod: %b, httpMethod %s, configured methods %s", () -> retryableMethod, - () -> httpMethod, retryConfig::getMethods); - return retryableMethod && finalRetryableStatusCode; - }; - - statusCodeRepeat = Repeat.onlyIf(repeatPredicate) - .doOnRepeat(context -> reset(context.applicationContext())); - - BackoffConfig backoff = retryConfig.getBackoff(); - if (backoff != null) { - statusCodeRepeat = statusCodeRepeat.backoff(getBackoff(backoff)); - } - } - - // TODO: support timeout, backoff, jitter, etc... in Builder - - Retry exceptionRetry = null; - if (!retryConfig.getExceptions().isEmpty()) { - Predicate> retryContextPredicate = context -> { - - ServerWebExchange exchange = context.applicationContext(); - - if (exceedsMaxIterations(exchange, retryConfig)) { - return false; - } - - Throwable exception = context.exception(); - for (Class retryableClass : retryConfig.getExceptions()) { - if (retryableClass.isInstance(exception) - || (exception != null && retryableClass.isInstance(exception.getCause()))) { - trace("exception or its cause is retryable %s, configured exceptions %s", - () -> getExceptionNameWithCause(exception), retryConfig::getExceptions); - - HttpMethod httpMethod = exchange.getRequest().getMethod(); - boolean retryableMethod = retryConfig.getMethods().contains(httpMethod); - trace("retryableMethod: %b, httpMethod %s, configured methods %s", () -> retryableMethod, - () -> httpMethod, retryConfig::getMethods); - return retryableMethod; - } - } - trace("exception or its cause is not retryable %s, configured exceptions %s", - () -> getExceptionNameWithCause(exception), retryConfig::getExceptions); - return false; - }; - exceptionRetry = Retry.onlyIf(retryContextPredicate) - .doOnRetry(context -> reset(context.applicationContext())).retryMax(retryConfig.getRetries()); - BackoffConfig backoff = retryConfig.getBackoff(); - if (backoff != null) { - exceptionRetry = exceptionRetry.backoff(getBackoff(backoff)); - } - } - - GatewayFilter gatewayFilter = apply(retryConfig.getRouteId(), statusCodeRepeat, exceptionRetry); - return new GatewayFilter() { - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - return gatewayFilter.filter(exchange, chain); - } - - @Override - public String toString() { - return filterToStringCreator(RetryGatewayFilterFactory.this).append("routeId", retryConfig.getRouteId()) - .append("retries", retryConfig.getRetries()).append("series", retryConfig.getSeries()) - .append("statuses", retryConfig.getStatuses()).append("methods", retryConfig.getMethods()) - .append("exceptions", retryConfig.getExceptions()).toString(); - } - }; - } - - private String getExceptionNameWithCause(Throwable exception) { - if (exception != null) { - StringBuilder builder = new StringBuilder(exception.getClass().getName()); - Throwable cause = exception.getCause(); - if (cause != null) { - builder.append("{cause=").append(cause.getClass().getName()).append("}"); - } - return builder.toString(); - } - else { - return "null"; - } - } - - private Backoff getBackoff(BackoffConfig backoff) { - return Backoff.exponential(backoff.firstBackoff, backoff.maxBackoff, backoff.factor, - backoff.basedOnPreviousValue); - } - - public boolean exceedsMaxIterations(ServerWebExchange exchange, RetryConfig retryConfig) { - Integer iteration = exchange.getAttribute(RETRY_ITERATION_KEY); - - // TODO: deal with null iteration - boolean exceeds = iteration != null && iteration >= retryConfig.getRetries(); - trace("exceedsMaxIterations %b, iteration %d, configured retries %d", () -> exceeds, () -> iteration, - retryConfig::getRetries); - return exceeds; - } - - @Deprecated - /** - * Use {@link ServerWebExchangeUtils#reset(ServerWebExchange)} - */ - public void reset(ServerWebExchange exchange) { - Connection conn = exchange.getAttribute(ServerWebExchangeUtils.CLIENT_RESPONSE_CONN_ATTR); - if (conn != null) { - trace("disposing response connection before next iteration"); - conn.dispose(); - exchange.getAttributes().remove(ServerWebExchangeUtils.CLIENT_RESPONSE_CONN_ATTR); - } - ServerWebExchangeUtils.reset(exchange); - } - - public GatewayFilter apply(String routeId, Repeat repeat, Retry retry) { - if (routeId != null && getPublisher() != null) { - // send an event to enable caching - getPublisher().publishEvent(new EnableBodyCachingEvent(this, routeId)); - } - return (exchange, chain) -> { - trace("Entering retry-filter"); - - // chain.filter returns a Mono - Publisher publisher = chain.filter(exchange) - // .log("retry-filter", Level.INFO) - .doOnSuccess(aVoid -> updateIteration(exchange)).doOnError(throwable -> updateIteration(exchange)); - - if (retry != null) { - // retryWhen returns a Mono - // retry needs to go before repeat - publisher = ((Mono) publisher) - .retryWhen(reactor.util.retry.Retry.withThrowable(retry.withApplicationContext(exchange))); - } - if (repeat != null) { - // repeatWhen returns a Flux - // so this needs to be last and the variable a Publisher - publisher = ((Mono) publisher).repeatWhen(repeat.withApplicationContext(exchange)); - } - - return Mono.fromDirect(publisher); - }; - } - - private void updateIteration(ServerWebExchange exchange) { - int iteration = exchange.getAttributeOrDefault(RETRY_ITERATION_KEY, -1); - int newIteration = iteration + 1; - trace("setting new iteration in attr %d", () -> newIteration); - exchange.getAttributes().put(RETRY_ITERATION_KEY, newIteration); - } - - @SafeVarargs - private final void trace(String message, Supplier... argSuppliers) { - if (log.isTraceEnabled()) { - Object[] args = new Object[argSuppliers.length]; - int i = 0; - for (Supplier a : argSuppliers) { - args[i] = a.get(); - ++i; - } - log.trace(String.format(message, args)); - } - } - - @SuppressWarnings("unchecked") - public static class RetryConfig implements HasRouteId { - - private String routeId; - - private int retries = 3; - - private List series = toList(Series.SERVER_ERROR); - - private List statuses = new ArrayList<>(); - - private List methods = toList(HttpMethod.GET); - - private List> exceptions = toList(IOException.class, TimeoutException.class); - - private BackoffConfig backoff; - - public RetryConfig allMethods() { - return setMethods(HttpMethod.values()); - } - - public void validate() { - Assert.isTrue(this.retries > 0, "retries must be greater than 0"); - Assert.isTrue(!this.series.isEmpty() || !this.statuses.isEmpty() || !this.exceptions.isEmpty(), - "series, status and exceptions may not all be empty"); - Assert.notEmpty(this.methods, "methods may not be empty"); - if (this.backoff != null) { - this.backoff.validate(); - } - } - - public BackoffConfig getBackoff() { - return backoff; - } - - public RetryConfig setBackoff(BackoffConfig backoff) { - this.backoff = backoff; - return this; - } - - public RetryConfig setBackoff(Duration firstBackoff, Duration maxBackoff, int factor, - boolean basedOnPreviousValue) { - this.backoff = new BackoffConfig(firstBackoff, maxBackoff, factor, basedOnPreviousValue); - return this; - } - - @Override - public void setRouteId(String routeId) { - this.routeId = routeId; - } - - @Override - public String getRouteId() { - return this.routeId; - } - - public int getRetries() { - return retries; - } - - public RetryConfig setRetries(int retries) { - this.retries = retries; - return this; - } - - public List getSeries() { - return series; - } - - public RetryConfig setSeries(Series... series) { - this.series = Arrays.asList(series); - return this; - } - - public List getStatuses() { - return statuses; - } - - public RetryConfig setStatuses(HttpStatus... statuses) { - this.statuses = Arrays.asList(statuses); - return this; - } - - public List getMethods() { - return methods; - } - - public RetryConfig setMethods(HttpMethod... methods) { - this.methods = Arrays.asList(methods); - return this; - } - - public List> getExceptions() { - return exceptions; - } - - public RetryConfig setExceptions(Class... exceptions) { - this.exceptions = Arrays.asList(exceptions); - return this; - } - - } - - public static class BackoffConfig { - - private Duration firstBackoff = Duration.ofMillis(5); - - private Duration maxBackoff; - - private int factor = 2; - - private boolean basedOnPreviousValue = true; - - public BackoffConfig() { - } - - public BackoffConfig(Duration firstBackoff, Duration maxBackoff, int factor, boolean basedOnPreviousValue) { - this.firstBackoff = firstBackoff; - this.maxBackoff = maxBackoff; - this.factor = factor; - this.basedOnPreviousValue = basedOnPreviousValue; - } - - public void validate() { - Assert.notNull(this.firstBackoff, "firstBackoff must be present"); - } - - public Duration getFirstBackoff() { - return firstBackoff; - } - - public void setFirstBackoff(Duration firstBackoff) { - this.firstBackoff = firstBackoff; - } - - public Duration getMaxBackoff() { - return maxBackoff; - } - - public void setMaxBackoff(Duration maxBackoff) { - this.maxBackoff = maxBackoff; - } - - public int getFactor() { - return factor; - } - - public void setFactor(int factor) { - this.factor = factor; - } - - public boolean isBasedOnPreviousValue() { - return basedOnPreviousValue; - } - - public void setBasedOnPreviousValue(boolean basedOnPreviousValue) { - this.basedOnPreviousValue = basedOnPreviousValue; - } - - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RewriteLocationResponseHeaderGatewayFilterFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RewriteLocationResponseHeaderGatewayFilterFactory.java deleted file mode 100644 index aa1a4844..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RewriteLocationResponseHeaderGatewayFilterFactory.java +++ /dev/null @@ -1,258 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import java.util.Arrays; -import java.util.List; -import java.util.regex.Pattern; - -import reactor.core.publisher.Mono; - -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.GatewayFilterChain; -import org.springframework.http.HttpHeaders; -import org.springframework.web.server.ServerWebExchange; - -import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator; - -/* -This filter intent is to modify the value of Location response header, ridding it of backend specific details. -Typical scenario: POST response from a legacy backend contains a Location header with backend's hostname. - You want to replace the backend's hostname:port with your documented gateway's hostname:port. -Additionally, sometimes backend returns a 'versioned' Location, and you may want to strip the version portion. - -Note that the protocol portion of the URL will not be replaced by this filter. If you need that too, -either follow this filter with a generic rewrite response header filter, or subclass this filter. - -Configuration parameters: -- stripVersion - NEVER_STRIP - Version will not be stripped, even if the original request path contains no version - AS_IN_REQUEST - Default. Version will be stripped only if the original request path contains no version - ALWAYS_STRIP - Version will be stripped, even if the original request path contains version -- locationHeaderName - String representing a custom location header name. Default - Location -- hostValue - A String. If provided, will be used to replace the host:port portion of the response Location header. - Default - The value of request Host header is used to replace. -- protocols - A valid regex String, against which the protocol name will be matched. If not matched, the filter will do nothing. - Default - https?|ftps? (which is the same as http|https|ftp|ftps) - -Example 1 - default-filters: - - RewriteLocationResponseHeader - -Host request header: api.example.com:443 -POST request path: /some/object/name -Location response header: https://object-service.prod.example.net/v2/some/object/id -Modified Location response header: https://api.example.com:443/some/object/id - -Example 2 - default-filters: - - name: RewriteLocationResponseHeader - args: - stripVersion: ALWAYS_STRIP - locationHeaderName: Link - hostValue: example-api.com - -Host request header (irrelevant): api.example.com:443 -POST request path: /v1/some/object/name -Link response header: https://object-service.prod.example.net/v1/some/object/id -Modified Link response header: https://example-api.com/some/object/id - -Example 3 - default-filters: - - name: RewriteLocationResponseHeader - args: - stripVersion: NEVER_STRIP - protocols: https|ftps # only replace host:port for https or ftps, but not http or ftp - -Host request header: api.example.com:443 -1. POST request path: /some/object/name -Location response header: https://object-service.prod.example.net/v1/some/object/id -Modified Location response header: https://api.example.com/v1/some/object/id -2. POST request path: /some/other/object/name -Location response header: http://object-service.prod.example.net/v1/some/object/id -Modified (not) Location response header: http://object-service.prod.example.net/v1/some/object/id - */ - -/** - * @author Vitaliy Pavlyuk - */ -public class RewriteLocationResponseHeaderGatewayFilterFactory - extends AbstractGatewayFilterFactory { - - private static final String STRIP_VERSION_KEY = "stripVersion"; - - private static final String LOCATION_HEADER_NAME_KEY = "locationHeaderName"; - - private static final String HOST_VALUE_KEY = "hostValue"; - - private static final String PROTOCOLS_KEY = "protocols"; - - private static final Pattern VERSIONED_PATH = Pattern.compile("^/v\\d+/.*"); - - private static final String DEFAULT_PROTOCOLS = "https?|ftps?"; - - private static final Pattern DEFAULT_HOST_PORT = compileHostPortPattern(DEFAULT_PROTOCOLS); - - private static final Pattern DEFAULT_HOST_PORT_VERSION = compileHostPortVersionPattern(DEFAULT_PROTOCOLS); - - public RewriteLocationResponseHeaderGatewayFilterFactory() { - super(Config.class); - } - - private static Pattern compileHostPortPattern(String protocols) { - return Pattern.compile("(?<=^(?:" + protocols + ")://)[^:/]+(?::\\d+)?(?=/)"); - } - - private static Pattern compileHostPortVersionPattern(String protocols) { - return Pattern.compile("(?<=^(?:" + protocols + ")://)[^:/]+(?::\\d+)?(?:/v\\d+)?(?=/)"); - } - - @Override - public List shortcutFieldOrder() { - return Arrays.asList(STRIP_VERSION_KEY, LOCATION_HEADER_NAME_KEY, HOST_VALUE_KEY, PROTOCOLS_KEY); - } - - @Override - public GatewayFilter apply(Config config) { - return new GatewayFilter() { - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - return chain.filter(exchange).then(Mono.fromRunnable(() -> rewriteLocation(exchange, config))); - } - - @Override - public String toString() { - // @formatter:off - return filterToStringCreator( - RewriteLocationResponseHeaderGatewayFilterFactory.this) - .append("stripVersion", config.stripVersion) - .append("locationHeaderName", config.locationHeaderName) - .append("hostValue", config.hostValue) - .append("protocols", config.protocols) - .toString(); - // @formatter:on - } - }; - } - - void rewriteLocation(ServerWebExchange exchange, Config config) { - final String location = exchange.getResponse().getHeaders().getFirst(config.getLocationHeaderName()); - final String host = config.getHostValue() != null ? config.getHostValue() - : exchange.getRequest().getHeaders().getFirst(HttpHeaders.HOST); - final String path = exchange.getRequest().getURI().getPath(); - if (location != null && host != null) { - final String fixedLocation = fixedLocation(location, host, path, config.getStripVersion(), - config.getHostPortPattern(), config.getHostPortVersionPattern()); - exchange.getResponse().getHeaders().set(config.getLocationHeaderName(), fixedLocation); - } - } - - String fixedLocation(String location, String host, String path, StripVersion stripVersion, Pattern hostPortPattern, - Pattern hostPortVersionPattern) { - final boolean doStrip = StripVersion.ALWAYS_STRIP.equals(stripVersion) - || (StripVersion.AS_IN_REQUEST.equals(stripVersion) && !VERSIONED_PATH.matcher(path).matches()); - final Pattern pattern = doStrip ? hostPortVersionPattern : hostPortPattern; - return pattern.matcher(location).replaceFirst(host); - } - - public enum StripVersion { - - /** - * Version will not be stripped, even if the original request path contains no - * version. - */ - NEVER_STRIP, - - /** - * Version will be stripped only if the original request path contains no version. - * This is the Default. - */ - AS_IN_REQUEST, - - /** - * Version will be stripped, even if the original request path contains version. - */ - ALWAYS_STRIP - - } - - public static class Config { - - private StripVersion stripVersion = StripVersion.AS_IN_REQUEST; - - private String locationHeaderName = HttpHeaders.LOCATION; - - private String hostValue; - - private String protocols = DEFAULT_PROTOCOLS; - - private Pattern hostPortPattern = DEFAULT_HOST_PORT; - - private Pattern hostPortVersionPattern = DEFAULT_HOST_PORT_VERSION; - - public StripVersion getStripVersion() { - return stripVersion; - } - - public Config setStripVersion(StripVersion stripVersion) { - this.stripVersion = stripVersion; - return this; - } - - public String getLocationHeaderName() { - return locationHeaderName; - } - - public Config setLocationHeaderName(String locationHeaderName) { - this.locationHeaderName = locationHeaderName; - return this; - } - - public String getHostValue() { - return hostValue; - } - - public Config setHostValue(String hostValue) { - this.hostValue = hostValue; - return this; - } - - public String getProtocols() { - return protocols; - } - - public Config setProtocols(String protocols) { - this.protocols = protocols; - this.hostPortPattern = compileHostPortPattern(protocols); - this.hostPortVersionPattern = compileHostPortVersionPattern(protocols); - return this; - } - - public Pattern getHostPortPattern() { - return hostPortPattern; - } - - public Pattern getHostPortVersionPattern() { - return hostPortVersionPattern; - } - - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RewritePathGatewayFilterFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RewritePathGatewayFilterFactory.java deleted file mode 100644 index 2f684ddc..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RewritePathGatewayFilterFactory.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import java.util.Arrays; -import java.util.List; -import java.util.regex.Pattern; - -import reactor.core.publisher.Mono; - -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.GatewayFilterChain; -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.util.Assert; -import org.springframework.web.server.ServerWebExchange; - -import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.addOriginalRequestUrl; - -/** - * @author Spencer Gibb - */ -public class RewritePathGatewayFilterFactory - extends AbstractGatewayFilterFactory { - - /** - * Regexp key. - */ - public static final String REGEXP_KEY = "regexp"; - - /** - * Replacement key. - */ - public static final String REPLACEMENT_KEY = "replacement"; - - public RewritePathGatewayFilterFactory() { - super(Config.class); - } - - @Override - public List shortcutFieldOrder() { - return Arrays.asList(REGEXP_KEY, REPLACEMENT_KEY); - } - - @Override - public GatewayFilter apply(Config config) { - String replacement = config.replacement.replace("$\\", "$"); - Pattern pattern = Pattern.compile(config.regexp); - return new GatewayFilter() { - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - ServerHttpRequest req = exchange.getRequest(); - addOriginalRequestUrl(exchange, req.getURI()); - String path = req.getURI().getRawPath(); - String newPath = pattern.matcher(path).replaceAll(replacement); - - ServerHttpRequest request = req.mutate().path(newPath).build(); - - exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, request.getURI()); - - return chain.filter(exchange.mutate().request(request).build()); - } - - @Override - public String toString() { - return filterToStringCreator(RewritePathGatewayFilterFactory.this) - .append(config.getRegexp(), replacement).toString(); - } - }; - } - - public static class Config { - - private String regexp; - - private String replacement; - - public String getRegexp() { - return regexp; - } - - public Config setRegexp(String regexp) { - Assert.hasText(regexp, "regexp must have a value"); - this.regexp = regexp; - return this; - } - - public String getReplacement() { - return replacement; - } - - public Config setReplacement(String replacement) { - Assert.notNull(replacement, "replacement must not be null"); - this.replacement = replacement; - return this; - } - - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RewriteResponseHeaderGatewayFilterFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RewriteResponseHeaderGatewayFilterFactory.java deleted file mode 100644 index 92cedef1..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/RewriteResponseHeaderGatewayFilterFactory.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import reactor.core.publisher.Mono; - -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.GatewayFilterChain; -import org.springframework.http.HttpHeaders; -import org.springframework.web.server.ServerWebExchange; - -import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator; - -/** - * @author Vitaliy Pavlyuk - */ -public class RewriteResponseHeaderGatewayFilterFactory - extends AbstractGatewayFilterFactory { - - /** - * Regexp key. - */ - public static final String REGEXP_KEY = "regexp"; - - /** - * Replacement key. - */ - public static final String REPLACEMENT_KEY = "replacement"; - - public RewriteResponseHeaderGatewayFilterFactory() { - super(Config.class); - } - - @Override - public List shortcutFieldOrder() { - return Arrays.asList(NAME_KEY, REGEXP_KEY, REPLACEMENT_KEY); - } - - @Override - public GatewayFilter apply(Config config) { - return new GatewayFilter() { - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - return chain.filter(exchange).then(Mono.fromRunnable(() -> rewriteHeaders(exchange, config))); - } - - @Override - public String toString() { - return filterToStringCreator(RewriteResponseHeaderGatewayFilterFactory.this) - .append("name", config.getName()).append("regexp", config.getRegexp()) - .append("replacement", config.getReplacement()).toString(); - } - }; - } - - @Deprecated - protected void rewriteHeader(ServerWebExchange exchange, Config config) { - rewriteHeaders(exchange, config); - } - - protected void rewriteHeaders(ServerWebExchange exchange, Config config) { - final String name = config.getName(); - final HttpHeaders responseHeaders = exchange.getResponse().getHeaders(); - responseHeaders.computeIfPresent(name, (k, v) -> rewriteHeaders(config, v)); - } - - protected List rewriteHeaders(Config config, List headers) { - ArrayList rewrittenHeaders = new ArrayList<>(); - for (int i = 0; i < headers.size(); i++) { - String rewriten = rewrite(headers.get(i), config.getRegexp(), config.getReplacement()); - rewrittenHeaders.add(rewriten); - } - return rewrittenHeaders; - } - - String rewrite(String value, String regexp, String replacement) { - return value.replaceAll(regexp, replacement.replace("$\\", "$")); - } - - public static class Config extends AbstractGatewayFilterFactory.NameConfig { - - private String regexp; - - private String replacement; - - public String getRegexp() { - return regexp; - } - - public Config setRegexp(String regexp) { - this.regexp = regexp; - return this; - } - - public String getReplacement() { - return replacement; - } - - public Config setReplacement(String replacement) { - this.replacement = replacement; - return this; - } - - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/SaveSessionGatewayFilterFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/SaveSessionGatewayFilterFactory.java deleted file mode 100644 index 3ad4b14a..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/SaveSessionGatewayFilterFactory.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import reactor.core.publisher.Mono; - -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.GatewayFilterChain; -import org.springframework.web.server.ServerWebExchange; -import org.springframework.web.server.WebSession; - -import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator; - -/** - * Save the current {@link WebSession} before executing the rest of the - * {@link org.springframework.cloud.gateway.filter.GatewayFilterChain}. - * - * Filter is very useful for situation where the WebSession is lazy (e.g. Spring Session - * MongoDB) and making a remote call requires that {@link WebSession#save()} be called - * before the remote call is made. - * - * @author Greg Turnquist - */ -public class SaveSessionGatewayFilterFactory extends AbstractGatewayFilterFactory { - - @Override - public GatewayFilter apply(Object config) { - return new GatewayFilter() { - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - return exchange.getSession().map(WebSession::save).then(chain.filter(exchange)); - } - - @Override - public String toString() { - return filterToStringCreator(SaveSessionGatewayFilterFactory.this).toString(); - } - }; - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/SecureHeadersGatewayFilterFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/SecureHeadersGatewayFilterFactory.java deleted file mode 100644 index 46d7e780..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/SecureHeadersGatewayFilterFactory.java +++ /dev/null @@ -1,271 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import java.util.List; - -import reactor.core.publisher.Mono; - -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.GatewayFilterChain; -import org.springframework.http.HttpHeaders; -import org.springframework.web.server.ServerWebExchange; - -import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator; - -/** - * https://blog.appcanary.com/2017/http-security-headers.html. - * - * @author Spencer Gibb, Thirunavukkarasu Ravichandran - */ -public class SecureHeadersGatewayFilterFactory - extends AbstractGatewayFilterFactory { - - /** - * Xss-Protection header name. - */ - public static final String X_XSS_PROTECTION_HEADER = "X-Xss-Protection"; - - /** - * Strict transport security header name. - */ - public static final String STRICT_TRANSPORT_SECURITY_HEADER = "Strict-Transport-Security"; - - /** - * Frame options header name. - */ - public static final String X_FRAME_OPTIONS_HEADER = "X-Frame-Options"; - - /** - * Content-Type Options header name. - */ - public static final String X_CONTENT_TYPE_OPTIONS_HEADER = "X-Content-Type-Options"; - - /** - * Referrer Policy header name. - */ - public static final String REFERRER_POLICY_HEADER = "Referrer-Policy"; - - /** - * Content-Security Policy header name. - */ - public static final String CONTENT_SECURITY_POLICY_HEADER = "Content-Security-Policy"; - - /** - * Download Options header name. - */ - public static final String X_DOWNLOAD_OPTIONS_HEADER = "X-Download-Options"; - - /** - * Permitted Cross-Domain Policies header name. - */ - public static final String X_PERMITTED_CROSS_DOMAIN_POLICIES_HEADER = "X-Permitted-Cross-Domain-Policies"; - - private final SecureHeadersProperties properties; - - public SecureHeadersGatewayFilterFactory(SecureHeadersProperties properties) { - super(Config.class); - this.properties = properties; - } - - @Override - public GatewayFilter apply(Config originalConfig) { - return new GatewayFilter() { - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - HttpHeaders headers = exchange.getResponse().getHeaders(); - - List disabled = properties.getDisable(); - Config config = originalConfig.withDefaults(properties); - - return chain.filter(exchange).then(Mono.fromRunnable(() -> { - if (isEnabled(disabled, X_XSS_PROTECTION_HEADER)) { - headers.addIfAbsent(X_XSS_PROTECTION_HEADER, config.getXssProtectionHeader()); - } - - if (isEnabled(disabled, STRICT_TRANSPORT_SECURITY_HEADER)) { - headers.addIfAbsent(STRICT_TRANSPORT_SECURITY_HEADER, config.getStrictTransportSecurity()); - } - - if (isEnabled(disabled, X_FRAME_OPTIONS_HEADER)) { - headers.addIfAbsent(X_FRAME_OPTIONS_HEADER, config.getFrameOptions()); - } - - if (isEnabled(disabled, X_CONTENT_TYPE_OPTIONS_HEADER)) { - headers.addIfAbsent(X_CONTENT_TYPE_OPTIONS_HEADER, config.getContentTypeOptions()); - } - - if (isEnabled(disabled, REFERRER_POLICY_HEADER)) { - headers.addIfAbsent(REFERRER_POLICY_HEADER, config.getReferrerPolicy()); - } - - if (isEnabled(disabled, CONTENT_SECURITY_POLICY_HEADER)) { - headers.addIfAbsent(CONTENT_SECURITY_POLICY_HEADER, config.getContentSecurityPolicy()); - } - - if (isEnabled(disabled, X_DOWNLOAD_OPTIONS_HEADER)) { - headers.addIfAbsent(X_DOWNLOAD_OPTIONS_HEADER, config.getDownloadOptions()); - } - - if (isEnabled(disabled, X_PERMITTED_CROSS_DOMAIN_POLICIES_HEADER)) { - headers.addIfAbsent(X_PERMITTED_CROSS_DOMAIN_POLICIES_HEADER, - config.getPermittedCrossDomainPolicies()); - } - })); - } - - @Override - public String toString() { - return filterToStringCreator(SecureHeadersGatewayFilterFactory.this).toString(); - } - }; - } - - private boolean isEnabled(List disabledHeaders, String header) { - return !disabledHeaders.contains(header.toLowerCase()); - } - - public static class Config { - - private String xssProtectionHeader; - - private String strictTransportSecurity; - - private String frameOptions; - - private String contentTypeOptions; - - private String referrerPolicy; - - private String contentSecurityPolicy; - - private String downloadOptions; - - private String permittedCrossDomainPolicies; - - public Config withDefaults(SecureHeadersProperties properties) { - Config config = new Config(); - config.setXssProtectionHeader(xssProtectionHeader); - config.setStrictTransportSecurity(strictTransportSecurity); - config.setFrameOptions(frameOptions); - config.setContentTypeOptions(contentTypeOptions); - config.setReferrerPolicy(referrerPolicy); - config.setContentSecurityPolicy(contentSecurityPolicy); - config.setDownloadOptions(downloadOptions); - config.setPermittedCrossDomainPolicies(permittedCrossDomainPolicies); - - if (config.xssProtectionHeader == null) { - config.xssProtectionHeader = properties.getXssProtectionHeader(); - } - - if (config.strictTransportSecurity == null) { - config.strictTransportSecurity = properties.getStrictTransportSecurity(); - } - - if (config.frameOptions == null) { - config.frameOptions = properties.getFrameOptions(); - } - - if (config.contentTypeOptions == null) { - config.contentTypeOptions = properties.getContentTypeOptions(); - } - - if (config.referrerPolicy == null) { - config.referrerPolicy = properties.getReferrerPolicy(); - } - - if (config.contentSecurityPolicy == null) { - config.contentSecurityPolicy = properties.getContentSecurityPolicy(); - } - - if (config.downloadOptions == null) { - config.downloadOptions = properties.getDownloadOptions(); - } - - if (config.permittedCrossDomainPolicies == null) { - config.permittedCrossDomainPolicies = properties.getPermittedCrossDomainPolicies(); - } - return config; - } - - public String getXssProtectionHeader() { - return xssProtectionHeader; - } - - public void setXssProtectionHeader(String xssProtectionHeader) { - this.xssProtectionHeader = xssProtectionHeader; - } - - public String getStrictTransportSecurity() { - return strictTransportSecurity; - } - - public void setStrictTransportSecurity(String strictTransportSecurity) { - this.strictTransportSecurity = strictTransportSecurity; - } - - public String getFrameOptions() { - return frameOptions; - } - - public void setFrameOptions(String frameOptions) { - this.frameOptions = frameOptions; - } - - public String getContentTypeOptions() { - return contentTypeOptions; - } - - public void setContentTypeOptions(String contentTypeOptions) { - this.contentTypeOptions = contentTypeOptions; - } - - public String getReferrerPolicy() { - return referrerPolicy; - } - - public void setReferrerPolicy(String referrerPolicy) { - this.referrerPolicy = referrerPolicy; - } - - public String getContentSecurityPolicy() { - return contentSecurityPolicy; - } - - public void setContentSecurityPolicy(String contentSecurityPolicy) { - this.contentSecurityPolicy = contentSecurityPolicy; - } - - public String getDownloadOptions() { - return downloadOptions; - } - - public void setDownloadOptions(String downloadOptions) { - this.downloadOptions = downloadOptions; - } - - public String getPermittedCrossDomainPolicies() { - return permittedCrossDomainPolicies; - } - - public void setPermittedCrossDomainPolicies(String permittedCrossDomainPolicies) { - this.permittedCrossDomainPolicies = permittedCrossDomainPolicies; - } - - } - -} 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 deleted file mode 100644 index 6802575a..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/SecureHeadersProperties.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import java.util.ArrayList; -import java.util.List; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -/** - * @author Spencer Gibb, Thirunavukkarasu Ravichandran - */ -@ConfigurationProperties("spring.cloud.gateway.filter.secure-headers") -public class SecureHeadersProperties { - - /** - * Xss-Protection header default. - */ - public static final String X_XSS_PROTECTION_HEADER_DEFAULT = "1 ; mode=block"; - - /** - * Strict transport security header default. - */ - public static final String STRICT_TRANSPORT_SECURITY_HEADER_DEFAULT = "max-age=631138519"; - - /** - * Frame Options header default. - */ - public static final String X_FRAME_OPTIONS_HEADER_DEFAULT = "DENY"; - - /** - * Content-Type Options header default. - */ - public static final String X_CONTENT_TYPE_OPTIONS_HEADER_DEFAULT = "nosniff"; - - /** - * Referrer Policy header default. - */ - public static final String REFERRER_POLICY_HEADER_DEFAULT = "no-referrer"; - - /** - * Content-Security Policy header default. - */ - public static final String CONTENT_SECURITY_POLICY_HEADER_DEFAULT = "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'"; - - /** - * Download Options header default. - */ - public static final String X_DOWNLOAD_OPTIONS_HEADER_DEFAULT = "noopen"; - - /** - * Permitted Cross-Domain Policies header default. - */ - public static final String X_PERMITTED_CROSS_DOMAIN_POLICIES_HEADER_DEFAULT = "none"; - - private String xssProtectionHeader = X_XSS_PROTECTION_HEADER_DEFAULT; - - private String strictTransportSecurity = STRICT_TRANSPORT_SECURITY_HEADER_DEFAULT; - - private String frameOptions = X_FRAME_OPTIONS_HEADER_DEFAULT; - - private String contentTypeOptions = X_CONTENT_TYPE_OPTIONS_HEADER_DEFAULT; - - private String referrerPolicy = REFERRER_POLICY_HEADER_DEFAULT; - - private String contentSecurityPolicy = CONTENT_SECURITY_POLICY_HEADER_DEFAULT; - - private String downloadOptions = X_DOWNLOAD_OPTIONS_HEADER_DEFAULT; - - private String permittedCrossDomainPolicies = X_PERMITTED_CROSS_DOMAIN_POLICIES_HEADER_DEFAULT; - - private List disable = new ArrayList<>(); - - public String getXssProtectionHeader() { - return xssProtectionHeader; - } - - public void setXssProtectionHeader(String xssProtectionHeader) { - this.xssProtectionHeader = xssProtectionHeader; - } - - public String getStrictTransportSecurity() { - return strictTransportSecurity; - } - - public void setStrictTransportSecurity(String strictTransportSecurity) { - this.strictTransportSecurity = strictTransportSecurity; - } - - public String getFrameOptions() { - return frameOptions; - } - - public void setFrameOptions(String frameOptions) { - this.frameOptions = frameOptions; - } - - public String getContentTypeOptions() { - return contentTypeOptions; - } - - public void setContentTypeOptions(String contentTypeOptions) { - this.contentTypeOptions = contentTypeOptions; - } - - public String getReferrerPolicy() { - return referrerPolicy; - } - - public void setReferrerPolicy(String referrerPolicy) { - this.referrerPolicy = referrerPolicy; - } - - public String getContentSecurityPolicy() { - return contentSecurityPolicy; - } - - public void setContentSecurityPolicy(String contentSecurityPolicy) { - this.contentSecurityPolicy = contentSecurityPolicy; - } - - public String getDownloadOptions() { - return downloadOptions; - } - - public void setDownloadOptions(String downloadOptions) { - this.downloadOptions = downloadOptions; - } - - public String getPermittedCrossDomainPolicies() { - return permittedCrossDomainPolicies; - } - - public void setPermittedCrossDomainPolicies(String permittedCrossDomainPolicies) { - this.permittedCrossDomainPolicies = permittedCrossDomainPolicies; - } - - public List getDisable() { - return disable; - } - - public void setDisable(List disable) { - this.disable = disable; - } - - @Override - public String toString() { - final StringBuffer sb = new StringBuffer("SecureHeadersProperties{"); - sb.append("xssProtectionHeader='").append(xssProtectionHeader).append('\''); - sb.append(", strictTransportSecurity='").append(strictTransportSecurity).append('\''); - sb.append(", frameOptions='").append(frameOptions).append('\''); - sb.append(", contentTypeOptions='").append(contentTypeOptions).append('\''); - sb.append(", referrerPolicy='").append(referrerPolicy).append('\''); - sb.append(", contentSecurityPolicy='").append(contentSecurityPolicy).append('\''); - sb.append(", downloadOptions='").append(downloadOptions).append('\''); - sb.append(", permittedCrossDomainPolicies='").append(permittedCrossDomainPolicies).append('\''); - sb.append(", disabled='").append(disable).append('\''); - sb.append('}'); - return sb.toString(); - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/SetPathGatewayFilterFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/SetPathGatewayFilterFactory.java deleted file mode 100644 index 105d28d9..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/SetPathGatewayFilterFactory.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import java.net.URI; -import java.util.Arrays; -import java.util.List; -import java.util.Map; - -import reactor.core.publisher.Mono; - -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.GatewayFilterChain; -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.web.server.ServerWebExchange; -import org.springframework.web.util.UriTemplate; - -import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.addOriginalRequestUrl; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.getUriTemplateVariables; - -/** - * @author Spencer Gibb - */ -public class SetPathGatewayFilterFactory extends AbstractGatewayFilterFactory { - - /** - * Template key. - */ - public static final String TEMPLATE_KEY = "template"; - - public SetPathGatewayFilterFactory() { - super(Config.class); - } - - @Override - public List shortcutFieldOrder() { - return Arrays.asList(TEMPLATE_KEY); - } - - @Override - public GatewayFilter apply(Config config) { - UriTemplate uriTemplate = new UriTemplate(config.template); - - return new GatewayFilter() { - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - ServerHttpRequest req = exchange.getRequest(); - addOriginalRequestUrl(exchange, req.getURI()); - - Map uriVariables = getUriTemplateVariables(exchange); - - URI uri = uriTemplate.expand(uriVariables); - String newPath = uri.getRawPath(); - - exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, uri); - - ServerHttpRequest request = req.mutate().path(newPath).build(); - - return chain.filter(exchange.mutate().request(request).build()); - } - - @Override - public String toString() { - return filterToStringCreator(SetPathGatewayFilterFactory.this).append("template", config.getTemplate()) - .toString(); - } - }; - } - - public static class Config { - - private String template; - - public String getTemplate() { - return template; - } - - public void setTemplate(String template) { - this.template = template; - } - - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/SetRequestHeaderGatewayFilterFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/SetRequestHeaderGatewayFilterFactory.java deleted file mode 100644 index 3874c47f..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/SetRequestHeaderGatewayFilterFactory.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import reactor.core.publisher.Mono; - -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.GatewayFilterChain; -import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.web.server.ServerWebExchange; - -import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator; - -/** - * @author Spencer Gibb - */ -public class SetRequestHeaderGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory { - - @Override - public GatewayFilter apply(NameValueConfig config) { - return new GatewayFilter() { - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - String value = ServerWebExchangeUtils.expand(exchange, config.getValue()); - ServerHttpRequest request = exchange.getRequest().mutate() - .headers(httpHeaders -> httpHeaders.set(config.name, value)).build(); - - return chain.filter(exchange.mutate().request(request).build()); - } - - @Override - public String toString() { - return filterToStringCreator(SetRequestHeaderGatewayFilterFactory.this) - .append(config.getName(), config.getValue()).toString(); - } - }; - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/SetRequestHostHeaderGatewayFilterFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/SetRequestHostHeaderGatewayFilterFactory.java deleted file mode 100644 index 463553c8..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/SetRequestHostHeaderGatewayFilterFactory.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import java.util.Collections; -import java.util.List; - -import reactor.core.publisher.Mono; - -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.GatewayFilterChain; -import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.web.server.ServerWebExchange; - -import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.PRESERVE_HOST_HEADER_ATTRIBUTE; - -/** - * @author Andrew Fitzgerald - */ -public class SetRequestHostHeaderGatewayFilterFactory - extends AbstractGatewayFilterFactory { - - public SetRequestHostHeaderGatewayFilterFactory() { - super(Config.class); - } - - @Override - public List shortcutFieldOrder() { - return Collections.singletonList("host"); - } - - @Override - public GatewayFilter apply(Config config) { - return new GatewayFilter() { - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - String value = ServerWebExchangeUtils.expand(exchange, config.getHost()); - - ServerHttpRequest request = exchange.getRequest().mutate().headers(httpHeaders -> { - httpHeaders.remove("Host"); - httpHeaders.add("Host", value); - }).build(); - - // Make sure the header we just set is preserved - exchange.getAttributes().put(PRESERVE_HOST_HEADER_ATTRIBUTE, true); - - return chain.filter(exchange.mutate().request(request).build()); - } - - @Override - public String toString() { - return filterToStringCreator(SetRequestHostHeaderGatewayFilterFactory.this).append(config.getHost()) - .toString(); - } - }; - } - - public static class Config { - - private String host; - - public String getHost() { - return host; - } - - public void setHost(String host) { - this.host = host; - } - - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/SetResponseHeaderGatewayFilterFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/SetResponseHeaderGatewayFilterFactory.java deleted file mode 100644 index a5a29018..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/SetResponseHeaderGatewayFilterFactory.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import reactor.core.publisher.Mono; - -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.GatewayFilterChain; -import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; -import org.springframework.web.server.ServerWebExchange; - -import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator; - -/** - * @author Spencer Gibb - */ -public class SetResponseHeaderGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory { - - @Override - public GatewayFilter apply(NameValueConfig config) { - return new GatewayFilter() { - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - String value = ServerWebExchangeUtils.expand(exchange, config.getValue()); - return chain.filter(exchange) - .then(Mono.fromRunnable(() -> exchange.getResponse().getHeaders().set(config.name, value))); - } - - @Override - public String toString() { - return filterToStringCreator(SetResponseHeaderGatewayFilterFactory.this) - .append(config.getName(), config.getValue()).toString(); - } - }; - } - -} 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 deleted file mode 100644 index bf99b71d..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/SetStatusGatewayFilterFactory.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import java.util.Arrays; -import java.util.List; - -import reactor.core.publisher.Mono; - -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.GatewayFilterChain; -import org.springframework.cloud.gateway.support.HttpStatusHolder; -import org.springframework.http.HttpStatusCode; -import org.springframework.web.server.ServerWebExchange; - -import static java.util.Collections.singletonList; -import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.setResponseStatus; - -/** - * @author Spencer Gibb - */ -@ConfigurationProperties("spring.cloud.gateway.set-status") -public class SetStatusGatewayFilterFactory extends AbstractGatewayFilterFactory { - - /** - * Status key. - */ - public static final String STATUS_KEY = "status"; - - /** - * The name of the header which contains http code of the proxied request. - */ - private String originalStatusHeaderName; - - public SetStatusGatewayFilterFactory() { - super(Config.class); - } - - @Override - public List shortcutFieldOrder() { - return Arrays.asList(STATUS_KEY); - } - - @Override - public GatewayFilter apply(Config config) { - HttpStatusHolder statusHolder = HttpStatusHolder.parse(config.status); - - return new GatewayFilter() { - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - // option 1 (runs in filter order) - /* - * exchange.getResponse().beforeCommit(() -> { - * exchange.getResponse().setStatusCode(finalStatus); return Mono.empty(); - * }); return chain.filter(exchange); - */ - - // option 2 (runs in reverse filter order) - return chain.filter(exchange).then(Mono.fromRunnable(() -> { - // check not really needed, since it is guarded in setStatusCode, - // but it's a good example - HttpStatusCode statusCode = exchange.getResponse().getStatusCode(); - boolean isStatusCodeUpdated = setResponseStatus(exchange, statusHolder); - if (isStatusCodeUpdated && originalStatusHeaderName != null) { - exchange.getResponse().getHeaders().set(originalStatusHeaderName, - singletonList(statusCode.value()).toString()); - } - })); - } - - @Override - public String toString() { - return filterToStringCreator(SetStatusGatewayFilterFactory.this).append("status", config.getStatus()) - .toString(); - } - }; - } - - public String getOriginalStatusHeaderName() { - return originalStatusHeaderName; - } - - public void setOriginalStatusHeaderName(String originalStatusHeaderName) { - this.originalStatusHeaderName = originalStatusHeaderName; - } - - public static class Config { - - // TODO: relaxed HttpStatus converter - private String status; - - public String getStatus() { - return status; - } - - public void setStatus(String status) { - this.status = status; - } - - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/SpringCloudCircuitBreakerFilterFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/SpringCloudCircuitBreakerFilterFactory.java deleted file mode 100644 index 58b0fd46..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/SpringCloudCircuitBreakerFilterFactory.java +++ /dev/null @@ -1,238 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import java.net.URI; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; - -import reactor.core.publisher.Mono; - -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.cloud.client.circuitbreaker.ReactiveCircuitBreaker; -import org.springframework.cloud.client.circuitbreaker.ReactiveCircuitBreakerFactory; -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.GatewayFilterChain; -import org.springframework.cloud.gateway.support.HasRouteId; -import org.springframework.cloud.gateway.support.HttpStatusHolder; -import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; -import org.springframework.http.HttpStatus; -import org.springframework.http.HttpStatusCode; -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.util.StringUtils; -import org.springframework.web.client.HttpStatusCodeException; -import org.springframework.web.reactive.DispatcherHandler; -import org.springframework.web.server.ServerWebExchange; -import org.springframework.web.util.UriComponentsBuilder; - -import static java.util.Collections.singletonList; -import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.CIRCUITBREAKER_EXECUTION_EXCEPTION_ATTR; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.containsEncodedParts; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.handle; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.reset; - -/** - * @author Ryan Baxter - */ -public abstract class SpringCloudCircuitBreakerFilterFactory - extends AbstractGatewayFilterFactory { - - /** CircuitBreaker component name. */ - public static final String NAME = "CircuitBreaker"; - - private ReactiveCircuitBreakerFactory reactiveCircuitBreakerFactory; - - private ReactiveCircuitBreaker cb; - - private final ObjectProvider dispatcherHandlerProvider; - - // do not use this dispatcherHandler directly, use getDispatcherHandler() instead. - private volatile DispatcherHandler dispatcherHandler; - - public SpringCloudCircuitBreakerFilterFactory(ReactiveCircuitBreakerFactory reactiveCircuitBreakerFactory, - ObjectProvider dispatcherHandlerProvider) { - super(Config.class); - this.reactiveCircuitBreakerFactory = reactiveCircuitBreakerFactory; - this.dispatcherHandlerProvider = dispatcherHandlerProvider; - } - - private DispatcherHandler getDispatcherHandler() { - if (dispatcherHandler == null) { - dispatcherHandler = dispatcherHandlerProvider.getIfAvailable(); - } - - return dispatcherHandler; - } - - @Override - public List shortcutFieldOrder() { - return singletonList(NAME_KEY); - } - - @Override - public GatewayFilter apply(Config config) { - ReactiveCircuitBreaker cb = reactiveCircuitBreakerFactory.create(config.getId()); - Set statuses = config.getStatusCodes().stream().map(HttpStatusHolder::parse) - .filter(statusHolder -> statusHolder.getHttpStatus() != null).map(HttpStatusHolder::getHttpStatus) - .collect(Collectors.toSet()); - - return new GatewayFilter() { - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - return cb.run(chain.filter(exchange).doOnSuccess(v -> { - if (statuses.contains(exchange.getResponse().getStatusCode())) { - HttpStatusCode status = exchange.getResponse().getStatusCode(); - throw new CircuitBreakerStatusCodeException(status); - } - }), t -> { - if (config.getFallbackUri() == null) { - return Mono.error(t); - } - - exchange.getResponse().setStatusCode(null); - - // TODO: copied from RouteToRequestUrlFilter - URI uri = exchange.getRequest().getURI(); - // TODO: assume always? - boolean encoded = containsEncodedParts(uri); - - String expandedFallbackUri = ServerWebExchangeUtils.expand(exchange, - config.getFallbackUri().getPath()); - String fullFallbackUri = String.format("%s:%s", config.getFallbackUri().getScheme(), - expandedFallbackUri); - URI requestUrl = UriComponentsBuilder.fromUri(uri).host(null).port(null) - .uri(URI.create(fullFallbackUri)).scheme(null).build(encoded).toUri(); - - exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUrl); - addExceptionDetails(t, exchange); - - // Reset the exchange - reset(exchange); - - ServerHttpRequest request = exchange.getRequest().mutate().uri(requestUrl).build(); - return handle(getDispatcherHandler(), exchange.mutate().request(request).build()); - }).onErrorResume(t -> handleErrorWithoutFallback(t, config.isResumeWithoutError())); - } - - @Override - public String toString() { - return filterToStringCreator(SpringCloudCircuitBreakerFilterFactory.this) - .append("name", config.getName()).append("fallback", config.fallbackUri).toString(); - } - }; - } - - protected abstract Mono handleErrorWithoutFallback(Throwable t, boolean resumeWithoutError); - - private void addExceptionDetails(Throwable t, ServerWebExchange exchange) { - if (t != null) { - exchange.getAttributes().put(CIRCUITBREAKER_EXECUTION_EXCEPTION_ATTR, t); - } - } - - @Override - public String name() { - return NAME; - } - - public static class Config implements HasRouteId { - - private String name; - - private URI fallbackUri; - - private String routeId; - - private Set statusCodes = new HashSet<>(); - - private boolean resumeWithoutError = false; - - @Override - public void setRouteId(String routeId) { - this.routeId = routeId; - } - - public String getRouteId() { - return routeId; - } - - public URI getFallbackUri() { - return fallbackUri; - } - - public Config setFallbackUri(URI fallbackUri) { - this.fallbackUri = fallbackUri; - return this; - } - - public Config setFallbackUri(String fallbackUri) { - return setFallbackUri(URI.create(fallbackUri)); - } - - public String getName() { - return name; - } - - public Config setName(String name) { - this.name = name; - return this; - } - - public String getId() { - if (!StringUtils.hasText(name) && StringUtils.hasText(routeId)) { - return routeId; - } - return name; - } - - public Set getStatusCodes() { - return statusCodes; - } - - public Config setStatusCodes(Set statusCodes) { - this.statusCodes = statusCodes; - return this; - } - - public Config addStatusCode(String statusCode) { - this.statusCodes.add(statusCode); - return this; - } - - public boolean isResumeWithoutError() { - return resumeWithoutError; - } - - public void setResumeWithoutError(boolean resumeWithoutError) { - this.resumeWithoutError = resumeWithoutError; - } - - } - - public class CircuitBreakerStatusCodeException extends HttpStatusCodeException { - - public CircuitBreakerStatusCodeException(HttpStatusCode statusCode) { - super(statusCode); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/SpringCloudCircuitBreakerResilience4JFilterFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/SpringCloudCircuitBreakerResilience4JFilterFactory.java deleted file mode 100644 index aebb2598..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/SpringCloudCircuitBreakerResilience4JFilterFactory.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import io.github.resilience4j.circuitbreaker.CallNotPermittedException; -import reactor.core.publisher.Mono; - -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.cloud.client.circuitbreaker.ReactiveCircuitBreakerFactory; -import org.springframework.cloud.gateway.support.ServiceUnavailableException; -import org.springframework.http.HttpStatus; -import org.springframework.web.reactive.DispatcherHandler; -import org.springframework.web.server.ResponseStatusException; - -/** - * @author Ryan Baxter - */ -public class SpringCloudCircuitBreakerResilience4JFilterFactory extends SpringCloudCircuitBreakerFilterFactory { - - public SpringCloudCircuitBreakerResilience4JFilterFactory( - ReactiveCircuitBreakerFactory reactiveCircuitBreakerFactory, - ObjectProvider dispatcherHandlerProvider) { - super(reactiveCircuitBreakerFactory, dispatcherHandlerProvider); - } - - @Override - protected Mono handleErrorWithoutFallback(Throwable t, boolean resumeWithoutError) { - if (java.util.concurrent.TimeoutException.class.isInstance(t)) { - return Mono.error(new ResponseStatusException(HttpStatus.GATEWAY_TIMEOUT, t.getMessage(), t)); - } - if (CallNotPermittedException.class.isInstance(t)) { - return Mono.error(new ServiceUnavailableException()); - } - if (resumeWithoutError) { - return Mono.empty(); - } - return Mono.error(t); - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/StripPrefixGatewayFilterFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/StripPrefixGatewayFilterFactory.java deleted file mode 100644 index 541568ae..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/StripPrefixGatewayFilterFactory.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import java.util.Arrays; -import java.util.List; - -import reactor.core.publisher.Mono; - -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.GatewayFilterChain; -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.util.StringUtils; -import org.springframework.web.server.ServerWebExchange; - -import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.addOriginalRequestUrl; - -/** - * This filter removes the first part of the path, known as the prefix, from the request - * before sending it downstream. - * - * @author Ryan Baxter - */ -public class StripPrefixGatewayFilterFactory - extends AbstractGatewayFilterFactory { - - /** - * Parts key. - */ - public static final String PARTS_KEY = "parts"; - - public StripPrefixGatewayFilterFactory() { - super(Config.class); - } - - @Override - public List shortcutFieldOrder() { - return Arrays.asList(PARTS_KEY); - } - - @Override - public GatewayFilter apply(Config config) { - return new GatewayFilter() { - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - ServerHttpRequest request = exchange.getRequest(); - addOriginalRequestUrl(exchange, request.getURI()); - String path = request.getURI().getRawPath(); - String[] originalParts = StringUtils.tokenizeToStringArray(path, "/"); - - // all new paths start with / - StringBuilder newPath = new StringBuilder("/"); - for (int i = 0; i < originalParts.length; i++) { - if (i >= config.getParts()) { - // only append slash if this is the second part or greater - if (newPath.length() > 1) { - newPath.append('/'); - } - newPath.append(originalParts[i]); - } - } - if (newPath.length() > 1 && path.endsWith("/")) { - newPath.append('/'); - } - - ServerHttpRequest newRequest = request.mutate().path(newPath.toString()).build(); - - exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, newRequest.getURI()); - - return chain.filter(exchange.mutate().request(newRequest).build()); - } - - @Override - public String toString() { - return filterToStringCreator(StripPrefixGatewayFilterFactory.this).append("parts", config.getParts()) - .toString(); - } - }; - } - - public static class Config { - - private int parts = 1; - - public int getParts() { - return parts; - } - - public void setParts(int parts) { - this.parts = parts; - } - - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/TokenRelayGatewayFilterFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/TokenRelayGatewayFilterFactory.java deleted file mode 100644 index 19ae95d6..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/TokenRelayGatewayFilterFactory.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright 2002-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.filter.factory; - -import java.util.Collections; -import java.util.List; - -import reactor.core.publisher.Mono; - -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest; -import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; -import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientManager; -import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.web.server.ServerWebExchange; - -/** - * @author Joe Grandja - * @author Steve Riesenberg - */ -public class TokenRelayGatewayFilterFactory - extends AbstractGatewayFilterFactory { - - private final ObjectProvider clientManagerProvider; - - public TokenRelayGatewayFilterFactory(ObjectProvider clientManagerProvider) { - super(NameConfig.class); - this.clientManagerProvider = clientManagerProvider; - } - - @Override - public List shortcutFieldOrder() { - return Collections.singletonList(NAME_KEY); - } - - public GatewayFilter apply() { - return apply((NameConfig) null); - } - - @Override - public GatewayFilter apply(NameConfig config) { - String defaultClientRegistrationId = (config == null) ? null : config.getName(); - return (exchange, chain) -> exchange.getPrincipal() - // .log("token-relay-filter") - .filter(principal -> principal instanceof Authentication).cast(Authentication.class) - .flatMap(principal -> authorizationRequest(defaultClientRegistrationId, principal)) - .flatMap(this::authorizedClient).map(OAuth2AuthorizedClient::getAccessToken) - .map(token -> withBearerAuth(exchange, token)) - // TODO: adjustable behavior if empty - .defaultIfEmpty(exchange).flatMap(chain::filter); - } - - private Mono authorizationRequest(String defaultClientRegistrationId, - Authentication principal) { - String clientRegistrationId = defaultClientRegistrationId; - if (clientRegistrationId == null && principal instanceof OAuth2AuthenticationToken) { - clientRegistrationId = ((OAuth2AuthenticationToken) principal).getAuthorizedClientRegistrationId(); - } - return Mono.justOrEmpty(clientRegistrationId).map(OAuth2AuthorizeRequest::withClientRegistrationId) - .map(builder -> builder.principal(principal).build()); - } - - private Mono authorizedClient(OAuth2AuthorizeRequest request) { - ReactiveOAuth2AuthorizedClientManager clientManager = clientManagerProvider.getIfAvailable(); - if (clientManager == null) { - return Mono.error(new IllegalStateException( - "No ReactiveOAuth2AuthorizedClientManager bean was found. Did you include the " - + "org.springframework.boot:spring-boot-starter-oauth2-client dependency?")); - } - // TODO: use Mono.defer() for request above? - return clientManager.authorize(request); - } - - private ServerWebExchange withBearerAuth(ServerWebExchange exchange, OAuth2AccessToken accessToken) { - return exchange.mutate().request(r -> r.headers(headers -> headers.setBearerAuth(accessToken.getTokenValue()))) - .build(); - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/CachedResponse.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/CachedResponse.java deleted file mode 100644 index fc7a78a1..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/CachedResponse.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright 2013-2022 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.filter.factory.cache; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.io.Serial; -import java.io.Serializable; -import java.nio.ByteBuffer; -import java.nio.channels.Channels; -import java.nio.charset.StandardCharsets; -import java.time.Instant; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Date; -import java.util.List; -import java.util.zip.GZIPInputStream; - -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatusCode; -import org.springframework.util.FileCopyUtils; - -/** - * @author Marta Medio - * @author Ignacio Lozano - */ -public final class CachedResponse implements Serializable { - - private HttpStatusCode statusCode; - - private HttpHeaders headers; - - private List body; - - private Date timestamp; - - private CachedResponse(HttpStatusCode statusCode, HttpHeaders headers, List body, Date timestamp) { - this.statusCode = statusCode; - this.headers = headers; - this.body = body; - this.timestamp = timestamp; - } - - @Serial - private void readObject(ObjectInputStream aInputStream) throws ClassNotFoundException, IOException { - statusCode = (HttpStatusCode) aInputStream.readObject(); - headers = (HttpHeaders) aInputStream.readObject(); - body = List.of(ByteBuffer.wrap(aInputStream.readAllBytes()).asReadOnlyBuffer()); - timestamp = (Date) aInputStream.readObject(); - } - - @Serial - private void writeObject(ObjectOutputStream aOutputStream) throws IOException { - aOutputStream.writeObject(statusCode); - aOutputStream.writeObject(headers); - aOutputStream.write(this.bodyAsByteArray()); - aOutputStream.writeObject(timestamp); - } - - public static Builder create(HttpStatusCode statusCode) { - return new Builder(statusCode); - } - - public HttpStatusCode statusCode() { - return this.statusCode; - } - - public HttpHeaders headers() { - return this.headers; - } - - public List body() { - return Collections.unmodifiableList(body); - } - - public Date timestamp() { - return this.timestamp; - } - - byte[] bodyAsByteArray() throws IOException { - var bodyStream = new ByteArrayOutputStream(); - var channel = Channels.newChannel(bodyStream); - for (ByteBuffer byteBuffer : body()) { - channel.write(byteBuffer); - } - return bodyStream.toByteArray(); - } - - String bodyAsString() throws IOException { - InputStream byteStream = new ByteArrayInputStream(bodyAsByteArray()); - if (headers.getOrEmpty(HttpHeaders.CONTENT_ENCODING).contains("gzip")) { - byteStream = new GZIPInputStream(byteStream); - } - return new String(FileCopyUtils.copyToByteArray(byteStream)); - } - - public static class Builder { - - private final HttpStatusCode statusCode; - - private final HttpHeaders headers = new HttpHeaders(); - - private final List body = new ArrayList<>(); - - private Instant timestamp; - - public Builder(HttpStatusCode statusCode) { - this.statusCode = statusCode; - } - - public Builder header(String name, String value) { - this.headers.add(name, value); - return this; - } - - public Builder headers(HttpHeaders headers) { - this.headers.addAll(headers); - return this; - } - - public Builder timestamp(Instant timestamp) { - this.timestamp = timestamp; - return this; - } - - public Builder timestamp(Date timestamp) { - this.timestamp = timestamp.toInstant(); - return this; - } - - public Builder body(String data) { - return appendToBody(ByteBuffer.wrap(data.getBytes(StandardCharsets.UTF_8))); - } - - public Builder appendToBody(ByteBuffer byteBuffer) { - this.body.add(byteBuffer); - return this; - } - - public CachedResponse build() { - return new CachedResponse(statusCode, headers, body, timestamp == null ? new Date() : Date.from(timestamp)); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/CachedResponseMetadata.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/CachedResponseMetadata.java deleted file mode 100644 index 8aa5ff93..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/CachedResponseMetadata.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2013-2022 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.filter.factory.cache; - -import java.io.Serializable; -import java.util.List; - -/** - * @author Marta Medio - * @author Ignacio Lozano - */ -public record CachedResponseMetadata(List varyOnHeaders) implements Serializable { -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/GlobalLocalResponseCacheGatewayFilter.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/GlobalLocalResponseCacheGatewayFilter.java deleted file mode 100644 index 03acc42a..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/GlobalLocalResponseCacheGatewayFilter.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory.cache; - -import java.time.Duration; - -import reactor.core.publisher.Mono; - -import org.springframework.cache.Cache; -import org.springframework.cloud.gateway.filter.GatewayFilterChain; -import org.springframework.cloud.gateway.filter.GlobalFilter; -import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter; -import org.springframework.core.Ordered; -import org.springframework.web.server.ServerWebExchange; - -import static org.springframework.cloud.gateway.filter.factory.cache.LocalResponseCacheGatewayFilterFactory.LOCAL_RESPONSE_CACHE_FILTER_APPLIED; - -/** - * Caches responses for routes that don't have the - * {@link LocalResponseCacheGatewayFilterFactory} configured. - * - * @author Ignacio Lozano - * @author Marta Medio - */ -public class GlobalLocalResponseCacheGatewayFilter implements GlobalFilter, Ordered { - - private final ResponseCacheGatewayFilter responseCacheGatewayFilter; - - public GlobalLocalResponseCacheGatewayFilter(ResponseCacheManagerFactory cacheManagerFactory, Cache globalCache, - Duration configuredTimeToLive) { - responseCacheGatewayFilter = new ResponseCacheGatewayFilter( - cacheManagerFactory.create(globalCache, configuredTimeToLive)); - } - - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - if (exchange.getAttributes().get(LOCAL_RESPONSE_CACHE_FILTER_APPLIED) == null) { - return responseCacheGatewayFilter.filter(exchange, chain); - } - return chain.filter(exchange); - } - - @Override - public int getOrder() { - return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 2; - } - -} 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 deleted file mode 100644 index cdeef696..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/LocalResponseCacheGatewayFilterFactory.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory.cache; - -import java.time.Duration; -import java.util.List; - -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.cache.Cache; -import org.springframework.cloud.gateway.config.LocalResponseCacheAutoConfiguration; -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; -import org.springframework.cloud.gateway.support.HasRouteId; -import org.springframework.util.unit.DataSize; -import org.springframework.validation.annotation.Validated; - -/** - * {@link org.springframework.cloud.gateway.filter.factory.GatewayFilterFactory} of - * {@link ResponseCacheGatewayFilter}. - * - * By default, a global cache (defined as properties in the application) is used. For - * specific route configuration, parameters can be added following - * {@link RouteCacheConfiguration} class. - * - * @author Marta Medio - * @author Ignacio Lozano - */ -@ConditionalOnProperty(value = "spring.cloud.gateway.filter.local-response-cache.enabled", havingValue = "true") -public class LocalResponseCacheGatewayFilterFactory - extends AbstractGatewayFilterFactory { - - /** - * Exchange attribute name to track if the request has been already process by cache - * at route filter level. - */ - public static final String LOCAL_RESPONSE_CACHE_FILTER_APPLIED = "LocalResponseCacheGatewayFilter-Applied"; - - private ResponseCacheManagerFactory cacheManagerFactory; - - private Duration defaultTimeToLive; - - private DataSize defaultSize; - - public LocalResponseCacheGatewayFilterFactory(ResponseCacheManagerFactory cacheManagerFactory, - Duration defaultTimeToLive) { - this(cacheManagerFactory, defaultTimeToLive, null); - } - - public LocalResponseCacheGatewayFilterFactory(ResponseCacheManagerFactory cacheManagerFactory, - Duration defaultTimeToLive, DataSize defaultSize) { - super(RouteCacheConfiguration.class); - this.cacheManagerFactory = cacheManagerFactory; - this.defaultTimeToLive = defaultTimeToLive; - this.defaultSize = defaultSize; - } - - @Override - public GatewayFilter apply(RouteCacheConfiguration config) { - LocalResponseCacheProperties cacheProperties = mapRouteCacheConfig(config); - - Cache routeCache = LocalResponseCacheAutoConfiguration.createGatewayCacheManager(cacheProperties) - .getCache(config.getRouteId() + "-cache"); - return new ResponseCacheGatewayFilter(cacheManagerFactory.create(routeCache, cacheProperties.getTimeToLive())); - - } - - private LocalResponseCacheProperties mapRouteCacheConfig(RouteCacheConfiguration config) { - Duration timeToLive = config.getTimeToLive() != null ? config.getTimeToLive() : defaultTimeToLive; - DataSize size = config.getSize() != null ? config.getSize() : defaultSize; - - LocalResponseCacheProperties responseCacheProperties = new LocalResponseCacheProperties(); - responseCacheProperties.setTimeToLive(timeToLive); - responseCacheProperties.setSize(size); - return responseCacheProperties; - } - - @Override - public List shortcutFieldOrder() { - return List.of("timeToLive", "size"); - } - - @Validated - public static class RouteCacheConfiguration implements HasRouteId { - - private DataSize size; - - private Duration timeToLive; - - private String routeId; - - public DataSize getSize() { - return size; - } - - public RouteCacheConfiguration setSize(DataSize size) { - this.size = size; - return this; - } - - public Duration getTimeToLive() { - return timeToLive; - } - - public RouteCacheConfiguration setTimeToLive(Duration timeToLive) { - this.timeToLive = timeToLive; - return this; - } - - @Override - public void setRouteId(String routeId) { - this.routeId = routeId; - } - - @Override - public String getRouteId() { - return this.routeId; - } - - } - -} 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 deleted file mode 100644 index 96e76da1..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/LocalResponseCacheProperties.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2013-2022 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.filter.factory.cache; - -import java.time.Duration; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.util.unit.DataSize; - -/** - * @author Ignacio Lozano - */ -@ConfigurationProperties(prefix = LocalResponseCacheProperties.PREFIX) -public class LocalResponseCacheProperties { - - static final String PREFIX = "spring.cloud.gateway.filter.local-response-cache"; - - private static final Log LOGGER = LogFactory.getLog(LocalResponseCacheProperties.class); - - private static final Duration DEFAULT_CACHE_TTL_SECONDS = Duration.ofMinutes(5); - - private DataSize size; - - private Duration timeToLive; - - public DataSize getSize() { - return size; - } - - public void setSize(DataSize size) { - this.size = size; - } - - public Duration getTimeToLive() { - if (timeToLive == null) { - LOGGER.debug(String.format( - "No TTL configuration found. Default TTL will be applied for cache entries: %s seconds", - DEFAULT_CACHE_TTL_SECONDS)); - return DEFAULT_CACHE_TTL_SECONDS; - } - else { - return timeToLive; - } - } - - public void setTimeToLive(Duration timeToLive) { - this.timeToLive = timeToLive; - } - - @Override - public String toString() { - return "LocalResponseCacheProperties{" + "timeToLive=" + getTimeToLive() + '\'' + ", size='" + getSize() + '}'; - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/ResponseCacheGatewayFilter.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/ResponseCacheGatewayFilter.java deleted file mode 100644 index a7769df1..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/ResponseCacheGatewayFilter.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2013-2022 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.filter.factory.cache; - -import java.util.Optional; - -import org.reactivestreams.Publisher; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.GatewayFilterChain; -import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter; -import org.springframework.core.Ordered; -import org.springframework.core.io.buffer.DataBuffer; -import org.springframework.http.server.reactive.ServerHttpResponse; -import org.springframework.http.server.reactive.ServerHttpResponseDecorator; -import org.springframework.web.server.ServerWebExchange; - -import static org.springframework.cloud.gateway.filter.factory.cache.LocalResponseCacheGatewayFilterFactory.LOCAL_RESPONSE_CACHE_FILTER_APPLIED; - -/** - * {@literal LocalResponseCache} Gateway Filter that stores HTTP Responses in a cache, so - * latency and upstream overhead is reduced. - * - * @author Marta Medio - * @author Ignacio Lozano - */ -public class ResponseCacheGatewayFilter implements GatewayFilter, Ordered { - - private final ResponseCacheManager responseCacheManager; - - public ResponseCacheGatewayFilter(ResponseCacheManager responseCacheManager) { - this.responseCacheManager = responseCacheManager; - } - - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - if (responseCacheManager.isRequestCacheable(exchange.getRequest())) { - exchange.getAttributes().put(LOCAL_RESPONSE_CACHE_FILTER_APPLIED, true); - return filterWithCache(exchange, chain); - } - else { - return chain.filter(exchange); - } - } - - @Override - public int getOrder() { - return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 3; - } - - private Mono filterWithCache(ServerWebExchange exchange, GatewayFilterChain chain) { - final String metadataKey = responseCacheManager.resolveMetadataKey(exchange); - Optional cached = responseCacheManager.getFromCache(exchange.getRequest(), metadataKey); - - if (cached.isPresent()) { - return responseCacheManager.processFromCache(exchange, metadataKey, cached.get()); - } - else { - return chain - .filter(exchange.mutate().response(new CachingResponseDecorator(metadataKey, exchange)).build()); - } - } - - private class CachingResponseDecorator extends ServerHttpResponseDecorator { - - private final String metadataKey; - - private final ServerWebExchange exchange; - - CachingResponseDecorator(String metadataKey, ServerWebExchange exchange) { - super(exchange.getResponse()); - this.metadataKey = metadataKey; - this.exchange = exchange; - } - - @Override - public Mono writeWith(Publisher body) { - final ServerHttpResponse response = exchange.getResponse(); - - Flux decoratedBody; - if (responseCacheManager.isResponseCacheable(response)) { - decoratedBody = responseCacheManager.processFromUpstream(metadataKey, exchange, Flux.from(body)); - } - else { - decoratedBody = Flux.from(body); - } - - return super.writeWith(decoratedBody); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/ResponseCacheManager.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/ResponseCacheManager.java deleted file mode 100644 index 4ad1a82d..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/ResponseCacheManager.java +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright 2013-2022 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.filter.factory.cache; - -import java.nio.ByteBuffer; -import java.time.Clock; -import java.time.Duration; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Optional; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -import org.springframework.cache.Cache; -import org.springframework.cloud.gateway.filter.factory.cache.keygenerator.CacheKeyGenerator; -import org.springframework.cloud.gateway.filter.factory.cache.postprocessor.AfterCacheExchangeMutator; -import org.springframework.cloud.gateway.filter.factory.cache.postprocessor.SetMaxAgeHeaderAfterCacheExchangeMutator; -import org.springframework.cloud.gateway.filter.factory.cache.postprocessor.SetResponseHeadersAfterCacheExchangeMutator; -import org.springframework.cloud.gateway.filter.factory.cache.postprocessor.SetStatusCodeAfterCacheExchangeMutator; -import org.springframework.core.io.buffer.DataBuffer; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMessage; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.http.HttpStatusCode; -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.http.server.reactive.ServerHttpResponse; -import org.springframework.web.server.ServerWebExchange; - -/** - * @author Marta Medio - * @author Ignacio Lozano - */ -public class ResponseCacheManager { - - private static final Log LOGGER = LogFactory.getLog(ResponseCacheManager.class); - - private static final List forbiddenCacheControlValues = Arrays.asList("private", "no-store"); - - private static final String VARY_WILDCARD = "*"; - - final CacheKeyGenerator cacheKeyGenerator; - - final List afterCacheExchangeMutators; - - private final Cache cache; - - public ResponseCacheManager(CacheKeyGenerator cacheKeyGenerator, Cache cache, Duration configuredTimeToLive) { - this.cacheKeyGenerator = cacheKeyGenerator; - this.cache = cache; - this.afterCacheExchangeMutators = List.of(new SetResponseHeadersAfterCacheExchangeMutator(), - new SetStatusCodeAfterCacheExchangeMutator(), - new SetMaxAgeHeaderAfterCacheExchangeMutator(configuredTimeToLive, Clock.systemDefaultZone())); - } - - private static final List statusesToCache = Arrays.asList(HttpStatus.OK, HttpStatus.PARTIAL_CONTENT, - HttpStatus.MOVED_PERMANENTLY); - - public Optional getFromCache(ServerHttpRequest request, String metadataKey) { - CachedResponseMetadata metadata = retrieveMetadata(metadataKey); - String key = cacheKeyGenerator.generateKey(request, - metadata != null ? metadata.varyOnHeaders() : Collections.emptyList()); - - return getFromCache(key); - } - - public Flux processFromUpstream(String metadataKey, ServerWebExchange exchange, Flux body) { - final ServerHttpResponse response = exchange.getResponse(); - final CachedResponseMetadata metadata = new CachedResponseMetadata(response.getHeaders().getVary()); - final String key = resolveKey(exchange, metadata.varyOnHeaders()); - CachedResponse.Builder cachedResponseBuilder = CachedResponse.create(response.getStatusCode()) - .headers(response.getHeaders()); - CachedResponse toProcess = cachedResponseBuilder.build(); - afterCacheExchangeMutators.forEach(processor -> processor.accept(exchange, toProcess)); - - // Note: `map` instead of `doOnNext - // `doOnNext` is only for side-effect operations (like logging or emitting other - // events). Order is not guaranteed. In some cases, the signal is not in order and - // the object will be corrupted in cache - return body.map(dataBuffer -> { - ByteBuffer byteBuffer = dataBuffer.toByteBuffer().asReadOnlyBuffer(); - cachedResponseBuilder.appendToBody(byteBuffer); - return dataBuffer; - }).doOnComplete(() -> { - CachedResponse responseToCache = cachedResponseBuilder.timestamp(toProcess.timestamp()).build(); - saveMetadataInCache(metadataKey, metadata); - saveInCache(key, responseToCache); - }); - } - - private Optional getFromCache(String key) { - CachedResponse cachedResponse; - try { - cachedResponse = cache.get(key, CachedResponse.class); - } - catch (RuntimeException anyException) { - LOGGER.error("Error reading from cache. Data will not come from cache.", anyException); - cachedResponse = null; - } - return Optional.ofNullable(cachedResponse); - } - - public String resolveMetadataKey(ServerWebExchange exchange) { - return cacheKeyGenerator.generateMetadataKey(exchange.getRequest()); - } - - public String resolveKey(ServerWebExchange exchange, List varyOnHeaders) { - return cacheKeyGenerator.generateKey(exchange.getRequest(), varyOnHeaders); - } - - Mono processFromCache(ServerWebExchange exchange, String metadataKey, CachedResponse cachedResponse) { - final ServerHttpResponse response = exchange.getResponse(); - - afterCacheExchangeMutators.forEach(processor -> processor.accept(exchange, cachedResponse)); - saveMetadataInCache(metadataKey, new CachedResponseMetadata(cachedResponse.headers().getVary())); - - if (HttpStatus.NOT_MODIFIED.equals(response.getStatusCode())) { - return response.writeWith(Mono.empty()); - } - else { - return response.writeWith( - Flux.fromIterable(cachedResponse.body()).map(data -> response.bufferFactory().wrap(data))); - } - } - - private CachedResponseMetadata retrieveMetadata(String metadataKey) { - CachedResponseMetadata metadata; - try { - metadata = cache.get(metadataKey, CachedResponseMetadata.class); - } - catch (RuntimeException anyException) { - LOGGER.error("Error reading from cache. Metadata Data will not come from cache.", anyException); - metadata = null; - } - return metadata; - } - - boolean isResponseCacheable(ServerHttpResponse response) { - return isStatusCodeToCache(response) && isCacheControlAllowed(response) && !isVaryWildcard(response); - } - - private boolean isStatusCodeToCache(ServerHttpResponse response) { - return statusesToCache.contains(response.getStatusCode()); - } - - boolean isRequestCacheable(ServerHttpRequest request) { - return HttpMethod.GET.equals(request.getMethod()) && !hasRequestBody(request) && isCacheControlAllowed(request); - } - - private boolean isVaryWildcard(ServerHttpResponse response) { - HttpHeaders headers = response.getHeaders(); - List varyValues = headers.getOrEmpty(HttpHeaders.VARY); - - return varyValues.stream().anyMatch(VARY_WILDCARD::equals); - } - - private boolean isCacheControlAllowed(HttpMessage request) { - HttpHeaders headers = request.getHeaders(); - List cacheControlHeader = headers.getOrEmpty(HttpHeaders.CACHE_CONTROL); - - return cacheControlHeader.stream().noneMatch(forbiddenCacheControlValues::contains); - } - - private static boolean hasRequestBody(ServerHttpRequest request) { - return request.getHeaders().getContentLength() > 0; - } - - private void saveInCache(String cacheKey, CachedResponse cachedResponse) { - try { - cache.put(cacheKey, cachedResponse); - } - catch (RuntimeException anyException) { - LOGGER.error("Error writing into cache. Data will not be cached", anyException); - } - } - - private void saveMetadataInCache(String metadataKey, CachedResponseMetadata metadata) { - try { - cache.put(metadataKey, metadata); - } - catch (RuntimeException anyException) { - LOGGER.error("Error writing into cache. Data will not be cached", anyException); - } - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/ResponseCacheManagerFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/ResponseCacheManagerFactory.java deleted file mode 100644 index 00592068..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/ResponseCacheManagerFactory.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory.cache; - -import java.time.Duration; - -import org.springframework.cache.Cache; -import org.springframework.cloud.gateway.filter.factory.cache.keygenerator.CacheKeyGenerator; - -/** - * @author Marta Medio - * @author Ignacio Lozano - */ -public class ResponseCacheManagerFactory { - - private final CacheKeyGenerator cacheKeyGenerator; - - public ResponseCacheManagerFactory(CacheKeyGenerator cacheKeyGenerator) { - this.cacheKeyGenerator = cacheKeyGenerator; - } - - public ResponseCacheManager create(Cache cache, Duration timeToLive) { - return new ResponseCacheManager(cacheKeyGenerator, cache, timeToLive); - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/ResponseCacheSizeWeigher.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/ResponseCacheSizeWeigher.java deleted file mode 100644 index abedf5c1..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/ResponseCacheSizeWeigher.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2013-2022 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.filter.factory.cache; - -import java.nio.Buffer; -import java.util.List; -import java.util.stream.Stream; - -import com.github.benmanes.caffeine.cache.Weigher; - -/** - * @author Marta Medio - * @author Ignacio Lozano - */ -public class ResponseCacheSizeWeigher implements Weigher { - - @Override - public int weigh(String key, Object value) { - if (value instanceof CachedResponse cached) { - return cached.headers().getContentLength() > -1 ? (int) cached.headers().getContentLength() - : estimateContentLength(cached); - } - else { - return 0; - } - } - - private int estimateContentLength(CachedResponse value) { - return Stream.ofNullable(value.body()).flatMap(List::stream).mapToInt(Buffer::limit).sum(); - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/keygenerator/CacheKeyGenerator.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/keygenerator/CacheKeyGenerator.java deleted file mode 100644 index ccbf7b54..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/keygenerator/CacheKeyGenerator.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2013-2022 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.filter.factory.cache.keygenerator; - -import java.io.ByteArrayOutputStream; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.Arrays; -import java.util.Base64; -import java.util.Collections; -import java.util.List; -import java.util.stream.Stream; - -import org.springframework.http.HttpHeaders; -import org.springframework.http.server.reactive.ServerHttpRequest; - -/** - * @author Marta Medio - * @author Ignacio Lozano - * @author Simone Gerevini - */ -public class CacheKeyGenerator { - - /* for testing */ static final String KEY_SEPARATOR = ";"; - - private static final byte[] KEY_SEPARATOR_BYTES = KEY_SEPARATOR.getBytes(); - - private final ThreadLocal messageDigest; - - /* for testing */ static final List DEFAULT_KEY_VALUE_GENERATORS = List.of( - new UriKeyValueGenerator(), new HeaderKeyValueGenerator(HttpHeaders.AUTHORIZATION, KEY_SEPARATOR), - new CookiesKeyValueGenerator(KEY_SEPARATOR)); - - public CacheKeyGenerator() { - messageDigest = ThreadLocal.withInitial(() -> { - try { - return MessageDigest.getInstance("MD5"); - } - catch (NoSuchAlgorithmException e) { - throw new RuntimeException("Error creating CacheKeyGenerator", e); - } - }); - } - - public String generateMetadataKey(ServerHttpRequest request, String... varyHeaders) { - return "META_" + generateKey(request, varyHeaders); - } - - public String generateKey(ServerHttpRequest request, String... varyHeaders) { - return generateKey(request, varyHeaders != null ? Arrays.asList(varyHeaders) : Collections.emptyList()); - } - - public String generateKey(ServerHttpRequest request, List varyHeaders) { - byte[] rawKey = generateRawKey(request, varyHeaders); - byte[] digest = messageDigest.get().digest(rawKey); - - return Base64.getEncoder().encodeToString(digest); - } - - private Stream getKeyValueGenerators(List varyHeaders) { - return Stream.concat(DEFAULT_KEY_VALUE_GENERATORS.stream(), - varyHeaders.stream().sorted().map(header -> new HeaderKeyValueGenerator(header, ","))); - } - - private byte[] generateRawKey(ServerHttpRequest request, List varyHeaders) { - Stream keyValueGenerators = getKeyValueGenerators(varyHeaders); - - final ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream(); - keyValueGenerators.map(generator -> generator.apply(request)).map(String::getBytes).forEach(bytes -> { - byteOutputStream.writeBytes(bytes); - byteOutputStream.writeBytes(KEY_SEPARATOR_BYTES); - }); - - return byteOutputStream.toByteArray(); - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/keygenerator/CookiesKeyValueGenerator.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/keygenerator/CookiesKeyValueGenerator.java deleted file mode 100644 index 14d86541..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/keygenerator/CookiesKeyValueGenerator.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory.cache.keygenerator; - -import java.util.Collection; -import java.util.Objects; -import java.util.stream.Collectors; - -import org.springframework.http.HttpCookie; -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.util.CollectionUtils; -import org.springframework.util.MultiValueMap; - -/** - * @author Marta Medio - * @author Ignacio Lozano - */ -class CookiesKeyValueGenerator implements KeyValueGenerator { - - private final String valueSeparator; - - CookiesKeyValueGenerator(String valueSeparator) { - this.valueSeparator = Objects.requireNonNull(valueSeparator); - } - - public String getKeyValue(ServerHttpRequest request) { - String cookiesData = null; - MultiValueMap cookies = request.getCookies(); - if (!CollectionUtils.isEmpty(cookies)) { - cookiesData = cookies.values().stream().flatMap(Collection::stream) - .map(c -> String.format("%s=%s", c.getName(), c.getValue())).sorted() - .collect(Collectors.joining(valueSeparator)); - } - return cookiesData; - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/keygenerator/HeaderKeyValueGenerator.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/keygenerator/HeaderKeyValueGenerator.java deleted file mode 100644 index d5ea55ca..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/keygenerator/HeaderKeyValueGenerator.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory.cache.keygenerator; - -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import org.springframework.http.HttpHeaders; -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.util.StringUtils; - -/** - * @author Marta Medio - * @author Ignacio Lozano - */ -class HeaderKeyValueGenerator implements KeyValueGenerator { - - private final String header; - - private final String valueSeparator; - - HeaderKeyValueGenerator(String header, String valueSeparator) { - this.valueSeparator = valueSeparator; - if (!StringUtils.hasText(header)) { - throw new IllegalArgumentException("The parameter cannot be empty or null"); - } - this.header = header; - } - - @Override - public String getKeyValue(ServerHttpRequest request) { - HttpHeaders headers = request.getHeaders(); - if (headers.get(header) != null) { - StringBuilder keyVaryHeaders = new StringBuilder(); - keyVaryHeaders.append(header).append("=") - .append(getHeaderValues(headers).sorted().collect(Collectors.joining(valueSeparator))); - return keyVaryHeaders.toString(); - } - return null; - } - - private Stream getHeaderValues(HttpHeaders headers) { - List value = headers.get(header); - return value == null ? Stream.empty() : value.stream(); - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/keygenerator/KeyValueGenerator.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/keygenerator/KeyValueGenerator.java deleted file mode 100644 index a0665e9d..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/keygenerator/KeyValueGenerator.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory.cache.keygenerator; - -import org.springframework.http.server.reactive.ServerHttpRequest; - -/** - * Function to calculate a key value based on a {@link ServerHttpRequest}. - * - * @author Marta Medio - * @author Ignacio Lozano - */ -interface KeyValueGenerator { - - /* - * Calls getKeyValue() and guards against null. - */ - default String apply(ServerHttpRequest request) { - String key = getKeyValue(request); - if (key == null) { - return ""; - } - return key; - } - - String getKeyValue(ServerHttpRequest request); - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/keygenerator/UriKeyValueGenerator.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/keygenerator/UriKeyValueGenerator.java deleted file mode 100644 index a30f15ee..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/keygenerator/UriKeyValueGenerator.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2013-2022 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.filter.factory.cache.keygenerator; - -import org.springframework.http.server.reactive.ServerHttpRequest; - -/** - * It generates key value based on the URI. - * - * @author Marta Medio - * @author Ignacio Lozano - */ -public class UriKeyValueGenerator implements KeyValueGenerator { - - @Override - public String getKeyValue(ServerHttpRequest request) { - return request.getURI().toString(); - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/postprocessor/AfterCacheExchangeMutator.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/postprocessor/AfterCacheExchangeMutator.java deleted file mode 100644 index 9106489d..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/postprocessor/AfterCacheExchangeMutator.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2013-2022 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.filter.factory.cache.postprocessor; - -import java.util.function.BiConsumer; - -import org.springframework.cloud.gateway.filter.factory.cache.CachedResponse; -import org.springframework.web.server.ServerWebExchange; - -/** - * Marking interface for a {@link BiConsumer} that could alter the - * {@link ServerWebExchange} . - * - * @author Marta Medio - * @author Ignacio Lozano - */ -public interface AfterCacheExchangeMutator extends BiConsumer { - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/postprocessor/SetMaxAgeHeaderAfterCacheExchangeMutator.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/postprocessor/SetMaxAgeHeaderAfterCacheExchangeMutator.java deleted file mode 100644 index 7b9b5dc0..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/postprocessor/SetMaxAgeHeaderAfterCacheExchangeMutator.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory.cache.postprocessor; - -import java.time.Clock; -import java.time.Duration; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.stream.Collectors; - -import org.springframework.cloud.gateway.filter.factory.cache.CachedResponse; -import org.springframework.http.HttpHeaders; -import org.springframework.http.server.reactive.ServerHttpResponse; -import org.springframework.web.server.ServerWebExchange; - -/** - * It sets the {@link HttpHeaders#CACHE_CONTROL} {@literal max-age} value. The value is - * calculated taking the {@link #configuredTimeToLive} cache configuration and the age of - * the entry. - * - * @author Marta Medio - * @author Ignacio Lozano - */ -public class SetMaxAgeHeaderAfterCacheExchangeMutator implements AfterCacheExchangeMutator { - - private static final String MAX_AGE_PREFIX = "max-age="; - - private final Duration configuredTimeToLive; - - private final Clock clock; - - public SetMaxAgeHeaderAfterCacheExchangeMutator(Duration configuredTimeToLive, Clock clock) { - this.configuredTimeToLive = configuredTimeToLive; - this.clock = clock; - } - - @Override - public void accept(ServerWebExchange exchange, CachedResponse cachedResponse) { - ServerHttpResponse response = exchange.getResponse(); - long calculatedMaxAgeInSeconds = calculateMaxAgeInSeconds(cachedResponse, configuredTimeToLive); - rewriteCacheControlMaxAge(response.getHeaders(), calculatedMaxAgeInSeconds); - } - - private long calculateMaxAgeInSeconds(CachedResponse cachedResponse, Duration configuredTimeToLive) { - long maxAge; - if (configuredTimeToLive.getSeconds() == -1) { - maxAge = -1; - } - else { - maxAge = Math.max(0, configuredTimeToLive.minus(getElapsedTimeInSeconds(cachedResponse)).getSeconds()); - } - - return maxAge; - } - - private Duration getElapsedTimeInSeconds(CachedResponse cachedResponse) { - return Duration.ofMillis(clock.millis() - cachedResponse.timestamp().getTime()); - } - - private static void rewriteCacheControlMaxAge(HttpHeaders headers, long seconds) { - boolean isMaxAgePresent = headers.getCacheControl() != null - && headers.getCacheControl().contains(MAX_AGE_PREFIX); - - if (isMaxAgePresent) { - List cacheControlHeaders = headers.get(HttpHeaders.CACHE_CONTROL); - cacheControlHeaders = cacheControlHeaders == null ? Collections.emptyList() : cacheControlHeaders; - List replacedCacheControlHeaders = new ArrayList<>(); - for (String value : cacheControlHeaders) { - if (value.contains(MAX_AGE_PREFIX)) { - if (seconds == -1) { - List removedMaxAgeList = Arrays.stream(value.split(",")) - .filter(i -> !i.trim().startsWith(MAX_AGE_PREFIX)).collect(Collectors.toList()); - value = String.join(",", removedMaxAgeList); - } - else { - value = value.replaceFirst("\\bmax-age=\\d+\\b", MAX_AGE_PREFIX + seconds); - } - } - replacedCacheControlHeaders.add(value); - } - headers.remove(HttpHeaders.CACHE_CONTROL); - headers.addAll(HttpHeaders.CACHE_CONTROL, replacedCacheControlHeaders); - } - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/postprocessor/SetResponseHeadersAfterCacheExchangeMutator.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/postprocessor/SetResponseHeadersAfterCacheExchangeMutator.java deleted file mode 100644 index d1b0ec76..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/postprocessor/SetResponseHeadersAfterCacheExchangeMutator.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2013-2022 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.filter.factory.cache.postprocessor; - -import org.springframework.cloud.gateway.filter.factory.cache.CachedResponse; -import org.springframework.http.server.reactive.ServerHttpResponse; -import org.springframework.web.server.ServerWebExchange; - -/** - * It sets HTTP Headers using {@link CachedResponse} values. - * - * @author Marta Medio - * @author Ignacio Lozano - */ -public class SetResponseHeadersAfterCacheExchangeMutator implements AfterCacheExchangeMutator { - - @Override - public void accept(ServerWebExchange exchange, CachedResponse cachedResponse) { - ServerHttpResponse response = exchange.getResponse(); - response.getHeaders().clear(); - response.getHeaders().addAll(cachedResponse.headers()); - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/postprocessor/SetStatusCodeAfterCacheExchangeMutator.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/postprocessor/SetStatusCodeAfterCacheExchangeMutator.java deleted file mode 100644 index 41b2c817..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/cache/postprocessor/SetStatusCodeAfterCacheExchangeMutator.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory.cache.postprocessor; - -import org.springframework.cloud.gateway.filter.factory.cache.CachedResponse; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.server.reactive.ServerHttpResponse; -import org.springframework.util.CollectionUtils; -import org.springframework.web.server.ServerWebExchange; - -/** - * It sets HTTP Status Code depending {@literal no-cache} - * {@link HttpHeaders#CACHE_CONTROL} header. - * - * @author Marta Medio - * @author Ignacio Lozano - */ -public class SetStatusCodeAfterCacheExchangeMutator implements AfterCacheExchangeMutator { - - private static final String NO_CACHE_VALUE = "no-cache"; - - @Override - public void accept(ServerWebExchange exchange, CachedResponse cachedResponse) { - HttpHeaders requestHeaders = exchange.getRequest().getHeaders(); - ServerHttpResponse response = exchange.getResponse(); - - if (!CollectionUtils.isEmpty(cachedResponse.body()) && isRequestNoCache(requestHeaders)) { - response.setStatusCode(HttpStatus.NOT_MODIFIED); - } - else { - response.setStatusCode(cachedResponse.statusCode()); - } - } - - private boolean isRequestNoCache(HttpHeaders requestHeaders) { - return requestHeaders.getCacheControl() != null && requestHeaders.getCacheControl().contains(NO_CACHE_VALUE); - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/rewrite/CachedBodyOutputMessage.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/rewrite/CachedBodyOutputMessage.java deleted file mode 100644 index e23e86c2..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/rewrite/CachedBodyOutputMessage.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory.rewrite; - -import java.util.function.Supplier; - -import org.reactivestreams.Publisher; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -import org.springframework.core.io.buffer.DataBuffer; -import org.springframework.core.io.buffer.DataBufferFactory; -import org.springframework.http.HttpHeaders; -import org.springframework.http.ReactiveHttpOutputMessage; -import org.springframework.http.client.reactive.ClientHttpRequest; -import org.springframework.web.server.ServerWebExchange; - -/** - * Implementation of {@link ClientHttpRequest} that saves body as a field. - */ -public class CachedBodyOutputMessage implements ReactiveHttpOutputMessage { - - private final DataBufferFactory bufferFactory; - - private final HttpHeaders httpHeaders; - - private boolean cached = false; - - private Flux body = null; - - public CachedBodyOutputMessage(ServerWebExchange exchange, HttpHeaders httpHeaders) { - this.bufferFactory = exchange.getResponse().bufferFactory(); - this.httpHeaders = httpHeaders; - } - - @Override - public void beforeCommit(Supplier> action) { - - } - - @Override - public boolean isCommitted() { - return false; - } - - boolean isCached() { - return this.cached; - } - - @Override - public HttpHeaders getHeaders() { - return this.httpHeaders; - } - - @Override - public DataBufferFactory bufferFactory() { - return this.bufferFactory; - } - - /** - * Return the request body, or an error stream if the body was never set or when. - * @return body as {@link Flux} - */ - public Flux getBody() { - if (body == null) { - return Flux - .error(new IllegalStateException("The body is not set. " + "Did handling complete with success?")); - } - return this.body; - } - - public Mono writeWith(Publisher body) { - this.body = Flux.from(body); - this.cached = true; - return Mono.empty(); - } - - @Override - public Mono writeAndFlushWith(Publisher> body) { - return writeWith(Flux.from(body).flatMap(p -> p)); - } - - @Override - public Mono setComplete() { - return writeWith(Flux.empty()); - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/rewrite/GzipMessageBodyResolver.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/rewrite/GzipMessageBodyResolver.java deleted file mode 100644 index f6afbb80..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/rewrite/GzipMessageBodyResolver.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory.rewrite; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.zip.GZIPInputStream; -import java.util.zip.GZIPOutputStream; - -import org.springframework.core.io.buffer.DataBuffer; -import org.springframework.util.FileCopyUtils; - -public class GzipMessageBodyResolver implements MessageBodyDecoder, MessageBodyEncoder { - - @Override - public String encodingType() { - return "gzip"; - } - - @Override - public byte[] decode(byte[] encoded) { - try { - ByteArrayInputStream bis = new ByteArrayInputStream(encoded); - GZIPInputStream gis = new GZIPInputStream(bis); - return FileCopyUtils.copyToByteArray(gis); - } - catch (IOException e) { - throw new IllegalStateException("couldn't decode body from gzip", e); - } - } - - @Override - public byte[] encode(DataBuffer original) { - try { - ByteArrayOutputStream bis = new ByteArrayOutputStream(); - GZIPOutputStream gos = new GZIPOutputStream(bis); - FileCopyUtils.copy(original.asInputStream(), gos); - return bis.toByteArray(); - } - catch (IOException e) { - throw new IllegalStateException("couldn't encode body to gzip", e); - } - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/rewrite/MessageBodyDecoder.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/rewrite/MessageBodyDecoder.java deleted file mode 100644 index 4f2951e4..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/rewrite/MessageBodyDecoder.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory.rewrite; - -/** - * Decoder that is used to decode message body in case it's encoding from Content-Encoding - * header matches encoding returned by {@code encodingType()} call. - */ -public interface MessageBodyDecoder { - - byte[] decode(byte[] encoded); - - String encodingType(); - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/rewrite/MessageBodyEncoder.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/rewrite/MessageBodyEncoder.java deleted file mode 100644 index 7f3f90a8..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/rewrite/MessageBodyEncoder.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory.rewrite; - -import org.springframework.core.io.buffer.DataBuffer; - -/** - * Encoder that is used to encode message body in case it's encoding from Content-Encoding - * header matches encoding returned by {@code encodingType()} call. - */ -public interface MessageBodyEncoder { - - byte[] encode(DataBuffer original); - - String encodingType(); - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/rewrite/ModifyRequestBodyGatewayFilterFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/rewrite/ModifyRequestBodyGatewayFilterFactory.java deleted file mode 100644 index cf5cd030..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/rewrite/ModifyRequestBodyGatewayFilterFactory.java +++ /dev/null @@ -1,214 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory.rewrite; - -import java.util.List; -import java.util.function.Function; - -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.GatewayFilterChain; -import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; -import org.springframework.cloud.gateway.support.BodyInserterContext; -import org.springframework.core.ParameterizedTypeReference; -import org.springframework.core.io.buffer.DataBuffer; -import org.springframework.core.io.buffer.DataBufferUtils; -import org.springframework.http.HttpHeaders; -import org.springframework.http.codec.HttpMessageReader; -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.http.server.reactive.ServerHttpRequestDecorator; -import org.springframework.web.reactive.function.BodyInserter; -import org.springframework.web.reactive.function.BodyInserters; -import org.springframework.web.reactive.function.server.HandlerStrategies; -import org.springframework.web.reactive.function.server.ServerRequest; -import org.springframework.web.server.ServerWebExchange; - -import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator; - -/** - * GatewayFilter that modifies the request body. - */ -public class ModifyRequestBodyGatewayFilterFactory - extends AbstractGatewayFilterFactory { - - private final List> messageReaders; - - public ModifyRequestBodyGatewayFilterFactory() { - super(Config.class); - this.messageReaders = HandlerStrategies.withDefaults().messageReaders(); - } - - public ModifyRequestBodyGatewayFilterFactory(List> messageReaders) { - super(Config.class); - this.messageReaders = messageReaders; - } - - @Override - @SuppressWarnings("unchecked") - public GatewayFilter apply(Config config) { - return new GatewayFilter() { - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - ParameterizedTypeReference inClass = config.getInClass(); - ServerRequest serverRequest = ServerRequest.create(exchange, messageReaders); - - // TODO: flux or mono - Mono modifiedBody = serverRequest.bodyToMono(inClass) - .flatMap(originalBody -> config.getRewriteFunction().apply(exchange, originalBody)) - .switchIfEmpty(Mono.defer(() -> (Mono) config.getRewriteFunction().apply(exchange, null))); - - BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, config.getOutClass()); - HttpHeaders headers = new HttpHeaders(); - headers.putAll(exchange.getRequest().getHeaders()); - - // the new content type will be computed by bodyInserter - // and then set in the request decorator - headers.remove(HttpHeaders.CONTENT_LENGTH); - - // if the body is changing content types, set it here, to the bodyInserter - // will know about it - if (config.getContentType() != null) { - headers.set(HttpHeaders.CONTENT_TYPE, config.getContentType()); - } - CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, headers); - return bodyInserter.insert(outputMessage, new BodyInserterContext()) - // .log("modify_request", Level.INFO) - .then(Mono.defer(() -> { - ServerHttpRequest decorator = decorate(exchange, headers, outputMessage); - return chain.filter(exchange.mutate().request(decorator).build()); - })).onErrorResume((Function>) throwable -> release(exchange, - outputMessage, throwable)); - } - - @Override - public String toString() { - return filterToStringCreator(ModifyRequestBodyGatewayFilterFactory.this) - .append("Content type", config.getContentType()).append("In class", config.getInClass()) - .append("Out class", config.getOutClass()).toString(); - } - }; - } - - protected Mono release(ServerWebExchange exchange, CachedBodyOutputMessage outputMessage, - Throwable throwable) { - if (outputMessage.isCached()) { - return outputMessage.getBody().map(DataBufferUtils::release).then(Mono.error(throwable)); - } - return Mono.error(throwable); - } - - ServerHttpRequestDecorator decorate(ServerWebExchange exchange, HttpHeaders headers, - CachedBodyOutputMessage outputMessage) { - return new ServerHttpRequestDecorator(exchange.getRequest()) { - @Override - public HttpHeaders getHeaders() { - long contentLength = headers.getContentLength(); - HttpHeaders httpHeaders = new HttpHeaders(); - httpHeaders.putAll(headers); - if (contentLength > 0) { - httpHeaders.setContentLength(contentLength); - } - else { - // TODO: this causes a 'HTTP/1.1 411 Length Required' // on - // httpbin.org - httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked"); - } - return httpHeaders; - } - - @Override - public Flux getBody() { - return outputMessage.getBody(); - } - }; - } - - public static class Config { - - private ParameterizedTypeReference inClass; - - private ParameterizedTypeReference outClass; - - private String contentType; - - private RewriteFunction rewriteFunction; - - public ParameterizedTypeReference getInClass() { - return inClass; - } - - public Config setInClass(Class inClass) { - return setInClass(ParameterizedTypeReference.forType(inClass)); - } - - public Config setInClass(ParameterizedTypeReference inTypeReference) { - this.inClass = inTypeReference; - return this; - } - - public ParameterizedTypeReference getOutClass() { - return outClass; - } - - public Config setOutClass(Class outClass) { - return setOutClass(ParameterizedTypeReference.forType(outClass)); - } - - public Config setOutClass(ParameterizedTypeReference outClass) { - this.outClass = outClass; - return this; - } - - public RewriteFunction getRewriteFunction() { - return rewriteFunction; - } - - public Config setRewriteFunction(RewriteFunction rewriteFunction) { - this.rewriteFunction = rewriteFunction; - return this; - } - - public Config setRewriteFunction(Class inClass, Class outClass, - RewriteFunction rewriteFunction) { - setInClass(inClass); - setOutClass(outClass); - setRewriteFunction(rewriteFunction); - return this; - } - - public Config setRewriteFunction(ParameterizedTypeReference inClass, - ParameterizedTypeReference outClass, RewriteFunction rewriteFunction) { - setInClass(inClass); - setOutClass(outClass); - setRewriteFunction(rewriteFunction); - return this; - } - - public String getContentType() { - return contentType; - } - - public Config setContentType(String contentType) { - this.contentType = contentType; - return this; - } - - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/rewrite/ModifyResponseBodyGatewayFilterFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/rewrite/ModifyResponseBodyGatewayFilterFactory.java deleted file mode 100644 index d365ed67..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/rewrite/ModifyResponseBodyGatewayFilterFactory.java +++ /dev/null @@ -1,300 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory.rewrite; - -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; - -import org.reactivestreams.Publisher; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import reactor.core.scheduler.Schedulers; - -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.GatewayFilterChain; -import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter; -import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.GatewayFilterFactory; -import org.springframework.cloud.gateway.support.BodyInserterContext; -import org.springframework.core.Ordered; -import org.springframework.core.io.buffer.DataBuffer; -import org.springframework.core.io.buffer.DataBufferFactory; -import org.springframework.core.io.buffer.DataBufferUtils; -import org.springframework.http.HttpHeaders; -import org.springframework.http.codec.HttpMessageReader; -import org.springframework.http.server.reactive.ServerHttpResponse; -import org.springframework.http.server.reactive.ServerHttpResponseDecorator; -import org.springframework.web.reactive.function.BodyInserter; -import org.springframework.web.reactive.function.BodyInserters; -import org.springframework.web.reactive.function.client.ClientResponse; -import org.springframework.web.server.ServerWebExchange; - -import static java.util.function.Function.identity; -import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR; - -/** - * GatewayFilter that modifies the response body. - */ -public class ModifyResponseBodyGatewayFilterFactory - extends AbstractGatewayFilterFactory { - - private final Map messageBodyDecoders; - - private final Map messageBodyEncoders; - - private final List> messageReaders; - - public ModifyResponseBodyGatewayFilterFactory(List> messageReaders, - Set messageBodyDecoders, Set messageBodyEncoders) { - super(Config.class); - this.messageReaders = messageReaders; - this.messageBodyDecoders = messageBodyDecoders.stream() - .collect(Collectors.toMap(MessageBodyDecoder::encodingType, identity())); - this.messageBodyEncoders = messageBodyEncoders.stream() - .collect(Collectors.toMap(MessageBodyEncoder::encodingType, identity())); - } - - @Override - public GatewayFilter apply(Config config) { - ModifyResponseGatewayFilter gatewayFilter = new ModifyResponseGatewayFilter(config); - gatewayFilter.setFactory(this); - return gatewayFilter; - } - - public static class Config { - - private Class inClass; - - private Class outClass; - - private Map inHints; - - private Map outHints; - - private String newContentType; - - private RewriteFunction rewriteFunction; - - public Class getInClass() { - return inClass; - } - - public Config setInClass(Class inClass) { - this.inClass = inClass; - return this; - } - - public Class getOutClass() { - return outClass; - } - - public Config setOutClass(Class outClass) { - this.outClass = outClass; - return this; - } - - public Map getInHints() { - return inHints; - } - - public Config setInHints(Map inHints) { - this.inHints = inHints; - return this; - } - - public Map getOutHints() { - return outHints; - } - - public Config setOutHints(Map outHints) { - this.outHints = outHints; - return this; - } - - public String getNewContentType() { - return newContentType; - } - - public Config setNewContentType(String newContentType) { - this.newContentType = newContentType; - return this; - } - - public RewriteFunction getRewriteFunction() { - return rewriteFunction; - } - - public Config setRewriteFunction(RewriteFunction rewriteFunction) { - this.rewriteFunction = rewriteFunction; - return this; - } - - public Config setRewriteFunction(Class inClass, Class outClass, - RewriteFunction rewriteFunction) { - setInClass(inClass); - setOutClass(outClass); - setRewriteFunction(rewriteFunction); - return this; - } - - } - - public class ModifyResponseGatewayFilter implements GatewayFilter, Ordered { - - private final Config config; - - private GatewayFilterFactory gatewayFilterFactory; - - public ModifyResponseGatewayFilter(Config config) { - this.config = config; - } - - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - return chain.filter(exchange.mutate().response(new ModifiedServerHttpResponse(exchange, config)).build()); - } - - @Override - public int getOrder() { - return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 1; - } - - @Override - public String toString() { - Object obj = (this.gatewayFilterFactory != null) ? this.gatewayFilterFactory : this; - return filterToStringCreator(obj).append("New content type", config.getNewContentType()) - .append("In class", config.getInClass()).append("Out class", config.getOutClass()).toString(); - } - - public void setFactory(GatewayFilterFactory gatewayFilterFactory) { - this.gatewayFilterFactory = gatewayFilterFactory; - } - - } - - protected class ModifiedServerHttpResponse extends ServerHttpResponseDecorator { - - private final ServerWebExchange exchange; - - private final Config config; - - public ModifiedServerHttpResponse(ServerWebExchange exchange, Config config) { - super(exchange.getResponse()); - this.exchange = exchange; - this.config = config; - } - - @SuppressWarnings("unchecked") - @Override - public Mono writeWith(Publisher body) { - - Class inClass = config.getInClass(); - Class outClass = config.getOutClass(); - - String originalResponseContentType = exchange.getAttribute(ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR); - HttpHeaders httpHeaders = new HttpHeaders(); - // explicitly add it in this way instead of - // 'httpHeaders.setContentType(originalResponseContentType)' - // this will prevent exception in case of using non-standard media - // types like "Content-Type: image" - httpHeaders.add(HttpHeaders.CONTENT_TYPE, originalResponseContentType); - - ClientResponse clientResponse = prepareClientResponse(body, httpHeaders); - - // TODO: flux or mono - Mono modifiedBody = extractBody(exchange, clientResponse, inClass) - .flatMap(originalBody -> config.getRewriteFunction().apply(exchange, originalBody)) - .switchIfEmpty(Mono.defer(() -> (Mono) config.getRewriteFunction().apply(exchange, null))); - - BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, outClass); - CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, - exchange.getResponse().getHeaders()); - return bodyInserter.insert(outputMessage, new BodyInserterContext()).then(Mono.defer(() -> { - Mono messageBody = writeBody(getDelegate(), outputMessage, outClass); - HttpHeaders headers = getDelegate().getHeaders(); - if (!headers.containsKey(HttpHeaders.TRANSFER_ENCODING) - || headers.containsKey(HttpHeaders.CONTENT_LENGTH)) { - messageBody = messageBody.doOnNext(data -> headers.setContentLength(data.readableByteCount())); - } - // TODO: fail if isStreamingMediaType? - return getDelegate().writeWith(messageBody); - })); - } - - @Override - public Mono writeAndFlushWith(Publisher> body) { - return writeWith(Flux.from(body).flatMapSequential(p -> p)); - } - - private ClientResponse prepareClientResponse(Publisher body, HttpHeaders httpHeaders) { - ClientResponse.Builder builder; - builder = ClientResponse.create(exchange.getResponse().getStatusCode(), messageReaders); - return builder.headers(headers -> headers.putAll(httpHeaders)).body(Flux.from(body)).build(); - } - - private Mono extractBody(ServerWebExchange exchange, ClientResponse clientResponse, Class inClass) { - // if inClass is byte[] then just return body, otherwise check if - // decoding required - if (byte[].class.isAssignableFrom(inClass)) { - return clientResponse.bodyToMono(inClass); - } - - List encodingHeaders = exchange.getResponse().getHeaders().getOrEmpty(HttpHeaders.CONTENT_ENCODING); - for (String encoding : encodingHeaders) { - MessageBodyDecoder decoder = messageBodyDecoders.get(encoding); - if (decoder != null) { - return clientResponse.bodyToMono(byte[].class).publishOn(Schedulers.parallel()).map(decoder::decode) - .map(bytes -> exchange.getResponse().bufferFactory().wrap(bytes)) - .map(buffer -> prepareClientResponse(Mono.just(buffer), - exchange.getResponse().getHeaders())) - .flatMap(response -> response.bodyToMono(inClass)); - } - } - - return clientResponse.bodyToMono(inClass); - } - - private Mono writeBody(ServerHttpResponse httpResponse, CachedBodyOutputMessage message, - Class outClass) { - Mono response = DataBufferUtils.join(message.getBody()); - if (byte[].class.isAssignableFrom(outClass)) { - return response; - } - - List encodingHeaders = httpResponse.getHeaders().getOrEmpty(HttpHeaders.CONTENT_ENCODING); - for (String encoding : encodingHeaders) { - MessageBodyEncoder encoder = messageBodyEncoders.get(encoding); - if (encoder != null) { - DataBufferFactory dataBufferFactory = httpResponse.bufferFactory(); - response = response.publishOn(Schedulers.parallel()).map(buffer -> { - byte[] encodedResponse = encoder.encode(buffer); - DataBufferUtils.release(buffer); - return encodedResponse; - }).map(dataBufferFactory::wrap); - break; - } - } - - return response; - } - - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/rewrite/RewriteFunction.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/rewrite/RewriteFunction.java deleted file mode 100644 index 1978660a..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/factory/rewrite/RewriteFunction.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory.rewrite; - -import java.util.function.BiFunction; - -import org.reactivestreams.Publisher; - -import org.springframework.web.server.ServerWebExchange; - -/** - * This interface is BETA and may be subject to change in a future release. - * - * @param the type of the first argument to the function - * @param the type of element signaled by the {@link Publisher} - */ -public interface RewriteFunction extends BiFunction> { - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/ForwardedHeadersFilter.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/ForwardedHeadersFilter.java deleted file mode 100644 index 788d305c..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/ForwardedHeadersFilter.java +++ /dev/null @@ -1,198 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.headers; - -import java.net.Inet6Address; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.URI; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.springframework.core.Ordered; -import org.springframework.http.HttpHeaders; -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.util.CollectionUtils; -import org.springframework.util.LinkedCaseInsensitiveMap; -import org.springframework.util.ObjectUtils; -import org.springframework.util.StringUtils; -import org.springframework.web.server.ServerWebExchange; - -public class ForwardedHeadersFilter implements HttpHeadersFilter, Ordered { - - /** - * Forwarded header. - */ - public static final String FORWARDED_HEADER = "Forwarded"; - - /* for testing */ - static List parse(List values) { - ArrayList forwardeds = new ArrayList<>(); - if (CollectionUtils.isEmpty(values)) { - return forwardeds; - } - for (String value : values) { - Forwarded forwarded = parse(value); - forwardeds.add(forwarded); - } - return forwardeds; - } - - /* for testing */ - static Forwarded parse(String value) { - String[] pairs = StringUtils.tokenizeToStringArray(value, ";"); - - LinkedCaseInsensitiveMap result = splitIntoCaseInsensitiveMap(pairs); - if (result == null) { - return null; - } - - Forwarded forwarded = new Forwarded(result); - - return forwarded; - } - - /* for testing */ static LinkedCaseInsensitiveMap splitIntoCaseInsensitiveMap(String[] pairs) { - if (ObjectUtils.isEmpty(pairs)) { - return null; - } - - LinkedCaseInsensitiveMap result = new LinkedCaseInsensitiveMap<>(); - for (String element : pairs) { - String[] splittedElement = StringUtils.split(element, "="); - if (splittedElement == null) { - continue; - } - result.put(splittedElement[0].trim(), splittedElement[1].trim()); - } - return result; - } - - @Override - public int getOrder() { - return 0; - } - - @Override - public HttpHeaders filter(HttpHeaders input, ServerWebExchange exchange) { - ServerHttpRequest request = exchange.getRequest(); - HttpHeaders original = input; - HttpHeaders updated = new HttpHeaders(); - - // copy all headers except Forwarded - for (Map.Entry> entry : original.entrySet()) { - if (!entry.getKey().equalsIgnoreCase(FORWARDED_HEADER)) { - updated.addAll(entry.getKey(), entry.getValue()); - } - } - - List forwardeds = parse(original.get(FORWARDED_HEADER)); - - for (Forwarded f : forwardeds) { - updated.add(FORWARDED_HEADER, f.toHeaderValue()); - } - - // TODO: add new forwarded - URI uri = request.getURI(); - String host = original.getFirst(HttpHeaders.HOST); - Forwarded forwarded = new Forwarded().put("host", host).put("proto", uri.getScheme()); - - InetSocketAddress remoteAddress = request.getRemoteAddress(); - if (remoteAddress != null) { - // If remoteAddress is unresolved, calling getHostAddress() would cause a - // NullPointerException. - String forValue; - if (remoteAddress.isUnresolved()) { - forValue = remoteAddress.getHostName(); - } - else { - InetAddress address = remoteAddress.getAddress(); - forValue = remoteAddress.getAddress().getHostAddress(); - if (address instanceof Inet6Address) { - forValue = "[" + forValue + "]"; - } - } - int port = remoteAddress.getPort(); - if (port >= 0) { - forValue = forValue + ":" + port; - } - forwarded.put("for", forValue); - } - // TODO: support by? - - updated.add(FORWARDED_HEADER, forwarded.toHeaderValue()); - - return updated; - } - - /* for testing */ static class Forwarded { - - private static final char EQUALS = '='; - - private static final char SEMICOLON = ';'; - - private final Map values; - - Forwarded() { - this.values = new HashMap<>(); - } - - Forwarded(Map values) { - this.values = values; - } - - public Forwarded put(String key, String value) { - this.values.put(key, quoteIfNeeded(value)); - return this; - } - - private String quoteIfNeeded(String s) { - if (s != null && s.contains(":")) { // TODO: broaded quote - return "\"" + s + "\""; - } - return s; - } - - public String get(String key) { - return this.values.get(key); - } - - /* for testing */ Map getValues() { - return this.values; - } - - @Override - public String toString() { - return "Forwarded{" + "values=" + this.values + '}'; - } - - public String toHeaderValue() { - StringBuilder builder = new StringBuilder(); - for (Map.Entry entry : this.values.entrySet()) { - if (builder.length() > 0) { - builder.append(SEMICOLON); - } - builder.append(entry.getKey()).append(EQUALS).append(entry.getValue()); - } - return builder.toString(); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/GRPCRequestHeadersFilter.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/GRPCRequestHeadersFilter.java deleted file mode 100644 index 71489ae9..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/GRPCRequestHeadersFilter.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.headers; - -import java.util.List; -import java.util.Map; - -import org.springframework.core.Ordered; -import org.springframework.http.HttpHeaders; -import org.springframework.util.StringUtils; -import org.springframework.web.server.ServerWebExchange; - -/** - * @author Alberto C. Ríos - */ -public class GRPCRequestHeadersFilter implements HttpHeadersFilter, Ordered { - - @Override - public HttpHeaders filter(HttpHeaders headers, ServerWebExchange exchange) { - HttpHeaders updated = new HttpHeaders(); - - for (Map.Entry> entry : headers.entrySet()) { - updated.addAll(entry.getKey(), entry.getValue()); - } - - // https://datatracker.ietf.org/doc/html/rfc7540#section-8.1.2.2 - if (isGRPC(headers.getFirst(HttpHeaders.CONTENT_TYPE))) { - updated.add("te", "trailers"); - } - return updated; - } - - private boolean isGRPC(String contentTypeValue) { - return StringUtils.startsWithIgnoreCase(contentTypeValue, "application/grpc"); - } - - @Override - public boolean supports(Type type) { - return Type.REQUEST.equals(type); - } - - @Override - public int getOrder() { - return Ordered.LOWEST_PRECEDENCE; - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/GRPCResponseHeadersFilter.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/GRPCResponseHeadersFilter.java deleted file mode 100644 index 25a45d29..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/GRPCResponseHeadersFilter.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.headers; - -import reactor.netty.http.server.HttpServerResponse; - -import org.springframework.core.Ordered; -import org.springframework.http.HttpHeaders; -import org.springframework.http.server.reactive.AbstractServerHttpResponse; -import org.springframework.http.server.reactive.ServerHttpResponse; -import org.springframework.http.server.reactive.ServerHttpResponseDecorator; -import org.springframework.util.StringUtils; -import org.springframework.web.server.ServerWebExchange; - -/** - * @author Alberto C. Ríos - */ -public class GRPCResponseHeadersFilter implements HttpHeadersFilter, Ordered { - - private static final String GRPC_STATUS_HEADER = "grpc-status"; - - private static final String GRPC_MESSAGE_HEADER = "grpc-message"; - - @Override - public HttpHeaders filter(HttpHeaders headers, ServerWebExchange exchange) { - ServerHttpResponse response = exchange.getResponse(); - HttpHeaders responseHeaders = response.getHeaders(); - if (isGRPC(exchange)) { - String trailerHeaderValue = GRPC_STATUS_HEADER + "," + GRPC_MESSAGE_HEADER; - String originalTrailerHeaderValue = responseHeaders.getFirst(HttpHeaders.TRAILER); - if (originalTrailerHeaderValue != null) { - trailerHeaderValue += "," + originalTrailerHeaderValue; - } - responseHeaders.set(HttpHeaders.TRAILER, trailerHeaderValue); - - while (response instanceof ServerHttpResponseDecorator) { - response = ((ServerHttpResponseDecorator) response).getDelegate(); - } - if (response instanceof AbstractServerHttpResponse) { - String grpcStatus = getGrpcStatus(headers); - String grpcMessage = getGrpcMessage(headers); - ((HttpServerResponse) ((AbstractServerHttpResponse) response).getNativeResponse()).trailerHeaders(h -> { - h.set(GRPC_STATUS_HEADER, grpcStatus); - h.set(GRPC_MESSAGE_HEADER, grpcMessage); - }); - } - - } - return headers; - } - - private boolean isGRPC(ServerWebExchange exchange) { - String contentTypeValue = exchange.getRequest().getHeaders().getFirst(HttpHeaders.CONTENT_TYPE); - return StringUtils.startsWithIgnoreCase(contentTypeValue, "application/grpc"); - } - - private String getGrpcStatus(HttpHeaders headers) { - final String grpcStatusValue = headers.getFirst(GRPC_STATUS_HEADER); - return StringUtils.hasText(grpcStatusValue) ? grpcStatusValue : "0"; - } - - private String getGrpcMessage(HttpHeaders headers) { - final String grpcStatusValue = headers.getFirst(GRPC_MESSAGE_HEADER); - return StringUtils.hasText(grpcStatusValue) ? grpcStatusValue : ""; - } - - @Override - public boolean supports(Type type) { - return Type.RESPONSE.equals(type); - } - - @Override - public int getOrder() { - return 0; - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/HttpHeadersFilter.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/HttpHeadersFilter.java deleted file mode 100644 index 2859eca5..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/HttpHeadersFilter.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.headers; - -import java.util.List; - -import org.springframework.http.HttpHeaders; -import org.springframework.web.server.ServerWebExchange; - -public interface HttpHeadersFilter { - - static HttpHeaders filterRequest(List filters, ServerWebExchange exchange) { - HttpHeaders headers = exchange.getRequest().getHeaders(); - return filter(filters, headers, exchange, Type.REQUEST); - } - - static HttpHeaders filter(List filters, HttpHeaders input, ServerWebExchange exchange, - Type type) { - if (filters != null) { - HttpHeaders filtered = input; - for (int i = 0; i < filters.size(); i++) { - HttpHeadersFilter filter = filters.get(i); - if (filter.supports(type)) { - filtered = filter.filter(filtered, exchange); - } - } - return filtered; - } - - return input; - } - - /** - * Filters a set of Http Headers. - * @param input Http Headers - * @param exchange a {@link ServerWebExchange} that should be filtered - * @return filtered Http Headers - */ - HttpHeaders filter(HttpHeaders input, ServerWebExchange exchange); - - default boolean supports(Type type) { - return type.equals(Type.REQUEST); - } - - enum Type { - - /** - * Filter for request headers. - */ - REQUEST, - - /** - * Filter for response headers. - */ - RESPONSE - - } - -} 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 deleted file mode 100644 index 54332534..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/RemoveHopByHopHeadersFilter.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.headers; - -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.core.Ordered; -import org.springframework.http.HttpHeaders; -import org.springframework.web.server.ServerWebExchange; - -@ConfigurationProperties("spring.cloud.gateway.filter.remove-hop-by-hop") -public class RemoveHopByHopHeadersFilter implements HttpHeadersFilter, Ordered { - - /** - * Headers to remove as the result of applying the filter. - */ - public static final Set HEADERS_REMOVED_ON_REQUEST = new HashSet<>( - Arrays.asList("connection", "keep-alive", "transfer-encoding", "te", "trailer", "proxy-authorization", - "proxy-authenticate", "x-application-context", "upgrade" - // these two are not listed in - // https://tools.ietf.org/html/draft-ietf-httpbis-p1-messaging-14#section-7.1.3 - // "proxy-connection", - // "content-length", - )); - - private int order = Ordered.LOWEST_PRECEDENCE - 1; - - private Set headers = HEADERS_REMOVED_ON_REQUEST; - - public Set getHeaders() { - return headers; - } - - public void setHeaders(Set headers) { - this.headers = headers; - } - - @Override - public int getOrder() { - return order; - } - - public void setOrder(int order) { - this.order = order; - } - - @Override - public HttpHeaders filter(HttpHeaders input, ServerWebExchange exchange) { - HttpHeaders filtered = new HttpHeaders(); - - for (Map.Entry> entry : input.entrySet()) { - if (!this.headers.contains(entry.getKey().toLowerCase())) { - filtered.addAll(entry.getKey(), entry.getValue()); - } - } - - return filtered; - } - - @Override - public boolean supports(Type type) { - return type.equals(Type.REQUEST) || type.equals(Type.RESPONSE); - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/TransferEncodingNormalizationHeadersFilter.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/TransferEncodingNormalizationHeadersFilter.java deleted file mode 100644 index e393c1ec..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/TransferEncodingNormalizationHeadersFilter.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2013-2021 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.filter.headers; - -import org.springframework.core.Ordered; -import org.springframework.http.HttpHeaders; -import org.springframework.web.server.ServerWebExchange; - -/** - * See https://datatracker.ietf.org/doc/html/rfc7230#section-3.3.3 for details. - */ -public class TransferEncodingNormalizationHeadersFilter implements HttpHeadersFilter, Ordered { - - @Override - public int getOrder() { - return 1000; - } - - @Override - public HttpHeaders filter(HttpHeaders input, ServerWebExchange exchange) { - String transferEncoding = input.getFirst(HttpHeaders.TRANSFER_ENCODING); - if (transferEncoding != null && "chunked".equalsIgnoreCase(transferEncoding.trim()) - && input.containsKey(HttpHeaders.CONTENT_LENGTH)) { - - HttpHeaders filtered = new HttpHeaders(); - // avoids read only if input is read only - filtered.addAll(input); - filtered.remove(HttpHeaders.CONTENT_LENGTH); - return filtered; - } - - return input; - } - -} 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 deleted file mode 100644 index 1623349e..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/XForwardedHeadersFilter.java +++ /dev/null @@ -1,333 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.headers; - -import java.net.URI; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; - -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.core.Ordered; -import org.springframework.http.HttpHeaders; -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.util.ObjectUtils; -import org.springframework.util.StringUtils; -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") -public class XForwardedHeadersFilter implements HttpHeadersFilter, Ordered { - - /** Default http port. */ - public static final int HTTP_PORT = 80; - - /** Default https port. */ - public static final int HTTPS_PORT = 443; - - /** Http url scheme. */ - public static final String HTTP_SCHEME = "http"; - - /** Https url scheme. */ - public static final String HTTPS_SCHEME = "https"; - - /** X-Forwarded-For Header. */ - public static final String X_FORWARDED_FOR_HEADER = "X-Forwarded-For"; - - /** X-Forwarded-Host Header. */ - public static final String X_FORWARDED_HOST_HEADER = "X-Forwarded-Host"; - - /** X-Forwarded-Port Header. */ - public static final String X_FORWARDED_PORT_HEADER = "X-Forwarded-Port"; - - /** X-Forwarded-Proto Header. */ - public static final String X_FORWARDED_PROTO_HEADER = "X-Forwarded-Proto"; - - /** X-Forwarded-Prefix Header. */ - public static final String X_FORWARDED_PREFIX_HEADER = "X-Forwarded-Prefix"; - - /** The order of the XForwardedHeadersFilter. */ - private int order = 0; - - /** 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; - - @Override - public int getOrder() { - return this.order; - } - - public void setOrder(int order) { - this.order = order; - } - - public boolean isEnabled() { - return enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - - public boolean isForEnabled() { - return forEnabled; - } - - public void setForEnabled(boolean forEnabled) { - this.forEnabled = forEnabled; - } - - public boolean isHostEnabled() { - return hostEnabled; - } - - public void setHostEnabled(boolean hostEnabled) { - this.hostEnabled = hostEnabled; - } - - public boolean isPortEnabled() { - return portEnabled; - } - - public void setPortEnabled(boolean portEnabled) { - this.portEnabled = portEnabled; - } - - public boolean isProtoEnabled() { - return protoEnabled; - } - - public void setProtoEnabled(boolean protoEnabled) { - this.protoEnabled = protoEnabled; - } - - public boolean isPrefixEnabled() { - return prefixEnabled; - } - - public void setPrefixEnabled(boolean prefixEnabled) { - this.prefixEnabled = prefixEnabled; - } - - public boolean isForAppend() { - return forAppend; - } - - public void setForAppend(boolean forAppend) { - this.forAppend = forAppend; - } - - public boolean isHostAppend() { - return hostAppend; - } - - public void setHostAppend(boolean hostAppend) { - this.hostAppend = hostAppend; - } - - public boolean isPortAppend() { - return portAppend; - } - - public void setPortAppend(boolean portAppend) { - this.portAppend = portAppend; - } - - public boolean isProtoAppend() { - return protoAppend; - } - - public void setProtoAppend(boolean protoAppend) { - this.protoAppend = protoAppend; - } - - public boolean isPrefixAppend() { - return prefixAppend; - } - - public void setPrefixAppend(boolean prefixAppend) { - this.prefixAppend = prefixAppend; - } - - @Override - public HttpHeaders filter(HttpHeaders input, ServerWebExchange exchange) { - - ServerHttpRequest request = exchange.getRequest(); - HttpHeaders original = input; - HttpHeaders updated = new HttpHeaders(); - - for (Map.Entry> entry : original.entrySet()) { - updated.addAll(entry.getKey(), entry.getValue()); - } - - if (isForEnabled() && request.getRemoteAddress() != null && request.getRemoteAddress().getAddress() != null) { - String remoteAddr = request.getRemoteAddress().getAddress().getHostAddress(); - write(updated, X_FORWARDED_FOR_HEADER, remoteAddr, isForAppend()); - } - - String proto = request.getURI().getScheme(); - if (isProtoEnabled()) { - write(updated, X_FORWARDED_PROTO_HEADER, proto, isProtoAppend()); - } - - if (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 - // to routedservice:8090/get then /prefix is the prefix - // - see XForwardedHeadersFilterTests, so first get uris, then extract paths - // and remove one from another if it's the ending part. - - LinkedHashSet originalUris = exchange.getAttribute(GATEWAY_ORIGINAL_REQUEST_URL_ATTR); - URI requestUri = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR); - - if (originalUris != null && requestUri != null) { - - originalUris.forEach(originalUri -> { - - if (originalUri != null && originalUri.getPath() != null) { - String prefix = originalUri.getPath(); - - // strip trailing slashes before checking if request path is end - // of original path - String originalUriPath = stripTrailingSlash(originalUri); - String requestUriPath = stripTrailingSlash(requestUri); - - updateRequest(updated, originalUri, originalUriPath, requestUriPath); - - } - }); - } - } - - if (isPortEnabled()) { - String port = String.valueOf(request.getURI().getPort()); - if (request.getURI().getPort() < 0) { - port = String.valueOf(getDefaultPort(proto)); - } - write(updated, X_FORWARDED_PORT_HEADER, port, isPortAppend()); - } - - if (isHostEnabled()) { - String host = toHostHeader(request); - write(updated, X_FORWARDED_HOST_HEADER, host, isHostAppend()); - } - - return updated; - } - - private void updateRequest(HttpHeaders updated, URI originalUri, String originalUriPath, String requestUriPath) { - String prefix; - 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()); - } - } - } - - private static String substringBeforeLast(String str, String separator) { - if (ObjectUtils.isEmpty(str) || ObjectUtils.isEmpty(separator)) { - return str; - } - int pos = str.lastIndexOf(separator); - if (pos == -1) { - return str; - } - return str.substring(0, pos); - } - - private void write(HttpHeaders headers, String name, String value, boolean append) { - if (value == null) { - return; - } - if (append) { - headers.add(name, value); - // these headers should be treated as a single comma separated header - List values = headers.get(name); - String delimitedValue = StringUtils.collectionToCommaDelimitedString(values); - headers.set(name, delimitedValue); - } - else { - headers.set(name, value); - } - } - - private int getDefaultPort(String scheme) { - return HTTPS_SCHEME.equals(scheme) ? HTTPS_PORT : HTTP_PORT; - } - - private boolean hasHeader(ServerHttpRequest request, String name) { - HttpHeaders headers = request.getHeaders(); - return headers.containsKey(name) && StringUtils.hasLength(headers.getFirst(name)); - } - - private String toHostHeader(ServerHttpRequest request) { - int port = request.getURI().getPort(); - String host = request.getURI().getHost(); - String scheme = request.getURI().getScheme(); - if (port < 0 || (port == HTTP_PORT && HTTP_SCHEME.equals(scheme)) - || (port == HTTPS_PORT && HTTPS_SCHEME.equals(scheme))) { - return host; - } - else { - return host + ":" + port; - } - } - - private String stripTrailingSlash(URI uri) { - if (uri.getPath().endsWith("/")) { - return uri.getPath().substring(0, uri.getPath().length() - 1); - } - else { - return uri.getPath(); - } - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/observation/DefaultGatewayObservationConvention.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/observation/DefaultGatewayObservationConvention.java deleted file mode 100644 index 1bc4e15f..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/observation/DefaultGatewayObservationConvention.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2013-2022 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.filter.headers.observation; - -import io.micrometer.common.KeyValues; -import io.micrometer.common.lang.NonNull; -import io.micrometer.common.lang.Nullable; - -import org.springframework.cloud.gateway.route.Route; -import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; -import org.springframework.http.server.reactive.ServerHttpResponse; - -import static org.springframework.cloud.gateway.filter.headers.observation.GatewayDocumentedObservation.HighCardinalityKeys.URI; -import static org.springframework.cloud.gateway.filter.headers.observation.GatewayDocumentedObservation.LowCardinalityKeys.METHOD; -import static org.springframework.cloud.gateway.filter.headers.observation.GatewayDocumentedObservation.LowCardinalityKeys.ROUTE_ID; -import static org.springframework.cloud.gateway.filter.headers.observation.GatewayDocumentedObservation.LowCardinalityKeys.ROUTE_URI; -import static org.springframework.cloud.gateway.filter.headers.observation.GatewayDocumentedObservation.LowCardinalityKeys.STATUS; - -/** - * Default implementation of the {@link GatewayObservationConvention}. - * - * @author Marcin Grzejszczak - * @since 4.0.0 - */ -public class DefaultGatewayObservationConvention implements GatewayObservationConvention { - - /** - * Default instance of the {@link DefaultGatewayObservationConvention}. - */ - public static final GatewayObservationConvention INSTANCE = new DefaultGatewayObservationConvention(); - - @Override - public KeyValues getLowCardinalityKeyValues(GatewayContext context) { - KeyValues keyValues = KeyValues.empty(); - if (context.getCarrier() == null) { - return keyValues; - } - Route route = context.getServerWebExchange().getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR); - keyValues = keyValues - .and(ROUTE_URI.withValue(route.getUri().toString()), - METHOD.withValue(context.getRequest().getMethod().name())) - .and(ROUTE_ID.withValue(route.getId())); - ServerHttpResponse response = context.getResponse(); - if (response != null && response.getStatusCode() != null) { - keyValues = keyValues.and(STATUS.withValue(String.valueOf(response.getStatusCode().value()))); - } - else { - keyValues = keyValues.and(STATUS.withValue("UNKNOWN")); - } - return keyValues; - } - - @Override - public KeyValues getHighCardinalityKeyValues(GatewayContext context) { - return KeyValues.of(URI.withValue(context.getRequest().getURI().toString())); - } - - @Override - @NonNull - public String getName() { - return "http.client.requests"; - } - - @Nullable - @Override - public String getContextualName(GatewayContext context) { - if (context.getRequest() == null) { - return null; - } - return "HTTP " + context.getRequest().getMethod(); - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/observation/GatewayContext.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/observation/GatewayContext.java deleted file mode 100644 index dbe5b5b9..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/observation/GatewayContext.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2013-2022 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.filter.headers.observation; - -import java.util.Collections; -import java.util.Objects; - -import io.micrometer.observation.transport.RequestReplySenderContext; - -import org.springframework.http.HttpHeaders; -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.http.server.reactive.ServerHttpResponse; -import org.springframework.web.server.ServerWebExchange; - -/** - * A {@link RequestReplySenderContext} for {@link ServerHttpRequest} and - * {@link ServerHttpResponse}. - * - * @author Marcin Grzejszczak - * @since 4.0.0 - */ -public class GatewayContext extends RequestReplySenderContext { - - private final ServerHttpRequest request; - - private final ServerWebExchange serverWebExchange; - - public GatewayContext(HttpHeaders headers, ServerHttpRequest request, ServerWebExchange serverWebExchange) { - super((carrier, key, value) -> Objects.requireNonNull(carrier).put(key, Collections.singletonList(value))); - this.request = request; - this.serverWebExchange = serverWebExchange; - setCarrier(headers); - } - - public ServerHttpRequest getRequest() { - return this.request; - } - - public ServerWebExchange getServerWebExchange() { - return serverWebExchange; - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/observation/GatewayDocumentedObservation.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/observation/GatewayDocumentedObservation.java deleted file mode 100644 index a88c295d..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/observation/GatewayDocumentedObservation.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright 2013-2022 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.filter.headers.observation; - -import io.micrometer.common.docs.KeyName; -import io.micrometer.common.lang.NonNullApi; -import io.micrometer.observation.Observation; -import io.micrometer.observation.ObservationConvention; -import io.micrometer.observation.docs.ObservationDocumentation; - -enum GatewayDocumentedObservation implements ObservationDocumentation { - - /** - * Observation created when sending a request through the gateway. - */ - GATEWAY_HTTP_CLIENT_OBSERVATION { - @Override - public Class> getDefaultConvention() { - return DefaultGatewayObservationConvention.class; - } - - @Override - public KeyName[] getLowCardinalityKeyNames() { - return LowCardinalityKeys.values(); - } - - @Override - public KeyName[] getHighCardinalityKeyNames() { - return HighCardinalityKeys.values(); - } - - }; - - @NonNullApi - enum LowCardinalityKeys implements KeyName { - - /** - * Route ID. - */ - ROUTE_ID { - @Override - public String asString() { - return "spring.cloud.gateway.route.id"; - } - }, - - /** - * HTTP Method. - */ - METHOD { - @Override - public String asString() { - return "http.method"; - } - }, - - /** - * HTTP Status. - */ - STATUS { - @Override - public String asString() { - return "http.status_code"; - } - }, - - /** - * HTTP URI taken from the Route. - */ - ROUTE_URI { - @Override - public String asString() { - return "spring.cloud.gateway.route.uri"; - } - } - - } - - @NonNullApi - enum HighCardinalityKeys implements KeyName { - - /** - * Full HTTP URI. - */ - URI { - @Override - public String asString() { - return "http.uri"; - } - } - - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/observation/GatewayObservationConvention.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/observation/GatewayObservationConvention.java deleted file mode 100644 index d3a685a7..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/observation/GatewayObservationConvention.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2013-2022 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.filter.headers.observation; - -import io.micrometer.observation.Observation; -import io.micrometer.observation.ObservationConvention; - -/** - * {@link ObservationConvention} for {@link GatewayContext}. - * - * @author Marcin Grzejszczak - * @since 4.0.0 - */ -public interface GatewayObservationConvention extends ObservationConvention { - - @Override - default boolean supportsContext(Observation.Context context) { - return context instanceof GatewayContext; - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/observation/GatewayPropagatingSenderTracingObservationHandler.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/observation/GatewayPropagatingSenderTracingObservationHandler.java deleted file mode 100644 index 0c267dd8..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/observation/GatewayPropagatingSenderTracingObservationHandler.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2013-2022 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.filter.headers.observation; - -import java.util.List; -import java.util.Locale; -import java.util.Objects; - -import io.micrometer.observation.Observation; -import io.micrometer.tracing.Tracer; -import io.micrometer.tracing.handler.PropagatingSenderTracingObservationHandler; -import io.micrometer.tracing.propagation.Propagator; - -/** - * Tracing header removing {@link PropagatingSenderTracingObservationHandler}. - * - * @author Marcin Grzejszczak - * @since 4.0.0 - */ -public class GatewayPropagatingSenderTracingObservationHandler - extends PropagatingSenderTracingObservationHandler { - - private final Propagator propagator; - - private final List remoteFieldsLowerCase; - - /** - * Creates a new instance of {@link PropagatingSenderTracingObservationHandler}. - * @param tracer the tracer to use to record events - * @param propagator the mechanism to propagate tracing information into the carrier - * @param remoteFields remote fields to be propagated over the wire - */ - public GatewayPropagatingSenderTracingObservationHandler(Tracer tracer, Propagator propagator, - List remoteFields) { - super(tracer, propagator); - this.propagator = propagator; - this.remoteFieldsLowerCase = remoteFields.stream().map(s -> s.toLowerCase(Locale.ROOT)).toList(); - } - - @Override - public void onStart(GatewayContext context) { - this.propagator.fields().stream() - .filter(field -> !remoteFieldsLowerCase.contains(field.toLowerCase(Locale.ROOT))) - .forEach(s -> Objects.requireNonNull(context.getCarrier()).remove(s)); - super.onStart(context); - } - - @Override - public boolean supportsContext(Observation.Context context) { - return context instanceof GatewayContext; - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/observation/ObservationClosingWebExceptionHandler.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/observation/ObservationClosingWebExceptionHandler.java deleted file mode 100644 index 94c43745..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/observation/ObservationClosingWebExceptionHandler.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2013-2022 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.filter.headers.observation; - -import io.micrometer.observation.Observation; -import reactor.core.publisher.Mono; - -import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; -import org.springframework.core.log.LogAccessor; -import org.springframework.web.server.ServerWebExchange; -import org.springframework.web.server.WebExceptionHandler; - -/** - * A {@link WebExceptionHandler} that will stop an {@link Observation} if an exception was - * thrown and there is no response to set on the context. - * - * @author Marcin Grzejszczak - * @since 4.0.0 - */ -public class ObservationClosingWebExceptionHandler implements WebExceptionHandler { - - private static final LogAccessor log = new LogAccessor(ObservationClosingWebExceptionHandler.class); - - @Override - public Mono handle(ServerWebExchange exchange, Throwable ex) { - Object attribute = exchange.getAttribute(ObservedResponseHttpHeadersFilter.OBSERVATION_STOPPED); - if (attribute == null) { - Observation observation = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_OBSERVATION_ATTR); - if (observation != null) { - if (log.isDebugEnabled()) { - observation.scoped(() -> log.debug( - () -> "An exception occurred and observation was not previously stopped, will stop it. The exception was [" - + ex + "]")); - } - observation.error(ex); - observation.stop(); - } - } - return Mono.error(ex); - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/observation/ObservedRequestHttpHeadersFilter.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/observation/ObservedRequestHttpHeadersFilter.java deleted file mode 100644 index 37d62c5b..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/observation/ObservedRequestHttpHeadersFilter.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright 2013-2022 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.filter.headers.observation; - -import io.micrometer.common.lang.Nullable; -import io.micrometer.observation.Observation; -import io.micrometer.observation.ObservationRegistry; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import reactor.util.context.ContextView; - -import org.springframework.cloud.gateway.filter.headers.HttpHeadersFilter; -import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; -import org.springframework.http.HttpHeaders; -import org.springframework.web.server.ServerWebExchange; - -/** - * Observation of {@link HttpHeadersFilter} for a request. It will start an - * {@link Observation} when the requests are being filtered. The {@link Observation} will - * be stopped when a response is being parsed via - * {@link ObservedResponseHttpHeadersFilter}. - * - * @author Marcin Grzejszczak - * @since 4.0.0 - */ -public class ObservedRequestHttpHeadersFilter implements HttpHeadersFilter { - - private static final Log log = LogFactory.getLog(ObservedRequestHttpHeadersFilter.class); - - private final ObservationRegistry observationRegistry; - - @Nullable - private final GatewayObservationConvention customGatewayObservationConvention; - - public ObservedRequestHttpHeadersFilter(ObservationRegistry observationRegistry) { - this(observationRegistry, null); - } - - public ObservedRequestHttpHeadersFilter(ObservationRegistry observationRegistry, - @Nullable GatewayObservationConvention customGatewayObservationConvention) { - this.observationRegistry = observationRegistry; - this.customGatewayObservationConvention = customGatewayObservationConvention; - } - - @Override - public HttpHeaders filter(HttpHeaders input, ServerWebExchange exchange) { - HttpHeaders newHeaders = new HttpHeaders(); - newHeaders.putAll(input); - if (log.isDebugEnabled()) { - log.debug("Will instrument the HTTP request headers " + newHeaders); - } - Observation parentObservation = getParentObservation(exchange); - GatewayContext gatewayContext = new GatewayContext(newHeaders, exchange.getRequest(), exchange); - Observation childObservation = GatewayDocumentedObservation.GATEWAY_HTTP_CLIENT_OBSERVATION.observation( - this.customGatewayObservationConvention, DefaultGatewayObservationConvention.INSTANCE, - () -> gatewayContext, this.observationRegistry); - if (parentObservation != null) { - childObservation.parentObservation(parentObservation); - } - childObservation.start(); - if (log.isDebugEnabled()) { - log.debug("Client observation " + childObservation + " created for the request. New headers are " - + newHeaders); - } - exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_OBSERVATION_ATTR, childObservation); - return newHeaders; - } - - /** - * The "micrometer.observation" key comes from - * {@link io.micrometer.observation.contextpropagation.ObservationThreadLocalAccessor} - * that requires the Context Propagation library on the classpath. Since we don't know - * if it will be there on the classpath we're referencing the key via a String and - * then we're testing its presence in tests via a fixed test dependency to Context - * Propagation and {@code ObservationThreadLocalAccessor}. - * @param exchange server web exchange - * @return parent observation or {@code null} when there is none - */ - private Observation getParentObservation(ServerWebExchange exchange) { - ContextView contextView = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REACTOR_CONTEXT_ATTR); - if (contextView == null) { - return null; - } - return contextView.getOrDefault("micrometer.observation", null); - } - - @Override - public boolean supports(Type type) { - return type.equals(Type.REQUEST); - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/observation/ObservedResponseHttpHeadersFilter.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/observation/ObservedResponseHttpHeadersFilter.java deleted file mode 100644 index 1902b6e5..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/headers/observation/ObservedResponseHttpHeadersFilter.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2013-2022 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.filter.headers.observation; - -import io.micrometer.observation.Observation; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.cloud.gateway.filter.headers.HttpHeadersFilter; -import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; -import org.springframework.http.HttpHeaders; -import org.springframework.web.server.ServerWebExchange; - -/** - * Observation representation of {@link HttpHeadersFilter} for a response. It is started - * in {@link ObservedRequestHttpHeadersFilter} and it's stopped in this class. - * - * @author Marcin Grzejszczak - * @since 4.0.0 - */ -public class ObservedResponseHttpHeadersFilter implements HttpHeadersFilter { - - private static final Log log = LogFactory.getLog(ObservedResponseHttpHeadersFilter.class); - - static final String OBSERVATION_STOPPED = "gateway.observation.stopped"; - - @Override - public HttpHeaders filter(HttpHeaders input, ServerWebExchange exchange) { - Observation childObservation = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_OBSERVATION_ATTR); - if (childObservation == null) { - return input; - } - Observation.Context childObservationContext = childObservation.getContext(); - if (childObservationContext instanceof GatewayContext context) { - if (log.isDebugEnabled()) { - log.debug("Will instrument the response"); - } - context.setResponse(exchange.getResponse()); - if (log.isDebugEnabled()) { - log.debug("The response was handled for observation " + childObservation); - } - } - childObservation.stop(); - exchange.getAttributes().put(OBSERVATION_STOPPED, "true"); - return input; - } - - @Override - public boolean supports(Type type) { - return type.equals(Type.RESPONSE); - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/ratelimit/AbstractRateLimiter.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/ratelimit/AbstractRateLimiter.java deleted file mode 100644 index 4b0e0c18..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/ratelimit/AbstractRateLimiter.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.ratelimit; - -import java.util.Map; - -import org.springframework.cloud.gateway.event.FilterArgsEvent; -import org.springframework.cloud.gateway.support.AbstractStatefulConfigurable; -import org.springframework.cloud.gateway.support.ConfigurationService; -import org.springframework.context.ApplicationListener; -import org.springframework.core.style.ToStringCreator; - -public abstract class AbstractRateLimiter extends AbstractStatefulConfigurable - implements RateLimiter, ApplicationListener { - - private String configurationPropertyName; - - private ConfigurationService configurationService; - - protected AbstractRateLimiter(Class configClass, String configurationPropertyName, - ConfigurationService configurationService) { - super(configClass); - this.configurationPropertyName = configurationPropertyName; - this.configurationService = configurationService; - } - - protected String getConfigurationPropertyName() { - return configurationPropertyName; - } - - protected void setConfigurationService(ConfigurationService configurationService) { - this.configurationService = configurationService; - } - - @Override - public void onApplicationEvent(FilterArgsEvent event) { - Map args = event.getArgs(); - - if (args.isEmpty() || !hasRelevantKey(args)) { - return; - } - - String routeId = event.getRouteId(); - - C routeConfig = newConfig(); - if (this.configurationService != null) { - this.configurationService.with(routeConfig).name(this.configurationPropertyName).normalizedProperties(args) - .bind(); - } - getConfig().put(routeId, routeConfig); - } - - private boolean hasRelevantKey(Map args) { - return args.keySet().stream().anyMatch(key -> key.startsWith(configurationPropertyName + ".")); - } - - @Override - public String toString() { - return new ToStringCreator(this).append("configurationPropertyName", configurationPropertyName) - .append("config", getConfig()).append("configClass", getConfigClass()).toString(); - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/ratelimit/KeyResolver.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/ratelimit/KeyResolver.java deleted file mode 100644 index 9e3542b2..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/ratelimit/KeyResolver.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.ratelimit; - -import reactor.core.publisher.Mono; - -import org.springframework.web.server.ServerWebExchange; - -/** - * @author Spencer Gibb - */ -public interface KeyResolver { - - Mono resolve(ServerWebExchange exchange); - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/ratelimit/PrincipalNameKeyResolver.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/ratelimit/PrincipalNameKeyResolver.java deleted file mode 100644 index 31e0242d..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/ratelimit/PrincipalNameKeyResolver.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.ratelimit; - -import reactor.core.publisher.Mono; - -import org.springframework.web.server.ServerWebExchange; - -public class PrincipalNameKeyResolver implements KeyResolver { - - /** - * {@link PrincipalNameKeyResolver} bean name. - */ - public static final String BEAN_NAME = "principalNameKeyResolver"; - - @Override - public Mono resolve(ServerWebExchange exchange) { - return exchange.getPrincipal().flatMap(p -> Mono.justOrEmpty(p.getName())); - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/ratelimit/RateLimiter.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/ratelimit/RateLimiter.java deleted file mode 100644 index 1eb37dd5..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/ratelimit/RateLimiter.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.ratelimit; - -import java.util.Collections; -import java.util.Map; - -import reactor.core.publisher.Mono; - -import org.springframework.cloud.gateway.support.StatefulConfigurable; -import org.springframework.util.Assert; - -/** - * @author Spencer Gibb - */ -public interface RateLimiter extends StatefulConfigurable { - - Mono isAllowed(String routeId, String id); - - class Response { - - private final boolean allowed; - - private final long tokensRemaining; - - private final Map headers; - - public Response(boolean allowed, Map headers) { - this.allowed = allowed; - this.tokensRemaining = -1; - Assert.notNull(headers, "headers may not be null"); - this.headers = headers; - } - - public boolean isAllowed() { - return allowed; - } - - public Map getHeaders() { - return Collections.unmodifiableMap(headers); - } - - @Override - public String toString() { - final StringBuffer sb = new StringBuffer("Response{"); - sb.append("allowed=").append(allowed); - sb.append(", headers=").append(headers); - sb.append(", tokensRemaining=").append(tokensRemaining); - sb.append('}'); - return sb.toString(); - } - - } - -} 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 deleted file mode 100644 index ab34615f..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/ratelimit/RedisRateLimiter.java +++ /dev/null @@ -1,360 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.ratelimit; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.atomic.AtomicBoolean; - -import jakarta.validation.constraints.Min; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -import org.springframework.beans.BeansException; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator; -import org.springframework.cloud.gateway.support.ConfigurationService; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; -import org.springframework.core.style.ToStringCreator; -import org.springframework.data.redis.core.ReactiveStringRedisTemplate; -import org.springframework.data.redis.core.script.RedisScript; -import org.springframework.util.Assert; -import org.springframework.validation.annotation.Validated; - -/** - * See https://stripe.com/blog/rate-limiters and - * https://gist.github.com/ptarjan/e38f45f2dfe601419ca3af937fff574d#file-1-check_request_rate_limiter-rb-L11-L34. - * - * @author Spencer Gibb - * @author Ronny Bräunlich - * @author Denis Cutic - */ -@ConfigurationProperties("spring.cloud.gateway.redis-rate-limiter") -public class RedisRateLimiter extends AbstractRateLimiter implements ApplicationContextAware { - - /** - * Redis Rate Limiter property name. - */ - public static final String CONFIGURATION_PROPERTY_NAME = "redis-rate-limiter"; - - /** - * Redis Script name. - */ - public static final String REDIS_SCRIPT_NAME = "redisRequestRateLimiterScript"; - - /** - * Remaining Rate Limit header name. - */ - public static final String REMAINING_HEADER = "X-RateLimit-Remaining"; - - /** - * Replenish Rate Limit header name. - */ - public static final String REPLENISH_RATE_HEADER = "X-RateLimit-Replenish-Rate"; - - /** - * Burst Capacity header name. - */ - public static final String BURST_CAPACITY_HEADER = "X-RateLimit-Burst-Capacity"; - - /** - * Requested Tokens header name. - */ - public static final String REQUESTED_TOKENS_HEADER = "X-RateLimit-Requested-Tokens"; - - private Log log = LogFactory.getLog(getClass()); - - private ReactiveStringRedisTemplate redisTemplate; - - private RedisScript> script; - - private AtomicBoolean initialized = new AtomicBoolean(false); - - private Config defaultConfig; - - // configuration properties - /** - * Whether or not to include headers containing rate limiter information, defaults to - * true. - */ - private boolean includeHeaders = true; - - /** - * The name of the header that returns number of remaining requests during the current - * second. - */ - private String remainingHeader = REMAINING_HEADER; - - /** The name of the header that returns the replenish rate configuration. */ - private String replenishRateHeader = REPLENISH_RATE_HEADER; - - /** The name of the header that returns the burst capacity configuration. */ - private String burstCapacityHeader = BURST_CAPACITY_HEADER; - - /** The name of the header that returns the requested tokens configuration. */ - private String requestedTokensHeader = REQUESTED_TOKENS_HEADER; - - public RedisRateLimiter(ReactiveStringRedisTemplate redisTemplate, RedisScript> script, - ConfigurationService configurationService) { - super(Config.class, CONFIGURATION_PROPERTY_NAME, configurationService); - this.redisTemplate = redisTemplate; - this.script = script; - this.initialized.compareAndSet(false, true); - } - - /** - * This creates an instance with default static configuration, useful in Java DSL. - * @param defaultReplenishRate how many tokens per second in token-bucket algorithm. - * @param defaultBurstCapacity how many tokens the bucket can hold in token-bucket - * algorithm. - */ - public RedisRateLimiter(int defaultReplenishRate, int defaultBurstCapacity) { - super(Config.class, CONFIGURATION_PROPERTY_NAME, (ConfigurationService) null); - this.defaultConfig = new Config().setReplenishRate(defaultReplenishRate).setBurstCapacity(defaultBurstCapacity); - } - - /** - * This creates an instance with default static configuration, useful in Java DSL. - * @param defaultReplenishRate how many tokens per second in token-bucket algorithm. - * @param defaultBurstCapacity how many tokens the bucket can hold in token-bucket - * algorithm. - * @param defaultRequestedTokens how many tokens are requested per request. - */ - public RedisRateLimiter(int defaultReplenishRate, int defaultBurstCapacity, int defaultRequestedTokens) { - this(defaultReplenishRate, defaultBurstCapacity); - this.defaultConfig.setRequestedTokens(defaultRequestedTokens); - } - - static List getKeys(String id) { - // use `{}` around keys to use Redis Key hash tags - // this allows for using redis cluster - - // Make a unique key per user. - String prefix = "request_rate_limiter.{" + id; - - // You need two Redis keys for Token Bucket. - String tokenKey = prefix + "}.tokens"; - String timestampKey = prefix + "}.timestamp"; - return Arrays.asList(tokenKey, timestampKey); - } - - public boolean isIncludeHeaders() { - return includeHeaders; - } - - public void setIncludeHeaders(boolean includeHeaders) { - this.includeHeaders = includeHeaders; - } - - public String getRemainingHeader() { - return remainingHeader; - } - - public void setRemainingHeader(String remainingHeader) { - this.remainingHeader = remainingHeader; - } - - public String getReplenishRateHeader() { - return replenishRateHeader; - } - - public void setReplenishRateHeader(String replenishRateHeader) { - this.replenishRateHeader = replenishRateHeader; - } - - public String getBurstCapacityHeader() { - return burstCapacityHeader; - } - - public void setBurstCapacityHeader(String burstCapacityHeader) { - this.burstCapacityHeader = burstCapacityHeader; - } - - public String getRequestedTokensHeader() { - return requestedTokensHeader; - } - - public void setRequestedTokensHeader(String requestedTokensHeader) { - this.requestedTokensHeader = requestedTokensHeader; - } - - /** - * Used when setting default configuration in constructor. - * @param context the ApplicationContext object to be used by this object - * @throws BeansException if thrown by application context methods - */ - @Override - @SuppressWarnings("unchecked") - public void setApplicationContext(ApplicationContext context) throws BeansException { - if (initialized.compareAndSet(false, true)) { - if (this.redisTemplate == null) { - this.redisTemplate = context.getBean(ReactiveStringRedisTemplate.class); - } - this.script = context.getBean(REDIS_SCRIPT_NAME, RedisScript.class); - if (context.getBeanNamesForType(ConfigurationService.class).length > 0) { - setConfigurationService(context.getBean(ConfigurationService.class)); - } - } - } - - /* for testing */ Config getDefaultConfig() { - return defaultConfig; - } - - /** - * This uses a basic token bucket algorithm and relies on the fact that Redis scripts - * execute atomically. No other operations can run between fetching the count and - * writing the new count. - */ - @Override - @SuppressWarnings("unchecked") - public Mono isAllowed(String routeId, String id) { - if (!this.initialized.get()) { - throw new IllegalStateException("RedisRateLimiter is not initialized"); - } - - Config routeConfig = loadConfiguration(routeId); - - // How many requests per second do you want a user to be allowed to do? - int replenishRate = routeConfig.getReplenishRate(); - - // How much bursting do you want to allow? - int burstCapacity = routeConfig.getBurstCapacity(); - - // How many tokens are requested per request? - int requestedTokens = routeConfig.getRequestedTokens(); - - try { - List keys = getKeys(id); - - // The arguments to the LUA script. time() returns unixtime in seconds. - List scriptArgs = Arrays.asList(replenishRate + "", burstCapacity + "", "", requestedTokens + ""); - // allowed, tokens_left = redis.eval(SCRIPT, keys, args) - Flux> flux = this.redisTemplate.execute(this.script, keys, scriptArgs); - // .log("redisratelimiter", Level.FINER); - return flux.onErrorResume(throwable -> { - if (log.isDebugEnabled()) { - log.debug("Error calling rate limiter lua", throwable); - } - return Flux.just(Arrays.asList(1L, -1L)); - }).reduce(new ArrayList(), (longs, l) -> { - longs.addAll(l); - return longs; - }).map(results -> { - boolean allowed = results.get(0) == 1L; - Long tokensLeft = results.get(1); - - Response response = new Response(allowed, getHeaders(routeConfig, tokensLeft)); - - if (log.isDebugEnabled()) { - log.debug("response: " + response); - } - return response; - }); - } - catch (Exception e) { - /* - * We don't want a hard dependency on Redis to allow traffic. Make sure to set - * an alert so you know if this is happening too much. Stripe's observed - * failure rate is 0.01%. - */ - log.error("Error determining if user allowed from redis", e); - } - return Mono.just(new Response(true, getHeaders(routeConfig, -1L))); - } - - /* for testing */ Config loadConfiguration(String routeId) { - Config routeConfig = getConfig().getOrDefault(routeId, defaultConfig); - - if (routeConfig == null) { - routeConfig = getConfig().get(RouteDefinitionRouteLocator.DEFAULT_FILTERS); - } - - if (routeConfig == null) { - throw new IllegalArgumentException("No Configuration found for route " + routeId + " or defaultFilters"); - } - return routeConfig; - } - - public Map getHeaders(Config config, Long tokensLeft) { - Map headers = new HashMap<>(); - if (isIncludeHeaders()) { - headers.put(this.remainingHeader, tokensLeft.toString()); - headers.put(this.replenishRateHeader, String.valueOf(config.getReplenishRate())); - headers.put(this.burstCapacityHeader, String.valueOf(config.getBurstCapacity())); - headers.put(this.requestedTokensHeader, String.valueOf(config.getRequestedTokens())); - } - return headers; - } - - @Validated - public static class Config { - - @Min(1) - private int replenishRate; - - @Min(0) - private int burstCapacity = 1; - - @Min(1) - private int requestedTokens = 1; - - public int getReplenishRate() { - return replenishRate; - } - - public Config setReplenishRate(int replenishRate) { - this.replenishRate = replenishRate; - return this; - } - - public int getBurstCapacity() { - return burstCapacity; - } - - public Config setBurstCapacity(int burstCapacity) { - Assert.isTrue(burstCapacity >= this.replenishRate, "BurstCapacity(" + burstCapacity - + ") must be greater than or equal than replenishRate(" + this.replenishRate + ")"); - this.burstCapacity = burstCapacity; - return this; - } - - public int getRequestedTokens() { - return requestedTokens; - } - - public Config setRequestedTokens(int requestedTokens) { - this.requestedTokens = requestedTokens; - return this; - } - - @Override - public String toString() { - return new ToStringCreator(this).append("replenishRate", replenishRate) - .append("burstCapacity", burstCapacity).append("requestedTokens", requestedTokens).toString(); - - } - - } - -} 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 deleted file mode 100644 index e15694eb..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/AsyncPredicate.java +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright 2013-2020 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.handler; - -import java.util.function.Function; -import java.util.function.Predicate; - -import org.reactivestreams.Publisher; -import reactor.core.publisher.Mono; - -import org.springframework.cloud.gateway.handler.predicate.GatewayPredicate; -import org.springframework.cloud.gateway.support.HasConfig; -import org.springframework.cloud.gateway.support.Visitor; -import org.springframework.util.Assert; -import org.springframework.web.server.ServerWebExchange; - -/** - * @author Ben Hale - */ -public interface AsyncPredicate extends Function>, HasConfig { - - default AsyncPredicate and(AsyncPredicate other) { - return new AndAsyncPredicate<>(this, other); - } - - default AsyncPredicate negate() { - return new NegateAsyncPredicate<>(this); - } - - default AsyncPredicate not(AsyncPredicate other) { - return new NegateAsyncPredicate<>(other); - } - - default AsyncPredicate or(AsyncPredicate other) { - return new OrAsyncPredicate<>(this, other); - } - - default void accept(Visitor visitor) { - visitor.visit(this); - } - - static AsyncPredicate from(Predicate predicate) { - return new DefaultAsyncPredicate<>(GatewayPredicate.wrapIfNeeded(predicate)); - } - - class DefaultAsyncPredicate implements AsyncPredicate { - - private final Predicate delegate; - - public DefaultAsyncPredicate(Predicate delegate) { - this.delegate = delegate; - } - - @Override - public Publisher apply(T t) { - return Mono.just(delegate.test(t)); - } - - @Override - public String toString() { - return this.delegate.toString(); - } - - @Override - public void accept(Visitor visitor) { - if (delegate instanceof GatewayPredicate) { - GatewayPredicate gatewayPredicate = (GatewayPredicate) delegate; - gatewayPredicate.accept(visitor); - } - } - - } - - class NegateAsyncPredicate implements AsyncPredicate { - - private final AsyncPredicate predicate; - - public NegateAsyncPredicate(AsyncPredicate predicate) { - Assert.notNull(predicate, "predicate AsyncPredicate must not be null"); - this.predicate = predicate; - } - - @Override - public Publisher apply(T t) { - return Mono.from(predicate.apply(t)).map(b -> !b); - } - - @Override - public String toString() { - return String.format("!(%s)", this.predicate); - } - - } - - class AndAsyncPredicate implements AsyncPredicate { - - private final AsyncPredicate left; - - private final AsyncPredicate right; - - public AndAsyncPredicate(AsyncPredicate left, AsyncPredicate right) { - Assert.notNull(left, "Left AsyncPredicate must not be null"); - Assert.notNull(right, "Right AsyncPredicate must not be null"); - this.left = left; - this.right = right; - } - - @Override - public Publisher apply(T t) { - return Mono.from(left.apply(t)).flatMap(result -> !result ? Mono.just(false) : Mono.from(right.apply(t))); - } - - @Override - public void accept(Visitor visitor) { - left.accept(visitor); - right.accept(visitor); - } - - @Override - public String toString() { - return String.format("(%s && %s)", this.left, this.right); - } - - } - - class OrAsyncPredicate implements AsyncPredicate { - - private final AsyncPredicate left; - - private final AsyncPredicate right; - - public OrAsyncPredicate(AsyncPredicate left, AsyncPredicate right) { - Assert.notNull(left, "Left AsyncPredicate must not be null"); - Assert.notNull(right, "Right AsyncPredicate must not be null"); - this.left = left; - this.right = right; - } - - @Override - public Publisher apply(T t) { - return Mono.from(left.apply(t)).flatMap(result -> result ? Mono.just(true) : Mono.from(right.apply(t))); - } - - @Override - public void accept(Visitor visitor) { - left.accept(visitor); - right.accept(visitor); - } - - @Override - public String toString() { - return String.format("(%s || %s)", this.left, this.right); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/FilteringWebHandler.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/FilteringWebHandler.java deleted file mode 100644 index 8be3308b..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/FilteringWebHandler.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright 2013-2020 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.handler; - -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import reactor.core.publisher.Mono; - -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.GatewayFilterChain; -import org.springframework.cloud.gateway.filter.GlobalFilter; -import org.springframework.cloud.gateway.filter.OrderedGatewayFilter; -import org.springframework.cloud.gateway.filter.factory.GatewayFilterFactory; -import org.springframework.cloud.gateway.route.Route; -import org.springframework.core.Ordered; -import org.springframework.core.annotation.AnnotationAwareOrderComparator; -import org.springframework.web.server.ServerWebExchange; -import org.springframework.web.server.WebHandler; - -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR; - -/** - * WebHandler that delegates to a chain of {@link GlobalFilter} instances and - * {@link GatewayFilterFactory} instances then to the target {@link WebHandler}. - * - * @author Rossen Stoyanchev - * @author Spencer Gibb - * @since 0.1 - */ -public class FilteringWebHandler implements WebHandler { - - protected static final Log logger = LogFactory.getLog(FilteringWebHandler.class); - - private final List globalFilters; - - public FilteringWebHandler(List globalFilters) { - this.globalFilters = loadFilters(globalFilters); - } - - private static List loadFilters(List filters) { - return filters.stream().map(filter -> { - GatewayFilterAdapter gatewayFilter = new GatewayFilterAdapter(filter); - if (filter instanceof Ordered) { - int order = ((Ordered) filter).getOrder(); - return new OrderedGatewayFilter(gatewayFilter, order); - } - return gatewayFilter; - }).collect(Collectors.toList()); - } - - /* - * TODO: relocate @EventListener(RefreshRoutesEvent.class) void handleRefresh() { - * this.combinedFiltersForRoute.clear(); - */ - - @Override - public Mono handle(ServerWebExchange exchange) { - Route route = exchange.getRequiredAttribute(GATEWAY_ROUTE_ATTR); - List gatewayFilters = route.getFilters(); - - List combined = new ArrayList<>(this.globalFilters); - combined.addAll(gatewayFilters); - // TODO: needed or cached? - AnnotationAwareOrderComparator.sort(combined); - - if (logger.isDebugEnabled()) { - logger.debug("Sorted gatewayFilterFactories: " + combined); - } - - return new DefaultGatewayFilterChain(combined).filter(exchange); - } - - private static class DefaultGatewayFilterChain implements GatewayFilterChain { - - private final int index; - - private final List filters; - - DefaultGatewayFilterChain(List filters) { - this.filters = filters; - this.index = 0; - } - - private DefaultGatewayFilterChain(DefaultGatewayFilterChain parent, int index) { - this.filters = parent.getFilters(); - this.index = index; - } - - public List getFilters() { - return filters; - } - - @Override - public Mono filter(ServerWebExchange exchange) { - return Mono.defer(() -> { - if (this.index < filters.size()) { - GatewayFilter filter = filters.get(this.index); - DefaultGatewayFilterChain chain = new DefaultGatewayFilterChain(this, this.index + 1); - return filter.filter(exchange, chain); - } - else { - return Mono.empty(); // complete - } - }); - } - - } - - private static class GatewayFilterAdapter implements GatewayFilter { - - private final GlobalFilter delegate; - - GatewayFilterAdapter(GlobalFilter delegate) { - this.delegate = delegate; - } - - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - return this.delegate.filter(exchange, chain); - } - - @Override - public String toString() { - final StringBuilder sb = new StringBuilder("GatewayFilterAdapter{"); - sb.append("delegate=").append(delegate); - sb.append('}'); - return sb.toString(); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/RoutePredicateHandlerMapping.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/RoutePredicateHandlerMapping.java deleted file mode 100644 index 2df3e0fb..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/RoutePredicateHandlerMapping.java +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright 2013-2020 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.handler; - -import java.util.function.Function; - -import reactor.core.publisher.Mono; - -import org.springframework.cloud.gateway.config.GatewayProperties; -import org.springframework.cloud.gateway.config.GlobalCorsProperties; -import org.springframework.cloud.gateway.route.Route; -import org.springframework.cloud.gateway.route.RouteLocator; -import org.springframework.core.env.Environment; -import org.springframework.web.cors.CorsConfiguration; -import org.springframework.web.reactive.handler.AbstractHandlerMapping; -import org.springframework.web.server.ServerWebExchange; - -import static org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping.ManagementPortType.DIFFERENT; -import static org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping.ManagementPortType.DISABLED; -import static org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping.ManagementPortType.SAME; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_HANDLER_MAPPER_ATTR; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_PREDICATE_ROUTE_ATTR; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REACTOR_CONTEXT_ATTR; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR; - -/** - * @author Spencer Gibb - */ -public class RoutePredicateHandlerMapping extends AbstractHandlerMapping { - - private final FilteringWebHandler webHandler; - - private final RouteLocator routeLocator; - - private final Integer managementPort; - - private final ManagementPortType managementPortType; - - public RoutePredicateHandlerMapping(FilteringWebHandler webHandler, RouteLocator routeLocator, - GlobalCorsProperties globalCorsProperties, Environment environment) { - this.webHandler = webHandler; - this.routeLocator = routeLocator; - - this.managementPort = getPortProperty(environment, "management.server."); - this.managementPortType = getManagementPortType(environment); - setOrder(environment.getProperty(GatewayProperties.PREFIX + ".handler-mapping.order", Integer.class, 1)); - setCorsConfigurations(globalCorsProperties.getCorsConfigurations()); - } - - private ManagementPortType getManagementPortType(Environment environment) { - Integer serverPort = getPortProperty(environment, "server."); - if (this.managementPort != null && this.managementPort < 0) { - return DISABLED; - } - return ((this.managementPort == null || (serverPort == null && this.managementPort.equals(8080)) - || (this.managementPort != 0 && this.managementPort.equals(serverPort))) ? SAME : DIFFERENT); - } - - private static Integer getPortProperty(Environment environment, String prefix) { - return environment.getProperty(prefix + "port", Integer.class); - } - - @Override - protected Mono getHandlerInternal(ServerWebExchange exchange) { - // don't handle requests on management port if set and different than server port - if (this.managementPortType == DIFFERENT && this.managementPort != null - && exchange.getRequest().getLocalAddress() != null - && exchange.getRequest().getLocalAddress().getPort() == this.managementPort) { - return Mono.empty(); - } - exchange.getAttributes().put(GATEWAY_HANDLER_MAPPER_ATTR, getSimpleName()); - - return Mono.deferContextual(contextView -> { - exchange.getAttributes().put(GATEWAY_REACTOR_CONTEXT_ATTR, contextView); - return lookupRoute(exchange) - // .log("route-predicate-handler-mapping", Level.FINER) //name this - .flatMap((Function>) r -> { - exchange.getAttributes().remove(GATEWAY_PREDICATE_ROUTE_ATTR); - if (logger.isDebugEnabled()) { - logger.debug("Mapping [" + getExchangeDesc(exchange) + "] to " + r); - } - - exchange.getAttributes().put(GATEWAY_ROUTE_ATTR, r); - return Mono.just(webHandler); - }).switchIfEmpty(Mono.empty().then(Mono.fromRunnable(() -> { - exchange.getAttributes().remove(GATEWAY_PREDICATE_ROUTE_ATTR); - if (logger.isTraceEnabled()) { - logger.trace("No RouteDefinition found for [" + getExchangeDesc(exchange) + "]"); - } - }))); - }); - } - - @Override - protected CorsConfiguration getCorsConfiguration(Object handler, ServerWebExchange exchange) { - // TODO: support cors configuration via properties on a route see gh-229 - // see RequestMappingHandlerMapping.initCorsConfiguration() - // also see - // https://github.com/spring-projects/spring-framework/blob/master/spring-web/src/test/java/org/springframework/web/cors/reactive/CorsWebFilterTests.java - return super.getCorsConfiguration(handler, exchange); - } - - // TODO: get desc from factory? - private String getExchangeDesc(ServerWebExchange exchange) { - StringBuilder out = new StringBuilder(); - out.append("Exchange: "); - out.append(exchange.getRequest().getMethod()); - out.append(" "); - out.append(exchange.getRequest().getURI()); - return out.toString(); - } - - protected Mono lookupRoute(ServerWebExchange exchange) { - return this.routeLocator.getRoutes() - // individually filter routes so that filterWhen error delaying is not a - // problem - .concatMap(route -> Mono.just(route).filterWhen(r -> { - // add the current route we are testing - exchange.getAttributes().put(GATEWAY_PREDICATE_ROUTE_ATTR, r.getId()); - return r.getPredicate().apply(exchange); - }) - // instead of immediately stopping main flux due to error, log and - // swallow it - .doOnError(e -> logger.error("Error applying predicate for route: " + route.getId(), e)) - .onErrorResume(e -> Mono.empty())) - // .defaultIfEmpty() put a static Route not found - // or .switchIfEmpty() - // .switchIfEmpty(Mono.empty().log("noroute")) - .next() - // TODO: error handling - .map(route -> { - if (logger.isDebugEnabled()) { - logger.debug("Route matched: " + route.getId()); - } - validateRoute(route, exchange); - return route; - }); - - /* - * TODO: trace logging if (logger.isTraceEnabled()) { - * logger.trace("RouteDefinition did not match: " + routeDefinition.getId()); } - */ - } - - /** - * Validate the given handler against the current request. - *

- * The default implementation is empty. Can be overridden in subclasses, for example - * to enforce specific preconditions expressed in URL mappings. - * @param route the Route object to validate - * @param exchange current exchange - * @throws Exception if validation failed - */ - @SuppressWarnings("UnusedParameters") - protected void validateRoute(Route route, ServerWebExchange exchange) { - } - - protected String getSimpleName() { - return "RoutePredicateHandlerMapping"; - } - - public enum ManagementPortType { - - /** - * The management port has been disabled. - */ - DISABLED, - - /** - * The management port is the same as the server port. - */ - SAME, - - /** - * The management port and server port are different. - */ - DIFFERENT; - - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/AbstractRoutePredicateFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/AbstractRoutePredicateFactory.java deleted file mode 100644 index c60ad60f..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/AbstractRoutePredicateFactory.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2013-2020 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.handler.predicate; - -import org.springframework.cloud.gateway.support.AbstractConfigurable; - -public abstract class AbstractRoutePredicateFactory extends AbstractConfigurable - implements RoutePredicateFactory { - - public AbstractRoutePredicateFactory(Class configClass) { - super(configClass); - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/AfterRoutePredicateFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/AfterRoutePredicateFactory.java deleted file mode 100644 index fc9ff192..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/AfterRoutePredicateFactory.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2013-2020 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.handler.predicate; - -import java.time.ZonedDateTime; -import java.util.Collections; -import java.util.List; -import java.util.function.Predicate; - -import jakarta.validation.constraints.NotNull; - -import org.springframework.web.server.ServerWebExchange; - -/** - * @author Spencer Gibb - */ -public class AfterRoutePredicateFactory extends AbstractRoutePredicateFactory { - - /** - * DateTime key. - */ - public static final String DATETIME_KEY = "datetime"; - - public AfterRoutePredicateFactory() { - super(Config.class); - } - - @Override - public List shortcutFieldOrder() { - return Collections.singletonList(DATETIME_KEY); - } - - @Override - public Predicate apply(Config config) { - return new GatewayPredicate() { - @Override - public boolean test(ServerWebExchange serverWebExchange) { - final ZonedDateTime now = ZonedDateTime.now(); - return now.isAfter(config.getDatetime()); - } - - @Override - public Object getConfig() { - return config; - } - - @Override - public String toString() { - return String.format("After: %s", config.getDatetime()); - } - }; - } - - public static class Config { - - @NotNull - private ZonedDateTime datetime; - - public ZonedDateTime getDatetime() { - return datetime; - } - - public void setDatetime(ZonedDateTime datetime) { - this.datetime = datetime; - } - - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/BeforeRoutePredicateFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/BeforeRoutePredicateFactory.java deleted file mode 100644 index 47037e8c..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/BeforeRoutePredicateFactory.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2013-2020 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.handler.predicate; - -import java.time.ZonedDateTime; -import java.util.Collections; -import java.util.List; -import java.util.function.Predicate; - -import org.springframework.web.server.ServerWebExchange; - -/** - * @author Spencer Gibb - */ -public class BeforeRoutePredicateFactory extends AbstractRoutePredicateFactory { - - /** - * DateTime key. - */ - public static final String DATETIME_KEY = "datetime"; - - public BeforeRoutePredicateFactory() { - super(Config.class); - } - - @Override - public List shortcutFieldOrder() { - return Collections.singletonList(DATETIME_KEY); - } - - @Override - public Predicate apply(Config config) { - return new GatewayPredicate() { - @Override - public boolean test(ServerWebExchange serverWebExchange) { - final ZonedDateTime now = ZonedDateTime.now(); - return now.isBefore(config.getDatetime()); - } - - @Override - public Object getConfig() { - return config; - } - - @Override - public String toString() { - return String.format("Before: %s", config.getDatetime()); - } - }; - } - - public static class Config { - - private ZonedDateTime datetime; - - public ZonedDateTime getDatetime() { - return datetime; - } - - public void setDatetime(ZonedDateTime datetime) { - this.datetime = datetime; - } - - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/BetweenRoutePredicateFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/BetweenRoutePredicateFactory.java deleted file mode 100644 index c55bd4e8..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/BetweenRoutePredicateFactory.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright 2013-2020 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.handler.predicate; - -import java.time.ZonedDateTime; -import java.util.Arrays; -import java.util.List; -import java.util.function.Predicate; - -import jakarta.validation.constraints.NotNull; - -import org.springframework.util.Assert; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.server.ServerWebExchange; - -/** - * @author Spencer Gibb - */ -public class BetweenRoutePredicateFactory extends AbstractRoutePredicateFactory { - - /** - * DateTime 1 key. - */ - public static final String DATETIME1_KEY = "datetime1"; - - /** - * DateTime 2 key. - */ - public static final String DATETIME2_KEY = "datetime2"; - - public BetweenRoutePredicateFactory() { - super(Config.class); - } - - @Override - public List shortcutFieldOrder() { - return Arrays.asList(DATETIME1_KEY, DATETIME2_KEY); - } - - @Override - public Predicate apply(Config config) { - Assert.isTrue(config.getDatetime1().isBefore(config.getDatetime2()), - config.getDatetime1() + " must be before " + config.getDatetime2()); - - return new GatewayPredicate() { - @Override - public boolean test(ServerWebExchange serverWebExchange) { - final ZonedDateTime now = ZonedDateTime.now(); - return now.isAfter(config.getDatetime1()) && now.isBefore(config.getDatetime2()); - } - - @Override - public Object getConfig() { - return config; - } - - @Override - public String toString() { - return String.format("Between: %s and %s", config.getDatetime1(), config.getDatetime2()); - } - }; - } - - @Validated - public static class Config { - - @NotNull - private ZonedDateTime datetime1; - - @NotNull - private ZonedDateTime datetime2; - - public ZonedDateTime getDatetime1() { - return datetime1; - } - - public Config setDatetime1(ZonedDateTime datetime1) { - this.datetime1 = datetime1; - return this; - } - - public ZonedDateTime getDatetime2() { - return datetime2; - } - - public Config setDatetime2(ZonedDateTime datetime2) { - this.datetime2 = datetime2; - return this; - } - - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/CloudFoundryRouteServiceRoutePredicateFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/CloudFoundryRouteServiceRoutePredicateFactory.java deleted file mode 100644 index 45c06c2a..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/CloudFoundryRouteServiceRoutePredicateFactory.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2013-2020 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.handler.predicate; - -import java.util.function.Predicate; - -import org.springframework.web.server.ServerWebExchange; - -/** - * Creates a predicate which indicates if the request is intended for a Cloud Foundry - * Route Service. - * - * @author Andrew Fitzgerald - * @see Cloud Foundry - * Route Service documentation - */ -public class CloudFoundryRouteServiceRoutePredicateFactory extends AbstractRoutePredicateFactory { - - /** - * Forwarded URL header name. - */ - public static final String X_CF_FORWARDED_URL = "X-CF-Forwarded-Url"; - - /** - * Proxy signature header name. - */ - public static final String X_CF_PROXY_SIGNATURE = "X-CF-Proxy-Signature"; - - /** - * Proxy metadata header name. - */ - public static final String X_CF_PROXY_METADATA = "X-CF-Proxy-Metadata"; - - private final HeaderRoutePredicateFactory factory = new HeaderRoutePredicateFactory(); - - public CloudFoundryRouteServiceRoutePredicateFactory() { - super(Object.class); - } - - @Override - public Predicate apply(Object unused) { - return headerPredicate(X_CF_FORWARDED_URL).and(headerPredicate(X_CF_PROXY_SIGNATURE)) - .and(headerPredicate(X_CF_PROXY_METADATA)); - } - - private Predicate headerPredicate(String header) { - HeaderRoutePredicateFactory.Config config = factory.newConfig(); - config.setHeader(header); - config.setRegexp(".*"); - return factory.apply(config); - } - -} 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 deleted file mode 100644 index 7b48397e..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/CookieRoutePredicateFactory.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright 2013-2020 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.handler.predicate; - -import java.util.Arrays; -import java.util.List; -import java.util.function.Predicate; - -import jakarta.validation.constraints.NotEmpty; - -import org.springframework.http.HttpCookie; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.server.ServerWebExchange; - -/** - * @author Spencer Gibb - */ -public class CookieRoutePredicateFactory extends AbstractRoutePredicateFactory { - - /** - * Name key. - */ - public static final String NAME_KEY = "name"; - - /** - * Regexp key. - */ - public static final String REGEXP_KEY = "regexp"; - - public CookieRoutePredicateFactory() { - super(Config.class); - } - - @Override - public List shortcutFieldOrder() { - return Arrays.asList(NAME_KEY, REGEXP_KEY); - } - - @Override - public Predicate apply(Config config) { - return new GatewayPredicate() { - @Override - public boolean test(ServerWebExchange exchange) { - List cookies = exchange.getRequest().getCookies().get(config.name); - if (cookies == null) { - return false; - } - for (HttpCookie cookie : cookies) { - if (cookie.getValue().matches(config.regexp)) { - return true; - } - } - return false; - } - - @Override - public Object getConfig() { - return config; - } - - @Override - public String toString() { - return String.format("Cookie: name=%s regexp=%s", config.name, config.regexp); - } - }; - } - - @Validated - public static class Config { - - @NotEmpty - private String name; - - @NotEmpty - private String regexp; - - public String getName() { - return name; - } - - public Config setName(String name) { - this.name = name; - return this; - } - - public String getRegexp() { - return regexp; - } - - public Config setRegexp(String regexp) { - this.regexp = regexp; - return this; - } - - } - -} 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 deleted file mode 100644 index c373296a..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/GatewayPredicate.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright 2013-2020 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.handler.predicate; - -import java.util.function.Predicate; - -import org.springframework.cloud.gateway.support.HasConfig; -import org.springframework.cloud.gateway.support.Visitor; -import org.springframework.util.Assert; -import org.springframework.web.server.ServerWebExchange; - -public interface GatewayPredicate extends Predicate, HasConfig { - - @Override - default Predicate and(Predicate other) { - return new AndGatewayPredicate(this, wrapIfNeeded(other)); - } - - @Override - default Predicate negate() { - return new NegateGatewayPredicate(this); - } - - @Override - default Predicate or(Predicate other) { - return new OrGatewayPredicate(this, wrapIfNeeded(other)); - } - - default void accept(Visitor visitor) { - visitor.visit(this); - } - - static GatewayPredicate wrapIfNeeded(Predicate other) { - GatewayPredicate right; - - if (other instanceof GatewayPredicate) { - right = (GatewayPredicate) other; - } - else { - right = new GatewayPredicateWrapper(other); - } - return right; - } - - class GatewayPredicateWrapper implements GatewayPredicate { - - private final Predicate delegate; - - public GatewayPredicateWrapper(Predicate delegate) { - Assert.notNull(delegate, "delegate GatewayPredicate must not be null"); - this.delegate = delegate; - } - - @Override - public boolean test(ServerWebExchange exchange) { - return this.delegate.test(exchange); - } - - @Override - public void accept(Visitor visitor) { - if (delegate instanceof GatewayPredicate) { - GatewayPredicate gatewayPredicate = (GatewayPredicate) delegate; - gatewayPredicate.accept(visitor); - } - } - - @Override - public String toString() { - return this.delegate.getClass().getSimpleName(); - } - - } - - class NegateGatewayPredicate implements GatewayPredicate { - - private final GatewayPredicate predicate; - - public NegateGatewayPredicate(GatewayPredicate predicate) { - Assert.notNull(predicate, "predicate GatewayPredicate must not be null"); - this.predicate = predicate; - } - - @Override - public boolean test(ServerWebExchange t) { - return !this.predicate.test(t); - } - - @Override - public void accept(Visitor visitor) { - predicate.accept(visitor); - } - - @Override - public String toString() { - return String.format("!%s", this.predicate); - } - - } - - class AndGatewayPredicate implements GatewayPredicate { - - private final GatewayPredicate left; - - private final GatewayPredicate right; - - public AndGatewayPredicate(GatewayPredicate left, GatewayPredicate right) { - Assert.notNull(left, "Left GatewayPredicate must not be null"); - Assert.notNull(right, "Right GatewayPredicate must not be null"); - this.left = left; - this.right = right; - } - - @Override - public boolean test(ServerWebExchange t) { - return (this.left.test(t) && this.right.test(t)); - } - - @Override - public void accept(Visitor visitor) { - left.accept(visitor); - right.accept(visitor); - } - - @Override - public String toString() { - return String.format("(%s && %s)", this.left, this.right); - } - - } - - class OrGatewayPredicate implements GatewayPredicate { - - private final GatewayPredicate left; - - private final GatewayPredicate right; - - public OrGatewayPredicate(GatewayPredicate left, GatewayPredicate right) { - Assert.notNull(left, "Left GatewayPredicate must not be null"); - Assert.notNull(right, "Right GatewayPredicate must not be null"); - this.left = left; - this.right = right; - } - - @Override - public boolean test(ServerWebExchange t) { - return (this.left.test(t) || this.right.test(t)); - } - - @Override - public void accept(Visitor visitor) { - left.accept(visitor); - right.accept(visitor); - } - - @Override - public String toString() { - return String.format("(%s || %s)", this.left, this.right); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/HeaderRoutePredicateFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/HeaderRoutePredicateFactory.java deleted file mode 100644 index d5382866..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/HeaderRoutePredicateFactory.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright 2013-2020 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.handler.predicate; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.function.Predicate; -import java.util.regex.Pattern; - -import jakarta.validation.constraints.NotEmpty; - -import org.springframework.util.StringUtils; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.server.ServerWebExchange; - -/** - * @author Spencer Gibb - */ -public class HeaderRoutePredicateFactory extends AbstractRoutePredicateFactory { - - /** - * Header key. - */ - public static final String HEADER_KEY = "header"; - - /** - * Regexp key. - */ - public static final String REGEXP_KEY = "regexp"; - - public HeaderRoutePredicateFactory() { - super(Config.class); - } - - @Override - public List shortcutFieldOrder() { - return Arrays.asList(HEADER_KEY, REGEXP_KEY); - } - - @Override - public Predicate apply(Config config) { - Pattern pattern = (StringUtils.hasText(config.regexp)) ? Pattern.compile(config.regexp) : null; - - return new GatewayPredicate() { - @Override - public boolean test(ServerWebExchange exchange) { - List values = exchange.getRequest().getHeaders().getOrDefault(config.header, - Collections.emptyList()); - if (values.isEmpty()) { - return false; - } - // values is now guaranteed to not be empty - if (pattern != null) { - // check if a header value matches - for (int i = 0; i < values.size(); i++) { - String value = values.get(i); - if (pattern.asMatchPredicate().test(value)) { - return true; - } - } - return false; - } - - // there is a value and since regexp is empty, we only check existence. - return true; - } - - @Override - public Object getConfig() { - return config; - } - - @Override - public String toString() { - return String.format("Header: %s regexp=%s", config.header, config.regexp); - } - }; - } - - @Validated - public static class Config { - - @NotEmpty - private String header; - - private String regexp; - - public String getHeader() { - return header; - } - - public Config setHeader(String header) { - this.header = header; - return this; - } - - public String getRegexp() { - return regexp; - } - - public Config setRegexp(String regexp) { - this.regexp = regexp; - return this; - } - - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/HostRoutePredicateFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/HostRoutePredicateFactory.java deleted file mode 100644 index f9b51114..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/HostRoutePredicateFactory.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright 2013-2020 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.handler.predicate; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.function.Predicate; - -import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; -import org.springframework.core.style.ToStringCreator; -import org.springframework.util.AntPathMatcher; -import org.springframework.util.PathMatcher; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.server.ServerWebExchange; - -/** - * @author Spencer Gibb - */ -public class HostRoutePredicateFactory extends AbstractRoutePredicateFactory { - - private PathMatcher pathMatcher = new AntPathMatcher("."); - - public HostRoutePredicateFactory() { - super(Config.class); - } - - public void setPathMatcher(PathMatcher pathMatcher) { - this.pathMatcher = pathMatcher; - } - - @Override - public List shortcutFieldOrder() { - return Collections.singletonList("patterns"); - } - - @Override - public ShortcutType shortcutType() { - return ShortcutType.GATHER_LIST; - } - - @Override - public Predicate apply(Config config) { - return new GatewayPredicate() { - @Override - public boolean test(ServerWebExchange exchange) { - String host = exchange.getRequest().getHeaders().getFirst("Host"); - String match = null; - for (int i = 0; i < config.getPatterns().size(); i++) { - String pattern = config.getPatterns().get(i); - if (pathMatcher.match(pattern, host)) { - match = pattern; - break; - } - } - - if (match != null) { - Map variables = pathMatcher.extractUriTemplateVariables(match, host); - ServerWebExchangeUtils.putUriTemplateVariables(exchange, variables); - return true; - } - - return false; - } - - @Override - public Object getConfig() { - return config; - } - - @Override - public String toString() { - return String.format("Hosts: %s", config.getPatterns()); - } - }; - } - - @Validated - public static class Config { - - private List patterns = new ArrayList<>(); - - public List getPatterns() { - return patterns; - } - - public Config setPatterns(List patterns) { - this.patterns = patterns; - return this; - } - - @Override - public String toString() { - return new ToStringCreator(this).append("patterns", patterns).toString(); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/MethodRoutePredicateFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/MethodRoutePredicateFactory.java deleted file mode 100644 index 009b7680..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/MethodRoutePredicateFactory.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2013-2020 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.handler.predicate; - -import java.util.Arrays; -import java.util.List; -import java.util.function.Predicate; - -import org.springframework.http.HttpMethod; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.server.ServerWebExchange; - -import static java.util.Arrays.stream; - -/** - * @author Spencer Gibb - * @author Dennis Menge - */ -public class MethodRoutePredicateFactory extends AbstractRoutePredicateFactory { - - /** - * Methods key. - */ - public static final String METHODS_KEY = "methods"; - - public MethodRoutePredicateFactory() { - super(Config.class); - } - - @Override - public List shortcutFieldOrder() { - return Arrays.asList(METHODS_KEY); - } - - @Override - public ShortcutType shortcutType() { - return ShortcutType.GATHER_LIST; - } - - @Override - public Predicate apply(Config config) { - return new GatewayPredicate() { - @Override - public boolean test(ServerWebExchange exchange) { - HttpMethod requestMethod = exchange.getRequest().getMethod(); - return stream(config.getMethods()).anyMatch(httpMethod -> httpMethod == requestMethod); - } - - @Override - public String toString() { - return String.format("Methods: %s", Arrays.toString(config.getMethods())); - } - }; - } - - @Validated - public static class Config { - - private HttpMethod[] methods; - - public HttpMethod[] getMethods() { - return methods; - } - - public void setMethods(HttpMethod... methods) { - this.methods = methods; - } - - } - -} 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 deleted file mode 100644 index f4a1d7c7..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicateFactory.java +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright 2013-2020 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.handler.predicate; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.function.Predicate; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.core.style.ToStringCreator; -import org.springframework.http.server.PathContainer; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.server.ServerWebExchange; -import org.springframework.web.util.pattern.PathPattern; -import org.springframework.web.util.pattern.PathPattern.PathMatchInfo; -import org.springframework.web.util.pattern.PathPatternParser; - -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_PREDICATE_MATCHED_PATH_ATTR; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_PREDICATE_MATCHED_PATH_ROUTE_ID_ATTR; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_PREDICATE_PATH_CONTAINER_ATTR; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_PREDICATE_ROUTE_ATTR; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.putUriTemplateVariables; -import static org.springframework.http.server.PathContainer.parsePath; - -/** - * @author Spencer Gibb - * @author Dhawal Kapil - */ -public class PathRoutePredicateFactory extends AbstractRoutePredicateFactory { - - private static final Log log = LogFactory.getLog(PathRoutePredicateFactory.class); - - private static final String MATCH_TRAILING_SLASH = "matchTrailingSlash"; - - private PathPatternParser pathPatternParser = new PathPatternParser(); - - public PathRoutePredicateFactory() { - super(Config.class); - } - - private static void traceMatch(String prefix, Object desired, Object actual, boolean match) { - if (log.isTraceEnabled()) { - String message = String.format("%s \"%s\" %s against value \"%s\"", prefix, desired, - match ? "matches" : "does not match", actual); - log.trace(message); - } - } - - public void setPathPatternParser(PathPatternParser pathPatternParser) { - this.pathPatternParser = pathPatternParser; - } - - @Override - public List shortcutFieldOrder() { - return Arrays.asList("patterns", MATCH_TRAILING_SLASH); - } - - @Override - public ShortcutType shortcutType() { - return ShortcutType.GATHER_LIST_TAIL_FLAG; - } - - @Override - public Predicate apply(Config config) { - final ArrayList pathPatterns = new ArrayList<>(); - synchronized (this.pathPatternParser) { - pathPatternParser.setMatchOptionalTrailingSeparator(config.isMatchTrailingSlash()); - config.getPatterns().forEach(pattern -> { - PathPattern pathPattern = this.pathPatternParser.parse(pattern); - pathPatterns.add(pathPattern); - }); - } - return new GatewayPredicate() { - @Override - public boolean test(ServerWebExchange exchange) { - PathContainer path = (PathContainer) exchange.getAttributes().computeIfAbsent( - GATEWAY_PREDICATE_PATH_CONTAINER_ATTR, - s -> parsePath(exchange.getRequest().getURI().getRawPath())); - - PathPattern match = null; - for (int i = 0; i < pathPatterns.size(); i++) { - PathPattern pathPattern = pathPatterns.get(i); - if (pathPattern.matches(path)) { - match = pathPattern; - break; - } - } - - if (match != null) { - traceMatch("Pattern", match.getPatternString(), path, true); - PathMatchInfo pathMatchInfo = match.matchAndExtract(path); - putUriTemplateVariables(exchange, pathMatchInfo.getUriVariables()); - exchange.getAttributes().put(GATEWAY_PREDICATE_MATCHED_PATH_ATTR, match.getPatternString()); - String routeId = (String) exchange.getAttributes().get(GATEWAY_PREDICATE_ROUTE_ATTR); - if (routeId != null) { - // populated in RoutePredicateHandlerMapping - exchange.getAttributes().put(GATEWAY_PREDICATE_MATCHED_PATH_ROUTE_ID_ATTR, routeId); - } - return true; - } - else { - traceMatch("Pattern", config.getPatterns(), path, false); - return false; - } - } - - @Override - public Object getConfig() { - return config; - } - - @Override - public String toString() { - return String.format("Paths: %s, match trailing slash: %b", config.getPatterns(), - config.isMatchTrailingSlash()); - } - }; - } - - @Validated - public static class Config { - - private List patterns = new ArrayList<>(); - - private boolean matchTrailingSlash = true; - - public List getPatterns() { - return patterns; - } - - public Config setPatterns(List patterns) { - this.patterns = patterns; - return this; - } - - /** - * @deprecated use {@link #isMatchTrailingSlash()} - */ - @Deprecated - public boolean isMatchOptionalTrailingSeparator() { - return isMatchTrailingSlash(); - } - - /** - * @deprecated use {@link #setMatchTrailingSlash(boolean)} - */ - @Deprecated - public Config setMatchOptionalTrailingSeparator(boolean matchOptionalTrailingSeparator) { - setMatchTrailingSlash(matchOptionalTrailingSeparator); - return this; - } - - public boolean isMatchTrailingSlash() { - return matchTrailingSlash; - } - - public Config setMatchTrailingSlash(boolean matchTrailingSlash) { - this.matchTrailingSlash = matchTrailingSlash; - return this; - } - - @Override - public String toString() { - return new ToStringCreator(this).append("patterns", patterns) - .append(MATCH_TRAILING_SLASH, matchTrailingSlash).toString(); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/PredicateDefinition.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/PredicateDefinition.java deleted file mode 100644 index 86c40cac..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/PredicateDefinition.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright 2013-2020 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.handler.predicate; - -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Objects; - -import jakarta.validation.ValidationException; -import jakarta.validation.constraints.NotNull; - -import org.springframework.cloud.gateway.support.NameUtils; -import org.springframework.validation.annotation.Validated; - -import static org.springframework.util.StringUtils.tokenizeToStringArray; - -/** - * @author Spencer Gibb - */ -@Validated -public class PredicateDefinition { - - @NotNull - private String name; - - private Map args = new LinkedHashMap<>(); - - public PredicateDefinition() { - } - - public PredicateDefinition(String text) { - int eqIdx = text.indexOf('='); - if (eqIdx <= 0) { - throw new ValidationException( - "Unable to parse PredicateDefinition text '" + text + "'" + ", must be of the form name=value"); - } - setName(text.substring(0, eqIdx)); - - String[] args = tokenizeToStringArray(text.substring(eqIdx + 1), ","); - - for (int i = 0; i < args.length; i++) { - this.args.put(NameUtils.generateName(i), args[i]); - } - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public Map getArgs() { - return args; - } - - public void setArgs(Map args) { - this.args = args; - } - - public void addArg(String key, String value) { - this.args.put(key, value); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - PredicateDefinition that = (PredicateDefinition) o; - return Objects.equals(name, that.name) && Objects.equals(args, that.args); - } - - @Override - public int hashCode() { - return Objects.hash(name, args); - } - - @Override - public String toString() { - final StringBuilder sb = new StringBuilder("PredicateDefinition{"); - sb.append("name='").append(name).append('\''); - sb.append(", args=").append(args); - sb.append('}'); - return sb.toString(); - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/QueryRoutePredicateFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/QueryRoutePredicateFactory.java deleted file mode 100644 index 2dd37fbd..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/QueryRoutePredicateFactory.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright 2013-2020 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.handler.predicate; - -import java.util.Arrays; -import java.util.List; -import java.util.function.Predicate; - -import jakarta.validation.constraints.NotEmpty; - -import org.springframework.util.StringUtils; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.server.ServerWebExchange; - -/** - * @author Spencer Gibb - */ -public class QueryRoutePredicateFactory extends AbstractRoutePredicateFactory { - - /** - * Param key. - */ - public static final String PARAM_KEY = "param"; - - /** - * Regexp key. - */ - public static final String REGEXP_KEY = "regexp"; - - public QueryRoutePredicateFactory() { - super(Config.class); - } - - @Override - public List shortcutFieldOrder() { - return Arrays.asList(PARAM_KEY, REGEXP_KEY); - } - - @Override - public Predicate apply(Config config) { - return new GatewayPredicate() { - @Override - public boolean test(ServerWebExchange exchange) { - if (!StringUtils.hasText(config.regexp)) { - // check existence of header - return exchange.getRequest().getQueryParams().containsKey(config.param); - } - - List values = exchange.getRequest().getQueryParams().get(config.param); - if (values == null) { - return false; - } - for (String value : values) { - if (value != null && value.matches(config.regexp)) { - return true; - } - } - return false; - } - - @Override - public Object getConfig() { - return config; - } - - @Override - public String toString() { - return String.format("Query: param=%s regexp=%s", config.getParam(), config.getRegexp()); - } - }; - } - - @Validated - public static class Config { - - @NotEmpty - private String param; - - private String regexp; - - public String getParam() { - return param; - } - - public Config setParam(String param) { - this.param = param; - return this; - } - - public String getRegexp() { - return regexp; - } - - public Config setRegexp(String regexp) { - this.regexp = regexp; - return this; - } - - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/ReadBodyRoutePredicateFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/ReadBodyRoutePredicateFactory.java deleted file mode 100644 index ae4e8ce5..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/ReadBodyRoutePredicateFactory.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright 2013-2020 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.handler.predicate; - -import java.util.List; -import java.util.Map; -import java.util.function.Predicate; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.reactivestreams.Publisher; -import reactor.core.publisher.Mono; - -import org.springframework.cloud.gateway.handler.AsyncPredicate; -import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; -import org.springframework.http.codec.HttpMessageReader; -import org.springframework.web.reactive.function.server.HandlerStrategies; -import org.springframework.web.reactive.function.server.ServerRequest; -import org.springframework.web.server.ServerWebExchange; - -/** - * Predicate that reads the body and applies a user provided predicate to run on the body. - * The body is cached in memory so that possible subsequent calls to the predicate do not - * need to deserialize again. - */ -public class ReadBodyRoutePredicateFactory extends AbstractRoutePredicateFactory { - - protected static final Log log = LogFactory.getLog(ReadBodyRoutePredicateFactory.class); - - private static final String TEST_ATTRIBUTE = "read_body_predicate_test_attribute"; - - private static final String CACHE_REQUEST_BODY_OBJECT_KEY = "cachedRequestBodyObject"; - - private final List> messageReaders; - - public ReadBodyRoutePredicateFactory() { - super(Config.class); - this.messageReaders = HandlerStrategies.withDefaults().messageReaders(); - } - - public ReadBodyRoutePredicateFactory(List> messageReaders) { - super(Config.class); - this.messageReaders = messageReaders; - } - - @Override - @SuppressWarnings("unchecked") - public AsyncPredicate applyAsync(Config config) { - return new AsyncPredicate() { - @Override - public Publisher apply(ServerWebExchange exchange) { - Class inClass = config.getInClass(); - - Object cachedBody = exchange.getAttribute(CACHE_REQUEST_BODY_OBJECT_KEY); - Mono modifiedBody; - // We can only read the body from the request once, once that happens if - // we try to read the body again an exception will be thrown. The below - // if/else caches the body object as a request attribute in the - // ServerWebExchange so if this filter is run more than once (due to more - // than one route using it) we do not try to read the request body - // multiple times - if (cachedBody != null) { - try { - boolean test = config.predicate.test(cachedBody); - exchange.getAttributes().put(TEST_ATTRIBUTE, test); - return Mono.just(test); - } - catch (ClassCastException e) { - if (log.isDebugEnabled()) { - log.debug("Predicate test failed because class in predicate " - + "does not match the cached body object", e); - } - } - return Mono.just(false); - } - else { - return ServerWebExchangeUtils.cacheRequestBodyAndRequest(exchange, - (serverHttpRequest) -> ServerRequest - .create(exchange.mutate().request(serverHttpRequest).build(), messageReaders) - .bodyToMono(inClass).doOnNext(objectValue -> exchange.getAttributes() - .put(CACHE_REQUEST_BODY_OBJECT_KEY, objectValue)) - .map(objectValue -> config.getPredicate().test(objectValue))); - } - } - - @Override - public Object getConfig() { - return config; - } - - @Override - public String toString() { - return String.format("ReadBody: %s", config.getInClass()); - } - }; - } - - @Override - @SuppressWarnings("unchecked") - public Predicate apply(Config config) { - throw new UnsupportedOperationException("ReadBodyPredicateFactory is only async."); - } - - public static class Config { - - private Class inClass; - - private Predicate predicate; - - private Map hints; - - public Class getInClass() { - return inClass; - } - - public Config setInClass(Class inClass) { - this.inClass = inClass; - return this; - } - - public Predicate getPredicate() { - return predicate; - } - - public Config setPredicate(Predicate predicate) { - this.predicate = predicate; - return this; - } - - public Config setPredicate(Class inClass, Predicate predicate) { - setInClass(inClass); - this.predicate = predicate; - return this; - } - - public Map getHints() { - return hints; - } - - public Config setHints(Map hints) { - this.hints = hints; - return this; - } - - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/RemoteAddrRoutePredicateFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/RemoteAddrRoutePredicateFactory.java deleted file mode 100644 index 03702cb4..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/RemoteAddrRoutePredicateFactory.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright 2013-2020 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.handler.predicate; - -import java.net.InetSocketAddress; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.function.Predicate; - -import io.netty.handler.ipfilter.IpFilterRuleType; -import io.netty.handler.ipfilter.IpSubnetFilterRule; -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.cloud.gateway.support.ipresolver.RemoteAddressResolver; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.server.ServerWebExchange; - -import static org.springframework.cloud.gateway.support.ShortcutConfigurable.ShortcutType.GATHER_LIST; - -/** - * @author Spencer Gibb - */ -public class RemoteAddrRoutePredicateFactory - extends AbstractRoutePredicateFactory { - - private static final Log log = LogFactory.getLog(RemoteAddrRoutePredicateFactory.class); - - public RemoteAddrRoutePredicateFactory() { - super(Config.class); - } - - @Override - public ShortcutType shortcutType() { - return GATHER_LIST; - } - - @Override - public List shortcutFieldOrder() { - return Collections.singletonList("sources"); - } - - @NotNull - private List convert(List values) { - List sources = new ArrayList<>(); - for (String arg : values) { - addSource(sources, arg); - } - return sources; - } - - @Override - public Predicate apply(Config config) { - List sources = convert(config.sources); - - return new GatewayPredicate() { - @Override - public boolean test(ServerWebExchange exchange) { - InetSocketAddress remoteAddress = config.remoteAddressResolver.resolve(exchange); - if (remoteAddress != null && remoteAddress.getAddress() != null) { - String hostAddress = remoteAddress.getAddress().getHostAddress(); - String host = exchange.getRequest().getURI().getHost(); - - if (log.isDebugEnabled() && !hostAddress.equals(host)) { - log.debug("Remote addresses didn't match " + hostAddress + " != " + host); - } - - for (IpSubnetFilterRule source : sources) { - if (source.matches(remoteAddress)) { - return true; - } - } - } - - return false; - } - - @Override - public Object getConfig() { - return config; - } - - @Override - public String toString() { - return String.format("RemoteAddrs: %s", config.getSources()); - } - }; - } - - private void addSource(List sources, String source) { - if (!source.contains("/")) { // no netmask, add default - source = source + "/32"; - } - - String[] ipAddressCidrPrefix = source.split("/", 2); - String ipAddress = ipAddressCidrPrefix[0]; - int cidrPrefix = Integer.parseInt(ipAddressCidrPrefix[1]); - - sources.add(new IpSubnetFilterRule(ipAddress, cidrPrefix, IpFilterRuleType.ACCEPT)); - } - - @Validated - public static class Config { - - @NotEmpty - private List sources = new ArrayList<>(); - - @NotNull - private RemoteAddressResolver remoteAddressResolver = new RemoteAddressResolver() { - }; - - public List getSources() { - return sources; - } - - public Config setSources(List sources) { - this.sources = sources; - return this; - } - - public Config setSources(String... sources) { - this.sources = Arrays.asList(sources); - return this; - } - - public Config setRemoteAddressResolver(RemoteAddressResolver remoteAddressResolver) { - this.remoteAddressResolver = remoteAddressResolver; - return this; - } - - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/RoutePredicateFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/RoutePredicateFactory.java deleted file mode 100644 index 687a64b3..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/RoutePredicateFactory.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2013-2020 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.handler.predicate; - -import java.util.function.Consumer; -import java.util.function.Predicate; - -import org.springframework.cloud.gateway.handler.AsyncPredicate; -import org.springframework.cloud.gateway.support.Configurable; -import org.springframework.cloud.gateway.support.NameUtils; -import org.springframework.cloud.gateway.support.ShortcutConfigurable; -import org.springframework.web.server.ServerWebExchange; - -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.toAsyncPredicate; - -/** - * @author Spencer Gibb - */ -@FunctionalInterface -public interface RoutePredicateFactory extends ShortcutConfigurable, Configurable { - - /** - * Pattern key. - */ - String PATTERN_KEY = "pattern"; - - // useful for javadsl - default Predicate apply(Consumer consumer) { - C config = newConfig(); - consumer.accept(config); - beforeApply(config); - return apply(config); - } - - default AsyncPredicate applyAsync(Consumer consumer) { - C config = newConfig(); - consumer.accept(config); - beforeApply(config); - return applyAsync(config); - } - - default Class getConfigClass() { - throw new UnsupportedOperationException("getConfigClass() not implemented"); - } - - @Override - default C newConfig() { - throw new UnsupportedOperationException("newConfig() not implemented"); - } - - default void beforeApply(C config) { - } - - Predicate apply(C config); - - default AsyncPredicate applyAsync(C config) { - return toAsyncPredicate(apply(config)); - } - - default String name() { - return NameUtils.normalizeRoutePredicateName(getClass()); - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/WeightRoutePredicateFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/WeightRoutePredicateFactory.java deleted file mode 100644 index a702a9cd..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/WeightRoutePredicateFactory.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright 2013-2020 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.handler.predicate; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.function.Predicate; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.cloud.gateway.event.WeightDefinedEvent; -import org.springframework.cloud.gateway.support.WeightConfig; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.context.ApplicationEventPublisherAware; -import org.springframework.web.server.ServerWebExchange; - -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_PREDICATE_ROUTE_ATTR; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.WEIGHT_ATTR; - -/** - * @author Spencer Gibb - */ -// TODO: make this a generic Choose out of group predicate? -public class WeightRoutePredicateFactory extends AbstractRoutePredicateFactory - implements ApplicationEventPublisherAware { - - /** - * Weight config group key. - */ - public static final String GROUP_KEY = WeightConfig.CONFIG_PREFIX + ".group"; - - /** - * Weight config weight key. - */ - public static final String WEIGHT_KEY = WeightConfig.CONFIG_PREFIX + ".weight"; - - private static final Log log = LogFactory.getLog(WeightRoutePredicateFactory.class); - - private ApplicationEventPublisher publisher; - - public WeightRoutePredicateFactory() { - super(WeightConfig.class); - } - - @Override - public void setApplicationEventPublisher(ApplicationEventPublisher publisher) { - this.publisher = publisher; - } - - @Override - public List shortcutFieldOrder() { - return Arrays.asList(GROUP_KEY, WEIGHT_KEY); - } - - @Override - public String shortcutFieldPrefix() { - return WeightConfig.CONFIG_PREFIX; - } - - @Override - public void beforeApply(WeightConfig config) { - if (publisher != null) { - publisher.publishEvent(new WeightDefinedEvent(this, config)); - } - } - - @Override - public Predicate apply(WeightConfig config) { - return new GatewayPredicate() { - @Override - public boolean test(ServerWebExchange exchange) { - Map weights = exchange.getAttributeOrDefault(WEIGHT_ATTR, Collections.emptyMap()); - - String routeId = exchange.getAttribute(GATEWAY_PREDICATE_ROUTE_ATTR); - - // all calculations and comparison against random num happened in - // WeightCalculatorWebFilter - String group = config.getGroup(); - if (weights.containsKey(group)) { - - String chosenRoute = weights.get(group); - if (log.isTraceEnabled()) { - log.trace("in group weight: " + group + ", current route: " + routeId + ", chosen route: " - + chosenRoute); - } - - return routeId.equals(chosenRoute); - } - else if (log.isTraceEnabled()) { - log.trace("no weights found for group: " + group + ", current route: " + routeId); - } - - return false; - } - - @Override - public Object getConfig() { - return config; - } - - @Override - public String toString() { - return String.format("Weight: %s %s", config.getGroup(), config.getWeight()); - } - }; - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/XForwardedRemoteAddrRoutePredicateFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/XForwardedRemoteAddrRoutePredicateFactory.java deleted file mode 100644 index ccfdecf9..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/handler/predicate/XForwardedRemoteAddrRoutePredicateFactory.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright 2013-2020 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.handler.predicate; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.function.Predicate; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.cloud.gateway.support.ipresolver.XForwardedRemoteAddressResolver; -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.web.server.ServerWebExchange; - -/* - -This route predicate allows requests to be filtered based on the "X-Forwarded-For" HTTP header. - -This can be used with reverse proxies such as load balancers or web application firewalls where -the request should only be allowed if it comes from a trusted list of IP addresses used by those -reverse proxies. - -With this implementation, a separate "XForwardedRemoteAddr" route predicate is offered which can -be configured with a list of allowed IP addresses and which by default has "maxTrustedIndex" set to 1. -This value means we trust the last (right-most) value in the "X-Forwarded-For" header, which represents -the last reverse proxy that was used when calling the gateway. That IP address is then checked against -the list of allowed IP addresses and used to determine whether or not the request is allowed. - -See https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#modifying-the-way-remote-addresses-are-resolved. - -Note that this predicate implementation does not implement any core logic itself, it aggregates the -"RemoteAddrRoutePredicateFactory" and "XForwardedRemoteAddressResolver" classes into a single predicate -that can be enabled directly from application configuration without the need to specify anything in -custom code. - -Example usage in application.yml which trusts two reverse proxies (one using an IPv6 range): - - ... - - predicates: - - XForwardedRemoteAddr="20.103.252.85", "2a01:111:2050::/44" - -*/ - -/** - * @author Jelle Druyts - */ -public class XForwardedRemoteAddrRoutePredicateFactory - extends AbstractRoutePredicateFactory { - - private static final Log log = LogFactory.getLog(XForwardedRemoteAddrRoutePredicateFactory.class); - - public XForwardedRemoteAddrRoutePredicateFactory() { - super(Config.class); - } - - @Override - public ShortcutType shortcutType() { - return ShortcutType.GATHER_LIST; - } - - @Override - public List shortcutFieldOrder() { - return Arrays.asList("sources"); - } - - @Override - public Predicate apply(Config config) { - if (log.isDebugEnabled()) { - log.debug("Applying XForwardedRemoteAddr route predicate with maxTrustedIndex of " - + config.getMaxTrustedIndex() + " for " + config.getSources().size() + " source(s)"); - } - - // Reuse the standard RemoteAddrRoutePredicateFactory but instead of using the - // default RemoteAddressResolver to determine the client IP address, use an - // XForwardedRemoteAddressResolver. - RemoteAddrRoutePredicateFactory.Config wrappedConfig = new RemoteAddrRoutePredicateFactory.Config(); - wrappedConfig.setSources(config.getSources()); - wrappedConfig - .setRemoteAddressResolver(XForwardedRemoteAddressResolver.maxTrustedIndex(config.getMaxTrustedIndex())); - RemoteAddrRoutePredicateFactory remoteAddrRoutePredicateFactory = new RemoteAddrRoutePredicateFactory(); - Predicate wrappedPredicate = remoteAddrRoutePredicateFactory.apply(wrappedConfig); - - return exchange -> { - Boolean isAllowed = wrappedPredicate.test(exchange); - - if (log.isDebugEnabled()) { - ServerHttpRequest request = exchange.getRequest(); - log.debug("Request for \"" + request.getURI() + "\" from client \"" - + request.getRemoteAddress().getAddress().getHostAddress() + "\" with \"" - + XForwardedRemoteAddressResolver.X_FORWARDED_FOR + "\" header value of \"" - + request.getHeaders().get(XForwardedRemoteAddressResolver.X_FORWARDED_FOR) + "\" is " - + (isAllowed ? "ALLOWED" : "NOT ALLOWED")); - } - - return isAllowed; - }; - } - - public static class Config { - - // Trust the last (right-most) value in the "X-Forwarded-For" header by default, - // which represents the last reverse proxy that was used when calling the gateway. - private int maxTrustedIndex = 1; - - private List sources = new ArrayList<>(); - - public int getMaxTrustedIndex() { - return this.maxTrustedIndex; - } - - public Config setMaxTrustedIndex(int maxTrustedIndex) { - this.maxTrustedIndex = maxTrustedIndex; - return this; - } - - public List getSources() { - return this.sources; - } - - public Config setSources(List sources) { - this.sources = sources; - return this; - } - - public Config setSources(String... sources) { - this.sources = Arrays.asList(sources); - return this; - } - - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/CachingRouteDefinitionLocator.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/CachingRouteDefinitionLocator.java deleted file mode 100644 index 77ca4307..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/CachingRouteDefinitionLocator.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2013-2020 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.route; - -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.stream.Collectors; - -import reactor.cache.CacheFlux; -import reactor.core.publisher.Flux; - -import org.springframework.cloud.gateway.event.RefreshRoutesEvent; -import org.springframework.context.ApplicationListener; - -/** - * @author Spencer Gibb - */ -public class CachingRouteDefinitionLocator implements RouteDefinitionLocator, ApplicationListener { - - private static final String CACHE_KEY = "routeDefs"; - - private final RouteDefinitionLocator delegate; - - private final Flux routeDefinitions; - - private final Map cache = new ConcurrentHashMap<>(); - - public CachingRouteDefinitionLocator(RouteDefinitionLocator delegate) { - this.delegate = delegate; - routeDefinitions = CacheFlux.lookup(cache, CACHE_KEY, RouteDefinition.class).onCacheMissResume(this::fetch); - } - - private Flux fetch() { - return this.delegate.getRouteDefinitions(); - } - - @Override - public Flux getRouteDefinitions() { - return this.routeDefinitions; - } - - /** - * Clears the cache of routeDefinitions. - * @return routeDefinitions flux - */ - public Flux refresh() { - this.cache.clear(); - return this.routeDefinitions; - } - - @Override - public void onApplicationEvent(RefreshRoutesEvent event) { - fetch().materialize().collect(Collectors.toList()).doOnNext(routes -> cache.put(CACHE_KEY, routes)).subscribe(); - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/CachingRouteLocator.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/CachingRouteLocator.java deleted file mode 100644 index 301e1e4c..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/CachingRouteLocator.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright 2013-2020 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.route; - -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.stream.Collectors; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import reactor.cache.CacheFlux; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -import org.springframework.cloud.gateway.event.RefreshRoutesEvent; -import org.springframework.cloud.gateway.event.RefreshRoutesResultEvent; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.context.ApplicationEventPublisherAware; -import org.springframework.context.ApplicationListener; -import org.springframework.core.Ordered; -import org.springframework.core.annotation.AnnotationAwareOrderComparator; - -/** - * @author Spencer Gibb - */ -public class CachingRouteLocator - implements Ordered, RouteLocator, ApplicationListener, ApplicationEventPublisherAware { - - private static final Log log = LogFactory.getLog(CachingRouteLocator.class); - - private static final String CACHE_KEY = "routes"; - - private final RouteLocator delegate; - - private final Flux routes; - - private final Map cache = new ConcurrentHashMap<>(); - - private ApplicationEventPublisher applicationEventPublisher; - - public CachingRouteLocator(RouteLocator delegate) { - this.delegate = delegate; - routes = CacheFlux.lookup(cache, CACHE_KEY, Route.class).onCacheMissResume(this::fetch); - } - - private Flux fetch() { - return this.delegate.getRoutes().sort(AnnotationAwareOrderComparator.INSTANCE); - } - - private Flux fetch(Map metadata) { - return this.delegate.getRoutesByMetadata(metadata).sort(AnnotationAwareOrderComparator.INSTANCE); - } - - @Override - public Flux getRoutes() { - return this.routes; - } - - /** - * Clears the routes cache. - * @return routes flux - */ - public Flux refresh() { - this.cache.remove(CACHE_KEY); - return this.routes; - } - - @Override - public void onApplicationEvent(RefreshRoutesEvent event) { - try { - if (this.cache.containsKey(CACHE_KEY) && event.isScoped()) { - final Mono> scopedRoutes = fetch(event.getMetadata()).collect(Collectors.toList()) - .onErrorResume(s -> Mono.just(List.of())); - - scopedRoutes.subscribe(scopedRoutesList -> { - Flux.concat(Flux.fromIterable(scopedRoutesList), getNonScopedRoutes(event)).materialize() - .collect(Collectors.toList()).subscribe(signals -> { - applicationEventPublisher.publishEvent(new RefreshRoutesResultEvent(this)); - cache.put(CACHE_KEY, signals); - }, this::handleRefreshError); - }, this::handleRefreshError); - } - else { - final Mono> allRoutes = fetch().collect(Collectors.toList()); - - allRoutes.subscribe(list -> Flux.fromIterable(list).materialize().collect(Collectors.toList()) - .subscribe(signals -> { - applicationEventPublisher.publishEvent(new RefreshRoutesResultEvent(this)); - cache.put(CACHE_KEY, signals); - }, this::handleRefreshError), this::handleRefreshError); - } - } - catch (Throwable e) { - handleRefreshError(e); - } - } - - private Flux getNonScopedRoutes(RefreshRoutesEvent scopedEvent) { - return this.getRoutes() - .filter(route -> !RouteLocator.matchMetadata(route.getMetadata(), scopedEvent.getMetadata())); - } - - private void handleRefreshError(Throwable throwable) { - if (log.isErrorEnabled()) { - log.error("Refresh routes error !!!", throwable); - } - applicationEventPublisher.publishEvent(new RefreshRoutesResultEvent(this, throwable)); - } - - @Override - public int getOrder() { - return 0; - } - - @Override - public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { - this.applicationEventPublisher = applicationEventPublisher; - } - -} 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 deleted file mode 100644 index 217cda6c..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/CompositeRouteDefinitionLocator.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2013-2020 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.route; - -import java.util.UUID; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import reactor.core.scheduler.Schedulers; - -import org.springframework.util.AlternativeJdkIdGenerator; -import org.springframework.util.IdGenerator; - -/** - * @author Spencer Gibb - */ -public class CompositeRouteDefinitionLocator implements RouteDefinitionLocator { - - private static final Log log = LogFactory.getLog(CompositeRouteDefinitionLocator.class); - - private final Flux delegates; - - private final IdGenerator idGenerator; - - public CompositeRouteDefinitionLocator(Flux delegates) { - this(delegates, new AlternativeJdkIdGenerator()); - } - - public CompositeRouteDefinitionLocator(Flux delegates, IdGenerator idGenerator) { - this.delegates = delegates; - this.idGenerator = idGenerator; - } - - @Override - public Flux getRouteDefinitions() { - return this.delegates.flatMapSequential(RouteDefinitionLocator::getRouteDefinitions) - .flatMap(routeDefinition -> { - if (routeDefinition.getId() == null) { - return randomId().map(id -> { - routeDefinition.setId(id); - if (log.isDebugEnabled()) { - log.debug("Id set on route definition: " + routeDefinition); - } - return routeDefinition; - }); - } - return Mono.just(routeDefinition); - }); - } - - protected Mono randomId() { - return Mono.fromSupplier(idGenerator::generateId).map(UUID::toString).publishOn(Schedulers.boundedElastic()); - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/CompositeRouteLocator.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/CompositeRouteLocator.java deleted file mode 100644 index 60208571..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/CompositeRouteLocator.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2013-2020 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.route; - -import java.util.Map; - -import reactor.core.publisher.Flux; - -/** - * @author Spencer Gibb - */ -public class CompositeRouteLocator implements RouteLocator { - - private final Flux delegates; - - public CompositeRouteLocator(Flux delegates) { - this.delegates = delegates; - } - - @Override - public Flux getRoutes() { - return this.delegates.flatMapSequential(RouteLocator::getRoutes); - } - - @Override - public Flux getRoutesByMetadata(Map metadata) { - return this.delegates.flatMapSequential(routeLocator -> routeLocator.getRoutesByMetadata(metadata)); - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/InMemoryRouteDefinitionRepository.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/InMemoryRouteDefinitionRepository.java deleted file mode 100644 index 4ad951a3..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/InMemoryRouteDefinitionRepository.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2013-2020 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.route; - -import java.util.LinkedHashMap; -import java.util.Map; - -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -import org.springframework.cloud.gateway.support.NotFoundException; -import org.springframework.util.ObjectUtils; - -import static java.util.Collections.synchronizedMap; - -/** - * @author Spencer Gibb - */ -public class InMemoryRouteDefinitionRepository implements RouteDefinitionRepository { - - private final Map routes = synchronizedMap(new LinkedHashMap()); - - @Override - public Mono save(Mono route) { - return route.flatMap(r -> { - if (ObjectUtils.isEmpty(r.getId())) { - return Mono.error(new IllegalArgumentException("id may not be empty")); - } - routes.put(r.getId(), r); - return Mono.empty(); - }); - } - - @Override - public Mono delete(Mono routeId) { - return routeId.flatMap(id -> { - if (routes.containsKey(id)) { - routes.remove(id); - return Mono.empty(); - } - return Mono.defer(() -> Mono.error(new NotFoundException("RouteDefinition not found: " + routeId))); - }); - } - - @Override - public Flux getRouteDefinitions() { - Map routesSafeCopy = new LinkedHashMap<>(routes); - return Flux.fromIterable(routesSafeCopy.values()); - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/RedisRouteDefinitionRepository.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/RedisRouteDefinitionRepository.java deleted file mode 100644 index 0beef09f..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/RedisRouteDefinitionRepository.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2013-2017 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.route; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -import org.springframework.cloud.gateway.support.NotFoundException; -import org.springframework.data.redis.core.ReactiveRedisTemplate; -import org.springframework.data.redis.core.ReactiveValueOperations; -import org.springframework.data.redis.core.ScanOptions; -import org.springframework.stereotype.Repository; - -/** - * @author Dennis Menge - * @author lzhpo - */ -@Repository -public class RedisRouteDefinitionRepository implements RouteDefinitionRepository { - - private static final Logger log = LoggerFactory.getLogger(RedisRouteDefinitionRepository.class); - - /** - * Key prefix for RouteDefinition queries to redis. - */ - private static final String ROUTEDEFINITION_REDIS_KEY_PREFIX_QUERY = "routedefinition_"; - - private ReactiveRedisTemplate reactiveRedisTemplate; - - private ReactiveValueOperations routeDefinitionReactiveValueOperations; - - public RedisRouteDefinitionRepository(ReactiveRedisTemplate reactiveRedisTemplate) { - this.reactiveRedisTemplate = reactiveRedisTemplate; - this.routeDefinitionReactiveValueOperations = reactiveRedisTemplate.opsForValue(); - } - - @Override - public Flux getRouteDefinitions() { - return reactiveRedisTemplate.scan(ScanOptions.scanOptions().match(createKey("*")).build()) - .flatMap(key -> reactiveRedisTemplate.opsForValue().get(key)) - .onErrorContinue((throwable, routeDefinition) -> { - if (log.isErrorEnabled()) { - log.error("get routes from redis error cause : {}", throwable.toString(), throwable); - } - }); - } - - @Override - public Mono save(Mono route) { - return route.flatMap(routeDefinition -> routeDefinitionReactiveValueOperations - .set(createKey(routeDefinition.getId()), routeDefinition).flatMap(success -> { - if (success) { - return Mono.empty(); - } - return Mono.defer(() -> Mono.error(new RuntimeException( - String.format("Could not add route to redis repository: %s", routeDefinition)))); - })); - } - - @Override - public Mono delete(Mono routeId) { - return routeId.flatMap(id -> routeDefinitionReactiveValueOperations.delete(createKey(id)).flatMap(success -> { - if (success) { - return Mono.empty(); - } - return Mono.defer(() -> Mono.error(new NotFoundException( - String.format("Could not remove route from redis repository with id: %s", routeId)))); - })); - } - - private String createKey(String routeId) { - return ROUTEDEFINITION_REDIS_KEY_PREFIX_QUERY + routeId; - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/Route.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/Route.java deleted file mode 100644 index b4a30a5e..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/Route.java +++ /dev/null @@ -1,322 +0,0 @@ -/* - * Copyright 2013-2020 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.route; - -import java.net.URI; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.function.Predicate; - -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.handler.AsyncPredicate; -import org.springframework.cloud.gateway.route.builder.Buildable; -import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; -import org.springframework.core.Ordered; -import org.springframework.util.Assert; -import org.springframework.web.server.ServerWebExchange; -import org.springframework.web.util.UriComponentsBuilder; - -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.toAsyncPredicate; - -/** - * @author Spencer Gibb - */ -public class Route implements Ordered { - - private final String id; - - private final URI uri; - - private final int order; - - private final AsyncPredicate predicate; - - private final List gatewayFilters; - - private final Map metadata; - - private Route(String id, URI uri, int order, AsyncPredicate predicate, - List gatewayFilters, Map metadata) { - this.id = id; - this.uri = uri; - this.order = order; - this.predicate = predicate; - this.gatewayFilters = gatewayFilters; - this.metadata = metadata; - } - - public static Builder builder() { - return new Builder(); - } - - public static Builder builder(RouteDefinition routeDefinition) { - // @formatter:off - return new Builder().id(routeDefinition.getId()) - .uri(routeDefinition.getUri()) - .order(routeDefinition.getOrder()) - .metadata(routeDefinition.getMetadata()); - // @formatter:on - } - - public static AsyncBuilder async() { - return new AsyncBuilder(); - } - - public static AsyncBuilder async(RouteDefinition routeDefinition) { - // @formatter:off - return new AsyncBuilder().id(routeDefinition.getId()) - .uri(routeDefinition.getUri()) - .order(routeDefinition.getOrder()) - .metadata(routeDefinition.getMetadata()); - // @formatter:on - } - - public String getId() { - return this.id; - } - - public URI getUri() { - return this.uri; - } - - public int getOrder() { - return order; - } - - public AsyncPredicate getPredicate() { - return this.predicate; - } - - public List getFilters() { - return Collections.unmodifiableList(this.gatewayFilters); - } - - public Map getMetadata() { - return Collections.unmodifiableMap(metadata); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - Route route = (Route) o; - return this.order == route.order && Objects.equals(this.id, route.id) && Objects.equals(this.uri, route.uri) - && Objects.equals(this.predicate, route.predicate) - && Objects.equals(this.gatewayFilters, route.gatewayFilters) - && Objects.equals(this.metadata, route.metadata); - } - - @Override - public int hashCode() { - return Objects.hash(this.id, this.uri, this.order, this.predicate, this.gatewayFilters, this.metadata); - } - - @Override - public String toString() { - final StringBuffer sb = new StringBuffer("Route{"); - sb.append("id='").append(id).append('\''); - sb.append(", uri=").append(uri); - sb.append(", order=").append(order); - sb.append(", predicate=").append(predicate); - sb.append(", gatewayFilters=").append(gatewayFilters); - sb.append(", metadata=").append(metadata); - sb.append('}'); - return sb.toString(); - } - - public abstract static class AbstractBuilder> implements Buildable { - - protected String id; - - protected URI uri; - - protected int order = 0; - - protected List gatewayFilters = new ArrayList<>(); - - protected Map metadata = new HashMap<>(); - - protected AbstractBuilder() { - } - - protected abstract B getThis(); - - public B id(String id) { - this.id = id; - return getThis(); - } - - public String getId() { - return id; - } - - public B order(int order) { - this.order = order; - return getThis(); - } - - public B uri(String uri) { - return uri(URI.create(uri)); - } - - public B uri(URI uri) { - this.uri = uri; - String scheme = this.uri.getScheme(); - Assert.hasText(scheme, "The parameter [" + this.uri + "] format is incorrect, scheme can not be empty"); - if (this.uri.getPort() < 0 && scheme.startsWith("http")) { - // default known http ports - int port = this.uri.getScheme().equals("https") ? 443 : 80; - this.uri = UriComponentsBuilder.fromUri(this.uri).port(port).build(false).toUri(); - } - return getThis(); - } - - public B replaceMetadata(Map metadata) { - this.metadata = metadata; - return getThis(); - } - - public B metadata(Map metadata) { - this.metadata.putAll(metadata); - return getThis(); - } - - public B metadata(String key, Object value) { - this.metadata.put(key, value); - return getThis(); - } - - public abstract AsyncPredicate getPredicate(); - - public B replaceFilters(List gatewayFilters) { - this.gatewayFilters = gatewayFilters; - return getThis(); - } - - public B filter(GatewayFilter gatewayFilter) { - this.gatewayFilters.add(gatewayFilter); - return getThis(); - } - - public B filters(Collection gatewayFilters) { - this.gatewayFilters.addAll(gatewayFilters); - return getThis(); - } - - public B filters(GatewayFilter... gatewayFilters) { - return filters(Arrays.asList(gatewayFilters)); - } - - public Route build() { - Assert.notNull(this.id, "id can not be null"); - Assert.notNull(this.uri, "uri can not be null"); - AsyncPredicate predicate = getPredicate(); - Assert.notNull(predicate, "predicate can not be null"); - - return new Route(this.id, this.uri, this.order, predicate, this.gatewayFilters, this.metadata); - } - - } - - public static class AsyncBuilder extends AbstractBuilder { - - protected AsyncPredicate predicate; - - @Override - protected AsyncBuilder getThis() { - return this; - } - - @Override - public AsyncPredicate getPredicate() { - return this.predicate; - } - - public AsyncBuilder predicate(Predicate predicate) { - return asyncPredicate(toAsyncPredicate(predicate)); - } - - public AsyncBuilder asyncPredicate(AsyncPredicate predicate) { - this.predicate = predicate; - return this; - } - - public AsyncBuilder and(AsyncPredicate predicate) { - Assert.notNull(this.predicate, "can not call and() on null predicate"); - this.predicate = this.predicate.and(predicate); - return this; - } - - public AsyncBuilder or(AsyncPredicate predicate) { - Assert.notNull(this.predicate, "can not call or() on null predicate"); - this.predicate = this.predicate.or(predicate); - return this; - } - - public AsyncBuilder negate() { - Assert.notNull(this.predicate, "can not call negate() on null predicate"); - this.predicate = this.predicate.negate(); - return this; - } - - } - - public static class Builder extends AbstractBuilder { - - protected Predicate predicate; - - @Override - protected Builder getThis() { - return this; - } - - @Override - public AsyncPredicate getPredicate() { - return ServerWebExchangeUtils.toAsyncPredicate(this.predicate); - } - - public Builder and(Predicate predicate) { - Assert.notNull(this.predicate, "can not call and() on null predicate"); - this.predicate = this.predicate.and(predicate); - return this; - } - - public Builder or(Predicate predicate) { - Assert.notNull(this.predicate, "can not call or() on null predicate"); - this.predicate = this.predicate.or(predicate); - return this; - } - - public Builder negate() { - Assert.notNull(this.predicate, "can not call negate() on null predicate"); - this.predicate = this.predicate.negate(); - return this; - } - - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/RouteDefinition.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/RouteDefinition.java deleted file mode 100644 index 8c35e138..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/RouteDefinition.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright 2013-2020 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.route; - -import java.net.URI; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; - -import jakarta.validation.Valid; -import jakarta.validation.ValidationException; -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; - -import org.springframework.cloud.gateway.filter.FilterDefinition; -import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition; -import org.springframework.validation.annotation.Validated; - -import static org.springframework.util.StringUtils.tokenizeToStringArray; - -/** - * @author Spencer Gibb - */ -@Validated -public class RouteDefinition { - - private String id; - - @NotEmpty - @Valid - private List predicates = new ArrayList<>(); - - @Valid - private List filters = new ArrayList<>(); - - @NotNull - private URI uri; - - private Map metadata = new HashMap<>(); - - private int order = 0; - - public RouteDefinition() { - } - - public RouteDefinition(String text) { - int eqIdx = text.indexOf('='); - if (eqIdx <= 0) { - throw new ValidationException( - "Unable to parse RouteDefinition text '" + text + "'" + ", must be of the form name=value"); - } - - setId(text.substring(0, eqIdx)); - - String[] args = tokenizeToStringArray(text.substring(eqIdx + 1), ","); - - setUri(URI.create(args[0])); - - for (int i = 1; i < args.length; i++) { - this.predicates.add(new PredicateDefinition(args[i])); - } - } - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public List getPredicates() { - return predicates; - } - - public void setPredicates(List predicates) { - this.predicates = predicates; - } - - public List getFilters() { - return filters; - } - - public void setFilters(List filters) { - this.filters = filters; - } - - public URI getUri() { - return uri; - } - - public void setUri(URI uri) { - this.uri = uri; - } - - public int getOrder() { - return order; - } - - public void setOrder(int order) { - this.order = order; - } - - public Map getMetadata() { - return metadata; - } - - public void setMetadata(Map metadata) { - this.metadata = metadata; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - RouteDefinition that = (RouteDefinition) o; - return this.order == that.order && Objects.equals(this.id, that.id) - && Objects.equals(this.predicates, that.predicates) && Objects.equals(this.filters, that.filters) - && Objects.equals(this.uri, that.uri) && Objects.equals(this.metadata, that.metadata); - } - - @Override - public int hashCode() { - return Objects.hash(this.id, this.predicates, this.filters, this.uri, this.metadata, this.order); - } - - @Override - public String toString() { - return "RouteDefinition{" + "id='" + id + '\'' + ", predicates=" + predicates + ", filters=" + filters - + ", uri=" + uri + ", order=" + order + ", metadata=" + metadata + '}'; - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/RouteDefinitionLocator.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/RouteDefinitionLocator.java deleted file mode 100644 index 2f271197..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/RouteDefinitionLocator.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2013-2020 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.route; - -import reactor.core.publisher.Flux; - -/** - * @author Spencer Gibb - */ -public interface RouteDefinitionLocator { - - Flux getRouteDefinitions(); - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/RouteDefinitionMetrics.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/RouteDefinitionMetrics.java deleted file mode 100644 index 211c1213..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/RouteDefinitionMetrics.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2013-2020 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.route; - -import java.util.concurrent.atomic.AtomicInteger; - -import io.micrometer.core.instrument.MeterRegistry; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.cloud.gateway.event.RefreshRoutesEvent; -import org.springframework.cloud.gateway.filter.GatewayMetricsFilter; -import org.springframework.context.ApplicationListener; - -/** - * A metric to track the number of routes definitions on the gateway. - * - * @author Fredrich Ombico - */ -public class RouteDefinitionMetrics implements ApplicationListener { - - private static final Log log = LogFactory.getLog(GatewayMetricsFilter.class); - - private final RouteDefinitionLocator routeLocator; - - private final AtomicInteger routeDefinitionCount; - - private final String metricsPrefix; - - public RouteDefinitionMetrics(MeterRegistry meterRegistry, RouteDefinitionLocator routeLocator, - String metricsPrefix) { - this.routeLocator = routeLocator; - - if (metricsPrefix.endsWith(".")) { - this.metricsPrefix = metricsPrefix.substring(0, metricsPrefix.length() - 1); - } - else { - this.metricsPrefix = metricsPrefix; - } - routeDefinitionCount = meterRegistry.gauge(this.metricsPrefix + ".routes.count", new AtomicInteger(0)); - } - - public String getMetricsPrefix() { - return metricsPrefix; - } - - @Override - public void onApplicationEvent(RefreshRoutesEvent event) { - routeLocator.getRouteDefinitions().count().subscribe(count -> { - routeDefinitionCount.set(count.intValue()); - if (log.isDebugEnabled()) { - log.debug("New routes count: " + routeDefinitionCount); - } - }); - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/RouteDefinitionRepository.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/RouteDefinitionRepository.java deleted file mode 100644 index 09b0654b..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/RouteDefinitionRepository.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2013-2020 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.route; - -/** - * @author Spencer Gibb - */ -public interface RouteDefinitionRepository extends RouteDefinitionLocator, RouteDefinitionWriter { - -} 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 deleted file mode 100644 index f9d3aead..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/RouteDefinitionRouteLocator.java +++ /dev/null @@ -1,238 +0,0 @@ -/* - * Copyright 2013-2020 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.route; - -import java.util.ArrayList; -import java.util.HashMap; -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 reactor.core.publisher.Flux; - -import org.springframework.cloud.gateway.config.GatewayProperties; -import org.springframework.cloud.gateway.event.FilterArgsEvent; -import org.springframework.cloud.gateway.event.PredicateArgsEvent; -import org.springframework.cloud.gateway.filter.FilterDefinition; -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.OrderedGatewayFilter; -import org.springframework.cloud.gateway.filter.factory.GatewayFilterFactory; -import org.springframework.cloud.gateway.handler.AsyncPredicate; -import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition; -import org.springframework.cloud.gateway.handler.predicate.RoutePredicateFactory; -import org.springframework.cloud.gateway.support.ConfigurationService; -import org.springframework.cloud.gateway.support.HasRouteId; -import org.springframework.core.Ordered; -import org.springframework.core.annotation.AnnotationAwareOrderComparator; -import org.springframework.util.CollectionUtils; -import org.springframework.web.server.ServerWebExchange; - -/** - * {@link RouteLocator} that loads routes from a {@link RouteDefinitionLocator}. - * - * @author Spencer Gibb - */ -public class RouteDefinitionRouteLocator implements RouteLocator { - - /** - * Default filters name. - */ - public static final String DEFAULT_FILTERS = "defaultFilters"; - - protected final Log logger = LogFactory.getLog(getClass()); - - private final RouteDefinitionLocator routeDefinitionLocator; - - private final ConfigurationService configurationService; - - private final Map predicates = new LinkedHashMap<>(); - - private final Map gatewayFilterFactories = new HashMap<>(); - - private final GatewayProperties gatewayProperties; - - public RouteDefinitionRouteLocator(RouteDefinitionLocator routeDefinitionLocator, - List predicates, List gatewayFilterFactories, - GatewayProperties gatewayProperties, ConfigurationService configurationService) { - this.routeDefinitionLocator = routeDefinitionLocator; - this.configurationService = configurationService; - initFactories(predicates); - gatewayFilterFactories.forEach(factory -> this.gatewayFilterFactories.put(factory.name(), factory)); - this.gatewayProperties = gatewayProperties; - } - - private void initFactories(List predicates) { - predicates.forEach(factory -> { - String key = factory.name(); - if (this.predicates.containsKey(key)) { - this.logger.warn("A RoutePredicateFactory named " + key + " already exists, class: " - + this.predicates.get(key) + ". It will be overwritten."); - } - this.predicates.put(key, factory); - if (logger.isInfoEnabled()) { - logger.info("Loaded RoutePredicateFactory [" + key + "]"); - } - }); - } - - /** - * Filtering is done via {@link RouteDefinition} instead of {@link Route} to prevent - * creating Route instances that will be discarded. - */ - @Override - public Flux getRoutesByMetadata(Map metadata) { - return getRoutes(this.routeDefinitionLocator.getRouteDefinitions() - .filter(routeDef -> RouteLocator.matchMetadata(routeDef.getMetadata(), metadata))); - } - - @Override - public Flux getRoutes() { - return getRoutes(this.routeDefinitionLocator.getRouteDefinitions()); - } - - private Flux getRoutes(Flux routeDefinitions) { - Flux routes = routeDefinitions.map(this::convertToRoute); - - if (!gatewayProperties.isFailOnRouteDefinitionError()) { - // instead of letting error bubble up, continue - routes = routes.onErrorContinue((error, obj) -> { - if (logger.isWarnEnabled()) { - logger.warn("RouteDefinition id " + ((RouteDefinition) obj).getId() - + " will be ignored. Definition has invalid configs, " + error.getMessage()); - } - }); - } - - return routes.map(route -> { - if (logger.isDebugEnabled()) { - logger.debug("RouteDefinition matched: " + route.getId()); - } - return route; - }); - } - - private Route convertToRoute(RouteDefinition routeDefinition) { - AsyncPredicate predicate = combinePredicates(routeDefinition); - List gatewayFilters = getFilters(routeDefinition); - - return Route.async(routeDefinition).asyncPredicate(predicate).replaceFilters(gatewayFilters).build(); - } - - @SuppressWarnings("unchecked") - List loadGatewayFilters(String id, List filterDefinitions) { - ArrayList ordered = new ArrayList<>(filterDefinitions.size()); - for (int i = 0; i < filterDefinitions.size(); i++) { - FilterDefinition definition = filterDefinitions.get(i); - GatewayFilterFactory factory = this.gatewayFilterFactories.get(definition.getName()); - if (factory == null) { - throw new IllegalArgumentException( - "Unable to find GatewayFilterFactory with name " + definition.getName()); - } - if (logger.isDebugEnabled()) { - logger.debug("RouteDefinition " + id + " applying filter " + definition.getArgs() + " to " - + definition.getName()); - } - - // @formatter:off - Object configuration = this.configurationService.with(factory) - .name(definition.getName()) - .properties(definition.getArgs()) - .eventFunction((bound, properties) -> new FilterArgsEvent( - // TODO: why explicit cast needed or java compile fails - RouteDefinitionRouteLocator.this, id, (Map) properties)) - .bind(); - // @formatter:on - - // some filters require routeId - // TODO: is there a better place to apply this? - if (configuration instanceof HasRouteId) { - HasRouteId hasRouteId = (HasRouteId) configuration; - hasRouteId.setRouteId(id); - } - - GatewayFilter gatewayFilter = factory.apply(configuration); - if (gatewayFilter instanceof Ordered) { - ordered.add(gatewayFilter); - } - else { - ordered.add(new OrderedGatewayFilter(gatewayFilter, i + 1)); - } - } - - return ordered; - } - - private List getFilters(RouteDefinition routeDefinition) { - List filters = new ArrayList<>(); - - // TODO: support option to apply defaults after route specific filters? - if (!this.gatewayProperties.getDefaultFilters().isEmpty()) { - filters.addAll(loadGatewayFilters(routeDefinition.getId(), - new ArrayList<>(this.gatewayProperties.getDefaultFilters()))); - } - - final List definitionFilters = routeDefinition.getFilters(); - if (!CollectionUtils.isEmpty(definitionFilters)) { - filters.addAll(loadGatewayFilters(routeDefinition.getId(), definitionFilters)); - } - - AnnotationAwareOrderComparator.sort(filters); - return filters; - } - - private AsyncPredicate combinePredicates(RouteDefinition routeDefinition) { - List predicates = routeDefinition.getPredicates(); - if (predicates == null || predicates.isEmpty()) { - // 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; - } - - @SuppressWarnings("unchecked") - private AsyncPredicate lookup(RouteDefinition route, PredicateDefinition predicate) { - RoutePredicateFactory factory = this.predicates.get(predicate.getName()); - if (factory == null) { - throw new IllegalArgumentException("Unable to find RoutePredicateFactory with name " + predicate.getName()); - } - if (logger.isDebugEnabled()) { - logger.debug("RouteDefinition " + route.getId() + " applying " + predicate.getArgs() + " to " - + predicate.getName()); - } - - // @formatter:off - Object config = this.configurationService.with(factory) - .name(predicate.getName()) - .properties(predicate.getArgs()) - .eventFunction((bound, properties) -> new PredicateArgsEvent( - RouteDefinitionRouteLocator.this, route.getId(), properties)) - .bind(); - // @formatter:on - - return factory.applyAsync(config); - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/RouteDefinitionWriter.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/RouteDefinitionWriter.java deleted file mode 100644 index 9be7aa23..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/RouteDefinitionWriter.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2013-2020 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.route; - -import reactor.core.publisher.Mono; - -/** - * @author Spencer Gibb - */ -public interface RouteDefinitionWriter { - - Mono save(Mono route); - - Mono delete(Mono routeId); - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/RouteLocator.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/RouteLocator.java deleted file mode 100644 index 3b1812ce..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/RouteLocator.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2013-2020 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.route; - -import java.util.Map; - -import reactor.core.publisher.Flux; - -import org.springframework.util.CollectionUtils; - -/** - * @author Spencer Gibb - */ -// TODO: rename to Routes? -public interface RouteLocator { - - Flux getRoutes(); - - /** - * Gets routes whose {@link Route#getId()} matches with any of the ids passed by - * parameters. If an ID cannot be found, it will not return a route for that ID. - */ - default Flux getRoutesByMetadata(Map metadata) { - return getRoutes().filter(route -> matchMetadata(route.getMetadata(), metadata)); - } - - static boolean matchMetadata(Map toCheck, Map expectedMetadata) { - if (CollectionUtils.isEmpty(expectedMetadata)) { - return true; - } - else { - return toCheck != null - && expectedMetadata.entrySet().stream().allMatch(keyValue -> toCheck.containsKey(keyValue.getKey()) - && toCheck.get(keyValue.getKey()).equals(keyValue.getValue())); - } - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/RouteRefreshListener.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/RouteRefreshListener.java deleted file mode 100644 index ac2309fc..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/RouteRefreshListener.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2013-2020 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.route; - -import org.springframework.boot.web.context.WebServerApplicationContext; -import org.springframework.cloud.client.discovery.event.HeartbeatEvent; -import org.springframework.cloud.client.discovery.event.HeartbeatMonitor; -import org.springframework.cloud.client.discovery.event.InstanceRegisteredEvent; -import org.springframework.cloud.client.discovery.event.ParentHeartbeatEvent; -import org.springframework.cloud.context.scope.refresh.RefreshScopeRefreshedEvent; -import org.springframework.cloud.gateway.event.RefreshRoutesEvent; -import org.springframework.context.ApplicationEvent; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.context.ApplicationListener; -import org.springframework.context.event.ContextRefreshedEvent; -import org.springframework.util.Assert; - -// see ZuulDiscoveryRefreshListener -// TODO: make abstract class in commons? -public class RouteRefreshListener implements ApplicationListener { - - private final ApplicationEventPublisher publisher; - - private HeartbeatMonitor monitor = new HeartbeatMonitor(); - - public RouteRefreshListener(ApplicationEventPublisher publisher) { - Assert.notNull(publisher, "publisher may not be null"); - this.publisher = publisher; - } - - @Override - public void onApplicationEvent(ApplicationEvent event) { - if (event instanceof ContextRefreshedEvent) { - ContextRefreshedEvent refreshedEvent = (ContextRefreshedEvent) event; - if (!WebServerApplicationContext.hasServerNamespace(refreshedEvent.getApplicationContext(), "management")) { - reset(); - } - } - else if (event instanceof RefreshScopeRefreshedEvent || event instanceof InstanceRegisteredEvent) { - reset(); - } - else if (event instanceof ParentHeartbeatEvent) { - ParentHeartbeatEvent e = (ParentHeartbeatEvent) event; - resetIfNeeded(e.getValue()); - } - else if (event instanceof HeartbeatEvent) { - HeartbeatEvent e = (HeartbeatEvent) event; - resetIfNeeded(e.getValue()); - } - } - - private void resetIfNeeded(Object value) { - if (this.monitor.update(value)) { - reset(); - } - } - - private void reset() { - this.publisher.publishEvent(new RefreshRoutesEvent(this)); - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/builder/BooleanSpec.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/builder/BooleanSpec.java deleted file mode 100644 index f0d8a559..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/builder/BooleanSpec.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright 2013-2020 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.route.builder; - -import java.util.function.Function; -import java.util.function.Predicate; - -import org.springframework.cloud.gateway.handler.AsyncPredicate; -import org.springframework.cloud.gateway.route.Route; -import org.springframework.util.Assert; -import org.springframework.web.server.ServerWebExchange; - -import static org.springframework.cloud.gateway.route.builder.BooleanSpec.Operator.AND; -import static org.springframework.cloud.gateway.route.builder.BooleanSpec.Operator.OR; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.toAsyncPredicate; - -/** - * A spec used to apply logical operators. - */ -public class BooleanSpec extends UriSpec { - - final AsyncPredicate predicate; - - public BooleanSpec(Route.AsyncBuilder routeBuilder, RouteLocatorBuilder.Builder builder) { - super(routeBuilder, builder); - // save current predicate useful in kotlin dsl - predicate = routeBuilder.getPredicate(); - } - - /** - * Apply logical {@code and} operator. - * @return a {@link BooleanSpec} to be used to add logical operators - */ - public BooleanOpSpec and() { - return new BooleanOpSpec(routeBuilder, builder, AND); - } - - /** - * Apply logical {@code or} operator. - * @return a {@link BooleanSpec} to be used to add logical operators - */ - public BooleanOpSpec or() { - return new BooleanOpSpec(routeBuilder, builder, OR); - } - - /** - * Negate the logical operator. - * @return a {@link BooleanSpec} to be used to add logical operators - */ - public BooleanSpec negate() { - this.routeBuilder.negate(); - return new BooleanSpec(routeBuilder, builder); - } - - /** - * Add filters to the route definition. - * @param fn A {@link Function} that takes in a {@link GatewayFilterSpec} and returns - * a {@link UriSpec} - * @return a {@link UriSpec} - */ - public UriSpec filters(Function fn) { - return fn.apply(new GatewayFilterSpec(routeBuilder, builder)); - } - - enum Operator { - - AND, OR, NEGATE - - } - - public static class BooleanOpSpec extends PredicateSpec { - - private Operator operator; - - BooleanOpSpec(Route.AsyncBuilder routeBuilder, RouteLocatorBuilder.Builder builder, Operator operator) { - super(routeBuilder, builder); - Assert.notNull(operator, "operator may not be null"); - this.operator = operator; - } - - public BooleanSpec predicate(Predicate predicate) { - return asyncPredicate(toAsyncPredicate(predicate)); - } - - @Override - public BooleanSpec asyncPredicate(AsyncPredicate predicate) { - switch (this.operator) { - case AND: - this.routeBuilder.and(predicate); - break; - case OR: - this.routeBuilder.or(predicate); - break; - case NEGATE: - this.routeBuilder.negate(); - } - return new BooleanSpec(this.routeBuilder, this.builder); - } - - public BooleanSpec not(Function fn) { - return fn.apply(new NotOpSpec(this.routeBuilder, this.builder, this.operator)); - } - - } - - public static class NotOpSpec extends BooleanOpSpec { - - NotOpSpec(Route.AsyncBuilder routeBuilder, RouteLocatorBuilder.Builder builder, Operator operator) { - super(routeBuilder, builder, operator); - } - - @Override - public BooleanSpec asyncPredicate(AsyncPredicate predicate) { - AsyncPredicate negated = this.routeBuilder.getPredicate().not(predicate); - return super.asyncPredicate(negated); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/builder/Buildable.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/builder/Buildable.java deleted file mode 100644 index 841a7e13..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/builder/Buildable.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2013-2020 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.route.builder; - -public interface Buildable { - - T build(); - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/builder/GatewayFilterSpec.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/builder/GatewayFilterSpec.java deleted file mode 100644 index ada0ff8c..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/builder/GatewayFilterSpec.java +++ /dev/null @@ -1,937 +0,0 @@ -/* - * Copyright 2013-2020 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.route.builder; - -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.time.Duration; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import java.util.Optional; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.core.ParameterizedTypeReference; -import reactor.retry.Repeat; -import reactor.retry.Retry; - -import org.springframework.beans.factory.NoSuchBeanDefinitionException; -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.OrderedGatewayFilter; -import org.springframework.cloud.gateway.filter.factory.AbstractChangeRequestUriGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.AddRequestHeaderGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.AddRequestHeadersIfNotPresentGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.AddRequestHeadersIfNotPresentGatewayFilterFactory.KeyValue; -import org.springframework.cloud.gateway.filter.factory.AddRequestParameterGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.AddResponseHeaderGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.CacheRequestBodyGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.DedupeResponseHeaderGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.DedupeResponseHeaderGatewayFilterFactory.Strategy; -import org.springframework.cloud.gateway.filter.factory.FallbackHeadersGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.JsonToGrpcGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.MapRequestHeaderGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.PrefixPathGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.PreserveHostHeaderGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.RedirectToGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.RemoveJsonAttributesResponseBodyGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.RemoveRequestHeaderGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.RemoveRequestParameterGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.RemoveResponseHeaderGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.RequestHeaderSizeGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.RequestHeaderToRequestUriGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.RequestRateLimiterGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.RequestSizeGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.RetryGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.RewriteLocationResponseHeaderGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.RewriteLocationResponseHeaderGatewayFilterFactory.StripVersion; -import org.springframework.cloud.gateway.filter.factory.RewritePathGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.RewriteResponseHeaderGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.SaveSessionGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.SecureHeadersGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.SetPathGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.SetRequestHeaderGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.SetRequestHostHeaderGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.SetResponseHeaderGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.SetStatusGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.SpringCloudCircuitBreakerFilterFactory; -import org.springframework.cloud.gateway.filter.factory.StripPrefixGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.TokenRelayGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.cache.LocalResponseCacheGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.rewrite.ModifyRequestBodyGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.rewrite.ModifyResponseBodyGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.rewrite.RewriteFunction; -import org.springframework.cloud.gateway.filter.ratelimit.RateLimiter; -import org.springframework.cloud.gateway.route.Route; -import org.springframework.core.Ordered; -import org.springframework.http.HttpStatus; -import org.springframework.util.unit.DataSize; -import org.springframework.web.server.ServerWebExchange; - -/** - * Applies specific filters to routes. - */ -public class GatewayFilterSpec extends UriSpec { - - private static final Log log = LogFactory.getLog(GatewayFilterSpec.class); - - public GatewayFilterSpec(Route.AsyncBuilder routeBuilder, RouteLocatorBuilder.Builder builder) { - super(routeBuilder, builder); - } - - /** - * Applies the filter to the route. - * @param gatewayFilter the filter to apply - * @return a {@link GatewayFilterSpec} that can be used to apply additional filters - */ - public GatewayFilterSpec filter(GatewayFilter gatewayFilter) { - if (gatewayFilter instanceof Ordered) { - this.routeBuilder.filter(gatewayFilter); - return this; - } - return this.filter(gatewayFilter, 0); - } - - /** - * Applies the filter to the route. - * @param gatewayFilter the filter to apply - * @param order the order to apply the filter - * @return a {@link GatewayFilterSpec} that can be used to apply additional filters - */ - public GatewayFilterSpec filter(GatewayFilter gatewayFilter, int order) { - if (gatewayFilter instanceof Ordered) { - this.routeBuilder.filter(gatewayFilter); - log.warn("GatewayFilter already implements ordered " + gatewayFilter.getClass() - + "ignoring order parameter: " + order); - return this; - } - this.routeBuilder.filter(new OrderedGatewayFilter(gatewayFilter, order)); - return this; - } - - /** - * Applies the list of filters to the route. - * @param gatewayFilters the filters to apply - * @return a {@link GatewayFilterSpec} that can be used to apply additional filters - */ - public GatewayFilterSpec filters(GatewayFilter... gatewayFilters) { - List filters = transformToOrderedFilters(Stream.of(gatewayFilters)); - this.routeBuilder.filters(filters); - return this; - } - - public List transformToOrderedFilters(Stream stream) { - return stream.map(filter -> { - if (filter instanceof Ordered) { - return filter; - } - else { - return new OrderedGatewayFilter(filter, 0); - } - }).collect(Collectors.toList()); - } - - /** - * Applies the list of filters to the route. - * @param gatewayFilters the filters to apply - * @return a {@link GatewayFilterSpec} that can be used to apply additional filters - */ - public GatewayFilterSpec filters(Collection gatewayFilters) { - List filters = transformToOrderedFilters(gatewayFilters.stream()); - this.routeBuilder.filters(filters); - return this; - } - - /** - * Adds a request header to the request before it is routed by the Gateway. - * @param headerName the header name - * @param headerValue the header value - * @return a {@link GatewayFilterSpec} that can be used to apply additional filters - */ - public GatewayFilterSpec addRequestHeader(String headerName, String headerValue) { - return filter(getBean(AddRequestHeaderGatewayFilterFactory.class) - .apply(c -> c.setName(headerName).setValue(headerValue))); - } - - /** - * Adds a request header to the request before it is routed by the Gateway. - * @param headers the header name(s) and value(s) as - * 'name-1:value-1,name-2:value-2,...' - * @return a {@link GatewayFilterSpec} that can be used to apply additional filters - */ - public GatewayFilterSpec addRequestHeadersIfNotPresent(String... headers) { - return filter(getBean(AddRequestHeadersIfNotPresentGatewayFilterFactory.class).apply(c -> { - KeyValue[] values = Arrays.stream(headers).map(header -> header.split(":")) - .map(parts -> new KeyValue(parts[0], parts[1])).toArray(size -> new KeyValue[size]); - c.setKeyValues(values); - })); - } - - /** - * Adds a request parameter to the request before it is routed by the Gateway. - * @param param the parameter name - * @param value the parameter vaule - * @return a {@link GatewayFilterSpec} that can be used to apply additional filters - */ - public GatewayFilterSpec addRequestParameter(String param, String value) { - return filter( - getBean(AddRequestParameterGatewayFilterFactory.class).apply(c -> c.setName(param).setValue(value))); - } - - /** - * Adds a header to the response returned to the Gateway from the route. - * @param headerName the header name - * @param headerValue the header value - * @return a {@link GatewayFilterSpec} that can be used to apply additional filters - */ - public GatewayFilterSpec addResponseHeader(String headerName, String headerValue) { - return filter(getBean(AddResponseHeaderGatewayFilterFactory.class) - .apply(c -> c.setName(headerName).setValue(headerValue))); - } - - /** - * A filter that adds a local cache for storing response body for repeated requests. - *

- * If `timeToLive` and `size` are null, a global cache is used configured by the - * global configuration - * {@link org.springframework.cloud.gateway.filter.factory.cache.LocalResponseCacheProperties}. - * @param timeToLive time an entry is kept in cache. Default: 5 minutes - * @param size size expression to limit cache size (See format in {@link DataSize}. - * Default: {@code null} (no limit) - * @return a {@link GatewayFilterSpec} that can be used to apply additional filters - */ - public GatewayFilterSpec localResponseCache(Duration timeToLive, DataSize size) { - return filter(getBean(LocalResponseCacheGatewayFilterFactory.class) - .apply(c -> c.setTimeToLive(timeToLive).setSize(size))); - } - - /** - * A filter that removes duplication on a response header before it is returned to the - * client by the Gateway. - * @param headerName the header name(s), space separated - * @param strategy RETAIN_FIRST, RETAIN_LAST, or RETAIN_UNIQUE - * @return a {@link GatewayFilterSpec} that can be used to apply additional filters - */ - public GatewayFilterSpec dedupeResponseHeader(String headerName, String strategy) { - return filter(getBean(DedupeResponseHeaderGatewayFilterFactory.class) - .apply(c -> c.setStrategy(Strategy.valueOf(strategy)).setName(headerName))); - } - - public GatewayFilterSpec circuitBreaker(Consumer configConsumer) { - SpringCloudCircuitBreakerFilterFactory filterFactory; - try { - filterFactory = getBean(SpringCloudCircuitBreakerFilterFactory.class); - } - catch (NoSuchBeanDefinitionException e) { - throw new NoSuchBeanDefinitionException(SpringCloudCircuitBreakerFilterFactory.class, - "There needs to be a circuit breaker implementation on the classpath that supports reactive APIs."); - } - return filter(filterFactory.apply(this.routeBuilder.getId(), configConsumer)); - } - - /** - * A filter that transforms a JSON request into a gRPC one. - * @param protoDescriptor relative path to the proto descriptor file. - * @param protoFile relative path to the proto definition file. - * @param service fully qualified name of the service that will handle the request. - * @param method method name in the service that will handle the request. - */ - public GatewayFilterSpec jsonToGRPC(String protoDescriptor, String protoFile, String service, String method) { - return filter(getBean(JsonToGrpcGatewayFilterFactory.class).apply(c -> c.setMethod(method) - .setProtoDescriptor(protoDescriptor).setProtoFile(protoFile).setService(service))); - } - - /** - * Maps headers from one name to another. - * @param fromHeader the header name of the original header. - * @param toHeader the header name of the new header. - * @return a {@link GatewayFilterSpec} that can be used to apply additional filters - */ - public GatewayFilterSpec mapRequestHeader(String fromHeader, String toHeader) { - return filter(getBean(MapRequestHeaderGatewayFilterFactory.class) - .apply(c -> c.setFromHeader(fromHeader).setToHeader(toHeader))); - } - - /** - * A filter that can be used to modify the request body. - * @param inClass the class to convert the incoming request body to - * @param outClass the class the Gateway will add to the request before it is routed - * @param rewriteFunction the {@link RewriteFunction} that transforms the request body - * @param the original request body class - * @param the new request body class - * @return a {@link GatewayFilterSpec} that can be used to apply additional filters - */ - // TODO: setup custom spec - public GatewayFilterSpec modifyRequestBody(Class inClass, Class outClass, - RewriteFunction rewriteFunction) { - return filter(getBean(ModifyRequestBodyGatewayFilterFactory.class) - .apply(c -> c.setRewriteFunction(inClass, outClass, rewriteFunction))); - } - - /** - * A filter that can be used to modify the request body. - * @param inClass the parameterized type reference to convert the incoming request - * body to - * @param outClass the parameterized type reference the Gateway will add to the - * request before it is routed - * @param rewriteFunction the {@link RewriteFunction} that transforms the request body - * @param the original request body class - * @param the new request body class - * @return a {@link GatewayFilterSpec} that can be used to apply additional filters - */ - public GatewayFilterSpec modifyRequestBody(ParameterizedTypeReference inClass, - ParameterizedTypeReference outClass, RewriteFunction rewriteFunction) { - return filter(getBean(ModifyRequestBodyGatewayFilterFactory.class) - .apply(c -> c.setRewriteFunction(inClass, outClass, rewriteFunction))); - } - - /** - * A filter that can be used to modify the request body. - * @param inClass the class to convert the incoming request body to - * @param outClass the class the Gateway will add to the request before it is routed - * @param newContentType the new Content-Type header to be sent - * @param rewriteFunction the {@link RewriteFunction} that transforms the request body - * @param the original request body class - * @param the new request body class - * @return a {@link GatewayFilterSpec} that can be used to apply additional filters - */ - public GatewayFilterSpec modifyRequestBody(Class inClass, Class outClass, String newContentType, - RewriteFunction rewriteFunction) { - return filter(getBean(ModifyRequestBodyGatewayFilterFactory.class) - .apply(c -> c.setRewriteFunction(inClass, outClass, rewriteFunction).setContentType(newContentType))); - } - - /** - * A filter that can be used to modify the request body. - * @param inClass the parameterized type reference to convert the incoming request - * body to - * @param outClass the parameterized type reference the Gateway will add to the - * request before it is routed - * @param newContentType the new Content-Type header to be sent - * @param rewriteFunction the {@link RewriteFunction} that transforms the request body - * @param the original request body class - * @param the new request body class - * @return a {@link GatewayFilterSpec} that can be used to apply additional filters - */ - public GatewayFilterSpec modifyRequestBody(ParameterizedTypeReference inClass, - ParameterizedTypeReference outClass, String newContentType, RewriteFunction rewriteFunction) { - return filter(getBean(ModifyRequestBodyGatewayFilterFactory.class) - .apply(c -> c.setRewriteFunction(inClass, outClass, rewriteFunction).setContentType(newContentType))); - } - - /** - * A filter that can cache the request body. - * @param bodyClass the class to convert the incoming request body to the original - * request body class - * @return a {@link GatewayFilterSpec} that can be used to apply additional filters - */ - public GatewayFilterSpec cacheRequestBody(Class bodyClass) { - return filter(getBean(CacheRequestBodyGatewayFilterFactory.class).apply(c -> c.setBodyClass(bodyClass))); - } - - /** - * A filter that can be used to modify the request body. - * @param configConsumer request spec for response modification - * @param the original request body class - * @param the new request body class - * @return a {@link GatewayFilterSpec} that can be used to apply additional filters - *

-	 * {@code
-	 * ...
-	 * .modifyRequestBody(c -> c
-	 *		.setInClass(Some.class)
-	 *		.setOutClass(SomeOther.class)
-	 *		.setInHints(hintsIn)
-	 *		.setOutHints(hintsOut)
-	 *		.setRewriteFunction(rewriteFunction))
-	 * }
-	 * 
- */ - public GatewayFilterSpec modifyRequestBody( - Consumer configConsumer) { - return filter(getBean(ModifyRequestBodyGatewayFilterFactory.class).apply(configConsumer)); - } - - /** - * A filter that can be used to modify the response body. - * @param inClass the class to conver the response body to - * @param outClass the class the Gateway will add to the response before it is - * returned to the client - * @param rewriteFunction the {@link RewriteFunction} that transforms the response - * body - * @param the original response body class - * @param the new response body class - * @return a {@link GatewayFilterSpec} that can be used to apply additional filters - */ - public GatewayFilterSpec modifyResponseBody(Class inClass, Class outClass, - RewriteFunction rewriteFunction) { - return filter(getBean(ModifyResponseBodyGatewayFilterFactory.class) - .apply(c -> c.setRewriteFunction(inClass, outClass, rewriteFunction))); - } - - /** - * A filter that can be used to modify the response body. - * @param inClass the class to conver the response body to - * @param outClass the class the Gateway will add to the response before it is - * returned to the client - * @param newContentType the new Content-Type header to be returned - * @param rewriteFunction the {@link RewriteFunction} that transforms the response - * body - * @param the original response body class - * @param the new response body class - * @return a {@link GatewayFilterSpec} that can be used to apply additional filters - */ - // TODO: setup custom spec - public GatewayFilterSpec modifyResponseBody(Class inClass, Class outClass, String newContentType, - RewriteFunction rewriteFunction) { - return filter(getBean(ModifyResponseBodyGatewayFilterFactory.class).apply( - c -> c.setRewriteFunction(inClass, outClass, rewriteFunction).setNewContentType(newContentType))); - } - - /** - * A filter that can be used to modify the response body using custom spec. - * @param configConsumer response spec for response modification - * @param the original response body class - * @param the new response body class - * @return a {@link GatewayFilterSpec} that can be used to apply additional filters - *
-	 * {@code
-	 * ...
-	 * .modifyResponseBody(c -> c
-	 *		.setInClass(Some.class)
-	 *		.setOutClass(SomeOther.class)
-	 *		.setOutHints(hintsOut)
-	 *		.setRewriteFunction(rewriteFunction))
-	 * }
-	 * 
- */ - public GatewayFilterSpec modifyResponseBody( - Consumer configConsumer) { - return filter(getBean(ModifyResponseBodyGatewayFilterFactory.class).apply(configConsumer)); - } - - /** - * A filter that can be used to add a prefix to the path of a request before it is - * routed by the Gateway. - * @param prefix the prefix to add to the path - * @return a {@link GatewayFilterSpec} that can be used to apply additional filters - */ - public GatewayFilterSpec prefixPath(String prefix) { - return filter(getBean(PrefixPathGatewayFilterFactory.class).apply(c -> c.setPrefix(prefix))); - } - - /** - * A filter that will preserve the host header of the request on the outgoing request - * from the Gateway. - * @return a {@link GatewayFilterSpec} that can be used to apply additional filters - */ - public GatewayFilterSpec preserveHostHeader() { - return filter(getBean(PreserveHostHeaderGatewayFilterFactory.class).apply()); - } - - /** - * A filter that will set the Host header to - * {@param hostName} on the outgoing - * request. - * @param hostName the updated Host header value - * @return a {@link GatewayFilterSpec} that can be used to apply additional filters - */ - public GatewayFilterSpec setHostHeader(String hostName) { - return filter(getBean(SetRequestHostHeaderGatewayFilterFactory.class).apply(c -> c.setHost(hostName))); - } - - /** - * A filter that will return a redirect response back to the client. - * @param status an HTTP status code, should be a {@code 300} series redirect - * @param url the URL to redirect to. This URL will be set in the {@code location} - * header - * @return a {@link GatewayFilterSpec} that can be used to apply additional filters - */ - public GatewayFilterSpec redirect(int status, URI url) { - return redirect(String.valueOf(status), url.toString()); - } - - /** - * A filter that will return a redirect response back to the client. - * @param status an HTTP status code, should be a {@code 300} series redirect - * @param url the URL to redirect to. This URL will be set in the {@code location} - * header - * @return a {@link GatewayFilterSpec} that can be used to apply additional filters - */ - public GatewayFilterSpec redirect(int status, String url) { - return redirect(String.valueOf(status), url); - } - - /** - * A filter that will return a redirect response back to the client. - * @param status an HTTP status code, should be a {@code 300} series redirect - * @param url the URL to redirect to. This URL will be set in the {@code location} - * header - * @return a {@link GatewayFilterSpec} that can be used to apply additional filters - */ - public GatewayFilterSpec redirect(String status, URI url) { - return redirect(status, url.toString()); - } - - /** - * A filter that will return a redirect response back to the client. - * @param status an HTTP status code, should be a {@code 300} series redirect - * @param url the URL to redirect to. This URL will be set in the {@code location} - * header - * @return a {@link GatewayFilterSpec} that can be used to apply additional filters - */ - public GatewayFilterSpec redirect(String status, String url) { - return filter(getBean(RedirectToGatewayFilterFactory.class).apply(status, url)); - } - - /** - * A filter that will return a redirect response back to the client. - * @param status an HTTP status code, should be a {@code 300} series redirect - * @param url the URL to redirect to. This URL will be set in the {@code location} - * header - * @return a {@link GatewayFilterSpec} that can be used to apply additional filters - */ - public GatewayFilterSpec redirect(HttpStatus status, URL url) { - try { - return filter(getBean(RedirectToGatewayFilterFactory.class).apply(status, url.toURI())); - } - catch (URISyntaxException e) { - throw new IllegalArgumentException("Invalid URL", e); - } - } - - /** - * A filter that can be used to modify the response body. By default, this only - * removes root attributes. - * @param attributes list of attributes to remove. - * @return a {@link GatewayFilterSpec} that can be used to apply additional filters - */ - public GatewayFilterSpec removeJsonAttributes(String... attributes) { - return removeJsonAttributes(false, attributes); - - } - - /** - * A filter that can be used to modify the response body. - * @param attributes list of attributes to remove. - * @param deleteRecursively if true, all attributes regardless of nesting will be - * removed. - * @return a {@link GatewayFilterSpec} that can be used to apply additional filters - */ - public GatewayFilterSpec removeJsonAttributes(boolean deleteRecursively, String... attributes) { - return filter(getBean(RemoveJsonAttributesResponseBodyGatewayFilterFactory.class) - .apply(c -> c.setFieldList(Arrays.asList(attributes)).setDeleteRecursively(deleteRecursively))); - - } - - /** - * A filter that will remove a request header before the request is routed by the - * Gateway. - * @param headerName the name of the header to remove - * @return a {@link GatewayFilterSpec} that can be used to apply additional filters - */ - public GatewayFilterSpec removeRequestHeader(String headerName) { - return filter(getBean(RemoveRequestHeaderGatewayFilterFactory.class).apply(c -> c.setName(headerName))); - } - - /** - * A filter that will remove a request param before the request is routed by the - * Gateway. - * @param paramName the name of the header to remove - * @return a {@link GatewayFilterSpec} that can be used to apply additional filters - */ - public GatewayFilterSpec removeRequestParameter(String paramName) { - return filter(getBean(RemoveRequestParameterGatewayFilterFactory.class).apply(c -> c.setName(paramName))); - } - - /** - * A filter that will remove a response header before the Gateway returns the response - * to the client. - * @param headerName the name of the header to remove - * @return a {@link GatewayFilterSpec} that can be used to apply additional filters - */ - public GatewayFilterSpec removeResponseHeader(String headerName) { - return filter(getBean(RemoveResponseHeaderGatewayFilterFactory.class).apply(c -> c.setName(headerName))); - } - - /** - * A filter that will set up a request rate limiter for a route. - * @param configConsumer a {@link Consumer} that will return configuration for the - * rate limiter - * @return a {@link GatewayFilterSpec} that can be used to apply additional filters - */ - public GatewayFilterSpec requestRateLimiter( - Consumer configConsumer) { - return filter( - getBean(RequestRateLimiterGatewayFilterFactory.class).apply(this.routeBuilder.getId(), configConsumer)); - } - - /** - * A filter that will set up a request rate limiter for a route. - * @return a {@link GatewayFilterSpec} that can be used to apply additional filters - */ - public RequestRateLimiterSpec requestRateLimiter() { - return new RequestRateLimiterSpec(getBean(RequestRateLimiterGatewayFilterFactory.class)); - } - - /** - * A filter which rewrites the request path before it is routed by the Gateway. - * @param regex a Java regular expression to match the path against - * @param replacement the replacement for the path - * @return a {@link GatewayFilterSpec} that can be used to apply additional filters - */ - public GatewayFilterSpec rewritePath(String regex, String replacement) { - return filter(getBean(RewritePathGatewayFilterFactory.class) - .apply(c -> c.setRegexp(regex).setReplacement(replacement))); - } - - /** - * A filter that will retry failed requests. By default {@code 5xx} errors and - * {@code GET}s are retryable. - * @param retries max number of retries - * @return a {@link GatewayFilterSpec} that can be used to apply additional filters - */ - public GatewayFilterSpec retry(int retries) { - return filter(getBean(RetryGatewayFilterFactory.class).apply(this.routeBuilder.getId(), - retryConfig -> retryConfig.setRetries(retries))); - } - - /** - * A filter that will retry failed requests. - * @param retryConsumer a {@link Consumer} which returns a - * {@link org.springframework.cloud.gateway.filter.factory.RetryGatewayFilterFactory.RetryConfig} - * to configure the retry functionality - * @return a {@link GatewayFilterSpec} that can be used to apply additional filters - */ - public GatewayFilterSpec retry(Consumer retryConsumer) { - return filter(getBean(RetryGatewayFilterFactory.class).apply(this.routeBuilder.getId(), retryConsumer)); - } - - /** - * A filter that will retry failed requests. - * @param repeat a {@link Repeat} - * @param retry a {@link Retry} - * @return a {@link GatewayFilterSpec} that can be used to apply additional filters - */ - public GatewayFilterSpec retry(Repeat repeat, Retry retry) { - RetryGatewayFilterFactory filterFactory = getBean(RetryGatewayFilterFactory.class); - return filter(filterFactory.apply(this.routeBuilder.getId(), repeat, retry)); - } - - /** - * A filter that adds a number of headers to the response at the reccomendation from - * this blog - * post. - * @return a {@link GatewayFilterSpec} that can be used to apply additional filters - */ - public GatewayFilterSpec secureHeaders() { - return filter(getBean(SecureHeadersGatewayFilterFactory.class).apply(config -> { - })); - } - - /** - * A filter that adds a number of headers to the response at the reccomendation from - * this blog - * post. - * @param configConsumer self define headers - * @return a {@link GatewayFilterSpec} that can be used to apply additional filters - */ - public GatewayFilterSpec secureHeaders(Consumer configConsumer) { - return filter(getBean(SecureHeadersGatewayFilterFactory.class).apply(configConsumer)); - } - - /** - * A filter that sets the path of the request before it is routed by the Gateway. - * @param template the path to set on the request, allows multiple matching segments - * using URI templates from Spring Framework - * @return a {@link GatewayFilterSpec} that can be used to apply additional filters - */ - public GatewayFilterSpec setPath(String template) { - return filter(getBean(SetPathGatewayFilterFactory.class).apply(c -> c.setTemplate(template))); - } - - /** - * A filter that sets a header on the request before it is routed by the Gateway. - * @param headerName the header name - * @param headerValue the value of the header - * @return a {@link GatewayFilterSpec} that can be used to apply additional filters - */ - public GatewayFilterSpec setRequestHeader(String headerName, String headerValue) { - return filter(getBean(SetRequestHeaderGatewayFilterFactory.class) - .apply(c -> c.setName(headerName).setValue(headerValue))); - } - - /** - * A filter that sets a header on the response before it is returned to the client by - * the Gateway. - * @param headerName the header name - * @param headerValue the value of the header - * @return a {@link GatewayFilterSpec} that can be used to apply additional filters - */ - public GatewayFilterSpec setResponseHeader(String headerName, String headerValue) { - return filter(getBean(SetResponseHeaderGatewayFilterFactory.class) - .apply(c -> c.setName(headerName).setValue(headerValue))); - } - - /** - * A filter that rewrites a header value on the response before it is returned to the - * client by the Gateway. - * @param headerName the header name - * @param regex a Java regular expression to match the path against - * @param replacement the replacement for the path - * @return a {@link GatewayFilterSpec} that can be used to apply additional filters - */ - public GatewayFilterSpec rewriteResponseHeader(String headerName, String regex, String replacement) { - return filter(getBean(RewriteResponseHeaderGatewayFilterFactory.class) - .apply(c -> c.setReplacement(replacement).setRegexp(regex).setName(headerName))); - } - - /** - * A filter that rewrites the value of Location response header, ridding it of backend - * specific details. - * @param stripVersionMode NEVER_STRIP, AS_IN_REQUEST, or ALWAYS_STRIP - * @param locationHeaderName a location header name - * @param hostValue host value - * @param protocolsRegex a valid regex String, against which the protocol name will be - * matched - * @return a {@link GatewayFilterSpec} that can be used to apply additional filters - */ - public GatewayFilterSpec rewriteLocationResponseHeader(String stripVersionMode, String locationHeaderName, - String hostValue, String protocolsRegex) { - return filter(getBean(RewriteLocationResponseHeaderGatewayFilterFactory.class).apply( - c -> c.setStripVersion(StripVersion.valueOf(stripVersionMode)).setLocationHeaderName(locationHeaderName) - .setHostValue(hostValue).setProtocols(protocolsRegex))); - } - - /** - * A filter that sets the status on the response before it is returned to the client - * by the Gateway. - * @param status the status to set on the response - * @return a {@link GatewayFilterSpec} that can be used to apply additional filters - */ - public GatewayFilterSpec setStatus(int status) { - return setStatus(String.valueOf(status)); - } - - /** - * A filter that sets the status on the response before it is returned to the client - * by the Gateway. - * @param status the status to set on the response - * @return a {@link GatewayFilterSpec} that can be used to apply additional filters - */ - public GatewayFilterSpec setStatus(HttpStatus status) { - return setStatus(status.name()); - } - - /** - * A filter that sets the status on the response before it is returned to the client - * by the Gateway. - * @param status the status to set on the response - * @return a {@link GatewayFilterSpec} that can be used to apply additional filters - */ - public GatewayFilterSpec setStatus(String status) { - return filter(getBean(SetStatusGatewayFilterFactory.class).apply(c -> c.setStatus(status))); - } - - /** - * A filter which forces a {@code WebSession::save} operation before forwarding the - * call downstream. This is of particular use when using something like - * Spring Session with a lazy - * data store and need to ensure the session state has been saved before making the - * forwarded call. If you are integrating - * Spring Security with - * Spring Session, and want to ensure security details have been forwarded to the - * remote process, this is critical. - * @return a {@link GatewayFilterSpec} that can be used to apply additional filters - */ - @SuppressWarnings("unchecked") - public GatewayFilterSpec saveSession() { - return filter(getBean(SaveSessionGatewayFilterFactory.class).apply(c -> { - })); - } - - /** - * Strips the prefix from the path of the request before it is routed by the Gateway. - * @param parts the number of parts of the path to remove - * @return a {@link GatewayFilterSpec} that can be used to apply additional filters - */ - public GatewayFilterSpec stripPrefix(int parts) { - return filter(getBean(StripPrefixGatewayFilterFactory.class).apply(c -> c.setParts(parts))); - } - - /** - * A filter which changes the URI the request will be routed to by the Gateway by - * pulling it from a header on the request. - * @param headerName the header name containing the URI - * @return a {@link GatewayFilterSpec} that can be used to apply additional filters - */ - public GatewayFilterSpec requestHeaderToRequestUri(String headerName) { - return filter(getBean(RequestHeaderToRequestUriGatewayFilterFactory.class).apply(c -> c.setName(headerName))); - } - - /** - * A filter which change the URI the request will be routed to by the Gateway. - * @param determineRequestUri a {@link Function} which takes a - * {@link ServerWebExchange} and returns a URI to route the request to - * @return a {@link GatewayFilterSpec} that can be used to apply additional filters - */ - public GatewayFilterSpec changeRequestUri(Function> determineRequestUri) { - return filter(new AbstractChangeRequestUriGatewayFilterFactory(Object.class) { - @Override - protected Optional determineRequestUri(ServerWebExchange exchange, Object config) { - return determineRequestUri.apply(exchange); - } - }.apply(c -> { - })); - } - - /** - * A filter that sets the maximum permissible size of a Request. - * @param size the maximum size of a request - * @return a {@link GatewayFilterSpec} that can be used to apply additional filters - */ - public GatewayFilterSpec setRequestSize(Long size) { - return setRequestSize(DataSize.ofBytes(size)); - } - - /** - * A filter that sets the maximum permissible size of a Request. - * @param size the maximum size of a request - * @return a {@link GatewayFilterSpec} that can be used to apply additional filters - */ - public GatewayFilterSpec setRequestSize(DataSize size) { - return filter(getBean(RequestSizeGatewayFilterFactory.class).apply(c -> c.setMaxSize(size))); - } - - /** - * A filter that sets the maximum permissible size of headers of Request. - * @param size the maximum size of header of request - * @return a {@link GatewayFilterSpec} that can be used to apply additional filters - */ - public GatewayFilterSpec setRequestHeaderSize(DataSize size) { - return filter(getBean(RequestHeaderSizeGatewayFilterFactory.class).apply(c -> c.setMaxSize(size))); - } - - /** - * A filter that enables token relay by extracting the access token from the currently - * authenticated user and puts it in a request header for downstream requests. - * @return a {@link GatewayFilterSpec} that can be used to apply additional filters - */ - public GatewayFilterSpec tokenRelay() { - try { - return filter(getBean(TokenRelayGatewayFilterFactory.class).apply(c -> { - })); - } - catch (NoSuchBeanDefinitionException e) { - throw new IllegalStateException("No TokenRelayGatewayFilterFactory bean was found. Did you include the " - + "org.springframework.boot:spring-boot-starter-oauth2-client dependency?"); - } - } - - /** - * A filter that enables token relay by extracting the access token of a specified - * {@code ClientRegistration} and puts it in a request header for downstream requests. - * @param clientRegistrationId the client registration id to use for building the - * authorization request - * @return a {@link GatewayFilterSpec} that can be used to apply additional filters - */ - public GatewayFilterSpec tokenRelay(String clientRegistrationId) { - try { - return filter(getBean(TokenRelayGatewayFilterFactory.class).apply(c -> c.setName(clientRegistrationId))); - } - catch (NoSuchBeanDefinitionException e) { - throw new IllegalStateException("No TokenRelayGatewayFilterFactory bean was found. Did you include the " - + "org.springframework.boot:spring-boot-starter-oauth2-client dependency?"); - } - } - - /** - * Adds hystrix execution exception headers to fallback request. Depends on @{code - * org.springframework.cloud::spring-cloud-starter-netflix-hystrix} being on the - * classpath, {@see https://cloud.spring.io/spring-cloud-netflix/} - * @param config a {@link FallbackHeadersGatewayFilterFactory.Config} which provides - * the header names. If header names arguments are not provided, default values are - * used. - * @return a {@link GatewayFilterSpec} that can be used to apply additional filters - */ - public GatewayFilterSpec fallbackHeaders(FallbackHeadersGatewayFilterFactory.Config config) { - FallbackHeadersGatewayFilterFactory factory = getFallbackHeadersGatewayFilterFactory(); - return filter(factory.apply(config)); - } - - /** - * Adds hystrix execution exception headers to fallback request. Depends on @{code - * org.springframework.cloud::spring-cloud-starter-netflix-hystrix} being on the - * classpath, {@see https://cloud.spring.io/spring-cloud-netflix/} - * @param configConsumer a {@link Consumer} which can be used to set up the names of - * the headers in the config. If header names arguments are not provided, default - * values are used. - * @return a {@link GatewayFilterSpec} that can be used to apply additional filters - */ - public GatewayFilterSpec fallbackHeaders(Consumer configConsumer) { - FallbackHeadersGatewayFilterFactory factory = getFallbackHeadersGatewayFilterFactory(); - return filter(factory.apply(configConsumer)); - } - - private FallbackHeadersGatewayFilterFactory getFallbackHeadersGatewayFilterFactory() { - FallbackHeadersGatewayFilterFactory factory; - try { - factory = getBean(FallbackHeadersGatewayFilterFactory.class); - } - catch (NoSuchBeanDefinitionException e) { - throw new NoSuchBeanDefinitionException(FallbackHeadersGatewayFilterFactory.class, - "This is probably because Hystrix is missing from the classpath, which can be resolved by adding dependency on 'org.springframework.cloud:spring-cloud-starter-netflix-hystrix'"); - } - return factory; - } - - public class RequestRateLimiterSpec { - - private final RequestRateLimiterGatewayFilterFactory filter; - - public RequestRateLimiterSpec(RequestRateLimiterGatewayFilterFactory filter) { - this.filter = filter; - } - - public > RequestRateLimiterSpec rateLimiter(Class rateLimiterType, - Consumer configConsumer) { - R rateLimiter = getBean(rateLimiterType); - C config = rateLimiter.newConfig(); - configConsumer.accept(config); - rateLimiter.getConfig().put(routeBuilder.getId(), config); - return this; - } - - public GatewayFilterSpec configure(Consumer configConsumer) { - filter(this.filter.apply(routeBuilder.getId(), configConsumer)); - return GatewayFilterSpec.this; - } - - // useful when nothing to configure - public GatewayFilterSpec and() { - return configure(config -> { - }); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/builder/PredicateSpec.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/builder/PredicateSpec.java deleted file mode 100644 index d88a7cc1..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/builder/PredicateSpec.java +++ /dev/null @@ -1,310 +0,0 @@ -/* - * Copyright 2013-2020 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.route.builder; - -import java.time.ZonedDateTime; -import java.util.Arrays; -import java.util.function.Function; -import java.util.function.Predicate; - -import org.springframework.cloud.gateway.handler.AsyncPredicate; -import org.springframework.cloud.gateway.handler.predicate.AfterRoutePredicateFactory; -import org.springframework.cloud.gateway.handler.predicate.BeforeRoutePredicateFactory; -import org.springframework.cloud.gateway.handler.predicate.BetweenRoutePredicateFactory; -import org.springframework.cloud.gateway.handler.predicate.CloudFoundryRouteServiceRoutePredicateFactory; -import org.springframework.cloud.gateway.handler.predicate.CookieRoutePredicateFactory; -import org.springframework.cloud.gateway.handler.predicate.HeaderRoutePredicateFactory; -import org.springframework.cloud.gateway.handler.predicate.HostRoutePredicateFactory; -import org.springframework.cloud.gateway.handler.predicate.MethodRoutePredicateFactory; -import org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory; -import org.springframework.cloud.gateway.handler.predicate.QueryRoutePredicateFactory; -import org.springframework.cloud.gateway.handler.predicate.ReadBodyRoutePredicateFactory; -import org.springframework.cloud.gateway.handler.predicate.RemoteAddrRoutePredicateFactory; -import org.springframework.cloud.gateway.handler.predicate.WeightRoutePredicateFactory; -import org.springframework.cloud.gateway.handler.predicate.XForwardedRemoteAddrRoutePredicateFactory; -import org.springframework.cloud.gateway.route.Route; -import org.springframework.cloud.gateway.support.ipresolver.RemoteAddressResolver; -import org.springframework.http.HttpMethod; -import org.springframework.web.server.ServerWebExchange; - -import static java.util.Arrays.stream; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.toAsyncPredicate; - -/** - * Predicates that can be applied to a URI route. - */ -public class PredicateSpec extends UriSpec { - - PredicateSpec(Route.AsyncBuilder routeBuilder, RouteLocatorBuilder.Builder builder) { - super(routeBuilder, builder); - } - - public PredicateSpec order(int order) { - this.routeBuilder.order(order); - return this; - } - - public BooleanSpec predicate(Predicate predicate) { - return asyncPredicate(toAsyncPredicate(predicate)); - } - - public BooleanSpec asyncPredicate(AsyncPredicate predicate) { - this.routeBuilder.asyncPredicate(predicate); - return new BooleanSpec(this.routeBuilder, this.builder); - } - - protected GatewayFilterSpec createGatewayFilterSpec() { - return new GatewayFilterSpec(this.routeBuilder, this.builder); - } - - /** - * A predicate to check if a request was made after a specific {@link ZonedDateTime}. - * @param datetime requests would only be routed after this {@link ZonedDateTime} - * @return a {@link BooleanSpec} to be used to add logical operators - */ - public BooleanSpec after(ZonedDateTime datetime) { - return asyncPredicate(getBean(AfterRoutePredicateFactory.class).applyAsync(c -> c.setDatetime(datetime))); - } - - /** - * A predicate to check if a request was made before a specific {@link ZonedDateTime}. - * @param datetime requests will only be routed before this {@link ZonedDateTime} - * @return a {@link BooleanSpec} to be used to add logical operators - */ - public BooleanSpec before(ZonedDateTime datetime) { - return asyncPredicate(getBean(BeforeRoutePredicateFactory.class).applyAsync(c -> c.setDatetime(datetime))); - } - - /** - * A predicate to check if a request was made between two {@link ZonedDateTime}s. - * @param datetime1 the request must have been made after this {@link ZonedDateTime} - * @param datetime2 the request must be made before this {@link ZonedDateTime} - * @return a {@link BooleanSpec} to be used to add logical operators - */ - public BooleanSpec between(ZonedDateTime datetime1, ZonedDateTime datetime2) { - return asyncPredicate(getBean(BetweenRoutePredicateFactory.class) - .applyAsync(c -> c.setDatetime1(datetime1).setDatetime2(datetime2))); - } - - /** - * A predicate that checks if a cookie matches a given regular expression. - * @param name the name of the cookie - * @param regex the value of the cookies will be evaluated against this regular - * expression - * @return a {@link BooleanSpec} to be used to add logical operators - */ - public BooleanSpec cookie(String name, String regex) { - return asyncPredicate( - getBean(CookieRoutePredicateFactory.class).applyAsync(c -> c.setName(name).setRegexp(regex))); - } - - /** - * A predicate that checks if a given header is present on the request. - * @param header the header name to check - * @return a {@link BooleanSpec} to be used to add logical operators - */ - public BooleanSpec header(String header) { - return asyncPredicate(getBean(HeaderRoutePredicateFactory.class).applyAsync(c -> c.setHeader(header))); - } - - /** - * A predicate that checks if a given headers has a value which matches a regular - * expression. - * @param header the header name to check - * @param regex the regular expression to check against - * @return a {@link BooleanSpec} to be used to add logical operators - */ - public BooleanSpec header(String header, String regex) { - return asyncPredicate( - getBean(HeaderRoutePredicateFactory.class).applyAsync(c -> c.setHeader(header).setRegexp(regex))); - } - - /** - * A predicate that checks if the {@code host} header matches a given pattern. - * @param pattern the pattern to check against. The pattern is an Ant style pattern - * with {@code .} as a separator - * @return a {@link BooleanSpec} to be used to add logical operators - */ - public BooleanSpec host(String... pattern) { - return asyncPredicate( - getBean(HostRoutePredicateFactory.class).applyAsync(c -> c.setPatterns(Arrays.asList(pattern)))); - } - - /** - * A predicate that checks if the HTTP method matches. - * @param methods the name of the HTTP methods - * @return a {@link BooleanSpec} to be used to add logical operators - */ - public BooleanSpec method(String... methods) { - return asyncPredicate(getBean(MethodRoutePredicateFactory.class).applyAsync(c -> { - HttpMethod[] httpMethods = stream(methods).map(HttpMethod::valueOf).toArray(HttpMethod[]::new); - c.setMethods(httpMethods); - })); - } - - /** - * A predicate that checks if the HTTP method matches. - * @param methods the HTTP methods - * @return a {@link BooleanSpec} to be used to add logical operators - */ - public BooleanSpec method(HttpMethod... methods) { - return asyncPredicate(getBean(MethodRoutePredicateFactory.class).applyAsync(c -> { - c.setMethods(methods); - })); - } - - /** - * A predicate that checks if the path of the request matches the given pattern. - * @param patterns the pattern to check the path against. The pattern is a - * {@link org.springframework.util.PathMatcher} pattern - * @return a {@link BooleanSpec} to be used to add logical operators - */ - public BooleanSpec path(String... patterns) { - return asyncPredicate( - getBean(PathRoutePredicateFactory.class).applyAsync(c -> c.setPatterns(Arrays.asList(patterns)))); - } - - /** - * A predicate that checks if the path of the request matches the given pattern. - * @param patterns the pattern to check the path against. The pattern is a - * {@link org.springframework.util.PathMatcher} pattern - * @param matchTrailingSlash set to false if you do not want this path to match when - * there is a trailing / - * @return a {@link BooleanSpec} to be used to add logical operators - */ - public BooleanSpec path(boolean matchTrailingSlash, String... patterns) { - return asyncPredicate(getBean(PathRoutePredicateFactory.class) - .applyAsync(c -> c.setPatterns(Arrays.asList(patterns)).setMatchTrailingSlash(matchTrailingSlash))); - } - - /** - * This predicate is BETA and may be subject to change in a future release. A - * predicate that checks the contents of the request body - * @param inClass the class to parse the body to - * @param predicate a predicate to check the contents of the body - * @param the type the body is parsed to - * @return a {@link BooleanSpec} to be used to add logical operators - */ - public BooleanSpec readBody(Class inClass, Predicate predicate) { - return asyncPredicate( - getBean(ReadBodyRoutePredicateFactory.class).applyAsync(c -> c.setPredicate(inClass, predicate))); - } - - /** - * A predicate that checks if a query parameter matches a regular expression. - * @param param the query parameter name - * @param regex the regular expression to evaluate the query parameter value against - * @return a {@link BooleanSpec} to be used to add logical operators - */ - public BooleanSpec query(String param, String regex) { - return asyncPredicate( - getBean(QueryRoutePredicateFactory.class).applyAsync(c -> c.setParam(param).setRegexp(regex))); - } - - /** - * A predicate that checks if a given query parameter is present in the request URL. - * @param param the query parameter name - * @return a {@link BooleanSpec} to be used to add logical operators - */ - public BooleanSpec query(String param) { - return asyncPredicate(getBean(QueryRoutePredicateFactory.class).applyAsync(c -> c.setParam(param))); - } - - /** - * A predicate which checks the remote address of the request. By default the - * RemoteAddr Route Predicate Factory uses the remote address from the incoming - * request. This may not match the actual client IP address if Spring Cloud Gateway - * sits behind a proxy layer. Use - * {@link PredicateSpec#remoteAddr(RemoteAddressResolver, String...)} to customize the - * resolver. You can customize the way that the remote address is resolved by setting - * a custom RemoteAddressResolver. - * @param addrs the remote address to verify. Should use CIDR-notation (IPv4 or IPv6) - * strings. - * @return a {@link BooleanSpec} to be used to add logical operators - */ - public BooleanSpec remoteAddr(String... addrs) { - return remoteAddr(null, addrs); - } - - /** - * A predicate which checks the remote address of the request. Useful if Spring Cloud - * Gateway site behind a proxy layer. Spring Cloud Gateway comes with one non-default - * remote address resolver which is based off of the {@code X-Forwarded-For} header, - * {@link org.springframework.cloud.gateway.support.ipresolver.XForwardedRemoteAddressResolver}. - * See - * {@link org.springframework.cloud.gateway.support.ipresolver.XForwardedRemoteAddressResolver} - * for more information. - * @param resolver the {@link RemoteAddressResolver} to use to resolve the remote IP - * address against - * @param addrs the remote address to verify. Should use CIDR-notation (IPv4 or IPv6) - * strings. - * @return a {@link BooleanSpec} to be used to add logical operators - */ - public BooleanSpec remoteAddr(RemoteAddressResolver resolver, String... addrs) { - return asyncPredicate(getBean(RemoteAddrRoutePredicateFactory.class).applyAsync(c -> { - c.setSources(addrs); - if (resolver != null) { - c.setRemoteAddressResolver(resolver); - } - })); - } - - /** - * A predicate which checks the remote address of the request based off of the - * {@code X-Forwarded-For} header. Useful if Spring Cloud Gateway site behind a proxy - * layer. See - * {@link org.springframework.cloud.gateway.support.ipresolver.XForwardedRemoteAddressResolver} - * for more information. - * @param addrs the remote address to verify. Should use CIDR-notation (IPv4 or IPv6) - * strings. - * @return a {@link BooleanSpec} to be used to add logical operators - */ - public BooleanSpec xForwardedRemoteAddr(String... addrs) { - return asyncPredicate(getBean(XForwardedRemoteAddrRoutePredicateFactory.class).applyAsync(c -> { - c.setSources(addrs); - })); - } - - /** - * A predicate which will select a route based on its assigned weight. - * @param group the group the route belongs to - * @param weight the weight for the route - * @return a {@link BooleanSpec} to be used to add logical operators - */ - public BooleanSpec weight(String group, int weight) { - return asyncPredicate(getBean(WeightRoutePredicateFactory.class) - .applyAsync(c -> c.setGroup(group).setRouteId(routeBuilder.getId()).setWeight(weight))); - } - - public BooleanSpec cloudFoundryRouteService() { - return predicate(getBean(CloudFoundryRouteServiceRoutePredicateFactory.class).apply(c -> { - })); - } - - /** - * A predicate which is always true. - * @return a {@link BooleanSpec} to be used to add logical operators - */ - public BooleanSpec alwaysTrue() { - return predicate(exchange -> true); - } - - public BooleanSpec not(Function fn) { - return alwaysTrue().and().not(fn); - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/builder/RouteLocatorBuilder.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/builder/RouteLocatorBuilder.java deleted file mode 100644 index 818b64c8..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/builder/RouteLocatorBuilder.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright 2013-2020 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.route.builder; - -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; -import java.util.function.Function; - -import reactor.core.publisher.Flux; - -import org.springframework.cloud.gateway.route.Route; -import org.springframework.cloud.gateway.route.RouteLocator; -import org.springframework.context.ConfigurableApplicationContext; - -/** - * Used to build a {@link RouteLocator}. - */ -public class RouteLocatorBuilder { - - private ConfigurableApplicationContext context; - - public RouteLocatorBuilder(ConfigurableApplicationContext context) { - this.context = context; - } - - /** - * Creates a new {@link Builder}. - * @return a new {@link Builder}. - */ - public Builder routes() { - return new Builder(context); - } - - /** - * A class that can be used to construct routes and return a {@link RouteLocator}. - */ - public static class Builder { - - private List> routes = new ArrayList<>(); - - private ConfigurableApplicationContext context; - - public Builder(ConfigurableApplicationContext context) { - this.context = context; - } - - /** - * Creates a new {@link Route}. - * @param id the unique id for the route - * @param fn a function which takes in a {@link PredicateSpec} and returns a - * {@link Route.AsyncBuilder} - * @return a {@link Builder} - */ - public Builder route(String id, Function> fn) { - Buildable routeBuilder = fn.apply(new RouteSpec(this).id(id)); - add(routeBuilder); - return this; - } - - /** - * Creates a new {@link Route}. - * @param fn a function which takes in a {@link PredicateSpec} and returns a - * {@link Route.AsyncBuilder} - * @return a {@link Builder} - */ - public Builder route(Function> fn) { - Buildable routeBuilder = fn.apply(new RouteSpec(this).randomId()); - add(routeBuilder); - return this; - } - - /** - * Builds and returns a {@link RouteLocator}. - * @return a {@link RouteLocator} - */ - public RouteLocator build() { - return () -> Flux.fromIterable(this.routes).map(routeBuilder -> routeBuilder.build()); - } - - ConfigurableApplicationContext getContext() { - return context; - } - - void add(Buildable route) { - routes.add(route); - } - - } - - public static class RouteSpec { - - private final Route.AsyncBuilder routeBuilder = Route.async(); - - private final Builder builder; - - RouteSpec(Builder builder) { - this.builder = builder; - } - - public PredicateSpec id(String id) { - this.routeBuilder.id(id); - return predicateBuilder(); - } - - public PredicateSpec randomId() { - return id(UUID.randomUUID().toString()); - } - - private PredicateSpec predicateBuilder() { - return new PredicateSpec(this.routeBuilder, this.builder); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/builder/UriSpec.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/builder/UriSpec.java deleted file mode 100644 index 7b678da0..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/route/builder/UriSpec.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2013-2020 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.route.builder; - -import java.net.URI; -import java.util.Map; -import java.util.function.Consumer; - -import org.springframework.cloud.gateway.route.Route; - -/** - * A specification to add a URI to a route. - */ -public class UriSpec { - - final Route.AsyncBuilder routeBuilder; - - final RouteLocatorBuilder.Builder builder; - - UriSpec(Route.AsyncBuilder routeBuilder, RouteLocatorBuilder.Builder builder) { - this.routeBuilder = routeBuilder; - this.builder = builder; - } - - public UriSpec customize(Consumer routeConsumer) { - routeConsumer.accept(this.routeBuilder); - return this; - } - - public UriSpec replaceMetadata(Map metadata) { - this.routeBuilder.replaceMetadata(metadata); - return this; - } - - public UriSpec metadata(Map metadata) { - this.routeBuilder.metadata(metadata); - return this; - } - - public UriSpec metadata(String key, Object value) { - this.routeBuilder.metadata(key, value); - return this; - } - - /** - * Set the URI for the route. - * @param uri the URI for the route - * @return a {@link Route.AsyncBuilder} - */ - public Buildable uri(String uri) { - return this.routeBuilder.uri(uri); - } - - /** - * Set the URI for the route. - * @param uri the URI for the route. - * @return a {@link Route.AsyncBuilder}S - */ - public Buildable uri(URI uri) { - return this.routeBuilder.uri(uri); - } - - T getBean(Class type) { - return this.builder.getContext().getBean(type); - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/AbstractConfigurable.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/AbstractConfigurable.java deleted file mode 100644 index 1e2e93b5..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/AbstractConfigurable.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2013-2020 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.support; - -import org.springframework.beans.BeanUtils; -import org.springframework.core.style.ToStringCreator; - -public abstract class AbstractConfigurable implements Configurable { - - private Class configClass; - - protected AbstractConfigurable(Class configClass) { - this.configClass = configClass; - } - - @Override - public Class getConfigClass() { - return configClass; - } - - @Override - public C newConfig() { - return BeanUtils.instantiateClass(this.configClass); - } - - @Override - public String toString() { - return new ToStringCreator(this).append("configClass", configClass).toString(); - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/AbstractStatefulConfigurable.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/AbstractStatefulConfigurable.java deleted file mode 100644 index ae1df0fa..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/AbstractStatefulConfigurable.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2013-2020 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.support; - -import java.util.HashMap; -import java.util.Map; - -import org.springframework.core.style.ToStringCreator; - -public abstract class AbstractStatefulConfigurable extends AbstractConfigurable - implements StatefulConfigurable { - - private Map config = new HashMap<>(); - - protected AbstractStatefulConfigurable(Class configClass) { - super(configClass); - } - - @Override - public Map getConfig() { - return this.config; - } - - @Override - public String toString() { - return new ToStringCreator(this).append("config", config).append("configClass", getConfigClass()).toString(); - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/BodyInserterContext.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/BodyInserterContext.java deleted file mode 100644 index 53d187e3..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/BodyInserterContext.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2013-2020 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.support; - -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -import org.springframework.http.codec.HttpMessageWriter; -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.web.reactive.function.BodyInserter; -import org.springframework.web.reactive.function.client.ExchangeStrategies; - -public class BodyInserterContext implements BodyInserter.Context { - - private final ExchangeStrategies exchangeStrategies; - - public BodyInserterContext() { - this.exchangeStrategies = ExchangeStrategies.withDefaults(); - } - - public BodyInserterContext(ExchangeStrategies exchangeStrategies) { - this.exchangeStrategies = exchangeStrategies; // TODO: support custom strategies - } - - @Override - public List> messageWriters() { - return exchangeStrategies.messageWriters(); - } - - @Override - public Optional serverRequest() { - return Optional.empty(); - } - - @Override - public Map hints() { - return Collections.emptyMap(); // TODO: support hints - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/Configurable.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/Configurable.java deleted file mode 100644 index 26a91b2c..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/Configurable.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2013-2020 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.support; - -public interface Configurable { - - Class getConfigClass(); - - C newConfig(); - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ConfigurationService.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ConfigurationService.java deleted file mode 100644 index d1978755..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ConfigurationService.java +++ /dev/null @@ -1,256 +0,0 @@ -/* - * Copyright 2013-2020 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.support; - -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.function.BiFunction; -import java.util.function.Supplier; - -import org.springframework.aop.framework.Advised; -import org.springframework.aop.support.AopUtils; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.boot.context.properties.bind.BindHandler; -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; -import org.springframework.boot.context.properties.bind.validation.ValidationBindHandler; -import org.springframework.boot.context.properties.source.ConfigurationPropertySource; -import org.springframework.boot.context.properties.source.MapConfigurationPropertySource; -import org.springframework.context.ApplicationEvent; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.context.ApplicationEventPublisherAware; -import org.springframework.core.convert.ConversionService; -import org.springframework.expression.spel.standard.SpelExpressionParser; -import org.springframework.util.Assert; -import org.springframework.validation.Validator; - -public class ConfigurationService implements ApplicationEventPublisherAware { - - private ApplicationEventPublisher publisher; - - private BeanFactory beanFactory; - - private Supplier conversionService; - - private SpelExpressionParser parser = new SpelExpressionParser(); - - private Supplier validator; - - public ConfigurationService(BeanFactory beanFactory, ObjectProvider conversionService, - ObjectProvider validator) { - this.beanFactory = beanFactory; - this.conversionService = conversionService::getIfAvailable; - this.validator = validator::getIfAvailable; - } - - public ConfigurationService(BeanFactory beanFactory, Supplier conversionService, - Supplier validator) { - this.beanFactory = beanFactory; - this.conversionService = conversionService; - this.validator = validator; - } - - public ApplicationEventPublisher getPublisher() { - return this.publisher; - } - - @Override - public void setApplicationEventPublisher(ApplicationEventPublisher publisher) { - this.publisher = publisher; - } - - public & ShortcutConfigurable> ConfigurableBuilder with(C configurable) { - return new ConfigurableBuilder(this, configurable); - } - - public InstanceBuilder with(T instance) { - return new InstanceBuilder(this, instance); - } - - /* for testing */ static T bindOrCreate(Bindable bindable, Map properties, - String configurationPropertyName, Validator validator, ConversionService conversionService) { - // see ConfigurationPropertiesBinder from spring boot for this definition. - BindHandler handler = new IgnoreTopLevelConverterNotFoundBindHandler(); - - if (validator != null) { // TODO: list of validators? - handler = new ValidationBindHandler(handler, validator); - } - - List propertySources = Collections - .singletonList(new MapConfigurationPropertySource(properties)); - - return new Binder(propertySources, null, conversionService).bindOrCreate(configurationPropertyName, bindable, - handler); - } - - @SuppressWarnings("unchecked") - /* for testing */ static T getTargetObject(Object candidate) { - try { - if (AopUtils.isAopProxy(candidate) && (candidate instanceof Advised)) { - return (T) ((Advised) candidate).getTargetSource().getTarget(); - } - } - catch (Exception ex) { - throw new IllegalStateException("Failed to unwrap proxied object", ex); - } - return (T) candidate; - } - - public static class ConfigurableBuilder & ShortcutConfigurable> - extends AbstractBuilder> { - - private final C configurable; - - public ConfigurableBuilder(ConfigurationService service, C configurable) { - super(service); - this.configurable = configurable; - } - - @Override - protected ConfigurableBuilder getThis() { - return this; - } - - @Override - protected void validate() { - Assert.notNull(this.configurable, "configurable may not be null"); - } - - @Override - protected Map normalizeProperties() { - if (this.service.beanFactory != null) { - return this.configurable.shortcutType().normalize(this.properties, this.configurable, - this.service.parser, this.service.beanFactory); - } - return super.normalizeProperties(); - } - - @Override - protected T doBind() { - Bindable bindable = Bindable.of(this.configurable.getConfigClass()); - T bound = bindOrCreate(bindable, this.normalizedProperties, this.configurable.shortcutFieldPrefix(), - /* this.name, */this.service.validator.get(), this.service.conversionService.get()); - - return bound; - } - - } - - public static class InstanceBuilder extends AbstractBuilder> { - - private final T instance; - - public InstanceBuilder(ConfigurationService service, T instance) { - super(service); - this.instance = instance; - } - - @Override - protected InstanceBuilder getThis() { - return this; - } - - @Override - protected void validate() { - Assert.notNull(this.instance, "instance may not be null"); - } - - @Override - protected T doBind() { - T toBind = getTargetObject(this.instance); - Bindable bindable = Bindable.ofInstance(toBind); - return bindOrCreate(bindable, this.normalizedProperties, this.name, this.service.validator.get(), - this.service.conversionService.get()); - } - - } - - public static abstract class AbstractBuilder> { - - protected final ConfigurationService service; - - protected BiFunction, ApplicationEvent> eventFunction; - - protected String name; - - protected Map normalizedProperties; - - protected Map properties; - - public AbstractBuilder(ConfigurationService service) { - this.service = service; - } - - protected abstract B getThis(); - - public B name(String name) { - this.name = name; - return getThis(); - } - - public B eventFunction(BiFunction, ApplicationEvent> eventFunction) { - this.eventFunction = eventFunction; - return getThis(); - } - - public B normalizedProperties(Map normalizedProperties) { - this.normalizedProperties = normalizedProperties; - return getThis(); - } - - public B properties(Map properties) { - this.properties = properties; - return getThis(); - } - - protected abstract void validate(); - - protected Map normalizeProperties() { - Map normalizedProperties = new HashMap<>(); - this.properties.forEach(normalizedProperties::put); - return normalizedProperties; - } - - protected abstract T doBind(); - - public T bind() { - validate(); - Assert.hasText(this.name, "name may not be empty"); - Assert.isTrue(this.properties != null || this.normalizedProperties != null, - "properties and normalizedProperties both may not be null"); - - if (this.normalizedProperties == null) { - this.normalizedProperties = normalizeProperties(); - } - - T bound = doBind(); - - if (this.eventFunction != null && this.service.publisher != null) { - ApplicationEvent applicationEvent = this.eventFunction.apply(bound, this.normalizedProperties); - this.service.publisher.publishEvent(applicationEvent); - } - - return bound; - } - - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/DelegatingServiceInstance.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/DelegatingServiceInstance.java deleted file mode 100644 index fd82b80f..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/DelegatingServiceInstance.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2013-2020 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.support; - -import java.net.URI; -import java.util.Map; - -import org.springframework.cloud.client.ServiceInstance; - -/** - * A {@link ServiceInstance} implementation that uses a delegate instance under the hood. - * - * @author Spencer Gibb - */ -public class DelegatingServiceInstance implements ServiceInstance { - - final ServiceInstance delegate; - - private String overrideScheme; - - public DelegatingServiceInstance(ServiceInstance delegate, String overrideScheme) { - this.delegate = delegate; - this.overrideScheme = overrideScheme; - } - - @Override - public String getServiceId() { - return delegate.getServiceId(); - } - - @Override - public String getHost() { - return delegate.getHost(); - } - - @Override - public int getPort() { - return delegate.getPort(); - } - - @Override - public boolean isSecure() { - // TODO: move to map - if ("https".equals(this.overrideScheme) || "wss".equals(this.overrideScheme)) { - return true; - } - return delegate.isSecure(); - } - - @Override - public URI getUri() { - return delegate.getUri(); - } - - @Override - public Map getMetadata() { - return delegate.getMetadata(); - } - - @Override - public String getScheme() { - String scheme = delegate.getScheme(); - if (scheme != null) { - return scheme; - } - return this.overrideScheme; - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/GatewayToStringStyler.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/GatewayToStringStyler.java deleted file mode 100644 index 1d47c454..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/GatewayToStringStyler.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2013-2020 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.support; - -import java.util.function.Function; - -import org.springframework.cloud.gateway.filter.factory.GatewayFilterFactory; -import org.springframework.core.style.DefaultToStringStyler; -import org.springframework.core.style.DefaultValueStyler; -import org.springframework.core.style.ToStringCreator; -import org.springframework.util.ClassUtils; - -@SuppressWarnings("rawtypes") -public class GatewayToStringStyler extends DefaultToStringStyler { - - private static final GatewayToStringStyler FILTER_INSTANCE = new GatewayToStringStyler(GatewayFilterFactory.class, - NameUtils::normalizeFilterFactoryName); - - private final Function classNameFormatter; - - private final Class instanceClass; - - public static ToStringCreator filterToStringCreator(Object obj) { - return new ToStringCreator(obj, FILTER_INSTANCE); - } - - public GatewayToStringStyler(Class instanceClass, Function classNameFormatter) { - super(new DefaultValueStyler()); - this.classNameFormatter = classNameFormatter; - this.instanceClass = instanceClass; - } - - @Override - public void styleStart(StringBuilder buffer, Object obj) { - if (!obj.getClass().isArray()) { - String shortName; - if (instanceClass.isInstance(obj)) { - shortName = classNameFormatter.apply(obj.getClass()); - } - else { - shortName = ClassUtils.getShortName(obj.getClass()); - } - buffer.append('[').append(shortName); - } - else { - buffer.append('['); - styleValue(buffer, obj); - } - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/HasConfig.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/HasConfig.java deleted file mode 100644 index 1cb587d4..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/HasConfig.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2013-2020 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.support; - -public interface HasConfig { - - default Object getConfig() { - return null; - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/HasRouteId.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/HasRouteId.java deleted file mode 100644 index 8ce637df..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/HasRouteId.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2013-2020 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.support; - -public interface HasRouteId { - - void setRouteId(String routeId); - - String getRouteId(); - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/HttpStatusHolder.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/HttpStatusHolder.java deleted file mode 100644 index 1898b133..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/HttpStatusHolder.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright 2013-2020 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.support; - -import org.springframework.core.style.ToStringCreator; -import org.springframework.http.HttpStatus; -import org.springframework.util.Assert; - -public class HttpStatusHolder { - - private final HttpStatus httpStatus; - - private final Integer status; - - public HttpStatusHolder(HttpStatus httpStatus, Integer status) { - Assert.isTrue(httpStatus != null || status != null, "httpStatus and status may not both be null"); - this.httpStatus = httpStatus; - this.status = status; - } - - public static HttpStatusHolder parse(String status) { - final HttpStatus httpStatus = ServerWebExchangeUtils.parse(status); - final Integer intStatus; - if (httpStatus == null) { - intStatus = Integer.parseInt(status); - } - else { - intStatus = null; - } - - return new HttpStatusHolder(httpStatus, intStatus); - } - - public HttpStatus getHttpStatus() { - return httpStatus; - } - - public Integer getStatus() { - return status; - } - - /** - * Whether this status code is in the HTTP series - * {@link org.springframework.http.HttpStatus.Series#INFORMATIONAL}. - * @return true if status code is in the INFORMATIONAL http series - */ - public boolean is1xxInformational() { - return HttpStatus.Series.INFORMATIONAL.equals(getSeries()); - } - - /** - * Whether this status code is in the HTTP series - * {@link org.springframework.http.HttpStatus.Series#SUCCESSFUL}. - * @return true if status code is in the SUCCESSFUL http series - */ - public boolean is2xxSuccessful() { - return HttpStatus.Series.SUCCESSFUL.equals(getSeries()); - } - - /** - * Whether this status code is in the HTTP series - * {@link org.springframework.http.HttpStatus.Series#REDIRECTION}. - * @return true if status code is in the REDIRECTION http series - */ - public boolean is3xxRedirection() { - return HttpStatus.Series.REDIRECTION.equals(getSeries()); - } - - /** - * Whether this status code is in the HTTP series - * {@link org.springframework.http.HttpStatus.Series#CLIENT_ERROR}. - * @return true if status code is in the CLIENT_ERROR http series - */ - public boolean is4xxClientError() { - return HttpStatus.Series.CLIENT_ERROR.equals(getSeries()); - } - - /** - * Whether this status code is in the HTTP series - * {@link org.springframework.http.HttpStatus.Series#SERVER_ERROR}. - * @return true if status code is in the SERVER_ERROR http series - */ - public boolean is5xxServerError() { - return HttpStatus.Series.SERVER_ERROR.equals(getSeries()); - } - - public HttpStatus.Series getSeries() { - if (httpStatus != null) { - return httpStatus.series(); - } - if (status != null) { - return HttpStatus.Series.valueOf(status); - } - return null; - } - - /** - * Whether this status code is in the HTTP series - * {@link org.springframework.http.HttpStatus.Series#CLIENT_ERROR} or - * {@link org.springframework.http.HttpStatus.Series#SERVER_ERROR}. - * @return true if is either CLIENT_ERROR or SERVER_ERROR - */ - public boolean isError() { - return is4xxClientError() || is5xxServerError(); - } - - @Override - public String toString() { - return new ToStringCreator(this).append("httpStatus", httpStatus).append("status", status).toString(); - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/KeyValueConverter.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/KeyValueConverter.java deleted file mode 100644 index 572b4190..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/KeyValueConverter.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2013-2022 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.support; - -import org.springframework.cloud.gateway.filter.factory.AddRequestHeadersIfNotPresentGatewayFilterFactory.KeyValue; -import org.springframework.core.convert.converter.Converter; -import org.springframework.util.StringUtils; - -public class KeyValueConverter implements Converter { - - private static final String INVALID_CONFIGURATION_MESSAGE = "Invalid configuration, expected format is: 'key:value'"; - - @Override - public KeyValue convert(String source) throws IllegalArgumentException { - try { - String[] split = source.split(":"); - if (source.contains(":") && StringUtils.hasText(split[0])) { - return new KeyValue(split[0], split.length == 1 ? "" : split[1]); - } - throw new IllegalArgumentException(INVALID_CONFIGURATION_MESSAGE); - } - catch (ArrayIndexOutOfBoundsException e) { - throw new IllegalArgumentException(INVALID_CONFIGURATION_MESSAGE); - } - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/MvcFoundOnClasspathException.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/MvcFoundOnClasspathException.java deleted file mode 100644 index a0ee6abd..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/MvcFoundOnClasspathException.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright 2013-2021 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.support; - -public class MvcFoundOnClasspathException extends RuntimeException { - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/MvcFoundOnClasspathFailureAnalyzer.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/MvcFoundOnClasspathFailureAnalyzer.java deleted file mode 100644 index c0d5b41f..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/MvcFoundOnClasspathFailureAnalyzer.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2013-2021 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.support; - -import org.springframework.boot.diagnostics.AbstractFailureAnalyzer; -import org.springframework.boot.diagnostics.FailureAnalysis; - -public class MvcFoundOnClasspathFailureAnalyzer extends AbstractFailureAnalyzer { - - /** - * Message for MvcFoundOnClasspathException. - */ - public static final String MESSAGE = "Spring MVC found on classpath, which is incompatible with Spring Cloud Gateway."; - - /** - * Action for MvcFoundOnClasspathException. - */ - public static final String ACTION = "Please set spring.main.web-application-type=reactive or remove spring-boot-starter-web dependency."; - - @Override - protected FailureAnalysis analyze(Throwable rootFailure, MvcFoundOnClasspathException cause) { - return new FailureAnalysis(MESSAGE, ACTION, cause); - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/NameUtils.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/NameUtils.java deleted file mode 100644 index fb7498cb..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/NameUtils.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2013-2020 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.support; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.springframework.cloud.gateway.filter.GlobalFilter; -import org.springframework.cloud.gateway.filter.factory.GatewayFilterFactory; -import org.springframework.cloud.gateway.handler.predicate.RoutePredicateFactory; - -/** - * @author Spencer Gibb - */ -public final class NameUtils { - - private NameUtils() { - throw new AssertionError("Must not instantiate utility class."); - } - - /** - * Generated name prefix. - */ - public static final String GENERATED_NAME_PREFIX = "_genkey_"; - - private static final Pattern NAME_PATTERN = Pattern.compile("([A-Z][a-z0-9]+)"); - - public static String generateName(int i) { - return GENERATED_NAME_PREFIX + i; - } - - public static String normalizeRoutePredicateName(Class clazz) { - return removeGarbage(clazz.getSimpleName().replace(RoutePredicateFactory.class.getSimpleName(), "")); - } - - public static String normalizeRoutePredicateNameAsProperty(Class clazz) { - return normalizeToCanonicalPropertyFormat(normalizeRoutePredicateName(clazz)); - } - - public static String normalizeFilterFactoryName(Class clazz) { - return removeGarbage(clazz.getSimpleName().replace(GatewayFilterFactory.class.getSimpleName(), "")); - } - - public static String normalizeGlobalFilterName(Class clazz) { - return removeGarbage(clazz.getSimpleName().replace(GlobalFilter.class.getSimpleName(), "")).replace("Filter", - ""); - } - - public static String normalizeFilterFactoryNameAsProperty(Class clazz) { - return normalizeToCanonicalPropertyFormat(normalizeFilterFactoryName(clazz)); - } - - public static String normalizeGlobalFilterNameAsProperty(Class filterClass) { - return normalizeToCanonicalPropertyFormat(normalizeGlobalFilterName(filterClass)); - } - - public static String normalizeToCanonicalPropertyFormat(String name) { - Matcher matcher = NAME_PATTERN.matcher(name); - StringBuffer stringBuffer = new StringBuffer(); - while (matcher.find()) { - if (stringBuffer.length() != 0) { - matcher.appendReplacement(stringBuffer, "-" + matcher.group(1)); - } - else { - matcher.appendReplacement(stringBuffer, matcher.group(1)); - } - } - return stringBuffer.toString().toLowerCase(); - } - - private static String removeGarbage(String s) { - int garbageIdx = s.indexOf("$Mockito"); - if (garbageIdx > 0) { - return s.substring(0, garbageIdx); - } - - return s; - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/NotFoundException.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/NotFoundException.java deleted file mode 100644 index 7d1a7ed8..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/NotFoundException.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2013-2020 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.support; - -import org.springframework.http.HttpStatus; -import org.springframework.web.server.ResponseStatusException; - -/** - * @author Spencer Gibb - */ -public class NotFoundException extends ResponseStatusException { - - public NotFoundException(String message) { - this(HttpStatus.SERVICE_UNAVAILABLE, message); - } - - public NotFoundException(String message, Throwable cause) { - this(HttpStatus.SERVICE_UNAVAILABLE, message, cause); - } - - private NotFoundException(HttpStatus httpStatus, String message) { - super(httpStatus, message); - } - - private NotFoundException(HttpStatus httpStatus, String message, Throwable cause) { - super(httpStatus, message, cause); - } - - public static NotFoundException create(boolean with404, String message) { - HttpStatus httpStatus = with404 ? HttpStatus.NOT_FOUND : HttpStatus.SERVICE_UNAVAILABLE; - return new NotFoundException(httpStatus, message); - } - - public static NotFoundException create(boolean with404, String message, Throwable cause) { - HttpStatus httpStatus = with404 ? HttpStatus.NOT_FOUND : HttpStatus.SERVICE_UNAVAILABLE; - return new NotFoundException(httpStatus, message, cause); - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/RouteMetadataUtils.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/RouteMetadataUtils.java deleted file mode 100644 index 0d5fd5ed..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/RouteMetadataUtils.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2013-2020 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.support; - -public final class RouteMetadataUtils { - - /** - * Response timeout attribute name. - */ - public static final String RESPONSE_TIMEOUT_ATTR = "response-timeout"; - - /** - * Connect timeout attribute name. - */ - public static final String CONNECT_TIMEOUT_ATTR = "connect-timeout"; - - private RouteMetadataUtils() { - throw new AssertionError("Must not instantiate utility class."); - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ServerWebExchangeUtils.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ServerWebExchangeUtils.java deleted file mode 100644 index 8ab2b8cd..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ServerWebExchangeUtils.java +++ /dev/null @@ -1,429 +0,0 @@ -/* - * Copyright 2013-2020 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.support; - -import java.net.URI; -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Set; -import java.util.function.Function; -import java.util.function.Predicate; - -import io.netty.buffer.Unpooled; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -import org.springframework.cloud.client.loadbalancer.Response; -import org.springframework.cloud.gateway.filter.factory.GatewayFilterFactory; -import org.springframework.cloud.gateway.handler.AsyncPredicate; -import org.springframework.cloud.gateway.handler.predicate.RoutePredicateFactory; -import org.springframework.core.io.buffer.DataBuffer; -import org.springframework.core.io.buffer.DataBufferFactory; -import org.springframework.core.io.buffer.DataBufferUtils; -import org.springframework.core.io.buffer.DefaultDataBuffer; -import org.springframework.core.io.buffer.NettyDataBuffer; -import org.springframework.http.HttpStatus; -import org.springframework.http.server.reactive.AbstractServerHttpResponse; -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.http.server.reactive.ServerHttpRequestDecorator; -import org.springframework.http.server.reactive.ServerHttpResponse; -import org.springframework.util.Assert; -import org.springframework.web.reactive.DispatcherHandler; -import org.springframework.web.server.ServerWebExchange; -import org.springframework.web.util.UriComponentsBuilder; - -/** - * @author Spencer Gibb - */ -public final class ServerWebExchangeUtils { - - private static final Log log = LogFactory.getLog(ServerWebExchangeUtils.class); - - /** - * Preserve-Host header attribute name. - */ - public static final String PRESERVE_HOST_HEADER_ATTRIBUTE = qualify("preserveHostHeader"); - - /** - * URI template variables attribute name. - */ - public static final String URI_TEMPLATE_VARIABLES_ATTRIBUTE = qualify("uriTemplateVariables"); - - /** - * Client response attribute name. - */ - public static final String CLIENT_RESPONSE_ATTR = qualify("gatewayClientResponse"); - - /** - * Client response connection attribute name. - */ - public static final String CLIENT_RESPONSE_CONN_ATTR = qualify("gatewayClientResponseConnection"); - - /** - * Client response header names attribute name. - */ - public static final String CLIENT_RESPONSE_HEADER_NAMES = qualify("gatewayClientResponseHeaderNames"); - - /** - * Gateway route attribute name. - */ - public static final String GATEWAY_ROUTE_ATTR = qualify("gatewayRoute"); - - /** - * Original Reactor Context corresponding to the processed request. - */ - public static final String GATEWAY_REACTOR_CONTEXT_ATTR = qualify("gatewayReactorContext"); - - /** - * Gateway request URL attribute name. - */ - public static final String GATEWAY_REQUEST_URL_ATTR = qualify("gatewayRequestUrl"); - - /** - * Gateway original request URL attribute name. - */ - public static final String GATEWAY_ORIGINAL_REQUEST_URL_ATTR = qualify("gatewayOriginalRequestUrl"); - - /** - * Gateway handler mapper attribute name. - */ - public static final String GATEWAY_HANDLER_MAPPER_ATTR = qualify("gatewayHandlerMapper"); - - /** - * Gateway scheme prefix attribute name. - */ - public static final String GATEWAY_SCHEME_PREFIX_ATTR = qualify("gatewaySchemePrefix"); - - /** - * Gateway predicate route attribute name. - */ - public static final String GATEWAY_PREDICATE_ROUTE_ATTR = qualify("gatewayPredicateRouteAttr"); - - /** - * Gateway predicate matched path attribute name. - */ - public static final String GATEWAY_PREDICATE_MATCHED_PATH_ATTR = qualify("gatewayPredicateMatchedPathAttr"); - - /** - * Gateway predicate matched path route id attribute name. - */ - public static final String GATEWAY_PREDICATE_MATCHED_PATH_ROUTE_ID_ATTR = qualify( - "gatewayPredicateMatchedPathRouteIdAttr"); - - /** - * Gateway predicate path container attribute name. - */ - public static final String GATEWAY_PREDICATE_PATH_CONTAINER_ATTR = qualify("gatewayPredicatePathContainer"); - - /** - * Weight attribute name. - */ - public static final String WEIGHT_ATTR = qualify("routeWeight"); - - /** - * Original response Content-Type attribute name. - */ - public static final String ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR = "original_response_content_type"; - - /** - * CircuitBreaker execution exception attribute name. - */ - public static final String CIRCUITBREAKER_EXECUTION_EXCEPTION_ATTR = qualify("circuitBreakerExecutionException"); - - /** - * Used when a routing filter has been successfully called. Allows users to write - * custom routing filters that disable built in routing filters. - */ - public static final String GATEWAY_ALREADY_ROUTED_ATTR = qualify("gatewayAlreadyRouted"); - - /** - * Gateway already prefixed attribute name. - */ - public static final String GATEWAY_ALREADY_PREFIXED_ATTR = qualify("gatewayAlreadyPrefixed"); - - /** - * Cached ServerHttpRequestDecorator attribute name. Used when - * {@link #cacheRequestBodyAndRequest(ServerWebExchange, Function)} is called. - */ - public static final String CACHED_SERVER_HTTP_REQUEST_DECORATOR_ATTR = "cachedServerHttpRequestDecorator"; - - /** - * Cached request body key. Used when - * {@link #cacheRequestBodyAndRequest(ServerWebExchange, Function)} or - * {@link #cacheRequestBody(ServerWebExchange, Function)} are called. - */ - public static final String CACHED_REQUEST_BODY_ATTR = "cachedRequestBody"; - - /** - * Gateway LoadBalancer {@link Response} attribute name. - */ - public static final String GATEWAY_LOADBALANCER_RESPONSE_ATTR = qualify("gatewayLoadBalancerResponse"); - - /** - * Gateway Client {@code Observation} attribute name. - */ - public static final String GATEWAY_OBSERVATION_ATTR = qualify("gateway.observation"); - - private static final byte[] EMPTY_BYTES = {}; - - private ServerWebExchangeUtils() { - throw new AssertionError("Must not instantiate utility class."); - } - - private static String qualify(String attr) { - return ServerWebExchangeUtils.class.getName() + "." + attr; - } - - public static void setAlreadyRouted(ServerWebExchange exchange) { - exchange.getAttributes().put(GATEWAY_ALREADY_ROUTED_ATTR, true); - } - - public static void removeAlreadyRouted(ServerWebExchange exchange) { - exchange.getAttributes().remove(GATEWAY_ALREADY_ROUTED_ATTR); - } - - public static boolean isAlreadyRouted(ServerWebExchange exchange) { - return exchange.getAttributeOrDefault(GATEWAY_ALREADY_ROUTED_ATTR, false); - } - - public static boolean setResponseStatus(ServerWebExchange exchange, HttpStatus httpStatus) { - boolean response = exchange.getResponse().setStatusCode(httpStatus); - if (!response && log.isWarnEnabled()) { - log.warn("Unable to set status code to " + httpStatus + ". Response already committed."); - } - return response; - } - - public static void reset(ServerWebExchange exchange) { - // TODO: what else to do to reset exchange? - Set addedHeaders = exchange.getAttributeOrDefault(CLIENT_RESPONSE_HEADER_NAMES, Collections.emptySet()); - addedHeaders.forEach(header -> exchange.getResponse().getHeaders().remove(header)); - removeAlreadyRouted(exchange); - } - - public static boolean setResponseStatus(ServerWebExchange exchange, HttpStatusHolder statusHolder) { - if (exchange.getResponse().isCommitted()) { - return false; - } - if (log.isDebugEnabled()) { - log.debug("Setting response status to " + statusHolder); - } - if (statusHolder.getHttpStatus() != null) { - return setResponseStatus(exchange, statusHolder.getHttpStatus()); - } - if (statusHolder.getStatus() != null && exchange.getResponse() instanceof AbstractServerHttpResponse) { // non-standard - ((AbstractServerHttpResponse) exchange.getResponse()).setRawStatusCode(statusHolder.getStatus()); - return true; - } - return false; - } - - public static boolean containsEncodedParts(URI uri) { - boolean encoded = (uri.getRawQuery() != null && uri.getRawQuery().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).build(true); - return true; - } - catch (IllegalArgumentException ignored) { - if (log.isTraceEnabled()) { - log.trace("Error in containsEncodedParts", ignored); - } - } - - return false; - } - - return encoded; - } - - public static HttpStatus parse(String statusString) { - HttpStatus httpStatus; - - try { - int status = Integer.parseInt(statusString); - httpStatus = HttpStatus.resolve(status); - } - catch (NumberFormatException e) { - // try the enum string - httpStatus = HttpStatus.valueOf(statusString.toUpperCase()); - } - return httpStatus; - } - - public static void addOriginalRequestUrl(ServerWebExchange exchange, URI url) { - exchange.getAttributes().computeIfAbsent(GATEWAY_ORIGINAL_REQUEST_URL_ATTR, s -> new LinkedHashSet<>()); - LinkedHashSet uris = exchange.getRequiredAttribute(GATEWAY_ORIGINAL_REQUEST_URL_ATTR); - uris.add(url); - } - - public static AsyncPredicate toAsyncPredicate(Predicate predicate) { - Assert.notNull(predicate, "predicate must not be null"); - return AsyncPredicate.from(predicate); - } - - public static String expand(ServerWebExchange exchange, String template) { - Assert.notNull(exchange, "exchange may not be null"); - Assert.notNull(template, "template may not be null"); - - if (template.indexOf('{') == -1) { // short circuit - return template; - } - - Map variables = getUriTemplateVariables(exchange); - return UriComponentsBuilder.fromPath(template).build().expand(variables).getPath(); - } - - @SuppressWarnings("unchecked") - public static void putUriTemplateVariables(ServerWebExchange exchange, Map uriVariables) { - if (exchange.getAttributes().containsKey(URI_TEMPLATE_VARIABLES_ATTRIBUTE)) { - Map existingVariables = (Map) exchange.getAttributes() - .get(URI_TEMPLATE_VARIABLES_ATTRIBUTE); - HashMap newVariables = new HashMap<>(); - newVariables.putAll(existingVariables); - newVariables.putAll(uriVariables); - exchange.getAttributes().put(URI_TEMPLATE_VARIABLES_ATTRIBUTE, newVariables); - } - else { - exchange.getAttributes().put(URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriVariables); - } - } - - public static Map getUriTemplateVariables(ServerWebExchange exchange) { - return exchange.getAttributeOrDefault(URI_TEMPLATE_VARIABLES_ATTRIBUTE, new HashMap<>()); - } - - /** - * Caches the request body and the created {@link ServerHttpRequestDecorator} in - * ServerWebExchange attributes. Those attributes are - * {@link #CACHED_REQUEST_BODY_ATTR} and - * {@link #CACHED_SERVER_HTTP_REQUEST_DECORATOR_ATTR} respectively. This method is - * useful when the {@link ServerWebExchange} can not be modified, such as a - * {@link RoutePredicateFactory}. - * @param exchange the available ServerWebExchange. - * @param function a function that accepts the created ServerHttpRequestDecorator. - * @param generic type for the return {@link Mono}. - * @return Mono of type T created by the function parameter. - */ - public static Mono cacheRequestBodyAndRequest(ServerWebExchange exchange, - Function> function) { - return cacheRequestBody(exchange, true, function); - } - - /** - * Caches the request body in a ServerWebExchange attributes. The attribute is - * {@link #CACHED_REQUEST_BODY_ATTR}. This method is useful when the - * {@link ServerWebExchange} can be mutated, such as a {@link GatewayFilterFactory}. - * @param exchange the available ServerWebExchange. - * @param function a function that accepts the created ServerHttpRequestDecorator. - * @param generic type for the return {@link Mono}. - * @return Mono of type T created by the function parameter. - */ - public static Mono cacheRequestBody(ServerWebExchange exchange, - Function> function) { - return cacheRequestBody(exchange, false, function); - } - - /** - * Caches the request body in a ServerWebExchange attribute. The attribute is - * {@link #CACHED_REQUEST_BODY_ATTR}. If this method is called from a location that - * can not mutate the ServerWebExchange (such as a Predicate), setting - * cacheDecoratedRequest to true will put a {@link ServerHttpRequestDecorator} in an - * attribute {@link #CACHED_SERVER_HTTP_REQUEST_DECORATOR_ATTR} for adaptation later. - * @param exchange the available ServerWebExchange. - * @param cacheDecoratedRequest if true, the ServerHttpRequestDecorator will be - * cached. - * @param function a function that accepts a ServerHttpRequest. It can be the created - * ServerHttpRequestDecorator or the original if there is no body. - * @param generic type for the return {@link Mono}. - * @return Mono of type T created by the function parameter. - */ - private static Mono cacheRequestBody(ServerWebExchange exchange, boolean cacheDecoratedRequest, - Function> function) { - ServerHttpResponse response = exchange.getResponse(); - DataBufferFactory factory = response.bufferFactory(); - // Join all the DataBuffers so we have a single DataBuffer for the body - return DataBufferUtils.join(exchange.getRequest().getBody()).defaultIfEmpty(factory.wrap(EMPTY_BYTES)) - .map(dataBuffer -> decorate(exchange, dataBuffer, cacheDecoratedRequest)) - .switchIfEmpty(Mono.just(exchange.getRequest())).flatMap(function); - } - - private static ServerHttpRequest decorate(ServerWebExchange exchange, DataBuffer dataBuffer, - boolean cacheDecoratedRequest) { - if (dataBuffer.readableByteCount() > 0) { - if (log.isTraceEnabled()) { - log.trace("retaining body in exchange attribute"); - } - - Object cachedDataBuffer = exchange.getAttribute(CACHED_REQUEST_BODY_ATTR); - // don't cache if body is already cached - if (!(cachedDataBuffer instanceof DataBuffer)) { - exchange.getAttributes().put(CACHED_REQUEST_BODY_ATTR, dataBuffer); - } - } - - ServerHttpRequest decorator = new ServerHttpRequestDecorator(exchange.getRequest()) { - @Override - public Flux getBody() { - return Mono.fromSupplier(() -> { - if (exchange.getAttribute(CACHED_REQUEST_BODY_ATTR) == null) { - // probably == downstream closed or no body - return null; - } - if (dataBuffer instanceof NettyDataBuffer) { - NettyDataBuffer pdb = (NettyDataBuffer) dataBuffer; - return pdb.factory().wrap(pdb.getNativeBuffer().retainedSlice()); - } - else if (dataBuffer instanceof DefaultDataBuffer) { - DefaultDataBuffer ddf = (DefaultDataBuffer) dataBuffer; - return ddf.factory().wrap(Unpooled.wrappedBuffer(ddf.getNativeBuffer()).nioBuffer()); - } - else { - throw new IllegalArgumentException( - "Unable to handle DataBuffer of type " + dataBuffer.getClass()); - } - }).flux(); - } - }; - if (cacheDecoratedRequest) { - exchange.getAttributes().put(CACHED_SERVER_HTTP_REQUEST_DECORATOR_ATTR, decorator); - } - return decorator; - } - - /** - * One place to handle forwarding using DispatcherHandler. Allows for common code to - * be reused. - * @param handler The DispatcherHandler. - * @param exchange The ServerWebExchange. - * @return value from handler. - */ - public static Mono handle(DispatcherHandler handler, ServerWebExchange exchange) { - // remove attributes that may disrupt the forwarded request - exchange.getAttributes().remove(GATEWAY_PREDICATE_PATH_CONTAINER_ATTR); - - return handler.handle(exchange); - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ServiceUnavailableException.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ServiceUnavailableException.java deleted file mode 100644 index 94f6e9ba..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ServiceUnavailableException.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2013-2020 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.support; - -import org.springframework.web.bind.annotation.ResponseStatus; - -import static org.springframework.http.HttpStatus.SERVICE_UNAVAILABLE; - -@ResponseStatus(value = SERVICE_UNAVAILABLE, reason = "Upstream service is temporarily unavailable") -public class ServiceUnavailableException extends Exception { - - public ServiceUnavailableException() { - } - - public ServiceUnavailableException(String message) { - super(message); - } - - /** - * Disables fillInStackTrace for performance reasons. - * @return this - */ - @Override - public synchronized Throwable fillInStackTrace() { - return this; - } - -} 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 deleted file mode 100644 index 5d8ee9f5..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ShortcutConfigurable.java +++ /dev/null @@ -1,259 +0,0 @@ -/* - * Copyright 2013-2020 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.support; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -import org.springframework.beans.factory.BeanFactory; -import org.springframework.context.expression.BeanFactoryResolver; -import org.springframework.core.env.Environment; -import org.springframework.expression.BeanResolver; -import org.springframework.expression.ConstructorResolver; -import org.springframework.expression.EvaluationContext; -import org.springframework.expression.Expression; -import org.springframework.expression.MethodResolver; -import org.springframework.expression.OperatorOverloader; -import org.springframework.expression.PropertyAccessor; -import org.springframework.expression.TypeComparator; -import org.springframework.expression.TypeConverter; -import org.springframework.expression.TypeLocator; -import org.springframework.expression.TypedValue; -import org.springframework.expression.common.TemplateParserContext; -import org.springframework.expression.spel.standard.SpelExpressionParser; -import org.springframework.expression.spel.support.ReflectivePropertyAccessor; -import org.springframework.expression.spel.support.SimpleEvaluationContext; -import org.springframework.lang.Nullable; -import org.springframework.util.Assert; - -/** - * @author Spencer Gibb - */ -public interface ShortcutConfigurable { - - static String normalizeKey(String key, int entryIdx, ShortcutConfigurable argHints, Map args) { - // RoutePredicateFactory has name hints and this has a fake key name - // replace with the matching key hint - if (key.startsWith(NameUtils.GENERATED_NAME_PREFIX) && !argHints.shortcutFieldOrder().isEmpty() - && entryIdx < args.size() && entryIdx < argHints.shortcutFieldOrder().size()) { - key = argHints.shortcutFieldOrder().get(entryIdx); - } - return key; - } - - static Object getValue(SpelExpressionParser parser, BeanFactory beanFactory, String entryValue) { - Object value; - String rawValue = entryValue; - if (rawValue != null) { - rawValue = rawValue.trim(); - } - if (rawValue != null && rawValue.startsWith("#{") && entryValue.endsWith("}")) { - // assume it's spel - GatewayEvaluationContext context = new GatewayEvaluationContext(beanFactory); - Expression expression = parser.parseExpression(entryValue, new TemplateParserContext()); - value = expression.getValue(context); - } - else { - value = entryValue; - } - return value; - } - - default ShortcutType shortcutType() { - return ShortcutType.DEFAULT; - } - - /** - * Returns hints about the number of args and the order for shortcut parsing. - * @return the list of hints - */ - default List shortcutFieldOrder() { - return Collections.emptyList(); - } - - default String shortcutFieldPrefix() { - return ""; - } - - enum ShortcutType { - - /** - * Default shortcut type. - */ - DEFAULT { - @Override - public Map normalize(Map args, ShortcutConfigurable shortcutConf, - SpelExpressionParser parser, BeanFactory beanFactory) { - Map map = new HashMap<>(); - int entryIdx = 0; - for (Map.Entry entry : args.entrySet()) { - String key = normalizeKey(entry.getKey(), entryIdx, shortcutConf, args); - Object value = getValue(parser, beanFactory, entry.getValue()); - - map.put(key, value); - entryIdx++; - } - return map; - } - }, - - /** - * List shortcut type. - */ - GATHER_LIST { - @Override - public Map normalize(Map args, ShortcutConfigurable shortcutConf, - SpelExpressionParser parser, BeanFactory beanFactory) { - Map map = new HashMap<>(); - // field order should be of size 1 - List fieldOrder = shortcutConf.shortcutFieldOrder(); - Assert.isTrue(fieldOrder != null && fieldOrder.size() == 1, - "Shortcut Configuration Type GATHER_LIST must have shortcutFieldOrder of size 1"); - String fieldName = fieldOrder.get(0); - map.put(fieldName, args.values().stream().map(value -> getValue(parser, beanFactory, value)) - .collect(Collectors.toList())); - return map; - } - }, - - /** - * List is all elements except last which is a boolean flag. - */ - GATHER_LIST_TAIL_FLAG { - @Override - public Map normalize(Map args, ShortcutConfigurable shortcutConf, - SpelExpressionParser parser, BeanFactory beanFactory) { - Map map = new HashMap<>(); - // field order should be of size 1 - List fieldOrder = shortcutConf.shortcutFieldOrder(); - Assert.isTrue(fieldOrder != null && fieldOrder.size() == 2, - "Shortcut Configuration Type GATHER_LIST_HEAD must have shortcutFieldOrder of size 2"); - List values = new ArrayList<>(args.values()); - if (!values.isEmpty()) { - // strip boolean flag if last entry is true or false - int lastIdx = values.size() - 1; - String lastValue = values.get(lastIdx); - if (lastValue.equalsIgnoreCase("true") || lastValue.equalsIgnoreCase("false")) { - values = values.subList(0, lastIdx); - map.put(fieldOrder.get(1), getValue(parser, beanFactory, lastValue)); - } - } - String fieldName = fieldOrder.get(0); - map.put(fieldName, values.stream().map(value -> getValue(parser, beanFactory, value)) - .collect(Collectors.toList())); - return map; - } - }; - - public abstract Map normalize(Map args, ShortcutConfigurable shortcutConf, - SpelExpressionParser parser, BeanFactory beanFactory); - - } - - class GatewayEvaluationContext implements EvaluationContext { - - private final BeanFactoryResolver beanFactoryResolver; - - private final SimpleEvaluationContext delegate; - - 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); - if (restrictive) { - delegate = SimpleEvaluationContext.forPropertyAccessors(new RestrictivePropertyAccessor()) - .withMethodResolvers((context, targetObject, name, argumentTypes) -> null).build(); - } - else { - delegate = SimpleEvaluationContext.forReadOnlyDataBinding().build(); - } - } - - @Override - public TypedValue getRootObject() { - return delegate.getRootObject(); - } - - @Override - public List getPropertyAccessors() { - return delegate.getPropertyAccessors(); - } - - @Override - public List getConstructorResolvers() { - return delegate.getConstructorResolvers(); - } - - @Override - public List getMethodResolvers() { - return delegate.getMethodResolvers(); - } - - @Override - @Nullable - public BeanResolver getBeanResolver() { - return this.beanFactoryResolver; - } - - @Override - public TypeLocator getTypeLocator() { - return delegate.getTypeLocator(); - } - - @Override - public TypeConverter getTypeConverter() { - return delegate.getTypeConverter(); - } - - @Override - public TypeComparator getTypeComparator() { - return delegate.getTypeComparator(); - } - - @Override - public OperatorOverloader getOperatorOverloader() { - return delegate.getOperatorOverloader(); - } - - @Override - public void setVariable(String name, Object value) { - delegate.setVariable(name, value); - } - - @Override - @Nullable - public Object lookupVariable(String name) { - return delegate.lookupVariable(name); - } - - } - - class RestrictivePropertyAccessor extends ReflectivePropertyAccessor { - - @Override - public boolean canRead(EvaluationContext context, Object target, String name) { - return false; - } - - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/StatefulConfigurable.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/StatefulConfigurable.java deleted file mode 100644 index 6e4d42ad..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/StatefulConfigurable.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2013-2020 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.support; - -import java.util.Map; - -public interface StatefulConfigurable extends Configurable { - - Map getConfig(); - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/StringToZonedDateTimeConverter.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/StringToZonedDateTimeConverter.java deleted file mode 100644 index 2327c95b..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/StringToZonedDateTimeConverter.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2013-2020 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.support; - -import java.time.Instant; -import java.time.ZoneOffset; -import java.time.ZonedDateTime; - -import org.springframework.core.convert.converter.Converter; - -public class StringToZonedDateTimeConverter implements Converter { - - @Override - public ZonedDateTime convert(String source) { - ZonedDateTime dateTime; - try { - long epoch = Long.parseLong(source); - - dateTime = Instant.ofEpochMilli(epoch).atOffset(ZoneOffset.ofTotalSeconds(0)).toZonedDateTime(); - } - catch (NumberFormatException e) { - // try ZonedDateTime instead - dateTime = ZonedDateTime.parse(source); - } - - return dateTime; - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/TimeoutException.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/TimeoutException.java deleted file mode 100644 index cd46dde6..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/TimeoutException.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2013-2020 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.support; - -import org.springframework.web.bind.annotation.ResponseStatus; - -import static org.springframework.http.HttpStatus.GATEWAY_TIMEOUT; - -@ResponseStatus(value = GATEWAY_TIMEOUT, reason = "Response took longer than configured timeout") -public class TimeoutException extends Exception { - - public TimeoutException() { - } - - public TimeoutException(String message) { - super(message); - } - - /** - * Disables fillInStackTrace for performance reasons. - * @return this - */ - @Override - public synchronized Throwable fillInStackTrace() { - return this; - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/Visitor.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/Visitor.java deleted file mode 100644 index 7430f95a..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/Visitor.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2013-2021 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.support; - -/** - * Simple visitor interface that allows users to inspect a graph of objects. - * - * @author Spencer Gibb - * @since 3.1.0 - */ -@FunctionalInterface -public interface Visitor { - - void visit(HasConfig hasConfig); - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/WeightConfig.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/WeightConfig.java deleted file mode 100644 index ad8fae9b..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/WeightConfig.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2013-2020 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.support; - -import jakarta.validation.constraints.Min; -import jakarta.validation.constraints.NotEmpty; - -import org.springframework.core.style.ToStringCreator; -import org.springframework.validation.annotation.Validated; - -@Validated -public class WeightConfig { - - /** - * Configuration prefix for {@link WeightConfig}. - */ - public static final String CONFIG_PREFIX = "weight"; - - @NotEmpty - private String group; - - private String routeId; - - @Min(0) - private int weight; - - private WeightConfig() { - } - - public WeightConfig(String group, String routeId, int weight) { - this.routeId = routeId; - this.group = group; - this.weight = weight; - } - - public WeightConfig(String routeId) { - this.routeId = routeId; - } - - public String getGroup() { - return group; - } - - public WeightConfig setGroup(String group) { - this.group = group; - return this; - } - - public String getRouteId() { - return routeId; - } - - public WeightConfig setRouteId(String routeId) { - this.routeId = routeId; - return this; - } - - public int getWeight() { - return weight; - } - - public WeightConfig setWeight(int weight) { - this.weight = weight; - return this; - } - - @Override - public String toString() { - return new ToStringCreator(this).append("group", group).append("routeId", routeId).append("weight", weight) - .toString(); - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ipresolver/RemoteAddressResolver.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ipresolver/RemoteAddressResolver.java deleted file mode 100644 index d41aee42..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ipresolver/RemoteAddressResolver.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2013-2020 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.support.ipresolver; - -import java.net.InetSocketAddress; - -import org.springframework.web.server.ServerWebExchange; - -/** - * @author Andrew Fitzgerald - */ -public interface RemoteAddressResolver { - - default InetSocketAddress resolve(ServerWebExchange exchange) { - return exchange.getRequest().getRemoteAddress(); - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ipresolver/XForwardedRemoteAddressResolver.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ipresolver/XForwardedRemoteAddressResolver.java deleted file mode 100644 index f827f2c8..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ipresolver/XForwardedRemoteAddressResolver.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright 2013-2020 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.support.ipresolver; - -import java.net.InetSocketAddress; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; -import org.springframework.web.server.ServerWebExchange; - -/** - * Parses the client address from the X-Forwarded-For header. If header is not present, - * falls back to {@link RemoteAddressResolver} and - * {@link ServerHttpRequest#getRemoteAddress()}. Use the static constructor methods which - * meets your security requirements. - * - * @author Andrew Fitzgerald - * @see X-Forwarded-For - * reference - */ -public class XForwardedRemoteAddressResolver implements RemoteAddressResolver { - - /** - * Forwarded-For header name. - */ - public static final String X_FORWARDED_FOR = "X-Forwarded-For"; - - private static final Logger log = LoggerFactory.getLogger(XForwardedRemoteAddressResolver.class); - - private final RemoteAddressResolver defaultRemoteIpResolver = new RemoteAddressResolver() { - }; - - private final int maxTrustedIndex; - - private XForwardedRemoteAddressResolver(int maxTrustedIndex) { - this.maxTrustedIndex = maxTrustedIndex; - } - - /** - * @return a {@link XForwardedRemoteAddressResolver} which always extracts the first - * IP address found in the X-Forwarded-For header (when present). Equivalent to - * calling {@link #maxTrustedIndex(int)} with a {@link #maxTrustedIndex} of - * {@link Integer#MAX_VALUE}. This configuration is vulnerable to spoofing via - * manually setting the X-Forwarded-For header. If the resulting IP address is used - * for security purposes, use {@link #maxTrustedIndex(int)} instead. - */ - public static XForwardedRemoteAddressResolver trustAll() { - return new XForwardedRemoteAddressResolver(Integer.MAX_VALUE); - } - - /** - * trusted IP address found in the X-Forwarded-For header (when present). - * This configuration exists to prevent a malicious actor from spoofing the value of - * the X-Forwarded-For header. If you know that your gateway application is only - * accessible from a a trusted load balancer, then you can trust that the load - * balancer will append a valid client IP address to the X-Forwarded-For header, and - * should use a value of `1` for the `maxTrustedIndex`. - * - * - * Given the X-Forwarded-For value of [0.0.0.1, 0.0.0.2, 0.0.0.3]: - * - *
-	 * maxTrustedIndex -> result
-	 *
-	 * [MIN_VALUE,0] -> IllegalArgumentException
-	 * 1 -> 0.0.0.3
-	 * 2 -> 0.0.0.2
-	 * 3 -> 0.0.0.1
-	 * [4, MAX_VALUE] -> 0.0.0.1
-	 * 
- * @param maxTrustedIndex correlates to the number of trusted proxies expected in - * front of Spring Cloud Gateway (index starts at 1). - * @return a {@link XForwardedRemoteAddressResolver} which extracts the last - */ - public static XForwardedRemoteAddressResolver maxTrustedIndex(int maxTrustedIndex) { - Assert.isTrue(maxTrustedIndex > 0, "An index greater than 0 is required"); - return new XForwardedRemoteAddressResolver(maxTrustedIndex); - } - - /** - * The X-Forwarded-For header contains a comma separated list of IP addresses. This - * method parses those IP addresses into a list. If no X-Forwarded-For header is - * found, an empty list is returned. If multiple X-Forwarded-For headers are found, an - * empty list is returned out of caution. - * @return The parsed values of the X-Forwarded-Header. - */ - @Override - public InetSocketAddress resolve(ServerWebExchange exchange) { - List xForwardedValues = extractXForwardedValues(exchange); - if (!xForwardedValues.isEmpty()) { - int index = Math.max(0, xForwardedValues.size() - maxTrustedIndex); - return new InetSocketAddress(xForwardedValues.get(index), 0); - } - return defaultRemoteIpResolver.resolve(exchange); - } - - private List extractXForwardedValues(ServerWebExchange exchange) { - List xForwardedValues = exchange.getRequest().getHeaders().get(X_FORWARDED_FOR); - if (xForwardedValues == null || xForwardedValues.isEmpty()) { - return Collections.emptyList(); - } - if (xForwardedValues.size() > 1) { - log.warn("Multiple X-Forwarded-For headers found, discarding all"); - return Collections.emptyList(); - } - String[] values = StringUtils.tokenizeToStringArray(xForwardedValues.get(0), ","); - if (values.length == 1 && !StringUtils.hasText(values[0])) { - return Collections.emptyList(); - } - return Arrays.asList(values); - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/tagsprovider/GatewayHttpTagsProvider.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/tagsprovider/GatewayHttpTagsProvider.java deleted file mode 100644 index 789e3bd7..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/tagsprovider/GatewayHttpTagsProvider.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2013-2020 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.support.tagsprovider; - -import io.micrometer.core.instrument.Tags; - -import org.springframework.http.HttpStatus; -import org.springframework.http.HttpStatusCode; -import org.springframework.http.server.reactive.AbstractServerHttpResponse; -import org.springframework.web.server.ServerWebExchange; - -/** - * @author Ingyu Hwang - */ -public class GatewayHttpTagsProvider implements GatewayTagsProvider { - - @Override - public Tags apply(ServerWebExchange exchange) { - String outcome = "CUSTOM"; - String status = "CUSTOM"; - String httpStatusCodeStr = "NA"; - - String httpMethod = exchange.getRequest().getMethod().name(); - - // a non standard HTTPS status could be used. Let's be defensive here - // it needs to be checked for first, otherwise the delegate response - // who's status DIDN'T change, will be used - if (exchange.getResponse() instanceof AbstractServerHttpResponse) { - Integer statusInt = ((AbstractServerHttpResponse) exchange.getResponse()).getRawStatusCode(); - if (statusInt != null) { - status = String.valueOf(statusInt); - httpStatusCodeStr = status; - HttpStatus resolved = HttpStatus.resolve(statusInt); - if (resolved != null) { - // this is not a CUSTOM status, so use series here. - outcome = resolved.series().name(); - status = resolved.name(); - } - } - } - else { - HttpStatusCode statusCode = exchange.getResponse().getStatusCode(); - if (statusCode != null) { - httpStatusCodeStr = String.valueOf(statusCode.value()); - if (statusCode instanceof HttpStatus) { - HttpStatus httpStatus = (HttpStatus) statusCode; - outcome = httpStatus.series().name(); - status = httpStatus.name(); - } - } - } - - return Tags.of("outcome", outcome, "status", status, "httpStatusCode", httpStatusCodeStr, "httpMethod", - httpMethod); - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/tagsprovider/GatewayPathTagsProvider.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/tagsprovider/GatewayPathTagsProvider.java deleted file mode 100644 index 68369136..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/tagsprovider/GatewayPathTagsProvider.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2013-2020 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.support.tagsprovider; - -import io.micrometer.core.instrument.Tags; - -import org.springframework.cloud.gateway.route.Route; -import org.springframework.web.server.ServerWebExchange; - -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_PREDICATE_MATCHED_PATH_ATTR; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_PREDICATE_MATCHED_PATH_ROUTE_ID_ATTR; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR; - -/** - * @author Marta Medio - * @author Alberto C. Ríos - */ -public class GatewayPathTagsProvider implements GatewayTagsProvider { - - @Override - public Tags apply(ServerWebExchange exchange) { - Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR); - - if (route != null) { - String matchedPathRouteId = exchange.getAttribute(GATEWAY_PREDICATE_MATCHED_PATH_ROUTE_ID_ATTR); - String matchedPath = exchange.getAttribute(GATEWAY_PREDICATE_MATCHED_PATH_ATTR); - - // check that the matched path belongs to the route that was actually - // selected. - if (route.getId().equals(matchedPathRouteId) && matchedPath != null) { - return Tags.of("path", matchedPath); - } - } - - return Tags.empty(); - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/tagsprovider/GatewayRouteTagsProvider.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/tagsprovider/GatewayRouteTagsProvider.java deleted file mode 100644 index 07296762..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/tagsprovider/GatewayRouteTagsProvider.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2013-2020 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.support.tagsprovider; - -import io.micrometer.core.instrument.Tags; - -import org.springframework.cloud.gateway.route.Route; -import org.springframework.web.server.ServerWebExchange; - -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR; - -/** - * @author Ingyu Hwang - */ -public class GatewayRouteTagsProvider implements GatewayTagsProvider { - - @Override - public Tags apply(ServerWebExchange exchange) { - Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR); - - if (route != null) { - return Tags.of("routeId", route.getId(), "routeUri", route.getUri().toString()); - } - - return Tags.empty(); - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/tagsprovider/GatewayTagsProvider.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/tagsprovider/GatewayTagsProvider.java deleted file mode 100644 index 38b9d346..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/tagsprovider/GatewayTagsProvider.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2013-2020 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.support.tagsprovider; - -import java.util.function.Function; - -import io.micrometer.core.instrument.Tags; - -import org.springframework.util.Assert; -import org.springframework.web.server.ServerWebExchange; - -/** - * @author Ingyu Hwang - */ -public interface GatewayTagsProvider extends Function { - - default GatewayTagsProvider and(GatewayTagsProvider other) { - Assert.notNull(other, "other must not be null"); - - return exchange -> other.apply(exchange).and(apply(exchange)); - } - -} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/tagsprovider/PropertiesTagsProvider.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/tagsprovider/PropertiesTagsProvider.java deleted file mode 100644 index 6cd3eb2a..00000000 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/tagsprovider/PropertiesTagsProvider.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2013-2020 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.support.tagsprovider; - -import java.util.Map; -import java.util.stream.Collectors; - -import io.micrometer.core.instrument.Tag; -import io.micrometer.core.instrument.Tags; - -import org.springframework.web.server.ServerWebExchange; - -/** - * @author Ingyu Hwang - */ -public class PropertiesTagsProvider implements GatewayTagsProvider { - - private final Tags propertiesTags; - - public PropertiesTagsProvider(Map tagsMap) { - this.propertiesTags = Tags.of(tagsMap.entrySet().stream().map(entry -> Tag.of(entry.getKey(), entry.getValue())) - .collect(Collectors.toList())); - } - - @Override - public Tags apply(ServerWebExchange serverWebExchange) { - return propertiesTags; - } - -} diff --git a/spring-cloud-gateway-server/src/main/kotlin/org/springframework/cloud/gateway/route/builder/RouteDsl.kt b/spring-cloud-gateway-server/src/main/kotlin/org/springframework/cloud/gateway/route/builder/RouteDsl.kt deleted file mode 100644 index 7c285ba1..00000000 --- a/spring-cloud-gateway-server/src/main/kotlin/org/springframework/cloud/gateway/route/builder/RouteDsl.kt +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright 2013-2020 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.route.builder - -import org.springframework.cloud.gateway.route.Route -import org.springframework.cloud.gateway.route.RouteLocator -import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder.RouteSpec -import java.util.function.Predicate - -/** - * A Kotlin based DSL to configure a [RouteLocator] - * - * Example: - * ``` - * @Autowired - * lateinit var builder: RouteLocatorBuilder - * - * val routeLocator = builder.routes { - * route(id = "test") { - * host("**.abc.org") and path("/image/png") - * filters { - * add(addResponseHeader("X-TestHeader", "foobar")) - * } - * uri("http://httpbin.org:80") - * } - * } - * ``` - * - * @author Biju Kunjummen - * @author Spencer Gibb - */ -fun RouteLocatorBuilder.routes(routeLocator: RouteLocatorDsl.() -> Unit): RouteLocator { - return RouteLocatorDsl(this).apply(routeLocator).build() -} - - -/** - * Provider for [RouteLocator] DSL functionality - */ -class RouteLocatorDsl(val builder: RouteLocatorBuilder) { - private val routes = builder.routes() - - /** - * DSL to add a route to the [RouteLocator] - * - * @see [Route.Builder] - */ - fun route(id: String? = null, order: Int = 0, uri: String? = null, init: PredicateSpec.() -> Unit) { - val predicateSpec = if (id == null) { - RouteSpec(routes).randomId() - } else { - RouteSpec(routes).id(id) - } - predicateSpec.order(order) - if (uri != null) { - predicateSpec.uri(uri) - } - - predicateSpec.apply(init) - - val route: Route.AsyncBuilder = predicateSpec.routeBuilder - routes.add(route) - } - - fun build(): RouteLocator { - return routes.build() - } - - /** - * A helper to return a composed [Predicate] that tests against this [Predicate] AND the [other] predicate - */ - infix fun BooleanSpec.and(other: BooleanSpec) = - this.routeBuilder.asyncPredicate(this.predicate.and(other.predicate)) - - /** - * A helper to return a composed [Predicate] that tests against this [Predicate] OR the [other] predicate - */ - infix fun BooleanSpec.or(other: BooleanSpec) = - this.routeBuilder.asyncPredicate(this.predicate.or(other.predicate)) - - -} - -/** - * Extension method to add filters {} block to dsl - */ -fun PredicateSpec.filters(init: GatewayFilterSpec.() -> Unit) { - val spec = createGatewayFilterSpec() - spec.apply(init) -} - - 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 deleted file mode 100644 index 90105d6b..00000000 --- a/spring-cloud-gateway-server/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ /dev/null @@ -1,417 +0,0 @@ -{ - "properties": [ - { - "name": "spring.cloud.gateway.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", - "type": "java.lang.Boolean", - "description": "Enables the map-request-header filter.", - "defaultValue": "true" - }, - { - "name": "spring.cloud.gateway.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", - "type": "java.lang.Boolean", - "description": "Enables the add-response-header filter.", - "defaultValue": "true" - }, - { - "name": "spring.cloud.gateway.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", - "type": "java.lang.Boolean", - "description": "Enables the modify-request-body filter.", - "defaultValue": "true" - }, - { - "name": "spring.cloud.gateway.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", - "type": "org.springframework.util.unit.DataSize", - "description": "Maximum size of the cache to evict entries for this route (in KB, MB and GB).", - "defaultValue": "5m" - }, - { - "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)." - }, - { - "name": "spring.cloud.gateway.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", - "type": "java.lang.Boolean", - "description": "Enables the modify-response-body filter.", - "defaultValue": "true" - }, - { - "name": "spring.cloud.gateway.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", - "type": "java.lang.Boolean", - "description": "Enables the preserve-host-header filter.", - "defaultValue": "true" - }, - { - "name": "spring.cloud.gateway.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", - "type": "java.lang.Boolean", - "description": "Enables the remove-request-header filter.", - "defaultValue": "true" - }, - { - "name": "spring.cloud.gateway.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", - "type": "java.lang.Boolean", - "description": "Enables the remove-response-header filter.", - "defaultValue": "true" - }, - { - "name": "spring.cloud.gateway.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", - "type": "java.lang.Boolean", - "description": "Enables the rewrite-path filter.", - "defaultValue": "true" - }, - { - "name": "spring.cloud.gateway.filter.retry.enabled", - "type": "java.lang.Boolean", - "description": "Enables the retry filter.", - "defaultValue": "true" - }, - { - "name": "spring.cloud.gateway.filter.set-path.enabled", - "type": "java.lang.Boolean", - "description": "Enables the set-path filter.", - "defaultValue": "true" - }, - { - "name": "spring.cloud.gateway.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", - "type": "java.lang.Boolean", - "description": "Enables the set-request-header filter.", - "defaultValue": "true" - }, - { - "name": "spring.cloud.gateway.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", - "type": "java.lang.Boolean", - "description": "Enables the set-response-header filter.", - "defaultValue": "true" - }, - { - "name": "spring.cloud.gateway.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", - "type": "java.lang.Boolean", - "description": "Enables the rewrite-location-response-header filter.", - "defaultValue": "true" - }, - { - "name": "spring.cloud.gateway.filter.rewrite-location.enabled", - "type": "java.lang.Boolean", - "description": "Enables the rewrite-location filter.", - "defaultValue": "true" - }, - { - "name": "spring.cloud.gateway.filter.set-status.enabled", - "type": "java.lang.Boolean", - "description": "Enables the set-status filter.", - "defaultValue": "true" - }, - { - "name": "spring.cloud.gateway.filter.save-session.enabled", - "type": "java.lang.Boolean", - "description": "Enables the save-session filter.", - "defaultValue": "true" - }, - { - "name": "spring.cloud.gateway.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", - "type": "java.lang.Boolean", - "description": "Enables the request-header-to-request-uri filter.", - "defaultValue": "true" - }, - { - "name": "spring.cloud.gateway.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", - "type": "java.lang.Boolean", - "description": "Enables the request-header-size filter.", - "defaultValue": "true" - }, - { - "name": "spring.cloud.gateway.filter.circuit-breaker.enabled", - "type": "java.lang.Boolean", - "description": "Enables the circuit-breaker filter.", - "defaultValue": "true" - }, - { - "name": "spring.cloud.gateway.filter.hystrix.enabled", - "type": "java.lang.Boolean", - "description": "Enables the hystrix filter.", - "defaultValue": "true" - }, - { - "name": "spring.cloud.gateway.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", - "type": "java.lang.Boolean", - "description": "Enables the adapt-cached-body global filter.", - "defaultValue": "true" - }, - { - "name": "spring.cloud.gateway.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", - "type": "java.lang.Boolean", - "description": "Enables the route-to-request-url global filter.", - "defaultValue": "true" - }, - { - "name": "spring.cloud.gateway.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", - "type": "java.lang.Boolean", - "description": "Enables the forward-path global filter.", - "defaultValue": "true" - }, - { - "name": "spring.cloud.gateway.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", - "type": "java.lang.Boolean", - "description": "Enables the netty-write-response global filter.", - "defaultValue": "true" - }, - { - "name": "spring.cloud.gateway.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", - "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", - "type": "java.lang.Boolean", - "description": "Enables the load-balancer-client global filter.", - "defaultValue": "true" - }, - { - "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" - }, - { - "name": "spring.cloud.gateway.predicate.after.enabled", - "type": "java.lang.Boolean", - "description": "Enables the after predicate.", - "defaultValue": "true" - }, - { - "name": "spring.cloud.gateway.predicate.before.enabled", - "type": "java.lang.Boolean", - "description": "Enables the before predicate.", - "defaultValue": "true" - }, - { - "name": "spring.cloud.gateway.predicate.between.enabled", - "type": "java.lang.Boolean", - "description": "Enables the between predicate.", - "defaultValue": "true" - }, - { - "name": "spring.cloud.gateway.predicate.cookie.enabled", - "type": "java.lang.Boolean", - "description": "Enables the cookie predicate.", - "defaultValue": "true" - }, - { - "name": "spring.cloud.gateway.predicate.header.enabled", - "type": "java.lang.Boolean", - "description": "Enables the header predicate.", - "defaultValue": "true" - }, - { - "name": "spring.cloud.gateway.predicate.host.enabled", - "type": "java.lang.Boolean", - "description": "Enables the host predicate.", - "defaultValue": "true" - }, - { - "name": "spring.cloud.gateway.predicate.method.enabled", - "type": "java.lang.Boolean", - "description": "Enables the method predicate.", - "defaultValue": "true" - }, - { - "name": "spring.cloud.gateway.predicate.path.enabled", - "type": "java.lang.Boolean", - "description": "Enables the path predicate.", - "defaultValue": "true" - }, - { - "name": "spring.cloud.gateway.predicate.query.enabled", - "type": "java.lang.Boolean", - "description": "Enables the query predicate.", - "defaultValue": "true" - }, - { - "name": "spring.cloud.gateway.predicate.read-body.enabled", - "type": "java.lang.Boolean", - "description": "Enables the read-body predicate.", - "defaultValue": "true" - }, - { - "name": "spring.cloud.gateway.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", - "type": "java.lang.Boolean", - "description": "Enables the xforwarded-remote-addr predicate.", - "defaultValue": "true" - }, - { - "name": "spring.cloud.gateway.predicate.weight.enabled", - "type": "java.lang.Boolean", - "description": "Enables the weight predicate.", - "defaultValue": "true" - }, - { - "name": "spring.cloud.gateway.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", - "type": "java.lang.Boolean", - "description": "Enables gateway functionality.", - "defaultValue": "true" - }, - { - "name": "spring.cloud.gateway.forwarded.enabled", - "type": "java.lang.Boolean", - "description": "Enables the ForwardedHeadersFilter.", - "defaultValue": "true" - }, - { - "name": "spring.cloud.gateway.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", - "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", - "type": "java.lang.Integer", - "description": "The order of RoutePredicateHandlerMapping.", - "defaultValue": "1" - }, - { - "name": "spring.cloud.gateway.restrictive-property-accessor.enabled", - "type": "java.lang.Boolean", - "description": "Restricts method and property access in SpEL.", - "defaultValue": "true" - }, - { - "name": "spring.cloud.gateway.observability.enabled", - "type": "java.lang.Boolean", - "description": "If Micrometer Observability support should be turned on.", - "defaultValue": "true" - } - ] -} diff --git a/spring-cloud-gateway-server/src/main/resources/META-INF/scripts/request_rate_limiter.lua b/spring-cloud-gateway-server/src/main/resources/META-INF/scripts/request_rate_limiter.lua deleted file mode 100644 index 7959d95e..00000000 --- a/spring-cloud-gateway-server/src/main/resources/META-INF/scripts/request_rate_limiter.lua +++ /dev/null @@ -1,60 +0,0 @@ -redis.replicate_commands() - -local tokens_key = KEYS[1] -local timestamp_key = KEYS[2] ---redis.log(redis.LOG_WARNING, "tokens_key " .. tokens_key) - -local rate = tonumber(ARGV[1]) -local capacity = tonumber(ARGV[2]) -local now = tonumber(ARGV[3]) -local requested = tonumber(ARGV[4]) - -local fill_time = capacity / rate -local ttl = math.floor(fill_time * 2) - --- for testing, it should use redis system time in production -if now == nil then - now = redis.call('TIME')[1] -end - ---redis.log(redis.LOG_WARNING, "rate " .. ARGV[1]) ---redis.log(redis.LOG_WARNING, "capacity " .. ARGV[2]) ---redis.log(redis.LOG_WARNING, "now " .. now) ---redis.log(redis.LOG_WARNING, "requested " .. ARGV[4]) ---redis.log(redis.LOG_WARNING, "filltime " .. fill_time) ---redis.log(redis.LOG_WARNING, "ttl " .. ttl) - -local last_tokens = tonumber(redis.call("get", tokens_key)) -if last_tokens == nil then - last_tokens = capacity -end ---redis.log(redis.LOG_WARNING, "last_tokens " .. last_tokens) - -local last_refreshed = tonumber(redis.call("get", timestamp_key)) -if last_refreshed == nil then - last_refreshed = 0 -end ---redis.log(redis.LOG_WARNING, "last_refreshed " .. last_refreshed) - -local delta = math.max(0, now-last_refreshed) -local filled_tokens = math.min(capacity, last_tokens+(delta*rate)) -local allowed = filled_tokens >= requested -local new_tokens = filled_tokens -local allowed_num = 0 -if allowed then - new_tokens = filled_tokens - requested - allowed_num = 1 -end - ---redis.log(redis.LOG_WARNING, "delta " .. delta) ---redis.log(redis.LOG_WARNING, "filled_tokens " .. filled_tokens) ---redis.log(redis.LOG_WARNING, "allowed_num " .. allowed_num) ---redis.log(redis.LOG_WARNING, "new_tokens " .. new_tokens) - -if ttl > 0 then - redis.call("setex", tokens_key, ttl, new_tokens) - redis.call("setex", timestamp_key, ttl, now) -end - --- return { allowed_num, new_tokens, capacity, filled_tokens, requested, new_tokens } -return { allowed_num, new_tokens } diff --git a/spring-cloud-gateway-server/src/main/resources/META-INF/services/jakarta.validation.ConstraintValidator b/spring-cloud-gateway-server/src/main/resources/META-INF/services/jakarta.validation.ConstraintValidator deleted file mode 100644 index e1c9f58c..00000000 --- a/spring-cloud-gateway-server/src/main/resources/META-INF/services/jakarta.validation.ConstraintValidator +++ /dev/null @@ -1,17 +0,0 @@ -# -# Copyright 2013-2020 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. -# - -org.springframework.cloud.gateway.config.MaxDataSizeValidator \ No newline at end of file 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 deleted file mode 100644 index dd7e5a51..00000000 --- a/spring-cloud-gateway-server/src/main/resources/META-INF/spring.factories +++ /dev/null @@ -1,6 +0,0 @@ -org.springframework.boot.env.EnvironmentPostProcessor=\ -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 diff --git a/spring-cloud-gateway-server/src/main/resources/META-INF/spring/aot.factories b/spring-cloud-gateway-server/src/main/resources/META-INF/spring/aot.factories deleted file mode 100644 index 93651d0f..00000000 --- a/spring-cloud-gateway-server/src/main/resources/META-INF/spring/aot.factories +++ /dev/null @@ -1,2 +0,0 @@ -org.springframework.aot.hint.RuntimeHintsRegistrar=\ -org.springframework.cloud.gateway.config.GatewayHints \ No newline at end of file diff --git a/spring-cloud-gateway-server/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-cloud-gateway-server/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports deleted file mode 100644 index 2c486c7c..00000000 --- a/spring-cloud-gateway-server/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ /dev/null @@ -1,11 +0,0 @@ -org.springframework.cloud.gateway.config.GatewayClassPathWarningAutoConfiguration -org.springframework.cloud.gateway.config.GatewayAutoConfiguration -org.springframework.cloud.gateway.config.GatewayResilience4JCircuitBreakerAutoConfiguration -org.springframework.cloud.gateway.config.GatewayNoLoadBalancerClientAutoConfiguration -org.springframework.cloud.gateway.config.GatewayMetricsAutoConfiguration -org.springframework.cloud.gateway.config.GatewayRedisAutoConfiguration -org.springframework.cloud.gateway.discovery.GatewayDiscoveryClientAutoConfiguration -org.springframework.cloud.gateway.config.SimpleUrlHandlerMappingGlobalCorsAutoConfiguration -org.springframework.cloud.gateway.config.GatewayReactiveLoadBalancerClientAutoConfiguration -org.springframework.cloud.gateway.config.GatewayReactiveOAuth2AutoConfiguration -org.springframework.cloud.gateway.config.LocalResponseCacheAutoConfiguration \ No newline at end of file diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/actuate/GatewayControllerEndpointTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/actuate/GatewayControllerEndpointTests.java deleted file mode 100644 index 17337172..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/actuate/GatewayControllerEndpointTests.java +++ /dev/null @@ -1,520 +0,0 @@ -/* - * Copyright 2013-2020 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.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import java.util.function.Predicate; - -import org.assertj.core.util.Maps; -import org.junit.jupiter.api.Assertions; -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.web.server.LocalServerPort; -import org.springframework.cloud.gateway.filter.FilterDefinition; -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; -import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory; -import org.springframework.cloud.gateway.handler.predicate.GatewayPredicate; -import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition; -import org.springframework.cloud.gateway.route.RouteDefinition; -import org.springframework.cloud.gateway.route.RouteLocator; -import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; -import org.springframework.cloud.gateway.test.PermitAllSecurityConfiguration; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; -import org.springframework.http.HttpStatus; -import org.springframework.http.HttpStatusCode; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.test.web.reactive.server.WebTestClient; -import org.springframework.web.reactive.function.BodyInserters; -import org.springframework.web.server.ServerWebExchange; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; - -@SpringBootTest(properties = { "management.endpoint.gateway.enabled=true", - "management.endpoints.web.exposure.include=*", "spring.cloud.gateway.actuator.verbose.enabled=true" }, - webEnvironment = RANDOM_PORT) -public class GatewayControllerEndpointTests { - - @Autowired - WebTestClient testClient; - - @LocalServerPort - int port; - - @Test - public void testRefresh() { - testClient.post().uri("http://localhost:" + port + "/actuator/gateway/refresh").exchange().expectStatus() - .isOk(); - } - - @Test - public void testRoutes() { - testClient.get().uri("http://localhost:" + port + "/actuator/gateway/routes").exchange().expectStatus().isOk() - .expectBodyList(Map.class).consumeWith(result -> { - List responseBody = result.getResponseBody(); - assertThat(responseBody).isNotEmpty(); - }); - } - - @Test - public void testGetSpecificRoute() { - testClient.get().uri("http://localhost:" + port + "/actuator/gateway/routes/test-service").exchange() - .expectStatus().isOk().expectBodyList(Map.class).consumeWith(result -> { - List responseBody = result.getResponseBody(); - assertThat(responseBody).isNotNull(); - assertThat(responseBody.size()).isEqualTo(1); - assertThat(responseBody).isNotEmpty(); - }); - } - - @Test - public void testRouteReturnsMetadata() { - testClient.get().uri("http://localhost:" + port + "/actuator/gateway/routes/route_with_metadata").exchange() - .expectStatus().isOk().expectBody().jsonPath("$.metadata") - .value(map -> assertThat((Map) map).hasSize(3) - .containsEntry("optionName", "OptionValue").containsEntry("iAmNumber", 1) - .containsEntry("compositeObject", Maps.newHashMap("name", "value"))); - } - - @Test - public void testRouteFilters() { - testClient.get().uri("http://localhost:" + port + "/actuator/gateway/routefilters").exchange().expectStatus() - .isOk().expectBody(Map.class).consumeWith(result -> { - Map responseBody = result.getResponseBody(); - assertThat(responseBody).isNotEmpty(); - }); - } - - @Test - public void testRoutePredicates() { - testClient.get().uri("http://localhost:" + port + "/actuator/gateway/routepredicates").exchange().expectStatus() - .isOk().expectBody(Map.class).consumeWith(result -> { - Map responseBody = result.getResponseBody(); - assertThat(responseBody).isNotEmpty(); - }); - } - - @Test - public void testRouteDelete() { - RouteDefinition testRouteDefinition = new RouteDefinition(); - testRouteDefinition.setUri(URI.create("http://example.org")); - - PredicateDefinition methodRoutePredicateDefinition = new PredicateDefinition("Method=GET"); - - testRouteDefinition.setPredicates(Arrays.asList(methodRoutePredicateDefinition)); - - testClient.post().uri("http://localhost:" + port + "/actuator/gateway/routes/test-route-to-be-delete") - .accept(MediaType.APPLICATION_JSON).body(BodyInserters.fromValue(testRouteDefinition)).exchange() - .expectStatus().isCreated(); - - testClient.delete().uri("http://localhost:" + port + "/actuator/gateway/routes/test-route-to-be-delete") - .exchange().expectStatus().isOk().expectBody(ResponseEntity.class).consumeWith(result -> { - HttpStatusCode httpStatus = result.getStatus(); - Assertions.assertEquals(HttpStatus.OK, httpStatus); - }); - } - - @Test - public void testPostValidRouteDefinition() { - RouteDefinition testRouteDefinition = new RouteDefinition(); - testRouteDefinition.setUri(URI.create("http://example.org")); - - FilterDefinition prefixPathFilterDefinition = new FilterDefinition("PrefixPath=/test-path"); - FilterDefinition redirectToFilterDefinition = new FilterDefinition("RemoveResponseHeader=Sensitive-Header"); - FilterDefinition testFilterDefinition = new FilterDefinition("TestFilter"); - testRouteDefinition.setFilters( - Arrays.asList(prefixPathFilterDefinition, redirectToFilterDefinition, testFilterDefinition)); - - PredicateDefinition hostRoutePredicateDefinition = new PredicateDefinition("Host=myhost.org"); - PredicateDefinition methodRoutePredicateDefinition = new PredicateDefinition("Method=GET"); - PredicateDefinition testPredicateDefinition = new PredicateDefinition("Test=value"); - testRouteDefinition.setPredicates( - Arrays.asList(hostRoutePredicateDefinition, methodRoutePredicateDefinition, testPredicateDefinition)); - - testClient.post().uri("http://localhost:" + port + "/actuator/gateway/routes/test-route") - .accept(MediaType.APPLICATION_JSON).body(BodyInserters.fromValue(testRouteDefinition)).exchange() - .expectStatus().isCreated(); - } - - @Test - public void testRefreshByGroup() { - RouteDefinition testRouteDefinition = new RouteDefinition(); - testRouteDefinition.setUri(URI.create("http://example.org")); - String group1 = "group-1_" + UUID.randomUUID(); - testRouteDefinition.setMetadata(Map.of("groupBy", group1)); - - String routeId1 = "route-1_" + UUID.randomUUID(); - testClient.post().uri("http://localhost:" + port + "/actuator/gateway/routes/" + routeId1) - .accept(MediaType.APPLICATION_JSON).body(BodyInserters.fromValue(testRouteDefinition)).exchange() - .expectStatus().isCreated(); - - RouteDefinition testRouteDefinition2 = new RouteDefinition(); - testRouteDefinition2.setUri(URI.create("http://example.org")); - String group2 = "group-2_" + UUID.randomUUID(); - testRouteDefinition2.setMetadata(Map.of("groupBy", group2)); - String routeId2 = "route-2_" + UUID.randomUUID(); - testClient.post().uri("http://localhost:" + port + "/actuator/gateway/routes/" + routeId2) - .accept(MediaType.APPLICATION_JSON).body(BodyInserters.fromValue(testRouteDefinition2)).exchange() - .expectStatus().isCreated(); - - testClient.post().uri("http://localhost:" + port + "/actuator/gateway/refresh?metadata=groupBy:" + group1) - .exchange().expectStatus().isOk(); - - testClient.get().uri("http://localhost:" + port + "/actuator/gateway/routes").exchange().expectStatus().isOk() - .expectBodyList(Map.class).consumeWith(result -> { - List responseBody = result.getResponseBody(); - assertThat(responseBody).extracting("route_id").contains(routeId1).doesNotContain(routeId2); - }); - } - - @Test - public void testRefreshByGroup_whenRouteDefinitionsAreDeleted() { - RouteDefinition testRouteDefinition = new RouteDefinition(); - testRouteDefinition.setUri(URI.create("http://example.org")); - String group1 = "group-1_" + UUID.randomUUID(); - testRouteDefinition.setMetadata(Map.of("groupBy", group1)); - - String routeId1 = "route-1_" + UUID.randomUUID(); - testClient.post().uri("http://localhost:" + port + "/actuator/gateway/routes/" + routeId1) - .accept(MediaType.APPLICATION_JSON).body(BodyInserters.fromValue(testRouteDefinition)).exchange() - .expectStatus().isCreated(); - - testClient.post().uri("http://localhost:" + port + "/actuator/gateway/refresh?metadata=groupBy:" + group1) - .exchange().expectStatus().isOk(); - - testClient.delete().uri("http://localhost:" + port + "/actuator/gateway/routes/" + routeId1).exchange() - .expectStatus().isOk(); - - testClient.post().uri("http://localhost:" + port + "/actuator/gateway/refresh?metadata=groupBy:" + group1) - .exchange().expectStatus().isOk(); - - testClient.get().uri("http://localhost:" + port + "/actuator/gateway/routes").exchange().expectStatus().isOk() - .expectBodyList(Map.class).consumeWith(result -> { - List responseBody = result.getResponseBody(); - assertThat(responseBody).extracting("route_id").doesNotContain(routeId1); - }); - } - - @Test - public void testRefreshByGroupWithOneWrongFilterInSameGroup() { - RouteDefinition testRouteDefinition = new RouteDefinition(); - testRouteDefinition.setUri(URI.create("http://wrong.route")); - String group1 = "group-1_" + UUID.randomUUID(); - testRouteDefinition.setMetadata(Map.of("groupBy", group1)); - testRouteDefinition.setFilters(List.of(new FilterDefinition("StripPrefix=wrong"))); - - String routeId1 = UUID.randomUUID().toString(); - testClient.post().uri("http://localhost:" + port + "/actuator/gateway/routes/" + routeId1) - .accept(MediaType.APPLICATION_JSON).body(BodyInserters.fromValue(testRouteDefinition)).exchange() - .expectStatus().isCreated(); - - RouteDefinition testRouteDefinition2 = new RouteDefinition(); - testRouteDefinition2.setUri(URI.create("http://valid.route")); - testRouteDefinition2.setMetadata(Map.of("groupBy", group1)); - String routeId2 = UUID.randomUUID().toString(); - testClient.post().uri("http://localhost:" + port + "/actuator/gateway/routes/" + routeId2) - .accept(MediaType.APPLICATION_JSON).body(BodyInserters.fromValue(testRouteDefinition2)).exchange() - .expectStatus().isCreated(); - - testClient.post().uri("http://localhost:" + port + "/actuator/gateway/refresh?metadata=groupBy:" + group1) - .exchange().expectStatus().isOk(); - - testClient.get().uri("http://localhost:" + port + "/actuator/gateway/routes").exchange().expectStatus().isOk() - .expectBodyList(Map.class).consumeWith(result -> { - List responseBody = result.getResponseBody(); - assertThat(responseBody).extracting("route_id").doesNotContain(routeId1, routeId2); - }); - } - - @Test - public void testRefreshByGroupDoesntImpactOthers() { - RouteDefinition testRouteDefinition = new RouteDefinition(); - String routeId1 = UUID.randomUUID().toString(); - testRouteDefinition.setId(routeId1); - testRouteDefinition.setUri(URI.create("http://wrong-group-1.route")); - String group1 = "group-1_" + UUID.randomUUID(); - testRouteDefinition.setMetadata(Map.of("groupBy", group1)); - testRouteDefinition.setFilters(List.of(new FilterDefinition("StripPrefix=wrong"))); - RouteDefinition testRouteDefinition2 = new RouteDefinition(); - String routeId2 = UUID.randomUUID().toString(); - testRouteDefinition2.setId(routeId2); - testRouteDefinition2.setUri(URI.create("http://valid-group-1.route")); - testRouteDefinition2.setMetadata(Map.of("groupBy", group1)); - testClient.post().uri("http://localhost:" + port + "/actuator/gateway/routes/" + routeId1) - .accept(MediaType.APPLICATION_JSON).body(BodyInserters.fromValue(testRouteDefinition)).exchange() - .expectStatus().isCreated(); - testClient.post().uri("http://localhost:" + port + "/actuator/gateway/routes/" + routeId2) - .accept(MediaType.APPLICATION_JSON).body(BodyInserters.fromValue(testRouteDefinition2)).exchange() - .expectStatus().isCreated(); - - RouteDefinition testRouteDefinition3 = new RouteDefinition(); - String routeId3 = UUID.randomUUID().toString(); - testRouteDefinition3.setId(routeId3); - testRouteDefinition3.setUri(URI.create("http://valid-group-2.route")); - String group2 = "group-2_" + UUID.randomUUID(); - testRouteDefinition3.setMetadata(Map.of("groupBy", group2)); - RouteDefinition testRouteDefinition4 = new RouteDefinition(); - String routeId4 = UUID.randomUUID().toString(); - testRouteDefinition4.setId(routeId4); - testRouteDefinition4.setUri(URI.create("http://valid-group-2.route")); - testRouteDefinition4.setMetadata(Map.of("groupBy", group2)); - testClient.post().uri("http://localhost:" + port + "/actuator/gateway/routes/" + routeId3) - .accept(MediaType.APPLICATION_JSON).body(BodyInserters.fromValue(testRouteDefinition3)).exchange() - .expectStatus().isCreated(); - testClient.post().uri("http://localhost:" + port + "/actuator/gateway/routes/" + routeId4) - .accept(MediaType.APPLICATION_JSON).body(BodyInserters.fromValue(testRouteDefinition4)).exchange() - .expectStatus().isCreated(); - - // When - testClient.post().uri("http://localhost:" + port + "/actuator/gateway/refresh?metadata=groupBy:" + group1) - .exchange().expectStatus().isOk(); - testClient.post().uri("http://localhost:" + port + "/actuator/gateway/refresh?metadata=groupBy:" + group2) - .exchange().expectStatus().isOk(); - - // Then - testClient.get().uri("http://localhost:" + port + "/actuator/gateway/routes").exchange().expectStatus().isOk() - .expectBodyList(Map.class).consumeWith(result -> { - List responseBody = result.getResponseBody(); - assertThat(responseBody).extracting("route_id").doesNotContain(routeId1, routeId2) - .contains(routeId3, routeId4); - - }); - } - - public void testPostMultipleValidRouteDefinitions() { - RouteDefinition testRouteDefinition = new RouteDefinition(); - testRouteDefinition.setUri(URI.create("http://example.org")); - String routeId1 = UUID.randomUUID().toString(); - testRouteDefinition.setId(routeId1); - - FilterDefinition prefixPathFilterDefinition = new FilterDefinition("PrefixPath=/test-path"); - FilterDefinition redirectToFilterDefinition = new FilterDefinition("RemoveResponseHeader=Sensitive-Header"); - FilterDefinition testFilterDefinition = new FilterDefinition("TestFilter"); - testRouteDefinition.setFilters( - Arrays.asList(prefixPathFilterDefinition, redirectToFilterDefinition, testFilterDefinition)); - - PredicateDefinition hostRoutePredicateDefinition = new PredicateDefinition("Host=myhost.org"); - PredicateDefinition methodRoutePredicateDefinition = new PredicateDefinition("Method=GET"); - PredicateDefinition testPredicateDefinition = new PredicateDefinition("Test=value"); - testRouteDefinition.setPredicates( - Arrays.asList(hostRoutePredicateDefinition, methodRoutePredicateDefinition, testPredicateDefinition)); - - RouteDefinition testRouteDefinition2 = new RouteDefinition(); - testRouteDefinition2.setUri(URI.create("http://example-2.org")); - String routeId2 = UUID.randomUUID().toString(); - testRouteDefinition2.setId(routeId2); - - FilterDefinition prefixPathFilterDefinition2 = new FilterDefinition("PrefixPath=/test-path-2"); - FilterDefinition redirectToFilterDefinition2 = new FilterDefinition("RemoveResponseHeader=Sensitive-Header-2"); - FilterDefinition testFilterDefinition2 = new FilterDefinition("TestFilter"); - testRouteDefinition2.setFilters( - Arrays.asList(prefixPathFilterDefinition2, redirectToFilterDefinition2, testFilterDefinition2)); - - PredicateDefinition hostRoutePredicateDefinition2 = new PredicateDefinition("Host=myhost-2.org"); - PredicateDefinition methodRoutePredicateDefinition2 = new PredicateDefinition("Method=GET"); - PredicateDefinition testPredicateDefinition2 = new PredicateDefinition("Test=value-2"); - testRouteDefinition2.setPredicates(Arrays.asList(hostRoutePredicateDefinition2, methodRoutePredicateDefinition2, - testPredicateDefinition2)); - - List multipleRouteDefs = List.of(testRouteDefinition, testRouteDefinition2); - - testClient.post().uri("http://localhost:" + port + "/actuator/gateway/routes") - .accept(MediaType.APPLICATION_JSON).body(BodyInserters.fromValue(multipleRouteDefs)).exchange() - .expectStatus().isOk(); - testClient.get().uri("http://localhost:" + port + "/actuator/gateway/routedefinitions") - .accept(MediaType.APPLICATION_JSON).exchange().expectBody() - .jsonPath("[?(@.id in ['%s','%s'])].id".formatted(routeId1, routeId2)).exists(); - } - - @Test - public void testPostMultipleRoutesWithOneWrong_doesntPersistRouteDefinitions() { - - RouteDefinition testRouteDefinition = new RouteDefinition(); - testRouteDefinition.setUri(URI.create("http://example.org")); - String routeId1 = UUID.randomUUID().toString(); - testRouteDefinition.setId(routeId1); - - FilterDefinition prefixPathFilterDefinition = new FilterDefinition("PrefixPath=/test-path"); - FilterDefinition redirectToFilterDefinition = new FilterDefinition("RemoveResponseHeader=Sensitive-Header"); - FilterDefinition testFilterDefinition = new FilterDefinition("TestFilter"); - testRouteDefinition.setFilters( - Arrays.asList(prefixPathFilterDefinition, redirectToFilterDefinition, testFilterDefinition)); - - PredicateDefinition hostRoutePredicateDefinition = new PredicateDefinition("Host=myhost.org"); - PredicateDefinition methodRoutePredicateDefinition = new PredicateDefinition("Method=GET"); - PredicateDefinition testPredicateDefinition = new PredicateDefinition("Test=value"); - testRouteDefinition.setPredicates( - Arrays.asList(hostRoutePredicateDefinition, methodRoutePredicateDefinition, testPredicateDefinition)); - - RouteDefinition testRouteDefinition2 = new RouteDefinition(); - testRouteDefinition2.setUri(URI.create("this-is-wrong")); - String routeId2 = UUID.randomUUID().toString(); - testRouteDefinition2.setId(routeId2); - - List multipleRouteDefs = List.of(testRouteDefinition, testRouteDefinition2); - - testClient.post().uri("http://localhost:" + port + "/actuator/gateway/routes") - .accept(MediaType.APPLICATION_JSON).body(BodyInserters.fromValue(multipleRouteDefs)).exchange() - .expectStatus().is4xxClientError(); - - testClient.get().uri("http://localhost:" + port + "/actuator/gateway/routedefinitions") - .accept(MediaType.APPLICATION_JSON).exchange().expectBody() - .jsonPath("[?(@.id in ['%s','%s'])].id".formatted(routeId1, routeId2)).doesNotExist(); - } - - @Test - public void testPostValidShortcutRouteDefinition() { - RouteDefinition testRouteDefinition = new RouteDefinition(); - testRouteDefinition.setId( - "gatewaywithgrpcfiltertest-0-104014-8916263311295787431172436062-test-gateway-tls-client-mapping-0"); - testRouteDefinition.setUri(URI.create("https://localhost:8095")); - testRouteDefinition.setOrder(0); - testRouteDefinition.setMetadata(Collections.emptyMap()); - - FilterDefinition longFilterDefinition = new FilterDefinition(); - FilterDefinition stripPrefix = new FilterDefinition(); - stripPrefix.setName("StripPrefix"); - stripPrefix.addArg("_genkey_0", "1"); - - longFilterDefinition.setName("JsonToGrpc"); - longFilterDefinition.addArg("_genkey_0", "file:src/main/proto/hello.pb"); - longFilterDefinition.addArg("_genkey_1", "file:src/main/proto/hello.proto"); - longFilterDefinition.addArg("_genkey_2", "HelloService"); - longFilterDefinition.addArg("_genkey_3", "hello"); - testRouteDefinition.setFilters(Collections.singletonList(longFilterDefinition)); - - PredicateDefinition hostRoutePredicateDefinition = new PredicateDefinition(); - hostRoutePredicateDefinition.setName("Path"); - hostRoutePredicateDefinition.addArg("_genkey_0", "/json/hello"); - testRouteDefinition.setPredicates(Arrays.asList(hostRoutePredicateDefinition)); - - testClient.post().uri("http://localhost:" + port + "/actuator/gateway/routes/test-route") - .accept(MediaType.APPLICATION_JSON).body(BodyInserters.fromValue(testRouteDefinition)).exchange() - .expectStatus().isCreated(); - } - - @Test - public void testPostRouteWithNotExistingFilter() { - - RouteDefinition testRouteDefinition = new RouteDefinition(); - testRouteDefinition.setUri(URI.create("http://example.org")); - - FilterDefinition filterDefinition = new FilterDefinition("NotExistingFilter=test-config"); - testRouteDefinition.setFilters(Collections.singletonList(filterDefinition)); - - testClient.post().uri("http://localhost:" + port + "/actuator/gateway/routes/test-route") - .accept(MediaType.APPLICATION_JSON).body(BodyInserters.fromValue(testRouteDefinition)).exchange() - .expectStatus().isBadRequest().expectBody().jsonPath("$.message") - .isEqualTo("Invalid FilterDefinition: [NotExistingFilter]"); - } - - @Test - public void testPostRouteWithUriWithoutScheme() { - - RouteDefinition testRouteDefinition = new RouteDefinition(); - testRouteDefinition.setUri(URI.create("example.org")); - - testClient.post().uri("http://localhost:" + port + "/actuator/gateway/routes/no-scheme-test-route") - .accept(MediaType.APPLICATION_JSON).body(BodyInserters.fromValue(testRouteDefinition)).exchange() - .expectStatus().isBadRequest().expectBody().jsonPath("$.message") - .isEqualTo("The URI format [example.org] is incorrect, scheme can not be empty"); - } - - @Test - public void testPostRouteWithUri() { - - RouteDefinition testRouteDefinition = new RouteDefinition(); - testRouteDefinition.setUri(null); - - testClient.post().uri("http://localhost:" + port + "/actuator/gateway/routes/no-scheme-test-route") - .accept(MediaType.APPLICATION_JSON).body(BodyInserters.fromValue(testRouteDefinition)).exchange() - .expectStatus().isBadRequest().expectBody().jsonPath("$.message").isEqualTo("The URI can not be empty"); - } - - @Test - public void testPostRouteWithNotExistingPredicate() { - - RouteDefinition testRouteDefinition = new RouteDefinition(); - testRouteDefinition.setUri(URI.create("http://example.org")); - - PredicateDefinition predicateDefinition = new PredicateDefinition("NotExistingPredicate=test-config"); - testRouteDefinition.setPredicates(Collections.singletonList(predicateDefinition)); - - testClient.post().uri("http://localhost:" + port + "/actuator/gateway/routes/test-route") - .accept(MediaType.APPLICATION_JSON).body(BodyInserters.fromValue(testRouteDefinition)).exchange() - .expectStatus().isBadRequest().expectBody().jsonPath("$.message") - .isEqualTo("Invalid PredicateDefinition: [NotExistingPredicate]"); - } - - @SpringBootConfiguration - @EnableAutoConfiguration - @Import(PermitAllSecurityConfiguration.class) - static class TestConfig { - - @Bean - RouteLocator testRouteLocator(RouteLocatorBuilder routeLocatorBuilder) { - return routeLocatorBuilder.routes() - .route("test-service", r -> r.path("/test-service/**").uri("lb://test-service")).build(); - } - - @Bean - public TestFilterGatewayFilterFactory customGatewayFilterFactory() { - return new TestFilterGatewayFilterFactory(); - } - - @Bean - public TestRoutePredicateFactory customGatewayPredicateFactory() { - return new TestRoutePredicateFactory(Object.class); - } - - } - - private static class TestFilterGatewayFilterFactory extends AbstractGatewayFilterFactory { - - @Override - public GatewayFilter apply(Object config) { - return null; - } - - } - - private static class TestRoutePredicateFactory extends AbstractRoutePredicateFactory { - - TestRoutePredicateFactory(Class configClass) { - super(configClass); - } - - @Override - public Predicate apply(Object config) { - return (GatewayPredicate) serverWebExchange -> true; - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/ConfigurableHintsRegistrationProcessorTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/ConfigurableHintsRegistrationProcessorTests.java deleted file mode 100644 index 5d5cfaff..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/ConfigurableHintsRegistrationProcessorTests.java +++ /dev/null @@ -1,98 +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.config; - -import java.util.ArrayList; -import java.util.function.Consumer; - -import org.junit.jupiter.api.Test; - -import org.springframework.aot.generate.GeneratedClass; -import org.springframework.aot.generate.GeneratedMethods; -import org.springframework.aot.generate.GenerationContext; -import org.springframework.aot.generate.MethodReference; -import org.springframework.aot.hint.RuntimeHints; -import org.springframework.aot.test.generate.TestGenerationContext; -import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution; -import org.springframework.beans.factory.aot.BeanFactoryInitializationCode; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.support.BeanDefinitionBuilder; -import org.springframework.beans.factory.support.DefaultListableBeanFactory; -import org.springframework.cloud.gateway.filter.factory.SpringCloudCircuitBreakerFilterFactory; -import org.springframework.cloud.gateway.filter.factory.SpringCloudCircuitBreakerResilience4JFilterFactory; -import org.springframework.javapoet.TypeSpec; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.aot.hint.predicate.RuntimeHintsPredicates.reflection; - -/** - * Tests for {@link ConfigurableHintsRegistrationProcessor}. - * - * @author Olga Maciaszek-Sharma - */ -class ConfigurableHintsRegistrationProcessorTests { - - private final ConfigurableHintsRegistrationProcessor processor = new ConfigurableHintsRegistrationProcessor(); - - private final TestGenerationContext generationContext = new TestGenerationContext(); - - private final BeanFactoryInitializationCode beanFactoryInitializationCode = new MockBeanFactoryInitializationCode( - generationContext); - - private final DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); - - @Test - void shouldRegisterReflectionHintsForTypeAndSuperTypesAndGenerics() { - BeanDefinition beanDefinition = BeanDefinitionBuilder - .rootBeanDefinition(SpringCloudCircuitBreakerResilience4JFilterFactory.class).getBeanDefinition(); - beanFactory.registerBeanDefinition("test", beanDefinition); - - BeanFactoryInitializationAotContribution contribution = processor.processAheadOfTime(beanFactory); - assertThat(contribution).isNotNull(); - contribution.applyTo(generationContext, beanFactoryInitializationCode); - - RuntimeHints hints = generationContext.getRuntimeHints(); - assertThat(reflection().onType(SpringCloudCircuitBreakerResilience4JFilterFactory.class)).accepts(hints); - assertThat(reflection().onType(SpringCloudCircuitBreakerFilterFactory.class)).accepts(hints); - assertThat(reflection().onType(SpringCloudCircuitBreakerFilterFactory.Config.class)).accepts(hints); - } - - @SuppressWarnings("NullableProblems") - static class MockBeanFactoryInitializationCode implements BeanFactoryInitializationCode { - - private static final Consumer emptyTypeCustomizer = type -> { - }; - - private final GeneratedClass generatedClass; - - MockBeanFactoryInitializationCode(GenerationContext generationContext) { - generatedClass = generationContext.getGeneratedClasses().addForFeature("Test", emptyTypeCustomizer); - } - - @Override - public GeneratedMethods getMethods() { - return generatedClass.getMethods(); - } - - @Override - public void addInitializer(MethodReference methodReference) { - new ArrayList<>(); - } - - } - -} 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 deleted file mode 100644 index 73f80152..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/GatewayAutoConfigurationTests.java +++ /dev/null @@ -1,481 +0,0 @@ -/* - * Copyright 2013-2021 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.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; - -import javax.net.ssl.TrustManagerFactory; - -import io.netty.channel.ChannelOption; -import io.netty.handler.ssl.SslContextBuilder; -import io.netty.handler.ssl.util.InsecureTrustManagerFactory; -import org.junit.jupiter.api.Test; -import reactor.netty.http.HttpProtocol; -import reactor.netty.http.client.HttpClient; -import reactor.netty.http.client.HttpClientConfig; -import reactor.netty.http.client.WebsocketClientSpec; -import reactor.netty.http.server.WebsocketServerSpec; -import reactor.netty.resources.ConnectionProvider; -import reactor.netty.tcp.SslProvider; -import reactor.netty.transport.ProxyProvider; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration; -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.autoconfigure.AutoConfigureBefore; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration; -import org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration; -import org.springframework.boot.autoconfigure.web.ServerProperties; -import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; -import org.springframework.cloud.gateway.actuate.GatewayControllerEndpoint; -import org.springframework.cloud.gateway.actuate.GatewayLegacyControllerEndpoint; -import org.springframework.cloud.gateway.config.GatewayAutoConfigurationTests.CustomHttpClientFactory.CustomSslConfigurer; -import org.springframework.cloud.gateway.filter.factory.TokenRelayGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.headers.GRPCRequestHeadersFilter; -import org.springframework.cloud.gateway.filter.headers.GRPCResponseHeadersFilter; -import org.springframework.cloud.gateway.route.RouteLocator; -import org.springframework.cloud.gateway.route.builder.GatewayFilterSpec; -import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Primary; -import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientManager; -import org.springframework.web.filter.reactive.HiddenHttpMethodFilter; -import org.springframework.web.reactive.socket.client.ReactorNettyWebSocketClient; -import org.springframework.web.reactive.socket.server.upgrade.ReactorNettyRequestUpgradeStrategy; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -public class GatewayAutoConfigurationTests { - - @Test - public void noHiddenHttpMethodFilter() { - try (ConfigurableApplicationContext ctx = SpringApplication.run(Config.class, "--spring.jmx.enabled=false", - "--server.port=0")) { - assertThat(ctx.getEnvironment().getProperty("spring.webflux.hiddenmethod.filter.enabled")) - .isEqualTo("false"); - assertThat(ctx.getBeanNamesForType(HiddenHttpMethodFilter.class)).isEmpty(); - } - } - - @Test - public void nettyHttpClientDefaults() { - new ReactiveWebApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(WebFluxAutoConfiguration.class, MetricsAutoConfiguration.class, - SimpleMetricsExportAutoConfiguration.class, GatewayAutoConfiguration.class, - ServerPropertiesConfig.class)) - .withPropertyValues("debug=true").run(context -> { - assertThat(context).hasSingleBean(HttpClient.class); - HttpClient httpClient = context.getBean(HttpClient.class); - CustomHttpClientFactory factory = context.getBean(CustomHttpClientFactory.class); - - assertThat(factory.connectionProvider).isNotNull(); - assertThat(factory.connectionProvider.maxConnections()).isEqualTo(Integer.MAX_VALUE); // elastic - - assertThat(factory.proxyProvider).isNull(); - assertThat(factory.isSslConfigured()).isFalse(); - - assertThat(httpClient.configuration().isAcceptGzip()).isFalse(); - assertThat(httpClient.configuration().loggingHandler()).isNull(); - assertThat(httpClient.configuration().options()) - .doesNotContainKey(ChannelOption.CONNECT_TIMEOUT_MILLIS); - }); - } - - @Test - public void nettyHttpClientConfigured() { - new ReactiveWebApplicationContextRunner() - .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.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.compression=true", - "spring.cloud.gateway.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") - .run(context -> { - assertThat(context).hasSingleBean(HttpClient.class); - HttpClient httpClient = context.getBean(HttpClient.class); - CustomHttpClientFactory factory = context.getBean(CustomHttpClientFactory.class); - HttpClientProperties properties = context.getBean(HttpClientProperties.class); - assertThat(properties.getMaxInitialLineLength().toBytes()).isLessThanOrEqualTo(Integer.MAX_VALUE); - assertThat(properties.isCompression()).isEqualTo(true); - assertThat(properties.getPool().getEvictionInterval()).hasSeconds(10); - assertThat(properties.getPool().isMetrics()).isEqualTo(true); - - assertThat(httpClient.configuration().isAcceptGzip()).isTrue(); - assertThat(httpClient.configuration().loggingHandler()).isNotNull(); - assertThat(httpClient.configuration().options()).containsKey(ChannelOption.CONNECT_TIMEOUT_MILLIS); - assertThat(httpClient.configuration().options().get(ChannelOption.CONNECT_TIMEOUT_MILLIS)) - .isEqualTo(10); - - assertThat(factory.connectionProvider).isNotNull(); - // fixed pool - assertThat(factory.connectionProvider.maxConnections()) - .isEqualTo(ConnectionProvider.DEFAULT_POOL_MAX_CONNECTIONS); - - assertThat(factory.proxyProvider).isNotNull(); - assertThat(factory.proxyProvider.build().getAddress().get().getHostName()).isEqualTo("myhost"); - - assertThat(factory.isSslConfigured()).isTrue(); - assertThat(factory.isInsecureTrustManagerSet()).isTrue(); - - assertThat(context).hasSingleBean(ReactorNettyRequestUpgradeStrategy.class); - ReactorNettyRequestUpgradeStrategy upgradeStrategy = context - .getBean(ReactorNettyRequestUpgradeStrategy.class); - assertThat(upgradeStrategy.getWebsocketServerSpec().maxFramePayloadLength()).isEqualTo(1024); - assertThat(upgradeStrategy.getWebsocketServerSpec().handlePing()).isTrue(); - assertThat(context).hasSingleBean(ReactorNettyWebSocketClient.class); - ReactorNettyWebSocketClient webSocketClient = context.getBean(ReactorNettyWebSocketClient.class); - assertThat(webSocketClient.getWebsocketClientSpec().maxFramePayloadLength()).isEqualTo(1024); - HttpClientCustomizedConfig config = context.getBean(HttpClientCustomizedConfig.class); - assertThat(config.called.get()).isTrue(); - }); - } - - @Test - public void verboseActuatorEnabledByDefault() { - try (ConfigurableApplicationContext ctx = SpringApplication.run(Config.class, "--spring.jmx.enabled=false", - "--server.port=0", "--management.endpoint.gateway.enabled=true")) { - assertThat(ctx.getBeanNamesForType(GatewayControllerEndpoint.class)).hasSize(1); - assertThat(ctx.getBeanNamesForType(GatewayLegacyControllerEndpoint.class)).isEmpty(); - } - } - - @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", - "--management.endpoint.gateway.enabled=true")) { - assertThat(ctx.getBeanNamesForType(GatewayLegacyControllerEndpoint.class)).hasSize(1); - } - } - - @Test - public void tokenRelayBeansAreCreated() { - new ReactiveWebApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(ReactiveSecurityAutoConfiguration.class, - ReactiveOAuth2ClientAutoConfiguration.class, GatewayReactiveOAuth2AutoConfiguration.class, - GatewayAutoConfiguration.TokenRelayConfiguration.class)) - .withPropertyValues( - "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", - "spring.security.oauth2.client.registration[test].authorization-grant-type=authorization_code", - "spring.security.oauth2.client.registration[test].redirect-uri=http://localhost/redirect", - "spring.security.oauth2.client.registration[test].client-id=login-client") - .run(context -> { - assertThat(context).hasSingleBean(ReactiveOAuth2AuthorizedClientManager.class); - assertThat(context).hasSingleBean(TokenRelayGatewayFilterFactory.class); - }); - } - - @Test - public void gatewayReactiveOAuth2AuthorizedClientManagerBacksOffForCustomBean() { - new ReactiveWebApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(ReactiveSecurityAutoConfiguration.class, - ReactiveOAuth2ClientAutoConfiguration.class, GatewayReactiveOAuth2AutoConfiguration.class)) - .withUserConfiguration(TestReactiveOAuth2AuthorizedClientManagerConfig.class) - .withPropertyValues( - "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", - "spring.security.oauth2.client.registration[test].authorization-grant-type=authorization_code", - "spring.security.oauth2.client.registration[test].redirect-uri=http://localhost/redirect", - "spring.security.oauth2.client.registration[test].client-id=login-client") - .run(context -> { - assertThat(context).hasSingleBean(ReactiveOAuth2AuthorizedClientManager.class); - assertThat(context).hasBean("myReactiveOAuth2AuthorizedClientManager"); - }); - } - - @Test - public void noTokenRelayFilter() { - assertThatThrownBy(() -> { - try (ConfigurableApplicationContext ctx = SpringApplication.run(RouteLocatorBuilderConfig.class, - "--spring.jmx.enabled=false", "--spring.cloud.gateway.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", - "--spring.security.oauth2.client.registration[test].authorization-grant-type=authorization_code", - "--spring.security.oauth2.client.registration[test].redirect-uri=http://localhost/redirect", - "--spring.security.oauth2.client.registration[test].client-id=login-client", "--server.port=0", - "--spring.cloud.gateway.actuator.verbose.enabled=false")) { - assertThat(ctx.getBeanNamesForType(GatewayLegacyControllerEndpoint.class)).hasSize(1); - } - }).hasRootCauseInstanceOf(IllegalStateException.class) - .hasMessageContaining("No TokenRelayGatewayFilterFactory bean was found. Did you include"); - } - - @Test // gh-2159 - public void reactorNettyRequestUpgradeStrategyWebSocketSpecBuilderIsUniquePerRequest() - throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { - ReactorNettyRequestUpgradeStrategy strategy = new GatewayAutoConfiguration.NettyConfiguration() - .reactorNettyRequestUpgradeStrategy(new HttpClientProperties()); - - // Method "buildSpec" was introduced for Tests, but has only default visiblity - Method buildSpec = ReactorNettyRequestUpgradeStrategy.class.getDeclaredMethod("buildSpec", String.class); - buildSpec.setAccessible(true); - WebsocketServerSpec spec1 = (WebsocketServerSpec) buildSpec.invoke(strategy, "p1"); - WebsocketServerSpec spec2 = strategy.getWebsocketServerSpec(); - - assertThat(spec1.protocols()).isEqualTo("p1"); - assertThat(spec2.protocols()).isNull(); - } - - @Test // gh-2215 - public void webSocketClientSpecBuilderIsUniquePerReactorNettyWebSocketClient() - throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { - ReactorNettyWebSocketClient websocketClient = new GatewayAutoConfiguration.NettyConfiguration() - .reactorNettyWebSocketClient(new HttpClientProperties(), HttpClient.create()); - - // Method "buildSpec" has only private visibility - Method buildSpec = ReactorNettyWebSocketClient.class.getDeclaredMethod("buildSpec", String.class); - buildSpec.setAccessible(true); - WebsocketClientSpec spec1 = (WebsocketClientSpec) buildSpec.invoke(websocketClient, "p1"); - WebsocketClientSpec spec2 = websocketClient.getWebsocketClientSpec(); - - assertThat(spec1.protocols()).isEqualTo("p1"); - // Protocols should not be cached between requests: - assertThat(spec2.protocols()).isNull(); - } - - @Test - public void gRPCFiltersConfiguredWhenHTTP2Enabled() { - new ReactiveWebApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(WebFluxAutoConfiguration.class, MetricsAutoConfiguration.class, - SimpleMetricsExportAutoConfiguration.class, GatewayAutoConfiguration.class, - HttpClientCustomizedConfig.class, ServerPropertiesConfig.class)) - .withPropertyValues("server.http2.enabled=true").run(context -> { - assertThat(context).hasSingleBean(GRPCRequestHeadersFilter.class); - assertThat(context).hasSingleBean(GRPCResponseHeadersFilter.class); - HttpClient httpClient = context.getBean(HttpClient.class); - assertThat(httpClient.configuration().protocols()).contains(HttpProtocol.HTTP11, HttpProtocol.H2); - }); - } - - @Test - public void gRPCFiltersNotConfiguredWhenHTTP2Disabled() { - new ReactiveWebApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(WebFluxAutoConfiguration.class, MetricsAutoConfiguration.class, - SimpleMetricsExportAutoConfiguration.class, GatewayAutoConfiguration.class, - HttpClientCustomizedConfig.class, ServerPropertiesConfig.class)) - .withPropertyValues("server.http2.enabled=false").run(context -> { - assertThat(context).doesNotHaveBean(GRPCRequestHeadersFilter.class); - assertThat(context).doesNotHaveBean(GRPCResponseHeadersFilter.class); - }); - } - - @Test - public void insecureTrustManagerNotEnabledByDefaultWhenHTTP2Enabled() { - new ReactiveWebApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(WebFluxAutoConfiguration.class, MetricsAutoConfiguration.class, - SimpleMetricsExportAutoConfiguration.class, GatewayAutoConfiguration.class, - HttpClientCustomizedConfig.class, ServerPropertiesConfig.class)) - .withPropertyValues("server.http2.enabled=true").run(context -> { - assertThat(context).hasSingleBean(HttpClient.class); - CustomHttpClientFactory factory = context.getBean(CustomHttpClientFactory.class); - assertThat(factory.isInsecureTrustManagerSet()).isFalse(); - }); - } - - @Test - public void customHttpClientWorks() { - new ReactiveWebApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(WebFluxAutoConfiguration.class, MetricsAutoConfiguration.class, - SimpleMetricsExportAutoConfiguration.class, GatewayAutoConfiguration.class, - HttpClientCustomizedConfig.class, CustomHttpClientConfig.class)) - .run(context -> { - assertThat(context).hasSingleBean(HttpClient.class); - HttpClient httpClient = context.getBean(HttpClient.class); - assertThat(httpClient).isInstanceOf(CustomHttpClient.class); - }); - } - - @Configuration - @EnableConfigurationProperties(ServerProperties.class) - @AutoConfigureBefore(GatewayAutoConfiguration.class) - protected static class ServerPropertiesConfig { - - @Bean - @Primary - CustomHttpClientFactory customHttpClientFactory(HttpClientProperties properties, - ServerProperties serverProperties, List customizers, - HttpClientSslConfigurer sslConfigurer) { - return new CustomHttpClientFactory(properties, serverProperties, sslConfigurer, customizers); - } - - @Bean - @Primary - CustomSslConfigurer customSslContextFactory(ServerProperties serverProperties, - HttpClientProperties httpClientProperties) { - return new CustomSslConfigurer(httpClientProperties.getSsl(), serverProperties); - } - - } - - protected static class CustomHttpClientFactory extends HttpClientFactory { - - private ConnectionProvider connectionProvider; - - private ProxyProvider.Builder proxyProvider; - - private CustomSslConfigurer customSslContextFactory; - - public CustomHttpClientFactory(HttpClientProperties properties, ServerProperties serverProperties, - HttpClientSslConfigurer sslConfigurer, List customizers) { - super(properties, serverProperties, sslConfigurer, customizers); - this.customSslContextFactory = (CustomSslConfigurer) sslConfigurer; - } - - @Override - protected ConnectionProvider buildConnectionProvider(HttpClientProperties properties) { - connectionProvider = super.buildConnectionProvider(properties); - return connectionProvider; - } - - @Override - protected ProxyProvider.Builder configureProxyProvider(HttpClientProperties.Proxy proxy, - ProxyProvider.TypeSpec proxySpec) { - proxyProvider = super.configureProxyProvider(proxy, proxySpec); - return proxyProvider; - } - - public boolean isSslConfigured() { - return customSslContextFactory.sslConfigured; - } - - public boolean isInsecureTrustManagerSet() { - return customSslContextFactory.insecureTrustManagerSet; - } - - protected static class CustomSslConfigurer extends HttpClientSslConfigurer { - - boolean sslConfigured; - - boolean insecureTrustManagerSet; - - protected CustomSslConfigurer(HttpClientProperties.Ssl sslProperties, ServerProperties serverProperties) { - super(sslProperties, serverProperties); - } - - @Override - protected void configureSslContext(HttpClientProperties.Ssl ssl, - SslProvider.SslContextSpec sslContextSpec) { - sslConfigured = true; - super.configureSslContext(getSslProperties(), sslContextSpec); - } - - @Override - protected void setTrustManager(SslContextBuilder sslContextBuilder, TrustManagerFactory factory) { - insecureTrustManagerSet = factory == InsecureTrustManagerFactory.INSTANCE; - super.setTrustManager(sslContextBuilder, factory); - } - - } - - } - - @EnableAutoConfiguration - @SpringBootConfiguration - protected static class Config { - - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @EnableConfigurationProperties(ServerProperties.class) - @AutoConfigureBefore(GatewayAutoConfiguration.class) - protected static class CustomHttpClientConfig { - - @Bean - public HttpClient customHttpClient() { - return new CustomHttpClient(); - } - - } - - protected static class CustomHttpClient extends HttpClient { - - @Override - public HttpClientConfig configuration() { - return null; - } - - @Override - protected HttpClient duplicate() { - return this; - } - - } - - @EnableAutoConfiguration - @SpringBootConfiguration - protected static class RouteLocatorBuilderConfig { - - @Bean - public RouteLocator myRouteLocator(RouteLocatorBuilder builder) { - return builder.routes() - .route("test", r -> r.alwaysTrue().filters(GatewayFilterSpec::tokenRelay).uri("http://localhost")) - .build(); - } - - } - - @Configuration - protected static class HttpClientCustomizedConfig { - - private final AtomicBoolean called = new AtomicBoolean(); - - @Bean - HttpClientCustomizer myCustomCustomizer() { - return httpClient -> { - called.compareAndSet(false, true); - return httpClient; - }; - } - - } - - @Configuration - protected static class TestReactiveOAuth2AuthorizedClientManagerConfig { - - @Bean - ReactiveOAuth2AuthorizedClientManager myReactiveOAuth2AuthorizedClientManager() { - return authorizeRequest -> null; - } - - } - -} 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 deleted file mode 100644 index d328873e..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/GatewayMetricsAutoConfigurationTests.java +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Copyright 2013-2020 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.List; - -import io.micrometer.core.instrument.Tags; -import io.micrometer.tracing.Tracer; -import io.micrometer.tracing.propagation.Propagator; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.mockito.Mockito; - -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.autoconfigure.actuate.observability.AutoConfigureObservability; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.gateway.filter.GatewayMetricsFilter; -import org.springframework.cloud.gateway.filter.headers.observation.GatewayPropagatingSenderTracingObservationHandler; -import org.springframework.cloud.gateway.filter.headers.observation.ObservationClosingWebExceptionHandler; -import org.springframework.cloud.gateway.filter.headers.observation.ObservedRequestHttpHeadersFilter; -import org.springframework.cloud.gateway.filter.headers.observation.ObservedResponseHttpHeadersFilter; -import org.springframework.cloud.gateway.route.RouteDefinitionMetrics; -import org.springframework.cloud.gateway.support.tagsprovider.GatewayTagsProvider; -import org.springframework.context.annotation.Bean; -import org.springframework.web.server.ServerWebExchange; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Ingyu Hwang - */ -public class GatewayMetricsAutoConfigurationTests { - - @Nested - @SpringBootTest(classes = Config.class) - public class EnabledByDefault { - - @Autowired(required = false) - private GatewayMetricsFilter filter; - - @Autowired(required = false) - private RouteDefinitionMetrics routeDefinitionMetrics; - - @Autowired(required = false) - private List tagsProviders; - - @Autowired - private BeanFactory beanFactory; - - @Test - public void gatewayMetricsBeansExists() { - assertThat(filter).isNotNull(); - assertThat(filter.getMetricsPrefix()).isEqualTo("spring.cloud.gateway"); - assertThat(tagsProviders).isNotEmpty(); - } - - @Test - public void routeDefinitionMetricsBeanExists() { - assertThat(routeDefinitionMetrics).isNotNull(); - assertThat(routeDefinitionMetrics.getMetricsPrefix()).isEqualTo("spring.cloud.gateway"); - } - - @Test - public void observabilityBeansExist() { - assertThat(beanFactory.getBean(ObservedRequestHttpHeadersFilter.class)).isNotNull(); - assertThat(beanFactory.getBean(ObservedResponseHttpHeadersFilter.class)).isNotNull(); - assertThat(beanFactory.getBean(ObservationClosingWebExceptionHandler.class)).isNotNull(); - assertThat(beanFactory.getBean(GatewayPropagatingSenderTracingObservationHandler.class)).isNotNull(); - } - - } - - @Nested - @SpringBootTest(classes = Config.class, properties = "spring.cloud.gateway.metrics.enabled=false") - public class DisabledByProperty { - - @Autowired(required = false) - private GatewayMetricsFilter filter; - - @Autowired(required = false) - private RouteDefinitionMetrics routeDefinitionMetrics; - - @Test - public void gatewayMetricsBeanMissing() { - assertThat(filter).isNull(); - } - - @Test - public void routeDefinitionMetricsBeanMissing() { - assertThat(routeDefinitionMetrics).isNull(); - } - - } - - @Nested - @SpringBootTest(classes = Config.class, properties = "spring.cloud.gateway.observability.enabled=false") - public class ObservabilityDisabledByProperty { - - @Autowired - private BeanFactory beanFactory; - - @Test - public void observabilityBeansMissing() { - assertThat(beanFactory.getBeanProvider(ObservedRequestHttpHeadersFilter.class).getIfAvailable(() -> null)) - .isNull(); - assertThat(beanFactory.getBeanProvider(ObservedResponseHttpHeadersFilter.class).getIfAvailable(() -> null)) - .isNull(); - assertThat( - beanFactory.getBeanProvider(ObservationClosingWebExceptionHandler.class).getIfAvailable(() -> null)) - .isNull(); - assertThat(beanFactory.getBeanProvider(GatewayPropagatingSenderTracingObservationHandler.class) - .getIfAvailable(() -> null)).isNull(); - } - - } - - @Nested - @SpringBootTest(classes = CustomTagsProviderConfig.class, - properties = "spring.cloud.gateway.metrics.prefix=myprefix.") - public class AddCustomTagsProvider { - - @Autowired(required = false) - private GatewayMetricsFilter filter; - - @Autowired(required = false) - private RouteDefinitionMetrics routeDefinitionMetrics; - - @Autowired(required = false) - private List tagsProviders; - - @Test - public void gatewayMetricsBeansExists() { - assertThat(filter).isNotNull(); - assertThat(filter.getMetricsPrefix()).isEqualTo("myprefix"); - assertThat(tagsProviders).extracting("class").contains(CustomTagsProviderConfig.EmptyTagsProvider.class); - } - - @Test - public void routeDefinitionMetricsBeanExists() { - assertThat(routeDefinitionMetrics).isNotNull(); - assertThat(routeDefinitionMetrics.getMetricsPrefix()).isEqualTo("myprefix"); - } - - } - - @SpringBootConfiguration - @EnableAutoConfiguration - @AutoConfigureObservability - protected static class Config { - - @Bean - Tracer tracer() { - return Mockito.mock(Tracer.class); - } - - @Bean - Propagator propagator() { - return Mockito.mock(Propagator.class); - } - - } - - @SpringBootConfiguration - @EnableAutoConfiguration - protected static class CustomTagsProviderConfig { - - @Bean - public GatewayTagsProvider emptyTagsProvider() { - return new EmptyTagsProvider(); - } - - protected static class EmptyTagsProvider implements GatewayTagsProvider { - - @Override - public Tags apply(ServerWebExchange exchange) { - return Tags.empty(); - } - - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/GatewayNoLoadBalancerClientAutoConfigurationTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/GatewayNoLoadBalancerClientAutoConfigurationTests.java deleted file mode 100644 index bdb8b2cd..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/GatewayNoLoadBalancerClientAutoConfigurationTests.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2013-2020 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.BeforeClass; -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.cloud.gateway.route.RouteLocator; -import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; -import org.springframework.cloud.gateway.test.PermitAllSecurityConfiguration; -import org.springframework.cloud.test.ClassPathExclusions; -import org.springframework.cloud.test.ModifiedClassPathRunner; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; -import org.springframework.http.HttpHeaders; -import org.springframework.test.util.TestSocketUtils; -import org.springframework.test.web.reactive.server.WebTestClient; - -@RunWith(ModifiedClassPathRunner.class) -@ClassPathExclusions({ "spring-cloud-loadbalancer-*.jar" }) -public class GatewayNoLoadBalancerClientAutoConfigurationTests { - - private static int port; - - @BeforeClass - public static void init() { - port = TestSocketUtils.findAvailableTcpPort(); - } - - @Test - public void noLoadBalancerClientReportsError() { - try (ConfigurableApplicationContext context = new SpringApplication(Config.class).run("--server.port=" + port, - "--spring.jmx.enabled=false")) { - WebTestClient client = WebTestClient.bindToServer().baseUrl("http://localhost:" + port).build(); - client.get().header(HttpHeaders.HOST, "www.lbfail.org").exchange().expectStatus().is5xxServerError(); - } - } - - @SpringBootConfiguration - @EnableAutoConfiguration - @Import(PermitAllSecurityConfiguration.class) - public static class Config { - - @Bean - public RouteLocator routeLocator(RouteLocatorBuilder builder) { - return builder.routes().route("lb_fail", r -> r.host("**.lbfail.org").uri("lb://fail")).build(); - } - - } - -} 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 deleted file mode 100644 index a3a1b6ce..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/GatewayRedisAutoConfigurationTests.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright 2013-2020 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.Nested; -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.filter.ratelimit.RedisRateLimiter; -import org.springframework.cloud.gateway.route.RedisRouteDefinitionRepository; -import org.springframework.cloud.gateway.route.RedisRouteDefinitionRepositoryTests; -import org.springframework.context.annotation.Bean; -import org.springframework.data.redis.core.script.RedisScript; -import org.springframework.test.annotation.DirtiesContext; - -import static org.assertj.core.api.Assertions.assertThat; - -public class GatewayRedisAutoConfigurationTests { - - @SpringBootConfiguration - @EnableAutoConfiguration - protected static class Config { - - // TODO: figure out why I need these - @Bean - RedisRouteDefinitionRepositoryTests.TestGatewayFilterFactory testGatewayFilterFactory() { - return new RedisRouteDefinitionRepositoryTests.TestGatewayFilterFactory(); - } - - @Bean - RedisRouteDefinitionRepositoryTests.TestFilterGatewayFilterFactory testFilterGatewayFilterFactory() { - return new RedisRouteDefinitionRepositoryTests.TestFilterGatewayFilterFactory(); - } - - @Bean - RedisRouteDefinitionRepositoryTests.TestRoutePredicateFactory testRoutePredicateFactory() { - return new RedisRouteDefinitionRepositoryTests.TestRoutePredicateFactory(); - } - - } - - @Nested - @SpringBootTest(classes = Config.class) - class EnabledByDefault { - - @Autowired(required = false) - private RedisScript redisRequestRateLimiterScript; - - @Autowired(required = false) - private RedisRateLimiter redisRateLimiter; - - @Test - public void shouldInjectRedisBeans() { - assertThat(redisRequestRateLimiterScript).isNotNull(); - assertThat(redisRateLimiter).isNotNull(); - } - - } - - @Nested - @SpringBootTest(classes = Config.class, properties = "spring.cloud.gateway.redis.enabled=false") - class DisabledByProperty { - - @Autowired(required = false) - private RedisScript redisRequestRateLimiterScript; - - @Autowired(required = false) - private RedisRateLimiter redisRateLimiter; - - @Test - public void shouldDisableRedisBeans() { - assertThat(redisRequestRateLimiterScript).isNull(); - assertThat(redisRateLimiter).isNull(); - } - - } - - /** - * @author Dennis Menge - */ - @Nested - @SpringBootTest(classes = GatewayRedisAutoConfigurationTests.Config.class, - properties = "spring.cloud.gateway.redis-route-definition-repository.enabled=false") - @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) - class RedisRouteDefinitionRepositoryDisabledByProperty { - - @Autowired(required = false) - private RedisRouteDefinitionRepository redisRouteDefinitionRepository; - - @Test - public void redisRouteDefinitionRepository() { - assertThat(redisRouteDefinitionRepository).isNull(); - } - - } - -} 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 deleted file mode 100644 index ccf240e8..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/GatewayRedisRouteDefinitionRepositoryEnabledByPropertyTests.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2013-2021 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.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.cloud.gateway.route.RedisRouteDefinitionRepository; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.DynamicPropertyRegistry; -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") -@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) -@Testcontainers -@Tag("DockerRequired") -public class GatewayRedisRouteDefinitionRepositoryEnabledByPropertyTests { - - @Container - public static GenericContainer redis = new GenericContainer<>("redis:5.0.14-alpine").withExposedPorts(6379); - - @Autowired(required = false) - private RedisRouteDefinitionRepository redisRouteDefinitionRepository; - - @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); - } - - @Test - public void redisRouteDefinitionRepository() { - assertThat(redisRouteDefinitionRepository).isNotNull(); - } - -} 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 deleted file mode 100644 index f0ca6a3a..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/LocalResponseCacheAutoConfigurationTests.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2013-2020 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.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.cache.CacheManager; -import org.springframework.cache.caffeine.CaffeineCacheManager; -import org.springframework.cloud.gateway.filter.factory.cache.GlobalLocalResponseCacheGatewayFilter; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Primary; - -public class LocalResponseCacheAutoConfigurationTests { - - @Test - void onlyOneCacheManagerBeanCreated() { - new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(LocalResponseCacheAutoConfiguration.class)) - .withPropertyValues("spring.cloud.gateway.filter.local-response-cache.enabled=true").run(context -> { - context.containsBean(LocalResponseCacheAutoConfiguration.RESPONSE_CACHE_MANAGER_NAME); - context.assertThat().hasSingleBean(GlobalLocalResponseCacheGatewayFilter.class); - }); - } - - @Test - void twoCacheManagerBeans() { - new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(CustomCacheManagerConfig.class, - LocalResponseCacheAutoConfiguration.class)) - .withPropertyValues("spring.cloud.gateway.filter.local-response-cache.enabled=true").run(context -> { - context.containsBean(LocalResponseCacheAutoConfiguration.RESPONSE_CACHE_MANAGER_NAME); - context.containsBean("myCacheManager"); - context.assertThat().hasSingleBean(GlobalLocalResponseCacheGatewayFilter.class); - }); - } - - @Configuration(proxyBeanMethods = false) - static class CustomCacheManagerConfig { - - @Bean - @Primary - CacheManager myCacheManager() { - return new CaffeineCacheManager(); - } - - @Bean - Object myCacheConsumer(CacheManager cacheManager) { - return ""; - } - - } - -} 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 deleted file mode 100644 index 7892ab6e..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/conditional/DisableBuiltInFiltersTests.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright 2013-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.config.conditional; - -import java.util.List; - -import org.junit.jupiter.api.Nested; -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.filter.factory.AddRequestHeaderGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.GatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.MapRequestHeaderGatewayFilterFactory; -import org.springframework.test.context.ActiveProfiles; - -import static org.assertj.core.api.Assertions.assertThat; - -public class DisableBuiltInFiltersTests { - - @EnableAutoConfiguration - @SpringBootConfiguration - protected static class Config { - - } - - @Nested - @SpringBootTest(classes = Config.class) - public class FilterDefault { - - @Autowired - private List> gatewayFilters; - - @Test - public void shouldInjectBuiltInFilters() { - assertThat(gatewayFilters).hasSizeGreaterThanOrEqualTo(31); - } - - } - - @Nested - @SpringBootTest(classes = Config.class, - properties = { "spring.cloud.gateway.filter.add-request-header.enabled=false", - "spring.cloud.gateway.filter.map-request-header.enabled=false" }) - @ActiveProfiles("disable-components") - public class DisableSpecificsFiltersByProperty { - - @Autowired - private List> gatewayFilters; - - @Test - public void shouldInjectOnlyEnabledBuiltInFilters() { - assertThat(gatewayFilters).hasSizeGreaterThan(0); - assertThat(gatewayFilters).allSatisfy(filter -> assertThat(filter).isNotInstanceOfAny( - AddRequestHeaderGatewayFilterFactory.class, MapRequestHeaderGatewayFilterFactory.class)); - } - - } - - @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.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" }) - @ActiveProfiles("disable-components") - public class DisableAllFiltersByProperty { - - @Autowired(required = false) - private List> gatewayFilters; - - @Test - public void shouldDisableAllBuiltInFilters() { - assertThat(gatewayFilters).isNull(); - } - - } - -} 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 deleted file mode 100644 index edd1afa9..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/conditional/DisableBuiltInGlobalFiltersTests.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright 2013-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.config.conditional; - -import java.util.List; - -import org.junit.jupiter.api.Nested; -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.filter.GlobalFilter; -import org.springframework.cloud.gateway.filter.RemoveCachedBodyFilter; -import org.springframework.cloud.gateway.filter.RouteToRequestUrlFilter; -import org.springframework.test.context.ActiveProfiles; - -import static org.assertj.core.api.Assertions.assertThat; - -public class DisableBuiltInGlobalFiltersTests { - - @EnableAutoConfiguration - @SpringBootConfiguration - protected static class Config { - - } - - @Nested - @SpringBootTest(classes = Config.class) - public class GlobalFilterDefault { - - @Autowired - private List globalFilters; - - @Test - public void shouldInjectBuiltInFilters() { - assertThat(globalFilters).hasSizeGreaterThanOrEqualTo(10); - } - - } - - @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" }) - @ActiveProfiles("disable-components") - public class DisableSpecificsFiltersByProperty { - - @Autowired - private List globalFilters; - - @Test - public void shouldInjectOnlyEnabledBuiltInFilters() { - assertThat(globalFilters).hasSizeGreaterThan(0); - assertThat(globalFilters).allSatisfy(filter -> assertThat(filter) - .isNotInstanceOfAny(RemoveCachedBodyFilter.class, RouteToRequestUrlFilter.class)); - } - - } - - @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.metrics.enabled=false" }) - @ActiveProfiles("disable-components") - public class DisableAllGlobalFiltersByProperty { - - @Autowired(required = false) - private List globalFilters; - - @Test - public void shouldDisableAllBuiltInFilters() { - assertThat(globalFilters).isNull(); - } - - } - -} 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 deleted file mode 100644 index eeff65c8..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/conditional/DisableBuiltInPredicatesTests.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright 2013-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.config.conditional; - -import java.util.List; - -import org.junit.jupiter.api.Nested; -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.handler.predicate.AfterRoutePredicateFactory; -import org.springframework.cloud.gateway.handler.predicate.BeforeRoutePredicateFactory; -import org.springframework.cloud.gateway.handler.predicate.RoutePredicateFactory; -import org.springframework.test.context.ActiveProfiles; - -import static org.assertj.core.api.Assertions.assertThat; - -public class DisableBuiltInPredicatesTests { - - @EnableAutoConfiguration - @SpringBootConfiguration - protected static class Config { - - } - - @Nested - @SpringBootTest(classes = Config.class) - public class RoutePredicateDefault { - - @Autowired - private List> predicates; - - @Test - public void shouldInjectBuiltInPredicates() { - assertThat(predicates).hasSizeGreaterThanOrEqualTo(13); - } - - } - - @Nested - @SpringBootTest(classes = Config.class, - properties = { "spring.cloud.gateway.predicate.after.enabled=false", - "spring.cloud.gateway.predicate.before.enabled=false" }) - @ActiveProfiles("disable-components") - public class DisableSpecificsPredicatesByProperty { - - @Autowired - private List> predicates; - - @Test - public void shouldInjectOnlyEnabledBuiltInPredicates() { - assertThat(predicates).hasSizeGreaterThan(0); - assertThat(predicates).allSatisfy(filter -> assertThat(filter) - .isNotInstanceOfAny(AfterRoutePredicateFactory.class, BeforeRoutePredicateFactory.class)); - } - - } - - @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" }) - @ActiveProfiles("disable-components") - public class DisableAllPredicatesByProperty { - - @Autowired(required = false) - private List> predicates; - - @Test - public void shouldDisableAllBuiltInPredicates() { - assertThat(predicates).isNull(); - } - - } - -} 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 deleted file mode 100644 index 6ec1054a..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/conditional/OnEnabledComponentTests.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright 2013-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.config.conditional; - -import java.util.Collections; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.boot.autoconfigure.condition.ConditionOutcome; -import org.springframework.context.annotation.ConditionContext; -import org.springframework.core.type.AnnotatedTypeMetadata; -import org.springframework.mock.env.MockEnvironment; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -class OnEnabledComponentTests { - - private OnEnabledComponent onEnabledComponent; - - private MockEnvironment environment; - - private ConditionContext conditionContext; - - @BeforeEach - void setUp() { - this.onEnabledComponent = createOnEnabledComponent("test-class"); - this.environment = new MockEnvironment(); - this.conditionContext = mock(ConditionContext.class); - } - - @Test - public void shouldMatchComponent() { - when(conditionContext.getEnvironment()).thenReturn(environment); - - ConditionOutcome outcome = onEnabledComponent.getMatchOutcome(conditionContext, - mockMetaData(EnabledComponent.class)); - - assertThat(outcome.isMatch()).isTrue(); - } - - @Test - public void shouldNotMatchDisabledComponent() { - String componentName = "disabled-component"; - this.onEnabledComponent = createOnEnabledComponent(componentName); - when(conditionContext.getEnvironment()).thenReturn(environment); - environment.setProperty("spring.cloud.gateway." + componentName + ".enabled", "false"); - - ConditionOutcome outcome = onEnabledComponent.getMatchOutcome(conditionContext, - mockMetaData(DisabledComponent.class)); - - assertThat(outcome.isMatch()).isFalse(); - assertThat(outcome.getMessage()).contains("DisabledComponent").contains("bean is not available"); - } - - private AnnotatedTypeMetadata mockMetaData(Class value) { - AnnotatedTypeMetadata metadata = mock(AnnotatedTypeMetadata.class); - when(metadata.getAnnotationAttributes(eq(ConditionalOnEnabledFilter.class.getName()))) - .thenReturn(Collections.singletonMap("value", value)); - return metadata; - } - - private OnEnabledComponent createOnEnabledComponent(String componentName) { - return new OnEnabledComponent() { - @Override - protected String normalizeComponentName(Class componentClass) { - return componentName; - } - - @Override - protected Class annotationClass() { - return ConditionalOnEnabledFilter.class; - } - - @Override - protected Class defaultValueClass() { - return Object.class; - } - }; - } - - protected static class EnabledComponent { - - } - - protected static class DisabledComponent { - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/conditional/OnEnabledFilterTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/conditional/OnEnabledFilterTests.java deleted file mode 100644 index 3b1e2e8b..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/conditional/OnEnabledFilterTests.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2013-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.config.conditional; - -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.cloud.gateway.filter.factory.AddRequestHeaderGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.DedupeResponseHeaderGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.FallbackHeadersGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.GatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.MapRequestHeaderGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.SpringCloudCircuitBreakerResilience4JFilterFactory; - -import static org.assertj.core.api.Assertions.assertThat; - -class OnEnabledFilterTests { - - private OnEnabledFilter onEnabledFilter; - - @BeforeEach - void setUp() { - this.onEnabledFilter = new OnEnabledFilter(); - } - - @Test - void shouldNormalizeFiltersNames() { - List>> predicates = Arrays.asList( - AddRequestHeaderGatewayFilterFactory.class, DedupeResponseHeaderGatewayFilterFactory.class, - FallbackHeadersGatewayFilterFactory.class, MapRequestHeaderGatewayFilterFactory.class, - SpringCloudCircuitBreakerResilience4JFilterFactory.class); - - List resultNames = predicates.stream().map(onEnabledFilter::normalizeComponentName) - .collect(Collectors.toList()); - - List expectedNames = Stream.of("add-request-header", "dedupe-response-header", "fallback-headers", - "map-request-header", "circuit-breaker").map(s -> "filter." + s).collect(Collectors.toList()); - - assertThat(resultNames).isEqualTo(expectedNames); - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/conditional/OnEnabledGlobalFilterTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/conditional/OnEnabledGlobalFilterTests.java deleted file mode 100644 index 3c7bd025..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/conditional/OnEnabledGlobalFilterTests.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2013-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.config.conditional; - -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.cloud.gateway.filter.AdaptCachedBodyGlobalFilter; -import org.springframework.cloud.gateway.filter.ForwardPathFilter; -import org.springframework.cloud.gateway.filter.GlobalFilter; -import org.springframework.cloud.gateway.filter.WebsocketRoutingFilter; - -import static org.assertj.core.api.Assertions.assertThat; - -class OnEnabledGlobalFilterTests { - - private OnEnabledGlobalFilter onEnabledGlobalFilter; - - @BeforeEach - void setUp() { - this.onEnabledGlobalFilter = new OnEnabledGlobalFilter(); - } - - @Test - void shouldNormalizeGlobalFiltersNames() { - List> predicates = Arrays.asList(ForwardPathFilter.class, - AdaptCachedBodyGlobalFilter.class, WebsocketRoutingFilter.class); - - List resultNames = predicates.stream().map(onEnabledGlobalFilter::normalizeComponentName) - .collect(Collectors.toList()); - - List expectedNames = Stream.of("forward-path", "adapt-cached-body", "websocket-routing") - .map(s -> "global-filter." + s).collect(Collectors.toList()); - - assertThat(resultNames).isEqualTo(expectedNames); - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/conditional/OnEnabledPredicateTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/conditional/OnEnabledPredicateTests.java deleted file mode 100644 index 2d3e5591..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/conditional/OnEnabledPredicateTests.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2013-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.config.conditional; - -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.cloud.gateway.handler.predicate.AfterRoutePredicateFactory; -import org.springframework.cloud.gateway.handler.predicate.CloudFoundryRouteServiceRoutePredicateFactory; -import org.springframework.cloud.gateway.handler.predicate.ReadBodyRoutePredicateFactory; -import org.springframework.cloud.gateway.handler.predicate.RemoteAddrRoutePredicateFactory; -import org.springframework.cloud.gateway.handler.predicate.RoutePredicateFactory; - -import static org.assertj.core.api.Assertions.assertThat; - -class OnEnabledPredicateTests { - - private OnEnabledPredicate onEnabledPredicate; - - @BeforeEach - void setUp() { - this.onEnabledPredicate = new OnEnabledPredicate(); - } - - @Test - void shouldNormalizePredicatesNames() { - List>> predicates = Arrays.asList(AfterRoutePredicateFactory.class, - CloudFoundryRouteServiceRoutePredicateFactory.class, ReadBodyRoutePredicateFactory.class, - RemoteAddrRoutePredicateFactory.class); - - List resultNames = predicates.stream().map(onEnabledPredicate::normalizeComponentName) - .collect(Collectors.toList()); - - List expectedNames = Stream.of("after", "cloud-foundry-route-service", "read-body", "remote-addr") - .map(s -> "predicate." + s).collect(Collectors.toList()); - - assertThat(resultNames).isEqualTo(expectedNames); - } - -} 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 deleted file mode 100644 index d8332723..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/cors/CorsGlobalTests.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright 2013-2020 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.cors; - -import java.util.Arrays; - -import org.junit.jupiter.api.Test; -import org.junit.runner.RunWith; -import reactor.core.publisher.Mono; - -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.filter.cors.CorsGatewayFilterApplicationListener; -import org.springframework.cloud.gateway.test.BaseWebClientTests; -import org.springframework.context.annotation.Import; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.web.reactive.function.client.ClientResponse; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; - -@SpringBootTest(webEnvironment = RANDOM_PORT) -@DirtiesContext -@ActiveProfiles(profiles = "cors-global-config") -public class CorsGlobalTests extends BaseWebClientTests { - - @Test - public void testPreFlightCorsRequest() { - ClientResponse clientResponse = webClient.options().uri("/abc/123/function").header("Origin", "domain.com") - .header("Access-Control-Request-Method", "GET").exchangeToMono(Mono::just).block(); - HttpHeaders asHttpHeaders = clientResponse.headers().asHttpHeaders(); - Mono bodyToMono = clientResponse.bodyToMono(String.class); - // pre-flight request shouldn't return the response body - assertThat(bodyToMono.block()).isNull(); - assertThat(asHttpHeaders.getAccessControlAllowOrigin()) - .as("Missing header value in response: " + HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN).isEqualTo("*"); - assertThat(asHttpHeaders.getAccessControlAllowMethods()) - .as("Missing header value in response: " + HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS) - .isEqualTo(Arrays.asList(new HttpMethod[] { HttpMethod.GET })); - assertThat(clientResponse.statusCode()).as("Pre Flight call failed.").isEqualTo(HttpStatus.OK); - } - - @Test - public void testCorsRequest() { - ResponseEntity response = webClient.get().uri("/abc/123/function").header("Origin", "domain.com") - .header(HttpHeaders.HOST, "www.path.org").retrieve().toEntity(String.class).block(); - assertThat(response).isNotNull(); - assertThat(response.getBody()).isNotNull(); - assertThat(response.getHeaders().getAccessControlAllowOrigin()) - .as("Missing header value in response: " + HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN).isEqualTo("*"); - assertThat(response.getStatusCode()).as("CORS request failed.").isEqualTo(HttpStatus.OK); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(DefaultTestConfig.class) - public static class TestConfig { - - } - - @RunWith(SpringRunner.class) - @SpringBootTest(classes = TestConfig.class, properties = "spring.cloud.gateway.globalcors.enabled=false") - public static class DisabledByProperty { - - @Autowired(required = false) - private CorsGatewayFilterApplicationListener listener; - - @org.junit.Test - public void corsGatewayFilterApplicationListenerIsMissing() { - assertThat(listener).isNull(); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/cors/CorsPerRouteTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/cors/CorsPerRouteTests.java deleted file mode 100644 index 5f186903..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/cors/CorsPerRouteTests.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright 2013-2020 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.cors; - -import java.util.Map; - -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.gateway.route.RouteLocator; -import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; -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.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.ActiveProfiles; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; -import static org.springframework.http.HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS; -import static org.springframework.http.HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN; -import static org.springframework.http.HttpHeaders.ACCESS_CONTROL_MAX_AGE; - -@SpringBootTest(webEnvironment = RANDOM_PORT) -@DirtiesContext -@ActiveProfiles(profiles = "cors-per-route-config") -public class CorsPerRouteTests extends BaseWebClientTests { - - @Test - public void testPreFlightCorsRequest() { - testClient.options().uri("/abc").header("Origin", "domain.com").header("Access-Control-Request-Method", "GET") - .exchange().expectBody(Map.class).consumeWith(result -> { - assertThat(result.getResponseBody()).isNull(); - assertThat(result.getStatus()).isEqualTo(HttpStatus.OK); - - HttpHeaders responseHeaders = result.getResponseHeaders(); - assertThat(responseHeaders.getAccessControlAllowOrigin()) - .as(missingHeader(ACCESS_CONTROL_ALLOW_ORIGIN)).isEqualTo("domain.com"); - assertThat(responseHeaders.getAccessControlAllowMethods()) - .as(missingHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS)) - .containsExactlyInAnyOrder(HttpMethod.GET, HttpMethod.POST); - assertThat(responseHeaders.getAccessControlMaxAge()).as(missingHeader(ACCESS_CONTROL_MAX_AGE)) - .isEqualTo(30L); - assertThat(responseHeaders.getAccessControlAllowCredentials()) - .as(missingHeader(ACCESS_CONTROL_ALLOW_CREDENTIALS)).isEqualTo(true); - }); - } - - @Test - public void testPreFlightCorsRequestJavaConfig() { - testClient.options().uri("/route-test").header("Origin", "another-domain.com") - .header("Host", "www.javaconfhost.org").header("Access-Control-Request-Method", "GET").exchange() - .expectBody(Map.class).consumeWith(result -> { - assertThat(result.getResponseBody()).isNull(); - assertThat(result.getStatus()).isEqualTo(HttpStatus.OK); - - HttpHeaders responseHeaders = result.getResponseHeaders(); - assertThat(responseHeaders.getAccessControlAllowOrigin()) - .as(missingHeader(ACCESS_CONTROL_ALLOW_ORIGIN)).isEqualTo("another-domain.com"); - assertThat(responseHeaders.getAccessControlAllowMethods()) - .as(missingHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS)) - .containsExactlyInAnyOrder(HttpMethod.GET); - assertThat(responseHeaders.getAccessControlMaxAge()).as(missingHeader(ACCESS_CONTROL_MAX_AGE)) - .isEqualTo(50L); - }); - } - - @Test - public void testPreFlightForbiddenCorsRequest() { - testClient.get().uri("/cors").header("Origin", "domain.com").header("Access-Control-Request-Method", "GET") - .exchange().expectBody(Map.class).consumeWith(result -> { - assertThat(result.getResponseBody()).isNull(); - assertThat(result.getStatus()).isEqualTo(HttpStatus.FORBIDDEN); - }); - } - - @Test - public void testCorsValidatedRequest() { - testClient.get().uri("/cors/status/201").header("Origin", "https://test.com").exchange() - .expectBody(String.class).consumeWith(result -> { - assertThat(result.getResponseBody()).endsWith("201"); - assertThat(result.getStatus()).isEqualTo(HttpStatus.CREATED); - }); - } - - private String missingHeader(String accessControlAllowOrigin) { - return "Missing header value in response: " + accessControlAllowOrigin; - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(DefaultTestConfig.class) - public static class TestConfig { - - @Value("${test.uri}") - String uri; - - @Bean - public RouteLocator testRouteLocator(RouteLocatorBuilder builder) { - return builder.routes() - .route("cors_route_java_test", - r -> r.host("*.javaconfhost.org").and().path("/route-test/**") - .filters(f -> f.stripPrefix(1).prefixPath("/httpbin")) - .metadata(Map.of("cors", Map.of("allowedOrigins", "another-domain.com", - "allowedMethods", HttpMethod.GET.name(), "maxAge", 50))) - .uri(uri)) - .build(); - } - - } - -} 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 deleted file mode 100644 index f87da623..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/cors/SimpleUrlHandlerCorsTests.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2013-2020 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.cors; - -import java.util.List; - -import org.junit.jupiter.api.Test; -import reactor.core.publisher.Mono; - -import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.AutoConfigureBefore; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.gateway.config.GatewayAutoConfiguration; -import org.springframework.cloud.gateway.test.BaseWebClientTests; -import org.springframework.context.annotation.Import; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.http.HttpStatusCode; -import org.springframework.http.ResponseEntity; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.ActiveProfiles; - -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") -@DirtiesContext -@ActiveProfiles("request-header-web-filter") -public class SimpleUrlHandlerCorsTests extends BaseWebClientTests { - - @Test - public void testPreFlightCorsRequestNotHandledByGW() { - ResponseEntity response = webClient.options().uri("/abc/123/function").header("Origin", "domain.com") - .header("Access-Control-Request-Method", "GET").retrieve().toEntity(String.class).block(); - HttpHeaders asHttpHeaders = response.getHeaders(); - // pre-flight request shouldn't return the response body - assertThat(response.getBody()).isNull(); - assertThat(asHttpHeaders.getAccessControlAllowOrigin()) - .as("Missing header value in response: " + HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN).isEqualTo("*"); - assertThat(asHttpHeaders.getAccessControlAllowMethods()) - .as("Missing header value in response: " + HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS) - .isEqualTo(List.of(HttpMethod.GET)); - assertThat(response.getStatusCode()).as("Pre Flight call failed.").isEqualTo(HttpStatus.OK); - } - - @Test - public void testCorsRequestNotHandledByGW() { - ResponseEntity responseEntity = webClient.get().uri("/abc/123/function").header("Origin", "domain.com") - .header(HttpHeaders.HOST, "www.path.org").retrieve() - .onStatus(HttpStatusCode::isError, t -> Mono.empty()).toEntity(String.class).block(); - HttpHeaders asHttpHeaders = responseEntity.getHeaders(); - assertThat(responseEntity.getBody()).isNotNull(); - assertThat(asHttpHeaders.getAccessControlAllowOrigin()) - .as("Missing header value in response: " + HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN).isEqualTo("*"); - assertThat(responseEntity.getStatusCode()).as("CORS request failed.").isEqualTo(HttpStatus.NOT_FOUND); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @AutoConfigureBefore(GatewayAutoConfiguration.class) - @Import(DefaultTestConfig.class) - public static class TestConfig { - - } - -} 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 deleted file mode 100644 index efed18ad..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/discovery/DiscoveryClientRouteDefinitionLocatorIntegrationTests.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright 2013-2020 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.discovery; - -import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; - -import org.junit.jupiter.api.Test; -import reactor.core.publisher.Flux; - -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.client.DefaultServiceInstance; -import org.springframework.cloud.client.ServiceInstance; -import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient; -import org.springframework.cloud.client.discovery.event.HeartbeatEvent; -import org.springframework.cloud.gateway.route.Route; -import org.springframework.cloud.gateway.route.RouteLocator; -import org.springframework.context.ApplicationEventPublisher; -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__" }) -public class DiscoveryClientRouteDefinitionLocatorIntegrationTests { - - @Autowired - private RouteLocator routeLocator; - - @Autowired - private ApplicationEventPublisher publisher; - - @Autowired - private TestDiscoveryClient discoveryClient; - - @Test - public void newServiceAddsRoute() throws Exception { - List routes = routeLocator.getRoutes().filter(route -> route.getId().startsWith("test__")).collectList() - .block(); - assertThat(routes).hasSize(1); - - discoveryClient.multiple(); - - publisher.publishEvent(new HeartbeatEvent(this, 1L)); - - Thread.sleep(2000); - - routes = routeLocator.getRoutes().filter(route -> route.getId().startsWith("test__")).collectList().block(); - assertThat(routes).hasSize(2); - } - - @SpringBootConfiguration - @EnableAutoConfiguration - protected static class Config { - - @Bean - TestDiscoveryClient discoveryClient() { - return new TestDiscoveryClient(); - } - - } - - private static class TestDiscoveryClient implements ReactiveDiscoveryClient { - - AtomicBoolean single = new AtomicBoolean(true); - - DefaultServiceInstance instance1 = new DefaultServiceInstance("service1_1", "service1", "localhost", 8001, - false); - - DefaultServiceInstance instance2 = new DefaultServiceInstance("service2_1", "service2", "localhost", 8001, - false); - - public void multiple() { - single.set(false); - } - - @Override - public String description() { - return null; - } - - @Override - public Flux getInstances(String serviceId) { - if (serviceId.equals("service1")) { - return Flux.just(instance1); - } - if (serviceId.equals("service2")) { - return Flux.just(instance2); - } - return Flux.empty(); - } - - @Override - public Flux getServices() { - if (single.get()) { - return Flux.just("service1"); - } - return Flux.just("service1", "service2"); - } - - } - -} 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 deleted file mode 100644 index daf352f8..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/discovery/DiscoveryClientRouteDefinitionLocatorTests.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright 2013-2020 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.discovery; - -import java.util.Collections; -import java.util.List; -import java.util.Map; - -import org.junit.jupiter.api.Test; -import reactor.core.publisher.Flux; - -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.client.DefaultServiceInstance; -import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient; -import org.springframework.cloud.gateway.filter.FilterDefinition; -import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition; -import org.springframework.cloud.gateway.route.RouteDefinition; -import org.springframework.context.annotation.Bean; - -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.filter.factory.RewritePathGatewayFilterFactory.REGEXP_KEY; -import static org.springframework.cloud.gateway.filter.factory.RewritePathGatewayFilterFactory.REPLACEMENT_KEY; -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" - /* - * "spring.cloud.gateway.discovery.locator.predicates[0].name=Path", - * "spring.cloud.gateway.discovery.locator.predicates[0].args[pattern]='/'+serviceId.toLowerCase()+'/**'", - * "spring.cloud.gateway.discovery.locator.filters[0].name=RewritePath", - * "spring.cloud.gateway.discovery.locator.filters[0].args[regexp]='/' + serviceId.toLowerCase() + '/(?.*)'" - * , - * "spring.cloud.gateway.discovery.locator.filters[0].args[replacement]='/$\\\\{remaining}'", - */ - }) -public class DiscoveryClientRouteDefinitionLocatorTests { - - @Autowired(required = false) - private DiscoveryClientRouteDefinitionLocator locator; - - @Test - public void includeExpressionWorks() { - assertThat(locator).as("DiscoveryClientRouteDefinitionLocator was null").isNotNull(); - - List definitions = locator.getRouteDefinitions().collectList().block(); - assertThat(definitions).hasSize(2); - - RouteDefinition definition = definitions.get(0); - assertThat(definition.getId()).isEqualTo("testedge_SERVICE1"); - assertThat(definition.getUri()).hasScheme("lb").hasHost("SERVICE1"); - assertThat(definition.getMetadata()).containsEntry("edge", "true"); - - assertThat(definition.getPredicates()).hasSize(1); - PredicateDefinition predicate = definition.getPredicates().get(0); - assertThat(predicate.getName()).isEqualTo("Path"); - assertThat(predicate.getArgs()).hasSize(1).containsEntry(PATTERN_KEY, "/service1/**"); - - assertThat(definition.getFilters()).hasSize(1); - FilterDefinition filter = definition.getFilters().get(0); - assertThat(filter.getName()).isEqualTo("RewritePath"); - assertThat(filter.getArgs()).hasSize(2).containsEntry(REGEXP_KEY, "/service1/?(?.*)") - .containsEntry(REPLACEMENT_KEY, "/${remaining}"); - - RouteDefinition definition2 = definitions.get(1); - assertThat(definition2.getId()).isEqualTo("testedge_service3"); - assertThat(definition2.getUri()).hasScheme("lb").hasHost("service3"); - assertThat(definition2.getMetadata()).containsEntry("edge", "true"); - - assertThat(definition2.getPredicates()).hasSize(1); - PredicateDefinition predicate2 = definition2.getPredicates().get(0); - assertThat(predicate2.getName()).isEqualTo("Path"); - assertThat(predicate2.getArgs()).hasSize(1).containsEntry(PATTERN_KEY, "/service3/**"); - - assertThat(definition.getFilters()).hasSize(1); - FilterDefinition filter2 = definition2.getFilters().get(0); - assertThat(filter2.getName()).isEqualTo("RewritePath"); - assertThat(filter2.getArgs()).hasSize(2).containsEntry(REGEXP_KEY, "/service3/?(?.*)") - .containsEntry(REPLACEMENT_KEY, "/${remaining}"); - } - - @SpringBootConfiguration - @EnableAutoConfiguration - protected static class Config { - - @Bean - ReactiveDiscoveryClient discoveryClient() { - ReactiveDiscoveryClient discoveryClient = mock(ReactiveDiscoveryClient.class); - when(discoveryClient.getServices()).thenReturn(Flux.just("SERVICE1", "Service2", "service3")); - whenInstance(discoveryClient, "SERVICE1", Collections.singletonMap("edge", "true")); - whenInstance(discoveryClient, "Service2", Collections.emptyMap()); - whenInstance(discoveryClient, "service3", Collections.singletonMap("edge", "true")); - return discoveryClient; - } - - private void whenInstance(ReactiveDiscoveryClient discoveryClient, String serviceId, - Map metadata) { - if ("SERVICE1".equals(serviceId) || "Service2".equals(serviceId)) { - DefaultServiceInstance instance1 = new DefaultServiceInstance(serviceId + "8001", serviceId, - "localhost", 8001, false, metadata); - when(discoveryClient.getInstances(serviceId)).thenReturn(Flux.just(instance1)); - } - else if ("service3".equalsIgnoreCase(serviceId)) { - // given 3 instances, only one routedefinition should be created - DefaultServiceInstance instance2 = new DefaultServiceInstance(serviceId + "8002", serviceId, - "localhost", 8002, false, Collections.emptyMap()); - DefaultServiceInstance instance3 = new DefaultServiceInstance(serviceId + "8003", serviceId, - "localhost", 8003, false, metadata); - DefaultServiceInstance instance4 = new DefaultServiceInstance(serviceId + "8004", serviceId, - "localhost", 8004, false, metadata); - when(discoveryClient.getInstances(serviceId)).thenReturn(Flux.just(instance2, instance3, instance4)); - } - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/discovery/GatewayDiscoveryClientAutoConfigurationTest.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/discovery/GatewayDiscoveryClientAutoConfigurationTest.java deleted file mode 100644 index e37051c5..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/discovery/GatewayDiscoveryClientAutoConfigurationTest.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2013-2020 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.discovery; - -import java.util.Objects; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.cloud.client.ServiceInstance; -import org.springframework.cloud.gateway.filter.FilterDefinition; -import org.springframework.expression.spel.standard.SpelExpressionParser; -import org.springframework.expression.spel.support.SimpleEvaluationContext; - -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.filter.factory.RewritePathGatewayFilterFactory.REGEXP_KEY; -import static org.springframework.cloud.gateway.filter.factory.RewritePathGatewayFilterFactory.REPLACEMENT_KEY; - -public class GatewayDiscoveryClientAutoConfigurationTest { - - private static final String SERVICE_ID = "foo"; - - private static final String BASE_URI = "/" + SERVICE_ID; - - private static final SpelExpressionParser PARSER = new SpelExpressionParser(); - - private static final SimpleEvaluationContext CONTEXT = SimpleEvaluationContext.forReadOnlyDataBinding() - .withInstanceMethods().build(); - - private static final FilterDefinition DEFAULT_FILTER = GatewayDiscoveryClientAutoConfiguration.initFilters().get(0); - - private ServiceInstance serviceInstance; - - @BeforeEach - public void buildServiceInstance() { - this.serviceInstance = mock(ServiceInstance.class); - when(this.serviceInstance.getServiceId()).thenReturn(SERVICE_ID); - } - - @Test - public void defaultRewritePathShouldHandleEmptyRemainingWithoutSlash() { - String expectedRemotePath = "/"; - - final String result = replace(BASE_URI); - - assertThat(result).isEqualTo(expectedRemotePath); - } - - @Test - public void defaultRewritePathShouldHandleEmptyRemainingWithSlash() { - String extraUri = "/"; - - final String result = replace(BASE_URI + extraUri); - - assertThat(result).isEqualTo(extraUri); - } - - @Test - public void defaultRewritePathShouldHandleNonEmptyRemainingPath() { - String extraUri = "/some/additional/uri"; - - final String result = replace(BASE_URI + extraUri); - - assertThat(result).isEqualTo(extraUri); - } - - private String replace(String enteringPath) { - return enteringPath.replaceAll(evaluateExpression(DEFAULT_FILTER.getArgs().get(REGEXP_KEY)), - evaluateExpression(DEFAULT_FILTER.getArgs().get(REPLACEMENT_KEY))); - } - - private String evaluateExpression(String expression) { - return Objects - .requireNonNull(PARSER.parseExpression(expression).getValue(CONTEXT, serviceInstance, String.class)); - } - -} 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 deleted file mode 100644 index ef03d723..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/discovery/ReactiveGatewayDiscoveryClientAutoConfigurationTests.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2013-2020 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.discovery; - -import java.util.List; - -import org.junit.jupiter.api.Nested; -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.config.GatewayLoadBalancerProperties; -import org.springframework.cloud.gateway.route.RouteDefinition; - -import static org.assertj.core.api.Assertions.assertThat; - -//@ExtendWith(SpringExtension.class) -public class ReactiveGatewayDiscoveryClientAutoConfigurationTests { - - @Nested - @SpringBootTest(classes = Config.class, - properties = { "spring.cloud.gateway.discovery.locator.enabled=true", - "spring.cloud.gateway.loadbalancer.use404=true", - "spring.cloud.discovery.client.simple.instances.service[0].uri=https://service1:443" }) - public class EnabledByProperty { - - @Autowired(required = false) - private DiscoveryClientRouteDefinitionLocator locator; - - @Autowired(required = false) - private GatewayLoadBalancerProperties properties; - - @Test - public void routeLocatorBeanExists() { - assertThat(locator).as("DiscoveryClientRouteDefinitionLocator was null").isNotNull(); - List definitions = locator.getRouteDefinitions().collectList().block(); - assertThat(definitions).hasSize(1); - } - - @Test - public void use404() { - assertThat(properties.isUse404()).isTrue(); - } - - } - - @Nested - @SpringBootTest(classes = Config.class) - public class DisabledByDefault { - - @Autowired(required = false) - private DiscoveryClientRouteDefinitionLocator locator; - - @Test - public void routeLocatorBeanMissing() { - assertThat(locator).as("DiscoveryClientRouteDefinitionLocator exists").isNull(); - } - - } - - @SpringBootConfiguration - @EnableAutoConfiguration - protected static class Config { - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/ForwardRoutingFilterStaticIntegrationTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/ForwardRoutingFilterStaticIntegrationTests.java deleted file mode 100644 index e7adf9b9..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/ForwardRoutingFilterStaticIntegrationTests.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2013-2020 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.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.cloud.gateway.test.BaseWebClientTests; -import org.springframework.context.annotation.Import; -import org.springframework.test.context.ActiveProfiles; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; - -@SpringBootTest(webEnvironment = RANDOM_PORT) -@ActiveProfiles("forwardstatic") -public class ForwardRoutingFilterStaticIntegrationTests extends BaseWebClientTests { - - @Test - public void gatewayRequestsMeterFilterHasTags() { - testClient.get().uri("/mydocs").exchange().expectStatus().isOk().expectBody(String.class) - .consumeWith(result -> { - assertThat(result.getResponseBody()).contains("Docs 123"); - }); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(DefaultTestConfig.class) - public static class CustomConfig { - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/ForwardRoutingFilterTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/ForwardRoutingFilterTests.java deleted file mode 100644 index ab97965c..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/ForwardRoutingFilterTests.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright 2013-2020 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.filter; - -import java.net.URI; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.ArgumentCaptor; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.mockito.junit.jupiter.MockitoSettings; -import org.mockito.quality.Strictness; - -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.core.Ordered; -import org.springframework.mock.http.server.reactive.MockServerHttpRequest; -import org.springframework.mock.web.server.MockServerWebExchange; -import org.springframework.web.reactive.DispatcherHandler; -import org.springframework.web.server.ServerWebExchange; -import org.springframework.web.util.UriComponentsBuilder; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ALREADY_ROUTED_ATTR; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR; - -/** - * @author Arjun Curat - */ - -@ExtendWith(MockitoExtension.class) -@MockitoSettings(strictness = Strictness.LENIENT) -public class ForwardRoutingFilterTests { - - private ServerWebExchange exchange; - - @Mock - private GatewayFilterChain chain; - - @Mock - private ObjectProvider objectProvider; - - @Mock - private DispatcherHandler dispatcherHandler; - - @InjectMocks - private ForwardRoutingFilter forwardRoutingFilter; - - @BeforeEach - public void setup() { - exchange = MockServerWebExchange.from(MockServerHttpRequest.get("localendpoint").build()); - when(objectProvider.getIfAvailable()).thenReturn(this.dispatcherHandler); - } - - @Test - public void shouldNotFilterWhenGatewayRequestUrlSchemeIsNotForward() { - URI uri = UriComponentsBuilder.fromUriString("https://endpoint").build().toUri(); - exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, uri); - forwardRoutingFilter.filter(exchange, chain); - - verifyNoInteractions(dispatcherHandler); - verify(chain).filter(exchange); - verifyNoMoreInteractions(chain); - } - - @Test - public void shouldFilterWhenGatewayRequestUrlSchemeIsForward() { - URI uri = UriComponentsBuilder.fromUriString("forward://endpoint").build().toUri(); - exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, uri); - - assertThat(exchange.getAttributes().get(GATEWAY_ALREADY_ROUTED_ATTR)).isNull(); - - forwardRoutingFilter.filter(exchange, chain); - - verifyNoMoreInteractions(chain); - verify(dispatcherHandler).handle(exchange); - - assertThat(exchange.getAttributes().get(GATEWAY_ALREADY_ROUTED_ATTR)).isNull(); - } - - @Test - public void shouldFilterAndKeepHostPathAsSpecified() { - - URI uri = UriComponentsBuilder.fromUriString("forward://host/outage").build().toUri(); - exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, uri); - - ArgumentCaptor captor = ArgumentCaptor.forClass(ServerWebExchange.class); - - forwardRoutingFilter.filter(exchange, chain); - - verify(dispatcherHandler).handle(captor.capture()); - - assertThat(exchange.getAttributes().get(GATEWAY_ALREADY_ROUTED_ATTR)).isNull(); - - ServerWebExchange webExchange = captor.getValue(); - - URI forwardedUrl = webExchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR); - - assertThat(forwardedUrl).hasScheme("forward").hasHost("host").hasPath("/outage"); - } - - @Test - public void shouldNotFilterWhenGatewayRequestUrlSchemeIsForwardButAlreadyRouted() { - URI uri = UriComponentsBuilder.fromUriString("forward://host").build().toUri(); - exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, uri); - exchange.getAttributes().put(GATEWAY_ALREADY_ROUTED_ATTR, true); - - forwardRoutingFilter.filter(exchange, chain); - - verifyNoInteractions(dispatcherHandler); - verify(chain).filter(exchange); - verifyNoMoreInteractions(chain); - } - - @Test - public void orderIsLowestPrecedence() { - assertThat(forwardRoutingFilter.getOrder()).isEqualTo(Ordered.LOWEST_PRECEDENCE); - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/GatewayMetricsFilterCustomTagsTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/GatewayMetricsFilterCustomTagsTests.java deleted file mode 100644 index 31b705c4..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/GatewayMetricsFilterCustomTagsTests.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2013-2020 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.filter; - -import io.micrometer.core.instrument.MeterRegistry; -import io.micrometer.core.instrument.Tags; -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.gateway.support.tagsprovider.GatewayTagsProvider; -import org.springframework.cloud.gateway.test.BaseWebClientTests; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.test.annotation.DirtiesContext; - -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.config.GatewayMetricsProperties.DEFAULT_PREFIX; - -/** - * @author Ingyu Hwang - */ -@SpringBootTest(webEnvironment = RANDOM_PORT) -@DirtiesContext -class GatewayMetricsFilterCustomTagsTests extends BaseWebClientTests { - - private static final String REQUEST_METRICS_NAME = DEFAULT_PREFIX + ".requests"; - - @Autowired - private MeterRegistry meterRegistry; - - @Value("${test.uri}") - private String testUri; - - @Test - void gatewayRequestsMeterFilterHasCustomTags() { - testClient.get().uri("/headers").exchange().expectStatus().isOk(); - - // default tags - assertMetricsContainsTag("outcome", HttpStatus.Series.SUCCESSFUL.name()); - assertMetricsContainsTag("status", HttpStatus.OK.name()); - assertMetricsContainsTag("httpStatusCode", String.valueOf(HttpStatus.OK.value())); - assertMetricsContainsTag("httpMethod", HttpMethod.GET.toString()); - assertMetricsContainsTag("routeId", "default_path_to_httpbin"); - assertMetricsContainsTag("routeUri", testUri); - - // custom tags - assertMetricsContainsTag("custom1", "tag1"); - assertMetricsContainsTag("custom2", "tag2"); - } - - private void assertMetricsContainsTag(String tagKey, String tagValue) { - assertThat(this.meterRegistry.get(REQUEST_METRICS_NAME).tag(tagKey, tagValue).timer().count()).isEqualTo(1); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(DefaultTestConfig.class) - static class CustomConfig { - - @Bean - GatewayTagsProvider customGatewayTagsProvider() { - return exchange -> Tags.of("custom1", "tag1", "custom2", "tag2"); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/GatewayMetricsFilterTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/GatewayMetricsFilterTests.java deleted file mode 100644 index cf927f3b..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/GatewayMetricsFilterTests.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright 2013-2020 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.filter; - -import io.micrometer.core.instrument.MeterRegistry; -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.web.client.TestRestTemplate; -import org.springframework.cloud.gateway.route.RouteLocator; -import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; -import org.springframework.cloud.gateway.test.BaseWebClientTests; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -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.config.GatewayMetricsProperties.DEFAULT_PREFIX; - -@SpringBootTest(webEnvironment = RANDOM_PORT) -@DirtiesContext -public class GatewayMetricsFilterTests extends BaseWebClientTests { - - private static final String REQUEST_METRICS_NAME = DEFAULT_PREFIX + ".requests"; - - @Autowired - private MeterRegistry meterRegistry; - - @Value("${test.uri}") - private String testUri; - - @Test - public void gatewayRequestsMeterFilterHasTags() { - testClient.get().uri("/headers").exchange().expectStatus().isOk(); - assertMetricsContainsTag("outcome", HttpStatus.Series.SUCCESSFUL.name()); - assertMetricsContainsTag("status", HttpStatus.OK.name()); - assertMetricsContainsTag("httpStatusCode", String.valueOf(HttpStatus.OK.value())); - assertMetricsContainsTag("httpMethod", HttpMethod.GET.toString()); - assertMetricsContainsTag("routeId", "default_path_to_httpbin"); - assertMetricsContainsTag("routeUri", testUri); - } - - @Test - public void gatewayRequestsMeterFilterHasTagsForBadTargetUri() { - testClient.get().uri("/badtargeturi").exchange().expectStatus().is5xxServerError(); - assertMetricsContainsTag("outcome", HttpStatus.Series.SERVER_ERROR.name()); - assertMetricsContainsTag("status", HttpStatus.INTERNAL_SERVER_ERROR.name()); - assertMetricsContainsTag("httpStatusCode", String.valueOf(HttpStatus.INTERNAL_SERVER_ERROR.value())); - assertMetricsContainsTag("httpMethod", HttpMethod.GET.toString()); - assertMetricsContainsTag("routeId", "default_path_to_httpbin"); - assertMetricsContainsTag("routeUri", testUri); - } - - @Test - public void hasMetricsForSetStatusFilter() { - HttpHeaders headers = new HttpHeaders(); - headers.set(HttpHeaders.HOST, "www.setcustomstatusmetrics.org"); - // cannot use netty client since we cannot read custom http status - ResponseEntity response = new TestRestTemplate().exchange(baseUri + "/headers", HttpMethod.POST, - new HttpEntity<>(headers), String.class); - assertThat(response.getStatusCodeValue()).isEqualTo(432); - assertMetricsContainsTag("outcome", "CUSTOM"); - assertMetricsContainsTag("status", "432"); - assertMetricsContainsTag("routeId", "test_custom_http_status_metrics"); - assertMetricsContainsTag("routeUri", testUri); - assertMetricsContainsTag("httpStatusCode", "432"); - assertMetricsContainsTag("httpMethod", HttpMethod.POST.toString()); - } - - private void assertMetricsContainsTag(String tagKey, String tagValue) { - // @formatter:off - assertThat(this.meterRegistry.get(REQUEST_METRICS_NAME).tag(tagKey, tagValue) - .timer().count()) - .as("Wrong value for metric %s: %s", tagKey, tagValue) - .isGreaterThanOrEqualTo(1); - // @formatter:on - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @RestController - @Import(DefaultTestConfig.class) - public static class CustomConfig { - - @Value("${test.uri}") - protected String testUri; - - @Bean - public RouteLocator myRouteLocator(RouteLocatorBuilder builder) { - return builder.routes() - .route("test_custom_http_status_metrics", - r -> r.host("*.setcustomstatusmetrics.org").filters(f -> f.setStatus(432)).uri(testUri)) - .build(); - } - - @GetMapping("/httpbin/badtargeturi") - public String exception() { - throw new RuntimeException("an error"); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/LoadBalancerServiceInstanceCookieFilterTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/LoadBalancerServiceInstanceCookieFilterTests.java deleted file mode 100644 index 2821afca..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/LoadBalancerServiceInstanceCookieFilterTests.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright 2013-2021 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.filter; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; -import reactor.core.publisher.Mono; - -import org.springframework.cloud.client.DefaultServiceInstance; -import org.springframework.cloud.client.loadbalancer.DefaultResponse; -import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; -import org.springframework.http.HttpCookie; -import org.springframework.http.HttpHeaders; -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.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_LOADBALANCER_RESPONSE_ATTR; - -/** - * Tests for {@link LoadBalancerServiceInstanceCookieFilter}. - * - * @author Olga Maciaszek-Sharma - */ -class LoadBalancerServiceInstanceCookieFilterTests { - - private final LoadBalancerProperties properties = new LoadBalancerProperties(); - - private final GatewayFilterChain chain = mock(GatewayFilterChain.class); - - private final ServerWebExchange exchange = MockServerWebExchange - .from(MockServerHttpRequest.get("http://localhost/get").build()); - - private final LoadBalancerServiceInstanceCookieFilter filter = new LoadBalancerServiceInstanceCookieFilter( - properties); - - @BeforeEach - void setUp() { - properties.getStickySession().setAddServiceInstanceCookie(true); - } - - @Test - void shouldAddServiceInstanceCookieHeader() { - exchange.getAttributes().put(GATEWAY_LOADBALANCER_RESPONSE_ATTR, - new DefaultResponse(new DefaultServiceInstance("test-01", "test", "host", 8080, false))); - - ServerWebExchange filteredExchange = testFilter(exchange); - - assertThat(filteredExchange.getRequest().getHeaders().get(HttpHeaders.COOKIE)).hasSize(1); - assertThat(filteredExchange.getRequest().getHeaders().get(HttpHeaders.COOKIE)) - .containsExactly("sc-lb-instance-id=test-01"); - } - - @Test - void shouldAppendServiceInstanceCookieHeaderIfCookiesPresent() { - ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("http://localhost/get") - .cookie(new HttpCookie("testCookieName", "testCookieValue")).build()); - exchange.getAttributes().put(GATEWAY_LOADBALANCER_RESPONSE_ATTR, - new DefaultResponse(new DefaultServiceInstance("test-01", "test", "host", 8080, false))); - - ServerWebExchange filteredExchange = testFilter(exchange); - - assertThat(filteredExchange.getRequest().getHeaders().get(HttpHeaders.COOKIE)) - .containsExactly("testCookieName=testCookieValue", "sc-lb-instance-id=test-01"); - } - - @Test - void shouldContinueChainWhenNoServiceInstanceResponse() { - ServerWebExchange filteredExchange = testFilter(exchange); - - assertThat(filteredExchange.getRequest().getHeaders()).isEmpty(); - } - - @Test - void shouldContinueChainWhenNullServiceInstanceCookieName() { - exchange.getAttributes().put(GATEWAY_LOADBALANCER_RESPONSE_ATTR, - new DefaultResponse(new DefaultServiceInstance("test-01", "test", "host", 8080, false))); - properties.getStickySession().setInstanceIdCookieName(null); - - ServerWebExchange filteredExchange = testFilter(exchange); - - assertThat(filteredExchange.getRequest().getHeaders()).isEmpty(); - } - - @Test - void shouldContinueChainWhenEmptyServiceInstanceCookieName() { - exchange.getAttributes().put(GATEWAY_LOADBALANCER_RESPONSE_ATTR, - new DefaultResponse(new DefaultServiceInstance("test-01", "test", "host", 8080, false))); - properties.getStickySession().setInstanceIdCookieName(""); - - ServerWebExchange filteredExchange = testFilter(exchange); - - assertThat(filteredExchange.getRequest().getHeaders()).isEmpty(); - } - - private ServerWebExchange testFilter(ServerWebExchange exchange) { - ArgumentCaptor captor = ArgumentCaptor.forClass(ServerWebExchange.class); - when(chain.filter(captor.capture())).thenReturn(Mono.empty()); - - filter.filter(exchange, chain).block(); - verify(chain).filter(any(ServerWebExchange.class)); - verifyNoMoreInteractions(chain); - return captor.getValue(); - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/NettyRoutingFilterCompatibleTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/NettyRoutingFilterCompatibleTests.java deleted file mode 100644 index c7883099..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/NettyRoutingFilterCompatibleTests.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2013-2020 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.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.cloud.gateway.route.Route; -import org.springframework.cloud.gateway.test.BaseWebClientTests; -import org.springframework.context.annotation.Import; -import org.springframework.http.HttpStatus; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.web.server.ServerWebExchange; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; - -/** - * This test just avoid class cast exception with YAML or Properties parsing. - * - * {@link NettyRoutingFilter#getHttpClient(Route, ServerWebExchange)} - * {@link NettyRoutingFilter#getResponseTimeout(Route)} - * - * @author echooymxq - **/ -@SpringBootTest(webEnvironment = RANDOM_PORT) -@DirtiesContext -@ActiveProfiles("netty-routing-filter") -class NettyRoutingFilterCompatibleTests extends BaseWebClientTests { - - @Test - void shouldApplyConnectTimeoutPerRoute() { - assertThat(NettyRoutingFilter.getInteger("5")).isEqualTo(5); - assertThat(NettyRoutingFilter.getInteger(5)).isEqualTo(5); - } - - @Test - void getLongHandlesStringAndNumber() { - assertThat(NettyRoutingFilter.getLong("5")).isEqualTo(5); - assertThat(NettyRoutingFilter.getLong(5)).isEqualTo(5); - assertThat(NettyRoutingFilter.getLong(50000L)).isEqualTo(50000); - assertThat(NettyRoutingFilter.getLong(null)).isNull(); - assertThatThrownBy(() -> NettyRoutingFilter.getLong("notanumber")).isInstanceOf(NumberFormatException.class); - } - - @Test - void shouldApplyResponseTimeoutPerRoute() { - testClient.get().uri("/route/delay/2").exchange().expectStatus().isEqualTo(HttpStatus.GATEWAY_TIMEOUT) - .expectBody().jsonPath("$.status").isEqualTo(String.valueOf(HttpStatus.GATEWAY_TIMEOUT.value())) - .jsonPath("$.message").isEqualTo("Response took longer than timeout: PT1S"); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(DefaultTestConfig.class) - static class TestConfig { - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/NettyRoutingFilterIntegrationTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/NettyRoutingFilterIntegrationTests.java deleted file mode 100644 index e7e96113..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/NettyRoutingFilterIntegrationTests.java +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Copyright 2013-2020 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.filter; - -import org.junit.jupiter.api.Test; -import reactor.core.publisher.Mono; - -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.test.BaseWebClientTests; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; -import org.springframework.core.Ordered; -import org.springframework.core.annotation.Order; -import org.springframework.http.HttpStatus; -import org.springframework.http.server.reactive.ServerHttpResponse; -import org.springframework.http.server.reactive.ServerHttpResponseDecorator; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.web.reactive.server.WebTestClient; -import org.springframework.web.server.ServerWebExchange; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.assertj.core.data.Offset.offset; -import static org.hamcrest.Matchers.allOf; -import static org.hamcrest.Matchers.containsString; -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; - -@SpringBootTest(webEnvironment = RANDOM_PORT) -@DirtiesContext -@ActiveProfiles("netty-routing-filter") -public class NettyRoutingFilterIntegrationTests extends BaseWebClientTests { - - @Autowired - private ResponseDecoratingFilter responseDecorator; - - @Test - public void responseTimeoutWorks() { - testClient.get().uri("/delay/5").exchange().expectStatus().isEqualTo(HttpStatus.GATEWAY_TIMEOUT).expectBody() - .jsonPath("$.status").isEqualTo(String.valueOf(HttpStatus.GATEWAY_TIMEOUT.value())) - .jsonPath("$.message").isEqualTo("Response took longer than timeout: PT3S"); - } - - @Test - public void outboundHostHeaderNotOverwrittenByInbound() { - // different base url to have different host header in inbound / outbound requests - // Host: 127.0.0.1 -> request to Gateway, Host: localhost -> request from Gateway, - // resolved from lb://testservice - WebTestClient client = testClient.mutate().baseUrl("http://127.0.0.1:" + port).build(); - - client.get().uri("/headers").exchange().expectBody().jsonPath("$.headers.host").isEqualTo("localhost:" + port); - } - - @Test - public void canHandleDecoratedResponseWithNonStandardStatusValue() { - final int NON_STANDARD_STATUS = 480; - responseDecorator.decorateResponseTimes(1); - testClient.mutate().baseUrl("http://localhost:" + port).build().get().uri("/status/" + NON_STANDARD_STATUS) - .exchange().expectStatus().isEqualTo(NON_STANDARD_STATUS); - } - - @Test - public void canHandleUndecoratedResponseWithNonStandardStatusValue() { - final int NON_STANDARD_STATUS = 480; - responseDecorator.decorateResponseTimes(0); - testClient.mutate().baseUrl("http://localhost:" + port).build().get().uri("/status/" + NON_STANDARD_STATUS) - .exchange().expectStatus().isEqualTo(NON_STANDARD_STATUS); - } - - @Test - public void canHandleMultiplyDecoratedResponseWithNonStandardStatusValue() { - final int NON_STANDARD_STATUS = 142; - responseDecorator.decorateResponseTimes(14); - testClient.mutate().baseUrl("http://localhost:" + port).build().get().uri("/status/" + NON_STANDARD_STATUS) - .exchange().expectStatus().isEqualTo(NON_STANDARD_STATUS); - } - - @Test - public void shouldApplyConnectTimeoutPerRoute() { - long currentTimeMillisBeforeCall = System.currentTimeMillis(); - - testClient.get().uri("/connect/delay/2").exchange().expectStatus().isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR) - .expectBody().jsonPath("$.message") - .value(allOf(containsString("Connection refused:"), containsString(":32167"))); - - // default connect timeout is 45 sec, this test verifies that it is possible to - // reduce timeout via config - assertThat(System.currentTimeMillis() - currentTimeMillisBeforeCall).isCloseTo(5, offset(100L)); - } - - @Test - public void shouldApplyResponseTimeoutPerRoute() { - testClient.get().uri("/route/delay/2").exchange().expectStatus().isEqualTo(HttpStatus.GATEWAY_TIMEOUT) - .expectBody().jsonPath("$.status").isEqualTo(String.valueOf(HttpStatus.GATEWAY_TIMEOUT.value())) - .jsonPath("$.message").isEqualTo("Response took longer than timeout: PT1S"); - } - - @Test - public void shouldNotApplyResponseTimeoutPerRouteWhenNegativeValue() { - assertThatThrownBy(() -> { - testClient.get().uri("/disabledRoute/delay/10").exchange(); - }).isInstanceOf(IllegalStateException.class) - .hasMessageContaining("Timeout on blocking read for 5000000000 NANOSECONDS"); - } - - @Test - public void shouldApplyResponseTimeoutForPlaceholder() { - testClient.get().uri("/responseheaders/200").header("Host", "www.responsetimeoutplaceholder.org").exchange() - .expectStatus().isEqualTo(HttpStatus.OK); - } - - @Test - public void shouldApplyGlobalResponseTimeoutForInvalidRouteTimeoutValue() { - testClient.get().uri("/invalidRoute/delay/5").exchange().expectStatus().isEqualTo(HttpStatus.GATEWAY_TIMEOUT) - .expectBody().jsonPath("$.status").isEqualTo(String.valueOf(HttpStatus.GATEWAY_TIMEOUT.value())) - .jsonPath("$.message").isEqualTo("Response took longer than timeout: PT3S"); - } - - @Test - public void shouldNotApplyPerRouteTimeoutWhenItIsNotConfigured() { - testClient.get().uri("/delay/2").exchange().expectStatus().isEqualTo(HttpStatus.OK); - } - - @Test - // gh-2541 - public void shouldMergeResponseHeadersFromUpstreamWithCreatedByGateway() { - String header = "X-Test-SHOULD-MERGED-HEADER"; - String gatewayHeaderValue = "value-from-gateway"; - String upstreamHeaderValue = "value-from-upstream"; - testClient.post().uri("/responseheaders/200").header("Host", "www.mergeresponseheader.org") - .header(header, upstreamHeaderValue).exchange().expectHeader() - .valueEquals(header, upstreamHeaderValue, gatewayHeaderValue); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(DefaultTestConfig.class) - public static class TestConfig { - - @Bean - @Order(RouteToRequestUrlFilter.HIGHEST_PRECEDENCE) - public ResponseDecoratingFilter decoratingFilter() { - return new ResponseDecoratingFilter(); - } - - } - - public static final class ResponseDecoratingFilter implements GlobalFilter, Ordered { - - int decorationIterations = 1; - - public void decorateResponseTimes(int times) { - decorationIterations = times; - } - - @Override - public int getOrder() { - return RouteToRequestUrlFilter.HIGHEST_PRECEDENCE; - } - - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - ServerHttpResponse decorator = exchange.getResponse(); - for (int counter = 0; counter < decorationIterations; counter++) { - decorator = new ServerHttpResponseDecorator(decorator); - } - return chain.filter(exchange.mutate().response(decorator).build()); - } - - } - -} 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 deleted file mode 100644 index 7a2e5951..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/NettyRoutingFilterTests.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright 2013-2020 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.filter; - -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.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.gateway.route.RouteLocator; -import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; -import org.springframework.cloud.gateway.test.BaseWebClientTests; -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.test.util.TestSocketUtils; -import org.springframework.test.web.reactive.server.WebTestClient; - -import static org.assertj.core.api.Assertions.assertThat; - -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -class NettyRoutingFilterTests extends BaseWebClientTests { - - private static int port; - - @Autowired - private ApplicationContext context; - - @BeforeAll - public static void beforeAll() { - port = TestSocketUtils.findAvailableTcpPort(); - } - - @Test - @Disabled - void mockServerWorks() { - WebTestClient client = WebTestClient.bindToApplicationContext(this.context).build(); - client.get().uri("/mockexample").exchange().expectStatus().value(Matchers.lessThan(500)); - } - - @Test - // gh-2207 - void testCaseInsensitiveScheme() { - DisposableServer server = HttpServer.create().port(port).host("127.0.0.1").route( - routes -> routes.get("/issue", (request, response) -> response.sendString(Mono.just("issue2207")))) - .bindNow(); - - try { - testClient.get().uri("/issue").exchange().expectStatus().isOk().expectBody() - .consumeWith(entityExchangeResult -> { - assertThat(entityExchangeResult).isNotNull(); - assertThat(entityExchangeResult.getResponseBody()).isNotNull(); - String content = new String(entityExchangeResult.getResponseBody()); - assertThat(content).isEqualTo("issue2207"); - }); - } - finally { - server.disposeNow(); - } - } - - @SpringBootConfiguration - @EnableAutoConfiguration - @Import(PermitAllSecurityConfiguration.class) - public static class TestConfig { - - @Bean - public RouteLocator routes(RouteLocatorBuilder builder) { - 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)).build(); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/NettyWriteResponseFilterTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/NettyWriteResponseFilterTests.java deleted file mode 100644 index f9dd2749..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/NettyWriteResponseFilterTests.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2013-2020 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.filter; - -import java.nio.charset.Charset; -import java.util.ArrayList; - -import io.netty.buffer.ByteBuf; -import org.junit.jupiter.api.Test; - -import org.springframework.core.io.buffer.DataBuffer; -import org.springframework.core.io.buffer.NettyDataBufferFactory; -import org.springframework.core.io.buffer.PooledDataBuffer; -import org.springframework.mock.http.server.reactive.MockServerHttpResponse; - -import static io.netty.buffer.PooledByteBufAllocator.DEFAULT; -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Violeta Georgieva - */ -public class NettyWriteResponseFilterTests { - - @Test - public void testWrap_NettyDataBufferFactory() { - doTestWrap(new MockServerHttpResponse(new NettyDataBufferFactory(DEFAULT))); - } - - @Test - public void testWrap_DefaultDataBufferFactory() { - doTestWrap(new MockServerHttpResponse()); - } - - private void doTestWrap(MockServerHttpResponse response) { - NettyWriteResponseFilter filter = new NettyWriteResponseFilter(new ArrayList<>()); - - ByteBuf buffer = DEFAULT.buffer(); - buffer.writeCharSequence("test", Charset.defaultCharset()); - - DataBuffer result = filter.wrap(buffer, response); - - assertThat(result.toString(Charset.defaultCharset())).isEqualTo("test"); - - if (result instanceof PooledDataBuffer) { - ((PooledDataBuffer) result).release(); - } - - assertThat(buffer.refCnt()).isEqualTo(0); - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/ReactiveLoadBalancerClientFilterTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/ReactiveLoadBalancerClientFilterTests.java deleted file mode 100644 index f48ab97e..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/ReactiveLoadBalancerClientFilterTests.java +++ /dev/null @@ -1,448 +0,0 @@ -/* - * Copyright 2013-2020 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.filter; - -import java.net.URI; -import java.util.HashMap; -import java.util.LinkedHashSet; -import java.util.Map; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.ArgumentCaptor; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import reactor.core.publisher.Mono; - -import org.springframework.cloud.client.DefaultServiceInstance; -import org.springframework.cloud.client.ServiceInstance; -import org.springframework.cloud.client.loadbalancer.CompletionContext; -import org.springframework.cloud.client.loadbalancer.DefaultResponse; -import org.springframework.cloud.client.loadbalancer.EmptyResponse; -import org.springframework.cloud.client.loadbalancer.LoadBalancerLifecycle; -import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties; -import org.springframework.cloud.client.loadbalancer.Request; -import org.springframework.cloud.client.loadbalancer.RequestDataContext; -import org.springframework.cloud.client.loadbalancer.Response; -import org.springframework.cloud.gateway.config.GatewayLoadBalancerProperties; -import org.springframework.cloud.gateway.support.NotFoundException; -import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer; -import org.springframework.cloud.loadbalancer.core.RoundRobinLoadBalancer; -import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory; -import org.springframework.cloud.loadbalancer.support.ServiceInstanceListSuppliers; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.mock.http.server.reactive.MockServerHttpRequest; -import org.springframework.mock.web.server.MockServerWebExchange; -import org.springframework.web.server.ServerWebExchange; -import org.springframework.web.util.UriComponentsBuilder; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.argThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_LOADBALANCER_RESPONSE_ATTR; -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; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR; - -/** - * Tests for {@link ReactiveLoadBalancerClientFilter}. - * - * @author Spencer Gibb - * @author Tim Ysewyn - * @author Olga Maciaszek-Sharma - */ -@SuppressWarnings("UnassignedFluxMonoInstance") -@ExtendWith(MockitoExtension.class) -class ReactiveLoadBalancerClientFilterTests { - - private ServerWebExchange exchange; - - private GatewayLoadBalancerProperties properties; - - @Mock - private GatewayFilterChain chain; - - @Mock - private LoadBalancerClientFactory clientFactory; - - @Mock - private LoadBalancerProperties loadBalancerProperties; - - @InjectMocks - private ReactiveLoadBalancerClientFilter filter; - - @BeforeEach - void setup() { - properties = new GatewayLoadBalancerProperties(); - exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/mypath").build()); - } - - @Test - void shouldNotFilterWhenGatewayRequestUrlIsMissing() { - filter.filter(exchange, chain); - - verify(chain).filter(exchange); - verifyNoMoreInteractions(chain); - verifyNoInteractions(clientFactory); - } - - @Test - void shouldNotFilterWhenGatewayRequestUrlSchemeIsNotLb() { - URI uri = UriComponentsBuilder.fromUriString("http://myservice").build().toUri(); - exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, uri); - - filter.filter(exchange, chain); - - verify(chain).filter(exchange); - verifyNoMoreInteractions(chain); - verifyNoInteractions(clientFactory); - } - - @Test - void shouldThrowExceptionWhenNoServiceInstanceIsFound() { - when(clientFactory.getProperties(any())).thenReturn(loadBalancerProperties); - assertThatExceptionOfType(NotFoundException.class).isThrownBy(() -> { - URI uri = UriComponentsBuilder.fromUriString("lb://myservice").build().toUri(); - exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, uri); - - filter.filter(exchange, chain).block(); - }); - } - - @SuppressWarnings("unchecked") - @Test - void shouldFilter() { - when(clientFactory.getProperties(any())).thenReturn(loadBalancerProperties); - URI url = UriComponentsBuilder.fromUriString("lb://myservice").build().toUri(); - exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, url); - - ServiceInstance serviceInstance = new DefaultServiceInstance("myservice1", "myservice", "localhost", 8080, - true); - - RoundRobinLoadBalancer loadBalancer = new RoundRobinLoadBalancer( - ServiceInstanceListSuppliers.toProvider("myservice", serviceInstance), "myservice", -1); - when(clientFactory.getInstance("myservice", ReactorServiceInstanceLoadBalancer.class)).thenReturn(loadBalancer); - - when(chain.filter(exchange)).thenReturn(Mono.empty()); - - filter.filter(exchange, chain).block(); - - assertThat((LinkedHashSet) exchange.getAttribute(GATEWAY_ORIGINAL_REQUEST_URL_ATTR)).contains(url); - - verify(clientFactory).getInstance("myservice", ReactorServiceInstanceLoadBalancer.class); - - verify(clientFactory).getInstances("myservice", LoadBalancerLifecycle.class); - - verifyNoMoreInteractions(clientFactory); - - assertThat((URI) exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR)) - .isEqualTo(URI.create("https://localhost:8080/mypath")); - - verify(chain).filter(exchange); - verifyNoMoreInteractions(chain); - } - - @Test - void happyPath() { - when(clientFactory.getProperties(any())).thenReturn(loadBalancerProperties); - MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost/get?a=b").build(); - - URI lbUri = URI.create("lb://service1?a=b"); - ServerWebExchange webExchange = testFilter(request, lbUri); - URI uri = webExchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR); - assertThat(uri).hasScheme("http").hasHost("service1-host1").hasParameter("a", "b"); - } - - @Test - void noQueryParams() { - when(clientFactory.getProperties(any())).thenReturn(loadBalancerProperties); - MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost/get").build(); - - ServerWebExchange webExchange = testFilter(request, URI.create("lb://service1")); - URI uri = webExchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR); - assertThat(uri).hasScheme("http").hasHost("service1-host1"); - } - - @Test - void encodedParameters() { - when(clientFactory.getProperties(any())).thenReturn(loadBalancerProperties); - URI url = UriComponentsBuilder.fromUriString("http://localhost/get?a=b&c=d[]").buildAndExpand().encode() - .toUri(); - - MockServerHttpRequest request = MockServerHttpRequest.method(HttpMethod.GET, url).build(); - - URI lbUrl = UriComponentsBuilder.fromUriString("lb://service1?a=b&c=d[]").buildAndExpand().encode().toUri(); - - // prove that it is encoded - assertThat(lbUrl.getRawQuery()).isEqualTo("a=b&c=d%5B%5D"); - - assertThat(lbUrl).hasParameter("c", "d[]"); - - ServerWebExchange webExchange = testFilter(request, lbUrl); - URI uri = webExchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR); - assertThat(uri).hasScheme("http").hasHost("service1-host1").hasParameter("a", "b").hasParameter("c", "d[]"); - - // prove that it is not double encoded - assertThat(uri.getRawQuery()).isEqualTo("a=b&c=d%5B%5D"); - } - - @Test - void unencodedParameters() { - when(clientFactory.getProperties(any())).thenReturn(loadBalancerProperties); - URI url = URI.create("http://localhost/get?a=b&c=d[]"); - - MockServerHttpRequest request = MockServerHttpRequest.method(HttpMethod.GET, url).build(); - - URI lbUrl = URI.create("lb://service1?a=b&c=d[]"); - - // prove that it is unencoded - assertThat(lbUrl.getRawQuery()).isEqualTo("a=b&c=d[]"); - - ServerWebExchange webExchange = testFilter(request, lbUrl); - - URI uri = webExchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR); - assertThat(uri).hasScheme("http").hasHost("service1-host1").hasParameter("a", "b").hasParameter("c", "d[]"); - - // prove that it is NOT encoded - assertThat(uri.getRawQuery()).isEqualTo("a=b&c=d[]"); - } - - @Test - void happyPathWithAttributeRatherThanScheme() { - when(clientFactory.getProperties(any())).thenReturn(loadBalancerProperties); - MockServerHttpRequest request = MockServerHttpRequest.get("ws://localhost/get?a=b").build(); - - URI lbUri = URI.create("ws://service1?a=b"); - - exchange = MockServerWebExchange.from(request); - exchange.getAttributes().put(GATEWAY_SCHEME_PREFIX_ATTR, "lb"); - - ServerWebExchange webExchange = testFilter(exchange, lbUri); - URI uri = webExchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR); - assertThat(uri).hasScheme("ws").hasHost("service1-host1").hasParameter("a", "b"); - } - - @Test - void shouldNotFilterWhenGatewaySchemePrefixAttrIsNotLb() { - URI uri = UriComponentsBuilder.fromUriString("http://myservice").build().toUri(); - exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, uri); - exchange.getAttributes().put(GATEWAY_SCHEME_PREFIX_ATTR, "xx"); - - filter.filter(exchange, chain); - - verify(chain).filter(exchange); - verifyNoMoreInteractions(chain); - verifyNoInteractions(clientFactory); - } - - @Test - void shouldThrow4O4ExceptionWhenNoServiceInstanceIsFound() { - when(clientFactory.getProperties(any())).thenReturn(loadBalancerProperties); - URI uri = UriComponentsBuilder.fromUriString("lb://service1").build().toUri(); - exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, uri); - RoundRobinLoadBalancer loadBalancer = new RoundRobinLoadBalancer( - ServiceInstanceListSuppliers.toProvider("service1"), "service1", -1); - when(clientFactory.getInstance("service1", ReactorServiceInstanceLoadBalancer.class)).thenReturn(loadBalancer); - properties.setUse404(true); - ReactiveLoadBalancerClientFilter filter = new ReactiveLoadBalancerClientFilter(clientFactory, properties); - when(chain.filter(exchange)).thenReturn(Mono.empty()); - try { - filter.filter(exchange, chain).block(); - } - catch (NotFoundException exception) { - assertThat(exception.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND); - } - } - - @SuppressWarnings("unchecked") - @Test - void shouldOverrideSchemeUsingIsSecure() { - when(clientFactory.getProperties(any())).thenReturn(loadBalancerProperties); - when(clientFactory.getProperties(any())).thenReturn(loadBalancerProperties); - URI url = UriComponentsBuilder.fromUriString("lb://myservice").build().toUri(); - ServerWebExchange exchange = MockServerWebExchange - .from(MockServerHttpRequest.get("https://localhost:9999/mypath").build()); - exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, url); - ServiceInstance serviceInstance = new DefaultServiceInstance("myservice1", "myservice", "localhost", 8080, - false); - when(clientFactory.getInstance("myservice", ReactorServiceInstanceLoadBalancer.class)).thenReturn( - new RoundRobinLoadBalancer(ServiceInstanceListSuppliers.toProvider("myservice", serviceInstance), - "myservice", -1)); - when(chain.filter(exchange)).thenReturn(Mono.empty()); - - filter.filter(exchange, chain).block(); - - assertThat((LinkedHashSet) exchange.getAttribute(GATEWAY_ORIGINAL_REQUEST_URL_ATTR)).contains(url); - assertThat((URI) exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR)) - .isEqualTo(URI.create("http://localhost:8080/mypath")); - verify(chain).filter(exchange); - verifyNoMoreInteractions(chain); - } - - @SuppressWarnings({ "rawtypes" }) - @Test - void shouldPassRequestToLoadBalancer() { - String hint = "test"; - when(loadBalancerProperties.getHint()).thenReturn(buildHints(hint)); - when(clientFactory.getProperties(any())).thenReturn(loadBalancerProperties); - MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost/get?a=b").build(); - URI lbUri = URI.create("lb://service1?a=b"); - ServerWebExchange serverWebExchange = mock(ServerWebExchange.class); - when(serverWebExchange.getAttribute(GATEWAY_REQUEST_URL_ATTR)).thenReturn(lbUri); - when(serverWebExchange.getRequiredAttribute(GATEWAY_ORIGINAL_REQUEST_URL_ATTR)) - .thenReturn(new LinkedHashSet<>()); - when(serverWebExchange.getRequest()).thenReturn(request); - RoundRobinLoadBalancer loadBalancer = mock(RoundRobinLoadBalancer.class); - when(loadBalancer.choose(any(Request.class))).thenReturn(Mono.just( - new DefaultResponse(new DefaultServiceInstance("myservice1", "service1", "localhost", 8080, false)))); - when(clientFactory.getInstance("service1", ReactorServiceInstanceLoadBalancer.class)).thenReturn(loadBalancer); - when(chain.filter(any())).thenReturn(Mono.empty()); - - filter.filter(serverWebExchange, chain); - - verify(loadBalancer).choose(argThat((Request passedRequest) -> ((RequestDataContext) passedRequest.getContext()) - .getClientRequest().getUrl().equals(request.getURI()) - && ((RequestDataContext) passedRequest.getContext()).getHint().equals(hint))); - - } - - @SuppressWarnings({ "unchecked", "rawtypes" }) - @Test - void loadBalancerLifecycleCallbacksExecutedForSuccess() { - when(clientFactory.getProperties(any())).thenReturn(loadBalancerProperties); - LoadBalancerLifecycle lifecycleProcessor = mock(LoadBalancerLifecycle.class); - ServiceInstance serviceInstance = new DefaultServiceInstance("myservice1", "myservice", "localhost", 8080, - false); - ServerWebExchange serverWebExchange = mockExchange(serviceInstance, lifecycleProcessor, false); - - filter.filter(serverWebExchange, chain).subscribe(); - - verify(lifecycleProcessor).onStart(any(Request.class)); - verify(lifecycleProcessor).onStartRequest(any(Request.class), any(Response.class)); - verify(lifecycleProcessor).onComplete(argThat(completionContext -> CompletionContext.Status.SUCCESS - .equals(completionContext.status()) - && completionContext.getLoadBalancerResponse().getServer().equals(serviceInstance) - && HttpMethod.GET.equals( - ((RequestDataContext) completionContext.getLoadBalancerRequest().getContext()).method()))); - } - - @SuppressWarnings({ "unchecked", "rawtypes" }) - @Test - void loadBalancerLifecycleCallbacksExecutedForDiscard() { - when(clientFactory.getProperties(any())).thenReturn(loadBalancerProperties); - LoadBalancerLifecycle lifecycleProcessor = mock(LoadBalancerLifecycle.class); - ServiceInstance serviceInstance = null; - ServerWebExchange serverWebExchange = mockExchange(serviceInstance, lifecycleProcessor, false); - - filter.filter(serverWebExchange, chain).subscribe(); - - verify(lifecycleProcessor).onStart(any(Request.class)); - verify(lifecycleProcessor).onComplete(argThat(completionContext -> CompletionContext.Status.DISCARD - .equals(completionContext.status()) - && HttpMethod.GET.equals( - ((RequestDataContext) completionContext.getLoadBalancerRequest().getContext()).method()))); - } - - @SuppressWarnings({ "unchecked", "rawtypes" }) - @Test - void loadBalancerLifecycleCallbacksExecutedForFailed() { - when(clientFactory.getProperties(any())).thenReturn(loadBalancerProperties); - LoadBalancerLifecycle lifecycleProcessor = mock(LoadBalancerLifecycle.class); - ServiceInstance serviceInstance = new DefaultServiceInstance("myservice1", "myservice", "localhost", 8080, - false); - ServerWebExchange serverWebExchange = mockExchange(serviceInstance, lifecycleProcessor, true); - - filter.filter(serverWebExchange, chain).subscribe(); - - verify(lifecycleProcessor).onStart(any(Request.class)); - verify(lifecycleProcessor).onStartRequest(any(Request.class), any(Response.class)); - verify(lifecycleProcessor).onComplete(argThat(completionContext -> CompletionContext.Status.FAILED - .equals(completionContext.status()) - && HttpMethod.GET.equals( - ((RequestDataContext) completionContext.getLoadBalancerRequest().getContext()).method()))); - } - - @SuppressWarnings({ "rawtypes", "unchecked" }) - private ServerWebExchange mockExchange(ServiceInstance serviceInstance, LoadBalancerLifecycle lifecycleProcessor, - boolean shouldThrowException) { - Response response; - when(lifecycleProcessor.supports(any(Class.class), any(Class.class), any(Class.class))).thenReturn(true); - MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost/get?a=b").build(); - URI lbUri = URI.create("lb://service1?a=b"); - ServerWebExchange serverWebExchange = MockServerWebExchange.from(request); - if (serviceInstance == null) { - response = new EmptyResponse(); - } - else { - response = new DefaultResponse(serviceInstance); - } - serverWebExchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, lbUri); - serverWebExchange.getAttributes().put(GATEWAY_ORIGINAL_REQUEST_URL_ATTR, new LinkedHashSet<>()); - serverWebExchange.getAttributes().put(GATEWAY_LOADBALANCER_RESPONSE_ATTR, response); - RoundRobinLoadBalancer loadBalancer = mock(RoundRobinLoadBalancer.class); - when(loadBalancer.choose(any(Request.class))).thenReturn(Mono.just(response)); - when(clientFactory.getInstance("service1", ReactorServiceInstanceLoadBalancer.class)).thenReturn(loadBalancer); - Map lifecycleProcessors = new HashMap<>(); - lifecycleProcessors.put("service1", lifecycleProcessor); - when(clientFactory.getInstances("service1", LoadBalancerLifecycle.class)).thenReturn(lifecycleProcessors); - if (shouldThrowException) { - when(chain.filter(any())).thenReturn(Mono.error(new UnsupportedOperationException())); - } - else { - when(chain.filter(any())).thenReturn(Mono.empty()); - } - return serverWebExchange; - } - - private Map buildHints(String hint) { - Map hints = new HashMap<>(); - hints.put("default", hint); - return hints; - } - - private ServerWebExchange testFilter(MockServerHttpRequest request, URI uri) { - return testFilter(MockServerWebExchange.from(request), uri); - } - - private ServerWebExchange testFilter(ServerWebExchange exchange, URI uri) { - exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, uri); - - ArgumentCaptor captor = ArgumentCaptor.forClass(ServerWebExchange.class); - when(chain.filter(captor.capture())).thenReturn(Mono.empty()); - - RoundRobinLoadBalancer loadBalancer = new RoundRobinLoadBalancer( - ServiceInstanceListSuppliers.toProvider("service1", - new DefaultServiceInstance("service1_1", "service1", "service1-host1", 8081, false)), - "service1", -1); - when(clientFactory.getInstance("service1", ReactorServiceInstanceLoadBalancer.class)).thenReturn(loadBalancer); - - ReactiveLoadBalancerClientFilter filter = new ReactiveLoadBalancerClientFilter(clientFactory, properties); - filter.filter(exchange, chain).block(); - - return captor.getValue(); - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/RouteToRequestUrlFilterTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/RouteToRequestUrlFilterTests.java deleted file mode 100644 index b8bfd5ea..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/RouteToRequestUrlFilterTests.java +++ /dev/null @@ -1,208 +0,0 @@ -/* - * Copyright 2013-2020 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.filter; - -import java.net.URI; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; -import reactor.core.publisher.Mono; - -import org.springframework.boot.SpringBootVersion; -import org.springframework.cloud.gateway.route.Route; -import org.springframework.http.HttpMethod; -import org.springframework.mock.http.server.reactive.MockServerHttpRequest; -import org.springframework.mock.web.server.MockServerWebExchange; -import org.springframework.web.server.ServerWebExchange; -import org.springframework.web.util.UriComponentsBuilder; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assume.assumeTrue; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR; - -/** - * @author Spencer Gibb - */ -public class RouteToRequestUrlFilterTests { - - @Test - public void happyPath() { - MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost/get?a=b").build(); - - ServerWebExchange webExchange = testFilter(request, "http://myhost/mypath"); - URI uri = webExchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR); - assertThat(uri).hasScheme("http").hasHost("myhost").hasPath("/get").hasParameter("a", "b"); - } - - @Test - public void happyPathLb() { - MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost/getb").build(); - - ServerWebExchange webExchange = testFilter(request, "lb://myhost"); - URI uri = webExchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR); - assertThat(uri).hasScheme("lb").hasHost("myhost"); - } - - @Test - public void invalidHost() { - Assertions.assertThrows(IllegalStateException.class, () -> { - MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost/getb").build(); - testFilter(request, "lb://my_host"); - }); - } - - @Test - public void happyPathLbPlusScheme() { - MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost/getb").build(); - - ServerWebExchange webExchange = testFilter(request, "lb:http://myhost"); - URI uri = webExchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR); - assertThat(uri).hasScheme("http").hasHost("myhost"); - String schemePrefix = webExchange.getRequiredAttribute(GATEWAY_SCHEME_PREFIX_ATTR); - assertThat(schemePrefix).isEqualTo("lb"); - } - - @Test - public void noQueryParams() { - MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost/get").build(); - - ServerWebExchange webExchange = testFilter(request, "http://myhost"); - URI uri = webExchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR); - assertThat(uri).hasScheme("http").hasHost("myhost"); - } - - @Test - public void encodedParameters() { - URI url = UriComponentsBuilder.fromUriString("http://localhost/get?a=b&c=d[]").buildAndExpand().encode() - .toUri(); - - // prove that it is encoded - assertThat(url.getRawQuery()).isEqualTo("a=b&c=d%5B%5D"); - - assertThat(url).hasParameter("c", "d[]"); - - MockServerHttpRequest request = MockServerHttpRequest.method(HttpMethod.GET, url).build(); - - ServerWebExchange webExchange = testFilter(request, "http://myhost"); - URI uri = webExchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR); - assertThat(uri).hasScheme("http").hasHost("myhost").hasParameter("a", "b").hasParameter("c", "d[]"); - - // prove that it is not double encoded - assertThat(uri.getRawQuery()).isEqualTo("a=b&c=d%5B%5D"); - } - - @Test - public void partialEncodedParameters() { - assumeTrue("partialEncodedParameters ignored for boot 2.2", SpringBootVersion.getVersion().startsWith("2.3.")); - - URI url = UriComponentsBuilder.fromUriString("http://localhost/get?key[]=test= key&start=1533108081").build() - .toUri(); - - // prove that it is partial encoded - assertThat(url.getRawQuery()).isEqualTo("key[]=test=%20key&start=1533108081"); - - assertThat(url).hasParameter("key[]", "test= key"); - assertThat(url).hasParameter("start", "1533108081"); - - MockServerHttpRequest request = MockServerHttpRequest.method(HttpMethod.GET, url).build(); - - ServerWebExchange webExchange = testFilter(request, "http://myhost"); - URI uri = webExchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR); - assertThat(uri).hasScheme("http").hasHost("myhost") - // since https://github.com/joel-costigliola/assertj-core/issues/1699 - // assertj uses raw query - .hasParameter("key[]", "test=%20key").hasParameter("start", "1533108081"); - - // prove that it is double encoded since partial encoded uri is treated as - // unencoded. - assertThat(uri.getRawQuery()).isEqualTo("key[]=test=%2520key&start=1533108081"); - } - - @Test - public void encodedUrl() { - URI url = UriComponentsBuilder.fromUriString("http://localhost/abc def/get").buildAndExpand().encode().toUri(); - - // prove that it is encoded - assertThat(url.getRawPath()).isEqualTo("/abc%20def/get"); - - assertThat(url).hasPath("/abc def/get"); - - MockServerHttpRequest request = MockServerHttpRequest.method(HttpMethod.GET, url).build(); - - ServerWebExchange webExchange = testFilter(request, "http://myhost/abc%20def/get"); - URI uri = webExchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR); - assertThat(uri).hasScheme("http").hasHost("myhost").hasPath("/abc def/get"); - - // prove that it is not double encoded - assertThat(uri.getRawPath()).isEqualTo("/abc%20def/get"); - } - - @Test - public void unencodedParameters() { - URI url = URI.create("http://localhost/get?a=b&c=d[]"); - - // prove that it is unencoded - assertThat(url.getRawQuery()).isEqualTo("a=b&c=d[]"); - - MockServerHttpRequest request = MockServerHttpRequest.method(HttpMethod.GET, url).build(); - - ServerWebExchange webExchange = testFilter(request, "http://myhost"); - - URI uri = webExchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR); - assertThat(uri).hasScheme("http").hasHost("myhost").hasParameter("a", "b").hasParameter("c", "d[]"); - - // prove that it is NOT encoded - assertThat(uri.getRawQuery()).isEqualTo("a=b&c=d[]"); - } - - @Test - public void matcherWorks() { - testMatcher(true, "lb:a123:stuff", "lb:abc:stuff", "lb:a.bc:stuff", "lb:a-bc:stuff", "lb:a+bc:stuff"); - testMatcher(false, "lb:a", "lb:a123", "lb:123:stuff", "lb:a//:stuff"); - } - - private void testMatcher(boolean shouldMatch, String... uris) { - for (String s : uris) { - URI uri = URI.create(s); - boolean result = RouteToRequestUrlFilter.hasAnotherScheme(uri); - assertThat(result).as("%s should match: %s", s, result).isEqualTo(shouldMatch); - } - } - - private ServerWebExchange testFilter(MockServerHttpRequest request, String routeUri) { - Route value = Route.async().id("1").uri(URI.create(routeUri)).order(0).predicate(swe -> true).build(); - - ServerWebExchange exchange = MockServerWebExchange.from(request); - exchange.getAttributes().put(GATEWAY_ROUTE_ATTR, value); - - GatewayFilterChain filterChain = mock(GatewayFilterChain.class); - - ArgumentCaptor captor = ArgumentCaptor.forClass(ServerWebExchange.class); - when(filterChain.filter(captor.capture())).thenReturn(Mono.empty()); - - RouteToRequestUrlFilter filter = new RouteToRequestUrlFilter(); - filter.filter(exchange, filterChain); - - return captor.getValue(); - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/WebsocketRoutingFilterTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/WebsocketRoutingFilterTests.java deleted file mode 100644 index 19408ec1..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/WebsocketRoutingFilterTests.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright 2013-2020 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.filter; - -import java.net.URI; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.cloud.gateway.filter.headers.HttpHeadersFilter; -import org.springframework.http.HttpHeaders; -import org.springframework.mock.http.server.reactive.MockServerHttpRequest; -import org.springframework.mock.web.server.MockServerWebExchange; -import org.springframework.web.reactive.socket.client.WebSocketClient; -import org.springframework.web.reactive.socket.server.WebSocketService; -import org.springframework.web.server.ServerWebExchange; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.springframework.cloud.gateway.filter.WebsocketRoutingFilter.SEC_WEBSOCKET_PROTOCOL; -import static org.springframework.cloud.gateway.filter.WebsocketRoutingFilter.changeSchemeIfIsWebSocketUpgrade; -import static org.springframework.cloud.gateway.filter.WebsocketRoutingFilter.convertHttpToWs; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.PRESERVE_HOST_HEADER_ATTRIBUTE; -import static org.springframework.http.HttpHeaders.HOST; -import static org.springframework.http.HttpHeaders.UPGRADE; - -public class WebsocketRoutingFilterTests { - - @Test - @SuppressWarnings("unchecked") - public void testProtocolParsing() { - ObjectProvider> headersFilters = mock(ObjectProvider.class); - WebsocketRoutingFilter filter = new WebsocketRoutingFilter(mock(WebSocketClient.class), - mock(WebSocketService.class), headersFilters); - - HttpHeaders headers = new HttpHeaders(); - headers.put(SEC_WEBSOCKET_PROTOCOL, Arrays.asList(" p1,p2", "p3 , p4 ")); - List protocols = filter.getProtocols(headers); - assertThat(protocols).containsExactly("p1", "p2", "p3", "p4"); - } - - @Test - public void testConvertHttpToWs() { - assertThat(convertHttpToWs("http")).isEqualTo("ws"); - assertThat(convertHttpToWs("HTTP")).isEqualTo("ws"); - assertThat(convertHttpToWs("https")).isEqualTo("wss"); - assertThat(convertHttpToWs("HTTPS")).isEqualTo("wss"); - assertThat(convertHttpToWs("tcp")).isEqualTo("tcp"); - } - - @Test - public void testEncodedUrl() { - MockServerHttpRequest request = MockServerHttpRequest.get("http://not-matters-that") - .header(UPGRADE, "WebSocket").build(); - ServerWebExchange exchange = MockServerWebExchange.from(request); - exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, - URI.create("http://microservice/my-service/websocket%20upgrade")); - changeSchemeIfIsWebSocketUpgrade(exchange); - URI wsRequestUrl = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR); - assertThat(wsRequestUrl).isEqualTo(URI.create("ws://microservice/my-service/websocket%20upgrade")); - } - - @Test - public void testHeadersFilter() { - assertDefaultHeadersFilters(false); - } - - @Test - public void testHeadersFilterPreserveHost() { - assertDefaultHeadersFilters(true); - } - - @SuppressWarnings("unchecked") - private void assertDefaultHeadersFilters(boolean preserveHostHeader) { - ObjectProvider> headersFilters = mock(ObjectProvider.class); - when(headersFilters.getIfAvailable(any())).thenReturn(new ArrayList<>()); - WebsocketRoutingFilter filter = new WebsocketRoutingFilter(mock(WebSocketClient.class), - mock(WebSocketService.class), headersFilters); - List filters = filter.getHeadersFilters(); - MockServerHttpRequest request = MockServerHttpRequest.get("ws://not-matters-that").header(HOST, "MyHost") - .header("Sec-Websocket-Something", "someval").header("x-foo", "bar").build(); - MockServerWebExchange exchange = MockServerWebExchange.from(request); - exchange.getAttributes().put(PRESERVE_HOST_HEADER_ATTRIBUTE, preserveHostHeader); - HttpHeaders httpHeaders = HttpHeadersFilter.filterRequest(filters, exchange); - assertThat(httpHeaders).doesNotContainKeys("Sec-Websocket-Something").containsKey("x-foo"); - if (preserveHostHeader) { - assertThat(httpHeaders).containsKey(HOST); - } - else { - assertThat(httpHeaders).doesNotContainKeys(HOST); - } - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/WeightCalculatorWebFilterConcurrentTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/WeightCalculatorWebFilterConcurrentTests.java deleted file mode 100644 index f3ea3e7d..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/WeightCalculatorWebFilterConcurrentTests.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright 2013-2020 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.filter; - -import java.util.UUID; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; - -import io.netty.util.internal.ThreadLocalRandom; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import org.mockito.Mockito; -import reactor.core.publisher.Mono; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.gateway.event.WeightDefinedEvent; -import org.springframework.cloud.gateway.support.WeightConfig; -import org.springframework.cloud.gateway.test.BaseWebClientTests; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.context.annotation.Import; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.web.server.ServerWebExchange; -import org.springframework.web.server.WebFilterChain; - -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; - -/** - * Bug #1459: WeightCalculatorWebFilter is not thread safe - * - * @author Alexey Nakidkin - */ -@SpringBootTest(webEnvironment = RANDOM_PORT) -@ActiveProfiles("weights-concurrent") -@DirtiesContext -public class WeightCalculatorWebFilterConcurrentTests { - - @Value("${test.concurrent.execution.timeInSeconds:5}") - private int maxTestTimeSeconds; - - @Autowired - private ApplicationEventPublisher eventPublisher; - - @Autowired - private WeightCalculatorWebFilter weightCalculatorWebFilter; - - private ExecutorService executorService; - - private long startTime; - - @BeforeEach - public void setUp() { - executorService = Executors.newSingleThreadExecutor(); - startTime = System.currentTimeMillis(); - } - - @AfterEach - public void teardown() { - executorService.shutdown(); - } - - @Test - @Disabled - public void WeightCalculatorWebFilter_threadSafeTest() { - generateEvents(); - - ServerWebExchange serverWebExchangeMock = Mockito.mock(ServerWebExchange.class); - WebFilterChain emptyWebFilterChain = serverWebExchange -> Mono.empty(); - - while (isContinue()) { - weightCalculatorWebFilter.filter(serverWebExchangeMock, emptyWebFilterChain); - } - } - - private boolean isContinue() { - return (System.currentTimeMillis() - startTime) < TimeUnit.SECONDS.toMillis(maxTestTimeSeconds); - } - - private void generateEvents() { - executorService.execute(() -> { - while (isContinue()) { - eventPublisher.publishEvent(createWeightDefinedEvent()); - } - }); - } - - private WeightDefinedEvent createWeightDefinedEvent() { - int weight = ThreadLocalRandom.current().nextInt() & Integer.MAX_VALUE; - WeightConfig config = new WeightConfig("group_1", UUID.randomUUID().toString(), weight); - return new WeightDefinedEvent(new Object(), config); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(BaseWebClientTests.DefaultTestConfig.class) - public static class CustomConfig { - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/WeightCalculatorWebFilterTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/WeightCalculatorWebFilterTests.java deleted file mode 100644 index adbb0a22..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/WeightCalculatorWebFilterTests.java +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright 2013-2020 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.filter; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.function.Supplier; - -import org.junit.jupiter.api.Test; - -import org.springframework.cloud.gateway.event.PredicateArgsEvent; -import org.springframework.cloud.gateway.filter.WeightCalculatorWebFilter.GroupWeightConfig; -import org.springframework.cloud.gateway.support.ConfigurationService; -import org.springframework.cloud.gateway.support.WeightConfig; -import org.springframework.mock.http.server.reactive.MockServerHttpRequest; -import org.springframework.mock.web.server.MockServerWebExchange; -import org.springframework.web.server.WebFilterChain; - -import static java.util.Arrays.asList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class WeightCalculatorWebFilterTests { - - @Test - public void testWeightCalculation() { - WeightCalculatorWebFilter filter = createFilter(); - - String grp1 = "group1"; - String grp2 = "group2"; - int grp1idx = 1; - int grp2idx = 1; - - assertWeightCalculation(filter, grp1, grp1idx++, 1, asList(1.0)); - assertWeightCalculation(filter, grp2, grp2idx++, 1, asList(1.0)); - assertWeightCalculation(filter, grp1, grp1idx++, 3, asList(0.25, 0.75), 0.25); - assertWeightCalculation(filter, grp2, grp2idx++, 1, asList(0.5, 0.5), 0.5); - assertWeightCalculation(filter, grp1, grp1idx++, 6, asList(0.1, 0.3, 0.6), 0.1, 0.4); - assertWeightCalculation(filter, grp2, grp2idx++, 2, asList(0.25, 0.25, 0.5), 0.25, 0.5); - assertWeightCalculation(filter, grp2, grp2idx++, 4, asList(0.125, 0.125, 0.25, 0.5), 0.125, 0.25, 0.5); - } - - private WeightCalculatorWebFilter createFilter() { - return new WeightCalculatorWebFilter(null, new ConfigurationService(null, () -> null, () -> null)); - } - - private void assertWeightCalculation(WeightCalculatorWebFilter filter, String group, int item, int weight, - List normalized, Double... middleRanges) { - String routeId = route(item); - - filter.addWeightConfig(new WeightConfig(group, routeId, weight)); - - Map groupWeights = filter.getGroupWeights(); - assertThat(groupWeights).containsKey(group); - - GroupWeightConfig config = groupWeights.get(group); - assertThat(config.group).isEqualTo(group); - assertThat(config.weights).hasSize(item).containsEntry(routeId, weight); - assertThat(config.normalizedWeights).hasSize(item); - - for (int i = 0; i < normalized.size(); i++) { - assertThat(config.normalizedWeights).containsEntry(route(i + 1), normalized.get(i)); - } - - for (int i = 0; i < normalized.size(); i++) { - assertThat(config.rangeIndexes).containsEntry(i, route(i + 1)); - } - - assertThat(config.ranges).hasSize(item + 1).startsWith(0.0).endsWith(1.0); - - if (middleRanges.length > 0) { - assertThat(config.ranges).contains(middleRanges); - } - } - - private String route(int i) { - return "route" + i; - } - - @Test - public void testChooseRouteWithRandom() { - WeightCalculatorWebFilter filter = createFilter(); - filter.addWeightConfig(new WeightConfig("groupa", "route1", 1)); - filter.addWeightConfig(new WeightConfig("groupa", "route2", 3)); - filter.addWeightConfig(new WeightConfig("groupa", "route3", 6)); - - Supplier random = mock(Supplier.class); - - when(random.get()).thenReturn(0.05).thenReturn(0.2).thenReturn(0.6); - - filter.setRandomSupplier(random); - - MockServerWebExchange exchange = MockServerWebExchange - .from(MockServerHttpRequest.get("http://localhost").build()); - - WebFilterChain filterChain = mock(WebFilterChain.class); - filter.filter(exchange, filterChain); - Map weights = WeightCalculatorWebFilter.getWeights(exchange); - assertThat(weights).containsEntry("groupa", "route1"); - - filter.filter(exchange, filterChain); - weights = WeightCalculatorWebFilter.getWeights(exchange); - assertThat(weights).containsEntry("groupa", "route2"); - - filter.filter(exchange, filterChain); - weights = WeightCalculatorWebFilter.getWeights(exchange); - assertThat(weights).containsEntry("groupa", "route3"); - } - - @Test - public void receivesPredicateArgsEvent() { - TestWeightCalculatorWebFilter filter = new TestWeightCalculatorWebFilter(); - - HashMap args = new HashMap<>(); - args.put("weight.group", "group1"); - args.put("weight.weight", "1"); - PredicateArgsEvent event = new PredicateArgsEvent(this, "routeA", args); - filter.handle(event); - - WeightConfig weightConfig = filter.weightConfig; - assertThat(weightConfig.getGroup()).isEqualTo("group1"); - assertThat(weightConfig.getRouteId()).isEqualTo("routeA"); - assertThat(weightConfig.getWeight()).isEqualTo(1); - } - - class TestWeightCalculatorWebFilter extends WeightCalculatorWebFilter { - - private WeightConfig weightConfig; - - TestWeightCalculatorWebFilter() { - super(null, new ConfigurationService(null, () -> null, () -> null)); - } - - @Override - void addWeightConfig(WeightConfig weightConfig) { - this.weightConfig = weightConfig; - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/AddRequestHeaderGatewayFilterFactoryTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/AddRequestHeaderGatewayFilterFactoryTests.java deleted file mode 100644 index 4e50b2ab..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/AddRequestHeaderGatewayFilterFactoryTests.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import java.util.Arrays; -import java.util.Map; - -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.factory.AbstractNameValueGatewayFilterFactory.NameValueConfig; -import org.springframework.cloud.gateway.route.RouteLocator; -import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; -import org.springframework.cloud.gateway.test.BaseWebClientTests; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.ActiveProfiles; - -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; - -/** - * @author Spencer Gibb - * @author Biju Kunjummen - */ -@SpringBootTest(webEnvironment = RANDOM_PORT) -@DirtiesContext -@ActiveProfiles(profiles = "request-header-web-filter") -public class AddRequestHeaderGatewayFilterFactoryTests extends BaseWebClientTests { - - @Test - public void addRequestHeaderFilterWorks() { - testClient.get().uri("/headers").header("Host", "www.addrequestheader.org").exchange().expectBody(Map.class) - .consumeWith(result -> { - Map headers = getMap(result.getResponseBody(), "headers"); - assertThat(headers).containsEntry("X-Request-Example", "ValueA"); - }); - } - - @Test - public void addRequestHeaderFilterWorksMultipleValues() { - testClient.get().uri("/multivalueheaders").header("Host", "www.addrequestheader.org").exchange() - .expectBody(Map.class).consumeWith(result -> { - Map headers = getMap(result.getResponseBody(), "headers"); - assertThat(headers).containsEntry("X-Request-Example", Arrays.asList("ValueA", "ValueB")); - }); - } - - @Test - public void addRequestHeaderFilterWorksJavaDsl() { - testClient.get().uri("/headers").header("Host", "www.addrequestheaderjava.org").exchange().expectBody(Map.class) - .consumeWith(result -> { - Map headers = getMap(result.getResponseBody(), "headers"); - assertThat(headers).containsEntry("X-Request-Acme", "ValueB-www"); - }); - } - - @Test - public void addRequestHeaderFilterMultipleValuesWorksJavaDsl() { - testClient.get().uri("/multivalueheaders").header("Host", "www.addrequestheaderjava.org").exchange() - .expectBody(Map.class).consumeWith(result -> { - Map headers = getMap(result.getResponseBody(), "headers"); - assertThat(headers).containsEntry("X-Request-Acme", Arrays.asList("ValueB-www", "ValueC-www")); - }); - } - - @Test - public void toStringFormat() { - NameValueConfig config = new NameValueConfig().setName("myname").setValue("myvalue"); - GatewayFilter filter = new AddRequestHeaderGatewayFilterFactory().apply(config); - assertThat(filter.toString()).contains("myname").contains("myvalue"); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(DefaultTestConfig.class) - public static class TestConfig { - - @Value("${test.uri}") - String uri; - - @Bean - public RouteLocator testRouteLocator(RouteLocatorBuilder builder) { - return builder.routes().route("add_request_header_java_test", - r -> r.path("/headers").and().host("{sub}.addrequestheaderjava.org") - .filters(f -> f.prefixPath("/httpbin").addRequestHeader("X-Request-Acme", "ValueB-{sub}")) - .uri(uri)) - .route("add_multiple_request_header_java_test", - r -> r.path("/multivalueheaders").and().host("{sub}.addrequestheaderjava.org") - .filters(f -> f.prefixPath("/httpbin") - .addRequestHeader("X-Request-Acme", "ValueB-{sub}") - .addRequestHeader("X-Request-Acme", "ValueC-{sub}")) - .uri(uri)) - .build(); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/AddRequestHeadersIfNotPresentGatewayFilterFactoryTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/AddRequestHeadersIfNotPresentGatewayFilterFactoryTests.java deleted file mode 100644 index 160aa874..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/AddRequestHeadersIfNotPresentGatewayFilterFactoryTests.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import java.util.Arrays; -import java.util.Map; - -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.factory.AddRequestHeadersIfNotPresentGatewayFilterFactory.KeyValue; -import org.springframework.cloud.gateway.filter.factory.AddRequestHeadersIfNotPresentGatewayFilterFactory.KeyValueConfig; -import org.springframework.cloud.gateway.route.RouteLocator; -import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; -import org.springframework.cloud.gateway.test.BaseWebClientTests; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.ActiveProfiles; - -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) -@DirtiesContext -@ActiveProfiles(profiles = "request-headers-if-not-present-web-filter") -public class AddRequestHeadersIfNotPresentGatewayFilterFactoryTests extends BaseWebClientTests { - - private static final String TEST_HEADER_1 = "X-Request-Example"; - - private static final String TEST_HEADER_2 = "X-Request-Second-Example"; - - private static final String TEST_HOST_HEADER_VALUE = "www.addrequestheaderjava.org"; - - @Test - public void addRequestHeadersIfHeaderPresentFilterDoesNotAddHeaderIfPresent() { - final String initialHeaderValue = "initial-value"; - testClient.get().uri("/headers").header(TEST_HEADER_1, initialHeaderValue).exchange().expectBody(Map.class) - .consumeWith(result -> { - Map headers = getMap(result.getResponseBody(), "headers"); - assertThat(headers).containsEntry(TEST_HEADER_1, initialHeaderValue); - }); - } - - @Test - public void addRequestHeadersIfNotPresentFilterWorks() { - testClient.get().uri("/headers").exchange().expectBody(Map.class).consumeWith(result -> { - Map headers = getMap(result.getResponseBody(), "headers"); - assertThat(headers).containsEntry(TEST_HEADER_1, "ValueA"); - }); - } - - @Test - public void addRequestHeadersIfNotPresentFilterOnlyWorksFirstPassWhenMultipleValues() { - testClient.get().uri("/multivalueheaders").exchange().expectBody(Map.class).consumeWith(result -> { - Map headers = getMap(result.getResponseBody(), "headers"); - assertThat(headers).containsEntry(TEST_HEADER_1, Arrays.asList("ValueA")); - assertThat(headers).containsEntry(TEST_HEADER_2, Arrays.asList("ValueC")); - }); - } - - @Test - public void addRequestHeadersIfNotPresentFilterWorksOnlyMissingValues() { - final String existingValue = "existing-value"; - testClient.get().uri("/multivalueheaders").header(TEST_HEADER_2, existingValue).exchange().expectBody(Map.class) - .consumeWith(result -> { - Map headers = getMap(result.getResponseBody(), "headers"); - assertThat(headers).containsEntry(TEST_HEADER_1, Arrays.asList("ValueA")); - assertThat(headers).containsEntry(TEST_HEADER_2, Arrays.asList(existingValue)); - }); - } - - @Test - public void addRequestHeadersIfNotPresentFilterWorksJavaDsl() { - testClient.get().uri("/headers").header("Host", TEST_HOST_HEADER_VALUE).exchange().expectBody(Map.class) - .consumeWith(result -> { - Map headers = getMap(result.getResponseBody(), "headers"); - assertThat(headers).containsEntry("X-Request-Acme", "ValueB-www"); - }); - } - - @Test - public void addRequestHeadersIfNotPresentFilterMultipleValuesWorksJavaDsl() { - testClient.get().uri("/multivalueheaders").header("Host", TEST_HOST_HEADER_VALUE).exchange() - .expectBody(Map.class).consumeWith(result -> { - Map headers = getMap(result.getResponseBody(), "headers"); - assertThat(headers).containsEntry("X-Request-Acme", - Arrays.asList("ValueX", "ValueY", "ValueZ", "www")); - }); - } - - @Test - public void toStringFormat() { - KeyValueConfig keyValueConfig = new KeyValueConfig(); - keyValueConfig.setKeyValues(new KeyValue[] { new KeyValue("my-header-name-1", "my-header-value-1"), - new KeyValue("my-header-name-2", "my-header-value-2"), }); - GatewayFilter filter = new AddRequestHeadersIfNotPresentGatewayFilterFactory().apply(keyValueConfig); - assertThat(filter.toString()).startsWith("[AddRequestHeadersIfNotPresent") - .contains("my-header-name-1 = 'my-header-value-1'").contains("my-header-name-2 = 'my-header-value-2'") - .endsWith("]"); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(DefaultTestConfig.class) - public static class TestConfig { - - @Value("${test.uri}") - String uri; - - @Bean - public RouteLocator testRouteLocator(RouteLocatorBuilder builder) { - return builder.routes().route("add_request_headers_if_not_present_java_test", - r -> r.path("/headers").and().host("{sub}.addrequestheaderjava.org") - .filters(f -> f.addRequestHeadersIfNotPresent("X-Request-Acme:ValueB-{sub}")).uri(uri)) - .route("add_multiple_request_headers_java_test", - r -> r.path("/multivalueheaders").and().host("{sub}.addrequestheaderjava.org") - .filters(f -> f.addRequestHeadersIfNotPresent("X-Request-Acme:ValueX", - "X-Request-Acme:ValueY", "X-Request-Acme:ValueZ", "X-Request-Acme:{sub}")) - .uri(uri)) - .build(); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/AddRequestParameterGatewayFilterFactoryTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/AddRequestParameterGatewayFilterFactoryTests.java deleted file mode 100644 index cdd5b089..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/AddRequestParameterGatewayFilterFactoryTests.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import java.io.UnsupportedEncodingException; -import java.net.URI; -import java.net.URLDecoder; -import java.util.Map; - -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.factory.AbstractNameValueGatewayFilterFactory.NameValueConfig; -import org.springframework.cloud.gateway.route.RouteLocator; -import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; -import org.springframework.cloud.gateway.test.BaseWebClientTests; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.web.util.UriComponentsBuilder; - -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.support.ServerWebExchangeUtils.containsEncodedParts; -import static org.springframework.cloud.gateway.test.TestUtils.getMap; - -@SpringBootTest(webEnvironment = RANDOM_PORT) -@DirtiesContext -@ActiveProfiles(profiles = "request-parameter-web-filter") -public class AddRequestParameterGatewayFilterFactoryTests extends BaseWebClientTests { - - @Test - public void addRequestParameterFilterWorksBlankQuery() { - testRequestParameterFilter(null, null); - } - - @Test - public void addRequestParameterFilterWorksNonBlankQuery() { - testRequestParameterFilter("baz", "bam"); - } - - @Test - public void addRequestParameterFilterWorksEncodedQuery() { - testRequestParameterFilter("name", "%E6%89%8E%E6%A0%B9"); - } - - @Test - public void addRequestParameterFilterWorksEncodedQueryJavaDsl() { - testRequestParameterFilter("www.addreqparamjava.org", "ValueB-www", "javaname", "%E6%89%8E%E6%A0%B9"); - } - - private void testRequestParameterFilter(String name, String value) { - testRequestParameterFilter("www.addrequestparameter.org", "ValueA", name, value); - } - - private void testRequestParameterFilter(String host, String expectedValue, String name, String value) { - String query; - if (name != null) { - query = "?" + name + "=" + value; - } - else { - query = ""; - } - URI uri = UriComponentsBuilder.fromUriString(this.baseUri + "/get" + query).build(true).toUri(); - boolean checkForEncodedValue = containsEncodedParts(uri); - testClient.get().uri(uri).header("Host", host).exchange().expectBody(Map.class).consumeWith(response -> { - Map args = getMap(response.getResponseBody(), "args"); - assertThat(args).containsEntry("example", expectedValue); - if (name != null) { - if (checkForEncodedValue) { - try { - assertThat(args).containsEntry(name, URLDecoder.decode(value, "UTF-8")); - } - catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); - } - } - else { - assertThat(args).containsEntry(name, value); - } - } - }); - } - - @Test - public void toStringFormat() { - NameValueConfig config = new NameValueConfig().setName("myname").setValue("myvalue"); - GatewayFilter filter = new AddRequestParameterGatewayFilterFactory().apply(config); - assertThat(filter.toString()).contains("myname").contains("myvalue"); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(DefaultTestConfig.class) - public static class TestConfig { - - @Value("${test.uri}") - String uri; - - @Bean - public RouteLocator testRouteLocator(RouteLocatorBuilder builder) { - return builder.routes().route("add_request_param_java_test", - r -> r.path("/get").and().host("{sub}.addreqparamjava.org") - .filters(f -> f.prefixPath("/httpbin").addRequestParameter("example", "ValueB-{sub}")) - .uri(uri)) - .build(); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/AddResponseHeaderGatewayFilterFactoryTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/AddResponseHeaderGatewayFilterFactoryTests.java deleted file mode 100644 index bc60ec68..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/AddResponseHeaderGatewayFilterFactoryTests.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import java.net.URI; - -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.factory.AbstractNameValueGatewayFilterFactory.NameValueConfig; -import org.springframework.cloud.gateway.route.RouteLocator; -import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; -import org.springframework.cloud.gateway.test.BaseWebClientTests; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.web.util.UriComponentsBuilder; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; - -@SpringBootTest(webEnvironment = RANDOM_PORT) -@DirtiesContext -class AddResponseHeaderGatewayFilterFactoryTests extends BaseWebClientTests { - - @Test - void testResposneHeaderFilter() { - URI uri = UriComponentsBuilder.fromUriString(this.baseUri + "/headers").build(true).toUri(); - String host = "www.addresponseheader.org"; - String expectedValue = "Bar"; - testClient.get().uri(uri).header("Host", host).exchange().expectHeader().valueEquals("X-Request-Foo", - expectedValue); - } - - @Test - void testResposneHeaderFilterJavaDsl() { - URI uri = UriComponentsBuilder.fromUriString(this.baseUri + "/get").build(true).toUri(); - String host = "www.addresponseheaderjava.org"; - String expectedValue = "myresponsevalue-www"; - testClient.get().uri(uri).header("Host", host).exchange().expectHeader().valueEquals("example", expectedValue); - } - - @Test - void toStringFormat() { - NameValueConfig config = new NameValueConfig().setName("myname").setValue("myvalue"); - GatewayFilter filter = new AddResponseHeaderGatewayFilterFactory().apply(config); - assertThat(filter.toString()).contains("myname").contains("myvalue"); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(DefaultTestConfig.class) - static class TestConfig { - - @Value("${test.uri}") - String uri; - - @Bean - public RouteLocator testRouteLocator(RouteLocatorBuilder builder) { - return builder.routes() - .route("add_response_header_java_test", - r -> r.path("/get").and().host("{sub}.addresponseheaderjava.org").filters( - f -> f.prefixPath("/httpbin").addResponseHeader("example", "myresponsevalue-{sub}")) - .uri(uri)) - .build(); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/CacheRequestBodyGatewayFilterFactoryTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/CacheRequestBodyGatewayFilterFactoryTests.java deleted file mode 100644 index bcabf1c2..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/CacheRequestBodyGatewayFilterFactoryTests.java +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Copyright 2013-2020 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.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; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.GatewayFilterChain; -import org.springframework.cloud.gateway.route.RouteLocator; -import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; -import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; -import org.springframework.cloud.gateway.test.BaseWebClientTests; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; -import org.springframework.core.io.buffer.PooledDataBuffer; -import org.springframework.http.HttpStatus; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.util.StringUtils; -import org.springframework.web.server.ServerWebExchange; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; - -@SpringBootTest(webEnvironment = RANDOM_PORT) -@DirtiesContext -public class CacheRequestBodyGatewayFilterFactoryTests extends BaseWebClientTests { - - private static final String BODY_VALUE = "here is request body"; - - private static final String BODY_EMPTY = ""; - - private static final String BODY_CACHED_EXISTS = "BODY_CACHED_EXISTS"; - - @Test - public void cacheRequestBodyWorks() { - testClient.post().uri("/post").header("Host", "www.cacherequestbody.org").bodyValue(BODY_VALUE).exchange() - .expectStatus().isOk().expectBody(Map.class).consumeWith(result -> { - Map response = result.getResponseBody(); - assertThat(response).isNotNull(); - - String responseBody = (String) response.get("data"); - assertThat(responseBody).isEqualTo(BODY_VALUE); - }); - } - - @Test - public void cacheRequestBodyEmpty() { - testClient.post().uri("/post").header("Host", "www.cacherequestbodyempty.org").exchange().expectStatus().isOk() - .expectBody(Map.class).consumeWith(result -> { - Map response = result.getResponseBody(); - assertThat(response).isNotNull(); - - assertThat(response.get("data")).isNull(); - }); - } - - @Test - public void cacheRequestBodyExists() { - testClient.post().uri("/post").header("Host", "www.cacherequestbodyexists.org").exchange().expectStatus() - .isOk(); - } - - @Test - public void toStringFormat() { - CacheRequestBodyGatewayFilterFactory.Config config = new CacheRequestBodyGatewayFilterFactory.Config(); - config.setBodyClass(String.class); - GatewayFilter filter = new CacheRequestBodyGatewayFilterFactory().apply(config); - assertThat(filter.toString()).contains("String"); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(DefaultTestConfig.class) - public static class TestConfig { - - @Value("${test.uri}") - String uri; - - @Bean - public RouteLocator testRouteLocator(RouteLocatorBuilder builder) { - return builder.routes() - .route("cache_request_body_java_test", - r -> r.path("/post").and().host("**.cacherequestbody.org") - .filters(f -> f.prefixPath("/httpbin").cacheRequestBody(String.class) - .filter(new AssertCachedRequestBodyGatewayFilter(BODY_VALUE)) - .filter(new CheckCachedRequestBodyReleasedGatewayFilter())) - .uri(uri)) - .route("cache_request_body_empty_java_test", - r -> r.path("/post").and().host("**.cacherequestbodyempty.org") - .filters(f -> f.prefixPath("/httpbin").cacheRequestBody(String.class) - .filter(new AssertCachedRequestBodyGatewayFilter(BODY_EMPTY))) - .uri(uri)) - .route("cache_request_body_exists_java_test", - r -> r.path("/post").and().host("**.cacherequestbodyexists.org") - .filters(f -> f.prefixPath("/httpbin") - .filter(new SetExchangeCachedRequestBodyGatewayFilter(BODY_CACHED_EXISTS)) - .cacheRequestBody(String.class) - .filter(new AssertCachedRequestBodyGatewayFilter(BODY_CACHED_EXISTS))) - .uri(uri)) - .build(); - } - - } - - private static class AssertCachedRequestBodyGatewayFilter implements GatewayFilter { - - private boolean exceptNullBody; - - private String bodyExcepted; - - AssertCachedRequestBodyGatewayFilter(String body) { - this.exceptNullBody = !StringUtils.hasText(body); - this.bodyExcepted = body; - } - - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - String body = exchange.getAttribute(ServerWebExchangeUtils.CACHED_REQUEST_BODY_ATTR); - if (exceptNullBody) { - assertThat(body).isNull(); - } - else { - assertThat(body).isEqualTo(bodyExcepted); - } - return chain.filter(exchange); - } - - } - - private static class SetExchangeCachedRequestBodyGatewayFilter implements GatewayFilter { - - private String bodyToSetCache; - - SetExchangeCachedRequestBodyGatewayFilter(String toSet) { - this.bodyToSetCache = toSet; - } - - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - exchange.getAttributes().put(ServerWebExchangeUtils.CACHED_REQUEST_BODY_ATTR, bodyToSetCache); - return chain.filter(exchange); - } - - } - - private static class CheckCachedRequestBodyReleasedGatewayFilter implements GatewayFilter { - - CheckCachedRequestBodyReleasedGatewayFilter() { - } - - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - return chain.filter(exchange).doAfterTerminate(() -> { - Object o = exchange.getAttributes() - .get(CacheRequestBodyGatewayFilterFactory.CACHED_ORIGINAL_REQUEST_BODY_BACKUP_ATTR); - if (o instanceof PooledDataBuffer dataBuffer) { - if (dataBuffer.isAllocated()) { - exchange.getResponse().setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR); - fail("DataBuffer is not released"); - } - } - }); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/DedupeResponseHeaderGatewayFilterFactoryTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/DedupeResponseHeaderGatewayFilterFactoryTests.java deleted file mode 100644 index 4179fceb..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/DedupeResponseHeaderGatewayFilterFactoryTests.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -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.cloud.gateway.test.BaseWebClientTests; -import org.springframework.context.annotation.Import; -import org.springframework.test.annotation.DirtiesContext; - -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; - -@SpringBootTest(webEnvironment = RANDOM_PORT) -@DirtiesContext -class DedupeResponseHeaderGatewayFilterFactoryTests extends BaseWebClientTests { - - @Test - void dedupeResponseHeaderFilterWorks() { - testClient.get().uri("/headers").header("Host", "www.deduperesponseheader.org").exchange().expectStatus().isOk() - .expectHeader().valueEquals("Access-Control-Allow-Credentials", "true").expectHeader() - .valueEquals("Access-Control-Allow-Origin", "https://musk.mars").expectHeader() - .valueEquals("Scout-Cookie", "S'mores").expectHeader() - .valueEquals("Next-Week-Lottery-Numbers", "4", "2", "42"); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(DefaultTestConfig.class) - static class TestConfig { - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/DedupeResponseHeaderGatewayFilterFactoryUnitTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/DedupeResponseHeaderGatewayFilterFactoryUnitTests.java deleted file mode 100644 index efaca7b3..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/DedupeResponseHeaderGatewayFilterFactoryUnitTests.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import java.util.ArrayList; -import java.util.Arrays; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.Mockito; - -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.http.HttpHeaders; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.cloud.gateway.filter.factory.DedupeResponseHeaderGatewayFilterFactory.Config; -import static org.springframework.cloud.gateway.filter.factory.DedupeResponseHeaderGatewayFilterFactory.Strategy; - -public class DedupeResponseHeaderGatewayFilterFactoryUnitTests { - - private static final String NAME_1 = HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN; - - private static final String NAME_2 = HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS; - - private HttpHeaders headers; - - private Config config; - - private DedupeResponseHeaderGatewayFilterFactory filter; - - @BeforeEach - public void setUp() { - headers = Mockito.mock(HttpHeaders.class); - config = new Config(); - filter = new DedupeResponseHeaderGatewayFilterFactory(); - } - - @Test - public void dedupNullName() { - filter.dedupe(headers, config); - Mockito.verify(headers, Mockito.never()).get(Mockito.anyString()); - Mockito.verify(headers, Mockito.never()).set(Mockito.anyString(), Mockito.anyString()); - } - - @Test - public void dedupNullValues() { - config.setName(NAME_1); - Mockito.when(headers.get(NAME_1)).thenReturn(null); - filter.dedupe(headers, config); - Mockito.verify(headers).get(NAME_1); - Mockito.verify(headers, Mockito.never()).set(Mockito.anyString(), Mockito.anyString()); - } - - @Test - public void dedupEmptyValues() { - config.setName(NAME_1); - Mockito.when(headers.get(NAME_1)).thenReturn(new ArrayList<>()); - filter.dedupe(headers, config); - Mockito.verify(headers).get(NAME_1); - Mockito.verify(headers, Mockito.never()).set(Mockito.anyString(), Mockito.anyString()); - } - - @Test - public void dedupSingleValue() { - config.setName(NAME_1); - Mockito.when(headers.get(NAME_1)).thenReturn(Arrays.asList("1")); - filter.dedupe(headers, config); - Mockito.verify(headers).get(NAME_1); - Mockito.verify(headers, Mockito.never()).set(Mockito.anyString(), Mockito.anyString()); - } - - @Test - public void dedupMultipleValuesRetainFirst() { - config.setName(NAME_1 + " " + NAME_2); - Mockito.when(headers.get(NAME_1)).thenReturn(Arrays.asList("2", "3", "3", "4")); - Mockito.when(headers.get(NAME_2)).thenReturn(Arrays.asList("true", "false")); - filter.dedupe(headers, config); - Mockito.verify(headers).get(NAME_1); - Mockito.verify(headers).set(NAME_1, "2"); - Mockito.verify(headers).get(NAME_2); - Mockito.verify(headers).set(NAME_2, "true"); - Mockito.verify(headers, Mockito.times(2)).set(Mockito.anyString(), Mockito.anyString()); - } - - @Test - public void dedupMultipleValuesRetainLast() { - config.setName(NAME_1); - config.setStrategy(Strategy.RETAIN_LAST); - Mockito.when(headers.get(NAME_1)).thenReturn(Arrays.asList("2", "3", "3", "4")); - filter.dedupe(headers, config); - Mockito.verify(headers).get(NAME_1); - Mockito.verify(headers).set(NAME_1, "4"); - Mockito.verify(headers).set(Mockito.anyString(), Mockito.anyString()); - } - - @Test - public void dedupMultipleValuesRetainUnique() { - config.setName(NAME_1); - config.setStrategy(Strategy.RETAIN_UNIQUE); - Mockito.when(headers.get(NAME_1)).thenReturn(Arrays.asList("2", "3", "3", "4")); - filter.dedupe(headers, config); - Mockito.verify(headers).get(NAME_1); - Mockito.verify(headers).put(Mockito.eq(NAME_1), Mockito.eq(Arrays.asList("2", "3", "4"))); - Mockito.verify(headers).put(Mockito.anyString(), Mockito.anyList()); - } - - @Test - public void toStringFormat() { - Config config = new Config(); - config.setName("myname"); - config.setStrategy(Strategy.RETAIN_LAST); - GatewayFilter filter = new DedupeResponseHeaderGatewayFilterFactory().apply(config); - assertThat(filter.toString()).contains("myname").contains(Strategy.RETAIN_LAST.toString()); - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/MapRequestHeaderGatewayFilterFactoryTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/MapRequestHeaderGatewayFilterFactoryTests.java deleted file mode 100644 index 65aed5be..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/MapRequestHeaderGatewayFilterFactoryTests.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import java.util.List; -import java.util.Map; - -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.factory.MapRequestHeaderGatewayFilterFactory.Config; -import org.springframework.cloud.gateway.route.RouteLocator; -import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; -import org.springframework.cloud.gateway.test.BaseWebClientTests; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.ActiveProfiles; - -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; - -/** - * @author Tony Clarke - */ -@SpringBootTest(webEnvironment = RANDOM_PORT) -@DirtiesContext -@ActiveProfiles(profiles = "request-map-header-web-filter") -class MapRequestHeaderGatewayFilterFactoryTests extends BaseWebClientTests { - - @Test - void mapRequestHeaderFilterWorks() { - testClient.get().uri("/headers").header("Host", "www.maprequestheader.org").header("a", "tome").exchange() - .expectBody(Map.class).consumeWith(result -> { - Map headers = getMap(result.getResponseBody(), "headers"); - assertThat(headers).containsEntry("X-Request-Example", "tome"); - }); - } - - @Test - void mapRequestHeaderFilterWorksJavaDsl() { - testClient.get().uri("/headers").header("Host", "www.maprequestheaderjava.org").header("b", "tome").exchange() - .expectBody(Map.class).consumeWith(result -> { - Map headers = getMap(result.getResponseBody(), "headers"); - assertThat(headers).containsEntry("X-Request-Example-Java", "tome"); - }); - } - - @SuppressWarnings("unchecked") - @Test - void mapRequestHeaderWithMultiValueFilterWorks() { - testClient.get().uri("/multivalueheaders").header("Host", "www.maprequestheader.org") - .header("a", "tome", "toyou").exchange().expectBody(Map.class).consumeWith(result -> { - Map headers = getMap(result.getResponseBody(), "headers"); - assertThat(headers).containsKey("X-Request-Example"); - List values = (List) headers.get("X-Request-Example"); - assertThat(values).contains("tome", "toyou"); - }); - } - - @Test - void mapRequestHeaderWithNullValueFilterWorks() { - testClient.get().uri("/headers").header("Host", "www.maprequestheader.org").header("a", (String) null) - .exchange().expectBody(Map.class).consumeWith(result -> { - Map headers = getMap(result.getResponseBody(), "headers"); - assertThat(headers).doesNotContainKey("X-Request-Example"); - }); - } - - @Test - void mapRequestHeaderWhenInputHeaderDoesNotExist() { - testClient.get().uri("/headers").header("Host", "www.maprequestheader.org").exchange().expectBody(Map.class) - .consumeWith(result -> { - Map headers = getMap(result.getResponseBody(), "headers"); - assertThat(headers).doesNotContainKey("X-Request-Example"); - }); - } - - @Test - void toStringFormat() { - Config config = new Config().setFromHeader("myfromheader").setToHeader("mytoheader"); - GatewayFilter filter = new MapRequestHeaderGatewayFilterFactory().apply(config); - assertThat(filter.toString()).contains("myfromheader").contains("mytoheader"); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(DefaultTestConfig.class) - static class TestConfig { - - @Value("${test.uri}") - String uri; - - @Bean - RouteLocator testRouteLocator(RouteLocatorBuilder builder) { - return builder.routes().route("map_request_header_java_test", - r -> r.path("/headers").and().host("**.maprequestheaderjava.org") - .filters(f -> f.prefixPath("/httpbin").mapRequestHeader("b", "X-Request-Example-Java")) - .uri(uri)) - .build(); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/PrefixPathGatewayFilterFactoryTest.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/PrefixPathGatewayFilterFactoryTest.java deleted file mode 100644 index bc937d71..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/PrefixPathGatewayFilterFactoryTest.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright 2013-2022 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.filter.factory; - -import java.net.URI; -import java.util.HashMap; -import java.util.LinkedHashSet; - -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; -import reactor.core.publisher.Mono; - -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.GatewayFilterChain; -import org.springframework.cloud.gateway.filter.factory.PrefixPathGatewayFilterFactory.Config; -import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; -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; - -/** - * @author Ryan Baxter - */ -public class PrefixPathGatewayFilterFactoryTest { - - @Test - public void testPrefixPath() { - testPrefixPathFilter("/foo", "/bar", "/foo/bar"); - testPrefixPathFilter("/foo", "/hello%20world", "/foo/hello%20world"); - } - - @Test - public void testPrefixPathWithVariable() { - HashMap variables = new HashMap<>(); - variables.put("id", "foo"); - testPrefixPathFilter("/{id}", "/bar", "/foo/bar", variables); - } - - @Test - public void testPrefixPathWithMultipleVariables() { - HashMap variables = new HashMap<>(); - variables.put("id", "foo"); - variables.put("hello", "world"); - variables.put("product", "bar"); - testPrefixPathFilter("/{id}/v1/{hello}/{product}", "/test", "/foo/v1/world/bar/test", variables); - } - - private void testPrefixPathFilter(String prefix, String path, String expectedPath) { - testPrefixPathFilter(prefix, path, expectedPath, new HashMap<>()); - } - - private void testPrefixPathFilter(String prefix, String path, String expectedPath, - HashMap variables) { - GatewayFilter filter = new PrefixPathGatewayFilterFactory().apply(c -> c.setPrefix(prefix)); - MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost" + path).build(); - - ServerWebExchange exchange = MockServerWebExchange.from(request); - ServerWebExchangeUtils.putUriTemplateVariables(exchange, variables); - - 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(expectedPath); - LinkedHashSet uris = webExchange.getRequiredAttribute(GATEWAY_ORIGINAL_REQUEST_URL_ATTR); - assertThat(uris).contains(request.getURI()); - } - - @Test - public void toStringFormat() { - Config config = new Config(); - config.setPrefix("myprefix"); - GatewayFilter filter = new PrefixPathGatewayFilterFactory().apply(config); - assertThat(filter.toString()).contains("myprefix"); - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/PreserveHostHeaderGatewayFilterFactoryTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/PreserveHostHeaderGatewayFilterFactoryTests.java deleted file mode 100644 index 710f76e0..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/PreserveHostHeaderGatewayFilterFactoryTests.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import java.util.List; -import java.util.Map; - -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.route.RouteLocator; -import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; -import org.springframework.cloud.gateway.test.BaseWebClientTests; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; -import org.springframework.test.annotation.DirtiesContext; - -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; - -/** - * @author Spencer Gibb - */ -@SpringBootTest(webEnvironment = RANDOM_PORT) -@DirtiesContext -public class PreserveHostHeaderGatewayFilterFactoryTests extends BaseWebClientTests { - - @SuppressWarnings("unchecked") - @Test - public void preserveHostHeaderGatewayFilterFactoryWorks() { - testClient.get().uri("/multivalueheaders").header("Host", "www.preservehostheader.org").exchange() - .expectStatus().isOk().expectBody(Map.class).consumeWith(result -> { - Map headers = getMap(result.getResponseBody(), "headers"); - assertThat(headers).containsKey("Host"); - List values = (List) headers.get("Host"); - assertThat(values).containsExactly("myhost.net"); - }); - } - - @Test - public void toStringFormat() { - GatewayFilter filter = new PreserveHostHeaderGatewayFilterFactory().apply(); - assertThat(filter.toString()).contains("PreserveHostHeader"); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(DefaultTestConfig.class) - public static class TestConfig { - - @Value("${test.uri}") - String uri; - - @Bean - public RouteLocator testRouteLocator(RouteLocatorBuilder builder) { - return builder.routes() - .route("test_preserve_host_header", r -> r.order(-1).host("**.preservehostheader.org").filters( - f -> f.prefixPath("/httpbin").preserveHostHeader().setRequestHeader("Host", "myhost.net")) - .uri(uri)) - .build(); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RedirectToGatewayFilterFactoryTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RedirectToGatewayFilterFactoryTests.java deleted file mode 100644 index f9441fbb..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RedirectToGatewayFilterFactoryTests.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import java.net.URI; - -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.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.factory.RedirectToGatewayFilterFactory.Config; -import org.springframework.cloud.gateway.route.RouteLocator; -import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; -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.test.annotation.DirtiesContext; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; - -@SpringBootTest(webEnvironment = RANDOM_PORT) -@DirtiesContext -public class RedirectToGatewayFilterFactoryTests extends BaseWebClientTests { - - @Test - public void redirectToFilterWorks() { - testClient.get().uri("/").header("Host", "www.redirectto.org").exchange().expectStatus() - .isEqualTo(HttpStatus.FOUND).expectHeader().valueEquals(HttpHeaders.LOCATION, "https://example.org"); - } - - @Test - public void redirectToRelativeUrlFilterWorks() { - testClient.get().uri("/").header("Host", "www.relativeredirect.org").exchange().expectStatus() - .isEqualTo(HttpStatus.FOUND).expectHeader().valueEquals(HttpHeaders.LOCATION, "/index.html#/customers"); - } - - @Test - public void redirectToRelativeUrlFilterWorksWithStrStatusCode() { - testClient.get().uri("/").header("Host", "strcode.relativeredirect.org").exchange().expectStatus() - .isEqualTo(HttpStatus.FOUND).expectHeader().valueEquals(HttpHeaders.LOCATION, "/index.html#/customers"); - } - - @Test - public void toStringFormat() { - Config config = new Config(); - config.setStatus("301"); - config.setUrl("http://newurl"); - GatewayFilter filter = new RedirectToGatewayFilterFactory().apply(config); - assertThat(filter.toString()).contains("301").contains("http://newurl"); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(DefaultTestConfig.class) - public static class TestConfig { - - @Bean - public RouteLocator testRouteLocator(RouteLocatorBuilder builder) { - return builder.routes() - .route("relative_redirect_uri_object", r -> r.host("strcode.relativeredirect.org") - .filters(f -> f.redirect("302", URI.create("/index.html#/customers"))).uri("no://op")) - .route("relative_redirect", r -> r.host("**.relativeredirect.org") - .filters(f -> f.redirect(302, "/index.html#/customers")).uri("no://op")) - .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 deleted file mode 100644 index 84db78fd..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RemoveJsonAttributesResponseBodyGatewayFilterFactoryTests.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import java.util.Map; - -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.gateway.route.RouteLocator; -import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; -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.MediaType; -import org.springframework.test.annotation.DirtiesContext; - -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; - -/** - * @author Marta Medio - */ -@SpringBootTest(webEnvironment = RANDOM_PORT) -@DirtiesContext -public class RemoveJsonAttributesResponseBodyGatewayFilterFactoryTests extends BaseWebClientTests { - - @Test - public void removeJsonAttributeRootWorks() { - testClient.post().uri("/post").header("Host", "www.removejsonattributes.org") - .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).header("foo", "test") - .header("bar", "test").exchange().expectStatus().isOk().expectBody(Map.class).consumeWith(result -> { - Map response = result.getResponseBody(); - assertThat(response).isNotNull(); - - String responseBody = (String) response.get("data"); - assertThat(responseBody).isNull(); - - Map headers = getMap(response, "headers"); - assertThat(headers).containsKey("user-agent"); - - }); - } - - @Test - public void removeJsonAttributeRecursivelyWorks() { - - testClient.post().uri("/post").header("Host", "www.removejsonattributesrecursively.org") - .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).header("foo", "test") - .header("bar", "test").exchange().expectStatus().isOk().expectBody(Map.class).consumeWith(result -> { - Map response = result.getResponseBody(); - assertThat(response).isNotNull(); - - Map headers = getMap(response, "headers"); - assertThat(headers).doesNotContainKey("foo"); - assertThat(headers).containsEntry("bar", "test"); - }); - } - - @Test - public void removeJsonAttributeNoMatchesWorks() { - - testClient.post().uri("/post").header("Host", "www.removejsonattributesnomatches.org") - .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).exchange().expectStatus().isOk() - .expectBody(Map.class).consumeWith(result -> { - Map response = result.getResponseBody(); - assertThat(response).isNotNull(); - - Map headers = getMap(response, "headers"); - assertThat(headers).isNotNull(); - assertThat(headers).containsEntry(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); - }); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(DefaultTestConfig.class) - public static class TestConfig { - - @Value("${test.uri}") - String uri; - - @Bean - public RouteLocator testRouteLocator(RouteLocatorBuilder builder) { - return builder.routes() - .route("remove_json_attributes_root_level_java_test", - r -> r.path("/post").and().host("{sub}.removejsonattributes.org") - .filters(f -> f.removeJsonAttributes(false, "data", "foo")).uri(uri)) - .route("remove_json_attributes_recursively_java_test", - r -> r.path("/post").and().host("{sub}.removejsonattributesrecursively.org") - .filters(f -> f.removeJsonAttributes(true, "foo")).uri(uri)) - .route("remove_json_attributes_no_matches_java_test", - r -> r.path("/post").and().host("{sub}.removejsonattributesnomatches.org") - .filters(f -> f.removeJsonAttributes("test")).uri(uri)) - .build(); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RemoveRequestHeaderGatewayFilterFactoryTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RemoveRequestHeaderGatewayFilterFactoryTests.java deleted file mode 100644 index 0da13c06..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RemoveRequestHeaderGatewayFilterFactoryTests.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import java.util.Map; - -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.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory.NameConfig; -import org.springframework.cloud.gateway.test.BaseWebClientTests; -import org.springframework.context.annotation.Import; -import org.springframework.test.annotation.DirtiesContext; - -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) -@DirtiesContext -public class RemoveRequestHeaderGatewayFilterFactoryTests extends BaseWebClientTests { - - @Test - public void removeRequestHeaderFilterWorks() { - testClient.get().uri("/headers").header("Host", "www.removerequestheader.org").header("X-Request-Foo", "Bar") - .exchange().expectStatus().isOk().expectBody(Map.class).consumeWith(result -> { - Map headers = getMap(result.getResponseBody(), "headers"); - assertThat(headers).doesNotContainKey("X-Request-Foo"); - }); - } - - @Test - public void toStringFormat() { - NameConfig config = new NameConfig(); - config.setName("myname"); - GatewayFilter filter = new RemoveRequestHeaderGatewayFilterFactory().apply(config); - assertThat(filter.toString()).contains("myname"); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(DefaultTestConfig.class) - public static class TestConfig { - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RemoveRequestParameterGatewayFilterFactoryIntegrationTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RemoveRequestParameterGatewayFilterFactoryIntegrationTests.java deleted file mode 100644 index a067e12b..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RemoveRequestParameterGatewayFilterFactoryIntegrationTests.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import java.util.Map; - -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory.NameConfig; -import org.springframework.cloud.gateway.route.RouteLocator; -import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; -import org.springframework.cloud.gateway.test.BaseWebClientTests; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; -import org.springframework.test.annotation.DirtiesContext; - -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) -@DirtiesContext -public class RemoveRequestParameterGatewayFilterFactoryIntegrationTests extends BaseWebClientTests { - - @Test - public void removeResponseHeaderFilterWorks() { - testClient.get().uri("/get?foo=bar&baz=bam%20bar").header("Host", "www.removerequestparamjava.org").exchange() - .expectStatus().isOk().expectBody(Map.class).consumeWith(result -> { - Map params = getMap(result.getResponseBody(), "args"); - assertThat(params).doesNotContainKey("foo"); - assertThat(params).containsEntry("baz", "bam%20bar"); - }); - } - - @Test - public void toStringFormat() { - NameConfig config = new NameConfig(); - config.setName("myname"); - GatewayFilter filter = new RemoveRequestParameterGatewayFilterFactory().apply(config); - assertThat(filter.toString()).contains("myname"); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(DefaultTestConfig.class) - public static class TestConfig { - - @Value("${test.uri}") - String uri; - - @Bean - public RouteLocator testRouteLocator(RouteLocatorBuilder builder) { - return builder.routes() - .route("removerequestparam_java_test", - r -> r.path("/get").and().host("**.removerequestparamjava.org") - .filters(f -> f.prefixPath("/httpbin").removeRequestParameter("foo")).uri(uri)) - .build(); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RemoveRequestParameterGatewayFilterFactoryTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RemoveRequestParameterGatewayFilterFactoryTests.java deleted file mode 100644 index 470160f5..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RemoveRequestParameterGatewayFilterFactoryTests.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; -import reactor.core.publisher.Mono; - -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.GatewayFilterChain; -import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory.NameConfig; -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.mock.http.server.reactive.MockServerHttpRequest; -import org.springframework.mock.web.server.MockServerWebExchange; -import org.springframework.web.server.ServerWebExchange; - -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -/** - * @author Thirunavukkarasu Ravichandran - */ -public class RemoveRequestParameterGatewayFilterFactoryTests { - - private ServerWebExchange exchange; - - private GatewayFilterChain filterChain; - - private ArgumentCaptor captor; - - @BeforeEach - public void setUp() { - filterChain = mock(GatewayFilterChain.class); - captor = ArgumentCaptor.forClass(ServerWebExchange.class); - when(filterChain.filter(captor.capture())).thenReturn(Mono.empty()); - - } - - @Test - public void removeRequestParameterFilterWorks() { - MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost") - .queryParam("foo", singletonList("bar")).build(); - exchange = MockServerWebExchange.from(request); - NameConfig config = new NameConfig(); - config.setName("foo"); - GatewayFilter filter = new RemoveRequestParameterGatewayFilterFactory().apply(config); - - filter.filter(exchange, filterChain); - - ServerHttpRequest actualRequest = captor.getValue().getRequest(); - assertThat(actualRequest.getQueryParams()).doesNotContainKey("foo"); - } - - @Test - public void removeRequestParameterFilterWorksWhenParamIsNotPresentInRequest() { - MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost").build(); - exchange = MockServerWebExchange.from(request); - NameConfig config = new NameConfig(); - config.setName("foo"); - GatewayFilter filter = new RemoveRequestParameterGatewayFilterFactory().apply(config); - - filter.filter(exchange, filterChain); - - ServerHttpRequest actualRequest = captor.getValue().getRequest(); - assertThat(actualRequest.getQueryParams()).doesNotContainKey("foo"); - } - - @Test - public void removeRequestParameterFilterShouldOnlyRemoveSpecifiedParam() { - MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost").queryParam("foo", "bar") - .queryParam("abc", "xyz").build(); - exchange = MockServerWebExchange.from(request); - NameConfig config = new NameConfig(); - config.setName("foo"); - GatewayFilter filter = new RemoveRequestParameterGatewayFilterFactory().apply(config); - - filter.filter(exchange, filterChain); - - ServerHttpRequest actualRequest = captor.getValue().getRequest(); - assertThat(actualRequest.getQueryParams()).doesNotContainKey("foo"); - assertThat(actualRequest.getQueryParams()).containsEntry("abc", singletonList("xyz")); - } - - @Test - public void removeRequestParameterFilterShouldHandleRemainingParamsWhichRequiringEncoding() { - MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost").queryParam("foo", "bar") - .queryParam("aaa", "abc xyz").queryParam("bbb", "[xyz").queryParam("ccc", ",xyz").build(); - exchange = MockServerWebExchange.from(request); - NameConfig config = new NameConfig(); - config.setName("foo"); - GatewayFilter filter = new RemoveRequestParameterGatewayFilterFactory().apply(config); - - filter.filter(exchange, filterChain); - - ServerHttpRequest actualRequest = captor.getValue().getRequest(); - assertThat(actualRequest.getQueryParams()).doesNotContainKey("foo"); - assertThat(actualRequest.getQueryParams()).containsEntry("aaa", singletonList("abc xyz")); - assertThat(actualRequest.getQueryParams()).containsEntry("bbb", singletonList("[xyz")); - assertThat(actualRequest.getQueryParams()).containsEntry("ccc", singletonList(",xyz")); - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RemoveResponseHeaderGatewayFilterFactoryTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RemoveResponseHeaderGatewayFilterFactoryTests.java deleted file mode 100644 index 05a0fc4d..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RemoveResponseHeaderGatewayFilterFactoryTests.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -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.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory.NameConfig; -import org.springframework.cloud.gateway.test.BaseWebClientTests; -import org.springframework.context.annotation.Import; -import org.springframework.test.annotation.DirtiesContext; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; - -@SpringBootTest(webEnvironment = RANDOM_PORT) -@DirtiesContext -public class RemoveResponseHeaderGatewayFilterFactoryTests extends BaseWebClientTests { - - @Test - public void removeResponseHeaderFilterWorks() { - testClient.get().uri("/headers").header("Host", "www.removereresponseheader.org").exchange().expectStatus() - .isOk().expectHeader().doesNotExist("X-Request-Foo"); - } - - @Test - public void toStringFormat() { - NameConfig config = new NameConfig(); - config.setName("myname"); - GatewayFilter filter = new RemoveResponseHeaderGatewayFilterFactory().apply(config); - assertThat(filter.toString()).contains("myname"); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(DefaultTestConfig.class) - public static class TestConfig { - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RequestHeaderSizeGatewayFilterFactoryTest.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RequestHeaderSizeGatewayFilterFactoryTest.java deleted file mode 100644 index 70b23798..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RequestHeaderSizeGatewayFilterFactoryTest.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.factory.RequestHeaderSizeGatewayFilterFactory.Config; -import org.springframework.cloud.gateway.route.RouteLocator; -import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; -import org.springframework.cloud.gateway.test.BaseWebClientTests; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; -import org.springframework.http.HttpStatus; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.util.unit.DataSize; -import org.springframework.util.unit.DataUnit; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; - -/** - * @author Sakalya Deshpande - * @author Marta Medio - */ - -@SpringBootTest(webEnvironment = RANDOM_PORT) -@DirtiesContext -public class RequestHeaderSizeGatewayFilterFactoryTest extends BaseWebClientTests { - - private static final String longString = "11111111112222222222333333333344444444445555555"; - - @Test - public void setRequestHeaderSizeFilterWorks() { - System.err.println("Here: " + longString.length() + ", " + longString.getBytes().length); - testClient.get().uri("/headers").header("Host", "www.testrequestheadersizefilter.org") - .header("HeaderName", longString).exchange().expectStatus() - .isEqualTo(HttpStatus.REQUEST_HEADER_FIELDS_TOO_LARGE).expectHeader().value("errorMessage", - header -> assertThat(header).contains("permissible limit (46B)", "'HeaderName' is 57B")); - } - - @Test - public void setRequestHeaderSizeFilterShortcutWorks() { - testClient.get().uri("/headers").header("Host", "www.requestheadersize.org").header("HeaderName", longString) - .exchange().expectStatus().isEqualTo(HttpStatus.REQUEST_HEADER_FIELDS_TOO_LARGE).expectHeader() - .value("errorMessage", - header -> assertThat(header).contains("permissible limit (46B)", "'HeaderName' is 57B")); - } - - @Test - public void setRequestHeaderSizeFilterMultipleHeadersWorks() { - testClient.get().uri("/headers").header("Host", "www.requestheadersize.org").header("HeaderName", longString) - .header("HeaderName2", longString).exchange().expectStatus() - .isEqualTo(HttpStatus.REQUEST_HEADER_FIELDS_TOO_LARGE).expectHeader() - .value("errorMessage", header -> assertThat(header).contains("permissible limit (46B)", - "'HeaderName' is 57B", "'HeaderName2' is 58B")); - } - - @Test - public void setRequestHeaderSizeFilterTakesIntoAccountHeaderName() { - testClient.get().uri("/headers").header("Host", "www.testrequestheadersizefiltername.org") - .header("HeaderName", longString).exchange().expectStatus() - .isEqualTo(HttpStatus.REQUEST_HEADER_FIELDS_TOO_LARGE).expectHeader().value("errorMessage", - header -> assertThat(header).contains("permissible limit (47B)", "'HeaderName' is 57B")); - } - - @Test - public void toStringFormat() { - Config config = new Config(); - config.setMaxSize(DataSize.ofBytes(1000L)); - GatewayFilter filter = new RequestHeaderSizeGatewayFilterFactory().apply(config); - assertThat(filter.toString()).contains("maxSize").contains("1000B"); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(DefaultTestConfig.class) - public static class TestConfig { - - @Value("${test.uri}") - String uri; - - @Bean - public RouteLocator testRouteLocator(RouteLocatorBuilder builder) { - return builder.routes() - .route("test_request_header_size", - r -> r.order(-1).host("**.testrequestheadersizefilter.org") - .filters(f -> f.setRequestHeaderSize(DataSize.of(46L, DataUnit.BYTES))).uri(uri)) - .route("test_request_header_size_name", - r -> r.order(1).host("**.testrequestheadersizefiltername.org") - .filters(f -> f.setRequestHeaderSize(DataSize.of(47L, DataUnit.BYTES))).uri(uri)) - .build(); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RequestHeaderToRequestUriGatewayFilterFactoryIntegrationTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RequestHeaderToRequestUriGatewayFilterFactoryIntegrationTests.java deleted file mode 100644 index 6dc07f48..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RequestHeaderToRequestUriGatewayFilterFactoryIntegrationTests.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import java.net.URI; -import java.util.Optional; - -import com.fasterxml.jackson.databind.JsonNode; -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.web.server.LocalServerPort; -import org.springframework.cloud.gateway.route.RouteLocator; -import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; -import org.springframework.cloud.gateway.test.BaseWebClientTests; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; -import org.springframework.test.annotation.DirtiesContext; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; - -/** - * @author Toshiaki Maki - */ -@SpringBootTest(webEnvironment = RANDOM_PORT) -@DirtiesContext -public class RequestHeaderToRequestUriGatewayFilterFactoryIntegrationTests extends BaseWebClientTests { - - @LocalServerPort - int port; - - @Test - public void changeUriWorkWithProperties() { - testClient.get().uri("/").header("Host", "www.changeuri.org") - .header("X-CF-Forwarded-Url", "http://localhost:" + port + "/actuator/health").exchange() - .expectBody(JsonNode.class).consumeWith(r -> assertThat(r.getResponseBody().has("status")).isTrue()); - } - - @Test - public void changeUriWorkWithDsl() { - testClient.get().uri("/").header("Host", "www.changeuri.org") - .header("X-Next-Url", "http://localhost:" + port + "/actuator/health").exchange() - .expectBody(JsonNode.class).consumeWith(r -> assertThat(r.getResponseBody().has("status")).isTrue()); - } - - @Test - public void changeUriWorkWithCustomLogic() { - testClient.get() - .uri(b -> b.path("/").queryParam("url", "http://localhost:" + port + "/actuator/health").build()) - .header("Host", "www.changeuri.org").exchange().expectBody(JsonNode.class) - .consumeWith(r -> assertThat(r.getResponseBody().has("status")).isTrue()); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(DefaultTestConfig.class) - public static class TestConfig { - - @Bean - public RouteLocator routeLocator(RouteLocatorBuilder builder) { - return builder.routes() - .route(r -> r.host("**.changeuri.org").and().header("X-Next-Url") - .filters(f -> f.requestHeaderToRequestUri("X-Next-Url")).uri("https://example.com")) - .route(r -> r.host("**.changeuri.org").and().query("url") - .filters(f -> f.changeRequestUri( - e -> Optional.of(URI.create(e.getRequest().getQueryParams().getFirst("url"))))) - .uri("https://example.com")) - .build(); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RequestHeaderToRequestUriGatewayFilterFactoryTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RequestHeaderToRequestUriGatewayFilterFactoryTests.java deleted file mode 100644 index 16253ff6..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RequestHeaderToRequestUriGatewayFilterFactoryTests.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import java.net.URI; - -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; -import reactor.core.publisher.Mono; - -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.GatewayFilterChain; -import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory.NameConfig; -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_REQUEST_URL_ATTR; - -/** - * @author Toshiaki Maki - */ -public class RequestHeaderToRequestUriGatewayFilterFactoryTests { - - @Test - public void filterChangeRequestUri() { - RequestHeaderToRequestUriGatewayFilterFactory factory = new RequestHeaderToRequestUriGatewayFilterFactory(); - GatewayFilter filter = factory.apply(c -> c.setName("X-CF-Forwarded-Url")); - MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost") - .header("X-CF-Forwarded-Url", "https://example.com").build(); - ServerWebExchange exchange = MockServerWebExchange.from(request); - exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, URI.create("http://localhost")); - 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(); - URI uri = (URI) webExchange.getAttributes().get(GATEWAY_REQUEST_URL_ATTR); - assertThat(uri).isNotNull(); - assertThat(uri.toString()).isEqualTo("https://example.com"); - } - - @Test - public void filterDoesNotChangeRequestUriIfHeaderIsAbsent() { - RequestHeaderToRequestUriGatewayFilterFactory factory = new RequestHeaderToRequestUriGatewayFilterFactory(); - GatewayFilter filter = factory.apply(c -> c.setName("X-CF-Forwarded-Url")); - MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost").build(); - ServerWebExchange exchange = MockServerWebExchange.from(request); - exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, URI.create("http://localhost")); - 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(); - URI uri = (URI) webExchange.getAttributes().get(GATEWAY_REQUEST_URL_ATTR); - assertThat(uri).isNotNull(); - assertThat(uri.toString()).isEqualTo("http://localhost"); - } - - @Test - public void filterDoesNotChangeRequestUriIfHeaderIsInvalid() throws Exception { - RequestHeaderToRequestUriGatewayFilterFactory factory = new RequestHeaderToRequestUriGatewayFilterFactory(); - GatewayFilter filter = factory.apply(c -> c.setName("X-CF-Forwarded-Url")); - MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost") - .header("X-CF-Forwarded-Url", "example").build(); - ServerWebExchange exchange = MockServerWebExchange.from(request); - exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, URI.create("http://localhost")); - 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(); - URI uri = (URI) webExchange.getAttributes().get(GATEWAY_REQUEST_URL_ATTR); - assertThat(uri).isNotNull(); - assertThat(uri.toURL().toString()).isEqualTo("http://localhost"); - } - - @Test - public void toStringFormat() { - NameConfig config = new NameConfig(); - config.setName("myname"); - GatewayFilter filter = new RequestHeaderToRequestUriGatewayFilterFactory().apply(config); - assertThat(filter.toString()).contains("myname"); - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RequestRateLimiterGatewayFilterFactoryTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RequestRateLimiterGatewayFilterFactoryTests.java deleted file mode 100644 index 8f7b825b..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RequestRateLimiterGatewayFilterFactoryTests.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import java.util.Collections; -import java.util.Map; - -import org.junit.jupiter.api.Test; -import reactor.core.publisher.Mono; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.GatewayFilterChain; -import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver; -import org.springframework.cloud.gateway.filter.ratelimit.RateLimiter; -import org.springframework.cloud.gateway.filter.ratelimit.RateLimiter.Response; -import org.springframework.cloud.gateway.route.Route; -import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; -import org.springframework.cloud.gateway.test.BaseWebClientTests; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; -import org.springframework.context.annotation.Primary; -import org.springframework.http.HttpStatus; -import org.springframework.mock.http.server.reactive.MockServerHttpRequest; -import org.springframework.mock.web.server.MockServerWebExchange; -import org.springframework.test.annotation.DirtiesContext; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.when; -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; - -/** - * see - * https://gist.github.com/ptarjan/e38f45f2dfe601419ca3af937fff574d#file-1-check_request_rate_limiter-rb-L36-L62 - * - * @author Spencer Gibb - */ -@SpringBootTest(webEnvironment = RANDOM_PORT) -@DirtiesContext -public class RequestRateLimiterGatewayFilterFactoryTests extends BaseWebClientTests { - - @Autowired - @Qualifier("resolver2") - KeyResolver resolver2; - - @Autowired - private ApplicationContext context; - - @MockBean - private RateLimiter rateLimiter; - - @MockBean - private GatewayFilterChain filterChain; - - @Test - public void allowedWorks() { - // tests that auto wired as default works - assertFilterFactory(null, "allowedkey", true, HttpStatus.OK); - } - - @Test - public void notAllowedWorks() { - assertFilterFactory(resolver2, "notallowedkey", false, HttpStatus.TOO_MANY_REQUESTS); - } - - @Test - public void emptyKeyDenied() { - assertFilterFactory(exchange -> Mono.empty(), null, true, HttpStatus.FORBIDDEN); - } - - @Test - public void emptyKeyAllowed() { - assertFilterFactory(exchange -> Mono.empty(), null, true, HttpStatus.OK, false); - } - - private void assertFilterFactory(KeyResolver keyResolver, String key, boolean allowed, HttpStatus expectedStatus) { - assertFilterFactory(keyResolver, key, allowed, expectedStatus, null); - } - - private void assertFilterFactory(KeyResolver keyResolver, String key, boolean allowed, HttpStatus expectedStatus, - Boolean denyEmptyKey) { - - String tokensRemaining = allowed ? "1" : "0"; - - Map headers = Collections.singletonMap("X-Tokens-Remaining", tokensRemaining); - - if (key != null) { - when(rateLimiter.isAllowed("myroute", key)).thenReturn(Mono.just(new Response(allowed, headers))); - } - - MockServerHttpRequest request = MockServerHttpRequest.get("/").build(); - MockServerWebExchange exchange = MockServerWebExchange.from(request); - exchange.getResponse().setStatusCode(HttpStatus.OK); - exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR, - Route.async().id("myroute").predicate(ex -> true).uri("http://localhost").build()); - - when(this.filterChain.filter(exchange)).thenReturn(Mono.empty()); - - RequestRateLimiterGatewayFilterFactory factory = this.context - .getBean(RequestRateLimiterGatewayFilterFactory.class); - if (denyEmptyKey != null) { - factory.setDenyEmptyKey(denyEmptyKey); - } - GatewayFilter filter = factory.apply(config -> { - config.setRouteId("myroute"); - config.setKeyResolver(keyResolver); - }); - - Mono response = filter.filter(exchange, this.filterChain); - response.subscribe(aVoid -> { - assertThat(exchange.getResponse().getStatusCode()).isEqualTo(expectedStatus); - assertThat(exchange.getResponse().getHeaders()).containsEntry("X-Tokens-Remaining", - Collections.singletonList(tokensRemaining)); - }); - - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(BaseWebClientTests.DefaultTestConfig.class) - public static class TestConfig { - - @Bean - @Primary - KeyResolver resolver1() { - return exchange -> Mono.just("allowedkey"); - } - - @Bean - KeyResolver resolver2() { - return exchange -> Mono.just("notallowedkey"); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RequestSizeGatewayFilterFactoryTest.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RequestSizeGatewayFilterFactoryTest.java deleted file mode 100644 index 2dd2adc3..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RequestSizeGatewayFilterFactoryTest.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.factory.RequestSizeGatewayFilterFactory.RequestSizeConfig; -import org.springframework.cloud.gateway.route.RouteLocator; -import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; -import org.springframework.cloud.gateway.test.BaseWebClientTests; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; -import org.springframework.http.HttpStatus; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.util.unit.DataSize; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; - -/** - * @author Arpan Das - */ - -@SpringBootTest(webEnvironment = RANDOM_PORT) -@DirtiesContext -public class RequestSizeGatewayFilterFactoryTest extends BaseWebClientTests { - - private static final String responseMesssage = "Request size is larger than permissible limit. Request size is . . " - + "where permissible limit is .*"; - - @Test - public void setRequestSizeFilterWorks() { - testClient.post().uri("/post").header("Host", "www.setrequestsize.org").header("content-length", "6") - .bodyValue("123456").exchange().expectStatus().isEqualTo(HttpStatus.PAYLOAD_TOO_LARGE).expectHeader() - .valueMatches("errorMessage", responseMesssage); - } - - @Test - public void toStringFormat() { - RequestSizeConfig config = new RequestSizeConfig(); - config.setMaxSize(DataSize.ofBytes(1000L)); - GatewayFilter filter = new RequestSizeGatewayFilterFactory().apply(config); - assertThat(filter.toString()).contains("max").contains("1000"); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(DefaultTestConfig.class) - public static class TestConfig { - - @Value("${test.uri}") - String uri; - - @Bean - public RouteLocator testRouteLocator(RouteLocatorBuilder builder) { - return builder.routes() - .route("test_request_size", - r -> r.order(-1).host("**.setrequestsize.org").filters(f -> f.setRequestSize(5L)).uri(uri)) - .build(); - } - - } - -} 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 deleted file mode 100644 index 2f7689bc..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RetryGatewayFilterFactoryIntegrationTests.java +++ /dev/null @@ -1,318 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import java.io.IOException; -import java.time.Duration; -import java.util.Map; -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.hamcrest.CoreMatchers; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import reactor.core.publisher.Mono; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.system.CapturedOutput; -import org.springframework.boot.test.system.OutputCaptureExtension; -import org.springframework.boot.test.web.server.LocalServerPort; -import org.springframework.cloud.client.DefaultServiceInstance; -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.factory.RetryGatewayFilterFactory.RetryConfig; -import org.springframework.cloud.gateway.route.RouteLocator; -import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; -import org.springframework.cloud.gateway.test.BaseWebClientTests; -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.context.annotation.Import; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -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", - "logging.level.org.springframework.cloud.gateway.filter.factory.RetryGatewayFilterFactory=TRACE" }) -@DirtiesContext -// default filter AddResponseHeader suppresses bug -// https://github.com/spring-cloud/spring-cloud-gateway/issues/1315, -// so we use only PrefixPath filter -@ActiveProfiles("retrytests") -@ExtendWith(OutputCaptureExtension.class) -public class RetryGatewayFilterFactoryIntegrationTests extends BaseWebClientTests { - - @Test - public void retryFilterGet() { - testClient.get().uri("/retry?key=get").exchange().expectStatus().isOk().expectBody(String.class).isEqualTo("3"); - } - - @Test - public void retryFilterFailure() { - testClient.mutate().responseTimeout(Duration.ofSeconds(10)).build().get() - .uri("/retryalwaysfail?key=getjavafailure&count=4").header(HttpHeaders.HOST, "www.retryjava.org") - .exchange().expectStatus().is5xxServerError().expectBody(String.class).consumeWith(result -> { - assertThat(result.getResponseBody()).contains("permanently broken"); - }); - } - - @Test - public void retryWithBackoff() { - // @formatter:off - testClient.get() - .uri("/retry?key=retry-with-backoff&count=3") - .header(HttpHeaders.HOST, "www.retrywithbackoff.org") - .exchange() - .expectStatus().isOk() - .expectHeader().value("X-Retry-Count", CoreMatchers.equalTo("3")); - // @formatter:on - } - - @Test - public void retryFilterGetJavaDsl() { - testClient.get().uri("/retry?key=getjava&count=2").header(HttpHeaders.HOST, "www.retryjava.org").exchange() - .expectStatus().isOk().expectBody(String.class).isEqualTo("2"); - } - - @Test - public void retryFilterPost(CapturedOutput output) { - testClient.post().uri("/retrypost?key=postconfig&expectedbody=HelloConfig") - .header(HttpHeaders.HOST, "www.retrypostconfig.org").bodyValue("HelloConfig").exchange().expectStatus() - .isOk().expectBody(String.class).isEqualTo("3"); - assertThat(output).contains("disposing response connection before next iteration"); - } - - @Test - public void retryFilterPostJavaDsl() { - testClient.post().uri("/retrypost?key=post&expectedbody=Hello").header(HttpHeaders.HOST, "www.retryjava.org") - .bodyValue("Hello").exchange().expectStatus().isOk().expectBody(String.class).isEqualTo("3"); - } - - @Test - public void retryFilterPostOneTime(CapturedOutput output) { - testClient.post().uri("/retrypost?key=retryFilterPostOneTime&expectedbody=HelloGateway&count=1") - .header(HttpHeaders.HOST, "www.retrypostonceconfig.org").bodyValue("HelloGateway").exchange() - .expectStatus().isOk(); - assertThat(output).contains("setting new iteration in attr 0"); - assertThat(output).doesNotContain("setting new iteration in attr 1"); - } - - @Test - public void retriesSleepyRequest() throws Exception { - testClient.mutate().responseTimeout(Duration.ofSeconds(10)).build().get() - .uri("/sleep?key=sleepyRequest&millis=3000").header(HttpHeaders.HOST, "www.retryjava.org").exchange() - .expectStatus().isEqualTo(HttpStatus.GATEWAY_TIMEOUT); - - assertThat(TestConfig.map.get("sleepyRequest")).isNotNull().hasValue(3); - } - - @Test - public void shouldNotRetryWhenSleepyRequestPost() throws Exception { - testClient.mutate().responseTimeout(Duration.ofSeconds(10)).build().post() - .uri("/sleep?key=notRetriesSleepyRequestPost&millis=3000") - .header(HttpHeaders.HOST, "www.retry-only-get.org").exchange().expectStatus() - .isEqualTo(HttpStatus.GATEWAY_TIMEOUT); - - assertThat(TestConfig.map.get("notRetriesSleepyRequestPost")).isNotNull().hasValue(1); - } - - @Test - public void shouldNotRetryWhenSleepyRequestPostWithBody() throws Exception { - testClient.mutate().responseTimeout(Duration.ofSeconds(10)).build().post() - .uri("/sleep?key=notRetriesSleepyRequestPostWithBody&millis=3000") - .header(HttpHeaders.HOST, "www.retry-only-get.org").bodyValue("retry sleepy post with body").exchange() - .expectStatus().isEqualTo(HttpStatus.GATEWAY_TIMEOUT); - - assertThat(TestConfig.map.get("notRetriesSleepyRequestPostWithBody")).isNotNull().hasValue(1); - } - - @Test - public void shouldRetryWhenSleepyRequestGet() throws Exception { - testClient.mutate().responseTimeout(Duration.ofSeconds(10)).build().get() - .uri("/sleep?key=sleepyRequestGet&millis=3000").header(HttpHeaders.HOST, "www.retry-only-get.org") - .exchange().expectStatus().isEqualTo(HttpStatus.GATEWAY_TIMEOUT); - - assertThat(TestConfig.map.get("sleepyRequestGet")).isNotNull().hasValue(3); - } - - @Test - @SuppressWarnings("unchecked") - public void retryFilterLoadBalancedWithMultipleServers() { - String host = "www.retrywithloadbalancer.org"; - testClient.get().uri("/get").header(HttpHeaders.HOST, host).exchange().expectStatus().isOk() - .expectBody(Map.class).consumeWith(res -> { - Map body = res.getResponseBody(); - assertThat(body).isNotNull(); - Map headers = (Map) body.get("headers"); - assertThat(headers).containsEntry("X-Forwarded-Host", host); - }); - } - - @Test - public void retryFilterSeries() { - testClient.get().uri("/retry?key=series&failStatus=404").header(HttpHeaders.HOST, "www.retryseries.org") - .exchange().expectStatus().isOk().expectBody(String.class).isEqualTo("3"); - } - - @Test - @SuppressWarnings("unchecked") - public void toStringFormat() { - RetryConfig config = new RetryConfig(); - config.setRetries(4); - config.setMethods(HttpMethod.GET); - config.setSeries(HttpStatus.Series.SERVER_ERROR); - config.setExceptions(IOException.class); - GatewayFilter filter = new RetryGatewayFilterFactory().apply(config); - assertThat(filter.toString()).contains("4").contains("[GET]").contains("[SERVER_ERROR]") - .contains("[IOException]"); - } - - @RestController - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(DefaultTestConfig.class) - @LoadBalancerClient(name = "badservice2", configuration = TestBadLoadBalancerConfig.class) - public static class TestConfig { - - Log log = LogFactory.getLog(getClass()); - - static ConcurrentHashMap map = new ConcurrentHashMap<>(); - - @Value("${test.uri}") - private String uri; - - @RequestMapping("/httpbin/sleep") - public Mono> sleep(@RequestParam("key") String key, - @RequestParam("millis") long millisToSleep) { - AtomicInteger num = getCount(key); - int retryCount = num.incrementAndGet(); - log.warn("Retry count: " + retryCount); - return Mono.delay(Duration.ofMillis(millisToSleep)).thenReturn(ResponseEntity.status(HttpStatus.OK) - .header("X-Retry-Count", String.valueOf(retryCount)).body("slept " + millisToSleep + " ms")); - } - - @GetMapping("/httpbin/retryalwaysfail") - public ResponseEntity retryalwaysfail(@RequestParam("key") String key, - @RequestParam(name = "count", defaultValue = "3") int count) { - AtomicInteger num = getCount(key); - int i = num.incrementAndGet(); - log.warn("Retry count: " + i); - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).header("X-Retry-Count", String.valueOf(i)) - .body("permanently broken"); - } - - @PostMapping("/httpbin/retrypost") - public ResponseEntity retrypost(@RequestParam("key") String key, - @RequestParam(name = "count", defaultValue = "3") int count, - @RequestParam("expectedbody") String expectedbody, @RequestBody String body) { - ResponseEntity response = retry(key, count, null); - if (!expectedbody.equals(body)) { - AtomicInteger num = getCount(key); - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) - .header("X-Retry-Count", String.valueOf(num)).body("body did not match on try" + num); - } - return response; - } - - @GetMapping("/httpbin/retry") - public ResponseEntity retry(@RequestParam("key") String key, - @RequestParam(name = "count", defaultValue = "3") int count, - @RequestParam(name = "failStatus", required = false) Integer failStatus) { - AtomicInteger num = getCount(key); - 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); - } - - AtomicInteger getCount(String key) { - return map.computeIfAbsent(key, s -> new AtomicInteger()); - } - - @Bean - public RouteLocator hystrixRouteLocator(RouteLocatorBuilder builder) { - return builder.routes().route("retry_java", - r -> r.host("**.retryjava.org") - .filters(f -> f.prefixPath("/httpbin") - .retry(config -> config.setRetries(2).setMethods(HttpMethod.POST, HttpMethod.GET))) - .uri(uri)) - .route("retry_series", - r -> r.host("**.retryseries.org") - .filters(f -> f.prefixPath("/httpbin").retry( - config -> config.setRetries(2).setSeries(HttpStatus.Series.CLIENT_ERROR))) - .uri(uri)) - .route("retry_only_get", - r -> r.host("**.retry-only-get.org") - .filters(f -> f.prefixPath("/httpbin") - .retry(config -> config.setRetries(2).setMethods(HttpMethod.GET))) - .uri(uri)) - .route("retry_with_backoff", r -> r.host("**.retrywithbackoff.org") - .filters(f -> f.prefixPath("/httpbin").retry(config -> { - config.setRetries(2).setBackoff(Duration.ofMillis(100), null, 2, true); - })).uri(uri)) - - .route("retry_with_loadbalancer", - r -> r.host("**.retrywithloadbalancer.org") - .filters(f -> f.prefixPath("/httpbin").retry(config -> config.setRetries(2))) - .uri("lb://badservice2")) - .build(); - } - - } - - protected static class TestBadLoadBalancerConfig { - - @LocalServerPort - protected int port = 0; - - @Bean - public ServiceInstanceListSupplier staticServiceInstanceListSupplier() { - return ServiceInstanceListSuppliers.from("badservice2", - new DefaultServiceInstance("doesnotexist1", "badservice2", "localhost.domain.doesnot.exist", port, - true), - new DefaultServiceInstance("badservice2-1", "badservice2", "localhost", port, false)); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RewriteLocationResponseHeaderGatewayFilterFactoryTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RewriteLocationResponseHeaderGatewayFilterFactoryTests.java deleted file mode 100644 index 318a1033..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RewriteLocationResponseHeaderGatewayFilterFactoryTests.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -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.cloud.gateway.test.BaseWebClientTests; -import org.springframework.context.annotation.Import; -import org.springframework.test.annotation.DirtiesContext; - -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; - -@SpringBootTest(webEnvironment = RANDOM_PORT) -@DirtiesContext -public class RewriteLocationResponseHeaderGatewayFilterFactoryTests extends BaseWebClientTests { - - @Test - public void rewriteLocationResponseHeaderFilterWorks() { - testClient.post().uri("/headers").header("Host", "test1.rewritelocationresponseheader.org").exchange() - .expectStatus().isOk().expectHeader() - .valueEquals("Location", "https://test1.rewritelocationresponseheader.org/some/object/id"); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(DefaultTestConfig.class) - public static class TestConfig { - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RewriteLocationResponseHeaderGatewayFilterFactoryUnitTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RewriteLocationResponseHeaderGatewayFilterFactoryUnitTests.java deleted file mode 100644 index ae04e904..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RewriteLocationResponseHeaderGatewayFilterFactoryUnitTests.java +++ /dev/null @@ -1,218 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import java.net.URI; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.junit.jupiter.MockitoExtension; -import org.mockito.junit.jupiter.MockitoSettings; -import org.mockito.quality.Strictness; - -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.factory.RewriteLocationResponseHeaderGatewayFilterFactory.Config; -import org.springframework.cloud.gateway.filter.factory.RewriteLocationResponseHeaderGatewayFilterFactory.StripVersion; -import org.springframework.http.HttpHeaders; -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.http.server.reactive.ServerHttpResponse; -import org.springframework.web.server.ServerWebExchange; - -import static org.assertj.core.api.Assertions.assertThat; - -@ExtendWith(MockitoExtension.class) -@MockitoSettings(strictness = Strictness.LENIENT) -public class RewriteLocationResponseHeaderGatewayFilterFactoryUnitTests { - - @InjectMocks - private RewriteLocationResponseHeaderGatewayFilterFactory filter; - - @Mock - private ServerWebExchange exchange; - - @Mock - private ServerHttpRequest request; - - @Mock - private ServerHttpResponse response; - - @Mock - private HttpHeaders requestHeaders; - - @Mock - private HttpHeaders responseHeaders; - - private URI uri; - - private Config config; - - @BeforeEach - public void setUp() { - filter = new RewriteLocationResponseHeaderGatewayFilterFactory(); - Mockito.when(exchange.getRequest()).thenReturn(request); - Mockito.when(exchange.getResponse()).thenReturn(response); - Mockito.when(request.getHeaders()).thenReturn(requestHeaders); - Mockito.when(response.getHeaders()).thenReturn(responseHeaders); - config = new Config(); - } - - private void setupTest(String location, String host, String path) { - Mockito.when(responseHeaders.getFirst(HttpHeaders.LOCATION)).thenReturn(location); - Mockito.when(requestHeaders.getFirst(HttpHeaders.HOST)).thenReturn(host); - uri = URI.create("http://" + host + path); - Mockito.when(request.getURI()).thenReturn(uri); - } - - @Test - public void rewriteLocationNullLocation() { - setupTest(null, "host", "/path"); - filter.rewriteLocation(exchange, config); - Mockito.verify(responseHeaders, Mockito.never()).set(Mockito.anyString(), Mockito.anyString()); - } - - @Test - public void rewriteLocationNullHost() { - setupTest("location", null, "/path"); - filter.rewriteLocation(exchange, config); - Mockito.verify(responseHeaders, Mockito.never()).set(Mockito.anyString(), Mockito.anyString()); - } - - @Test - public void rewriteLocation() { - setupTest("location", "host", "/path"); - filter.rewriteLocation(exchange, config); - Mockito.verify(responseHeaders).set(Mockito.eq("Location"), Mockito.eq("location")); - } - - @Test - public void rewriteLocationCustomHeaderName() { - setupTest("location", "host", "/path"); - Mockito.when(responseHeaders.getFirst("Link")).thenReturn("link"); - config.setLocationHeaderName("Link"); - filter.rewriteLocation(exchange, config); - Mockito.verify(responseHeaders).set(Mockito.eq("Link"), Mockito.eq("link")); - } - - @Test - public void rewriteLocationCustomHostValue() { - setupTest("https://replaceme/some/path", "host", "/some/path"); - config.setHostValue("different.host"); - filter.rewriteLocation(exchange, config); - Mockito.verify(responseHeaders).set(Mockito.eq("Location"), Mockito.eq("https://different.host/some/path")); - } - - @Test - public void rewriteLocationCustomProtocols() { - setupTest("https://replaceme/some/path", "host", "/some/path"); - config.setProtocols("gopher|whatever"); - filter.rewriteLocation(exchange, config); - Mockito.verify(responseHeaders).set(Mockito.eq("Location"), Mockito.eq("https://replaceme/some/path")); - } - - @Test - public void fixedLocationVersionedAlwaysStrip() { - String location = "https://backend-url.example.com:443/v1/path/to/riches"; - String host = "example.com:443"; - String path = "/v1/path/to/riches"; - setupTest(location, host, path); - assertThat(filter.fixedLocation(location, host, path, StripVersion.ALWAYS_STRIP, config.getHostPortPattern(), - config.getHostPortVersionPattern())).isEqualTo("https://example.com:443/path/to/riches"); - } - - @Test - public void fixedLocationVersionedStripAsInRequest() { - String location = "https://backend-url.example.com:443/v1/path/to/riches"; - String host = "example.com:443"; - String path = "/v1/path/to/riches"; - setupTest(location, host, path); - assertThat(filter.fixedLocation(location, host, path, StripVersion.AS_IN_REQUEST, config.getHostPortPattern(), - config.getHostPortVersionPattern())).isEqualTo("https://example.com:443/v1/path/to/riches"); - } - - @Test - public void fixedLocationVersionedDontStrip() { - String location = "https://backend-url.example.com:443/v1/path/to/riches"; - String host = "example.com:443"; - String path = "/v1/path/to/riches"; - setupTest(location, host, path); - assertThat(filter.fixedLocation(location, host, path, StripVersion.NEVER_STRIP, config.getHostPortPattern(), - config.getHostPortVersionPattern())).isEqualTo("https://example.com:443/v1/path/to/riches"); - } - - @Test - public void fixedLocationUnversionedAlwaysStrip() { - String location = "https://backend-url.example.com:443/v2/path/to/riches"; - String host = "api.example.com:443"; - String path = "/path/to/riches"; - setupTest(location, host, path); - assertThat(filter.fixedLocation(location, host, path, StripVersion.ALWAYS_STRIP, config.getHostPortPattern(), - config.getHostPortVersionPattern())).isEqualTo("https://api.example.com:443/path/to/riches"); - } - - @Test - public void fixedLocationUnversionedStripAsInRequest() { - String location = "https://backend-url.example.com:443/v2/path/to/riches"; - String host = "api.example.com:443"; - String path = "/path/to/riches"; - setupTest(location, host, path); - assertThat(filter.fixedLocation(location, host, path, StripVersion.AS_IN_REQUEST, config.getHostPortPattern(), - config.getHostPortVersionPattern())).isEqualTo("https://api.example.com:443/path/to/riches"); - } - - @Test - public void fixedLocationUnversionedDontStrip() { - String location = "https://backend-url.example.com:443/v2/path/to/riches"; - String host = "api.example.com:443"; - String path = "/path/to/riches"; - setupTest(location, host, path); - assertThat(filter.fixedLocation(location, host, path, StripVersion.NEVER_STRIP, config.getHostPortPattern(), - config.getHostPortVersionPattern())).isEqualTo("https://api.example.com:443/v2/path/to/riches"); - } - - @Test - public void fixedLocationNoPort() { - String location = "https://backend-url.example.com/v2/path/to/riches"; - String host = "api.example.com:443"; - String path = "/path/to/riches"; - setupTest(location, host, path); - assertThat(filter.fixedLocation(location, host, path, StripVersion.AS_IN_REQUEST, config.getHostPortPattern(), - config.getHostPortVersionPattern())).isEqualTo("https://api.example.com:443/path/to/riches"); - } - - @Test - public void toStringFormat() { - // @formatter:off - Config config = new Config().setStripVersion(StripVersion.ALWAYS_STRIP) - .setLocationHeaderName("mylocation") - .setHostValue("myhost") - .setProtocols("myproto"); - GatewayFilter filter = new RewriteLocationResponseHeaderGatewayFilterFactory() - .apply(config); - assertThat(filter.toString()) - .contains("ALWAYS_STRIP") - .contains("mylocation") - .contains("myhost") - .contains("myproto"); - // @formatter:on - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RewritePathGatewayFilterFactoryIntegrationTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RewritePathGatewayFilterFactoryIntegrationTests.java deleted file mode 100644 index 414c61a5..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RewritePathGatewayFilterFactoryIntegrationTests.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -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.cloud.gateway.test.BaseWebClientTests; -import org.springframework.context.annotation.Import; -import org.springframework.test.annotation.DirtiesContext; - -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; - -@SpringBootTest(webEnvironment = RANDOM_PORT) -@DirtiesContext -public class RewritePathGatewayFilterFactoryIntegrationTests extends BaseWebClientTests { - - @Test - public void rewritePathFilterWorks() { - testClient.get().uri("/foo/get").header("Host", "www.baz.org").exchange().expectStatus().isOk(); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(DefaultTestConfig.class) - public static class TestConfig { - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RewritePathGatewayFilterFactoryTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RewritePathGatewayFilterFactoryTests.java deleted file mode 100644 index 18ec28c5..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RewritePathGatewayFilterFactoryTests.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import java.net.URI; -import java.util.LinkedHashSet; - -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; -import reactor.core.publisher.Mono; - -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.GatewayFilterChain; -import org.springframework.cloud.gateway.filter.factory.RewritePathGatewayFilterFactory.Config; -import org.springframework.http.HttpMethod; -import org.springframework.mock.http.server.reactive.MockServerHttpRequest; -import org.springframework.mock.web.server.MockServerWebExchange; -import org.springframework.web.server.ServerWebExchange; -import org.springframework.web.util.UriComponentsBuilder; - -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 - */ -public class RewritePathGatewayFilterFactoryTests { - - @Test - public void rewritePathFilterWorks() { - testRewriteFilter("/foo", "/baz", "/foo/bar", "/baz/bar"); - } - - @Test - public void rewriteEncodedPathFilterWorks() { - testRewriteFilter("/foo", "/baz", "/foo/bar%20foobar", "/baz/bar foobar"); - } - - @Test - public void rewritePathFilterWithNamedGroupWorks() { - testRewriteFilter("/foo/(?\\d.*)", "/bar/baz/$\\{id}", "/foo/123", "/bar/baz/123"); - } - - private ServerWebExchange testRewriteFilter(String regex, String replacement, String actualPath, - String expectedPath) { - GatewayFilter filter = new RewritePathGatewayFilterFactory() - .apply(c -> c.setRegexp(regex).setReplacement(replacement)); - - URI url = UriComponentsBuilder.fromUriString("http://localhost" + actualPath).build(true).toUri(); - MockServerHttpRequest request = MockServerHttpRequest.method(HttpMethod.GET, url).build(); - - ServerWebExchange exchange = MockServerWebExchange.from(request); - - 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(expectedPath); - - URI requestUrl = webExchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR); - assertThat(requestUrl).hasScheme("http").hasHost("localhost").hasNoPort().hasPath(expectedPath); - LinkedHashSet uris = webExchange.getRequiredAttribute(GATEWAY_ORIGINAL_REQUEST_URL_ATTR); - assertThat(uris).contains(request.getURI()); - - return webExchange; - } - - @Test - public void rewritePathWithEncodedParams() { - ServerWebExchange exchange = testRewriteFilter("/foo", "/baz", "/foo/bar?name=%E6%89%8E%E6%A0%B9", "/baz/bar"); - - URI uri = exchange.getRequest().getURI(); - assertThat(uri.getRawQuery()).isEqualTo("name=%E6%89%8E%E6%A0%B9"); - } - - @Test - public void toStringFormat() { - Config config = new Config().setRegexp("regexp1").setReplacement("replacement1"); - GatewayFilter filter = new RewritePathGatewayFilterFactory().apply(config); - assertThat(filter.toString()).contains("regexp1").contains("replacement1"); - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RewriteResponseHeaderGatewayFilterFactoryTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RewriteResponseHeaderGatewayFilterFactoryTests.java deleted file mode 100644 index db65493f..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RewriteResponseHeaderGatewayFilterFactoryTests.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -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.cloud.gateway.test.BaseWebClientTests; -import org.springframework.context.annotation.Import; -import org.springframework.test.annotation.DirtiesContext; - -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; - -@SpringBootTest(webEnvironment = RANDOM_PORT) -@DirtiesContext -public class RewriteResponseHeaderGatewayFilterFactoryTests extends BaseWebClientTests { - - @Test - public void rewriteResponseHeaderFilterWorks() { - testClient.get().uri("/headers").header("Host", "www.rewriteresponseheader.org").exchange().expectStatus() - .isOk().expectHeader().valueEquals("X-Request-Foo", "/42?user=ford&password=***&flag=true"); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(DefaultTestConfig.class) - public static class TestConfig { - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RewriteResponseHeaderGatewayFilterFactoryUnitTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RewriteResponseHeaderGatewayFilterFactoryUnitTests.java deleted file mode 100644 index eeba7e0a..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/RewriteResponseHeaderGatewayFilterFactoryUnitTests.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import java.util.List; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.factory.RewriteResponseHeaderGatewayFilterFactory.Config; -import org.springframework.http.HttpHeaders; -import org.springframework.http.server.reactive.ServerHttpResponse; -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.http.HttpHeaders.SET_COOKIE; - -public class RewriteResponseHeaderGatewayFilterFactoryUnitTests { - - private RewriteResponseHeaderGatewayFilterFactory filterFactory; - - @BeforeEach - public void setUp() { - filterFactory = new RewriteResponseHeaderGatewayFilterFactory(); - } - - @Test - public void testRewriteDollarSlash() { - assertThat(filterFactory.rewrite("/foo/bar", "/foo/(?.*)", "/$\\{segment}/$\\{segment}/42")) - .isEqualTo("/bar/bar/42"); - } - - @Test - public void testRewriteMultiple() { - assertThat(filterFactory.rewrite("/foo/bar/wat/bar", "bar", "cafe")).isEqualTo("/foo/cafe/wat/cafe"); - } - - @Test - public void testRewriteMultipleHeaders() { - Config config = new Config(); - config.setName(SET_COOKIE); - config.setRegexp("SameSite[^;]+"); - config.setReplacement("SameSite=Strict"); - - ServerWebExchange exchange = mock(ServerWebExchange.class); - ServerHttpResponse response = mock(ServerHttpResponse.class); - HttpHeaders headers = new HttpHeaders(); - headers.add(SET_COOKIE, "TestCookie=Value;SameSite=Lax"); - headers.add(SET_COOKIE, "OtherCookie=Value;SameSite=Lax"); - when(response.getHeaders()).thenReturn(headers); - when(exchange.getResponse()).thenReturn(response); - - filterFactory.rewriteHeaders(exchange, config); - List actualHeaders = headers.get(SET_COOKIE); - assertThat(actualHeaders).isNotNull(); - assertThat(actualHeaders).containsExactly("TestCookie=Value;SameSite=Strict", - "OtherCookie=Value;SameSite=Strict"); - } - - @Test - public void toStringFormat() { - Config config = new Config(); - config.setName("myname"); - config.setRegexp("myregexp"); - config.setReplacement("myreplacement"); - GatewayFilter filter = new RewriteResponseHeaderGatewayFilterFactory().apply(config); - assertThat(filter.toString()).contains("myname").contains("myregexp").contains("myreplacement"); - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/SaveSessionGatewayFilterFactoryTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/SaveSessionGatewayFilterFactoryTests.java deleted file mode 100644 index 74c7aab1..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/SaveSessionGatewayFilterFactoryTests.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import java.time.Duration; -import java.util.HashMap; -import java.util.Map; - -import org.junit.jupiter.api.Test; -import reactor.core.publisher.Mono; -import reactor.test.StepVerifier; - -import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.test.BaseWebClientTests; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.web.server.WebSession; -import org.springframework.web.server.session.WebSessionManager; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; - -/** - * @author Greg Turnquist - */ -@SpringBootTest(webEnvironment = RANDOM_PORT) -@DirtiesContext -@ActiveProfiles(profiles = "save-session-web-filter") -public class SaveSessionGatewayFilterFactoryTests extends BaseWebClientTests { - - static WebSession mockWebSession = mock(WebSession.class); - - @Test - public void webCallShouldTriggerWebSessionSaveAction() { - - when(mockWebSession.getAttributes()).thenReturn(new HashMap<>()); - when(mockWebSession.save()).thenReturn(Mono.empty()); - - Mono result = webClient.get().uri("/get").retrieve().bodyToMono(Map.class); - - StepVerifier.create(result).consumeNextWith(response -> { - // Don't care about data, just need to catch signal - }).expectComplete().verify(Duration.ofMinutes(10)); - - verify(mockWebSession).save(); - } - - @Test - public void toStringFormat() { - GatewayFilter filter = new SaveSessionGatewayFilterFactory().apply(""); - assertThat(filter.toString()).contains("SaveSession"); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(DefaultTestConfig.class) - static class TestConfig { - - @Bean - WebSessionManager webSessionManager() { - return exchange -> Mono.just(mockWebSession); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/SecureHeadersGatewayFilterFactoryTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/SecureHeadersGatewayFilterFactoryTests.java deleted file mode 100644 index 11c104a9..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/SecureHeadersGatewayFilterFactoryTests.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import org.junit.jupiter.api.Test; -import reactor.core.publisher.Mono; -import reactor.test.StepVerifier; - -import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.gateway.test.BaseWebClientTests; -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.reactive.function.client.ClientResponse; - -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.filter.factory.SecureHeadersGatewayFilterFactory.CONTENT_SECURITY_POLICY_HEADER; -import static org.springframework.cloud.gateway.filter.factory.SecureHeadersGatewayFilterFactory.REFERRER_POLICY_HEADER; -import static org.springframework.cloud.gateway.filter.factory.SecureHeadersGatewayFilterFactory.STRICT_TRANSPORT_SECURITY_HEADER; -import static org.springframework.cloud.gateway.filter.factory.SecureHeadersGatewayFilterFactory.X_CONTENT_TYPE_OPTIONS_HEADER; -import static org.springframework.cloud.gateway.filter.factory.SecureHeadersGatewayFilterFactory.X_DOWNLOAD_OPTIONS_HEADER; -import static org.springframework.cloud.gateway.filter.factory.SecureHeadersGatewayFilterFactory.X_FRAME_OPTIONS_HEADER; -import static org.springframework.cloud.gateway.filter.factory.SecureHeadersGatewayFilterFactory.X_PERMITTED_CROSS_DOMAIN_POLICIES_HEADER; -import static org.springframework.cloud.gateway.test.TestUtils.assertStatus; - -@SpringBootTest(webEnvironment = RANDOM_PORT) -@DirtiesContext -public class SecureHeadersGatewayFilterFactoryTests extends BaseWebClientTests { - - @Test - public void secureHeadersFilterWorks() { - Mono result = webClient.get().uri("/headers").header("Host", "www.secureheaders.org") - .exchangeToMono(Mono::just); - - SecureHeadersProperties defaults = new SecureHeadersProperties(); - - StepVerifier.create(result).consumeNextWith(response -> { - assertStatus(response, HttpStatus.OK); - HttpHeaders httpHeaders = response.headers().asHttpHeaders(); - // assertThat(httpHeaders.getFirst(X_XSS_PROTECTION_HEADER)).isEqualTo(defaults.getXssProtectionHeader()); - assertThat(httpHeaders.getFirst(STRICT_TRANSPORT_SECURITY_HEADER)) - .isEqualTo(defaults.getStrictTransportSecurity()); - assertThat(httpHeaders.getFirst(X_FRAME_OPTIONS_HEADER)).isEqualTo(defaults.getFrameOptions()); - assertThat(httpHeaders.getFirst(X_CONTENT_TYPE_OPTIONS_HEADER)).isEqualTo(defaults.getContentTypeOptions()); - assertThat(httpHeaders.getFirst(REFERRER_POLICY_HEADER)).isEqualTo(defaults.getReferrerPolicy()); - assertThat(httpHeaders.getFirst(CONTENT_SECURITY_POLICY_HEADER)) - .isEqualTo(defaults.getContentSecurityPolicy()); - assertThat(httpHeaders.getFirst(X_DOWNLOAD_OPTIONS_HEADER)).isEqualTo(defaults.getDownloadOptions()); - assertThat(httpHeaders.getFirst(X_PERMITTED_CROSS_DOMAIN_POLICIES_HEADER)) - .isEqualTo(defaults.getPermittedCrossDomainPolicies()); - }).expectComplete().verify(DURATION); - } - - @Test - public void addsSecureHeadersAfterResponseIsReceived() { - Mono> responseEntity = webClient.patch().uri("/headers") - .header("Host", "www.secureheaders.org").contentType(MediaType.APPLICATION_JSON) - .bodyValue("{ \"X-Frame-Options\": \"sameorigin\" }").retrieve().toEntity(String.class); - - StepVerifier.create(responseEntity).consumeNextWith(response -> { - assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); - assertThat(response.getHeaders().get(X_FRAME_OPTIONS_HEADER)).containsOnly("sameorigin"); - }).expectComplete().verify(DURATION); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(DefaultTestConfig.class) - public static class TestConfig { - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/SecureHeadersGatewayFilterFactoryUnitTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/SecureHeadersGatewayFilterFactoryUnitTests.java deleted file mode 100644 index 167238a1..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/SecureHeadersGatewayFilterFactoryUnitTests.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; -import reactor.core.publisher.Mono; - -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.GatewayFilterChain; -import org.springframework.http.server.reactive.ServerHttpResponse; -import org.springframework.mock.http.server.reactive.MockServerHttpRequest; -import org.springframework.mock.web.server.MockServerWebExchange; -import org.springframework.web.server.ServerWebExchange; - -import static java.util.Arrays.asList; -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.filter.factory.SecureHeadersGatewayFilterFactory.CONTENT_SECURITY_POLICY_HEADER; -import static org.springframework.cloud.gateway.filter.factory.SecureHeadersGatewayFilterFactory.Config; -import static org.springframework.cloud.gateway.filter.factory.SecureHeadersGatewayFilterFactory.REFERRER_POLICY_HEADER; -import static org.springframework.cloud.gateway.filter.factory.SecureHeadersGatewayFilterFactory.STRICT_TRANSPORT_SECURITY_HEADER; -import static org.springframework.cloud.gateway.filter.factory.SecureHeadersGatewayFilterFactory.X_CONTENT_TYPE_OPTIONS_HEADER; -import static org.springframework.cloud.gateway.filter.factory.SecureHeadersGatewayFilterFactory.X_DOWNLOAD_OPTIONS_HEADER; -import static org.springframework.cloud.gateway.filter.factory.SecureHeadersGatewayFilterFactory.X_FRAME_OPTIONS_HEADER; -import static org.springframework.cloud.gateway.filter.factory.SecureHeadersGatewayFilterFactory.X_PERMITTED_CROSS_DOMAIN_POLICIES_HEADER; -import static org.springframework.cloud.gateway.filter.factory.SecureHeadersGatewayFilterFactory.X_XSS_PROTECTION_HEADER; - -/** - * @author Thirunavukkarasu Ravichandran - */ -public class SecureHeadersGatewayFilterFactoryUnitTests { - - private GatewayFilter filter; - - private ServerWebExchange exchange; - - private GatewayFilterChain filterChain; - - private ArgumentCaptor captor; - - @BeforeEach - public void setUp() { - MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost").build(); - exchange = MockServerWebExchange.from(request); - filterChain = mock(GatewayFilterChain.class); - captor = ArgumentCaptor.forClass(ServerWebExchange.class); - when(filterChain.filter(captor.capture())).thenReturn(Mono.empty()); - - } - - @Test - public void addAllHeadersIfNothingIsDisabled() { - SecureHeadersGatewayFilterFactory filterFactory = new SecureHeadersGatewayFilterFactory( - new SecureHeadersProperties()); - filter = filterFactory.apply(new Config()); - - filter.filter(exchange, filterChain).block(); - - ServerHttpResponse response = exchange.getResponse(); - assertThat(response.getHeaders()).containsKeys(X_XSS_PROTECTION_HEADER, STRICT_TRANSPORT_SECURITY_HEADER, - X_FRAME_OPTIONS_HEADER, X_CONTENT_TYPE_OPTIONS_HEADER, REFERRER_POLICY_HEADER, - CONTENT_SECURITY_POLICY_HEADER, X_DOWNLOAD_OPTIONS_HEADER, X_PERMITTED_CROSS_DOMAIN_POLICIES_HEADER); - } - - @Test - public void doNotAddDisabledHeaders() { - SecureHeadersProperties properties = new SecureHeadersProperties(); - properties.setDisable(asList("x-xss-protection", "strict-transport-security", "x-frame-options", - "x-content-type-options", "referrer-policy", "content-security-policy", "x-download-options", - "x-permitted-cross-domain-policies")); - - SecureHeadersGatewayFilterFactory filterFactory = new SecureHeadersGatewayFilterFactory(properties); - filter = filterFactory.apply(new Config()); - - filter.filter(exchange, filterChain).block(); - - ServerHttpResponse response = captor.getValue().getResponse(); - assertThat(response.getHeaders()).doesNotContainKeys(X_XSS_PROTECTION_HEADER, STRICT_TRANSPORT_SECURITY_HEADER, - X_FRAME_OPTIONS_HEADER, X_CONTENT_TYPE_OPTIONS_HEADER, REFERRER_POLICY_HEADER, - CONTENT_SECURITY_POLICY_HEADER, X_DOWNLOAD_OPTIONS_HEADER, X_PERMITTED_CROSS_DOMAIN_POLICIES_HEADER); - - } - - @Test - public void overrideSomeHeaders() { - SecureHeadersProperties properties = new SecureHeadersProperties(); - SecureHeadersGatewayFilterFactory filterFactory = new SecureHeadersGatewayFilterFactory( - new SecureHeadersProperties()); - Config config = new Config(); - config.setStrictTransportSecurity("max-age=65535"); - config.setReferrerPolicy("referrer"); - filter = filterFactory.apply(config); - - filter.filter(exchange, filterChain).block(); - - ServerHttpResponse response = exchange.getResponse(); - assertThat(response.getHeaders()).containsKeys(X_XSS_PROTECTION_HEADER, STRICT_TRANSPORT_SECURITY_HEADER, - X_FRAME_OPTIONS_HEADER, X_CONTENT_TYPE_OPTIONS_HEADER, REFERRER_POLICY_HEADER, - CONTENT_SECURITY_POLICY_HEADER, X_DOWNLOAD_OPTIONS_HEADER, X_PERMITTED_CROSS_DOMAIN_POLICIES_HEADER); - - assertThat(response.getHeaders().get(STRICT_TRANSPORT_SECURITY_HEADER)).containsOnly("max-age=65535"); - assertThat(response.getHeaders().get(REFERRER_POLICY_HEADER)).containsOnly("referrer"); - - assertThat(response.getHeaders().get(X_XSS_PROTECTION_HEADER)) - .containsOnly(properties.getXssProtectionHeader()); - assertThat(response.getHeaders().get(X_FRAME_OPTIONS_HEADER)).containsOnly(properties.getFrameOptions()); - assertThat(response.getHeaders().get(X_CONTENT_TYPE_OPTIONS_HEADER)) - .containsOnly(properties.getContentTypeOptions()); - assertThat(response.getHeaders().get(CONTENT_SECURITY_POLICY_HEADER)) - .containsOnly(properties.getContentSecurityPolicy()); - assertThat(response.getHeaders().get(X_DOWNLOAD_OPTIONS_HEADER)).containsOnly(properties.getDownloadOptions()); - assertThat(response.getHeaders().get(X_PERMITTED_CROSS_DOMAIN_POLICIES_HEADER)) - .containsOnly(properties.getPermittedCrossDomainPolicies()); - - } - - @Test - public void doesNotDuplicateHeaders() { - String originalHeaderValue = "original-header-value"; - SecureHeadersGatewayFilterFactory filterFactory = new SecureHeadersGatewayFilterFactory( - new SecureHeadersProperties()); - Config config = new Config(); - - String[] headers = { X_XSS_PROTECTION_HEADER, STRICT_TRANSPORT_SECURITY_HEADER, X_FRAME_OPTIONS_HEADER, - X_CONTENT_TYPE_OPTIONS_HEADER, REFERRER_POLICY_HEADER, CONTENT_SECURITY_POLICY_HEADER, - X_DOWNLOAD_OPTIONS_HEADER, X_PERMITTED_CROSS_DOMAIN_POLICIES_HEADER }; - - for (String header : headers) { - filter = filterFactory.apply(config); - - MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost").build(); - exchange = MockServerWebExchange.from(request); - exchange.getResponse().getHeaders().set(header, originalHeaderValue); - - filter.filter(exchange, filterChain).block(); - - ServerHttpResponse response = captor.getValue().getResponse(); - assertThat(response.getHeaders().get(header)).containsOnly(originalHeaderValue); - } - } - - @Test - public void toStringFormat() { - GatewayFilter filter = new SecureHeadersGatewayFilterFactory(new SecureHeadersProperties()).apply(new Config()); - Assertions.assertThat(filter.toString()).contains("SecureHeaders"); - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/SetPathGatewayFilterFactoryIntegrationTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/SetPathGatewayFilterFactoryIntegrationTests.java deleted file mode 100644 index c58a57c6..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/SetPathGatewayFilterFactoryIntegrationTests.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -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.cloud.gateway.test.BaseWebClientTests; -import org.springframework.context.annotation.Import; -import org.springframework.test.annotation.DirtiesContext; - -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; - -@SpringBootTest(webEnvironment = RANDOM_PORT) -@DirtiesContext -public class SetPathGatewayFilterFactoryIntegrationTests extends BaseWebClientTests { - - @Test - public void setPathFilterDefaultValuesWork() { - testClient.get().uri("/foo/get").header("Host", "www.setpath.org").exchange().expectStatus().isOk() - .expectHeader().valueEquals(ROUTE_ID_HEADER, "set_path_test"); - } - - @Test - public void setPathViaHostFilterWork() { - testClient.get().uri("/").header("Host", "get.setpathhost.org").exchange().expectStatus().isOk().expectHeader() - .valueEquals(ROUTE_ID_HEADER, "set_path_host_test"); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(DefaultTestConfig.class) - public static class TestConfig { - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/SetPathGatewayFilterFactoryTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/SetPathGatewayFilterFactoryTests.java deleted file mode 100644 index 14c08fe1..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/SetPathGatewayFilterFactoryTests.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import java.net.URI; -import java.util.HashMap; -import java.util.LinkedHashSet; - -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; -import reactor.core.publisher.Mono; - -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.GatewayFilterChain; -import org.springframework.cloud.gateway.filter.factory.SetPathGatewayFilterFactory.Config; -import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; -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; - -/** - * @author Spencer Gibb - */ -public class SetPathGatewayFilterFactoryTests { - - @Test - public void setPathFilterWorks() { - HashMap variables = new HashMap<>(); - testFilter("/baz/bar", "/baz/bar", variables); - } - - @Test - public void setEncodedPathFilterWorks() { - HashMap variables = new HashMap<>(); - testFilter("/baz/foo%20bar", "/baz/foo%20bar", variables); - } - - @Test - public void setPathFilterWithTemplateVarsWorks() { - HashMap variables = new HashMap<>(); - variables.put("id", "123"); - testFilter("/bar/baz/{id}", "/bar/baz/123", variables); - } - - @Test - public void setPathFilterWithTemplatePrefixVarsWorks() { - HashMap variables = new HashMap<>(); - variables.put("org", "123"); - variables.put("scope", "abc"); - testFilter("/{org}/{scope}/function", "/123/abc/function", variables); - } - - @Test - public void setPathFilterWithEncodedCharactersWorks() { - HashMap variables = new HashMap<>(); - variables.put("id", "12 3"); - testFilter("/bar/baz/{id}", "/bar/baz/12 3", variables); - } - - private void testFilter(String template, String expectedPath, HashMap variables) { - GatewayFilter filter = new SetPathGatewayFilterFactory().apply(c -> c.setTemplate(template)); - - MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost").build(); - - ServerWebExchange exchange = MockServerWebExchange.from(request); - ServerWebExchangeUtils.putUriTemplateVariables(exchange, variables); - - 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(expectedPath); - LinkedHashSet uris = webExchange.getRequiredAttribute(GATEWAY_ORIGINAL_REQUEST_URL_ATTR); - assertThat(uris).contains(request.getURI()); - } - - @Test - public void toStringFormat() { - Config config = new Config(); - config.setTemplate("mytemplate"); - GatewayFilter filter = new SetPathGatewayFilterFactory().apply(config); - assertThat(filter.toString()).contains("mytemplate"); - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/SetRequestHeaderGatewayFilterFactoryTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/SetRequestHeaderGatewayFilterFactoryTests.java deleted file mode 100644 index f3386d34..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/SetRequestHeaderGatewayFilterFactoryTests.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import java.util.Map; - -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.factory.AbstractNameValueGatewayFilterFactory.NameValueConfig; -import org.springframework.cloud.gateway.route.RouteLocator; -import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; -import org.springframework.cloud.gateway.test.BaseWebClientTests; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; -import org.springframework.test.annotation.DirtiesContext; - -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; - -/** - * @author Spencer Gibb - * @author Biju Kunjummen - */ -@SpringBootTest(webEnvironment = RANDOM_PORT) -@DirtiesContext -public class SetRequestHeaderGatewayFilterFactoryTests extends BaseWebClientTests { - - @Test - public void setRequestHeaderFilterWorks() { - testClient.get().uri("/headers").header("Host", "www.setrequestheader.org").exchange().expectStatus().isOk() - .expectBody(Map.class).consumeWith(result -> { - Map headers = getMap(result.getResponseBody(), "headers"); - // add was called first, so sets will overwrite - assertThat(headers).doesNotContainEntry("X-Req-Foo", "First"); - assertThat(headers).containsEntry("X-Req-Foo", "Second-www"); - }); - } - - @Test - public void toStringFormat() { - NameValueConfig config = new NameValueConfig().setName("myname").setValue("myvalue"); - GatewayFilter filter = new SetRequestHeaderGatewayFilterFactory().apply(config); - assertThat(filter.toString()).contains("myname").contains("myvalue"); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(DefaultTestConfig.class) - public static class TestConfig { - - @Value("${test.uri}") - String uri; - - @Bean - public RouteLocator testRouteLocator(RouteLocatorBuilder builder) { - return builder.routes().route("test_set_request_header", - r -> r.order(-1).host("{sub}.setrequestheader.org").filters(f -> f.prefixPath("/httpbin") - .addRequestHeader("X-Req-Foo", "First").setRequestHeader("X-Req-Foo", "Second-{sub}")) - .uri(uri)) - .build(); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/SetRequestHostHeaderGatewayFilterFactoryTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/SetRequestHostHeaderGatewayFilterFactoryTests.java deleted file mode 100644 index 416f9ec0..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/SetRequestHostHeaderGatewayFilterFactoryTests.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import java.util.Map; - -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.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.test.BaseWebClientTests; -import org.springframework.context.annotation.Import; -import org.springframework.test.annotation.DirtiesContext; - -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) -@DirtiesContext -public class SetRequestHostHeaderGatewayFilterFactoryTests extends BaseWebClientTests { - - @Test - public void setRequestHostHeaderFilterWorks() { - testClient.get().uri("/headers").header("Host", "www.setrequesthostheader.org").exchange().expectStatus().isOk() - .expectBody(Map.class).consumeWith(result -> { - Map headers = getMap(result.getResponseBody(), "headers"); - assertThat(headers).hasEntrySatisfying("Host", val -> assertThat(val).isEqualTo("otherhost.io")); - }); - } - - @Test - public void toStringFormat() { - SetRequestHostHeaderGatewayFilterFactory.Config config = new SetRequestHostHeaderGatewayFilterFactory.Config(); - config.setHost("myhost"); - GatewayFilter filter = new SetRequestHostHeaderGatewayFilterFactory().apply(config); - assertThat(filter.toString()).contains("myhost"); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(DefaultTestConfig.class) - public static class TestConfig { - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/SetResponseHeaderGatewayFilterFactoryTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/SetResponseHeaderGatewayFilterFactoryTests.java deleted file mode 100644 index 7bfe96f9..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/SetResponseHeaderGatewayFilterFactoryTests.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.factory.AbstractNameValueGatewayFilterFactory.NameValueConfig; -import org.springframework.cloud.gateway.route.RouteLocator; -import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; -import org.springframework.cloud.gateway.test.BaseWebClientTests; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; -import org.springframework.test.annotation.DirtiesContext; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; - -@SpringBootTest(webEnvironment = RANDOM_PORT) -@DirtiesContext -public class SetResponseHeaderGatewayFilterFactoryTests extends BaseWebClientTests { - - @Test - public void setResponseHeaderFilterWorks() { - testClient.get().uri("/headers").header("Host", "www.setreresponseheader.org").exchange().expectStatus().isOk() - .expectHeader().valueEquals("X-Response-Foo", "Bar"); - } - - @Test - public void setResponseHeaderFilterWorksJavaDsl() { - testClient.get().uri("/headers").header("Host", "www.setresponseheaderdsl.org").exchange().expectStatus().isOk() - .expectHeader().valueEquals("X-Res-Foo", "Second-www"); - } - - @Test - public void toStringFormat() { - NameValueConfig config = new NameValueConfig().setName("myname").setValue("myvalue"); - GatewayFilter filter = new SetResponseHeaderGatewayFilterFactory().apply(config); - assertThat(filter.toString()).contains("myname").contains("myvalue"); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(DefaultTestConfig.class) - public static class TestConfig { - - @Value("${test.uri}") - String uri; - - @Bean - public RouteLocator testRouteLocator(RouteLocatorBuilder builder) { - return builder.routes().route("test_set_response_header_dsl", - r -> r.order(-1).host("{sub}.setresponseheaderdsl.org").filters(f -> f.prefixPath("/httpbin") - .setResponseHeader("X-Res-Foo", "Second-{sub}").addResponseHeader("X-Res-Foo", "First")) - .uri(uri)) - .build(); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/SetStatusGatewayFilterFactoryTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/SetStatusGatewayFilterFactoryTests.java deleted file mode 100644 index 59c46287..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/SetStatusGatewayFilterFactoryTests.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import org.hamcrest.Matchers; -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.web.client.TestRestTemplate; -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.factory.SetStatusGatewayFilterFactory.Config; -import org.springframework.cloud.gateway.route.RouteLocator; -import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; -import org.springframework.cloud.gateway.test.BaseWebClientTests; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.web.reactive.server.WebTestClient; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; - -@SpringBootTest(webEnvironment = RANDOM_PORT) -@DirtiesContext -public class SetStatusGatewayFilterFactoryTests extends BaseWebClientTests { - - @Autowired - private SetStatusGatewayFilterFactory filterFactory; - - @Test - public void setStatusIntWorks() { - setStatusStringTest("www.setstatusint.org", HttpStatus.UNAUTHORIZED); - } - - @Test - public void setStatusStringWorks() { - setStatusStringTest("www.setstatusstring.org", HttpStatus.BAD_REQUEST); - } - - @Test - public void nonStandardCodeWorks() { - HttpHeaders headers = new HttpHeaders(); - headers.set(HttpHeaders.HOST, "www.setcustomstatus.org"); - ResponseEntity response = new TestRestTemplate().exchange(baseUri + "/headers", HttpMethod.GET, - new HttpEntity<>(headers), String.class); - assertThat(response.getStatusCodeValue()).isEqualTo(432); - - // https://jira.spring.io/browse/SPR-16748 - /* - * testClient.get() .uri("/status/432") .exchange() .expectStatus().isEqualTo(432) - * .expectBody(String.class).isEqualTo("Failed with 432"); - */ - } - - @Test - public void shouldSetStatusIntAndAddOriginalHeader() { - String headerName = "original-http-status"; - filterFactory.setOriginalStatusHeaderName(headerName); - setStatusStringTest("www.setstatusint.org", HttpStatus.UNAUTHORIZED).expectHeader().value(headerName, - Matchers.is("[200]")); - - } - - @Test - public void toStringFormat() { - Config config = new Config(); - config.setStatus("401"); - GatewayFilter filter = new SetStatusGatewayFilterFactory().apply(config); - assertThat(filter.toString()).contains("401"); - } - - private WebTestClient.ResponseSpec setStatusStringTest(String host, HttpStatus status) { - return testClient.get().uri("/headers").header("Host", host).exchange().expectStatus().isEqualTo(status); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(DefaultTestConfig.class) - public static class TestEnumConfig { - - @Value("${test.uri}") - String uri; - - @Bean - public RouteLocator enumRouteLocator(RouteLocatorBuilder builder) { - return builder.routes().route("test_enum_http_status", - r -> r.host("*.setenumstatus.org").filters(f -> f.setStatus(HttpStatus.UNAUTHORIZED)).uri(uri)) - .build(); - } - - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(DefaultTestConfig.class) - public static class TestConfig { - - @Value("${test.uri}") - String uri; - - @Bean - public RouteLocator myRouteLocator(RouteLocatorBuilder builder) { - // @formatter:off - return builder.routes() - .route("test_custom_http_status", - r -> r.host("*.setcustomstatus.org") - .filters(f -> f.setStatus(432)) - .uri(uri)) - .build(); - // @formatter:on - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/SpringCloudCircuitBreakerFilterFactoryTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/SpringCloudCircuitBreakerFilterFactoryTests.java deleted file mode 100644 index 335e3122..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/SpringCloudCircuitBreakerFilterFactoryTests.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import org.junit.jupiter.api.Test; - -import org.springframework.cloud.gateway.test.BaseWebClientTests; -import org.springframework.http.HttpStatus; - -import static org.springframework.http.MediaType.APPLICATION_JSON; - -/** - * @author Ryan Baxter - */ -public abstract class SpringCloudCircuitBreakerFilterFactoryTests extends BaseWebClientTests { - - @Test - public void cbFilterWorks() { - testClient.get().uri("/get").header("Host", "www.sccbsuccess.org").exchange().expectStatus().isOk() - .expectHeader().valueEquals(ROUTE_ID_HEADER, "sccb_success_test"); - } - - @Test - public void cbFilterTimesout() { - testClient.get().uri("/delay/3").header("Host", "www.sccbtimeout.org").exchange().expectStatus() - .isEqualTo(HttpStatus.GATEWAY_TIMEOUT).expectBody().jsonPath("$.status") - .isEqualTo(String.valueOf(HttpStatus.GATEWAY_TIMEOUT.value())); - } - - /* - * Tests that timeouts bubbling from the underpinning WebClient are treated the same - * as CircuitBreaker timeouts in terms of outside response. (Internally, timeouts from - * the WebClient are seen as command failures and trigger the opening of circuit - * breakers the same way timeouts do; it may be confusing in terms of the - * CircuitBreaker metrics though) - */ - @Test - public void timeoutFromWebClient() { - testClient.get().uri("/delay/10").header("Host", "www.circuitbreakerresponsestall.org").exchange() - .expectStatus().isEqualTo(HttpStatus.GATEWAY_TIMEOUT); - } - - @Test - public void filterFallback() { - testClient.get().uri("/delay/3?a=b").header("Host", "www.circuitbreakerfallback.org").exchange().expectStatus() - .isOk().expectBody().json("{\"from\":\"circuitbreakerfallbackcontroller\"}"); - } - - @Test - public void filterWithVariables() { - testClient.get().uri("/delay/3/extra?a=b").header("Host", "www.circuitbreakervariables.org").exchange() - .expectStatus().isOk().expectBody() - .json("{\"uri\":\"/circuitbreakerUriFallbackController/3/extra/www?a=b\"}"); - } - - @Test - public void filterFallbackPath() { - testClient.get().uri("/status/200").header("Host", "www.circuitbreakerfallbackpath.org").exchange() - .expectStatus().isOk().expectBody().jsonPath("$.headers").exists(); - } - - @Test - public void filterWorksJavaDsl() { - testClient.get().uri("/get").header("Host", "www.circuitbreakerjava.org").exchange().expectStatus().isOk() - .expectHeader().valueEquals(ROUTE_ID_HEADER, "circuitbreaker_java"); - } - - @Test - public void filterFallbackJavaDsl() { - testClient.get().uri("/delay/3").header("Host", "www.circuitbreakerjava.org").exchange().expectStatus().isOk() - .expectBody().json("{\"from\":\"circuitbreakerfallbackcontroller2\"}"); - } - - @Test - public void filterConnectFailure() { - testClient.get().uri("/delay/3").header("Host", "www.circuitbreakerconnectfail.org").exchange().expectStatus() - .is5xxServerError(); - } - - @Test - public void filterErrorPage() { - testClient.get().uri("/delay/3").header("Host", "www.circuitbreakerconnectfail.org").accept(APPLICATION_JSON) - .exchange().expectStatus().is5xxServerError().expectBody().jsonPath("$.status").isEqualTo(500) - .jsonPath("$.message").isNotEmpty().jsonPath("$.error").isEqualTo("Internal Server Error"); - } - - @Test - public void filterFallbackForward() { - testClient.get().uri("/delay/3?a=c").header("Host", "www.circuitbreakerforward.org").exchange().expectStatus() - .isOk().expectBody().json("{\"from\":\"circuitbreakerfallbackcontroller3\"}"); - } - - @Test - public void filterStatusCodeFallback() { - testClient.get().uri("/status/500").header("Host", "www.circuitbreakerstatuscode.org").exchange().expectStatus() - .isOk().expectBody().json("{\"from\":\"statusCodeFallbackController\"}"); - - testClient.get().uri("/status/404").header("Host", "www.circuitbreakerstatuscode.org").exchange().expectStatus() - .isOk().expectBody().json("{\"from\":\"statusCodeFallbackController\"}"); - - testClient.get().uri("/status/200").header("Host", "www.circuitbreakerstatuscode.org").exchange().expectStatus() - .isOk().expectHeader().valueEquals(ROUTE_ID_HEADER, "circuitbreaker_fallback_test_statuscode"); - } - - @Test - public void filterStatusCodeResumeWithoutError() { - testClient.get().uri("/status/500").header("Host", "www.circuitbreakerresumewithouterror.org").exchange() - .expectStatus().isEqualTo(500); - - testClient.get().uri("/status/404").header("Host", "www.circuitbreakerresumewithouterror.org").exchange() - .expectStatus().isEqualTo(404); - - testClient.get().uri("/status/200").header("Host", "www.circuitbreakerresumewithouterror.org").exchange() - .expectStatus().isOk().expectHeader() - .valueEquals(ROUTE_ID_HEADER, "circuitbreaker_resume_without_error"); - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/SpringCloudCircuitBreakerResilience4JFilterFactoryTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/SpringCloudCircuitBreakerResilience4JFilterFactoryTests.java deleted file mode 100644 index 5e745b49..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/SpringCloudCircuitBreakerResilience4JFilterFactoryTests.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry; -import io.github.resilience4j.timelimiter.TimeLimiterRegistry; -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.circuitbreaker.resilience4j.ReactiveResilience4JCircuitBreakerFactory; -import org.springframework.cloud.circuitbreaker.resilience4j.Resilience4JCircuitBreakerFactory; -import org.springframework.cloud.client.circuitbreaker.Customizer; -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.test.BaseWebClientTests; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.reactive.function.BodyInserters; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.hamcrest.core.StringContains.containsString; -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; - -/** - * @author Ryan Baxter - */ -@SpringBootTest(webEnvironment = RANDOM_PORT, - properties = { "logging.level.org.springframework.cloud.gateway=INFO", "debug=false", - "spring.cloud.circuitbreaker.hystrix.enabled=false" }) -@ContextConfiguration(classes = SpringCloudCircuitBreakerResilience4JFilterFactoryTests.Config.class) -@DirtiesContext -public class SpringCloudCircuitBreakerResilience4JFilterFactoryTests - extends SpringCloudCircuitBreakerFilterFactoryTests { - - private static final String RETRIEVED_EXCEPTION = "Retrieved-Exception"; - - @Autowired - private Resilience4JCircuitBreakerFactory factory; - - @Test - public void r4jFilterServiceUnavailable() { - testClient.get().uri("/delay/3").header("Host", "www.sccbfailure.org").exchange().expectStatus() - .isEqualTo(HttpStatus.SERVICE_UNAVAILABLE); - } - - @Test - public void r4jFilterExceptionFallback() { - testClient.get().uri("/delay/3").header("Host", "www.circuitbreakerexceptionfallback.org").exchange() - .expectStatus().isOk().expectHeader().value(RETRIEVED_EXCEPTION, containsString("TimeoutException")); - } - - @Test - public void cbFilterTimesoutMessage() { - testClient.get().uri("/delay/3").header("Host", "www.sccbtimeout.org").exchange().expectStatus() - .isEqualTo(HttpStatus.GATEWAY_TIMEOUT).expectBody().jsonPath("$.status") - .isEqualTo(String.valueOf(HttpStatus.GATEWAY_TIMEOUT.value())).jsonPath("$.message") - .value(containsString("1000ms")); - } - - @Test - public void toStringFormat() { - SpringCloudCircuitBreakerFilterFactory.Config config = new SpringCloudCircuitBreakerFilterFactory.Config() - .setName("myname").setFallbackUri("forward:/myfallback"); - GatewayFilter filter = new SpringCloudCircuitBreakerResilience4JFilterFactory( - new ReactiveResilience4JCircuitBreakerFactory(CircuitBreakerRegistry.ofDefaults(), - TimeLimiterRegistry.ofDefaults()), - null).apply(config); - assertThat(filter.toString()).contains("myname").contains("forward:/myfallback"); - } - - @Test - public void testHeadersAreClearedOnFallback() { - testClient.post().uri("/responseheaders/502").body(BodyInserters.fromFormData("name-1", "value-1")) - .header("Host", "www.circuitbreakerresetexchange.org").header("X-Test-Header-1", "value1") - .accept(MediaType.APPLICATION_JSON).exchange().expectStatus().isOk().expectHeader() - .doesNotExist("X-Test-Header-1").expectHeader().valueEquals("X-Test-Header-1-fallback", "value1"); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(BaseWebClientTests.DefaultTestConfig.class) - @RestController - static class Config extends SpringCloudCircuitBreakerTestConfig { - - @Bean - public Customizer slowCusomtizer() { - return factory -> { - factory.addCircuitBreakerCustomizer(cb -> cb.transitionToForcedOpenState(), "failcmd"); - }; - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/SpringCloudCircuitBreakerTestConfig.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/SpringCloudCircuitBreakerTestConfig.java deleted file mode 100644 index e96093c5..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/SpringCloudCircuitBreakerTestConfig.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import java.util.AbstractMap; -import java.util.Collections; -import java.util.Map; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import reactor.core.publisher.Mono; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.cloud.gateway.filter.factory.RetryGatewayFilterFactoryIntegrationTests.TestBadLoadBalancerConfig; -import org.springframework.cloud.gateway.route.RouteLocator; -import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; -import org.springframework.cloud.gateway.test.BaseWebClientTests; -import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient; -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.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.reactive.function.server.RouterFunction; -import org.springframework.web.reactive.function.server.ServerRequest; -import org.springframework.web.reactive.function.server.ServerResponse; -import org.springframework.web.server.ServerWebExchange; - -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.CIRCUITBREAKER_EXECUTION_EXCEPTION_ATTR; -import static org.springframework.web.reactive.function.server.RequestPredicates.GET; -import static org.springframework.web.reactive.function.server.RouterFunctions.route; - -/** - * @author Ryan Baxter - */ -@EnableAutoConfiguration -@SpringBootConfiguration -@Import(BaseWebClientTests.DefaultTestConfig.class) -@RestController -@LoadBalancerClient(name = "badservice", configuration = TestBadLoadBalancerConfig.class) -public class SpringCloudCircuitBreakerTestConfig { - - @Value("${test.uri}") - private String uri; - - @GetMapping("/circuitbreakerFallbackController") - public Map fallbackcontroller(@RequestParam("a") String a) { - return Collections.singletonMap("from", "circuitbreakerfallbackcontroller"); - } - - @GetMapping("/circuitbreakerUriFallbackController/**") - public Map uriFallbackcontroller(ServerWebExchange exchange, @RequestParam("a") String a) { - return Collections.singletonMap("uri", exchange.getRequest().getURI().toString()); - } - - @GetMapping("/circuitbreakerFallbackController2") - public Map fallbackcontroller2() { - return Collections.singletonMap("from", "circuitbreakerfallbackcontroller2"); - } - - @GetMapping("/circuitbreakerFallbackController3") - public Map fallbackcontroller3() { - return Collections.singletonMap("from", "circuitbreakerfallbackcontroller3"); - } - - @GetMapping("/statusCodeFallbackController") - public Map statusCodeFallbackController(ServerWebExchange exchange) { - return Collections.singletonMap("from", "statusCodeFallbackController"); - } - - @RequestMapping("/resetExchangeFallbackController") - public ResponseEntity> resetExchangeFallbackController(ServerWebExchange exchange) { - return ResponseEntity.status(HttpStatus.OK) - .headers((HttpHeaders) exchange.getRequest().getHeaders().entrySet().stream() - .filter(entry -> entry.getKey().startsWith("X-Test-")) - .map(entry -> new AbstractMap.SimpleEntry<>(entry.getKey() + "-fallback", entry.getValue())) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, - (list1, list2) -> Stream.concat(list1.stream(), list2.stream()) - .collect(Collectors.toList()), - HttpHeaders::new))) - .body(Collections.singletonMap("from", "resetExchangeFallbackController")); - } - - @Bean - public RouteLocator circuitBreakerRouteLocator(RouteLocatorBuilder builder) { - return builder.routes() - .route("fallback_controller_3", - r -> r.path("/fallback").filters(f -> f.setPath("/circuitbreakerFallbackController3")).uri(uri)) - .route("circuitbreaker_fallback_forward", r -> r.host("**.circuitbreakerforward.org") - .filters(f -> f.circuitBreaker(config -> config.setFallbackUri("forward:/fallback"))).uri(uri)) - .route("circuitbreaker_java", - r -> r.host("**.circuitbreakerjava.org") - .filters(f -> f.prefixPath("/httpbin").circuitBreaker( - config -> config.setFallbackUri("forward:/circuitbreakerFallbackController2"))) - .uri(uri)) - .route("circuitbreaker_connection_failure", r -> r.host("**.circuitbreakerconnectfail.org") - .filters(f -> f.prefixPath("/httpbin").circuitBreaker(config -> { - })).uri("lb:badservice")) - /* - * This is a route encapsulated in a circuit breaker that is ready to wait - * for a response far longer than the underpinning WebClient would. - */ - .route("circuitbreaker_response_stall", - r -> r.host("**.circuitbreakerresponsestall.org") - .filters(f -> f.prefixPath("/httpbin") - .circuitBreaker(config -> config.setName("stalling-command"))) - .uri(uri)) - .route("circuitbreaker_fallback_test_reset_exchange", - r -> r.host("**.circuitbreakerresetexchange.org").filters(f -> f - .circuitBreaker(config -> config.setName("fallbackcmd") - .setFallbackUri("forward:/resetExchangeFallbackController")) - .filter((exchange, chain) -> chain.filter(exchange) - .then(Mono.defer(() -> !exchange.getResponse().isCommitted() - ? Mono.error(new Exception("Some Random Exception")) : Mono.empty())))) - .uri(uri)) - .build(); - } - - @Bean - CircuitBreakerExceptionFallbackHandler exceptionFallbackHandler() { - return new CircuitBreakerExceptionFallbackHandler(); - } - - @Bean - RouterFunction routerFunction(CircuitBreakerExceptionFallbackHandler exceptionFallbackHandler) { - return route(GET("/circuitbreakerExceptionFallback"), exceptionFallbackHandler::retrieveExceptionInfo); - } - - private static class CircuitBreakerExceptionFallbackHandler { - - static final String RETRIEVED_EXCEPTION = "Retrieved-Exception"; - - Mono retrieveExceptionInfo(ServerRequest serverRequest) { - String exceptionName = serverRequest.attribute(CIRCUITBREAKER_EXECUTION_EXCEPTION_ATTR) - .map(exception -> exception.getClass().getName()).orElse(""); - return ServerResponse.ok().header(RETRIEVED_EXCEPTION, exceptionName).build(); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/StripPrefixGatewayFilterFactoryIntegrationTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/StripPrefixGatewayFilterFactoryIntegrationTests.java deleted file mode 100644 index 079121b5..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/StripPrefixGatewayFilterFactoryIntegrationTests.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -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.cloud.gateway.test.BaseWebClientTests; -import org.springframework.context.annotation.Import; -import org.springframework.test.annotation.DirtiesContext; - -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; - -/** - * @author Ryan Baxter - */ -@SpringBootTest(webEnvironment = RANDOM_PORT) -@DirtiesContext -public class StripPrefixGatewayFilterFactoryIntegrationTests extends BaseWebClientTests { - - @Test - public void stripPrefixFilterStripMultiplePartsValuesWork() { - testClient.get().uri("/foo/bar/get").header("Host", "www.stripprefix.org").exchange().expectStatus().isOk(); - } - - @Test - public void stripPrefixFilterDefaultPartsWork() { - testClient.get().uri("/foo/get").header("Host", "www.stripprefixdefault.org").exchange().expectStatus().isOk(); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(DefaultTestConfig.class) - public static class TestConfig { - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/StripPrefixGatewayFilterFactoryTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/StripPrefixGatewayFilterFactoryTests.java deleted file mode 100644 index 17409c2b..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/StripPrefixGatewayFilterFactoryTests.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory; - -import java.net.URI; -import java.util.LinkedHashSet; - -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; -import reactor.core.publisher.Mono; - -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.GatewayFilterChain; -import org.springframework.cloud.gateway.filter.factory.StripPrefixGatewayFilterFactory.Config; -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 Ryan Baxter - */ -public class StripPrefixGatewayFilterFactoryTests { - - @Test - public void testStripPrefix() { - testStripPrefixFilter("/foo/bar", "/bar", 1); - testStripPrefixFilter("/foo/bar", "/", 2); - testStripPrefixFilter("/foo/bar", "/foo/bar", 0); - testStripPrefixFilter("/foo/bar/", "/", 2); - testStripPrefixFilter("/foo/bar/", "/foo/bar/", 0); - testStripPrefixFilter("", "/", 1); - testStripPrefixFilter("/", "/", 1); - testStripPrefixFilter("/", "/", 2); - testStripPrefixFilter("", "/", 2); - testStripPrefixFilter("/this/is/a/long/path/with/a/lot/of/slashes", "/path/with/a/lot/of/slashes", 4); - } - - private void testStripPrefixFilter(String actualPath, String expectedPath, int parts) { - GatewayFilter filter = new StripPrefixGatewayFilterFactory().apply(c -> c.setParts(parts)); - - MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost" + actualPath).build(); - - ServerWebExchange exchange = MockServerWebExchange.from(request); - - 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(expectedPath); - - URI requestUrl = webExchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR); - assertThat(requestUrl).hasScheme("http").hasHost("localhost").hasNoPort().hasPath(expectedPath); - LinkedHashSet uris = webExchange.getRequiredAttribute(GATEWAY_ORIGINAL_REQUEST_URL_ATTR); - assertThat(uris).contains(request.getURI()); - } - - @Test - public void toStringFormat() { - Config config = new Config(); - config.setParts(2); - GatewayFilter filter = new StripPrefixGatewayFilterFactory().apply(config); - assertThat(filter.toString()).contains("2"); - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/TokenRelayGatewayFilterFactoryTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/TokenRelayGatewayFilterFactoryTests.java deleted file mode 100644 index 0514c52c..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/TokenRelayGatewayFilterFactoryTests.java +++ /dev/null @@ -1,220 +0,0 @@ -/* - * Copyright 2014-2018 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.filter.factory; - -import java.time.Duration; -import java.util.Collections; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; -import reactor.core.publisher.Mono; - -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.GatewayFilterChain; -import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory.NameConfig; -import org.springframework.http.HttpHeaders; -import org.springframework.mock.http.server.reactive.MockServerHttpRequest; -import org.springframework.mock.web.server.MockServerWebExchange; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextImpl; -import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest; -import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; -import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientManager; -import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; -import org.springframework.security.oauth2.client.registration.ClientRegistration; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.user.OAuth2User; -import org.springframework.security.web.server.context.SecurityContextServerWebExchange; -import org.springframework.web.server.ServerWebExchange; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -/** - * @author Spencer Gibb - * - */ -public class TokenRelayGatewayFilterFactoryTests { - - private static final Duration TIMEOUT = Duration.ofSeconds(30); - - private ReactiveOAuth2AuthorizedClientManager authorizedClientManager; - - private MockServerHttpRequest request; - - private MockServerWebExchange mockExchange; - - private GatewayFilterChain filterChain; - - private ObjectProvider objectProvider; - - public TokenRelayGatewayFilterFactoryTests() { - } - - @BeforeEach - @SuppressWarnings("unchecked") - public void init() { - request = MockServerHttpRequest.get("/hello").build(); - mockExchange = MockServerWebExchange.from(request); - filterChain = mock(GatewayFilterChain.class); - when(filterChain.filter(any(ServerWebExchange.class))).thenReturn(Mono.empty()); - - authorizedClientManager = mock(ReactiveOAuth2AuthorizedClientManager.class); - objectProvider = mock(ObjectProvider.class); - when(objectProvider.getIfAvailable()).thenReturn(authorizedClientManager); - } - - @AfterEach - public void after() { - } - - @Test - public void emptyPrincipal() { - GatewayFilter filter = new TokenRelayGatewayFilterFactory(objectProvider).apply(); - filter.filter(mockExchange, filterChain).block(TIMEOUT); - assertThat(request.getHeaders()).doesNotContainKeys(HttpHeaders.AUTHORIZATION); - } - - @Test - public void whenPrincipalExistsAuthorizationHeaderAdded() { - OAuth2AccessToken accessToken = mock(OAuth2AccessToken.class); - when(accessToken.getTokenValue()).thenReturn("mytoken"); - - ClientRegistration clientRegistration = ClientRegistration.withRegistrationId("myregistrationid") - .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS).clientId("myclientid") - .tokenUri("mytokenuri").build(); - OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(clientRegistration, "joe", accessToken); - - when(authorizedClientManager.authorize(any(OAuth2AuthorizeRequest.class))) - .thenReturn(Mono.just(authorizedClient)); - - OAuth2AuthenticationToken authenticationToken = new OAuth2AuthenticationToken(mock(OAuth2User.class), - Collections.emptyList(), "myId"); - SecurityContextImpl securityContext = new SecurityContextImpl(authenticationToken); - SecurityContextServerWebExchange exchange = new SecurityContextServerWebExchange(mockExchange, - Mono.just(securityContext)); - - GatewayFilter filter = new TokenRelayGatewayFilterFactory(objectProvider).apply(); - filter.filter(exchange, filterChain).block(TIMEOUT); - - assertThat(request.getHeaders()).containsEntry(HttpHeaders.AUTHORIZATION, - Collections.singletonList("Bearer mytoken")); - - ArgumentCaptor authorizeRequestCaptor = ArgumentCaptor - .forClass(OAuth2AuthorizeRequest.class); - verify(authorizedClientManager).authorize(authorizeRequestCaptor.capture()); - - OAuth2AuthorizeRequest authorizeRequest = authorizeRequestCaptor.getValue(); - assertThat(authorizeRequest.getClientRegistrationId()) - .isEqualTo(authenticationToken.getAuthorizedClientRegistrationId()); - assertThat(authorizeRequest.getClientRegistrationId()).isNotEqualTo(clientRegistration.getRegistrationId()); - } - - @Test - public void whenClientRegistrationIdConfiguredAuthorizationHeaderAdded() { - OAuth2AccessToken accessToken = mock(OAuth2AccessToken.class); - when(accessToken.getTokenValue()).thenReturn("mytoken"); - - ClientRegistration clientRegistration = ClientRegistration.withRegistrationId("myregistrationid") - .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS).clientId("myclientid") - .tokenUri("mytokenuri").build(); - OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(clientRegistration, "steve", accessToken); - - when(authorizedClientManager.authorize(any(OAuth2AuthorizeRequest.class))) - .thenReturn(Mono.just(authorizedClient)); - - OAuth2AuthenticationToken authenticationToken = new OAuth2AuthenticationToken(mock(OAuth2User.class), - Collections.emptyList(), "myId"); - SecurityContextImpl securityContext = new SecurityContextImpl(authenticationToken); - SecurityContextServerWebExchange exchange = new SecurityContextServerWebExchange(mockExchange, - Mono.just(securityContext)); - - NameConfig config = new NameConfig(); - config.setName(clientRegistration.getRegistrationId()); - - GatewayFilter filter = new TokenRelayGatewayFilterFactory(objectProvider).apply(config); - filter.filter(exchange, filterChain).block(TIMEOUT); - - assertThat(request.getHeaders()).containsEntry(HttpHeaders.AUTHORIZATION, - Collections.singletonList("Bearer mytoken")); - - ArgumentCaptor authorizeRequestCaptor = ArgumentCaptor - .forClass(OAuth2AuthorizeRequest.class); - verify(authorizedClientManager).authorize(authorizeRequestCaptor.capture()); - - OAuth2AuthorizeRequest authorizeRequest = authorizeRequestCaptor.getValue(); - assertThat(authorizeRequest.getClientRegistrationId()).isEqualTo(clientRegistration.getRegistrationId()); - assertThat(authorizeRequest.getClientRegistrationId()) - .isNotEqualTo(authenticationToken.getAuthorizedClientRegistrationId()); - } - - @Test - public void principalIsNotOAuth2AuthenticationToken() { - SecurityContextImpl securityContext = new SecurityContextImpl(new TestingAuthenticationToken("my", null)); - SecurityContextServerWebExchange exchange = new SecurityContextServerWebExchange(mockExchange, - Mono.just(securityContext)); - - GatewayFilter filter = new TokenRelayGatewayFilterFactory(objectProvider).apply(); - filter.filter(exchange, filterChain).block(TIMEOUT); - - assertThat(request.getHeaders()).doesNotContainKeys(HttpHeaders.AUTHORIZATION); - } - - @Test - public void whenPrincipalIsNotOAuth2AuthenticationTokenAndClientRegistrationIdConfiguredAuthorizationHeaderAdded() { - OAuth2AccessToken accessToken = mock(OAuth2AccessToken.class); - when(accessToken.getTokenValue()).thenReturn("mytoken"); - - ClientRegistration clientRegistration = ClientRegistration.withRegistrationId("myregistrationid") - .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS).clientId("myclientid") - .tokenUri("mytokenuri").build(); - OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(clientRegistration, "steve", accessToken); - - when(authorizedClientManager.authorize(any(OAuth2AuthorizeRequest.class))) - .thenReturn(Mono.just(authorizedClient)); - - Authentication authenticationToken = new TestingAuthenticationToken("my", null); - SecurityContextImpl securityContext = new SecurityContextImpl(authenticationToken); - SecurityContextServerWebExchange exchange = new SecurityContextServerWebExchange(mockExchange, - Mono.just(securityContext)); - - NameConfig config = new NameConfig(); - config.setName(clientRegistration.getRegistrationId()); - - GatewayFilter filter = new TokenRelayGatewayFilterFactory(objectProvider).apply(config); - filter.filter(exchange, filterChain).block(TIMEOUT); - - assertThat(request.getHeaders()).containsEntry(HttpHeaders.AUTHORIZATION, - Collections.singletonList("Bearer mytoken")); - - ArgumentCaptor authorizeRequestCaptor = ArgumentCaptor - .forClass(OAuth2AuthorizeRequest.class); - verify(authorizedClientManager).authorize(authorizeRequestCaptor.capture()); - - OAuth2AuthorizeRequest authorizeRequest = authorizeRequestCaptor.getValue(); - assertThat(authorizeRequest.getClientRegistrationId()).isEqualTo(clientRegistration.getRegistrationId()); - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/cache/CacheKeyGeneratorTest.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/cache/CacheKeyGeneratorTest.java deleted file mode 100644 index 260b93a0..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/cache/CacheKeyGeneratorTest.java +++ /dev/null @@ -1,254 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory.cache; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; - -import org.junit.jupiter.api.Test; - -import org.springframework.cloud.gateway.filter.factory.cache.keygenerator.CacheKeyGenerator; -import org.springframework.http.HttpCookie; -import org.springframework.http.HttpHeaders; -import org.springframework.mock.http.server.reactive.MockServerHttpRequest; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.http.HttpHeaders.AUTHORIZATION; - -/** - * @author Ignacio Lozano - * @author Simone Gerevini - */ -class CacheKeyGeneratorTest { - - final CacheKeyGenerator cacheKeyGenerator = new CacheKeyGenerator(); - - @Test - public void shouldGenerateSameKeyForSameUri() { - MockServerHttpRequest request1 = MockServerHttpRequest.get("http://this").build(); - MockServerHttpRequest request2 = MockServerHttpRequest.get("http://this").build(); - - var key1 = cacheKeyGenerator.generateKey(request1); - var key2 = cacheKeyGenerator.generateKey(request2); - - assertThat(key1).isEqualTo(key2); - } - - @Test - void shouldGenerateDifferentKeyWhenAuthAreDifferent() { - var uri = "https://this"; - - var requestWithoutAuth = MockServerHttpRequest.get(uri).build(); - HttpHeaders httpHeaders = new HttpHeaders(); - httpHeaders.put(AUTHORIZATION, List.of("my-token")); - var requestWithAuth = MockServerHttpRequest.get(uri).headers(httpHeaders).build(); - - var keyWithoutAuth = cacheKeyGenerator.generateKey(requestWithoutAuth); - var keyWithAuth = cacheKeyGenerator.generateKey(requestWithAuth); - - assertThat(keyWithAuth).isNotEqualTo(keyWithoutAuth); - } - - @Test - void shouldGenerateDifferentKeyWhenCookiesAreDifferent() { - var httpHeaders = new HttpHeaders(); - var uri = "https://this"; - - var requestWithoutCookies = MockServerHttpRequest.get(uri).headers(httpHeaders).build(); - var cookies = new HttpCookie[] { new HttpCookie("user", "my-first-cookie") }; - var requestWithCookies = MockServerHttpRequest.get(uri).headers(httpHeaders).cookie(cookies).build(); - - var keyWithoutCookies = cacheKeyGenerator.generateKey(requestWithoutCookies); - var keyWithCookies = cacheKeyGenerator.generateKey(requestWithCookies); - - assertThat(keyWithoutCookies).isNotEqualTo(keyWithCookies); - } - - @Test - void shouldGenerateSameKeyWhenSameAuthAndCookieArePresent() { - var uri = "https://this"; - var cookies = new HttpCookie[] { new HttpCookie("user", "my-first-cookie") }; - var httpHeaders = new HttpHeaders(); - httpHeaders.put(AUTHORIZATION, List.of("my-token")); - - var request1 = MockServerHttpRequest.get(uri).headers(httpHeaders).cookie(cookies).build(); - var request2 = MockServerHttpRequest.get(uri).headers(httpHeaders).cookie(cookies).build(); - - var key1 = cacheKeyGenerator.generateKey(request1); - var key2 = cacheKeyGenerator.generateKey(request2); - - assertThat(key1).isEqualTo(key2); - } - - @Test - void shouldGenerateSameKeyWhenVaryHeadersAreEqual() { - final String varyHeader = "X-MY-VARY"; - var uri = "https://this"; - - HttpHeaders httpHeaders = new HttpHeaders(); - httpHeaders.put(varyHeader, List.of("VALUE1")); - var withFirstVary = MockServerHttpRequest.get(uri).headers(httpHeaders).build(); - HttpHeaders httpHeaders2 = new HttpHeaders(); - httpHeaders2.put(varyHeader, List.of("VALUE1")); - var withSecondVary = MockServerHttpRequest.get(uri).headers(httpHeaders2).build(); - - var keyWithFirstVary = cacheKeyGenerator.generateKey(withFirstVary, varyHeader); - var keyWithSecondVary = cacheKeyGenerator.generateKey(withSecondVary, varyHeader); - - assertThat(keyWithFirstVary).isEqualTo(keyWithSecondVary); - } - - @Test - void shouldGenerateDifferentKeyWhenVaryHeadersAreDifferent() { - final String varyHeader = "X-MY-VARY"; - var uri = "https://this"; - - HttpHeaders httpHeaders = new HttpHeaders(); - httpHeaders.put(varyHeader, List.of("VALUE1")); - var withFirstVary = MockServerHttpRequest.get(uri).headers(httpHeaders).build(); - HttpHeaders httpHeaders2 = new HttpHeaders(); - httpHeaders.put(varyHeader, List.of("VALUE2")); - var withSecondVary = MockServerHttpRequest.get(uri).headers(httpHeaders2).build(); - - var keyWithFirstVary = cacheKeyGenerator.generateKey(withFirstVary, varyHeader); - var keyWithSecondVary = cacheKeyGenerator.generateKey(withSecondVary, varyHeader); - - assertThat(keyWithFirstVary).isNotEqualTo(keyWithSecondVary); - } - - @Test - void shouldGenerateDifferentKeyWhenVaryHeaderIsMissingInSecondRequest() { - final String varyHeader = "X-MY-VARY"; - var uri = "https://this"; - - HttpHeaders httpHeaders = new HttpHeaders(); - httpHeaders.put(varyHeader, List.of("VALUE1")); - var withFirstVary = MockServerHttpRequest.get(uri).headers(httpHeaders).build(); - var withSecondVary = MockServerHttpRequest.get(uri).build(); - - var keyWithFirstVary = cacheKeyGenerator.generateKey(withFirstVary, varyHeader); - var keyWithSecondVary = cacheKeyGenerator.generateKey(withSecondVary, varyHeader); - - assertThat(keyWithFirstVary).isNotEqualTo(keyWithSecondVary); - } - - @Test - void shouldGenerateDifferentKeyWhenOneOfMultipleVaryHeadersIsDifferent() { - final String varyHeader = "X-MY-VARY"; - String varyHeader2 = "X-MY-SEC-VARY"; - var uri = "https://this"; - - HttpHeaders httpHeaders = new HttpHeaders(); - httpHeaders.put(varyHeader, List.of("VALUE1")); - httpHeaders.put(varyHeader2, List.of("VALUE1")); - var withFirstVary = MockServerHttpRequest.get(uri).headers(httpHeaders).build(); - HttpHeaders httpHeaders2 = new HttpHeaders(); - httpHeaders.put(varyHeader, List.of("VALUE2")); - var withSecondVary = MockServerHttpRequest.get(uri).headers(httpHeaders2).build(); - - var keyWithFirstVary = cacheKeyGenerator.generateKey(withFirstVary, varyHeader); - var keyWithSecondVary = cacheKeyGenerator.generateKey(withSecondVary, varyHeader); - - assertThat(keyWithFirstVary).isNotEqualTo(keyWithSecondVary); - } - - @Test - void shouldGenerateDifferentKeyWhenHeadersAreDifferentButValuesAreTheSame() { - var uri = "https://this"; - - HttpHeaders httpHeaders = new HttpHeaders(); - httpHeaders.put("X-MY-VARY-1", List.of("VALUE1")); - httpHeaders.put("X-MY-VARY-2", List.of("VALUE2")); - - HttpHeaders httpHeaders2 = new HttpHeaders(); - httpHeaders2.put("X-MY-VARY-3", List.of("VALUE1")); - httpHeaders2.put("X-MY-VARY-4", List.of("VALUE2")); - - var withFirstVary = MockServerHttpRequest.get(uri).headers(httpHeaders).build(); - var withSecondVary = MockServerHttpRequest.get(uri).headers(httpHeaders2).build(); - - var keyWithFirstVary = cacheKeyGenerator.generateKey(withFirstVary, "X-MY-VARY-1", "X-MY-VARY-2"); - var keyWithSecondVary = cacheKeyGenerator.generateKey(withSecondVary, "X-MY-VARY-3", "X-MY-VARY-4"); - - assertThat(keyWithFirstVary).isNotEqualTo(keyWithSecondVary); - } - - @Test - void whenHeaderHasEmptyValue() { - var uri = "https://this"; - - HttpHeaders httpHeaders = new HttpHeaders(); - httpHeaders.put("X-MY-VARY-1", List.of("")); - - var withFirstVary = MockServerHttpRequest.get(uri).headers(httpHeaders).build(); - var withoutVaryHeader = MockServerHttpRequest.get(uri).build(); - - var keyWithFirstVary = cacheKeyGenerator.generateKey(withFirstVary, "X-MY-VARY-1"); - var keyWithoutVary = cacheKeyGenerator.generateKey(withFirstVary, "X-MY-VARY-1"); - - assertThat(keyWithoutVary).isEqualTo(keyWithFirstVary); - } - - @Test - public void shouldNotFailWhenRunningInParallel() throws InterruptedException { - MockServerHttpRequest request = MockServerHttpRequest.get("http://this").build(); - int numberOfThreads = 100; - - List exceptions = executeInParallel(Executors.newFixedThreadPool(numberOfThreads), numberOfThreads, - () -> cacheKeyGenerator.generateKey(request)); - - assertThat(exceptions.size()).isEqualTo(0); - } - - private List executeInParallel(Executor executor, int nThreads, Runnable action) - throws InterruptedException { - CountDownLatch ready = new CountDownLatch(nThreads); - CountDownLatch start = new CountDownLatch(1); - CountDownLatch done = new CountDownLatch(nThreads); - List exceptions = new ArrayList<>(nThreads); - - for (int i = 0; i < nThreads; i++) { - executor.execute(() -> { - ready.countDown(); - try { - start.await(); - action.run(); - - } - catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - catch (RuntimeException e) { - exceptions.add(e); - } - finally { - done.countDown(); - } - }); - } - - ready.await(); - start.countDown(); - done.await(); - - return exceptions; - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/cache/CachedResponseTest.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/cache/CachedResponseTest.java deleted file mode 100644 index bf55d1cb..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/cache/CachedResponseTest.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory.cache; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.util.zip.GZIPOutputStream; - -import org.junit.jupiter.api.Test; - -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Ignacio Lozano - */ -class CachedResponseTest { - - @Test - void bodyAsByteArray_whenEmptyBody() throws IOException { - CachedResponse cachedResponse = CachedResponse.create(HttpStatus.OK).build(); - - byte[] asByteArray = cachedResponse.bodyAsByteArray(); - - assertThat(asByteArray).isEmpty(); - } - - @Test - void bodyAsByteArray_whenThereIsContent() throws IOException { - String body = "example"; - CachedResponse cachedResponse = CachedResponse.create(HttpStatus.OK).body(body).build(); - - byte[] asByteArray = cachedResponse.bodyAsByteArray(); - - assertThat(asByteArray).isEqualTo(body.getBytes()); - } - - @Test - void bodyAsString_whenEmptyBody() throws IOException { - CachedResponse cachedResponse = CachedResponse.create(HttpStatus.OK).build(); - - String asString = cachedResponse.bodyAsString(); - - assertThat(asString).isEmpty(); - } - - @Test - void bodyAsString_whenThereIsContent() throws IOException { - String body = "example"; - CachedResponse cachedResponse = CachedResponse.create(HttpStatus.OK).body(body).build(); - - String asString = cachedResponse.bodyAsString(); - - assertThat(asString).isEqualTo(body); - } - - @Test - void bodyAsString_whenThereIsGZipContent() throws IOException { - String body = "example"; - CachedResponse cachedResponse = CachedResponse.create(HttpStatus.OK) - .header(HttpHeaders.CONTENT_ENCODING, "gzip").appendToBody(convertToGzip(body)).build(); - - String asString = cachedResponse.bodyAsString(); - - assertThat(asString).isEqualTo(body); - } - - private ByteBuffer convertToGzip(String str) throws IOException { - var outBytes = new ByteArrayOutputStream(); - var outGzip = new GZIPOutputStream(outBytes); - outGzip.write(str.getBytes()); - outGzip.close(); - return ByteBuffer.wrap(outBytes.toByteArray()); - } - -} 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 deleted file mode 100644 index eb538345..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/cache/LocalResponseCacheGatewayFilterFactoryTests.java +++ /dev/null @@ -1,329 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory.cache; - -import java.time.Duration; -import java.util.Map; -import java.util.Objects; -import java.util.UUID; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.gateway.route.RouteLocator; -import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; -import org.springframework.cloud.gateway.test.BaseWebClientTests; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; -import org.springframework.http.CacheControl; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.util.StringUtils; -import org.springframework.util.unit.DataSize; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Ignacio Lozano - * @author Marta Medio - */ -@DirtiesContext -@ActiveProfiles(profiles = "local-cache-filter") -public class LocalResponseCacheGatewayFilterFactoryTests extends BaseWebClientTests { - - private static final String CUSTOM_HEADER = "X-Custom-Date"; - - @Nested - @SpringBootTest(properties = { "spring.cloud.gateway.filter.local-response-cache.enabled=true" }, - webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) - public class LocalResponseCacheUsingFilterParams extends BaseWebClientTests { - - @Test - void shouldNotCacheResponseWhenGetRequestHasBody() { - String uri = "/" + UUID.randomUUID() + "/cache/headers"; - - testClient.method(HttpMethod.GET).uri(uri).header("Host", "www.localresponsecache.org") - .header(CUSTOM_HEADER, "1").bodyValue("whatever").exchange().expectBody() - .jsonPath("$.headers." + CUSTOM_HEADER); - - testClient.method(HttpMethod.GET).uri(uri).header("Host", "www.localresponsecache.org") - .bodyValue("whatever").header(CUSTOM_HEADER, "2").exchange().expectBody() - .jsonPath("$.headers." + CUSTOM_HEADER).isEqualTo("2"); - } - - @Test - void shouldNotCacheResponseWhenPostRequestHasBody() { - String uri = "/" + UUID.randomUUID() + "/cache/headers"; - - testClient.method(HttpMethod.POST).uri(uri).header("Host", "www.localresponsecache.org") - .header(CUSTOM_HEADER, "1").bodyValue("whatever").exchange().expectBody() - .jsonPath("$.headers." + CUSTOM_HEADER); - - testClient.method(HttpMethod.POST).uri(uri).header("Host", "www.localresponsecache.org") - .bodyValue("whatever").header(CUSTOM_HEADER, "2").exchange().expectBody() - .jsonPath("$.headers." + CUSTOM_HEADER).isEqualTo("2"); - } - - @Test - void shouldNotCacheWhenCacheControlAsksToDoNotCache() { - String uri = "/" + UUID.randomUUID() + "/cache/headers"; - - testClient.get().uri(uri).header("Host", "www.localresponsecache.org").header(CUSTOM_HEADER, "1").exchange() - .expectBody().jsonPath("$.headers." + CUSTOM_HEADER); - - testClient.get().uri(uri).header("Host", "www.localresponsecache.org").header(CUSTOM_HEADER, "2") - // Cache-Control asks to not use the cached content and not store the - // response - .header(HttpHeaders.CACHE_CONTROL, CacheControl.noStore().getHeaderValue()).exchange().expectBody() - .jsonPath("$.headers." + CUSTOM_HEADER).isEqualTo("2"); - } - - @Test - void shouldCacheAndReturnNotModifiedStatusWhenCacheControlIsNoCache() { - String uri = "/" + UUID.randomUUID() + "/cache/headers"; - - testClient.get().uri(uri).header("Host", "www.localresponsecache.org").header(CUSTOM_HEADER, "1").exchange() - .expectBody().jsonPath("$.headers." + CUSTOM_HEADER); - - testClient.get().uri(uri).header("Host", "www.localresponsecache.org").header(CUSTOM_HEADER, "2") - // Cache-Control asks to not return cached content because it is - // HttpHeaders.NotModified - .header(HttpHeaders.CACHE_CONTROL, CacheControl.noCache().getHeaderValue()).exchange() - .expectStatus().isNotModified().expectBody().isEmpty(); - } - - @Test - void shouldCacheResponseWhenOnlyNonVaryHeaderIsDifferent() { - String uri = "/" + UUID.randomUUID() + "/cache/headers"; - - testClient.get().uri(uri).header("Host", "www.localresponsecache.org").header(CUSTOM_HEADER, "1").exchange() - .expectBody().jsonPath("$.headers." + CUSTOM_HEADER) - .value(customHeaderFromReq1 -> testClient.get().uri(uri) - .header("Host", "www.localresponsecache.org").header(CUSTOM_HEADER, "2").exchange() - .expectBody().jsonPath("$.headers." + CUSTOM_HEADER, customHeaderFromReq1)); - } - - @Test - void shouldNotCacheResponseWhenVaryHeaderIsDifferent() { - String varyHeader = HttpHeaders.ORIGIN; - String sameUri = "/" + UUID.randomUUID() + "/cache/vary-on-header"; - String firstNonVary = "1"; - String secondNonVary = "2"; - assertNonVaryHeaderInContent(sameUri, varyHeader, "origin-1", CUSTOM_HEADER, firstNonVary, firstNonVary); - assertNonVaryHeaderInContent(sameUri, varyHeader, "origin-1", CUSTOM_HEADER, secondNonVary, firstNonVary); - assertNonVaryHeaderInContent(sameUri, varyHeader, "origin-2", CUSTOM_HEADER, secondNonVary, secondNonVary); - } - - @Test - void shouldNotCacheResponseWhenResponseVaryIsWildcard() { - String uri = "/" + UUID.randomUUID() + "/cache/vary-on-header"; - // Vary: * - testClient.get().uri(uri).header("Host", "www.localresponsecache.org").header(CUSTOM_HEADER, "1") - .header("X-Request-Vary", "*").exchange().expectBody().jsonPath("$.headers." + CUSTOM_HEADER, "1"); - testClient.get().uri(uri).header("Host", "www.localresponsecache.org").header(CUSTOM_HEADER, "2") - .header("X-Request-Vary", "*").exchange().expectBody().jsonPath("$.headers." + CUSTOM_HEADER, "2"); - } - - @Test - void shouldNotCacheResponseWhenPathIsDifferent() { - String uri = "/" + UUID.randomUUID() + "/cache/headers"; - String uri2 = "/" + UUID.randomUUID() + "/cache/headers"; - - testClient.get().uri(uri).header("Host", "www.localresponsecache.org").header(CUSTOM_HEADER, "1").exchange() - .expectBody().jsonPath("$.headers." + CUSTOM_HEADER); - - testClient.get().uri(uri2).header("Host", "www.localresponsecache.org").header(CUSTOM_HEADER, "2") - .exchange().expectBody().jsonPath("$.headers." + CUSTOM_HEADER).isEqualTo("2"); - } - - @Test - void shouldDecreaseCacheControlMaxAgeTimeWhenResponseIsFromCache() throws InterruptedException { - String uri = "/" + UUID.randomUUID() + "/cache/headers"; - Long maxAgeRequest1 = testClient.get().uri(uri).header("Host", "www.localresponsecache.org").exchange() - .expectBody().returnResult().getResponseHeaders().get(HttpHeaders.CACHE_CONTROL).stream() - .map(this::parseMaxAge).filter(Objects::nonNull).findAny().orElse(null); - Thread.sleep(2000); - Long maxAgeRequest2 = testClient.get().uri(uri).header("Host", "www.localresponsecache.org").exchange() - .expectBody().returnResult().getResponseHeaders().get(HttpHeaders.CACHE_CONTROL).stream() - .map(this::parseMaxAge).filter(Objects::nonNull).findAny().orElse(null); - - assertThat(maxAgeRequest2).isLessThan(maxAgeRequest1); - } - - @Test - void shouldNotCacheResponseWhenTimeToLiveIsReached() { - String uri = "/" + UUID.randomUUID() + "/ephemeral-cache/headers"; - testClient.get().uri(uri).header("Host", "www.localresponsecache.org").header(CUSTOM_HEADER, "1").exchange() - .expectBody().jsonPath("$.headers." + CUSTOM_HEADER).value(customHeaderFromReq1 -> { - try { - Thread.sleep(100); // Min time to have entry expired - testClient.get().uri(uri).header("Host", "www.localresponsecache.org") - .header(CUSTOM_HEADER, "2").exchange().expectBody() - .jsonPath("$.headers." + CUSTOM_HEADER).isEqualTo("2"); - } - catch (InterruptedException e) { - throw new RuntimeException(e); - } - }); - } - - @Test - void shouldNotCacheWhenLocalResponseCacheSizeIsReached() { - String uri = "/" + UUID.randomUUID() + "/one-byte-cache/headers"; - - testClient.get().uri(uri).header("Host", "www.localresponsecache.org").header(CUSTOM_HEADER, "1").exchange() - .expectBody().jsonPath("$.headers." + CUSTOM_HEADER); - - testClient.get().uri(uri).header("Host", "www.localresponsecache.org").header(CUSTOM_HEADER, "2").exchange() - .expectBody().jsonPath("$.headers." + CUSTOM_HEADER, "2"); - } - - @Test - void shouldNotCacheWhenAuthorizationHeaderIsDifferent() { - String uri = "/" + UUID.randomUUID() + "/cache/headers"; - - testClient.get().uri(uri).header("Host", "www.localresponsecache.org") - .header(HttpHeaders.AUTHORIZATION, "1").header(CUSTOM_HEADER, "1").exchange().expectBody() - .jsonPath("$.headers." + CUSTOM_HEADER); - - testClient.get().uri(uri).header("Host", "www.localresponsecache.org") - .header(HttpHeaders.AUTHORIZATION, "2").header(CUSTOM_HEADER, "2").exchange().expectBody() - .jsonPath("$.headers." + CUSTOM_HEADER, "2"); - } - - private Long parseMaxAge(String cacheControlValue) { - if (StringUtils.hasText(cacheControlValue)) { - Pattern maxAgePattern = Pattern.compile("\\bmax-age=(\\d+)\\b"); - Matcher matcher = maxAgePattern.matcher(cacheControlValue); - if (matcher.find()) { - return Long.parseLong(matcher.group(1)); - } - } - return null; - } - - void assertNonVaryHeaderInContent(String uri, String varyHeader, String varyHeaderValue, String nonVaryHeader, - String nonVaryHeaderValue, String expectedNonVaryResponse) { - testClient.get().uri(uri).header("Host", "www.localresponsecache.org").header("X-Request-Vary", varyHeader) - .header(varyHeader, varyHeaderValue).header(nonVaryHeader, nonVaryHeaderValue).exchange() - .expectBody(Map.class).consumeWith(response -> { - assertThat(response.getResponseHeaders()).hasEntrySatisfying("Vary", - o -> assertThat(o).contains(varyHeader)); - assertThat((Map) response.getResponseBody().get("headers")).containsEntry(nonVaryHeader, - expectedNonVaryResponse); - }); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(DefaultTestConfig.class) - public static class TestConfig { - - @Value("${test.uri}") - String uri; - - @Bean - public RouteLocator testRouteLocator(RouteLocatorBuilder builder) { - return builder.routes() - .route("local_response_cache_java_test", - r -> r.path("/{namespace}/cache/**").and().host("{sub}.localresponsecache.org") - .filters(f -> f.stripPrefix(2).prefixPath("/httpbin") - .localResponseCache(Duration.ofMinutes(2), null)) - .uri(uri)) - .route("100_millisec_ephemeral_prefix_local_response_cache_java_test", - r -> r.path("/{namespace}/ephemeral-cache/**").and() - .host("{sub}.localresponsecache.org") - .filters(f -> f.stripPrefix(2).prefixPath("/httpbin") - .localResponseCache(Duration.ofMillis(100), null)) - .uri(uri)) - .route("min_sized_prefix_local_response_cache_java_test", - r -> r.path("/{namespace}/one-byte-cache/**").and().host("{sub}.localresponsecache.org") - .filters(f -> f.stripPrefix(2).prefixPath("/httpbin").localResponseCache(null, - DataSize.ofBytes(1L))) - .uri(uri)) - .build(); - } - - } - - } - - @Nested - @SpringBootTest( - properties = { "spring.cloud.gateway.filter.local-response-cache.enabled=true", - "spring.cloud.gateway.filter.local-response-cache.timeToLive=20s" }, - webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) - public class LocalResponseCacheUsingDefaultProperties extends BaseWebClientTests { - - @Test - void shouldApplyMaxAgeFromPropertiesWhenFilterHasNoParams() throws InterruptedException { - String uri = "/" + UUID.randomUUID() + "/cache/headers"; - Long maxAgeRequest1 = testClient.get().uri(uri).header("Host", "www.localresponsecache.org").exchange() - .expectBody().returnResult().getResponseHeaders().get(HttpHeaders.CACHE_CONTROL).stream() - .map(this::parseMaxAge).filter(Objects::nonNull).findAny().orElse(null); - assertThat(maxAgeRequest1).isLessThanOrEqualTo(20L); - - Thread.sleep(2000); - - Long maxAgeRequest2 = testClient.get().uri(uri).header("Host", "www.localresponsecache.org").exchange() - .expectBody().returnResult().getResponseHeaders().get(HttpHeaders.CACHE_CONTROL).stream() - .map(this::parseMaxAge).filter(Objects::nonNull).findAny().orElse(null); - - assertThat(maxAgeRequest2).isLessThan(maxAgeRequest1); - } - - private Long parseMaxAge(String cacheControlValue) { - if (StringUtils.hasText(cacheControlValue)) { - Pattern maxAgePattern = Pattern.compile("\\bmax-age=(\\d+)\\b"); - Matcher matcher = maxAgePattern.matcher(cacheControlValue); - if (matcher.find()) { - return Long.parseLong(matcher.group(1)); - } - } - return null; - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(DefaultTestConfig.class) - public static class TestConfig { - - @Value("${test.uri}") - String uri; - - @Bean - public RouteLocator testRouteLocator(RouteLocatorBuilder builder) { - return builder.routes().route("local_response_cache_java_test", - r -> r.path("/{namespace}/cache/**").and().host("{sub}.localresponsecache.org") - .filters(f -> f.stripPrefix(2).prefixPath("/httpbin").localResponseCache(null, null)) - .uri(uri)) - .build(); - } - - } - - } - -} 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 deleted file mode 100644 index 98a7d7dd..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/cache/LocalResponseCacheGlobalFilterTests.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory.cache; - -import java.util.UUID; - -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.gateway.route.RouteLocator; -import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; -import org.springframework.cloud.gateway.test.BaseWebClientTests; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.ActiveProfiles; - -/** - * @author Ignacio Lozano - * @author Marta Medio - */ -@DirtiesContext -@ActiveProfiles(profiles = "local-cache-filter") -public class LocalResponseCacheGlobalFilterTests { - - private static final String CUSTOM_HEADER = "X-Custom-Date"; - - @Nested - @SpringBootTest( - properties = { "spring.cloud.gateway.filter.local-response-cache.enabled=true", - "spring.cloud.gateway.global-filter.local-response-cache.enabled=false" }, - webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) - public class GlobalCacheNotEnabled extends BaseWebClientTests { - - @Test - void shouldNotCacheResponseWhenGlobalIsNotEnabled() { - String uri = "/" + UUID.randomUUID() + "/global-cache-deactivated/headers"; - - testClient.get().uri(uri).header("Host", "www.localresponsecache.org").header(CUSTOM_HEADER, "1").exchange() - .expectBody().jsonPath("$.headers." + CUSTOM_HEADER); - - testClient.get().uri(uri).header("Host", "www.localresponsecache.org").header(CUSTOM_HEADER, "2").exchange() - .expectBody().jsonPath("$.headers." + CUSTOM_HEADER).isEqualTo("2"); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(DefaultTestConfig.class) - public static class TestConfig { - - @Value("${test.uri}") - String uri; - - @Bean - public RouteLocator testRouteLocator(RouteLocatorBuilder builder) { - return builder.routes() - .route("global_local_response_cache_deactivated_java_test", - r -> r.path("/{namespace}/global-cache-deactivated/**").and() - .host("{sub}.localresponsecache.org") - .filters(f -> f.stripPrefix(2).prefixPath("/httpbin")).uri(uri)) - .build(); - } - - } - - } - - @Nested - @SpringBootTest(properties = { "spring.cloud.gateway.filter.local-response-cache.enabled=true" }, - webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) - public class GlobalCacheEnabled extends BaseWebClientTests { - - @Test - void shouldGlobalCacheResponseWhenRouteDoesNotHaveFilter() { - String uri = "/" + UUID.randomUUID() + "/global-cache/headers"; - - testClient.get().uri(uri).header("Host", "www.localresponsecache.org").header(CUSTOM_HEADER, "1").exchange() - .expectBody().jsonPath("$.headers." + CUSTOM_HEADER); - - testClient.get().uri(uri).header("Host", "www.localresponsecache.org").header(CUSTOM_HEADER, "2").exchange() - .expectBody().jsonPath("$.headers." + CUSTOM_HEADER).isEqualTo("1"); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(DefaultTestConfig.class) - public static class TestConfig { - - @Value("${test.uri}") - String uri; - - @Bean - public RouteLocator testRouteLocator(RouteLocatorBuilder builder) { - return builder.routes() - .route("global_local_response_cache_java_test", - r -> r.path("/{namespace}/global-cache/**").and().host("{sub}.localresponsecache.org") - .filters(f -> f.stripPrefix(2).prefixPath("/httpbin")).uri(uri)) - .build(); - } - - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/cache/ResponseCacheGatewayFilterTest.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/cache/ResponseCacheGatewayFilterTest.java deleted file mode 100644 index 64616cc8..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/cache/ResponseCacheGatewayFilterTest.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory.cache; - -import org.junit.jupiter.api.Test; - -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.mock.http.server.reactive.MockServerHttpRequest; -import org.springframework.mock.http.server.reactive.MockServerHttpResponse; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Ignacio Lozano - */ -class ResponseCacheGatewayFilterTest { - - ResponseCacheManager cacheManagerToTest = new ResponseCacheManager(null, null, null); - - @Test - void requestShouldBeCacheable() { - var uri = "http://test.com"; - HttpHeaders httpHeaders = new HttpHeaders(); - httpHeaders.add(HttpHeaders.CACHE_CONTROL, "no-transform"); - var request = MockServerHttpRequest.get(uri).headers(httpHeaders).build(); - - assertThat(cacheManagerToTest.isRequestCacheable(request)).isTrue(); - } - - @Test - void requestShouldNotBeCacheable() { - var uri = "http://test.com"; - HttpHeaders httpHeaders = new HttpHeaders(); - httpHeaders.add(HttpHeaders.CACHE_CONTROL, "no-store"); - var request = MockServerHttpRequest.get(uri).headers(httpHeaders).build(); - - assertThat(cacheManagerToTest.isRequestCacheable(request)).isFalse(); - - httpHeaders = new HttpHeaders(); - httpHeaders.add(HttpHeaders.CACHE_CONTROL, "no-transform"); - request = MockServerHttpRequest.post(uri).headers(httpHeaders).build(); - - assertThat(cacheManagerToTest.isRequestCacheable(request)).isFalse(); - } - - @Test - void responseShouldBeCacheable() { - HttpHeaders headers = new HttpHeaders(); - headers.add(HttpHeaders.CACHE_CONTROL, "public"); - var response = new MockServerHttpResponse(); - response.getHeaders().putAll(headers); - response.setStatusCode(HttpStatus.OK); - - assertThat(cacheManagerToTest.isResponseCacheable(response)).isTrue(); - - headers = new HttpHeaders(); - headers.add(HttpHeaders.CACHE_CONTROL, "public"); - response = new MockServerHttpResponse(); - response.getHeaders().putAll(headers); - response.setStatusCode(HttpStatus.PARTIAL_CONTENT); - - assertThat(cacheManagerToTest.isResponseCacheable(response)).isTrue(); - } - - @Test - void responseShouldNotBeCacheable() { - HttpHeaders headers = new HttpHeaders(); - headers.add(HttpHeaders.CACHE_CONTROL, "public"); - var response = new MockServerHttpResponse(); - response.getHeaders().putAll(headers); - response.setStatusCode(HttpStatus.TOO_MANY_REQUESTS); - - assertThat(cacheManagerToTest.isResponseCacheable(response)).isFalse(); - - headers = new HttpHeaders(); - headers.add(HttpHeaders.CACHE_CONTROL, "private"); - response = new MockServerHttpResponse(); - response.getHeaders().putAll(headers); - response.setStatusCode(HttpStatus.OK); - - assertThat(cacheManagerToTest.isResponseCacheable(response)).isFalse(); - - headers = new HttpHeaders(); - headers.add(HttpHeaders.CACHE_CONTROL, "no-store"); - response = new MockServerHttpResponse(); - response.getHeaders().putAll(headers); - response.setStatusCode(HttpStatus.OK); - - assertThat(cacheManagerToTest.isResponseCacheable(response)).isFalse(); - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/cache/keygenerator/DefaultKeyValueGeneratorTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/cache/keygenerator/DefaultKeyValueGeneratorTests.java deleted file mode 100644 index f22a5cee..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/cache/keygenerator/DefaultKeyValueGeneratorTests.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory.cache.keygenerator; - -import java.util.stream.Collectors; - -import org.junit.jupiter.api.Test; - -import org.springframework.http.HttpCookie; -import org.springframework.http.HttpHeaders; -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.mock.http.server.reactive.MockServerHttpRequest; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Ignacio Lozano - */ -class DefaultKeyValueGeneratorTests { - - @Test - void uriAuthorizationAndCookiesArePresent() { - String uri = "http://myuri"; - HttpHeaders headers = new HttpHeaders(); - String authorization = "my-auth"; - headers.set("Authorization", authorization); - String cookieName = "my-cookie"; - String cookieValue = "cookie-value"; - HttpCookie cookie = new HttpCookie(cookieName, cookieValue); - MockServerHttpRequest request = MockServerHttpRequest.get(uri).cookie(cookie).headers(headers).build(); - - String result = apply(request); - - assertThat(result).isEqualTo(uri + ";Authorization=" + authorization + ";" + cookieName + "=" + cookieValue); - } - - @Test - void uriAndCookiesArePresent() { - String uri = "http://myuri"; - HttpHeaders headers = new HttpHeaders(); - String cookieName = "my-cookie"; - String cookieValue = "cookie-value"; - HttpCookie cookie = new HttpCookie(cookieName, cookieValue); - MockServerHttpRequest request = MockServerHttpRequest.get(uri).cookie(cookie).headers(headers).build(); - - String result = apply(request); - - assertThat(result).isEqualTo(uri + ";" + "" + ";" + cookieName + "=" + cookieValue); - } - - @Test - void onlyUriPresent() { - String uri = "http://myuri"; - MockServerHttpRequest request = MockServerHttpRequest.get(uri).build(); - - String result = apply(request); - - assertThat(result).isEqualTo(uri + ";" + "" + ";" + ""); - } - - public String apply(ServerHttpRequest request) { - return CacheKeyGenerator.DEFAULT_KEY_VALUE_GENERATORS.stream().map(generator -> generator.apply(request)) - .collect(Collectors.joining(CacheKeyGenerator.KEY_SEPARATOR)); - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/cache/keygenerator/HeaderKeyValueGeneratorTest.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/cache/keygenerator/HeaderKeyValueGeneratorTest.java deleted file mode 100644 index 38f2cdde..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/cache/keygenerator/HeaderKeyValueGeneratorTest.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory.cache.keygenerator; - -import java.util.List; - -import org.junit.jupiter.api.Test; - -import org.springframework.http.HttpHeaders; -import org.springframework.mock.http.server.reactive.MockServerHttpRequest; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; - -/** - * @author Ignacio Lozano - */ -class HeaderKeyValueGeneratorTest { - - private static final String HEADER_NAME = "X-Header"; - - private static final String SINGLE_HEADER_VALUE = "header-value"; - - private static final String VALUE1 = "value-1"; - - private static final String VALUE2 = "value-2"; - - private static final String SEPARATOR = ","; - - @Test - void exceptionIsThrown_whenConstructorHeaderIsNull() { - assertThatExceptionOfType(IllegalArgumentException.class) - .isThrownBy(() -> new HeaderKeyValueGenerator(null, SEPARATOR)); - } - - @Test - void keyValuePatternIsGenerated_whenOneSingleValueHeaderIsFound() { - HttpHeaders headers = new HttpHeaders(); - headers.set(HEADER_NAME, SINGLE_HEADER_VALUE); - MockServerHttpRequest request = MockServerHttpRequest.get("http://this").headers(headers).build(); - - String result = new HeaderKeyValueGenerator(HEADER_NAME, SEPARATOR).apply(request); - - assertThat(result).isEqualTo(HEADER_NAME + "=" + SINGLE_HEADER_VALUE); - } - - @Test - void keyValuePatternIsGenerated_whenOneMultipleValueHeaderIsFound() { - HttpHeaders headers = new HttpHeaders(); - headers.put(HEADER_NAME, List.of(VALUE1, VALUE2)); - MockServerHttpRequest request = MockServerHttpRequest.get("http://this").headers(headers).build(); - - String result = new HeaderKeyValueGenerator(HEADER_NAME, SEPARATOR).apply(request); - - assertThat(result).isEqualTo(HEADER_NAME + "=" + VALUE1 + SEPARATOR + VALUE2); - } - - @Test - void sotedKeyValuePatternIsGenerated_whenOneMultipleUnsortedValueHeaderIsFound() { - HttpHeaders headers = new HttpHeaders(); - headers.put(HEADER_NAME, List.of(VALUE2, VALUE1)); - MockServerHttpRequest request = MockServerHttpRequest.get("http://this").headers(headers).build(); - - String result = new HeaderKeyValueGenerator(HEADER_NAME, SEPARATOR).apply(request); - - assertThat(result).isEqualTo(HEADER_NAME + "=" + VALUE1 + SEPARATOR + VALUE2); - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/cache/postprocessor/SetMaxAgeHeaderAfterCacheExchangeMutatorTest.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/cache/postprocessor/SetMaxAgeHeaderAfterCacheExchangeMutatorTest.java deleted file mode 100644 index b2adcd64..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/cache/postprocessor/SetMaxAgeHeaderAfterCacheExchangeMutatorTest.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory.cache.postprocessor; - -import java.time.Clock; -import java.time.Duration; -import java.time.Instant; -import java.util.List; -import java.util.Optional; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.cloud.gateway.filter.factory.cache.CachedResponse; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.server.reactive.ServerHttpResponse; -import org.springframework.mock.http.server.reactive.MockServerHttpRequest; -import org.springframework.mock.http.server.reactive.MockServerHttpResponse; -import org.springframework.mock.web.server.MockServerWebExchange; -import org.springframework.util.StringUtils; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Ignacio Lozano - */ -class SetMaxAgeHeaderAfterCacheExchangeMutatorTest { - - private static final int SECONDS_LATER = 10; - - private MockServerWebExchange inputExchange; - - private Clock clock; - - private Clock clockSecondsLater; - - @BeforeEach - void setUp() { - HttpHeaders responseHeaders = new HttpHeaders(); - responseHeaders.setCacheControl("max-age=1234"); - MockServerHttpRequest httpRequest = MockServerHttpRequest.get("https://this").build(); - - inputExchange = MockServerWebExchange.from(httpRequest); - MockServerHttpResponse httpResponse = inputExchange.getResponse(); - httpResponse.setStatusCode(HttpStatus.OK); - httpResponse.getHeaders().putAll(responseHeaders); - - clock = Clock.fixed(Instant.now(), Clock.systemDefaultZone().getZone()); - clockSecondsLater = Clock.fixed(clock.instant().plusSeconds(SECONDS_LATER), clock.getZone()); - } - - @Test - void maxAgeIsNotAdded_whenMaxAgeIsNotPresent() { - inputExchange.getResponse().getHeaders().setCacheControl((String) null); - - Duration timeToLive = Duration.ofSeconds(30); - CachedResponse inputCachedResponse = CachedResponse.create(HttpStatus.OK).timestamp(clock.instant()).build(); - - SetMaxAgeHeaderAfterCacheExchangeMutator toTest = new SetMaxAgeHeaderAfterCacheExchangeMutator(timeToLive, - clock); - toTest.accept(inputExchange, inputCachedResponse); - assertThat(parseMaxAge(inputExchange.getResponse())).isEmpty(); - } - - @Test - void maxAgeIsDecreasedByTimePassed_whenFilterIsAppliedAfterSecondsLater() { - Duration timeToLive = Duration.ofSeconds(30); - CachedResponse inputCachedResponse = CachedResponse.create(HttpStatus.OK).timestamp(clock.instant()).build(); - - SetMaxAgeHeaderAfterCacheExchangeMutator toTest = new SetMaxAgeHeaderAfterCacheExchangeMutator(timeToLive, - clock); - toTest.accept(inputExchange, inputCachedResponse); - Optional firstMaxAgeSeconds = parseMaxAge(inputExchange.getResponse()); - - SetMaxAgeHeaderAfterCacheExchangeMutator toTestSecondsLater = new SetMaxAgeHeaderAfterCacheExchangeMutator( - timeToLive, clockSecondsLater); - toTestSecondsLater.accept(inputExchange, inputCachedResponse); - Optional secondMaxAgeSeconds = parseMaxAge(inputExchange.getResponse()); - - assertThat(firstMaxAgeSeconds).contains(timeToLive.getSeconds()); - assertThat(secondMaxAgeSeconds).contains(timeToLive.getSeconds() - SECONDS_LATER); - } - - @Test - void maxAgeIsZero_whenRequestIsCachedMoreThanTimeToLive() { - Duration timeToLive = Duration.ofSeconds(SECONDS_LATER / 2); // To be staled after - // SECONDS_LATER - // passed - CachedResponse inputCachedResponse = CachedResponse.create(HttpStatus.OK).timestamp(clock.instant()).build(); - - SetMaxAgeHeaderAfterCacheExchangeMutator toTest = new SetMaxAgeHeaderAfterCacheExchangeMutator(timeToLive, - clock); - toTest.accept(inputExchange, inputCachedResponse); - Optional firstMaxAgeSeconds = parseMaxAge(inputExchange.getResponse()); - - SetMaxAgeHeaderAfterCacheExchangeMutator toTestSecondsLater = new SetMaxAgeHeaderAfterCacheExchangeMutator( - timeToLive, clockSecondsLater); - toTestSecondsLater.accept(inputExchange, inputCachedResponse); - Optional secondMaxAgeSeconds = parseMaxAge(inputExchange.getResponse()); - - assertThat(firstMaxAgeSeconds).contains(timeToLive.getSeconds()); - assertThat(secondMaxAgeSeconds).contains(0L); - } - - @Test - void otherCacheControlValuesAreNotRemoved_whenMaxAgeIsModified() { - inputExchange.getResponse().getHeaders().setCacheControl("max-stale=12, min-stale=1, max-age=1234"); - Duration timeToLive = Duration.ofSeconds(30); - CachedResponse inputCachedResponse = CachedResponse.create(HttpStatus.OK).timestamp(clock.instant()).build(); - - SetMaxAgeHeaderAfterCacheExchangeMutator toTest = new SetMaxAgeHeaderAfterCacheExchangeMutator(timeToLive, - clock); - toTest.accept(inputExchange, inputCachedResponse); - - String[] cacheControlValues = Optional.ofNullable(inputExchange.getResponse().getHeaders().getCacheControl()) - .map(s -> s.split("\\s*,\\s*")).orElse(null); - assertThat(cacheControlValues).contains("max-stale=12", "min-stale=1"); - } - - @Test - void otherHeadersAreNotRemoved_whenMaxAgeIsModified() { - inputExchange.getResponse().getHeaders().put("X-Custom-Header", List.of("DO-NOT-REMOVE")); - Duration timeToLive = Duration.ofSeconds(30); - CachedResponse inputCachedResponse = CachedResponse.create(HttpStatus.OK).timestamp(clock.instant()).build(); - - SetMaxAgeHeaderAfterCacheExchangeMutator toTest = new SetMaxAgeHeaderAfterCacheExchangeMutator(timeToLive, - clock); - toTest.accept(inputExchange, inputCachedResponse); - - List cacheControlValues = inputExchange.getResponse().getHeaders().get("X-Custom-Header"); - assertThat(cacheControlValues).contains("DO-NOT-REMOVE"); - } - - private Optional parseMaxAge(ServerHttpResponse response) { - return parseMaxAge(response.getHeaders().getCacheControl()); - } - - private Optional parseMaxAge(String cacheControlValue) { - if (StringUtils.hasText(cacheControlValue)) { - Pattern maxAgePattern = Pattern.compile("\\bmax-age=(\\d+)\\b"); - Matcher matcher = maxAgePattern.matcher(cacheControlValue); - if (matcher.find()) { - return Optional.of(Long.parseLong(matcher.group(1))); - } - } - return Optional.empty(); - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/cache/postprocessor/SetResponseHeadersAfterCacheExchangeMutatorTest.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/cache/postprocessor/SetResponseHeadersAfterCacheExchangeMutatorTest.java deleted file mode 100644 index 735271ad..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/cache/postprocessor/SetResponseHeadersAfterCacheExchangeMutatorTest.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory.cache.postprocessor; - -import java.util.List; - -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.cloud.gateway.filter.factory.cache.CachedResponse; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.mock.http.server.reactive.MockServerHttpRequest; -import org.springframework.mock.http.server.reactive.MockServerHttpResponse; -import org.springframework.mock.web.server.MockServerWebExchange; - -/** - * @author Ignacio Lozano - */ -class SetResponseHeadersAfterCacheExchangeMutatorTest { - - private MockServerWebExchange inputExchange; - - @BeforeEach - void setUp() { - HttpHeaders responseHeaders = new HttpHeaders(); - responseHeaders.setCacheControl("max-age=1234"); - MockServerHttpRequest httpRequest = MockServerHttpRequest.get("https://this").build(); - - inputExchange = MockServerWebExchange.from(httpRequest); - MockServerHttpResponse httpResponse = inputExchange.getResponse(); - httpResponse.setStatusCode(HttpStatus.OK); - httpResponse.getHeaders().putAll(responseHeaders); - } - - @Test - void headersFromCacheOverrideHeadersFromResponse() { - SetResponseHeadersAfterCacheExchangeMutator toTest = new SetResponseHeadersAfterCacheExchangeMutator(); - inputExchange.getResponse().getHeaders().set("X-Header-1", "Value-original"); - CachedResponse cachedResponse = new CachedResponse.Builder(HttpStatus.OK).header("X-Header-1", "Value-cached") - .build(); - - toTest.accept(inputExchange, cachedResponse); - - Assertions.assertThat(inputExchange.getResponse().getHeaders()).containsEntry("X-Header-1", - List.of("Value-cached")); - } - - @Test - void headersFromResponseAreDropped() { - SetResponseHeadersAfterCacheExchangeMutator toTest = new SetResponseHeadersAfterCacheExchangeMutator(); - inputExchange.getResponse().getHeaders().set("X-Header-1", "Value-original"); - CachedResponse cachedResponse = new CachedResponse.Builder(HttpStatus.OK).build(); - - toTest.accept(inputExchange, cachedResponse); - - Assertions.assertThat(inputExchange.getResponse().getHeaders()).doesNotContainKey("X-Header-1"); - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/cache/postprocessor/SetStatusCodeAfterCacheExchangeMutatorTest.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/cache/postprocessor/SetStatusCodeAfterCacheExchangeMutatorTest.java deleted file mode 100644 index 9ad1e802..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/cache/postprocessor/SetStatusCodeAfterCacheExchangeMutatorTest.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory.cache.postprocessor; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.cloud.gateway.filter.factory.cache.CachedResponse; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.mock.http.server.reactive.MockServerHttpRequest; -import org.springframework.mock.http.server.reactive.MockServerHttpResponse; -import org.springframework.mock.web.server.MockServerWebExchange; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Ignacio Lozano - */ -class SetStatusCodeAfterCacheExchangeMutatorTest { - - private MockServerWebExchange inputExchange; - - @BeforeEach - void setUp() { - HttpHeaders responseHeaders = new HttpHeaders(); - responseHeaders.setCacheControl("max-age=1234"); - MockServerHttpRequest httpRequest = MockServerHttpRequest.get("https://this").build(); - - inputExchange = MockServerWebExchange.from(httpRequest); - MockServerHttpResponse httpResponse = inputExchange.getResponse(); - httpResponse.setStatusCode(HttpStatus.OK); - httpResponse.getHeaders().putAll(responseHeaders); - } - - @Test - void statusCodeIs304_whenCacheHitsAndNoCacheHeaderIsPresent() { - CachedResponse cachedResponse = CachedResponse.create(HttpStatus.OK).body("some-data").build(); - MockServerHttpRequest httpRequest = MockServerHttpRequest.get("https://this") - .header("Cache-Control", "no-cache").build(); - - inputExchange = MockServerWebExchange.from(httpRequest); - - SetStatusCodeAfterCacheExchangeMutator toTest = new SetStatusCodeAfterCacheExchangeMutator(); - toTest.accept(inputExchange, cachedResponse); - - assertThat(inputExchange.getResponse().getStatusCode()).isEqualTo(HttpStatus.NOT_MODIFIED); - } - - @Test - void statusCodeIs200_whenCacheHitsAndNoCacheHeaderIsNotPresent() { - CachedResponse cachedResponse = CachedResponse.create(HttpStatus.OK).body("some-data").build(); - - SetStatusCodeAfterCacheExchangeMutator toTest = new SetStatusCodeAfterCacheExchangeMutator(); - toTest.accept(inputExchange, cachedResponse); - - assertThat(inputExchange.getResponse().getStatusCode()).isEqualTo(HttpStatus.OK); - } - - @Test - void statusCodeIs200_whenNoCacheHitsAndEvenNoCacheHeaderIsPresent() { - CachedResponse cachedResponse = CachedResponse.create(HttpStatus.OK).build(); - MockServerHttpRequest httpRequest = MockServerHttpRequest.get("https://this") - .header("Cache-Control", "no-cache").build(); - - inputExchange = MockServerWebExchange.from(httpRequest); - - SetStatusCodeAfterCacheExchangeMutator toTest = new SetStatusCodeAfterCacheExchangeMutator(); - toTest.accept(inputExchange, cachedResponse); - - assertThat(inputExchange.getResponse().getStatusCode()).isEqualTo(HttpStatus.OK); - } - -} 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 deleted file mode 100644 index 2cb27a89..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/rewrite/ModifyRequestBodyGatewayFilterFactorySslTimeoutTests.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory.rewrite; - -import java.util.concurrent.atomic.AtomicInteger; - -import javax.net.ssl.SSLException; - -import io.netty.handler.ssl.SslContext; -import io.netty.handler.ssl.SslContextBuilder; -import io.netty.handler.ssl.util.InsecureTrustManagerFactory; -import io.netty.util.internal.PlatformDependent; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable; -import org.junitpioneer.jupiter.RetryingTest; -import reactor.core.publisher.Mono; -import reactor.netty.http.client.HttpClient; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.gateway.route.RouteLocator; -import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; -import org.springframework.cloud.gateway.test.BaseWebClientTests; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.DependsOn; -import org.springframework.context.annotation.Import; -import org.springframework.context.annotation.Primary; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.client.reactive.ReactorClientHttpConnector; -import org.springframework.http.codec.ServerCodecConfigurer; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.web.reactive.function.BodyInserters; -import org.springframework.web.server.ServerWebExchange; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; - -/** - * @author fangfeikun - */ -@SpringBootTest(webEnvironment = RANDOM_PORT, - properties = { "spring.cloud.gateway.httpclient.ssl.handshake-timeout=1ms", - "spring.main.allow-bean-definition-overriding=true" }) -@DirtiesContext -@ActiveProfiles("single-cert-ssl") -class ModifyRequestBodyGatewayFilterFactorySslTimeoutTests extends BaseWebClientTests { - - @Autowired - AtomicInteger releaseCount; - - @BeforeEach - public void setup() { - try { - System.err.println("in github = " + System.getenv("GITHUB_ACTIONS")); - SslContext sslContext = SslContextBuilder.forClient().trustManager(InsecureTrustManagerFactory.INSTANCE) - .build(); - HttpClient httpClient = HttpClient.create().secure(ssl -> ssl.sslContext(sslContext)); - setup(new ReactorClientHttpConnector(httpClient), "https://localhost:" + port); - } - catch (SSLException e) { - throw new RuntimeException(e); - } - } - - @Test - @DisabledIfEnvironmentVariable(named = "GITHUB_ACTIONS", matches = "true") - void modifyRequestBodySSLTimeout() { - testClient.post().uri("/post").header("Host", "www.modifyrequestbodyssltimeout.org") - .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE) - .body(BodyInserters.fromValue("request")).exchange().expectStatus() - .isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR).expectBody().jsonPath("message") - .isEqualTo("handshake timed out after 1ms"); - } - - @DisabledIfEnvironmentVariable(named = "GITHUB_ACTIONS", matches = "true") - @RetryingTest(3) - void modifyRequestBodyRelease() { - releaseCount.set(0); - // long initialUsedDirectMemory = PlatformDependent.usedDirectMemory(); - for (int i = 0; i < 10; i++) { - testClient.post().uri("/post").header("Host", "www.modifyrequestbodyssltimeout.org") - .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE) - .body(BodyInserters.fromValue("request")).exchange().expectStatus() - .isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR); - long usedDirectMemory = PlatformDependent.usedDirectMemory(); - // Assert.assertTrue(usedDirectMemory - initialUsedDirectMemory < 2 * 10 * 10 - // * 1024 * 1024); - } - assertThat(releaseCount).hasValue(10); - } - - @Test - @DisabledIfEnvironmentVariable(named = "GITHUB_ACTIONS", matches = "true") - void modifyRequestBodyHappenedError() { - testClient.post().uri("/post").header("Host", "www.modifyrequestbodyexception.org") - .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE) - .body(BodyInserters.fromValue("request")).exchange().expectStatus() - .isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR).expectBody().jsonPath("message") - .isEqualTo("modify body exception"); - } - - @EnableAutoConfiguration - @SpringBootConfiguration(proxyBeanMethods = false) - @Import(DefaultTestConfig.class) - static class TestConfig { - - @Value("${test.uri}") - String uri; - - @Bean - @DependsOn("testModifyRequestBodyGatewayFilterFactory") - RouteLocator testRouteLocator(RouteLocatorBuilder builder) { - return builder.routes() - .route("test_modify_request_body_ssl_timeout", - r -> r.order(-1).host("**.modifyrequestbodyssltimeout.org") - .filters(f -> f.modifyRequestBody(String.class, String.class, - MediaType.APPLICATION_JSON_VALUE, (serverWebExchange, aVoid) -> { - byte[] largeBody = new byte[10 * 1024 * 1024]; - return Mono.just(new String(largeBody)); - })) - .uri(uri)) - .route("test_modify_request_body_exception", - r -> r.order(-1).host("**.modifyrequestbodyexception.org") - .filters(f -> f.modifyRequestBody(String.class, String.class, - MediaType.APPLICATION_JSON_VALUE, (serverWebExchange, body) -> { - return Mono.error(new Exception("modify body exception")); - })) - .uri(uri)) - .build(); - } - - @Bean - AtomicInteger count() { - return new AtomicInteger(); - } - - @Bean - @Primary - ModifyRequestBodyGatewayFilterFactory testModifyRequestBodyGatewayFilterFactory( - ServerCodecConfigurer codecConfigurer, AtomicInteger count) { - return new ModifyRequestBodyGatewayFilterFactory(codecConfigurer.getReaders()) { - @Override - protected Mono release(ServerWebExchange exchange, CachedBodyOutputMessage outputMessage, - Throwable throwable) { - if (outputMessage.isCached()) { - count.incrementAndGet(); - } - return super.release(exchange, outputMessage, throwable); - } - }; - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/rewrite/ModifyRequestBodyGatewayFilterFactoryTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/rewrite/ModifyRequestBodyGatewayFilterFactoryTests.java deleted file mode 100644 index 4d216a44..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/rewrite/ModifyRequestBodyGatewayFilterFactoryTests.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory.rewrite; - -import org.junit.jupiter.api.Test; -import reactor.core.publisher.Mono; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.gateway.route.RouteLocator; -import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; -import org.springframework.cloud.gateway.test.BaseWebClientTests; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; -import org.springframework.core.ParameterizedTypeReference; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.web.reactive.function.BodyInserters; - -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; - -/** - * @author Junghoon Song - */ -@SpringBootTest(webEnvironment = RANDOM_PORT, properties = "spring.codec.max-in-memory-size=13") -@DirtiesContext -public class ModifyRequestBodyGatewayFilterFactoryTests extends BaseWebClientTests { - - @Test - public void modifyRequestBody() { - testClient.post().uri("/post").header("Host", "www.modifyrequestbody.org") - .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE) - .body(BodyInserters.fromValue("request")).exchange().expectStatus().isEqualTo(HttpStatus.OK) - .expectBody().jsonPath("headers.Content-Type").isEqualTo(MediaType.APPLICATION_JSON_VALUE) - .jsonPath("data").isEqualTo("modifyrequest"); - } - - @Test - public void upstreamRequestBodyIsEmpty() { - testClient.post().uri("/post").header("Host", "www.modifyrequestbodyempty.org") - .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).exchange().expectStatus() - .isEqualTo(HttpStatus.OK).expectBody().jsonPath("headers.Content-Type") - .isEqualTo(MediaType.APPLICATION_JSON_VALUE).jsonPath("data").isEqualTo("modifyrequest"); - } - - @Test - public void modifyRequestBodyToLarge() { - testClient.post().uri("/post").header("Host", "www.modifyrequestbodyemptytolarge.org") - .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE) - .body(BodyInserters.fromValue("request")).exchange().expectStatus() - .isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR).expectBody().jsonPath("message") - .isEqualTo("Exceeded limit on max bytes to buffer : 13"); - } - - @Test - public void modifyRequestBodyParameterizedTypeReference() { - testClient.post().uri("/post").header("Host", "www.modifyrequestbodyspacetounderscore.org") - .header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN_VALUE) - .body(BodyInserters.fromValue("foo bar baz")).exchange().expectStatus().isEqualTo(HttpStatus.OK) - .expectBody().jsonPath("data").isEqualTo("FOO_BAR_BAZ"); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(DefaultTestConfig.class) - public static class TestConfig { - - @Value("${test.uri}") - String uri; - - @Bean - public RouteLocator testRouteLocator(RouteLocatorBuilder builder) { - return builder.routes().route("test_modify_request_body", - r -> r.order(-1).host("**.modifyrequestbody.org").filters(f -> f.modifyRequestBody(String.class, - String.class, MediaType.APPLICATION_JSON_VALUE, (serverWebExchange, aVoid) -> { - return Mono.just("modifyrequest"); - })).uri(uri)) - .route("test_modify_request_body_empty", - r -> r.order(-1).host("**.modifyrequestbodyempty.org") - .filters(f -> f.modifyRequestBody(String.class, String.class, - MediaType.APPLICATION_JSON_VALUE, (serverWebExchange, body) -> { - if (body == null) { - return Mono.just("modifyrequest"); - } - return Mono.just(body.toUpperCase()); - })) - .uri(uri)) - .route("test_modify_request_body_to_large", - r -> r.order(-1).host("**.modifyrequestbodyemptytolarge.org") - .filters(f -> f.modifyRequestBody(String.class, String.class, - MediaType.APPLICATION_JSON_VALUE, (serverWebExchange, body) -> { - return Mono.just( - "tolarge-tolarge-tolarge-tolarge-tolarge-tolarge-tolarge-tolarge-tolarge-tolarge-tolarge-tolarge-tolarge-tolarge-tolarge-tolarge"); - })) - .uri(uri)) - .route("test_modify_request_body_with_parameterizedtypereference", - r -> r.order(-1).host("**.modifyrequestbodyspacetounderscore.org") - .filters(f -> f.modifyRequestBody(new ParameterizedTypeReference() { - }, new ParameterizedTypeReference() { - }, (swe, body) -> { - return Mono.just(body.replaceAll(" ", "_").toUpperCase()); - })).uri(uri)) - .build(); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/rewrite/ModifyRequestBodyGatewayFilterFactoryUnitTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/rewrite/ModifyRequestBodyGatewayFilterFactoryUnitTests.java deleted file mode 100644 index b69f232a..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/rewrite/ModifyRequestBodyGatewayFilterFactoryUnitTests.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory.rewrite; - -import org.junit.jupiter.api.Test; - -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.factory.rewrite.ModifyRequestBodyGatewayFilterFactory.Config; -import org.springframework.core.ParameterizedTypeReference; - -import static org.assertj.core.api.Assertions.assertThat; - -public class ModifyRequestBodyGatewayFilterFactoryUnitTests { - - @Test - public void toStringFormat() { - Config config = new Config(); - config.setInClass(String.class); - config.setOutClass(Integer.class); - config.setContentType("mycontenttype"); - GatewayFilter filter = new ModifyRequestBodyGatewayFilterFactory().apply(config); - assertThat(filter.toString()).contains("String").contains("Integer").contains("mycontenttype"); - } - - @Test - public void toStringFormatWithParameterizedTypeReferences() { - Config config = new Config(); - config.setInClass(new ParameterizedTypeReference() { - }); - config.setOutClass(new ParameterizedTypeReference() { - }); - config.setContentType("mycontenttype"); - GatewayFilter filter = new ModifyRequestBodyGatewayFilterFactory().apply(config); - assertThat(filter.toString()).contains("String").contains("Integer").contains("mycontenttype"); - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/rewrite/ModifyResponseBodyGatewayFilterFactoryGzipTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/rewrite/ModifyResponseBodyGatewayFilterFactoryGzipTests.java deleted file mode 100644 index 6c871771..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/rewrite/ModifyResponseBodyGatewayFilterFactoryGzipTests.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory.rewrite; - -import java.net.URI; -import java.util.HashMap; -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; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.gateway.route.RouteLocator; -import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; -import org.springframework.cloud.gateway.test.BaseWebClientTests; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; -import org.springframework.http.MediaType; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.web.util.UriComponentsBuilder; - -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; - -@SpringBootTest(webEnvironment = RANDOM_PORT) -@DirtiesContext -class ModifyResponseBodyGatewayFilterFactoryGzipTests extends BaseWebClientTests { - - @Test - void testModificationOfResponseBody() { - URI uri = UriComponentsBuilder.fromUriString(this.baseUri + "/gzip").build(true).toUri(); - - testClient.get().uri(uri).header("Host", "www.modifyresponsebodyjava.org").accept(MediaType.APPLICATION_JSON) - .exchange().expectBody().json("{\"length\":25,\"value\":\"\\\"httpbin compatible home\\\"\"}"); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(DefaultTestConfig.class) - static class TestConfig { - - @Value("${test.uri}") - String uri; - - @Bean - RouteLocator testRouteLocator(RouteLocatorBuilder builder) { - return builder.routes().route("modify_response_java_test_gzip", r -> r.path("/gzip").and() - .host("www.modifyresponsebodyjava.org") - .filters(f -> f.modifyResponseBody(String.class, Map.class, (webExchange, originalResponse) -> { - Map modifiedResponse = new HashMap<>(); - modifiedResponse.put("value", originalResponse); - modifiedResponse.put("length", originalResponse.length()); - return Mono.just(modifiedResponse); - })).uri(uri)).build(); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/rewrite/ModifyResponseBodyGatewayFilterFactoryTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/rewrite/ModifyResponseBodyGatewayFilterFactoryTests.java deleted file mode 100644 index 6db20d0b..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/rewrite/ModifyResponseBodyGatewayFilterFactoryTests.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory.rewrite; - -import java.net.URI; -import java.util.HashMap; -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; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.gateway.route.RouteLocator; -import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; -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.test.annotation.DirtiesContext; -import org.springframework.web.reactive.function.BodyInserters; -import org.springframework.web.util.UriComponentsBuilder; - -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; - -@SpringBootTest(webEnvironment = RANDOM_PORT, properties = "spring.codec.max-in-memory-size=40") -@DirtiesContext -public class ModifyResponseBodyGatewayFilterFactoryTests extends BaseWebClientTests { - - private static final String toLarge; - - static { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < 1000; i++) { - sb.append("to-large-"); - } - toLarge = sb.toString(); - } - - @Test - public void testModificationOfResponseBody() { - URI uri = UriComponentsBuilder.fromUriString(this.baseUri + "/").build(true).toUri(); - - testClient.get().uri(uri).header("Host", "www.modifyresponsebodyjava.org").accept(MediaType.APPLICATION_JSON) - .exchange().expectBody().json("{\"value\": \"httpbin compatible home\", \"length\": 23}"); - } - - @Test - public void modifyResponeBodyToLarge() { - testClient.post().uri("/post").header("Host", "www.modifyresponsebodyjavatoolarge.org") - .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) - .body(BodyInserters.fromValue(toLarge)).exchange().expectStatus() - .isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR).expectBody().jsonPath("message") - .isEqualTo("Exceeded limit on max bytes to buffer : 40"); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(DefaultTestConfig.class) - public static class TestConfig { - - @Value("${test.uri}") - String uri; - - @Bean - public RouteLocator testRouteLocator(RouteLocatorBuilder builder) { - return builder.routes().route("modify_response_java_test", - r -> r.path("/").and().host("www.modifyresponsebodyjava.org").filters(f -> f.prefixPath("/httpbin") - .modifyResponseBody(String.class, Map.class, (webExchange, originalResponse) -> { - Map modifiedResponse = new HashMap<>(); - modifiedResponse.put("value", originalResponse); - modifiedResponse.put("length", originalResponse.length()); - return Mono.just(modifiedResponse); - })).uri(uri)) - .route("modify_response_java_test_to_large", - r -> r.path("/").and().host("www.modifyresponsebodyjavatoolarge.org") - .filters(f -> f.prefixPath("/httpbin").modifyResponseBody(String.class, - String.class, (webExchange, originalResponse) -> { - return Mono.just(toLarge); - })) - .uri(uri)) - .build(); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/rewrite/ModifyResponseBodyGatewayFilterFactoryUnitTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/rewrite/ModifyResponseBodyGatewayFilterFactoryUnitTests.java deleted file mode 100644 index a6bd9d7b..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/factory/rewrite/ModifyResponseBodyGatewayFilterFactoryUnitTests.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.factory.rewrite; - -import org.junit.jupiter.api.Test; - -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.factory.rewrite.ModifyResponseBodyGatewayFilterFactory.Config; -import org.springframework.http.codec.support.DefaultServerCodecConfigurer; - -import static java.util.Collections.emptySet; -import static org.assertj.core.api.Assertions.assertThat; - -public class ModifyResponseBodyGatewayFilterFactoryUnitTests { - - @Test - public void toStringFormat() { - Config config = new Config(); - config.setInClass(String.class); - config.setOutClass(Integer.class); - config.setNewContentType("mycontenttype"); - GatewayFilter filter = new ModifyResponseBodyGatewayFilterFactory( - new DefaultServerCodecConfigurer().getReaders(), emptySet(), emptySet()).apply(config); - assertThat(filter.toString()).contains("String").contains("Integer").contains("mycontenttype"); - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/ForwardedHeadersFilterTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/ForwardedHeadersFilterTests.java deleted file mode 100644 index 152b536b..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/ForwardedHeadersFilterTests.java +++ /dev/null @@ -1,198 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.headers; - -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.UnknownHostException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.junit.jupiter.api.Test; - -import org.springframework.cloud.gateway.filter.headers.ForwardedHeadersFilter.Forwarded; -import org.springframework.http.HttpHeaders; -import org.springframework.mock.http.server.reactive.MockServerHttpRequest; -import org.springframework.mock.web.server.MockServerWebExchange; -import org.springframework.util.StringUtils; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.cloud.gateway.filter.headers.ForwardedHeadersFilter.FORWARDED_HEADER; - -/** - * @author Spencer Gibb - */ -public class ForwardedHeadersFilterTests { - - public static Map map(String... values) { - if (values.length % 2 != 0) { - throw new IllegalArgumentException("values must have even number of items: " + Arrays.asList(values)); - } - HashMap map = new HashMap<>(); - for (int i = 0; i < values.length; i++) { - map.put(values[i], values[++i]); - } - return map; - } - - @Test - public void forwardedHeaderDoesNotExist() throws UnknownHostException { - MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost/get") - .remoteAddress(new InetSocketAddress(InetAddress.getByName("10.0.0.1"), 80)) - .header(HttpHeaders.HOST, "myhost").build(); - - ForwardedHeadersFilter filter = new ForwardedHeadersFilter(); - - HttpHeaders headers = filter.filter(request.getHeaders(), MockServerWebExchange.from(request)); - - assertThat(headers.get(FORWARDED_HEADER)).hasSize(1); - - List forwardeds = ForwardedHeadersFilter.parse(headers.get(FORWARDED_HEADER)); - - assertThat(forwardeds).hasSize(1); - Forwarded forwarded = forwardeds.get(0); - - assertThat(forwarded.getValues()).containsEntry("host", "myhost").containsEntry("proto", "http") - .containsEntry("for", "\"10.0.0.1:80\""); - } - - @Test - public void forwardedHeaderExists() throws UnknownHostException { - MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost/get") - .remoteAddress(new InetSocketAddress(InetAddress.getByName("10.0.0.1"), 80)) - .header(FORWARDED_HEADER, "for=12.34.56.78;host=example.com;proto=https; for=23.45.67.89").build(); - - ForwardedHeadersFilter filter = new ForwardedHeadersFilter(); - - HttpHeaders headers = filter.filter(request.getHeaders(), MockServerWebExchange.from(request)); - - assertThat(headers.get(FORWARDED_HEADER)).hasSize(2); - - List forwardeds = ForwardedHeadersFilter.parse(headers.get(FORWARDED_HEADER)); - - assertThat(forwardeds).hasSize(2); - Forwarded addedForwardedHeader = forwardeds.get(0); - Forwarded existingForwardedHeader = forwardeds.get(1); - - assertThat(existingForwardedHeader.getValues()).containsEntry("proto", "http").containsEntry("for", - "\"10.0.0.1:80\""); - - assertThat(addedForwardedHeader.getValues()).containsEntry("proto", "https").containsEntry("for", - "23.45.67.89"); - } - - @Test - public void noHostHeader() throws UnknownHostException { - MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost/get") - .remoteAddress(new InetSocketAddress(InetAddress.getByName("10.0.0.1"), 80)).build(); - - ForwardedHeadersFilter filter = new ForwardedHeadersFilter(); - - HttpHeaders headers = filter.filter(request.getHeaders(), MockServerWebExchange.from(request)); - - assertThat(headers.get(FORWARDED_HEADER)).hasSize(1); - - List forwardeds = ForwardedHeadersFilter.parse(headers.get(FORWARDED_HEADER)); - - assertThat(forwardeds).hasSize(1); - Forwarded forwarded = forwardeds.get(0); - - assertThat(forwarded.getValues()).containsEntry("proto", "http").containsEntry("for", "\"10.0.0.1:80\""); - } - - @Test - public void correctIPv6RemoteAddressMapping() throws UnknownHostException { - MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost/get") - .remoteAddress(new InetSocketAddress(InetAddress.getByName("2001:db8:cafe:0:0:0:0:17"), 80)) - .header(HttpHeaders.HOST, "myhost").build(); - - ForwardedHeadersFilter filter = new ForwardedHeadersFilter(); - - HttpHeaders headers = filter.filter(request.getHeaders(), MockServerWebExchange.from(request)); - - assertThat(headers.get(FORWARDED_HEADER)).hasSize(1); - - List forwardeds = ForwardedHeadersFilter.parse(headers.get(FORWARDED_HEADER)); - - assertThat(forwardeds).hasSize(1); - Forwarded forwarded = forwardeds.get(0); - - assertThat(forwarded.getValues()).containsEntry("for", "\"[2001:db8:cafe:0:0:0:0:17]:80\""); - } - - @Test - public void unresolvedRemoteAddressFallsBackToHostName() throws UnknownHostException { - MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost/get") - .remoteAddress(InetSocketAddress.createUnresolved("unresolvable-hostname", 80)).build(); - - ForwardedHeadersFilter filter = new ForwardedHeadersFilter(); - - HttpHeaders headers = filter.filter(request.getHeaders(), MockServerWebExchange.from(request)); - - assertThat(headers.get(FORWARDED_HEADER)).hasSize(1); - - List forwardeds = ForwardedHeadersFilter.parse(headers.get(FORWARDED_HEADER)); - - assertThat(forwardeds).hasSize(1); - Forwarded forwarded = forwardeds.get(0); - - assertThat(forwarded.getValues()).containsEntry("proto", "http").containsEntry("for", - "\"unresolvable-hostname:80\""); - } - - @Test - public void forwardedParsedCorrectly() { - String[] valid = new String[] { "for=\"_gazonk\"", "for=192.0.2.60;proto=http;by=203.0.113.43", - "for=192.0.2.43, for=198.51.100.17", "for=12.34.56.78;host=example.com;proto=https, for=23.45.67.89", - "for=12.34.56.78, for=23.45.67.89;secret=egah2CGj55fSJFs, for=10.1.2.3", - "For=\"[2001:db8:cafe::17]:4711\"", }; - - List>> expectedFor = new ArrayList<>(); - expectedFor.add(Arrays.asList(map("for", "\"_gazonk\""))); - expectedFor.add(Arrays.asList(map("for", "192.0.2.60", "proto", "http", "by", "203.0.113.43"))); - expectedFor.add(Arrays.asList(map("for", "192.0.2.43"), map("for", "198.51.100.17"))); - expectedFor.add(Arrays.asList(map("for", "12.34.56.78", "host", "example.com", "proto", "https"), - map("for", "23.45.67.89"))); - expectedFor.add(Arrays.asList(map("for", "12.34.56.78"), map("for", "23.45.67.89", "secret", "egah2CGj55fSJFs"), - map("for", "10.1.2.3"))); - expectedFor.add(Arrays.asList(map("for", "\"[2001:db8:cafe::17]:4711\""))); - - for (int i = 0; i < valid.length; i++) { - String value = valid[i]; - // simulate spring's parsed headers - String[] values = StringUtils.tokenizeToStringArray(value, ","); - List results = ForwardedHeadersFilter.parse(Arrays.asList(values)); - // System.out.println("results: "+results); - - assertThat(results).hasSize(values.length); - assertThat(results.get(0)).isNotNull(); - - List> expected = expectedFor.get(i); - // System.out.println("expected: "+Arrays.asList(expected)); - assertThat(expected).hasSize(results.size()); - - for (int j = 0; j < results.size(); j++) { - Forwarded forwarded = results.get(j); - assertThat(forwarded.getValues()).hasSize(expected.get(j).size()).containsAllEntriesOf(expected.get(j)); - } - } - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/GRPCRequestHeadersFilterTest.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/GRPCRequestHeadersFilterTest.java deleted file mode 100644 index ad8d3dd3..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/GRPCRequestHeadersFilterTest.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.headers; - -import org.junit.jupiter.api.Test; - -import org.springframework.http.HttpHeaders; -import org.springframework.mock.http.server.reactive.MockServerHttpRequest; -import org.springframework.mock.web.server.MockServerWebExchange; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Alberto C. Ríos - */ -public class GRPCRequestHeadersFilterTest { - - @Test - public void shouldIncludeTrailersHeaderIfGRPC() { - MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost:8080/get") - .header(HttpHeaders.CONTENT_TYPE, "application/grpc").build(); - - GRPCRequestHeadersFilter filter = new GRPCRequestHeadersFilter(); - - HttpHeaders headers = filter.filter(request.getHeaders(), MockServerWebExchange.from(request)); - - assertThat(headers).containsKeys("te"); - - assertThat(headers.getFirst("te")).isEqualTo("trailers"); - } - - @Test - public void shouldNotIncludeTrailersHeaderIfNotGRPC() { - MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost:8080/get") - .header(HttpHeaders.CONTENT_TYPE, "application/json").build(); - - GRPCRequestHeadersFilter filter = new GRPCRequestHeadersFilter(); - - HttpHeaders headers = filter.filter(request.getHeaders(), MockServerWebExchange.from(request)); - - assertThat(headers).doesNotContainKeys("te"); - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/HttpHeadersFilterMixedTypeTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/HttpHeadersFilterMixedTypeTests.java deleted file mode 100644 index c04e7af3..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/HttpHeadersFilterMixedTypeTests.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.headers; - -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; - -import org.junit.jupiter.api.Test; - -import org.springframework.cloud.gateway.filter.headers.HttpHeadersFilter.Type; -import org.springframework.http.HttpHeaders; -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; - -/** - * @author Biju Kunjummen - */ -public class HttpHeadersFilterMixedTypeTests { - - @Test - public void relevantDownstreamFiltersShouldActOnHeaders() { - MockServerHttpRequest mockRequest = MockServerHttpRequest.get("/get").header("header1", "value1") - .header("header2", "value2").header("header3", "value3").build(); - - HttpHeadersFilter filter1 = filterRemovingHeaders(Type.RESPONSE, "header1"); - - HttpHeadersFilter filter2 = filterRemovingHeaders(Type.REQUEST, "header2"); - - HttpHeaders result = HttpHeadersFilter.filterRequest(Arrays.asList(filter1, filter2), - MockServerWebExchange.from(mockRequest)); - - assertThat(result).containsOnlyKeys("header1", "header3"); - } - - private HttpHeadersFilter filterRemovingHeaders(Type type, String... headerNames) { - Set headerNamesSet = new HashSet<>(Arrays.asList(headerNames)); - HttpHeadersFilter filter = new HttpHeadersFilter() { - @Override - public HttpHeaders filter(HttpHeaders headers, ServerWebExchange exchange) { - HttpHeaders result = new HttpHeaders(); - headers.entrySet().forEach(entry -> { - if (!headerNamesSet.contains(entry.getKey())) { - result.put(entry.getKey(), entry.getValue()); - } - }); - return result; - } - - @Override - public boolean supports(Type path) { - return path.equals(type); - } - }; - return filter; - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/HttpHeadersFilterTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/HttpHeadersFilterTests.java deleted file mode 100644 index 049c8e6a..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/HttpHeadersFilterTests.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.headers; - -import java.util.Arrays; -import java.util.List; - -import org.junit.jupiter.api.Test; - -import org.springframework.http.HttpHeaders; -import org.springframework.mock.http.server.reactive.MockServerHttpRequest; -import org.springframework.mock.web.server.MockServerWebExchange; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Spencer Gibb - * @author Biju Kunjummen - */ -public class HttpHeadersFilterTests { - - @Test - public void httpHeadersFilterTests() { - MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost:8080/get").header("X-A", "aValue") - .header("X-B", "bValue").header("X-C", "cValue").build(); - - List filters = Arrays.asList((h, e) -> HttpHeadersFilterTests.this.filter(h, "X-A"), - (h, e) -> HttpHeadersFilterTests.this.filter(h, "X-B")); - - HttpHeaders headers = HttpHeadersFilter.filterRequest(filters, MockServerWebExchange.from(request)); - - assertThat(headers).containsOnlyKeys("X-C"); - } - - private HttpHeaders filter(HttpHeaders input, String keyToFilter) { - HttpHeaders filtered = new HttpHeaders(); - - input.entrySet().stream().filter(entry -> !entry.getKey().equals(keyToFilter)) - .forEach(entry -> filtered.addAll(entry.getKey(), entry.getValue())); - - return filtered; - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/HttpStatusInResponseHeadersFilterTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/HttpStatusInResponseHeadersFilterTests.java deleted file mode 100644 index 3bb08aee..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/HttpStatusInResponseHeadersFilterTests.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.headers; - -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.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.HttpStatusCode; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.web.server.ServerWebExchange; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; - -/** - * @author Spencer Gibb - */ -@SpringBootTest(webEnvironment = RANDOM_PORT) -@DirtiesContext -public class HttpStatusInResponseHeadersFilterTests extends BaseWebClientTests { - - @Test - public void statusCodeAvailableInResponseHttpHeadersFilter() { - testClient.get().uri("/get").exchange().expectStatus().isOk(); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(DefaultTestConfig.class) - public static class TestConfig { - - @Bean - public HttpHeadersFilter checkStatusFilter() { - return new HttpHeadersFilter() { - @Override - public HttpHeaders filter(HttpHeaders input, ServerWebExchange exchange) { - HttpStatusCode statusCode = exchange.getResponse().getStatusCode(); - assertThat(statusCode).isEqualTo(HttpStatus.OK); - return input; - } - - @Override - public boolean supports(Type type) { - return type == Type.RESPONSE; - } - }; - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/NonStandardHeadersInResponseTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/NonStandardHeadersInResponseTests.java deleted file mode 100644 index 42cdb6cc..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/NonStandardHeadersInResponseTests.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.headers; - -import java.net.URI; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.junit.jupiter.api.Test; -import reactor.core.publisher.Mono; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.gateway.filter.GlobalFilter; -import org.springframework.cloud.gateway.route.RouteLocator; -import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; -import org.springframework.cloud.gateway.test.BaseWebClientTests; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; -import org.springframework.core.annotation.Order; -import org.springframework.http.HttpHeaders; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.web.reactive.function.client.WebClient; -import org.springframework.web.util.UriComponentsBuilder; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT; - -@SpringBootTest(webEnvironment = DEFINED_PORT, properties = { "server.port=62175" }) -@DirtiesContext -public class NonStandardHeadersInResponseTests extends BaseWebClientTests { - - public static final String CONTENT_TYPE_IMAGE = "image"; - - @Test - public void nonStandardHeadersInResponse() { - URI uri = UriComponentsBuilder.fromUriString(this.baseUri + "/get-image").build(true).toUri(); - - String contentType = WebClient.builder().baseUrl(baseUri).build().get().uri(uri).exchangeToMono(Mono::just) - .map(clientResponse -> clientResponse.headers().asHttpHeaders().getFirst(HttpHeaders.CONTENT_TYPE)) - .block(); - - assertThat(contentType).isEqualTo(CONTENT_TYPE_IMAGE); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(DefaultTestConfig.class) - public static class TestConfig { - - private static final Log log = LogFactory.getLog(TestConfig.class); - - @Value("${test.uri}") - String uri; - - @Value("${server.port}") - int port; - - @Bean - @Order(5001) - public GlobalFilter addNonStandardHeaderFilter() { - return (exchange, chain) -> { - log.info("addNonStandardHeaderFilter pre phase"); - return chain.filter(exchange).then(Mono.fromRunnable(() -> { - log.info("addNonStandardHeaderFilter post phase"); - exchange.getResponse().getHeaders().set(HttpHeaders.CONTENT_TYPE, CONTENT_TYPE_IMAGE); - })); - }; - } - - @Bean - public RouteLocator testRouteLocator(RouteLocatorBuilder builder) { - return builder.routes().route("non_standard_header_route", r -> r.path("/get-image/**") - .filters(f -> f.addRequestHeader(HttpHeaders.HOST, "www.addrequestparameter.org").stripPrefix(1)) - .uri("http://localhost:" + port + "/get")) - .route("internal_route", r -> r.path("/get/**").filters(f -> f.prefixPath("/httpbin")).uri(uri)) - .build(); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/RemoveHopByHopHeadersFilterTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/RemoveHopByHopHeadersFilterTests.java deleted file mode 100644 index 1be4ac28..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/RemoveHopByHopHeadersFilterTests.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.headers; - -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; - -import org.junit.jupiter.api.Test; - -import org.springframework.http.HttpHeaders; -import org.springframework.mock.http.server.reactive.MockServerHttpRequest; -import org.springframework.mock.web.server.MockServerWebExchange; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.cloud.gateway.filter.headers.RemoveHopByHopHeadersFilter.HEADERS_REMOVED_ON_REQUEST; - -/** - * @author Spencer Gibb - */ -public class RemoveHopByHopHeadersFilterTests { - - @Test - public void happyPath() { - MockServerHttpRequest.BaseBuilder builder = MockServerHttpRequest.get("http://localhost/get"); - - HEADERS_REMOVED_ON_REQUEST.forEach(header -> builder.header(header, header + "1")); - - testFilter(MockServerWebExchange.from(builder)); - } - - @Test - public void caseInsensitive() { - MockServerHttpRequest.BaseBuilder builder = MockServerHttpRequest.get("http://localhost/get"); - - HEADERS_REMOVED_ON_REQUEST.forEach(header -> builder.header(header.toLowerCase(), header + "1")); - - testFilter(MockServerWebExchange.from(builder)); - } - - @Test - public void removesHeadersListedInConnectionHeader() { - MockServerHttpRequest.BaseBuilder builder = MockServerHttpRequest.get("http://localhost/get"); - - builder.header(HttpHeaders.CONNECTION, "upgrade", "keep-alive"); - builder.header(HttpHeaders.UPGRADE, "WebSocket"); - builder.header("Keep-Alive", "timeout:5"); - - testFilter(MockServerWebExchange.from(builder), "upgrade", "keep-alive"); - } - - private void testFilter(MockServerWebExchange exchange, String... additionalHeaders) { - RemoveHopByHopHeadersFilter filter = new RemoveHopByHopHeadersFilter(); - HttpHeaders headers = filter.filter(exchange.getRequest().getHeaders(), exchange); - - Set toRemove = new HashSet<>(HEADERS_REMOVED_ON_REQUEST); - toRemove.addAll(Arrays.asList(additionalHeaders)); - assertThat(headers).doesNotContainKeys(toRemove.toArray(new String[0])); - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/TransferEncodingNormalizationHeadersFilterIntegrationTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/TransferEncodingNormalizationHeadersFilterIntegrationTests.java deleted file mode 100644 index 5307f2e1..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/TransferEncodingNormalizationHeadersFilterIntegrationTests.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.headers; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.net.Socket; - -import com.fasterxml.jackson.annotation.JsonProperty; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -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.web.server.LocalServerPort; -import org.springframework.cloud.client.DefaultServiceInstance; -import org.springframework.cloud.gateway.route.RouteLocator; -import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; -import org.springframework.cloud.gateway.test.PermitAllSecurityConfiguration; -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.context.annotation.Import; -import org.springframework.core.log.LogMessage; -import org.springframework.http.MediaType; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.util.StreamUtils; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RestController; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; - -@SpringBootTest(properties = {}, webEnvironment = RANDOM_PORT) -@ActiveProfiles("transferencoding") -public class TransferEncodingNormalizationHeadersFilterIntegrationTests { - - private static final Log log = LogFactory.getLog(TransferEncodingNormalizationHeadersFilterIntegrationTests.class); - - @LocalServerPort - private int port; - - @Test - void legitRequestShouldNotFail() throws Exception { - final ClassLoader classLoader = this.getClass().getClassLoader(); - - // Issue a crafted request with smuggling attempt - assert200With("Should Fail", - StreamUtils.copyToByteArray(classLoader.getResourceAsStream("transfer-encoding/invalid-request.bin"))); - - // Issue a legit request, which should not fail - assert200With("Should Not Fail", - StreamUtils.copyToByteArray(classLoader.getResourceAsStream("transfer-encoding/valid-request.bin"))); - } - - private void assert200With(String name, byte[] payload) throws Exception { - final String response = execute("localhost", port, payload); - log.info(LogMessage.format("Request to localhost:%d %s\n%s", port, name, new String(payload))); - assertThat(response).isNotNull(); - log.info(LogMessage.format("Response %s\n%s", name, response)); - assertThat(response).matches("HTTP/1.\\d 200 OK"); - } - - private String execute(String target, int port, byte[] payload) throws IOException { - final Socket socket = new Socket(target, port); - - final OutputStream out = socket.getOutputStream(); - final BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream())); - - out.write(payload); - - final String headResponse = in.readLine(); - - out.close(); - in.close(); - - return headResponse; - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(PermitAllSecurityConfiguration.class) - @LoadBalancerClient(name = "xferenc", configuration = TestLoadBalancerConfig.class) - @RestController - public static class TestConfig { - - @PostMapping(value = "/echo", produces = { MediaType.APPLICATION_JSON_VALUE }) - public Message message(@RequestBody Message message) throws IOException { - return message; - } - - @Bean - public RouteLocator routeLocator(RouteLocatorBuilder builder) { - return builder.routes() - .route("echo", r -> r.path("/route/echo").filters(f -> f.stripPrefix(1)).uri("lb://xferenc")) - .build(); - } - - } - - public static class Message { - - private String message; - - public Message(@JsonProperty("message") String message) { - this.message = message; - } - - public String getMessage() { - return message; - } - - public void setMessage(String message) { - this.message = message; - } - - } - - public static class TestLoadBalancerConfig { - - @LocalServerPort - protected int port = 0; - - @Bean - public ServiceInstanceListSupplier staticServiceInstanceListSupplier() { - return ServiceInstanceListSuppliers.from("xferenc", - new DefaultServiceInstance("xferenc" + "-1", "xferenc", "localhost", port, false)); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/TransferEncodingNormalizationHeadersFilterTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/TransferEncodingNormalizationHeadersFilterTests.java deleted file mode 100644 index 129ed58f..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/TransferEncodingNormalizationHeadersFilterTests.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.headers; - -import org.junit.jupiter.api.Test; - -import org.springframework.http.HttpHeaders; -import org.springframework.mock.http.server.reactive.MockServerHttpRequest; -import org.springframework.mock.web.server.MockServerWebExchange; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Spencer Gibb - */ -public class TransferEncodingNormalizationHeadersFilterTests { - - @Test - public void noTransferEncodingWithContentLength() { - MockServerHttpRequest.BaseBuilder builder = MockServerHttpRequest.post("http://localhost/post") - .header(HttpHeaders.CONTENT_LENGTH, "6"); - - HttpHeaders headers = testFilter(MockServerWebExchange.from(builder)); - assertThat(headers).containsKey(HttpHeaders.CONTENT_LENGTH).doesNotContainKey(HttpHeaders.TRANSFER_ENCODING); - } - - @Test - public void transferEncodingWithContentLength() { - MockServerHttpRequest.BaseBuilder builder = MockServerHttpRequest.post("http://localhost/post") - .header(HttpHeaders.CONTENT_LENGTH, "6").header(HttpHeaders.TRANSFER_ENCODING, "chunked"); - - HttpHeaders headers = testFilter(MockServerWebExchange.from(builder)); - assertThat(headers).doesNotContainKey(HttpHeaders.CONTENT_LENGTH).containsKey(HttpHeaders.TRANSFER_ENCODING); - } - - @Test - public void transferEncodingCaseInsensitiveWithContentLength() { - MockServerHttpRequest.BaseBuilder builder = MockServerHttpRequest.post("http://localhost/post") - .header(HttpHeaders.CONTENT_LENGTH, "6").header(HttpHeaders.TRANSFER_ENCODING, "Chunked "); - - HttpHeaders headers = testFilter(MockServerWebExchange.from(builder)); - assertThat(headers).doesNotContainKey(HttpHeaders.CONTENT_LENGTH).containsKey(HttpHeaders.TRANSFER_ENCODING); - } - - private HttpHeaders testFilter(MockServerWebExchange exchange) { - TransferEncodingNormalizationHeadersFilter filter = new TransferEncodingNormalizationHeadersFilter(); - return filter.filter(exchange.getRequest().getHeaders(), exchange); - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/XForwardedHeadersFilterTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/XForwardedHeadersFilterTests.java deleted file mode 100644 index 04459e46..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/XForwardedHeadersFilterTests.java +++ /dev/null @@ -1,312 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.headers; - -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.URI; -import java.util.LinkedHashSet; - -import org.junit.jupiter.api.Test; - -import org.springframework.http.HttpHeaders; -import org.springframework.mock.http.server.reactive.MockServerHttpRequest; -import org.springframework.mock.web.server.MockServerWebExchange; -import org.springframework.web.server.ServerWebExchange; -import org.springframework.web.util.UriComponentsBuilder; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.cloud.gateway.filter.headers.XForwardedHeadersFilter.X_FORWARDED_FOR_HEADER; -import static org.springframework.cloud.gateway.filter.headers.XForwardedHeadersFilter.X_FORWARDED_HOST_HEADER; -import static org.springframework.cloud.gateway.filter.headers.XForwardedHeadersFilter.X_FORWARDED_PORT_HEADER; -import static org.springframework.cloud.gateway.filter.headers.XForwardedHeadersFilter.X_FORWARDED_PREFIX_HEADER; -import static org.springframework.cloud.gateway.filter.headers.XForwardedHeadersFilter.X_FORWARDED_PROTO_HEADER; -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 - */ -public class XForwardedHeadersFilterTests { - - @Test - public void remoteAddressIsNull() throws Exception { - MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost:8080/get") - .header(HttpHeaders.HOST, "myhost").build(); - - XForwardedHeadersFilter filter = new XForwardedHeadersFilter(); - - HttpHeaders headers = filter.filter(request.getHeaders(), MockServerWebExchange.from(request)); - - assertThat(headers).containsKeys(X_FORWARDED_HOST_HEADER, X_FORWARDED_PORT_HEADER, X_FORWARDED_PROTO_HEADER); - - assertThat(headers.getFirst(X_FORWARDED_HOST_HEADER)).isEqualTo("localhost:8080"); - assertThat(headers.getFirst(X_FORWARDED_PORT_HEADER)).isEqualTo("8080"); - assertThat(headers.getFirst(X_FORWARDED_PROTO_HEADER)).isEqualTo("http"); - } - - @Test - public void xForwardedHeadersDoNotExist() throws Exception { - MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost:8080/get") - .remoteAddress(new InetSocketAddress(InetAddress.getByName("10.0.0.1"), 80)) - .header(HttpHeaders.HOST, "myhost").build(); - - XForwardedHeadersFilter filter = new XForwardedHeadersFilter(); - - HttpHeaders headers = filter.filter(request.getHeaders(), MockServerWebExchange.from(request)); - - assertThat(headers).containsKeys(X_FORWARDED_FOR_HEADER, X_FORWARDED_HOST_HEADER, X_FORWARDED_PORT_HEADER, - X_FORWARDED_PROTO_HEADER); - - assertThat(headers.getFirst(X_FORWARDED_FOR_HEADER)).isEqualTo("10.0.0.1"); - assertThat(headers.getFirst(X_FORWARDED_HOST_HEADER)).isEqualTo("localhost:8080"); - assertThat(headers.getFirst(X_FORWARDED_PORT_HEADER)).isEqualTo("8080"); - assertThat(headers.getFirst(X_FORWARDED_PROTO_HEADER)).isEqualTo("http"); - } - - @Test - public void defaultPort() throws Exception { - MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost/get") - .remoteAddress(new InetSocketAddress(InetAddress.getByName("10.0.0.1"), 80)) - .header(HttpHeaders.HOST, "myhost").build(); - - XForwardedHeadersFilter filter = new XForwardedHeadersFilter(); - - HttpHeaders headers = filter.filter(request.getHeaders(), MockServerWebExchange.from(request)); - - assertThat(headers).containsKeys(X_FORWARDED_FOR_HEADER, X_FORWARDED_HOST_HEADER, X_FORWARDED_PORT_HEADER, - X_FORWARDED_PROTO_HEADER); - - assertThat(headers.getFirst(X_FORWARDED_FOR_HEADER)).isEqualTo("10.0.0.1"); - assertThat(headers.getFirst(X_FORWARDED_HOST_HEADER)).isEqualTo("localhost"); - assertThat(headers.getFirst(X_FORWARDED_PORT_HEADER)).isEqualTo("80"); - assertThat(headers.getFirst(X_FORWARDED_PROTO_HEADER)).isEqualTo("http"); - } - - @Test - public void appendsValues() throws Exception { - MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost:8080/get") - .remoteAddress(new InetSocketAddress(InetAddress.getByName("10.0.0.1"), 80)) - .header(X_FORWARDED_FOR_HEADER, "192.168.0.2").header(X_FORWARDED_HOST_HEADER, "example.com") - .header(X_FORWARDED_PORT_HEADER, "443").header(X_FORWARDED_PROTO_HEADER, "https").build(); - - XForwardedHeadersFilter filter = new XForwardedHeadersFilter(); - - HttpHeaders headers = filter.filter(request.getHeaders(), MockServerWebExchange.from(request)); - - assertThat(headers).containsKeys(X_FORWARDED_FOR_HEADER, X_FORWARDED_HOST_HEADER, X_FORWARDED_PORT_HEADER, - X_FORWARDED_PROTO_HEADER); - - assertThat(headers.getFirst(X_FORWARDED_FOR_HEADER)).isEqualTo("192.168.0.2,10.0.0.1"); - assertThat(headers.getFirst(X_FORWARDED_HOST_HEADER)).isEqualTo("example.com,localhost:8080"); - assertThat(headers.getFirst(X_FORWARDED_PORT_HEADER)).isEqualTo("443,8080"); - assertThat(headers.getFirst(X_FORWARDED_PROTO_HEADER)).isEqualTo("https,http"); - } - - @Test - public void appendDisabled() throws Exception { - MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost:8080/get") - .remoteAddress(new InetSocketAddress(InetAddress.getByName("10.0.0.1"), 80)) - .header(X_FORWARDED_FOR_HEADER, "192.168.0.2").header(X_FORWARDED_HOST_HEADER, "example.com") - .header(X_FORWARDED_PORT_HEADER, "443").header(X_FORWARDED_PROTO_HEADER, "https") - .header(X_FORWARDED_PREFIX_HEADER, "/prefix").build(); - - XForwardedHeadersFilter filter = new XForwardedHeadersFilter(); - filter.setForAppend(false); - filter.setHostAppend(false); - filter.setPortAppend(false); - filter.setProtoAppend(false); - filter.setPrefixAppend(false); - - HttpHeaders headers = filter.filter(request.getHeaders(), MockServerWebExchange.from(request)); - - assertThat(headers).containsKeys(X_FORWARDED_FOR_HEADER, X_FORWARDED_HOST_HEADER, X_FORWARDED_PORT_HEADER, - X_FORWARDED_PROTO_HEADER, X_FORWARDED_PREFIX_HEADER); - - assertThat(headers.getFirst(X_FORWARDED_FOR_HEADER)).isEqualTo("10.0.0.1"); - assertThat(headers.getFirst(X_FORWARDED_HOST_HEADER)).isEqualTo("localhost:8080"); - assertThat(headers.getFirst(X_FORWARDED_PORT_HEADER)).isEqualTo("8080"); - assertThat(headers.getFirst(X_FORWARDED_PROTO_HEADER)).isEqualTo("http"); - assertThat(headers.getFirst(X_FORWARDED_PREFIX_HEADER)).isEqualTo("/prefix"); - } - - @Test - public void prefixToInfer() throws Exception { - MockServerHttpRequest request = MockServerHttpRequest.get("https://originalhost:8080/prefix/get") - .remoteAddress(new InetSocketAddress(InetAddress.getByName("10.0.0.1"), 80)).build(); - - XForwardedHeadersFilter filter = new XForwardedHeadersFilter(); - filter.setPrefixAppend(true); - filter.setPrefixEnabled(true); - - ServerWebExchange exchange = MockServerWebExchange.from(request); - LinkedHashSet originalUris = new LinkedHashSet<>(); - originalUris.add(UriComponentsBuilder.fromUriString("https://originalhost:8080/prefix/get/").build().toUri()); // trailing - // slash - exchange.getAttributes().put(GATEWAY_ORIGINAL_REQUEST_URL_ATTR, originalUris); - URI requestUri = UriComponentsBuilder.fromUriString("https://routedservice:8090/get").build().toUri(); - exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUri); - - HttpHeaders headers = filter.filter(request.getHeaders(), exchange); - - assertThat(headers).containsKeys(X_FORWARDED_PREFIX_HEADER); - - assertThat(headers.getFirst(X_FORWARDED_PREFIX_HEADER)).isEqualTo("/prefix"); - } - - @Test - public void prefixToInferWhenEqualsResource() throws Exception { - MockServerHttpRequest request = MockServerHttpRequest.get("https://originalhost:8080/resource/resource/") - .remoteAddress(new InetSocketAddress(InetAddress.getByName("10.0.0.1"), 80)).build(); - - XForwardedHeadersFilter filter = new XForwardedHeadersFilter(); - filter.setPrefixAppend(true); - filter.setPrefixEnabled(true); - - ServerWebExchange exchange = MockServerWebExchange.from(request); - LinkedHashSet originalUris = new LinkedHashSet<>(); - originalUris.add( - UriComponentsBuilder.fromUriString("https://originalhost:8080/resource/resource/").build().toUri()); // trailing - // slash - exchange.getAttributes().put(GATEWAY_ORIGINAL_REQUEST_URL_ATTR, originalUris); - URI requestUri = UriComponentsBuilder.fromUriString("https://routedservice:8090/resource").build().toUri(); - exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUri); - - HttpHeaders headers = filter.filter(request.getHeaders(), exchange); - - assertThat(headers).containsKeys(X_FORWARDED_PREFIX_HEADER); - - assertThat(headers.getFirst(X_FORWARDED_PREFIX_HEADER)).isEqualTo("/resource"); - } - - @Test - public void prefixAddedWithoutTrailingSlash() throws Exception { - MockServerHttpRequest request = MockServerHttpRequest.get("https://originalhost:8080/foo/bar") - .remoteAddress(new InetSocketAddress(InetAddress.getByName("10.0.0.1"), 80)).build(); - - XForwardedHeadersFilter filter = new XForwardedHeadersFilter(); - filter.setPrefixAppend(true); - filter.setPrefixEnabled(true); - - ServerWebExchange exchange = MockServerWebExchange.from(request); - LinkedHashSet originalUris = new LinkedHashSet<>(); - originalUris.add(UriComponentsBuilder.fromUriString("https://originalhost:8080/foo/bar").build().toUri()); - exchange.getAttributes().put(GATEWAY_ORIGINAL_REQUEST_URL_ATTR, originalUris); - URI requestUri = UriComponentsBuilder.fromUriString("https://routedservice:8090/").build().toUri(); - exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUri); - - HttpHeaders headers = filter.filter(request.getHeaders(), exchange); - - assertThat(headers.getFirst(X_FORWARDED_PREFIX_HEADER)).isEqualTo("/foo/bar"); - } - - @Test - public void noPrefixToInfer() throws Exception { - MockServerHttpRequest request = MockServerHttpRequest.get("https://originalhost:8080/get") - .remoteAddress(new InetSocketAddress(InetAddress.getByName("10.0.0.1"), 80)).build(); - - XForwardedHeadersFilter filter = new XForwardedHeadersFilter(); - filter.setPrefixAppend(true); - filter.setPrefixEnabled(true); - filter.setForEnabled(false); - filter.setHostEnabled(false); - filter.setPortEnabled(false); - filter.setProtoEnabled(false); - - ServerWebExchange exchange = MockServerWebExchange.from(request); - LinkedHashSet originalUris = new LinkedHashSet<>(); - originalUris.add(UriComponentsBuilder.fromUriString("https://originalhost:8080/get/").build().toUri()); - exchange.getAttributes().put(GATEWAY_ORIGINAL_REQUEST_URL_ATTR, originalUris); - URI requestUri = UriComponentsBuilder.fromUriString("https://routedservice:8090/get").build().toUri(); - exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUri); - - HttpHeaders headers = filter.filter(request.getHeaders(), exchange); - - assertThat(headers).isEmpty(); - } - - @Test - public void routedPathInRequestPathButNotPrefix() throws Exception { - MockServerHttpRequest request = MockServerHttpRequest.get("https://originalhost:8080/get") - .remoteAddress(new InetSocketAddress(InetAddress.getByName("10.0.0.1"), 80)).build(); - - XForwardedHeadersFilter filter = new XForwardedHeadersFilter(); - filter.setPrefixAppend(true); - filter.setPrefixEnabled(true); - filter.setForEnabled(false); - filter.setHostEnabled(false); - filter.setPortEnabled(false); - filter.setProtoEnabled(false); - - ServerWebExchange exchange = MockServerWebExchange.from(request); - LinkedHashSet originalUris = new LinkedHashSet<>(); - originalUris.add(UriComponentsBuilder.fromUriString("https://originalhost:8080/one/two/three").build().toUri()); - exchange.getAttributes().put(GATEWAY_ORIGINAL_REQUEST_URL_ATTR, originalUris); - URI requestUri = UriComponentsBuilder.fromUriString("https://routedservice:8090/two").build().toUri(); - exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUri); - - HttpHeaders headers = filter.filter(request.getHeaders(), exchange); - - assertThat(headers).isEmpty(); - } - - @Test - public void allDisabled() throws Exception { - MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost:8080/get") - .remoteAddress(new InetSocketAddress(InetAddress.getByName("10.0.0.1"), 80)).build(); - - XForwardedHeadersFilter filter = new XForwardedHeadersFilter(); - filter.setForEnabled(false); - filter.setHostEnabled(false); - filter.setPortEnabled(false); - filter.setProtoEnabled(false); - filter.setPrefixEnabled(false); - - HttpHeaders headers = filter.filter(request.getHeaders(), MockServerWebExchange.from(request)); - - assertThat(headers).isEmpty(); - } - - @Test - public void allowDuplicateEntriesInXForwardedForHeader() throws Exception { - MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost:8080/get") - .remoteAddress(new InetSocketAddress(InetAddress.getByName("10.0.0.1"), 80)) - .header(X_FORWARDED_FOR_HEADER, "10.0.0.1").build(); - - XForwardedHeadersFilter filter = new XForwardedHeadersFilter(); - - HttpHeaders headers = filter.filter(request.getHeaders(), MockServerWebExchange.from(request)); - - assertThat(headers).containsKeys(X_FORWARDED_FOR_HEADER); - - assertThat(headers.getFirst(X_FORWARDED_FOR_HEADER)).isEqualTo("10.0.0.1,10.0.0.1"); - } - - @Test - public void nullValuesSkipped() throws Exception { - MockServerHttpRequest request = MockServerHttpRequest.get("/get") - .remoteAddress(new InetSocketAddress(InetAddress.getByName("10.0.0.1"), 80)) - .header(X_FORWARDED_FOR_HEADER, "10.0.0.1").build(); - - XForwardedHeadersFilter filter = new XForwardedHeadersFilter(); - - HttpHeaders headers = filter.filter(request.getHeaders(), MockServerWebExchange.from(request)); - - assertThat(headers).doesNotContainKeys(X_FORWARDED_PROTO_HEADER, X_FORWARDED_HOST_HEADER); - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/observation/B3BraveObservedHttpHeadersFilterTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/observation/B3BraveObservedHttpHeadersFilterTests.java deleted file mode 100644 index b8a8636d..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/observation/B3BraveObservedHttpHeadersFilterTests.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright 2013-2022 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.filter.headers.observation; - -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -import brave.Tracing; -import brave.propagation.B3Propagation; -import brave.sampler.Sampler; -import brave.test.TestSpanHandler; -import io.micrometer.observation.Observation; -import io.micrometer.observation.ObservationHandler; -import io.micrometer.observation.contextpropagation.ObservationThreadLocalAccessor; -import io.micrometer.observation.tck.TestObservationRegistry; -import io.micrometer.tracing.TraceContext; -import io.micrometer.tracing.Tracer; -import io.micrometer.tracing.brave.bridge.BraveBaggageManager; -import io.micrometer.tracing.brave.bridge.BraveCurrentTraceContext; -import io.micrometer.tracing.brave.bridge.BraveFinishedSpan; -import io.micrometer.tracing.brave.bridge.BravePropagator; -import io.micrometer.tracing.brave.bridge.BraveTracer; -import io.micrometer.tracing.exporter.FinishedSpan; -import io.micrometer.tracing.handler.DefaultTracingObservationHandler; -import io.micrometer.tracing.propagation.Propagator; -import io.micrometer.tracing.test.simple.SpansAssert; -import org.junit.jupiter.api.Test; -import reactor.util.context.Context; - -import org.springframework.cloud.gateway.route.Route; -import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatusCode; -import org.springframework.mock.http.server.reactive.MockServerHttpRequest; -import org.springframework.mock.web.server.MockServerWebExchange; - -import static org.assertj.core.api.Assertions.assertThat; - -class B3BraveObservedHttpHeadersFilterTests { - - TestSpanHandler testSpanHandler = new TestSpanHandler(); - - Tracing tracing = Tracing.newBuilder() - .propagationFactory(B3Propagation.newFactoryBuilder().injectFormat(B3Propagation.Format.SINGLE).build()) - .addSpanHandler(testSpanHandler).sampler(Sampler.ALWAYS_SAMPLE).build(); - - Tracer tracer = new BraveTracer(tracing.tracer(), new BraveCurrentTraceContext(tracing.currentTraceContext()), - new BraveBaggageManager()); - - Propagator propagator = new BravePropagator(tracing); - - @Test - void shouldWorkWithB3SingleHeader() { - TestObservationRegistry observationRegistry = TestObservationRegistry.create(); - observationRegistry.observationConfig() - .observationHandler( - new ObservationHandler.FirstMatchingCompositeObservationHandler( - new GatewayPropagatingSenderTracingObservationHandler(tracer, propagator, - Collections.singletonList("X-A")), - new DefaultTracingObservationHandler(tracer))); - - Observation.createNotStarted("parent", observationRegistry).observe(() -> { - // given - MockServerHttpRequest.BaseBuilder builder = MockServerHttpRequest - .get("http://localhost:8080/{foo}", "get").header("X-A", "aValue"); - TraceContext context = tracer.currentTraceContext().context(); - propagator.inject(context, builder, (b, k, v) -> b.header(k, v)); - - // and - MockServerHttpRequest request = builder.build(); - MockServerWebExchange exchange = MockServerWebExchange.from(request); - ServerWebExchangeUtils.putUriTemplateVariables(exchange, Map.of("foo", "get")); - exchange.getResponse().setStatusCode(HttpStatusCode.valueOf(200)); - Route route = Route.async().id("foo").uri("http://localhost:8080/").order(1) - .predicate(serverWebExchange -> true).build(); - exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR, route); - // Parent observation - Context ctx = Context - .of(Map.of(ObservationThreadLocalAccessor.KEY, observationRegistry.getCurrentObservation())); - exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_REACTOR_CONTEXT_ATTR, ctx); - - // and - ObservedRequestHttpHeadersFilter requestHttpHeadersFilter = new ObservedRequestHttpHeadersFilter( - observationRegistry); - ObservedResponseHttpHeadersFilter responseHttpHeadersFilter = new ObservedResponseHttpHeadersFilter(); - - // when - HttpHeaders headers = requestHttpHeadersFilter.filter(request.getHeaders(), exchange); - headers = responseHttpHeadersFilter.filter(headers, exchange); - - // then - assertThat(headers).containsOnlyKeys("X-A", "b3").doesNotContainEntry("b3", request.getHeaders().get("b3")); - assertThat(headers.get("b3").get(0)).matches("^" + context.traceId() + "-(.*)-1-" + context.spanId() + "$"); - List finishedSpans = testSpanHandler.spans().stream().map(BraveFinishedSpan::new) - .collect(Collectors.toList()); - SpansAssert.then(finishedSpans).hasASpanWithName("HTTP GET", - spanAssert -> spanAssert.hasTag("spring.cloud.gateway.route.id", "foo").hasTag("http.method", "GET") - .hasTag("http.status_code", "200") - .hasTag("spring.cloud.gateway.route.uri", "http://localhost:8080/") - .hasTag("http.uri", "http://localhost:8080/get")); - }); - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/observation/GatewayPropagatingSenderTracingObservationHandlerTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/observation/GatewayPropagatingSenderTracingObservationHandlerTests.java deleted file mode 100644 index 54b8054a..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/observation/GatewayPropagatingSenderTracingObservationHandlerTests.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2013-2022 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.filter.headers.observation; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import io.micrometer.tracing.Span; -import io.micrometer.tracing.TraceContext; -import io.micrometer.tracing.propagation.Propagator; -import io.micrometer.tracing.test.simple.SimpleSpanBuilder; -import io.micrometer.tracing.test.simple.SimpleTracer; -import org.junit.jupiter.api.Test; - -import org.springframework.http.HttpHeaders; -import org.springframework.mock.http.server.reactive.MockServerHttpRequest; -import org.springframework.mock.web.server.MockServerWebExchange; - -import static org.assertj.core.api.BDDAssertions.then; - -class GatewayPropagatingSenderTracingObservationHandlerTests { - - SimpleTracer tracer = new SimpleTracer(); - - Propagator propagator = new Propagator() { - @Override - public List fields() { - return Arrays.asList("foo", "bar"); - } - - @Override - public void inject(TraceContext context, C carrier, Setter setter) { - - } - - @Override - public Span.Builder extract(C carrier, Getter getter) { - return new SimpleSpanBuilder(tracer); - } - }; - - GatewayPropagatingSenderTracingObservationHandler handler = new GatewayPropagatingSenderTracingObservationHandler( - tracer, propagator, Collections.singletonList("remote")); - - @Test - void shouldRemovePropagationFieldsFromTheRequestBeforePropagating() { - HttpHeaders headers = new HttpHeaders(); - headers.put("foo", Collections.singletonList("foo value")); - headers.put("bar", Collections.singletonList("bar value")); - headers.put("baz", Collections.singletonList("baz value")); - headers.put("remote", Collections.singletonList("remote value")); - MockServerHttpRequest request = MockServerHttpRequest.get("/get").build(); - MockServerWebExchange serverWebExchange = MockServerWebExchange.from(request); - GatewayContext gatewayContext = new GatewayContext(headers, request, serverWebExchange); - - handler.onStart(gatewayContext); - - then(headers).doesNotContainKeys("foo", "bar").containsEntry("baz", Collections.singletonList("baz value")) - .containsEntry("remote", Collections.singletonList("remote value")); - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/observation/ObservationClosingWebExceptionHandlerTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/observation/ObservationClosingWebExceptionHandlerTests.java deleted file mode 100644 index d3ff9b47..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/observation/ObservationClosingWebExceptionHandlerTests.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2013-2022 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.filter.headers.observation; - -import io.micrometer.observation.Observation; -import org.junit.jupiter.api.Test; -import org.mockito.Mockito; - -import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; -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.assertThatNoException; - -class ObservationClosingWebExceptionHandlerTests { - - ObservationClosingWebExceptionHandler handler = new ObservationClosingWebExceptionHandler(); - - MockServerHttpRequest request = MockServerHttpRequest.get("/get").build(); - - ServerWebExchange exchange = MockServerWebExchange.from(request); - - @Test - void shouldDoNothingWhenObservationAlreadyStopped() { - exchange.getAttributes().put(ObservedResponseHttpHeadersFilter.OBSERVATION_STOPPED, "true"); - exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_OBSERVATION_ATTR, - "if this attribute will be attempted to be retrieved ClassCast will be thrown"); - - assertThatNoException().isThrownBy(() -> handler.handle(exchange, new RuntimeException())); - } - - @Test - void shouldDoNothingWhenThereIsNoObservation() { - assertThatNoException().isThrownBy(() -> handler.handle(exchange, new RuntimeException())); - } - - @Test - void shouldStopTheObservationIfItWasNotStoppedPreviouslyAndThereWasAnError() { - Observation observation = Mockito.mock(Observation.class); - exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_OBSERVATION_ATTR, observation); - RuntimeException runtimeException = new RuntimeException(); - - assertThatNoException().isThrownBy(() -> handler.handle(exchange, runtimeException)); - Mockito.verify(observation).error(runtimeException); - Mockito.verify(observation).stop(); - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/observation/ObservedHttpHeadersFilterTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/observation/ObservedHttpHeadersFilterTests.java deleted file mode 100644 index 88539034..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/observation/ObservedHttpHeadersFilterTests.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2013-2022 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.filter.headers.observation; - -import java.util.Collections; -import java.util.Deque; -import java.util.Map; -import java.util.function.BiConsumer; - -import io.micrometer.core.instrument.Tags; -import io.micrometer.core.tck.MeterRegistryAssert; -import io.micrometer.observation.Observation; -import io.micrometer.observation.ObservationHandler; -import io.micrometer.observation.contextpropagation.ObservationThreadLocalAccessor; -import io.micrometer.tracing.TraceContext; -import io.micrometer.tracing.test.SampleTestRunner; -import io.micrometer.tracing.test.reporter.BuildingBlocks; -import io.micrometer.tracing.test.simple.SpansAssert; -import reactor.util.context.Context; - -import org.springframework.cloud.gateway.route.Route; -import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatusCode; -import org.springframework.mock.http.server.reactive.MockServerHttpRequest; -import org.springframework.mock.web.server.MockServerWebExchange; - -import static org.assertj.core.api.Assertions.assertThat; - -public class ObservedHttpHeadersFilterTests extends SampleTestRunner { - - ObservedHttpHeadersFilterTests() { - super(SampleRunnerConfig.builder().build()); - } - - @Override - public TracingSetup[] getTracingSetup() { - return new TracingSetup[] { TracingSetup.IN_MEMORY_BRAVE, TracingSetup.ZIPKIN_BRAVE }; - } - - @Override - public SampleTestRunnerConsumer yourCode() throws Exception { - return (bb, meterRegistry) -> { - - // We assume that there has already been a trace - MockServerHttpRequest.BaseBuilder builder = MockServerHttpRequest - .get("http://localhost:8080/{foo}", "get").header("X-A", "aValue"); - TraceContext context = bb.getTracer().currentTraceContext().context(); - bb.getPropagator().inject(context, builder, (b, k, v) -> b.header(k, v)); - MockServerHttpRequest request = builder.build(); - MockServerWebExchange exchange = MockServerWebExchange.from(request); - ServerWebExchangeUtils.putUriTemplateVariables(exchange, Map.of("foo", "get")); - Route route = Route.async().id("foo").uri("http://localhost:8080/").order(1) - .predicate(serverWebExchange -> true).build(); - exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR, route); - // Parent observation - Context ctx = Context - .of(Map.of(ObservationThreadLocalAccessor.KEY, getObservationRegistry().getCurrentObservation())); - exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_REACTOR_CONTEXT_ATTR, ctx); - exchange.getResponse().setStatusCode(HttpStatusCode.valueOf(200)); - - // when - HttpHeaders headers = new ObservedRequestHttpHeadersFilter(getObservationRegistry()) - .filter(request.getHeaders(), exchange); - headers = new ObservedResponseHttpHeadersFilter().filter(headers, exchange); - - // then - assertThat((String) exchange.getAttribute(ObservedResponseHttpHeadersFilter.OBSERVATION_STOPPED)) - .isNotNull(); - assertThat(headers).containsEntry("X-A", Collections.singletonList("aValue")) - .containsEntry("X-B3-Sampled", Collections.singletonList("1")) - .containsEntry("X-B3-TraceId", Collections.singletonList(context.traceId())) - .doesNotContainEntry("X-B3-SpanId", Collections.singletonList(context.spanId())) - .containsKey("X-B3-SpanId"); - SpansAssert.then(bb.getFinishedSpans()).hasASpanWithName("HTTP GET", - spanAssert -> spanAssert.hasTag("http.method", "GET").hasTag("http.status_code", "200") - .hasTag("http.uri", "http://localhost:8080/get") - .hasTag("spring.cloud.gateway.route.uri", "http://localhost:8080/") - .hasTag("spring.cloud.gateway.route.id", "foo")); - MeterRegistryAssert.then(meterRegistry).hasTimerWithNameAndTags("http.client.requests", - Tags.of("spring.cloud.gateway.route.id", "foo", "error", "none", "http.method", "GET", - "http.status_code", "200", "spring.cloud.gateway.route.uri", "http://localhost:8080/")) - .hasMeterWithNameAndTags("http.client.requests.active", Tags.of("spring.cloud.gateway.route.id", - "foo", "http.method", "GET", "spring.cloud.gateway.route.uri", "http://localhost:8080/")); - }; - } - - @Override - public BiConsumer>> customizeObservationHandlers() { - return (bb, observationHandlers) -> observationHandlers - .addFirst(new GatewayPropagatingSenderTracingObservationHandler(bb.getTracer(), bb.getPropagator(), - Collections.emptyList())); - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/observation/ObservedResponseHttpHeadersFilterTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/observation/ObservedResponseHttpHeadersFilterTests.java deleted file mode 100644 index 64ba0135..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/headers/observation/ObservedResponseHttpHeadersFilterTests.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2013-2022 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.filter.headers.observation; - -import io.micrometer.observation.Observation; -import org.junit.jupiter.api.Test; - -import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; -import org.springframework.http.HttpHeaders; -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.BDDAssertions.thenNoException; - -class ObservedResponseHttpHeadersFilterTests { - - @Test - void shouldDoNothingWhenObservationIsNoOp() { - MockServerHttpRequest request = MockServerHttpRequest.get("/get").build(); - ServerWebExchange exchange = MockServerWebExchange.from(request); - exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_OBSERVATION_ATTR, Observation.NOOP); - ObservedResponseHttpHeadersFilter filter = new ObservedResponseHttpHeadersFilter(); - - thenNoException().isThrownBy(() -> filter.filter(new HttpHeaders(), exchange)); - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/ratelimit/PrincipalNameKeyResolverIntegrationTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/ratelimit/PrincipalNameKeyResolverIntegrationTests.java deleted file mode 100644 index c72a0c2c..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/ratelimit/PrincipalNameKeyResolverIntegrationTests.java +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.ratelimit; - -import java.security.Principal; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import reactor.core.publisher.Mono; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.web.server.LocalServerPort; -import org.springframework.cloud.gateway.route.RouteLocator; -import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Primary; -import org.springframework.security.config.web.server.ServerHttpSecurity; -import org.springframework.security.core.userdetails.MapReactiveUserDetailsService; -import org.springframework.security.core.userdetails.User; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.web.server.SecurityWebFilterChain; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.util.TestSocketUtils; -import org.springframework.test.web.reactive.server.WebTestClient; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT; -import static org.springframework.web.reactive.function.client.ExchangeFilterFunctions.basicAuthentication; - -@SpringBootTest(webEnvironment = DEFINED_PORT) -@ActiveProfiles("principalname") -public class PrincipalNameKeyResolverIntegrationTests { - - @LocalServerPort - protected int port = 0; - - protected WebTestClient client; - - protected String baseUri; - - @BeforeAll - public static void beforeClass() { - System.setProperty("server.port", String.valueOf(TestSocketUtils.findAvailableTcpPort())); - } - - @AfterAll - public static void afterClass() { - System.clearProperty("server.port"); - } - - @BeforeEach - public void setup() { - this.baseUri = "http://localhost:" + port; - this.client = WebTestClient.bindToServer().baseUrl(baseUri).build(); - } - - @Test - public void keyResolverWorks() { - this.client.mutate().filter(basicAuthentication("user", "password")).build().get().uri("/myapi/1").exchange() - .expectStatus().isOk().expectBody().json("{\"user\":\"1\"}"); - } - - @RestController - @RequestMapping("/downstream") - @EnableAutoConfiguration - @SpringBootConfiguration - protected static class TestConfig { - - @Value("${server.port}") - private int port; - - @GetMapping("/myapi/{id}") - public Map myapi(@PathVariable String id, Principal principal) { - return Collections.singletonMap(principal.getName(), id); - } - - @Bean - public RouteLocator customRouteLocator(RouteLocatorBuilder builder) { - return builder.routes() - .route(r -> r.path("/myapi/**").filters( - f -> f.requestRateLimiter(c -> c.setRateLimiter(myRateLimiter())).prefixPath("/downstream")) - .uri("http://localhost:" + port)) - .build(); - } - - @Bean - @Primary - MyRateLimiter myRateLimiter() { - return new MyRateLimiter(); - } - - @Bean - SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http) { - return http.httpBasic().and().authorizeExchange().pathMatchers("/myapi/**").authenticated().anyExchange() - .permitAll().and().build(); - } - - @Bean - public MapReactiveUserDetailsService reactiveUserDetailsService() { - UserDetails user = User.withUsername("user").password("{noop}password").roles("USER").build(); - return new MapReactiveUserDetailsService(user); - } - - class MyRateLimiter implements RateLimiter { - - private HashMap map = new HashMap<>(); - - @Override - public Mono isAllowed(String routeId, String id) { - return Mono.just(new RateLimiter.Response(true, Collections.singletonMap("X-Value", "5000000"))); - } - - @Override - public Class getConfigClass() { - return Object.class; - } - - @Override - public Map getConfig() { - return map; - } - - @Override - public Object newConfig() { - return null; - } - - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/ratelimit/PrincipalNameKeyResolverTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/ratelimit/PrincipalNameKeyResolverTests.java deleted file mode 100644 index c2656749..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/ratelimit/PrincipalNameKeyResolverTests.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2013-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.filter.ratelimit; - -import java.security.Principal; -import java.time.Duration; - -import org.junit.jupiter.api.Test; -import reactor.core.publisher.Mono; -import reactor.test.StepVerifier; - -import org.springframework.web.server.ServerWebExchange; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class PrincipalNameKeyResolverTests { - - @Test - public void nullPrincipalNameWorks() { - PrincipalNameKeyResolver keyResolver = new PrincipalNameKeyResolver(); - ServerWebExchange exchange = mock(ServerWebExchange.class); - when(exchange.getPrincipal()).thenReturn(Mono.just(mock(Principal.class))); - StepVerifier.create(keyResolver.resolve(exchange)).expectComplete().verify(Duration.ofSeconds(5)); - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/ratelimit/RedisRateLimiterConfigTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/ratelimit/RedisRateLimiterConfigTests.java deleted file mode 100644 index 59b993d1..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/ratelimit/RedisRateLimiterConfigTests.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.ratelimit; - -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -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.route.Route; -import org.springframework.cloud.gateway.route.RouteLocator; -import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; -import org.springframework.context.annotation.Bean; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.ActiveProfiles; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Spencer Gibb - */ -@SpringBootTest -@DirtiesContext -@ActiveProfiles("redis-rate-limiter-config") -public class RedisRateLimiterConfigTests { - - @Autowired - private RedisRateLimiter rateLimiter; - - @Autowired - private RouteLocator routeLocator; - - @BeforeEach - public void init() { - // prime routes since getRoutes() no longer blocks - routeLocator.getRoutes().collectList().block(); - } - - @Test - public void shouldThrowAnErrorWhenReplenishRateIsHigherThanBurstCapacity() { - Assertions.assertThatThrownBy(() -> new RedisRateLimiter(10, 5)).isInstanceOf(IllegalArgumentException.class); - } - - @Test - public void redisRateConfiguredFromEnvironment() { - assertFilter("redis_rate_limiter_config_test", 10, 20, 1, false); - } - - @Test - public void redisRateConfiguredFromEnvironmentMinimal() { - assertFilter("redis_rate_limiter_minimal_config_test", 2, 1, 1, false); - } - - @Test - public void redisRateConfiguredFromJavaAPI() { - assertFilter("custom_redis_rate_limiter", 20, 40, 10, false); - } - - @Test - public void redisRateConfiguredFromJavaAPIDirectBean() { - assertFilter("alt_custom_redis_rate_limiter", 30, 60, 20, true); - } - - private void assertFilter(String key, int replenishRate, int burstCapacity, int requestedTokens, - boolean useDefaultConfig) { - RedisRateLimiter.Config config; - - if (useDefaultConfig) { - config = rateLimiter.getDefaultConfig(); - } - else { - assertThat(rateLimiter.getConfig()).containsKey(key); - config = rateLimiter.getConfig().get(key); - } - assertThat(config).isNotNull(); - assertThat(config.getReplenishRate()).isEqualTo(replenishRate); - assertThat(config.getBurstCapacity()).isEqualTo(burstCapacity); - assertThat(config.getRequestedTokens()).isEqualTo(requestedTokens); - - Route route = routeLocator.getRoutes().filter(r -> r.getId().equals(key)).next().block(); - assertThat(route).isNotNull(); - assertThat(route.getFilters()).hasSize(1); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - public static class TestConfig { - - @Bean - public RouteLocator testRouteLocator(RouteLocatorBuilder builder) { - return builder.routes().route("custom_redis_rate_limiter", r -> r.path("/custom") - .filters(f -> f.requestRateLimiter() - .rateLimiter(RedisRateLimiter.class, - rl -> rl.setBurstCapacity(40).setReplenishRate(20).setRequestedTokens(10)) - .and()) - .uri("http://localhost")) - .route("alt_custom_redis_rate_limiter", - r -> r.path("/custom") - .filters(f -> f.requestRateLimiter(c -> c.setRateLimiter(myRateLimiter()))) - .uri("http://localhost")) - .build(); - - } - - @Bean - public RedisRateLimiter myRateLimiter() { - return new RedisRateLimiter(30, 60, 20); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/ratelimit/RedisRateLimiterDefaultFilterConfigTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/ratelimit/RedisRateLimiterDefaultFilterConfigTests.java deleted file mode 100644 index a7f5be0d..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/ratelimit/RedisRateLimiterDefaultFilterConfigTests.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.ratelimit; - -import org.junit.jupiter.api.BeforeEach; -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.route.Route; -import org.springframework.cloud.gateway.route.RouteLocator; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.ActiveProfiles; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Spencer Gibb - * @author Denis Cutic - */ -@SpringBootTest -@DirtiesContext -@ActiveProfiles("redis-rate-limiter-default-config") -public class RedisRateLimiterDefaultFilterConfigTests { - - @Autowired - private RedisRateLimiter rateLimiter; - - @Autowired - private RouteLocator routeLocator; - - @BeforeEach - public void init() { - // prime routes since getRoutes() no longer blocks - routeLocator.getRoutes().collectList().block(); - } - - @Test - public void redisRateConfiguredFromEnvironmentDefaultFilters() { - String routeId = "redis_rate_limiter_config_default_test"; - RedisRateLimiter.Config config = rateLimiter.loadConfiguration(routeId); - assertConfigAndRoute(routeId, 70, 80, 10, config); - } - - private void assertConfigAndRoute(String key, int replenishRate, int burstCapacity, int requestedTokens, - RedisRateLimiter.Config config) { - assertThat(config).isNotNull(); - assertThat(config.getReplenishRate()).isEqualTo(replenishRate); - assertThat(config.getBurstCapacity()).isEqualTo(burstCapacity); - assertThat(config.getRequestedTokens()).isEqualTo(requestedTokens); - - Route route = routeLocator.getRoutes().filter(r -> r.getId().equals(key)).next().block(); - assertThat(route).isNotNull(); - assertThat(route.getFilters()).isNotEmpty(); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - public static class TestConfig { - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/ratelimit/RedisRateLimiterLuaScriptTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/ratelimit/RedisRateLimiterLuaScriptTests.java deleted file mode 100644 index dc36f929..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/ratelimit/RedisRateLimiterLuaScriptTests.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright 2013-2022 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.filter.ratelimit; - -import java.util.Arrays; -import java.util.List; - -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.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.data.redis.core.ReactiveStringRedisTemplate; -import org.springframework.data.redis.core.script.RedisScript; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.DynamicPropertyRegistry; -import org.springframework.test.context.DynamicPropertySource; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Zhuozhi Ji - */ -@SpringBootTest -@DirtiesContext -@Testcontainers -@Tag("DockerRequired") -public class RedisRateLimiterLuaScriptTests { - - static final String KEY_PREFIX = "redis-rate-limiter-lua-script-tests"; - - @Container - public static GenericContainer redis = new GenericContainer<>("redis:5.0.14-alpine").withExposedPorts(6379); - - @Autowired - ReactiveStringRedisTemplate redisTemplate; - - @Autowired - RedisScript> redisScript; - - @DynamicPropertySource - static void containerProperties(DynamicPropertyRegistry registry) { - registry.add("spring.data.redis.host", redis::getContainerIpAddress); - registry.add("spring.data.redis.port", redis::getFirstMappedPort); - } - - static List getKeys(String id) { - String prefix = KEY_PREFIX + ".{" + id; - String tokens = prefix + "}.tokens"; - String timestamp = prefix + "}.timestamp"; - return Arrays.asList(tokens, timestamp); - } - - static List getArgs(long rate, long capacity, long now, long requested) { - return Arrays.asList(rate + "", capacity + "", now + "", requested + ""); - } - - @Test - public void testNewAccess() { - long rate = 1; - long capacity = 10; - long now = System.currentTimeMillis(); - long requested = 1; - List keys = getKeys("new_access"); - List args = getArgs(rate, capacity, now, requested); - List result = redisTemplate.execute(redisScript, keys, args).blockFirst(); - assertThat(result.get(0)).isEqualTo(1); - assertThat(result.get(1)).isEqualTo(9); - - for (String key : keys) { - long ttl = redisTemplate.getExpire(key).map(duration -> duration.getSeconds()).block(); - long fillTime = (capacity / rate); - assertThat(ttl).isGreaterThanOrEqualTo(fillTime); - } - } - - @Test - public void testTokenFilled() { - long rate = 1; - long capacity = 10; - long now = System.currentTimeMillis(); - long requested = 5; - List keys = getKeys("token_filled"); - List args = getArgs(rate, capacity, now, requested); - redisTemplate.execute(redisScript, keys, args).blockFirst(); - - now = now + 3; - args = getArgs(rate, capacity, now, requested); - List result = redisTemplate.execute(redisScript, keys, args).blockFirst(); - assertThat(result.get(0)).isEqualTo(1); - assertThat(result.get(1)).isEqualTo(3); - - for (String key : keys) { - long ttl = redisTemplate.getExpire(key).map(duration -> duration.getSeconds()).block(); - long fillTime = (capacity / rate); - assertThat(ttl).isGreaterThanOrEqualTo(fillTime); - } - } - - @Test - public void testAfterTillTime() { - long rate = 1; - long capacity = 10; - long now = System.currentTimeMillis(); - long requested = 1; - List keys = getKeys("after_fill_time"); - List args = getArgs(rate, capacity, now, requested); - redisTemplate.execute(redisScript, keys, args).blockFirst(); - - long fillTime = capacity / rate; - now = now + fillTime; - args = getArgs(rate, capacity, now, requested); - List result = redisTemplate.execute(redisScript, keys, args).blockFirst(); - assertThat(result.get(0)).isEqualTo(1); - assertThat(result.get(1)).isEqualTo(9); - } - - @Test - public void testTokensNotEnough() { - long rate = 1; - long capacity = 10; - long now = System.currentTimeMillis(); - long requested = 20; - List keys = getKeys("tokens_not_enough"); - List args = getArgs(rate, capacity, now, requested); - List result = redisTemplate.execute(redisScript, keys, args).blockFirst(); - assertThat(result.get(0)).isEqualTo(0); - assertThat(result.get(1)).isEqualTo(10); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - public static class TestConfig { - - } - -} 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 deleted file mode 100644 index 6c7abaad..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/ratelimit/RedisRateLimiterTests.java +++ /dev/null @@ -1,203 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.ratelimit; - -import java.util.UUID; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.Test; -import org.junitpioneer.jupiter.RetryingTest; -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.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.gateway.filter.ratelimit.RateLimiter.Response; -import org.springframework.cloud.gateway.test.BaseWebClientTests; -import org.springframework.context.annotation.Import; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.DynamicPropertyRegistry; -import org.springframework.test.context.DynamicPropertySource; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.nullValue; -import static org.junit.Assume.assumeThat; -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; - -/** - * see - * https://gist.github.com/ptarjan/e38f45f2dfe601419ca3af937fff574d#file-1-check_request_rate_limiter-rb-L36-L62 - * - * @author Spencer Gibb - * @author Ronny Bräunlich - * @author Denis Cutic - */ -@SpringBootTest(webEnvironment = RANDOM_PORT) -@DirtiesContext -@Testcontainers -@Tag("DockerRequired") -public class RedisRateLimiterTests extends BaseWebClientTests { - - @Container - public static GenericContainer redis = new GenericContainer<>("redis:5.0.14-alpine").withExposedPorts(6379); - - @Autowired - private RedisRateLimiter rateLimiter; - - @DynamicPropertySource - static void containerProperties(DynamicPropertyRegistry registry) { - registry.add("spring.data.redis.host", redis::getHost); - registry.add("spring.data.redis.port", redis::getFirstMappedPort); - } - - @BeforeEach - public void setUp() { - assumeThat("Ignore on Circle", System.getenv("CIRCLECI"), is(nullValue())); - } - - @AfterEach - public void tearDown() { - rateLimiter.setIncludeHeaders(true); - } - - @RetryingTest(3) - public void redisRateLimiterWorks() throws Exception { - String id = UUID.randomUUID().toString(); - - int replenishRate = 10; - int burstCapacity = 2 * replenishRate; - int requestedTokens = 1; - - String routeId = "myroute"; - rateLimiter.getConfig().put(routeId, new RedisRateLimiter.Config().setBurstCapacity(burstCapacity) - .setReplenishRate(replenishRate).setRequestedTokens(requestedTokens)); - - checkLimitEnforced(id, replenishRate, burstCapacity, requestedTokens, routeId); - } - - @Test - public void redisRateLimiterWorksForLowRates() throws Exception { - String id = UUID.randomUUID().toString(); - - int replenishRate = 1; - int burstCapacity = 3; - int requestedTokens = 3; - - String routeId = "low_rate_route"; - rateLimiter.getConfig().put(routeId, new RedisRateLimiter.Config().setBurstCapacity(burstCapacity) - .setReplenishRate(replenishRate).setRequestedTokens(requestedTokens)); - - checkLimitEnforced(id, replenishRate, burstCapacity, requestedTokens, routeId); - } - - @Test - public void redisRateLimiterWorksForZeroBurstCapacity() throws Exception { - String id = UUID.randomUUID().toString(); - - int replenishRate = 1; - int burstCapacity = 0; - int requestedTokens = 1; - - String routeId = "zero_burst_capacity_route"; - rateLimiter.getConfig().put(routeId, new RedisRateLimiter.Config().setBurstCapacity(burstCapacity) - .setReplenishRate(replenishRate).setRequestedTokens(requestedTokens)); - - Response response = rateLimiter.isAllowed(routeId, id).block(); - assertThat(response.isAllowed()).isFalse(); - } - - @Test - public void keysUseRedisKeyHashTags() { - assertThat(RedisRateLimiter.getKeys("1")).containsExactly("request_rate_limiter.{1}.tokens", - "request_rate_limiter.{1}.timestamp"); - } - - @Test - public void redisRateLimiterDoesNotSendHeadersIfDeactivated() throws Exception { - String id = UUID.randomUUID().toString(); - String routeId = "myroute"; - - rateLimiter.setIncludeHeaders(false); - - Response response = rateLimiter.isAllowed(routeId, id).block(); - assertThat(response.isAllowed()).isTrue(); - assertThat(response.getHeaders()).doesNotContainKey(RedisRateLimiter.REMAINING_HEADER); - assertThat(response.getHeaders()).doesNotContainKey(RedisRateLimiter.REPLENISH_RATE_HEADER); - assertThat(response.getHeaders()).doesNotContainKey(RedisRateLimiter.BURST_CAPACITY_HEADER); - assertThat(response.getHeaders()).doesNotContainKey(RedisRateLimiter.REQUESTED_TOKENS_HEADER); - } - - private void checkLimitEnforced(String id, int replenishRate, int burstCapacity, int requestedTokens, - String routeId) throws InterruptedException { - // Bursts work - simulateBurst(id, replenishRate, burstCapacity, requestedTokens, routeId); - - checkLimitReached(id, burstCapacity, routeId); - - Thread.sleep(Math.max(1, requestedTokens / replenishRate) * 1000); - - // # After the burst is done, check the steady state - checkSteadyState(id, replenishRate, routeId); - } - - private void simulateBurst(String id, int replenishRate, int burstCapacity, int requestedTokens, String routeId) { - for (int i = 0; i < burstCapacity / requestedTokens; i++) { - Response response = rateLimiter.isAllowed(routeId, id).block(); - assertThat(response.isAllowed()).as("Burst # %s is allowed", i).isTrue(); - assertThat(response.getHeaders()).containsKey(RedisRateLimiter.REMAINING_HEADER); - assertThat(response.getHeaders()).containsEntry(RedisRateLimiter.REPLENISH_RATE_HEADER, - String.valueOf(replenishRate)); - assertThat(response.getHeaders()).containsEntry(RedisRateLimiter.BURST_CAPACITY_HEADER, - String.valueOf(burstCapacity)); - assertThat(response.getHeaders()).containsEntry(RedisRateLimiter.REQUESTED_TOKENS_HEADER, - String.valueOf(requestedTokens)); - } - } - - private void checkLimitReached(String id, int burstCapacity, String routeId) { - Response response = rateLimiter.isAllowed(routeId, id).block(); - if (response.isAllowed()) { // TODO: sometimes there is an off by one error - response = rateLimiter.isAllowed(routeId, id).block(); - } - assertThat(response.isAllowed()).as("Burst # %s is not allowed", burstCapacity).isFalse(); - } - - private void checkSteadyState(String id, int replenishRate, String routeId) { - Response response; - for (int i = 0; i < replenishRate; i++) { - response = rateLimiter.isAllowed(routeId, id).block(); - assertThat(response.isAllowed()).as("steady state # %s is allowed", i).isTrue(); - } - - response = rateLimiter.isAllowed(routeId, id).block(); - assertThat(response.isAllowed()).as("steady state # %s is allowed", replenishRate).isFalse(); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(BaseWebClientTests.DefaultTestConfig.class) - public static class TestConfig { - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/ratelimit/RedisRateLimiterUnitTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/ratelimit/RedisRateLimiterUnitTests.java deleted file mode 100644 index ed5c52d6..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/ratelimit/RedisRateLimiterUnitTests.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright 2013-2020 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.filter.ratelimit; - -import io.lettuce.core.RedisException; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.junit.jupiter.MockitoExtension; -import reactor.core.publisher.Mono; - -import org.springframework.cloud.gateway.support.ConfigurationService; -import org.springframework.context.ApplicationContext; -import org.springframework.data.redis.core.ReactiveStringRedisTemplate; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.data.MapEntry.entry; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyList; -import static org.mockito.Mockito.when; - -/** - * @author Denis Cutic - */ -@ExtendWith(MockitoExtension.class) -public class RedisRateLimiterUnitTests { - - private static final int DEFAULT_REPLENISH_RATE = 1; - - private static final int DEFAULT_BURST_CAPACITY = 1; - - public static final String ROUTE_ID = "routeId"; - - public static final String REQUEST_ID = "id"; - - public static final String[] CONFIGURATION_SERVICE_BEANS = new String[0]; - - public static final RedisException REDIS_EXCEPTION = new RedisException("Mocked problem"); - - @Mock - private ApplicationContext applicationContext; - - @Mock - private ReactiveStringRedisTemplate redisTemplate; - - private RedisRateLimiter redisRateLimiter; - - @BeforeEach - public void setUp() { - when(applicationContext.getBean(ReactiveStringRedisTemplate.class)).thenReturn(redisTemplate); - when(applicationContext.getBeanNamesForType(ConfigurationService.class)) - .thenReturn(CONFIGURATION_SERVICE_BEANS); - redisRateLimiter = new RedisRateLimiter(DEFAULT_REPLENISH_RATE, DEFAULT_BURST_CAPACITY); - } - - @AfterEach - public void tearDown() { - Mockito.reset(applicationContext); - } - - @Test - public void shouldThrowWhenNotInitialized() { - Assertions.assertThrows(IllegalStateException.class, () -> redisRateLimiter.isAllowed(ROUTE_ID, REQUEST_ID)); - } - - @Test - public void shouldAllowRequestWhenRedisIssueOccurs() { - when(redisTemplate.execute(any(), anyList(), anyList())).thenThrow(REDIS_EXCEPTION); - redisRateLimiter.setApplicationContext(applicationContext); - Mono response = redisRateLimiter.isAllowed(ROUTE_ID, REQUEST_ID); - assertThat(response.block()).extracting(RateLimiter.Response::isAllowed).isEqualTo(true); - } - - @Test - public void shouldReturnHeadersWhenRedisIssueOccurs() { - when(redisTemplate.execute(any(), anyList(), anyList())).thenThrow(REDIS_EXCEPTION); - redisRateLimiter.setApplicationContext(applicationContext); - Mono response = redisRateLimiter.isAllowed(ROUTE_ID, REQUEST_ID); - assertThat(response.block().getHeaders()).containsOnly(entry(redisRateLimiter.getRemainingHeader(), "-1"), - entry(redisRateLimiter.getBurstCapacityHeader(), DEFAULT_BURST_CAPACITY + ""), - entry(redisRateLimiter.getReplenishRateHeader(), DEFAULT_REPLENISH_RATE + ""), - entry(redisRateLimiter.getRequestedTokensHeader(), "1")); - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/AsyncPredicateTest.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/AsyncPredicateTest.java deleted file mode 100644 index 876e5fed..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/AsyncPredicateTest.java +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright 2013-2020 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.handler; - -import java.util.function.Predicate; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.reactivestreams.Publisher; -import reactor.core.publisher.Mono; -import reactor.test.StepVerifier; - -public class AsyncPredicateTest { - - @Test - public void andPredicateShouldNotTestRightOperatorIfLeftOperatorIsFalse() { - TestAsyncPredicate left = new TestAsyncPredicate<>(o -> false); - TestAsyncPredicate right = new TestAsyncPredicate<>(o -> true); - Publisher andTest = left.and(right).apply(new Object()); - - StepVerifier.create(andTest).expectNext(false).expectComplete().verify(); - - left.assertTested(); - right.assertUntested(); - } - - @Test - public void andPredicateShouldTestRightOperatorIfLeftOperatorIsTrue() { - TestAsyncPredicate left = new TestAsyncPredicate<>(o -> true); - TestAsyncPredicate right = new TestAsyncPredicate<>(o -> false); - Publisher andTest = left.and(right).apply(new Object()); - - StepVerifier.create(andTest).expectNext(false).expectComplete().verify(); - - left.assertTested(); - right.assertTested(); - } - - @Test - public void orPredicateShouldNotTestRightOperatorIfLeftOperatorIsTrue() { - TestAsyncPredicate left = new TestAsyncPredicate<>(o -> true); - TestAsyncPredicate right = new TestAsyncPredicate<>(o -> false); - Publisher orTest = left.or(right).apply(new Object()); - - StepVerifier.create(orTest).expectNext(true).expectComplete().verify(); - - left.assertTested(); - right.assertUntested(); - } - - @Test - public void orPredicateShouldTestRightOperatorIfLeftOperatorIsFalse() { - TestAsyncPredicate left = new TestAsyncPredicate<>(o -> false); - TestAsyncPredicate right = new TestAsyncPredicate<>(o -> true); - Publisher orTest = left.or(right).apply(new Object()); - - StepVerifier.create(orTest).expectNext(true).expectComplete().verify(); - - left.assertTested(); - right.assertTested(); - } - - @Test - public void negateOperatorWorks() { - TestAsyncPredicate falsePredicate = new TestAsyncPredicate<>(o -> false); - TestAsyncPredicate truePredicate = new TestAsyncPredicate<>(o -> true); - Publisher falseNot = falsePredicate.negate().apply(new Object()); - Publisher trueNot = truePredicate.negate().apply(new Object()); - - StepVerifier.create(falseNot).expectNext(true).expectComplete().verify(); - StepVerifier.create(trueNot).expectNext(false).expectComplete().verify(); - - falsePredicate.assertTested(); - truePredicate.assertTested(); - } - - /** - * An AsyncPredicate decorator that records if the apply method was called. - */ - private final static class TestAsyncPredicate implements AsyncPredicate { - - private final Predicate delegate; - - private boolean tested = false; - - private TestAsyncPredicate(Predicate predicate) { - this.delegate = predicate; - } - - @Override - public Publisher apply(T t) { - tested = true; - return Mono.just(delegate.test(t)); - } - - @DisplayName("predicate must have been tested") - public void assertTested() { - Assertions.assertTrue(tested); - } - - @DisplayName("predicate must not have been tested") - public void assertUntested() { - Assertions.assertFalse(tested); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/RoutePredicateHandlerMappingEmptyPredicatesTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/RoutePredicateHandlerMappingEmptyPredicatesTests.java deleted file mode 100644 index b3337410..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/RoutePredicateHandlerMappingEmptyPredicatesTests.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2013-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.handler; - -import java.net.URI; - -import org.junit.jupiter.api.Test; -import reactor.core.publisher.Flux; - -import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.gateway.route.RouteDefinition; -import org.springframework.cloud.gateway.route.RouteDefinitionLocator; -import org.springframework.cloud.gateway.test.BaseWebClientTests; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; -import org.springframework.test.annotation.DirtiesContext; - -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; - -@SpringBootTest(webEnvironment = RANDOM_PORT, properties = {}) -@DirtiesContext -public class RoutePredicateHandlerMappingEmptyPredicatesTests extends BaseWebClientTests { - - @Test - public void requestsToManagementPortReturn404() { - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(DefaultTestConfig.class) - public static class TestConfig { - - @Bean - RouteDefinitionLocator myRouteDefinitionLocator() { - RouteDefinition definition = new RouteDefinition(); - definition.setId("empty_route"); - definition.setUri(URI.create("https://example")); - return () -> Flux.just(definition); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/RoutePredicateHandlerMappingIntegrationTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/RoutePredicateHandlerMappingIntegrationTests.java deleted file mode 100644 index 67179d1a..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/RoutePredicateHandlerMappingIntegrationTests.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright 2013-2020 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.handler; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.gateway.route.RouteLocator; -import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; -import org.springframework.cloud.gateway.test.BaseWebClientTests; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.util.TestSocketUtils; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; - -@SpringBootTest(webEnvironment = RANDOM_PORT, properties = "management.server.port=${test.port}") -@DirtiesContext -public class RoutePredicateHandlerMappingIntegrationTests extends BaseWebClientTests { - - private static int managementPort; - - @BeforeAll - public static void beforeClass() { - managementPort = TestSocketUtils.findAvailableTcpPort(); - System.setProperty("test.port", String.valueOf(managementPort)); - } - - @AfterAll - public static void afterClass() { - System.clearProperty("test.port"); - } - - @Test - public void requestsToManagementPortReturn404() { - testClient.mutate().baseUrl("http://localhost:" + managementPort).build().get().uri("/get").exchange() - .expectStatus().isNotFound(); - } - - @Test - public void requestsToManagementPortAndHostHeaderReturn404() { - String host = "example.com:8888"; - testClient.mutate().baseUrl("http://localhost:" + managementPort).build().get().uri("/get").header("host", host) - .exchange().expectStatus().isNotFound(); - } - - @Test - public void andNotWorksWithMissingParameter() { - testClient.get().uri("/andnotquery").exchange().expectBody(String.class).isEqualTo("notsupplied"); - } - - @Test - public void andNotWorksWithParameter() { - testClient.get().uri("/andnotquery?myquery=shouldnotsee").exchange().expectBody(String.class) - .isEqualTo("hasquery"); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(DefaultTestConfig.class) - @RestController - public static class TestConfig { - - @Value("${test.uri:http://httpbin.org:80}") - String uri; - - @GetMapping("/httpbin/andnotquery") - String andnotquery(@RequestParam(name = "myquery", defaultValue = "notsupplied") String myquery) { - return myquery; - } - - @GetMapping("/httpbin/hasquery") - String hasquery() { - return "hasquery"; - } - - @Bean - RouteLocator testRouteLocator(RouteLocatorBuilder builder) { - return builder.routes() - .route("and_not_missing_myquery", - r -> r.path("/andnotquery").and().not(p -> p.query("myquery")) - .filters(f -> f.prefixPath("/httpbin")).uri(uri)) - .route("and_not_has_myquery", r -> r.path("/andnotquery").and().query("myquery") - .filters(f -> f.setPath("/httpbin/hasquery")).uri(uri)) - .build(); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/RoutePredicateHandlerMappingSameManagementPortIntegrationTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/RoutePredicateHandlerMappingSameManagementPortIntegrationTests.java deleted file mode 100644 index b6dfbb62..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/RoutePredicateHandlerMappingSameManagementPortIntegrationTests.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2013-2020 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.handler; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -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.cloud.gateway.test.BaseWebClientTests; -import org.springframework.context.annotation.Import; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.util.TestSocketUtils; - -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.DEFINED_PORT; - -@SpringBootTest(webEnvironment = DEFINED_PORT) -@DirtiesContext -public class RoutePredicateHandlerMappingSameManagementPortIntegrationTests extends BaseWebClientTests { - - private static int samePort; - - @BeforeAll - public static void beforeClass() { - samePort = TestSocketUtils.findAvailableTcpPort(); - System.setProperty("server.port", String.valueOf(samePort)); - System.setProperty("management.server.port", String.valueOf(samePort)); - } - - @AfterAll - public static void afterClass() { - System.clearProperty("server.port"); - System.clearProperty("management.server.port"); - } - - @Test - public void requestsToGatewaySucceed() { - testClient.mutate().baseUrl("http://localhost:" + samePort).build().get().uri("/get").exchange().expectStatus() - .isOk(); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(DefaultTestConfig.class) - public static class TestConfig { - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/RoutePredicateHandlerMappingTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/RoutePredicateHandlerMappingTests.java deleted file mode 100644 index 151e3c26..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/RoutePredicateHandlerMappingTests.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 2013-2020 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.handler; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mockito; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import reactor.test.StepVerifier; - -import org.springframework.boot.test.system.CapturedOutput; -import org.springframework.boot.test.system.OutputCaptureExtension; -import org.springframework.cloud.gateway.config.GlobalCorsProperties; -import org.springframework.cloud.gateway.route.Route; -import org.springframework.cloud.gateway.route.RouteLocator; -import org.springframework.mock.env.MockEnvironment; -import org.springframework.web.server.ServerWebExchange; - -/** - * @author Simon Baslé - */ -@ExtendWith(OutputCaptureExtension.class) -public class RoutePredicateHandlerMappingTests { - - @Test - public void lookupRouteFromSyncPredicates(CapturedOutput capturedOutput) { - Route routeFalse = Route.async().id("routeFalse").uri("http://localhost").predicate(swe -> false).build(); - Route routeFail = Route.async().id("routeFail").uri("http://localhost").predicate(swe -> { - throw new IllegalStateException("boom"); - }).build(); - Route routeTrue = Route.async().id("routeTrue").uri("http://localhost").predicate(swe -> true).build(); - RouteLocator routeLocator = new RouteLocator() { - @Override - public Flux getRoutes() { - return Flux.just(routeFalse, routeFail, routeTrue).hide(); - } - }; - RoutePredicateHandlerMapping mapping = new RoutePredicateHandlerMapping(null, routeLocator, - new GlobalCorsProperties(), new MockEnvironment()); - - final Mono routeMono = mapping.lookupRoute(Mockito.mock(ServerWebExchange.class)); - - StepVerifier.create(routeMono.map(Route::getId)).expectNext("routeTrue").verifyComplete(); - Assertions.assertTrue(capturedOutput.getOut().contains("Error applying predicate for route: routeFail")); - Assertions.assertTrue(capturedOutput.getOut().contains("java.lang.IllegalStateException: boom")); - } - - @Test - public void lookupRouteFromAsyncPredicates(CapturedOutput capturedOutput) { - Route routeFalse = Route.async().id("routeFalse").uri("http://localhost") - .asyncPredicate(swe -> Mono.just(false)).build(); - Route routeError = Route.async().id("routeError").uri("http://localhost") - .asyncPredicate(swe -> Mono.error(new IllegalStateException("boom1"))).build(); - Route routeFail = Route.async().id("routeFail").uri("http://localhost").asyncPredicate(swe -> { - throw new IllegalStateException("boom2"); - }).build(); - Route routeTrue = Route.async().id("routeTrue").uri("http://localhost").asyncPredicate(swe -> Mono.just(true)) - .build(); - RouteLocator routeLocator = () -> Flux.just(routeFalse, routeError, routeFail, routeTrue).hide(); - RoutePredicateHandlerMapping mapping = new RoutePredicateHandlerMapping(null, routeLocator, - new GlobalCorsProperties(), new MockEnvironment()); - - final Mono routeMono = mapping.lookupRoute(Mockito.mock(ServerWebExchange.class)); - - StepVerifier.create(routeMono.map(Route::getId)).expectNext("routeTrue").verifyComplete(); - - Assertions.assertTrue(capturedOutput.getOut().contains("Error applying predicate for route: routeError")); - Assertions.assertTrue(capturedOutput.getOut().contains("java.lang.IllegalStateException: boom1")); - - Assertions.assertTrue(capturedOutput.getOut().contains("Error applying predicate for route: routeFail")); - Assertions.assertTrue(capturedOutput.getOut().contains("java.lang.IllegalStateException: boom2")); - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/RoutePredicateHandlerMappingWithAutoconfiguredClientIntegrationTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/RoutePredicateHandlerMappingWithAutoconfiguredClientIntegrationTests.java deleted file mode 100644 index c84840c1..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/RoutePredicateHandlerMappingWithAutoconfiguredClientIntegrationTests.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2013-2020 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.handler; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.gateway.route.RouteLocator; -import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; -import org.springframework.cloud.gateway.test.BaseWebClientTests; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; -import org.springframework.test.util.TestSocketUtils; -import org.springframework.test.web.reactive.server.WebTestClient; -import org.springframework.web.bind.annotation.RestController; - -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; - -@SpringBootTest(webEnvironment = RANDOM_PORT) -@AutoConfigureWebTestClient -public class RoutePredicateHandlerMappingWithAutoconfiguredClientIntegrationTests { - - @Autowired - WebTestClient webTestClient; - - @BeforeAll - static void beforeClass() { - int managementPort = TestSocketUtils.findAvailableTcpPort(); - System.setProperty("management.server.port", String.valueOf(managementPort)); - } - - @AfterAll - static void afterClass() { - System.clearProperty("management.server.port"); - } - - @Test - void shouldReturnOk() { - this.webTestClient.get().uri("/get").exchange().expectStatus().isOk(); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(BaseWebClientTests.DefaultTestConfig.class) - @RestController - public static class TestConfig { - - @Value("${test.uri:http://httpbin.org:80}") - String uri; - - @Bean - RouteLocator testRoutes(RouteLocatorBuilder builder) { - return builder.routes().route(r -> r.path("/get").filters(f -> f.prefixPath("/httpbin")).uri(uri)).build(); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/AfterRoutePredicateFactoryTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/AfterRoutePredicateFactoryTests.java deleted file mode 100644 index efde1586..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/AfterRoutePredicateFactoryTests.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright 2013-2020 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.handler.predicate; - -import java.time.ZonedDateTime; -import java.util.HashMap; -import java.util.function.Predicate; - -import org.junit.jupiter.api.Test; - -import org.springframework.cloud.gateway.handler.predicate.AfterRoutePredicateFactory.Config; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.cloud.gateway.handler.predicate.AfterRoutePredicateFactory.DATETIME_KEY; -import static org.springframework.cloud.gateway.handler.predicate.BetweenRoutePredicateFactoryTests.bindConfig; -import static org.springframework.cloud.gateway.handler.predicate.BetweenRoutePredicateFactoryTests.getExchange; -import static org.springframework.cloud.gateway.handler.predicate.BetweenRoutePredicateFactoryTests.minusHours; -import static org.springframework.cloud.gateway.handler.predicate.BetweenRoutePredicateFactoryTests.minusHoursMillis; -import static org.springframework.cloud.gateway.handler.predicate.BetweenRoutePredicateFactoryTests.plusHours; -import static org.springframework.cloud.gateway.handler.predicate.BetweenRoutePredicateFactoryTests.plusHoursMillis; - -/** - * @author Spencer Gibb - */ -public class AfterRoutePredicateFactoryTests { - - @Test - public void beforeStringWorks() { - String dateString = minusHours(1); - - final boolean result = runPredicate(dateString); - - assertThat(result).isTrue(); - } - - @Test - public void afterStringWorks() { - String dateString = plusHours(1); - - final boolean result = runPredicate(dateString); - - assertThat(result).isFalse(); - } - - @Test - public void beforeEpochWorks() { - String dateString = minusHoursMillis(1); - - final boolean result = runPredicate(dateString); - - assertThat(result).isTrue(); - } - - @Test - public void afterEpochWorks() { - String dateString = plusHoursMillis(1); - - final boolean result = runPredicate(dateString); - - assertThat(result).isFalse(); - } - - @Test - public void testPredicates() { - boolean result = new AfterRoutePredicateFactory().apply(c -> c.setDatetime(ZonedDateTime.now().minusHours(2))) - .test(getExchange()); - assertThat(result).isTrue(); - } - - private boolean runPredicate(String dateString) { - HashMap map = new HashMap<>(); - map.put(DATETIME_KEY, dateString); - AfterRoutePredicateFactory factory = new AfterRoutePredicateFactory(); - - Config config = bindConfig(map, factory); - - return factory.apply(config).test(getExchange()); - } - - @Test - public void toStringFormat() { - Config config = new Config(); - config.setDatetime(ZonedDateTime.now()); - Predicate predicate = new AfterRoutePredicateFactory().apply(config); - assertThat(predicate.toString()).contains("After: " + config.getDatetime()); - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/BeforeRoutePredicateFactoryTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/BeforeRoutePredicateFactoryTests.java deleted file mode 100644 index 7f981da9..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/BeforeRoutePredicateFactoryTests.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright 2013-2020 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.handler.predicate; - -import java.time.ZonedDateTime; -import java.util.HashMap; -import java.util.function.Predicate; - -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.cloud.gateway.handler.predicate.BeforeRoutePredicateFactory.DATETIME_KEY; -import static org.springframework.cloud.gateway.handler.predicate.BetweenRoutePredicateFactoryTests.bindConfig; -import static org.springframework.cloud.gateway.handler.predicate.BetweenRoutePredicateFactoryTests.getExchange; -import static org.springframework.cloud.gateway.handler.predicate.BetweenRoutePredicateFactoryTests.minusHours; -import static org.springframework.cloud.gateway.handler.predicate.BetweenRoutePredicateFactoryTests.minusHoursMillis; -import static org.springframework.cloud.gateway.handler.predicate.BetweenRoutePredicateFactoryTests.plusHours; -import static org.springframework.cloud.gateway.handler.predicate.BetweenRoutePredicateFactoryTests.plusHoursMillis; - -/** - * @author Spencer Gibb - */ -public class BeforeRoutePredicateFactoryTests { - - @Test - public void beforeStringWorks() { - String dateString = minusHours(1); - - boolean result = runPredicate(dateString); - - assertThat(result).isFalse(); - } - - @Test - public void afterStringWorks() { - String dateString = plusHours(1); - - boolean result = runPredicate(dateString); - - assertThat(result).isTrue(); - } - - @Test - public void beforeEpochWorks() { - String dateString = minusHoursMillis(1); - - final boolean result = runPredicate(dateString); - - assertThat(result).isFalse(); - } - - @Test - public void afterEpochWorks() { - String dateString = plusHoursMillis(1); - - final boolean result = runPredicate(dateString); - - assertThat(result).isTrue(); - } - - @Test - public void testPredicates() { - boolean result = new BeforeRoutePredicateFactory().apply(c -> c.setDatetime(ZonedDateTime.now().minusHours(2))) - .test(getExchange()); - assertThat(result).isFalse(); - } - - private boolean runPredicate(String dateString) { - HashMap map = new HashMap<>(); - map.put(DATETIME_KEY, dateString); - - BeforeRoutePredicateFactory factory = new BeforeRoutePredicateFactory(); - - BeforeRoutePredicateFactory.Config config = bindConfig(map, factory); - - return factory.apply(config).test(getExchange()); - } - - @Test - public void toStringFormat() { - BeforeRoutePredicateFactory.Config config = new BeforeRoutePredicateFactory.Config(); - config.setDatetime(ZonedDateTime.now()); - Predicate predicate = new BeforeRoutePredicateFactory().apply(config); - assertThat(predicate.toString()).contains("Before: " + config.getDatetime()); - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/BetweenRoutePredicateFactoryIntegrationTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/BetweenRoutePredicateFactoryIntegrationTests.java deleted file mode 100644 index f312f65d..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/BetweenRoutePredicateFactoryIntegrationTests.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2013-2020 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.handler.predicate; - -import java.time.ZonedDateTime; - -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping; -import org.springframework.cloud.gateway.route.RouteLocator; -import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; -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.test.annotation.DirtiesContext; - -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; - -@SpringBootTest(webEnvironment = RANDOM_PORT) -@DirtiesContext -public class BetweenRoutePredicateFactoryIntegrationTests extends BaseWebClientTests { - - @Test - public void betweenPredicateWithValidDates() { - testClient.get().uri("/get").header(HttpHeaders.HOST, "www.betweenvalid.org").exchange().expectStatus().isOk() - .expectHeader().valueEquals(HANDLER_MAPPER_HEADER, RoutePredicateHandlerMapping.class.getSimpleName()) - .expectHeader().valueEquals(ROUTE_ID_HEADER, "test_between_valid"); - } - - @Test - public void notBetweenPredicateWorks() { - testClient.get().uri("/get").header(HttpHeaders.HOST, "www.notbetween.org").exchange().expectStatus().isOk() - .expectHeader().valueEquals(HANDLER_MAPPER_HEADER, RoutePredicateHandlerMapping.class.getSimpleName()) - // should NOT be not_between_test because Between dates are in the past - .expectHeader().valueEquals(ROUTE_ID_HEADER, "default_path_to_httpbin"); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(DefaultTestConfig.class) - public static class TestConfig { - - @Value("${test.uri}") - String uri; - - @Bean - public RouteLocator testRouteLocator(RouteLocatorBuilder builder) { - return builder.routes() - .route("test_between_valid", - r -> r.host("**.betweenvalid.org").and() - .between(ZonedDateTime.now().minusDays(1), ZonedDateTime.now().plusDays(1)) - .filters(f -> f.prefixPath("/httpbin")).uri(uri)) - .build(); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/BetweenRoutePredicateFactoryTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/BetweenRoutePredicateFactoryTests.java deleted file mode 100644 index b18c8e64..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/BetweenRoutePredicateFactoryTests.java +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright 2013-2020 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.handler.predicate; - -import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; -import java.util.HashMap; -import java.util.function.Predicate; - -import org.junit.jupiter.api.Test; - -import org.springframework.boot.convert.ApplicationConversionService; -import org.springframework.cloud.gateway.support.ConfigurationService; -import org.springframework.cloud.gateway.support.StringToZonedDateTimeConverter; -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.springframework.cloud.gateway.handler.predicate.BetweenRoutePredicateFactory.DATETIME1_KEY; -import static org.springframework.cloud.gateway.handler.predicate.BetweenRoutePredicateFactory.DATETIME2_KEY; - -/** - * @author Spencer Gibb - */ -public class BetweenRoutePredicateFactoryTests { - - static T bindConfig(HashMap properties, AbstractRoutePredicateFactory factory) { - ApplicationConversionService conversionService = new ApplicationConversionService(); - conversionService.addConverter(new StringToZonedDateTimeConverter()); - // @formatter:off - T config = new ConfigurationService(null, () -> conversionService, () -> null) - .with(factory) - .name("myname") - .normalizedProperties(properties) - .bind(); - // @formatter:on - return config; - } - - static String minusHoursMillis(int hours) { - final int millis = hours * 1000 * 60 * 60; - return String.valueOf(System.currentTimeMillis() - millis); - } - - static String plusHoursMillis(int hours) { - final int millis = hours * 1000 * 60 * 60; - return String.valueOf(System.currentTimeMillis() + millis); - } - - static String minusHours(int hours) { - return ZonedDateTime.now().minusHours(hours).format(DateTimeFormatter.ISO_ZONED_DATE_TIME); - } - - static String plusHours(int hours) { - return ZonedDateTime.now().plusHours(hours).format(DateTimeFormatter.ISO_ZONED_DATE_TIME); - } - - static ServerWebExchange getExchange() { - MockServerHttpRequest request = MockServerHttpRequest.get("https://example.com").build(); - return MockServerWebExchange.from(request); - } - - @Test - public void beforeStringWorks() { - String dateString1 = plusHours(1); - String dateString2 = plusHours(2); - - final boolean result = runPredicate(dateString1, dateString2); - - assertThat(result).as("Now is not before %s", dateString1).isFalse(); - } - - @Test - public void betweenStringWorks() { - String dateString1 = minusHours(1); - String dateString2 = plusHours(1); - - ZonedDateTime parse = ZonedDateTime.parse(dateString1); - - final boolean result = runPredicate(dateString1, dateString2); - - assertThat(result).as("Now is not between %s and %s", dateString1, dateString2).isTrue(); - } - - @Test - public void afterStringWorks() { - String dateString1 = minusHours(2); - String dateString2 = minusHours(1); - - final boolean result = runPredicate(dateString1, dateString2); - - assertThat(result).as("Now is not after %s", dateString2).isFalse(); - } - - @Test - public void beforeEpochWorks() { - String dateString1 = plusHoursMillis(1); - String dateString2 = plusHoursMillis(2); - - final boolean result = runPredicate(dateString1, dateString2); - - assertThat(result).as("Now is not before %s", dateString1).isFalse(); - } - - @Test - public void betweenEpochWorks() { - String dateString1 = minusHoursMillis(1); - String dateString2 = plusHoursMillis(1); - - final boolean result = runPredicate(dateString1, dateString2); - - assertThat(result).as("Now is not between %s and %s", dateString1, dateString2).isTrue(); - } - - @Test - public void afterEpochWorks() { - String dateString1 = minusHoursMillis(2); - String dateString2 = minusHoursMillis(1); - - final boolean result = runPredicate(dateString1, dateString2); - - assertThat(result).as("Now is not after %s", dateString1).isFalse(); - } - - @Test - public void testPredicates() { - boolean result = new BetweenRoutePredicateFactory().apply( - c -> c.setDatetime1(ZonedDateTime.now().minusHours(2)).setDatetime2(ZonedDateTime.now().plusHours(1))) - .test(getExchange()); - assertThat(result).isTrue(); - } - - boolean runPredicate(String dateString1, String dateString2) { - HashMap map = new HashMap<>(); - map.put(DATETIME1_KEY, dateString1); - map.put(DATETIME2_KEY, dateString2); - - BetweenRoutePredicateFactory factory = new BetweenRoutePredicateFactory(); - - BetweenRoutePredicateFactory.Config config = bindConfig(map, factory); - - return factory.apply(config).test(getExchange()); - } - - @Test - public void toStringFormat() { - BetweenRoutePredicateFactory.Config config = new BetweenRoutePredicateFactory.Config(); - config.setDatetime1(ZonedDateTime.now()); - config.setDatetime2(ZonedDateTime.now().plusHours(1)); - Predicate predicate = new BetweenRoutePredicateFactory().apply(config); - assertThat(predicate.toString()) - .contains("Between: " + config.getDatetime1() + " and " + config.getDatetime2()); - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/CloudFoundryRouteServiceRoutePredicateFactoryIntegrationTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/CloudFoundryRouteServiceRoutePredicateFactoryIntegrationTests.java deleted file mode 100644 index 803e770d..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/CloudFoundryRouteServiceRoutePredicateFactoryIntegrationTests.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2013-2020 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.handler.predicate; - -import com.fasterxml.jackson.databind.JsonNode; -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.web.server.LocalServerPort; -import org.springframework.cloud.gateway.route.RouteLocator; -import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; -import org.springframework.cloud.gateway.test.BaseWebClientTests; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; -import org.springframework.test.annotation.DirtiesContext; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; - -/** - * @author Toshiaki Maki - */ -@SpringBootTest(webEnvironment = RANDOM_PORT) -@DirtiesContext -public class CloudFoundryRouteServiceRoutePredicateFactoryIntegrationTests extends BaseWebClientTests { - - @LocalServerPort - int port; - - @Test - public void predicateWorkWithProperties() { - testClient.get().uri("/").header("Host", "props.routeservice.example.com") - .header("X-CF-Forwarded-Url", "http://localhost:" + port + "/actuator/health") - .header("X-CF-Proxy-Signature", "foo").header("X-CF-Proxy-Metadata", "bar").exchange() - .expectBody(JsonNode.class).consumeWith(r -> assertThat(r.getResponseBody().has("status")).isTrue()); - } - - @Test - public void predicateWillNotWorkUnlessHeadersAreEnough() { - testClient.get().uri("/").header("Host", "props.routeservice.example.com") - .header("X-CF-Forwarded-Url", "http://localhost:" + port + "/actuator/health") - .header("X-CF-Proxy-Metadata", "bar").exchange().expectStatus().isOk().expectHeader() - .valueEquals(ROUTE_ID_HEADER, "default_path_to_httpbin"); - } - - @Test - public void predicateWorkWithDsl() { - testClient.get().uri("/").header("Host", "dsl.routeservice.example.com") - .header("X-CF-Forwarded-Url", "http://localhost:" + port + "/actuator/health") - .header("X-CF-Proxy-Signature", "foo").header("X-CF-Proxy-Metadata", "bar").exchange() - .expectBody(JsonNode.class).consumeWith(r -> assertThat(r.getResponseBody().has("status")).isTrue()); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(DefaultTestConfig.class) - public static class TestConfig { - - @Bean - public RouteLocator routeLocator(RouteLocatorBuilder builder) { - return builder.routes() - .route(r -> r.cloudFoundryRouteService().and().header("Host", "dsl.routeservice.example.com") - .filters(f -> f.requestHeaderToRequestUri("X-CF-Forwarded-Url")).uri("https://example.com")) - .build(); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/CloudFoundryRouteServiceRoutePredicateFactoryTest.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/CloudFoundryRouteServiceRoutePredicateFactoryTest.java deleted file mode 100644 index 1b217582..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/CloudFoundryRouteServiceRoutePredicateFactoryTest.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2013-2020 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.handler.predicate; - -import java.util.function.Predicate; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -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; - -/** - * @author Andrew Fitzgerald - */ -public class CloudFoundryRouteServiceRoutePredicateFactoryTest { - - private Predicate predicate; - - @BeforeEach - public void setUp() throws Exception { - CloudFoundryRouteServiceRoutePredicateFactory factory = new CloudFoundryRouteServiceRoutePredicateFactory(); - predicate = factory.apply(factory.newConfig()); - } - - @Test - public void itReturnsTrueWithAllHeadersPresent() { - MockServerHttpRequest request = MockServerHttpRequest.get("someurl") - .header(CloudFoundryRouteServiceRoutePredicateFactory.X_CF_FORWARDED_URL, "url") - .header(CloudFoundryRouteServiceRoutePredicateFactory.X_CF_PROXY_METADATA, "metadata") - .header(CloudFoundryRouteServiceRoutePredicateFactory.X_CF_PROXY_SIGNATURE, "signature").build(); - MockServerWebExchange exchange = MockServerWebExchange.from(request); - - assertThat(predicate.test(exchange)).isTrue(); - } - - @Test - public void itReturnsFalseWithAHeadersMissing() { - MockServerHttpRequest request = MockServerHttpRequest.get("someurl") - .header(CloudFoundryRouteServiceRoutePredicateFactory.X_CF_FORWARDED_URL, "url") - .header(CloudFoundryRouteServiceRoutePredicateFactory.X_CF_PROXY_METADATA, "metadata").build(); - MockServerWebExchange exchange = MockServerWebExchange.from(request); - - assertThat(predicate.test(exchange)).isFalse(); - } - - @Test - public void itReturnsFalseWithNoHeaders() { - MockServerHttpRequest request = MockServerHttpRequest.get("someurl").build(); - MockServerWebExchange exchange = MockServerWebExchange.from(request); - - assertThat(predicate.test(exchange)).isFalse(); - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/CookieRoutePredicateFactoryIntegrationTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/CookieRoutePredicateFactoryIntegrationTests.java deleted file mode 100644 index 36ff2fa1..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/CookieRoutePredicateFactoryIntegrationTests.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2013-2020 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.handler.predicate; - -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.cloud.gateway.test.BaseWebClientTests; -import org.springframework.context.annotation.Import; -import org.springframework.http.HttpHeaders; -import org.springframework.web.bind.annotation.CookieValue; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; - -@SpringBootTest(webEnvironment = RANDOM_PORT) -class CookieRoutePredicateFactoryIntegrationTests extends BaseWebClientTests { - - @Test - void cookieRegexWorks() { - assertCookieValue("helloaaaaa"); - - assertCookieValue("hello"); - } - - private void assertCookieValue(String value) { - testClient.get().uri("/cookie").header(HttpHeaders.HOST, "www.cookieregex.org").cookie("mycookie", value) - .exchange().expectStatus().isOk().expectHeader().valueEquals(ROUTE_ID_HEADER, "cookie_regex_test") - .expectBody(String.class).isEqualTo(value); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(DefaultTestConfig.class) - @RestController - static class TestConfig { - - @GetMapping("/httpbin/cookie") - String cookie(@CookieValue("mycookie") String mycookie) { - return mycookie; - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/CookieRoutePredicateFactoryTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/CookieRoutePredicateFactoryTests.java deleted file mode 100644 index 94e80fd7..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/CookieRoutePredicateFactoryTests.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2013-2020 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.handler.predicate; - -import java.util.function.Predicate; - -import org.junit.jupiter.api.Test; - -import org.springframework.cloud.gateway.handler.predicate.CookieRoutePredicateFactory.Config; -import org.springframework.cloud.gateway.test.BaseWebClientTests; -import org.springframework.http.HttpCookie; -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; - -public class CookieRoutePredicateFactoryTests extends BaseWebClientTests { - - @Test - public void noCookiesForYou() { - MockServerHttpRequest request = MockServerHttpRequest.get("https://example.com").build(); - MockServerWebExchange exchange = MockServerWebExchange.from(request); - - Predicate predicate = new CookieRoutePredicateFactory() - .apply(new Config().setName("mycookie").setRegexp("ch.p")); - - assertThat(predicate.test(exchange)).isFalse(); - } - - @Test - public void okOneCookieForYou() { - MockServerHttpRequest request = MockServerHttpRequest.get("https://example.com") - .cookie(new HttpCookie("yourcookie", "sugar"), new HttpCookie("mycookie", "chip")).build(); - MockServerWebExchange exchange = MockServerWebExchange.from(request); - - Predicate predicate = new CookieRoutePredicateFactory() - .apply(new Config().setName("mycookie").setRegexp("ch.p")); - - assertThat(predicate.test(exchange)).isTrue(); - } - - @Test - public void toStringFormat() { - Config config = new Config(); - config.setName("mycookie"); - config.setRegexp("myregexp"); - Predicate predicate = new CookieRoutePredicateFactory().apply(config); - assertThat(predicate.toString()).contains("Cookie: name=mycookie regexp=myregexp"); - } - -} 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 deleted file mode 100644 index 8785657c..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/GatewayPredicateVisitorTests.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2013-2020 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.handler.predicate; - -import java.util.ArrayList; -import java.util.function.Predicate; - -import org.junit.jupiter.api.Test; - -import org.springframework.cloud.gateway.handler.AsyncPredicate; -import org.springframework.cloud.gateway.route.Route; -import org.springframework.web.server.ServerWebExchange; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Spencer Gibb - */ -public class GatewayPredicateVisitorTests { - - @Test - public void asyncPredicateVisitVisitsEachNode() { - PathRoutePredicateFactory pathRoutePredicateFactory = new PathRoutePredicateFactory(); - HostRoutePredicateFactory hostRoutePredicateFactory = new HostRoutePredicateFactory(); - ReadBodyRoutePredicateFactory readBodyRoutePredicateFactory1 = new ReadBodyRoutePredicateFactory(); - ReadBodyRoutePredicateFactory readBodyRoutePredicateFactory2 = new ReadBodyRoutePredicateFactory(); - AsyncPredicate predicate = AsyncPredicate - .from(pathRoutePredicateFactory.apply(pathRoutePredicateFactory.newConfig())) - .and(AsyncPredicate.from(hostRoutePredicateFactory.apply(hostRoutePredicateFactory.newConfig()))) - .and(readBodyRoutePredicateFactory1.applyAsync(readBodyRoutePredicateFactory1.newConfig())) - .and(readBodyRoutePredicateFactory2.applyAsync(readBodyRoutePredicateFactory2.newConfig())); - - Route route = Route.async().id("git").uri("http://myuri").asyncPredicate(predicate).build(); - ArrayList configs = new ArrayList<>(); - route.getPredicate().accept(p -> configs.add(p.getConfig())); - - assertThat(configs).hasSize(4).hasExactlyElementsOfTypes(PathRoutePredicateFactory.Config.class, - HostRoutePredicateFactory.Config.class, ReadBodyRoutePredicateFactory.Config.class, - ReadBodyRoutePredicateFactory.Config.class); - } - - @Test - public void predicateVisitVisitsEachNode() { - PathRoutePredicateFactory pathRoutePredicateFactory = new PathRoutePredicateFactory(); - HostRoutePredicateFactory hostRoutePredicateFactory = new HostRoutePredicateFactory(); - Predicate predicate = pathRoutePredicateFactory.apply(pathRoutePredicateFactory.newConfig()) - .and(hostRoutePredicateFactory.apply(hostRoutePredicateFactory.newConfig())); - - Route route = Route.async().id("git").uri("http://myuri").predicate(predicate).build(); - ArrayList configs = new ArrayList<>(); - route.getPredicate().accept(p -> configs.add(p.getConfig())); - - assertThat(configs).hasSize(2).hasExactlyElementsOfTypes(PathRoutePredicateFactory.Config.class, - HostRoutePredicateFactory.Config.class); - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/HeaderRoutePredicateFactoryTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/HeaderRoutePredicateFactoryTests.java deleted file mode 100644 index 525db877..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/HeaderRoutePredicateFactoryTests.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2013-2020 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.handler.predicate; - -import java.util.function.Predicate; - -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping; -import org.springframework.cloud.gateway.handler.predicate.HeaderRoutePredicateFactory.Config; -import org.springframework.cloud.gateway.route.RouteLocator; -import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; -import org.springframework.cloud.gateway.test.BaseWebClientTests; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; -import org.springframework.test.annotation.DirtiesContext; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; - -@SpringBootTest(webEnvironment = RANDOM_PORT) -@DirtiesContext -public class HeaderRoutePredicateFactoryTests extends BaseWebClientTests { - - @Test - public void headerRouteWorks() { - testClient.get().uri("/get").header("Foo", "bar").exchange().expectStatus().isOk().expectHeader() - .valueEquals(HANDLER_MAPPER_HEADER, RoutePredicateHandlerMapping.class.getSimpleName()).expectHeader() - .valueEquals(ROUTE_ID_HEADER, "header_test"); - } - - @Test - @SuppressWarnings("Duplicates") - public void headerRouteIgnoredWhenHeaderMissing() { - testClient.get().uri("/get") - // no headers set. Test used to throw a null pointer exception. - .exchange().expectStatus().isOk().expectHeader() - .valueEquals(HANDLER_MAPPER_HEADER, RoutePredicateHandlerMapping.class.getSimpleName()).expectHeader() - .valueEquals(ROUTE_ID_HEADER, "default_path_to_httpbin"); - } - - @Test - public void headerExistsWorksWithDsl() { - testClient.get().uri("/get").header("X-Foo", "bar").exchange().expectStatus().isOk().expectHeader() - .valueEquals(HANDLER_MAPPER_HEADER, RoutePredicateHandlerMapping.class.getSimpleName()).expectHeader() - .valueEquals(ROUTE_ID_HEADER, "header_exists_dsl"); - } - - @Test - public void toStringFormat() { - Config config = new Config(); - config.setHeader("myheader"); - config.setRegexp("myregexp"); - Predicate predicate = new HeaderRoutePredicateFactory().apply(config); - assertThat(predicate.toString()).contains("Header: myheader regexp=myregexp"); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(DefaultTestConfig.class) - public static class TestConfig { - - @Value("${test.uri}") - private String uri; - - @Bean - RouteLocator queryRouteLocator(RouteLocatorBuilder builder) { - return builder.routes() - .route("header_exists_dsl", r -> r.header("X-Foo").filters(f -> f.prefixPath("/httpbin")).uri(uri)) - .build(); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/HostRoutePredicateFactoryTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/HostRoutePredicateFactoryTests.java deleted file mode 100644 index 10fb4f85..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/HostRoutePredicateFactoryTests.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright 2013-2020 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.handler.predicate; - -import java.util.Arrays; -import java.util.function.Predicate; - -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping; -import org.springframework.cloud.gateway.handler.predicate.HostRoutePredicateFactory.Config; -import org.springframework.cloud.gateway.route.RouteLocator; -import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; -import org.springframework.cloud.gateway.test.BaseWebClientTests; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; -import org.springframework.test.annotation.DirtiesContext; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; - -@SpringBootTest(webEnvironment = RANDOM_PORT) -@DirtiesContext -public class HostRoutePredicateFactoryTests extends BaseWebClientTests { - - @Test - public void hostRouteWorks() { - expectHostRoute("www.example.org", "host_example_to_httpbin"); - } - - public void expectHostRoute(String host, String routeId) { - testClient.get().uri("/get").header("Host", host).exchange().expectStatus().isOk().expectHeader() - .valueEquals(HANDLER_MAPPER_HEADER, RoutePredicateHandlerMapping.class.getSimpleName()).expectHeader() - .valueEquals(ROUTE_ID_HEADER, routeId); - } - - @Test - public void hostRouteBackwardsCompatiblePatternWorks() { - expectHostRoute("www.hostpatternarg.org", "host_backwards_compatible_test"); - } - - @Test - public void hostRouteBackwardsCompatibleShortcutWorks() { - expectHostRoute("www.hostpatternshortcut.org", "host_backwards_compatible_shortcut_test"); - } - - @Test - public void mulitHostRouteWorks() { - expectHostRoute("www.hostmulti1.org", "host_multi_test"); - expectHostRoute("www.hostmulti2.org", "host_multi_test"); - } - - @Test - public void mulitHostRouteDslWorks() { - expectHostRoute("www.hostmultidsl1.org", "host_multi_dsl"); - expectHostRoute("www.hostmultidsl2.org", "host_multi_dsl"); - } - - @Test - public void toStringFormat() { - Config config = new Config().setPatterns(Arrays.asList("pattern1", "pattern2")); - Predicate predicate = new HostRoutePredicateFactory().apply(config); - assertThat(predicate.toString()).contains("pattern1").contains("pattern2"); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(DefaultTestConfig.class) - public static class TestConfig { - - @Value("${test.uri}") - String uri; - - @Bean - public RouteLocator testRouteLocator(RouteLocatorBuilder builder) { - return builder.routes().route("host_multi_dsl", r -> r.host("**.hostmultidsl1.org", "**.hostmultidsl2.org") - .filters(f -> f.prefixPath("/httpbin")).uri(uri)).build(); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/MethodRoutePredicateFactoryTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/MethodRoutePredicateFactoryTests.java deleted file mode 100644 index 4f195c8a..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/MethodRoutePredicateFactoryTests.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright 2013-2020 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.handler.predicate; - -import java.util.function.Predicate; - -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping; -import org.springframework.cloud.gateway.handler.predicate.MethodRoutePredicateFactory.Config; -import org.springframework.cloud.gateway.route.RouteLocator; -import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; -import org.springframework.cloud.gateway.test.BaseWebClientTests; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.test.annotation.DirtiesContext; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; - -@SpringBootTest(webEnvironment = RANDOM_PORT) -@DirtiesContext -public class MethodRoutePredicateFactoryTests extends BaseWebClientTests { - - @Test - public void methodRouteWorks() { - testClient.get().uri("/get").header("Host", "www.method.org").exchange().expectStatus().isOk().expectHeader() - .valueEquals(HANDLER_MAPPER_HEADER, RoutePredicateHandlerMapping.class.getSimpleName()).expectHeader() - .valueEquals(ROUTE_ID_HEADER, "method_test_get"); - } - - @Test - public void methodGetAndPostRouteWorks() { - testClient.post().uri("/multivalueheaders").header("Host", "www.method.org").exchange().expectStatus().isOk() - .expectHeader().valueEquals(HANDLER_MAPPER_HEADER, RoutePredicateHandlerMapping.class.getSimpleName()) - .expectHeader().valueEquals(ROUTE_ID_HEADER, "method_test_get_and_post"); - - testClient.get().uri("/multivalueheaders").header("Host", "www.method.org").exchange().expectStatus().isOk() - .expectHeader().valueEquals(HANDLER_MAPPER_HEADER, RoutePredicateHandlerMapping.class.getSimpleName()) - .expectHeader().valueEquals(ROUTE_ID_HEADER, "method_test_get_and_post"); - } - - @Test - public void methodRouteNotMatching() { - testClient.delete().uri("/multivalueheaders").header("Host", "www.method.org").exchange().expectStatus() - .value(integer -> integer.equals(HttpStatus.METHOD_NOT_ALLOWED)).expectHeader() - .valueEquals(HANDLER_MAPPER_HEADER, RoutePredicateHandlerMapping.class.getSimpleName()).expectHeader() - /* - * Fallback to route with '/**' path predicate matches, see - * application.yml in test resources - */ - .valueEquals(ROUTE_ID_HEADER, "default_path_to_httpbin"); - } - - @Test - public void toStringFormatSingleMethod() { - Config config = new Config(); - config.setMethods(HttpMethod.GET); - Predicate predicate = new MethodRoutePredicateFactory().apply(config); - assertThat(predicate.toString()).contains("Methods: [GET]"); - } - - @Test - public void toStringFormatMultipleMethods() { - Config config = new Config(); - config.setMethods(HttpMethod.GET, HttpMethod.PUT); - Predicate predicate = new MethodRoutePredicateFactory().apply(config); - assertThat(predicate.toString()).contains("Methods: [GET, PUT]"); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(DefaultTestConfig.class) - public static class TestConfig { - - @Value("${test.uri}") - String uri; - - @Bean - public RouteLocator testRouteLocator(RouteLocatorBuilder builder) { - return builder.routes() - .route("method_test_get", - r -> r.method("GET").and().path("/get").filters(f -> f.prefixPath("/httpbin")).uri(uri)) - .route("method_test_get_and_post", r -> r.method("GET", "POST").and().path("/multivalueheaders") - .filters(f -> f.prefixPath("/httpbin")).uri(uri)) - .build(); - } - - } - -} 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 deleted file mode 100644 index e2b4ddca..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicateFactoryTests.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright 2013-2020 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.handler.predicate; - -import java.util.Arrays; -import java.util.function.Predicate; - -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping; -import org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory.Config; -import org.springframework.cloud.gateway.route.RouteLocator; -import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; -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.test.annotation.DirtiesContext; -import org.springframework.web.server.ServerWebExchange; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; - -@SpringBootTest(webEnvironment = RANDOM_PORT) -@DirtiesContext -public class PathRoutePredicateFactoryTests extends BaseWebClientTests { - - @Test - public void pathRouteWorks() { - expectPathRoute("/abc/123/function", "www.path.org", "path_test"); - } - - @Test - public void trailingSlashReturns404() { - // since the configuration does not allow the trailing / to match this should fail - testClient.get().uri("/abc/123/function/").header(HttpHeaders.HOST, "www.path.org").exchange().expectStatus() - .isNotFound(); - } - - @Test - public void defaultPathRouteWorks() { - expectPathRoute("/get", "www.thispathshouldnotmatch.org", "default_path_to_httpbin"); - } - - private void expectPathRoute(String uri, String host, String routeId) { - testClient.get().uri(uri).header(HttpHeaders.HOST, host).exchange().expectStatus().isOk().expectHeader() - .valueEquals(HANDLER_MAPPER_HEADER, RoutePredicateHandlerMapping.class.getSimpleName()).expectHeader() - .valueEquals(ROUTE_ID_HEADER, routeId); - } - - @Test - public void mulitPathRouteWorks() { - expectPathRoute("/anything/multi11", "www.pathmulti.org", "path_multi"); - expectPathRoute("/anything/multi22", "www.pathmulti.org", "path_multi"); - expectPathRoute("/anything/multi33", "www.pathmulti.org", "default_path_to_httpbin"); - } - - @Test - public void mulitPathDslRouteWorks() { - expectPathRoute("/anything/multidsl1", "www.pathmultidsl.org", "path_multi_dsl"); - expectPathRoute("/anything/multidsl2", "www.pathmultidsl.org", "default_path_to_httpbin"); - expectPathRoute("/anything/multidsl3", "www.pathmultidsl.org", "path_multi_dsl"); - } - - @Test - public void pathRouteWorksWithPercent() { - testClient.get().uri("/abc/123%/function").header(HttpHeaders.HOST, "www.path.org").exchange().expectStatus() - .isOk().expectHeader() - .valueEquals(HANDLER_MAPPER_HEADER, RoutePredicateHandlerMapping.class.getSimpleName()).expectHeader() - .valueEquals(ROUTE_ID_HEADER, "path_test"); - } - - @Test - public void pathRouteWorksWithRegex() { - testClient.get().uri("/regex/123").header(HttpHeaders.HOST, "www.pathregex.org").exchange().expectStatus() - .isOk().expectHeader() - .valueEquals(HANDLER_MAPPER_HEADER, RoutePredicateHandlerMapping.class.getSimpleName()).expectHeader() - .valueEquals(ROUTE_ID_HEADER, "path_regex"); - } - - @Test - public void matchOptionalTrailingSeparatorCopiedToMatchTrailingSlash() { - Config config = new Config().setPatterns(Arrays.asList("patternA", "patternB")).setMatchTrailingSlash(false); - assertThat(config.isMatchTrailingSlash()).isEqualTo(false); - } - - @Test - public void toStringFormat() { - Config config = new Config().setPatterns(Arrays.asList("patternA", "patternB")).setMatchTrailingSlash(false); - Predicate predicate = new PathRoutePredicateFactory().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); - assertThat(predicate.toString()).contains("patternA").contains("patternB").contains("true"); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(DefaultTestConfig.class) - public static class TestConfig { - - @Value("${test.uri}") - String uri; - - @Bean - public RouteLocator testRouteLocator(RouteLocatorBuilder builder) { - return builder.routes() - .route("path_multi_dsl", - r -> r.host("**.pathmultidsl.org").and() - .path(false, "/anything/multidsl1", "/anything/multidsl3") - .filters(f -> f.prefixPath("/httpbin")).uri(uri)) - .build(); - } - - } - -} 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 deleted file mode 100644 index 92032d0e..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicatePathContainerAttrBenchMarkTests.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2013-2020 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.handler.predicate; - -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import java.util.Random; -import java.util.function.Predicate; - -import org.apache.commons.lang3.RandomStringUtils; -import org.openjdk.jmh.annotations.Benchmark; -import org.openjdk.jmh.annotations.BenchmarkMode; -import org.openjdk.jmh.annotations.Fork; -import org.openjdk.jmh.annotations.Measurement; -import org.openjdk.jmh.annotations.Mode; -import org.openjdk.jmh.annotations.Threads; -import org.openjdk.jmh.annotations.Warmup; - -import org.springframework.mock.http.server.reactive.MockServerHttpRequest; -import org.springframework.mock.web.server.MockServerWebExchange; -import org.springframework.web.server.ServerWebExchange; - -public class PathRoutePredicatePathContainerAttrBenchMarkTests { - - private static List> predicates; - - private static String PATH_PATTERN_PREFIX; - - private final static String HOST = "http://localhost:8080"; - - private final static int ROUTES_NUM = 2000; - - static { - predicates = new LinkedList<>(); - PATH_PATTERN_PREFIX = String.format("/%s/%s/", RandomStringUtils.random(20, true, false), - RandomStringUtils.random(10, true, false)); - for (int i = 0; i < ROUTES_NUM; i++) { - PathRoutePredicateFactory.Config config = new PathRoutePredicateFactory.Config() - .setPatterns(Collections.singletonList(PATH_PATTERN_PREFIX + i)).setMatchTrailingSlash(true); - Predicate predicate = new PathRoutePredicateFactory().apply(config); - predicates.add(predicate); - } - } - - @Benchmark - @Threads(2) - @Fork(2) - @BenchmarkMode(Mode.All) - @Warmup(iterations = 1, time = 3) - @Measurement(iterations = 10, time = 1) - public void testPathContainerAttr() { - Random random = new Random(); - MockServerHttpRequest request = MockServerHttpRequest - .get(HOST + PATH_PATTERN_PREFIX + random.nextInt(ROUTES_NUM)).build(); - MockServerWebExchange exchange = MockServerWebExchange.from(request); - for (Predicate predicate : predicates) { - if (predicate.test(exchange)) { - break; - } - } - } - - public static void main(String[] args) throws Exception { - org.openjdk.jmh.Main.main(args); - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/QueryRoutePredicateFactoryTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/QueryRoutePredicateFactoryTests.java deleted file mode 100644 index 48892642..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/QueryRoutePredicateFactoryTests.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2013-2020 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.handler.predicate; - -import java.util.function.Predicate; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.system.CapturedOutput; -import org.springframework.boot.test.system.OutputCaptureExtension; -import org.springframework.cloud.gateway.handler.predicate.QueryRoutePredicateFactory.Config; -import org.springframework.cloud.gateway.route.RouteLocator; -import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; -import org.springframework.cloud.gateway.test.BaseWebClientTests; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; -import org.springframework.test.annotation.DirtiesContext; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; - -@SpringBootTest(webEnvironment = RANDOM_PORT) -@DirtiesContext -@ExtendWith(OutputCaptureExtension.class) -public class QueryRoutePredicateFactoryTests extends BaseWebClientTests { - - @Test - public void noQueryParamWorks(CapturedOutput output) { - testClient.get().uri("/get").exchange().expectStatus().isOk().expectHeader().valueEquals(ROUTE_ID_HEADER, - "default_path_to_httpbin"); - assertThat(output).doesNotContain("Error applying predicate for route: foo_query_param"); - } - - @Test - public void queryParamWorks() { - testClient.get().uri("/get?foo=bar").exchange().expectStatus().isOk().expectHeader() - .valueEquals(ROUTE_ID_HEADER, "foo_query_param"); - } - - @Test - public void emptyQueryParamWorks(CapturedOutput output) { - testClient.get().uri("/get?foo").exchange().expectStatus().isOk().expectHeader().valueEquals(ROUTE_ID_HEADER, - "default_path_to_httpbin"); - assertThat(output).doesNotContain("Error applying predicate for route: foo_query_param"); - } - - @Test - public void toStringFormat() { - Config config = new Config(); - config.setParam("myparam"); - config.setRegexp("myregexp"); - Predicate predicate = new QueryRoutePredicateFactory().apply(config); - assertThat(predicate.toString()).contains("Query: param=myparam regexp=myregexp"); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(DefaultTestConfig.class) - public static class TestConfig { - - @Value("${test.uri}") - private String uri; - - @Bean - RouteLocator queryRouteLocator(RouteLocatorBuilder builder) { - return builder.routes().route("foo_query_param", - r -> r.query("foo", "bar").filters(f -> f.prefixPath("/httpbin")).uri(uri)).build(); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/ReadBodyRoutePredicateFactoryTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/ReadBodyRoutePredicateFactoryTests.java deleted file mode 100644 index 6921ca88..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/ReadBodyRoutePredicateFactoryTests.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright 2013-2020 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.handler.predicate; - -import java.util.function.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.cloud.gateway.handler.AsyncPredicate; -import org.springframework.cloud.gateway.handler.predicate.ReadBodyRoutePredicateFactory.Config; -import org.springframework.cloud.gateway.route.RouteLocator; -import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; -import org.springframework.cloud.gateway.test.BaseWebClientTests.TestLoadBalancerConfig; -import org.springframework.cloud.gateway.test.PermitAllSecurityConfiguration; -import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient; -import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; -import org.springframework.http.HttpMethod; -import org.springframework.http.MediaType; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.web.reactive.server.WebTestClient; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.reactive.function.BodyInserters; -import org.springframework.web.server.ServerWebExchange; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; - -/** - * @author Ryan Baxter - */ -@SpringBootTest(webEnvironment = RANDOM_PORT) -@DirtiesContext -public class ReadBodyRoutePredicateFactoryTests { - - @Autowired - private WebTestClient webClient; - - @Test - public void readBodyWorks() { - - Event messageEvent = new Event("message", "bar"); - Event messageChannelEvent = new Event("message.channels", "bar"); - - webClient.post().uri("/events").body(BodyInserters.fromValue(messageEvent)).exchange().expectStatus().isOk() - .expectBody().jsonPath("$.headers.Hello").isEqualTo("World"); - - webClient.post().uri("/events").body(BodyInserters.fromValue(messageChannelEvent)).exchange().expectStatus() - .isOk().expectBody().jsonPath("$.headers.World").isEqualTo("Hello"); - - } - - @Test - public void toStringFormat() { - Config config = new Config(); - config.setInClass(String.class); - AsyncPredicate predicate = new ReadBodyRoutePredicateFactory().applyAsync(config); - assertThat(predicate.toString()).contains("ReadBody: " + config.getInClass()); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @LoadBalancerClients({ @LoadBalancerClient(name = "message", configuration = TestLoadBalancerConfig.class), - @LoadBalancerClient(name = "messageChannel", configuration = TestLoadBalancerConfig.class) }) - @Import(PermitAllSecurityConfiguration.class) - @RestController - public static class TestConfig { - - @Bean - public RouteLocator routeLocator(RouteLocatorBuilder builder) { - return builder.routes() - .route(p -> p.path("/events").and().method(HttpMethod.POST).and() - .readBody(Event.class, eventPredicate("message.channels")) - .filters(f -> f.setPath("/messageChannel/events")).uri("lb://messageChannel")) - .route(p -> p.path("/events").and().method(HttpMethod.POST).and() - .readBody(Event.class, eventPredicate("message")).filters(f -> f.setPath("/message/events")) - .uri("lb://message")) - .build(); - } - - private Predicate eventPredicate(String type) { - return r -> r.getFoo().equals(type); - } - - @PostMapping(path = "message/events", produces = MediaType.APPLICATION_JSON_VALUE) - public String messageEvents(@RequestBody Event e) { - return "{\"headers\":{\"Hello\":\"World\"}}"; - } - - @PostMapping(path = "messageChannel/events", produces = MediaType.APPLICATION_JSON_VALUE) - public String messageChannelEvents(@RequestBody Event e) { - return "{\"headers\":{\"World\":\"Hello\"}}"; - } - - } - - static class Event { - - private String foo; - - private String bar; - - Event() { - } - - Event(String foo, String bar) { - this.foo = foo; - this.bar = bar; - } - - public String getFoo() { - return foo; - } - - public void setFoo(String foo) { - this.foo = foo; - } - - public String getBar() { - return bar; - } - - public void setBar(String bar) { - this.bar = bar; - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/RemoteAddrRoutePredicateFactoryTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/RemoteAddrRoutePredicateFactoryTests.java deleted file mode 100644 index aacb7ebe..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/RemoteAddrRoutePredicateFactoryTests.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright 2013-2020 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.handler.predicate; - -import java.time.Duration; -import java.util.function.Predicate; - -import org.junit.jupiter.api.Test; -import reactor.core.publisher.Mono; -import reactor.test.StepVerifier; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.gateway.handler.predicate.RemoteAddrRoutePredicateFactory.Config; -import org.springframework.cloud.gateway.route.RouteLocator; -import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; -import org.springframework.cloud.gateway.support.ipresolver.XForwardedRemoteAddressResolver; -import org.springframework.cloud.gateway.test.BaseWebClientTests; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; -import org.springframework.http.HttpStatus; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.web.reactive.function.client.ClientResponse; - -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.assertStatus; - -@SpringBootTest(webEnvironment = RANDOM_PORT) -@DirtiesContext -@ActiveProfiles({ "remote-address" }) -public class RemoteAddrRoutePredicateFactoryTests extends BaseWebClientTests { - - @Test - public void remoteAddrWorks() { - Mono result = webClient.get().uri("/ok/httpbin/").exchangeToMono(Mono::just); - - StepVerifier.create(result).consumeNextWith(response -> assertStatus(response, HttpStatus.OK)).expectComplete() - .verify(DURATION); - } - - @Test - public void remoteAddrRejects() { - Mono result = webClient.get().uri("/nok/httpbin/").exchangeToMono(Mono::just); - - StepVerifier.create(result).consumeNextWith(response -> assertStatus(response, HttpStatus.NOT_FOUND)) - .expectComplete().verify(DURATION); - } - - @Test - public void remoteAddrWorksWithXForwardedRemoteAddress() { - Mono result = webClient.get().uri("/xforwardfor").header("X-Forwarded-For", "12.34.56.78") - .exchangeToMono(Mono::just); - - StepVerifier.create(result).consumeNextWith(response -> assertStatus(response, HttpStatus.OK)).expectComplete() - .verify(Duration.ofSeconds(20)); - } - - @Test - public void toStringFormat() { - Config config = new Config(); - config.setSources("1.2.3.4", "5.6.7.8"); - Predicate predicate = new RemoteAddrRoutePredicateFactory().apply(config); - assertThat(predicate.toString()).contains("RemoteAddrs: [1.2.3.4, 5.6.7.8]"); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(DefaultTestConfig.class) - public static class TestConfig { - - @Value("${test.uri}") - String uri; - - @Bean - public RouteLocator testRouteLocator(RouteLocatorBuilder builder) { - return builder.routes() - .route("x_forwarded_for_test", - r -> r.path("/xforwardfor").and() - .remoteAddr(XForwardedRemoteAddressResolver.maxTrustedIndex(1), "12.34.56.78") - .filters(f -> f.setStatus(200)).uri(uri)) - .build(); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/WeightRoutePredicateFactoryIntegrationTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/WeightRoutePredicateFactoryIntegrationTests.java deleted file mode 100644 index a4289bfb..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/WeightRoutePredicateFactoryIntegrationTests.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright 2013-2020 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.handler.predicate; - -import java.util.function.Predicate; -import java.util.function.Supplier; - -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.gateway.filter.WeightCalculatorWebFilter; -import org.springframework.cloud.gateway.route.RouteLocator; -import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; -import org.springframework.cloud.gateway.support.WeightConfig; -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.test.annotation.DirtiesContext; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; - -@SpringBootTest(webEnvironment = RANDOM_PORT) -@DirtiesContext -public class WeightRoutePredicateFactoryIntegrationTests extends BaseWebClientTests { - - @Autowired - private WeightCalculatorWebFilter filter; - - private static Supplier getRandom(double value) { - Supplier random = mock(Supplier.class); - when(random.get()).thenReturn(value); - return random; - } - - @Test - public void highWeight() { - filter.setRandomSupplier(getRandom(0.9)); - - testClient.get().uri("/get").header(HttpHeaders.HOST, "www.weighthigh.org").exchange().expectStatus().isOk() - .expectHeader().valueEquals(ROUTE_ID_HEADER, "weight_high_test"); - } - - @Test - public void lowWeight() { - filter.setRandomSupplier(getRandom(0.1)); - - testClient.get().uri("/get").header(HttpHeaders.HOST, "www.weightlow.org").exchange().expectStatus().isOk() - .expectHeader().valueEquals(ROUTE_ID_HEADER, "weight_low_test"); - } - - @Test - public void toStringFormat() { - WeightConfig config = new WeightConfig("mygroup", "myroute", 5); - Predicate predicate = new WeightRoutePredicateFactory().apply(config); - assertThat(predicate.toString()).contains("Weight: mygroup 5"); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(DefaultTestConfig.class) - public static class TestConfig { - - @Value("${test.uri}") - private String uri; - - public TestConfig(WeightCalculatorWebFilter filter) { - Supplier random = getRandom(0.4); - - filter.setRandomSupplier(random); - } - - @Bean - public RouteLocator testRouteLocator(RouteLocatorBuilder builder) { - return builder.routes().route("weight_low_test", r -> r.weight("group1", 2).and().host("**.weightlow.org") - .filters(f -> f.prefixPath("/httpbin")).uri(this.uri)).build(); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/WeightRoutePredicateFactoryYaml404Tests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/WeightRoutePredicateFactoryYaml404Tests.java deleted file mode 100644 index c4ff253e..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/WeightRoutePredicateFactoryYaml404Tests.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2013-2020 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.handler.predicate; - -import java.util.function.Supplier; - -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.filter.WeightCalculatorWebFilter; -import org.springframework.cloud.gateway.test.BaseWebClientTests; -import org.springframework.context.annotation.Import; -import org.springframework.http.HttpHeaders; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.ActiveProfiles; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; - -@SpringBootTest(webEnvironment = RANDOM_PORT) -@ActiveProfiles("weights-404") -@DirtiesContext -class WeightRoutePredicateFactoryYaml404Tests extends BaseWebClientTests { - - @Autowired - private WeightCalculatorWebFilter filter; - - private static Supplier getRandom(double value) { - Supplier random = mock(Supplier.class); - when(random.get()).thenReturn(value); - return random; - } - - @Test - void weightsFromYamlNot404() { - filter.setRandomSupplier(getRandom(0.5)); - - testClient.get().uri("/get").header(HttpHeaders.HOST, "www.weight4041.org").exchange().expectStatus().isOk() - .expectHeader().valueEquals(ROUTE_ID_HEADER, "weight_first_404_test_1"); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(DefaultTestConfig.class) - static class TestConfig { - - TestConfig(WeightCalculatorWebFilter filter) { - Supplier random = getRandom(0.4); - - filter.setRandomSupplier(random); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/XForwardedRemoteAddrRoutePredicateFactoryTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/XForwardedRemoteAddrRoutePredicateFactoryTests.java deleted file mode 100644 index 7f2c28c7..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/handler/predicate/XForwardedRemoteAddrRoutePredicateFactoryTests.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 2013-2020 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.handler.predicate; - -import java.time.Duration; - -import org.junit.jupiter.api.Test; -import reactor.core.publisher.Mono; -import reactor.test.StepVerifier; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.gateway.route.RouteLocator; -import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; -import org.springframework.cloud.gateway.test.BaseWebClientTests; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; -import org.springframework.http.HttpStatus; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.web.reactive.function.client.ClientResponse; - -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; -import static org.springframework.cloud.gateway.test.TestUtils.assertStatus; - -@SpringBootTest(webEnvironment = RANDOM_PORT) -@DirtiesContext -@ActiveProfiles({ "remote-address" }) -public class XForwardedRemoteAddrRoutePredicateFactoryTests extends BaseWebClientTests { - - @Test - public void xForwardedRemoteAddrWorks() { - Mono result = webClient.get().uri("/xforwardfor").header("X-Forwarded-For", "12.34.56.78") - .exchangeToMono(Mono::just); - - StepVerifier.create(result).consumeNextWith(response -> assertStatus(response, HttpStatus.OK)).expectComplete() - .verify(Duration.ofSeconds(20)); - } - - @Test - public void xForwardedRemoteAddrWorksUsingRightMostValueByDefault() { - Mono result = webClient.get().uri("/xforwardfor") - .header("X-Forwarded-For", "99.99.99.99,12.34.56.78").exchangeToMono(Mono::just); - - StepVerifier.create(result).consumeNextWith(response -> assertStatus(response, HttpStatus.OK)).expectComplete() - .verify(Duration.ofSeconds(20)); - } - - @Test - public void xForwardedRemoteAddrRejects() { - Mono result = webClient.get().uri("/xforwardfor").header("X-Forwarded-For", "99.99.99.99") - .exchangeToMono(Mono::just); - - StepVerifier.create(result).consumeNextWith(response -> assertStatus(response, HttpStatus.NOT_FOUND)) - .expectComplete().verify(Duration.ofSeconds(20)); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(DefaultTestConfig.class) - public static class TestConfig { - - @Value("${test.uri}") - String uri; - - @Bean - public RouteLocator testRouteLocator(RouteLocatorBuilder builder) { - return builder.routes().route("x_forwarded_for_test", r -> r.path("/xforwardfor").and() - .xForwardedRemoteAddr("12.34.56.78").filters(f -> f.setStatus(200)).uri(uri)).build(); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/route/CachingRouteDefinitionLocatorTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/route/CachingRouteDefinitionLocatorTests.java deleted file mode 100644 index 07b4449e..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/route/CachingRouteDefinitionLocatorTests.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright 2013-2020 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.route; - -import java.net.URI; -import java.util.List; -import java.util.concurrent.CountDownLatch; - -import org.junit.jupiter.api.Test; -import reactor.core.publisher.Flux; -import reactor.core.scheduler.Schedulers; - -import org.springframework.cloud.gateway.event.RefreshRoutesEvent; - -import static org.assertj.core.api.Assertions.assertThat; - -public class CachingRouteDefinitionLocatorTests { - - @Test - public void getRouteDefinitionsWorks() { - RouteDefinition routeDef1 = routeDef(1); - RouteDefinition routeDef2 = routeDef(2); - CachingRouteDefinitionLocator locator = new CachingRouteDefinitionLocator( - () -> Flux.just(routeDef2, routeDef1)); - - List routes = locator.getRouteDefinitions().collectList().block(); - - assertThat(routes).containsExactlyInAnyOrder(routeDef1, routeDef2); - } - - @Test - public void refreshWorks() { - RouteDefinition routeDef1 = routeDef(1); - RouteDefinition routeDef2 = routeDef(2); - CachingRouteDefinitionLocator locator = new CachingRouteDefinitionLocator( - new StubRouteDefinitionLocator(Flux.just(routeDef2), Flux.just(routeDef1, routeDef2))); - - List routes = locator.getRouteDefinitions().collectList().block(); - assertThat(routes).containsExactlyInAnyOrder(routeDef2); - - routes = locator.refresh().collectList().block(); - assertThat(routes).containsExactlyInAnyOrder(routeDef1, routeDef2); - } - - @Test - public void cacheIsNotClearedOnEvent() { - RouteDefinition routeDef1 = routeDef(1); - RouteDefinition routeDef2 = routeDef(2); - - CountDownLatch latch = new CountDownLatch(1); - CachingRouteDefinitionLocator locator = new CachingRouteDefinitionLocator( - new StubRouteDefinitionLocator(Flux.just(routeDef1), Flux.defer(() -> { - try { - latch.await(); - } - catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new IllegalStateException(e); - } - return Flux.just(routeDef1, routeDef2); - }).subscribeOn(Schedulers.single()))); - - List routes = locator.getRouteDefinitions().collectList().block(); - assertThat(routes).containsExactlyInAnyOrder(routeDef1); - - locator.onApplicationEvent(new RefreshRoutesEvent(this)); - - routes = locator.getRouteDefinitions().collectList().block(); - assertThat(routes).containsExactlyInAnyOrder(routeDef1); - - latch.countDown(); - } - - @Test - public void cacheIsRefreshedInTheBackgroundOnEvent() { - RouteDefinition routeDef1 = routeDef(1); - RouteDefinition routeDef2 = routeDef(2); - - CachingRouteDefinitionLocator locator = new CachingRouteDefinitionLocator(new StubRouteDefinitionLocator( - Flux.just(routeDef1), Flux.defer(() -> Flux.just(routeDef1, routeDef2)))); - - List routes = locator.getRouteDefinitions().collectList().block(); - assertThat(routes).containsExactlyInAnyOrder(routeDef1); - - locator.onApplicationEvent(new RefreshRoutesEvent(this)); - - List updatedRoutes = locator.getRouteDefinitions().collectList().block(); - assertThat(updatedRoutes).containsExactlyInAnyOrder(routeDef1, routeDef2); - } - - RouteDefinition routeDef(int id) { - RouteDefinition def = new RouteDefinition(); - def.setId(String.valueOf(id)); - def.setUri(URI.create("http://localhost/" + id)); - def.setOrder(id); - return def; - } - - private static final class StubRouteDefinitionLocator implements RouteDefinitionLocator { - - private final Flux first; - - private final Flux second; - - int i; - - private StubRouteDefinitionLocator(Flux first, Flux second) { - this.first = first; - this.second = second; - i = 0; - } - - @Override - public Flux getRouteDefinitions() { - if (i++ == 0) { - return first; - } - return second; - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/route/CachingRouteLocatorTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/route/CachingRouteLocatorTests.java deleted file mode 100644 index da36cdeb..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/route/CachingRouteLocatorTests.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright 2013-2020 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.route; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -import org.junit.jupiter.api.Test; -import reactor.core.publisher.Flux; - -import org.springframework.cloud.gateway.event.RefreshRoutesEvent; -import org.springframework.cloud.gateway.event.RefreshRoutesResultEvent; - -import static org.assertj.core.api.Assertions.assertThat; - -public class CachingRouteLocatorTests { - - @Test - public void getRoutesWorks() { - Route route1 = route(1); - Route route2 = route(2); - CachingRouteLocator locator = new CachingRouteLocator(() -> Flux.just(route2, route1)); - - List routes = locator.getRoutes().collectList().block(); - - assertThat(routes).containsExactly(route1, route2); - } - - @Test - public void refreshWorks() { - Route route1 = route(1); - Route route2 = route(2); - CachingRouteLocator locator = new CachingRouteLocator(new RouteLocator() { - int i = 0; - - @Override - public Flux getRoutes() { - if (i++ == 0) { - return Flux.just(route2); - } - return Flux.just(route2, route1); - } - }); - - List routes = locator.getRoutes().collectList().block(); - assertThat(routes).containsExactly(route2); - - routes = locator.refresh().collectList().block(); - assertThat(routes).containsExactly(route1, route2); - } - - @Test - public void refreshWorksWhenFirstRefreshSuccessAndOtherError() throws InterruptedException { - Route route1 = route(1); - Route route2 = route(2); - - CachingRouteLocator locator = new CachingRouteLocator(new RouteLocator() { - int i = 0; - - @Override - public Flux getRoutes() { - if (i == 0) { - i++; - return Flux.just(route1); - } - else if (i == 1) { - i++; - return Flux.just(route2).map(route -> { - throw new RuntimeException("in chain."); - }); - } - else if (i == 2) { - i++; - throw new RuntimeException("call getRoutes error."); - } - return Flux.just(route2); - } - }); - - List routes = locator.getRoutes().collectList().block(); - assertThat(routes).containsExactly(route1); - - List resultEvents = new ArrayList<>(); - - waitUntilRefreshFinished(locator, resultEvents); - assertThat(resultEvents).hasSize(1); - assertThat(resultEvents.get(0).getThrowable().getMessage()).isEqualTo("in chain."); - assertThat(resultEvents.get(0).isSuccess()).isEqualTo(false); - assertThat(locator.getRoutes().collectList().block()).containsExactly(route1); - - waitUntilRefreshFinished(locator, resultEvents); - assertThat(resultEvents).hasSize(2); - assertThat(resultEvents.get(1).getThrowable().getMessage()).isEqualTo("call getRoutes error."); - assertThat(resultEvents.get(1).isSuccess()).isEqualTo(false); - assertThat(locator.getRoutes().collectList().block()).containsExactly(route1); - - waitUntilRefreshFinished(locator, resultEvents); - assertThat(resultEvents).hasSize(3); - assertThat(resultEvents.get(2).isSuccess()).isEqualTo(true); - assertThat(locator.getRoutes().collectList().block()).containsExactly(route2); - - } - - private void waitUntilRefreshFinished(CachingRouteLocator locator, List resultEvents) - throws InterruptedException { - CountDownLatch cdl = new CountDownLatch(1); - locator.setApplicationEventPublisher(o -> { - resultEvents.add((RefreshRoutesResultEvent) o); - cdl.countDown(); - }); - locator.onApplicationEvent(new RefreshRoutesEvent(this)); - - assertThat(cdl.await(5, TimeUnit.SECONDS)).isTrue(); - } - - Route route(int id) { - return Route.async().id(String.valueOf(id)).uri("http://localhost/" + id).order(id).predicate(exchange -> true) - .build(); - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/route/InMemoryRouteDefinitionRepositoryTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/route/InMemoryRouteDefinitionRepositoryTests.java deleted file mode 100644 index 6bc6117d..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/route/InMemoryRouteDefinitionRepositoryTests.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright 2013-2020 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.route; - -import java.time.Duration; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import reactor.test.StepVerifier; - -import org.springframework.cloud.gateway.support.NotFoundException; - -public class InMemoryRouteDefinitionRepositoryTests { - - private InMemoryRouteDefinitionRepository repository; - - @BeforeEach - public void setUp() throws Exception { - repository = new InMemoryRouteDefinitionRepository(); - } - - @Test - public void shouldProtectRoutesAgainstConcurrentModificationException() { - Flux createRoutes = Flux.just(createRoute("foo1"), createRoute("foo2"), createRoute("foo3")) - .flatMap(repository::save); - - StepVerifier.create(createRoutes).verifyComplete(); - - Flux readRoutesWithDelay = repository.getRouteDefinitions() - .delayElements(Duration.ofMillis(100)); - - Mono createAnotherRoute = repository.save(createRoute("bar")); - - StepVerifier.withVirtualTime(() -> readRoutesWithDelay).expectSubscription().expectNextCount(1) - .then(createAnotherRoute::subscribe).thenAwait().expectNextCount(2).verifyComplete(); - } - - @Test - public void shouldValidateRouteIdOnCreate() { - Mono emptyRoute = Mono.just(new RouteDefinition()); - StepVerifier.create(repository.save(emptyRoute)).verifyError(IllegalArgumentException.class); - } - - @Test - public void shouldCreateRoute() { - StepVerifier.create(repository.save(createRoute("foo"))).verifyComplete(); - - StepVerifier.create(repository.getRouteDefinitions()).expectNextCount(1).verifyComplete(); - } - - @Test - public void shouldDeleteRoute() { - Flux createRoutes = Flux.just(createRoute("foo1"), createRoute("foo2"), createRoute("foo3")) - .flatMap(repository::save); - - StepVerifier.create(createRoutes).verifyComplete(); - - Mono deleteRoute = repository.delete(Mono.just("foo2")); - - StepVerifier.create(deleteRoute).verifyComplete(); - - StepVerifier.create(repository.getRouteDefinitions()).expectNextCount(2).verifyComplete(); - } - - @Test - public void shouldThrownNotFoundWhenDeleteInvalidRoute() { - StepVerifier.create(repository.delete(Mono.just("x"))).verifyError(NotFoundException.class); - } - - private Mono createRoute(String id) { - RouteDefinition routeDefinition = new RouteDefinition(); - routeDefinition.setId(id); - return Mono.just(routeDefinition); - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/route/RedisRouteDefinitionRepositoryTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/route/RedisRouteDefinitionRepositoryTests.java deleted file mode 100644 index d54c5109..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/route/RedisRouteDefinitionRepositoryTests.java +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright 2013-2017 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.route; - -import java.net.URI; -import java.util.Arrays; -import java.util.List; -import java.util.function.Predicate; - -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 reactor.core.publisher.Mono; - -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.filter.FilterDefinition; -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; -import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory; -import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition; -import org.springframework.context.annotation.Bean; -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.web.server.ServerWebExchange; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Dennis Menge - */ -@SpringBootTest(properties = { "debug=true", "logging.level.org.springframework.cloud.gateway=trace" }) -@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD) -@ActiveProfiles("redis-route-repository") -@Testcontainers -@Tag("DockerRequired") -public class RedisRouteDefinitionRepositoryTests { - - @Container - public static GenericContainer redis = new GenericContainer<>("redis:5.0.14-alpine").withExposedPorts(6379); - - @Autowired - private RedisRouteDefinitionRepository redisRouteDefinitionRepository; - - @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); - } - - @Test - public void testAddRouteToRedis() { - RouteDefinition testRouteDefinition = defaultTestRoute(); - - redisRouteDefinitionRepository.save(Mono.just(testRouteDefinition)).block(); - - List routeDefinitions = redisRouteDefinitionRepository.getRouteDefinitions().collectList() - .block(); - - assertThat(routeDefinitions.size()).isEqualTo(1); - assertThat(routeDefinitions.get(0)).isEqualTo(testRouteDefinition); - } - - @Test - public void testRemoveRouteFromRedis() { - - RouteDefinition testRouteDefinition = defaultTestRoute(); - - redisRouteDefinitionRepository.save(Mono.just(testRouteDefinition)).block(); - - List routeDefinitions = redisRouteDefinitionRepository.getRouteDefinitions().collectList() - .block(); - String routeId = routeDefinitions.get(0).getId(); - - // Assert that route has been added. - assertThat(routeDefinitions.size()).isEqualTo(1); - - // Delete route from repository - redisRouteDefinitionRepository.delete(Mono.just(routeId)).block(); - - // Assert that route has been removed. - assertThat(redisRouteDefinitionRepository.getRouteDefinitions().collectList().block().size()).isEqualTo(0); - } - - private RouteDefinition defaultTestRoute() { - RouteDefinition testRouteDefinition = new RouteDefinition(); - testRouteDefinition.setId("test-route"); - testRouteDefinition.setUri(URI.create("http://example.org")); - - FilterDefinition prefixPathFilterDefinition = new FilterDefinition("PrefixPath=/test-path"); - FilterDefinition redirectToFilterDefinition = new FilterDefinition("RemoveResponseHeader=Sensitive-Header"); - FilterDefinition testFilterDefinition = new FilterDefinition(); - testFilterDefinition.setName("Test"); - testRouteDefinition.setFilters( - Arrays.asList(prefixPathFilterDefinition, redirectToFilterDefinition, testFilterDefinition)); - - PredicateDefinition hostRoutePredicateDefinition = new PredicateDefinition("Host=myhost.org"); - PredicateDefinition methodRoutePredicateDefinition = new PredicateDefinition("Method=GET"); - PredicateDefinition testPredicateDefinition = new PredicateDefinition("Test=value"); - testRouteDefinition.setPredicates( - Arrays.asList(hostRoutePredicateDefinition, methodRoutePredicateDefinition, testPredicateDefinition)); - return testRouteDefinition; - } - - @EnableAutoConfiguration - @SpringBootConfiguration - public static class TestConfig { - - @Bean - TestGatewayFilterFactory testGatewayFilterFactory() { - return new TestGatewayFilterFactory(); - } - - @Bean - TestFilterGatewayFilterFactory testFilterGatewayFilterFactory() { - return new TestFilterGatewayFilterFactory(); - } - - @Bean - TestRoutePredicateFactory testRoutePredicateFactory() { - return new TestRoutePredicateFactory(); - } - - } - - public static class TestGatewayFilterFactory extends AbstractGatewayFilterFactory { - - @Override - public GatewayFilter apply(Object config) { - return (exchange, chain) -> chain.filter(exchange); - } - - } - - public static class TestFilterGatewayFilterFactory extends TestGatewayFilterFactory { - - } - - public static class TestRoutePredicateFactory extends AbstractRoutePredicateFactory { - - public TestRoutePredicateFactory() { - super(Object.class); - } - - @Override - public Predicate apply(Object config) { - return exchange -> true; - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/route/RouteDefinitionDefaultIdTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/route/RouteDefinitionDefaultIdTests.java deleted file mode 100644 index cba24bbc..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/route/RouteDefinitionDefaultIdTests.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2013-2020 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.route; - -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.config.GatewayProperties; -import org.springframework.cloud.gateway.test.BaseWebClientTests; -import org.springframework.context.annotation.Import; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.web.bind.annotation.RestController; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; - -@SpringBootTest(webEnvironment = RANDOM_PORT) -@DirtiesContext -@ActiveProfiles("defaultid") -public class RouteDefinitionDefaultIdTests { - - @Autowired - private GatewayProperties properties; - - @Test - public void testDefaultIdWorks() { - assertThat(properties.getRoutes()).hasSize(1); - RouteDefinition routeDefinition = properties.getRoutes().get(0); - assertThat(routeDefinition.getId()).isNotNull(); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(BaseWebClientTests.DefaultTestConfig.class) - @RestController - public static class TestConfig { - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/route/RouteDefinitionMetricsTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/route/RouteDefinitionMetricsTests.java deleted file mode 100644 index ab7e4549..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/route/RouteDefinitionMetricsTests.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright 2013-2020 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.route; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; - -import io.micrometer.core.instrument.MeterRegistry; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import reactor.core.publisher.Flux; - -import org.springframework.cloud.gateway.event.RefreshRoutesEvent; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.when; - -/** - * @author Fredrich Ombico - */ -@ExtendWith(MockitoExtension.class) -class RouteDefinitionMetricsTests { - - @Mock - private MeterRegistry registry; - - @Mock - private RouteDefinitionLocator routeDefinitionLocator; - - private RouteDefinitionMetrics routeDefinitionMetrics; - - private AtomicInteger routeDefinitionCount; - - @BeforeEach - void setUp() { - routeDefinitionCount = new AtomicInteger(0); - when(registry.gauge(any(String.class), any(AtomicInteger.class))).thenReturn(routeDefinitionCount); - routeDefinitionMetrics = new RouteDefinitionMetrics(registry, routeDefinitionLocator, "prefix."); - } - - @Test - void metricsPrefix() { - assertThat(routeDefinitionMetrics.getMetricsPrefix()).isEqualTo("prefix"); - } - - @Test - void shouldReportOneRoute() { - List oneRoute = Collections.singletonList(new RouteDefinition()); - when(routeDefinitionLocator.getRouteDefinitions()).thenReturn(Flux.fromStream(oneRoute.stream())); - - RefreshRoutesEvent refreshRoutesEvent = new RefreshRoutesEvent(this); - routeDefinitionMetrics.onApplicationEvent(refreshRoutesEvent); - - assertThat(routeDefinitionCount.get()).isEqualTo(1); - } - - @Test - void shouldReportMultipleRoutes() { - List multipleRoutes = Arrays.asList(new RouteDefinition(), new RouteDefinition(), - new RouteDefinition(), new RouteDefinition(), new RouteDefinition()); - when(routeDefinitionLocator.getRouteDefinitions()).thenReturn(Flux.fromStream(multipleRoutes.stream())); - - RefreshRoutesEvent refreshRoutesEvent = new RefreshRoutesEvent(this); - routeDefinitionMetrics.onApplicationEvent(refreshRoutesEvent); - - assertThat(routeDefinitionCount.get()).isEqualTo(5); - } - - @Test - void shouldReportZeroIfNoRoutes() { - RouteDefinition[] zeroRoutes = new RouteDefinition[0]; - when(routeDefinitionLocator.getRouteDefinitions()).thenReturn(Flux.fromArray(zeroRoutes)); - - RefreshRoutesEvent refreshRoutesEvent = new RefreshRoutesEvent(this); - routeDefinitionMetrics.onApplicationEvent(refreshRoutesEvent); - - assertThat(routeDefinitionCount.get()).isEqualTo(0); - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/route/RouteDefinitionRouteLocatorTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/route/RouteDefinitionRouteLocatorTests.java deleted file mode 100644 index 280108d0..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/route/RouteDefinitionRouteLocatorTests.java +++ /dev/null @@ -1,212 +0,0 @@ -/* - * Copyright 2013-2020 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.route; - -import java.net.URI; -import java.util.Arrays; -import java.util.List; - -import org.junit.jupiter.api.Test; -import reactor.core.publisher.Flux; -import reactor.test.StepVerifier; - -import org.springframework.cloud.gateway.config.GatewayProperties; -import org.springframework.cloud.gateway.config.PropertiesRouteDefinitionLocator; -import org.springframework.cloud.gateway.filter.FilterDefinition; -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.OrderedGatewayFilter; -import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.AddResponseHeaderGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.GatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.RemoveResponseHeaderGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.RetryGatewayFilterFactory; -import org.springframework.cloud.gateway.handler.predicate.HostRoutePredicateFactory; -import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition; -import org.springframework.cloud.gateway.handler.predicate.RoutePredicateFactory; -import org.springframework.cloud.gateway.support.ConfigurationService; -import org.springframework.util.ObjectUtils; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Toshiaki Maki - */ -public class RouteDefinitionRouteLocatorTests { - - @Test - public void contextLoads() { - List predicates = Arrays.asList(new HostRoutePredicateFactory()); - List gatewayFilterFactories = Arrays.asList( - new RemoveResponseHeaderGatewayFilterFactory(), new AddResponseHeaderGatewayFilterFactory(), - new TestOrderedGatewayFilterFactory()); - GatewayProperties gatewayProperties = new GatewayProperties(); - gatewayProperties.setRoutes(Arrays.asList(new RouteDefinition() { - { - setId("foo"); - setUri(URI.create("https://foo.example.com")); - setPredicates(Arrays.asList(new PredicateDefinition("Host=*.example.com"))); - setFilters(Arrays.asList(new FilterDefinition("RemoveResponseHeader=Server"), - new FilterDefinition("TestOrdered="), - new FilterDefinition("AddResponseHeader=X-Response-Foo, Bar"))); - } - })); - - PropertiesRouteDefinitionLocator routeDefinitionLocator = new PropertiesRouteDefinitionLocator( - gatewayProperties); - @SuppressWarnings("deprecation") - RouteDefinitionRouteLocator routeDefinitionRouteLocator = new RouteDefinitionRouteLocator( - new CompositeRouteDefinitionLocator(Flux.just(routeDefinitionLocator)), predicates, - gatewayFilterFactories, gatewayProperties, new ConfigurationService(null, () -> null, () -> null)); - - StepVerifier.create(routeDefinitionRouteLocator.getRoutes()).assertNext(route -> { - List filters = route.getFilters(); - assertThat(filters).hasSize(3); - assertThat(getFilterClassName(filters.get(0))).contains("RemoveResponseHeader"); - assertThat(getFilterClassName(filters.get(1))).contains("AddResponseHeader"); - assertThat(getFilterClassName(filters.get(2))) - .contains("RouteDefinitionRouteLocatorTests$TestOrderedGateway"); - }).expectComplete().verify(); - } - - @Test - public void simpleRetryDefinitionLoads() { - List predicates = Arrays.asList(new HostRoutePredicateFactory()); - List gatewayFilterFactories = Arrays.asList(new RetryGatewayFilterFactory()); - GatewayProperties gatewayProperties = new GatewayProperties(); - gatewayProperties.setRoutes(Arrays.asList(new RouteDefinition() { - { - setId("simple"); - setUri(URI.create("https://foo.example.com")); - setPredicates(Arrays.asList(new PredicateDefinition("Host=*.example.com"))); - setFilters(Arrays.asList(new FilterDefinition("Retry=3,INTERNAL_SERVER_ERROR,GET"))); - } - })); - - PropertiesRouteDefinitionLocator routeDefinitionLocator = new PropertiesRouteDefinitionLocator( - gatewayProperties); - RouteDefinitionRouteLocator routeDefinitionRouteLocator = new RouteDefinitionRouteLocator( - new CompositeRouteDefinitionLocator(Flux.just(routeDefinitionLocator)), predicates, - gatewayFilterFactories, gatewayProperties, new ConfigurationService(null, () -> null, () -> null)); - - StepVerifier.create(routeDefinitionRouteLocator.getRoutes()).assertNext(route -> { - List filters = route.getFilters(); - assertThat(filters).hasSize(1); - assertThat(getFilterClassName(filters.get(0))).contains("Retry"); - assertThat(filters.get(0).toString()).contains("retries = 3"); - assertThat(filters.get(0).toString()).contains("series = list[SERVER_ERROR]"); - assertThat(filters.get(0).toString()).contains("methods = list[GET]"); - }).expectComplete().verify(); - } - - @Test - public void contextLoadsWithErrorRecovery() { - List predicates = Arrays.asList(new HostRoutePredicateFactory()); - List gatewayFilterFactories = Arrays.asList( - new RemoveResponseHeaderGatewayFilterFactory(), new AddResponseHeaderGatewayFilterFactory(), - new TestOrderedGatewayFilterFactory()); - GatewayProperties gatewayProperties = new GatewayProperties(); - gatewayProperties.setRoutes(containsInvalidRoutes()); - gatewayProperties.setFailOnRouteDefinitionError(false); - - PropertiesRouteDefinitionLocator routeDefinitionLocator = new PropertiesRouteDefinitionLocator( - gatewayProperties); - @SuppressWarnings("deprecation") - RouteDefinitionRouteLocator routeDefinitionRouteLocator = new RouteDefinitionRouteLocator( - new CompositeRouteDefinitionLocator(Flux.just(routeDefinitionLocator)), predicates, - gatewayFilterFactories, gatewayProperties, new ConfigurationService(null, () -> null, () -> null)); - - StepVerifier.create(routeDefinitionRouteLocator.getRoutes()).assertNext(route -> { - List filters = route.getFilters(); - assertThat(filters).hasSize(3); - assertThat(getFilterClassName(filters.get(0))).contains("RemoveResponseHeader"); - assertThat(getFilterClassName(filters.get(1))).contains("AddResponseHeader"); - assertThat(getFilterClassName(filters.get(2))) - .contains("RouteDefinitionRouteLocatorTests$TestOrderedGateway"); - }).expectComplete().verify(); - } - - @Test - public void contextLoadsAndApplyRouteIdToRetryFilter() { - List predicates = Arrays.asList(new HostRoutePredicateFactory()); - List gatewayFilterFactories = Arrays.asList(new RetryGatewayFilterFactory(), - new AddResponseHeaderGatewayFilterFactory()); - GatewayProperties gatewayProperties = new GatewayProperties(); - gatewayProperties.setDefaultFilters(Arrays.asList(new FilterDefinition("Retry"))); - gatewayProperties.setRoutes(Arrays.asList(new RouteDefinition() { - { - setId("foo"); - setUri(URI.create("https://foo.example.com")); - setPredicates(Arrays.asList(new PredicateDefinition("Host=*.example.com"))); - setFilters(Arrays.asList(new FilterDefinition("AddResponseHeader=X-Response-Foo, Bar"))); - } - })); - - PropertiesRouteDefinitionLocator routeDefinitionLocator = new PropertiesRouteDefinitionLocator( - gatewayProperties); - @SuppressWarnings("deprecation") - RouteDefinitionRouteLocator routeDefinitionRouteLocator = new RouteDefinitionRouteLocator( - new CompositeRouteDefinitionLocator(Flux.just(routeDefinitionLocator)), predicates, - gatewayFilterFactories, gatewayProperties, new ConfigurationService(null, () -> null, () -> null)); - - StepVerifier.create(routeDefinitionRouteLocator.getRoutes()).assertNext(route -> { - List filters = route.getFilters(); - assertThat(filters).hasSize(2); - assertThat(filters.get(0).toString()).contains("routeId = 'foo'"); - assertThat(getFilterClassName(filters.get(0))).contains("Retry"); - assertThat(getFilterClassName(filters.get(1))).contains("AddResponseHeader"); - }).expectComplete().verify(); - } - - private List containsInvalidRoutes() { - RouteDefinition foo = new RouteDefinition(); - foo.setId("foo"); - foo.setUri(URI.create("https://foo.example.com")); - foo.setPredicates(Arrays.asList(new PredicateDefinition("Host=*.example.com"))); - foo.setFilters(Arrays.asList(new FilterDefinition("RemoveResponseHeader=Server"), - new FilterDefinition("TestOrdered="), new FilterDefinition("AddResponseHeader=X-Response-Foo, Bar"))); - RouteDefinition bad = new RouteDefinition(); - bad.setId("exceptionRaised"); - bad.setUri(URI.create("https://foo.example.com")); - bad.setPredicates(Arrays.asList(new PredicateDefinition("Host=*.example.com"))); - bad.setFilters(Arrays.asList(new FilterDefinition("Generate exception"))); - return Arrays.asList(foo, bad); - } - - private String getFilterClassName(GatewayFilter target) { - if (target instanceof OrderedGatewayFilter) { - return getFilterClassName(((OrderedGatewayFilter) target).getDelegate()); - } - else { - String simpleName = target.getClass().getSimpleName(); - if (ObjectUtils.isEmpty(simpleName)) { - // maybe a lambda using new toString methods - simpleName = target.toString(); - } - return simpleName; - } - } - - static class TestOrderedGatewayFilterFactory extends AbstractGatewayFilterFactory { - - @Override - public GatewayFilter apply(Object config) { - return new OrderedGatewayFilter((exchange, chain) -> chain.filter(exchange), 9999); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/route/RouteDefinitionTest.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/route/RouteDefinitionTest.java deleted file mode 100644 index 69d230ca..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/route/RouteDefinitionTest.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2013-2020 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.route; - -import java.util.Map; - -import org.assertj.core.util.Maps; -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Stefan Stus - */ -public class RouteDefinitionTest { - - @Test - public void addRouteDefinitionKeepsExistingMetadata() { - Map originalMetadata = Maps.newHashMap("key", "value"); - Map newMetadata = Maps.newHashMap("key2", "value2"); - - RouteDefinition routeDefinition = new RouteDefinition(); - routeDefinition.setMetadata(originalMetadata); - routeDefinition.getMetadata().putAll(newMetadata); - - assertThat(routeDefinition.getMetadata()).hasSize(2).containsAllEntriesOf(originalMetadata) - .containsAllEntriesOf(newMetadata); - } - - @Test - public void setRouteDefinitionReplacesExistingMetadata() { - Map originalMetadata = Maps.newHashMap("key", "value"); - Map newMetadata = Maps.newHashMap("key2", "value2"); - - RouteDefinition routeDefinition = new RouteDefinition(); - routeDefinition.setMetadata(originalMetadata); - routeDefinition.setMetadata(newMetadata); - - assertThat(routeDefinition.getMetadata()).isEqualTo(newMetadata); - } - - @Test - public void addSingleMetadataEntryKeepsOriginalMetadata() { - Map originalMetadata = Maps.newHashMap("key", "value"); - - RouteDefinition routeDefinition = new RouteDefinition(); - routeDefinition.setMetadata(originalMetadata); - routeDefinition.getMetadata().put("key2", "value2"); - - assertThat(routeDefinition.getMetadata()).hasSize(2).containsAllEntriesOf(originalMetadata) - .containsEntry("key2", "value2"); - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/route/RouteRefreshListenerTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/route/RouteRefreshListenerTests.java deleted file mode 100644 index 9b5fa660..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/route/RouteRefreshListenerTests.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright 2013-2020 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.route; - -import org.junit.jupiter.api.Test; - -import org.springframework.boot.web.context.WebServerApplicationContext; -import org.springframework.cloud.client.discovery.event.HeartbeatEvent; -import org.springframework.cloud.client.discovery.event.InstanceRegisteredEvent; -import org.springframework.cloud.client.discovery.event.ParentHeartbeatEvent; -import org.springframework.cloud.gateway.event.RefreshRoutesEvent; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.context.event.ContextRefreshedEvent; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.mockito.Mockito.when; - -public class RouteRefreshListenerTests { - - @Test - public void onContextRefreshedEventManagement() { - ApplicationEventPublisher publisher = mock(ApplicationEventPublisher.class); - RouteRefreshListener listener = new RouteRefreshListener(publisher); - - WebServerApplicationContext applicationContext = mock(WebServerApplicationContext.class); - when(applicationContext.getServerNamespace()).thenReturn("management"); - listener.onApplicationEvent(new ContextRefreshedEvent(applicationContext)); - - verifyNoInteractions(publisher); - } - - @Test - public void onContextRefreshedEvent() { - ApplicationEventPublisher publisher = mock(ApplicationEventPublisher.class); - RouteRefreshListener listener = new RouteRefreshListener(publisher); - - listener.onApplicationEvent(new ContextRefreshedEvent(mock(ApplicationContext.class))); - - verify(publisher).publishEvent(any(RefreshRoutesEvent.class)); - } - - @Test - public void onInstanceRegisteredEvent() { - ApplicationEventPublisher publisher = mock(ApplicationEventPublisher.class); - RouteRefreshListener listener = new RouteRefreshListener(publisher); - - listener.onApplicationEvent(new InstanceRegisteredEvent<>(this, new Object())); - - verify(publisher).publishEvent(any(RefreshRoutesEvent.class)); - } - - @Test - public void onHeartbeatEvent() { - ApplicationEventPublisher publisher = mock(ApplicationEventPublisher.class); - RouteRefreshListener listener = new RouteRefreshListener(publisher); - - listener.onApplicationEvent(new HeartbeatEvent(this, 1L)); - listener.onApplicationEvent(new HeartbeatEvent(this, 1L)); - listener.onApplicationEvent(new HeartbeatEvent(this, 2L)); - - verify(publisher, times(2)).publishEvent(any(RefreshRoutesEvent.class)); - } - - @Test - public void onParentHeartbeatEvent() { - ApplicationEventPublisher publisher = mock(ApplicationEventPublisher.class); - RouteRefreshListener listener = new RouteRefreshListener(publisher); - - listener.onApplicationEvent(new ParentHeartbeatEvent(this, 1L)); - listener.onApplicationEvent(new ParentHeartbeatEvent(this, 1L)); - listener.onApplicationEvent(new ParentHeartbeatEvent(this, 2L)); - - verify(publisher, times(2)).publishEvent(any(RefreshRoutesEvent.class)); - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/route/RouteTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/route/RouteTests.java deleted file mode 100644 index 5d3f8825..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/route/RouteTests.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2013-2020 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.route; - -import java.net.URI; -import java.util.HashMap; - -import org.assertj.core.util.Maps; -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -public class RouteTests { - - @Test - public void defaultHttpPort() { - Route route = Route.async().id("1").predicate(exchange -> true).uri("http://acme.com").build(); - - assertThat(route.getUri()).hasHost("acme.com").hasScheme("http").hasPort(80); - } - - @Test - public void defaultHttpsPort() { - Route route = Route.async().id("1").predicate(exchange -> true).uri("https://acme.com").build(); - - assertThat(route.getUri()).hasHost("acme.com").hasScheme("https").hasPort(443); - } - - @Test - public void fullUri() { - Route route = Route.async().id("1").predicate(exchange -> true).uri("http://acme.com:8080").build(); - - assertThat(route.getUri()).hasHost("acme.com").hasScheme("http").hasPort(8080); - } - - @Test - public void nullScheme() { - assertThatThrownBy(() -> Route.async().id("1").predicate(exchange -> true).uri("/pathonly")) - .isInstanceOf(IllegalArgumentException.class); - } - - @Test - public void defaultMetadataToEmpty() { - Route route = Route.async().id("1").predicate(exchange -> true).uri("http://acme.com:8080").build(); - - assertThat(route.getMetadata()).isEmpty(); - } - - @Test - public void isAbleToAddMetadata() { - Route route = Route.async().id("1").predicate(exchange -> true).uri("http://acme.com:8080") - .metadata(Maps.newHashMap("key", "value")).metadata("key2", "value2").build(); - - assertThat(route.getMetadata()).hasSize(2).containsEntry("key", "value").containsEntry("key2", "value2"); - } - - @Test - public void metadataIsAddedFromDefinition() { - RouteDefinition definition = new RouteDefinition(); - definition.setId("1"); - definition.setUri(URI.create("http://acme.com:8080")); - HashMap metadata = new HashMap<>(); - metadata.put("key", "value"); - metadata.put("key2", "value2"); - definition.setMetadata(metadata); - Route route = Route.async(definition).predicate(exchange -> true).build(); - - assertThat(route.getMetadata()).hasSize(2).containsEntry("key", "value").containsEntry("key2", "value2"); - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/route/builder/GatewayFilterSpecTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/route/builder/GatewayFilterSpecTests.java deleted file mode 100644 index e0f9d507..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/route/builder/GatewayFilterSpecTests.java +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Copyright 2013-2020 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.route.builder; - -import java.util.Collections; - -import org.junit.jupiter.api.Test; -import reactor.core.publisher.Mono; - -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.GatewayFilterChain; -import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter; -import org.springframework.cloud.gateway.filter.OrderedGatewayFilter; -import org.springframework.cloud.gateway.filter.factory.rewrite.ModifyResponseBodyGatewayFilterFactory; -import org.springframework.cloud.gateway.route.Route; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.core.Ordered; -import org.springframework.http.MediaType; -import org.springframework.web.reactive.function.server.HandlerStrategies; -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; - -public class GatewayFilterSpecTests { - - @Test - public void orderedInterfaceRespected() { - testFilter(MyOrderedFilter.class, new MyOrderedFilter(), 1000); - } - - @Test - public void unorderedWithDefaultOrder() { - testFilter(OrderedGatewayFilter.class, new MyUnorderedFilter(), 0); - } - - private void testFilter(Class type, GatewayFilter gatewayFilter, int order) { - ConfigurableApplicationContext context = mock(ConfigurableApplicationContext.class); - Route.AsyncBuilder routeBuilder = Route.async().id("123").uri("abc:123").predicate(exchange -> true); - RouteLocatorBuilder.Builder routes = new RouteLocatorBuilder(context).routes(); - GatewayFilterSpec spec = new GatewayFilterSpec(routeBuilder, routes); - spec.filter(gatewayFilter); - - Route route = routeBuilder.build(); - assertThat(route.getFilters()).hasSize(1); - assertFilter(route.getFilters().get(0), type, order); - } - - private void assertFilter(GatewayFilter filter, Class type, int order) { - assertThat(filter).isInstanceOf(type); - Ordered ordered = (Ordered) filter; - assertThat(ordered.getOrder()).isEqualTo(order); - } - - @Test - public void testFilters() { - ConfigurableApplicationContext context = mock(ConfigurableApplicationContext.class); - Route.AsyncBuilder routeBuilder = Route.async().id("123").uri("abc:123").predicate(exchange -> true); - RouteLocatorBuilder.Builder routes = new RouteLocatorBuilder(context).routes(); - GatewayFilterSpec spec = new GatewayFilterSpec(routeBuilder, routes); - spec.filters(new MyUnorderedFilter(), new MyOrderedFilter()); - - Route route = routeBuilder.build(); - assertThat(route.getFilters()).hasSize(2); - - assertFilter(route.getFilters().get(0), OrderedGatewayFilter.class, 0); - assertFilter(route.getFilters().get(1), MyOrderedFilter.class, 1000); - } - - @Test - public void shouldSetModifyBodyResponseFilterWithRewriteFunction() { - ConfigurableApplicationContext context = mock(ConfigurableApplicationContext.class); - Route.AsyncBuilder routeBuilder = Route.async().id("123").uri("abc:123").predicate(exchange -> true); - - when(context.getBean(ModifyResponseBodyGatewayFilterFactory.class)).thenReturn( - new ModifyResponseBodyGatewayFilterFactory(HandlerStrategies.withDefaults().messageReaders(), - Collections.emptySet(), Collections.emptySet())); - - RouteLocatorBuilder.Builder routes = new RouteLocatorBuilder(context).routes(); - GatewayFilterSpec spec = new GatewayFilterSpec(routeBuilder, routes); - spec.modifyResponseBody(String.class, String.class, (exchange, s) -> Mono.just(s)); - - Route route = routeBuilder.build(); - assertThat(route.getFilters()).hasSize(1); - - assertFilter(route.getFilters().get(0), - ModifyResponseBodyGatewayFilterFactory.ModifyResponseGatewayFilter.class, - NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 1); - } - - @Test - public void shouldSetModifyBodyResponseFilterWithRewriteFunctionAndEmptyBodySupplier() { - ConfigurableApplicationContext context = mock(ConfigurableApplicationContext.class); - Route.AsyncBuilder routeBuilder = Route.async().id("123").uri("abc:123").predicate(exchange -> true); - - when(context.getBean(ModifyResponseBodyGatewayFilterFactory.class)).thenReturn( - new ModifyResponseBodyGatewayFilterFactory(HandlerStrategies.withDefaults().messageReaders(), - Collections.emptySet(), Collections.emptySet())); - - RouteLocatorBuilder.Builder routes = new RouteLocatorBuilder(context).routes(); - GatewayFilterSpec spec = new GatewayFilterSpec(routeBuilder, routes); - spec.modifyResponseBody(String.class, String.class, (exchange, s) -> Mono.just(s == null ? "emptybody" : s)); - - Route route = routeBuilder.build(); - assertThat(route.getFilters()).hasSize(1); - - assertFilter(route.getFilters().get(0), - ModifyResponseBodyGatewayFilterFactory.ModifyResponseGatewayFilter.class, - NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 1); - } - - @Test - public void shouldSetModifyBodyResponseFilterWithRewriteFunctionAndNewContentType() { - ConfigurableApplicationContext context = mock(ConfigurableApplicationContext.class); - Route.AsyncBuilder routeBuilder = Route.async().id("123").uri("abc:123").predicate(exchange -> true); - - when(context.getBean(ModifyResponseBodyGatewayFilterFactory.class)).thenReturn( - new ModifyResponseBodyGatewayFilterFactory(HandlerStrategies.withDefaults().messageReaders(), - Collections.emptySet(), Collections.emptySet())); - - RouteLocatorBuilder.Builder routes = new RouteLocatorBuilder(context).routes(); - GatewayFilterSpec spec = new GatewayFilterSpec(routeBuilder, routes); - spec.modifyResponseBody(String.class, String.class, MediaType.APPLICATION_JSON_VALUE, - (exchange, s) -> Mono.just(s)); - - Route route = routeBuilder.build(); - assertThat(route.getFilters()).hasSize(1); - - assertFilter(route.getFilters().get(0), - ModifyResponseBodyGatewayFilterFactory.ModifyResponseGatewayFilter.class, - NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 1); - } - - @Test - public void shouldSetModifyBodyResponseFilterWithConfigConsumer() { - ConfigurableApplicationContext context = mock(ConfigurableApplicationContext.class); - Route.AsyncBuilder routeBuilder = Route.async().id("123").uri("abc:123").predicate(exchange -> true); - - when(context.getBean(ModifyResponseBodyGatewayFilterFactory.class)).thenReturn( - new ModifyResponseBodyGatewayFilterFactory(HandlerStrategies.withDefaults().messageReaders(), - Collections.emptySet(), Collections.emptySet())); - - RouteLocatorBuilder.Builder routes = new RouteLocatorBuilder(context).routes(); - GatewayFilterSpec spec = new GatewayFilterSpec(routeBuilder, routes); - spec.modifyResponseBody((smth) -> new ModifyResponseBodyGatewayFilterFactory.Config() - .setRewriteFunction(String.class, String.class, (exchange, s) -> Mono.just(s))); - - Route route = routeBuilder.build(); - assertThat(route.getFilters()).hasSize(1); - - assertFilter(route.getFilters().get(0), - ModifyResponseBodyGatewayFilterFactory.ModifyResponseGatewayFilter.class, - NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 1); - } - - protected static class MyOrderedFilter implements GatewayFilter, Ordered { - - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - return Mono.empty(); - } - - @Override - public int getOrder() { - return 1000; - } - - } - - protected static class MyUnorderedFilter implements GatewayFilter { - - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - return Mono.empty(); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/route/builder/RouteBuilderTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/route/builder/RouteBuilderTests.java deleted file mode 100644 index 88d0c881..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/route/builder/RouteBuilderTests.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright 2013-2020 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.route.builder; - -import java.net.URI; -import java.util.Map; - -import org.assertj.core.util.Maps; -import org.junit.jupiter.api.Test; -import reactor.test.StepVerifier; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.gateway.route.RouteLocator; -import org.springframework.context.annotation.Configuration; - -import static org.springframework.cloud.gateway.support.RouteMetadataUtils.CONNECT_TIMEOUT_ATTR; -import static org.springframework.cloud.gateway.support.RouteMetadataUtils.RESPONSE_TIMEOUT_ATTR; - -/** - * @author Biju Kunjummen - */ - -@SpringBootTest -public class RouteBuilderTests { - - @Autowired - private RouteLocatorBuilder routeLocatorBuilder; - - @Test - public void testASetOfRoutes() { - RouteLocator routeLocator = this.routeLocatorBuilder.routes() - .route("test1", - r -> r.host("*.somehost.org").and().path("/somepath") - .filters(f -> f.addRequestHeader("header1", "header-value-1")).uri("http://someuri")) - .route("test2", - r -> r.host("*.somehost2.org") - .filters(f -> f.addResponseHeader("header-response-1", "header-response-1")) - .uri("https://httpbin.org:9090")) - .build(); - - StepVerifier.create(routeLocator.getRoutes()) - .expectNextMatches(r -> r.getId().equals("test1") && r.getFilters().size() == 1 - && r.getUri().equals(URI.create("http://someuri:80"))) - .expectNextMatches(r -> r.getId().equals("test2") && r.getFilters().size() == 1 - && r.getUri().equals(URI.create("https://httpbin.org:9090"))) - .expectComplete().verify(); - } - - @Test - public void testRouteOptionsPropagatedToRoute() { - Map routeMetadata = Maps.newHashMap("key", "value"); - RouteLocator routeLocator = this.routeLocatorBuilder.routes() - .route("test1", - r -> r.host("*.somehost.org").and().path("/somepath") - .filters(f -> f.addRequestHeader("header1", "header-value-1")).metadata("key", "value") - .uri("http://someuri")) - .route("test2", - r -> r.host("*.somehost2.org") - .filters(f -> f.addResponseHeader("header-response-1", "header-response-1")) - .uri("https://httpbin.org:9090")) - .build(); - - StepVerifier.create(routeLocator.getRoutes()) - .expectNextMatches(r -> r.getId().equals("test1") && r.getFilters().size() == 1 - && r.getUri().equals(URI.create("http://someuri:80")) && r.getMetadata().equals(routeMetadata)) - .expectNextMatches(r -> r.getId().equals("test2") && r.getFilters().size() == 1 - && r.getUri().equals(URI.create("https://httpbin.org:9090")) && r.getMetadata().isEmpty()) - .expectComplete().verify(); - } - - @Test - public void testRoutesWithTimeout() { - RouteLocator routeLocator = this.routeLocatorBuilder.routes().route("test1", r -> { - return r.host("*.somehost.org").and().path("/somepath") - .filters(f -> f.addRequestHeader("header1", "header-value-1")).metadata(RESPONSE_TIMEOUT_ATTR, 1) - .metadata(CONNECT_TIMEOUT_ATTR, 1).uri("http://someuri"); - }).route("test2", - r -> r.host("*.somehost2.org") - .filters(f -> f.addResponseHeader("header-response-1", "header-response-1")) - .uri("https://httpbin.org:9090")) - .build(); - - StepVerifier.create(routeLocator.getRoutes()) - .expectNextMatches(r -> r.getId().equals("test1") && r.getFilters().size() == 1 - && r.getUri().equals(URI.create("http://someuri:80")) - && r.getMetadata().get(RESPONSE_TIMEOUT_ATTR).equals(1) - && r.getMetadata().get(CONNECT_TIMEOUT_ATTR).equals(1)) - .expectNextMatches(r -> r.getId().equals("test2") && r.getFilters().size() == 1 - && r.getUri().equals(URI.create("https://httpbin.org:9090"))) - .expectComplete().verify(); - } - - @EnableAutoConfiguration - @Configuration(proxyBeanMethods = false) - public static class SpringConfig { - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/support/ConfigurationServiceTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/support/ConfigurationServiceTests.java deleted file mode 100644 index b84de453..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/support/ConfigurationServiceTests.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright 2013-2020 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.support; - -import java.util.Collections; -import java.util.Map; - -import jakarta.validation.constraints.Max; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -import org.springframework.boot.context.properties.bind.BindException; -import org.springframework.boot.context.properties.bind.Bindable; -import org.springframework.boot.validation.MessageInterpolatorFactory; -import org.springframework.context.support.GenericApplicationContext; -import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; - -import static org.assertj.core.api.Assertions.assertThat; - -public class ConfigurationServiceTests { - - @Test - public void validationOnCreateWorks() { - Map map = Collections.singletonMap("config.value", 11); - - Assertions.assertThrows(BindException.class, () -> ConfigurationService - .bindOrCreate(Bindable.of(ValidatedConfig.class), map, "config", getValidator(), null)); - } - - @Test - public void createWorks() { - Map map = Collections.singletonMap("config.value", 9); - - ValidatedConfig config = ConfigurationService.bindOrCreate(Bindable.of(ValidatedConfig.class), map, "config", - getValidator(), null); - - assertThat(config).isNotNull().extracting(ValidatedConfig::getValue).isEqualTo(9); - } - - @Test - public void validationOnBindWorks() { - Map map = Collections.singletonMap("config.value", 11); - - ValidatedConfig config = new ValidatedConfig(); - - Assertions.assertThrows(BindException.class, () -> ConfigurationService - .bindOrCreate(Bindable.ofInstance(config), map, "config", getValidator(), null)); - - } - - @Test - public void bindWorks() { - Map map = Collections.singletonMap("config.value", 9); - - ValidatedConfig config = new ValidatedConfig(); - ConfigurationService.bindOrCreate(Bindable.ofInstance(config), map, "config", getValidator(), null); - - assertThat(config).isNotNull().extracting(ValidatedConfig::getValue).isEqualTo(9); - } - - LocalValidatorFactoryBean getValidator() { - GenericApplicationContext context = new GenericApplicationContext(); - context.refresh(); - LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean(); - validator.setApplicationContext(context); - validator.setMessageInterpolator(new MessageInterpolatorFactory().getObject()); - validator.afterPropertiesSet(); - return validator; - } - - public static class ValidatedConfig { - - @Max(10) - private int value; - - public int getValue() { - return this.value; - } - - public void setValue(int value) { - this.value = value; - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/support/NameUtilsTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/support/NameUtilsTests.java deleted file mode 100644 index bb4a987c..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/support/NameUtilsTests.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright 2013-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.support; - -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; - -import org.junit.jupiter.api.Test; - -import org.springframework.cloud.gateway.filter.AdaptCachedBodyGlobalFilter; -import org.springframework.cloud.gateway.filter.ForwardPathFilter; -import org.springframework.cloud.gateway.filter.GlobalFilter; -import org.springframework.cloud.gateway.filter.WebsocketRoutingFilter; -import org.springframework.cloud.gateway.filter.factory.AddRequestHeaderGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.DedupeResponseHeaderGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.FallbackHeadersGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.GatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.JsonToGrpcGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.MapRequestHeaderGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.factory.cache.LocalResponseCacheGatewayFilterFactory; -import org.springframework.cloud.gateway.handler.predicate.AfterRoutePredicateFactory; -import org.springframework.cloud.gateway.handler.predicate.CloudFoundryRouteServiceRoutePredicateFactory; -import org.springframework.cloud.gateway.handler.predicate.ReadBodyRoutePredicateFactory; -import org.springframework.cloud.gateway.handler.predicate.RemoteAddrRoutePredicateFactory; -import org.springframework.cloud.gateway.handler.predicate.RoutePredicateFactory; - -import static org.assertj.core.api.Assertions.assertThat; - -class NameUtilsTests { - - @Test - void shouldNormalizePredicatesNames() { - List>> predicates = Arrays.asList(AfterRoutePredicateFactory.class, - CloudFoundryRouteServiceRoutePredicateFactory.class, ReadBodyRoutePredicateFactory.class, - RemoteAddrRoutePredicateFactory.class); - - List resultNames = predicates.stream().map(NameUtils::normalizeRoutePredicateName) - .collect(Collectors.toList()); - - List expectedNames = Arrays.asList("After", "CloudFoundryRouteService", "ReadBody", "RemoteAddr"); - - assertThat(resultNames).isEqualTo(expectedNames); - } - - @Test - void shouldNormalizePredicatesNamesAsProperties() { - List>> predicates = Arrays.asList(AfterRoutePredicateFactory.class, - CloudFoundryRouteServiceRoutePredicateFactory.class, ReadBodyRoutePredicateFactory.class, - RemoteAddrRoutePredicateFactory.class); - - List resultNames = predicates.stream().map(NameUtils::normalizeRoutePredicateNameAsProperty) - .collect(Collectors.toList()); - - List expectedNames = Arrays.asList("after", "cloud-foundry-route-service", "read-body", "remote-addr"); - - assertThat(resultNames).isEqualTo(expectedNames); - } - - @Test - void shouldNormalizeFiltersNames() { - List>> predicates = Arrays.asList( - AddRequestHeaderGatewayFilterFactory.class, DedupeResponseHeaderGatewayFilterFactory.class, - FallbackHeadersGatewayFilterFactory.class, MapRequestHeaderGatewayFilterFactory.class); - - List resultNames = predicates.stream().map(NameUtils::normalizeFilterFactoryName) - .collect(Collectors.toList()); - - List expectedNames = Arrays.asList("AddRequestHeader", "DedupeResponseHeader", "FallbackHeaders", - "MapRequestHeader"); - - assertThat(resultNames).isEqualTo(expectedNames); - } - - @Test - void shouldNormalizeFiltersNamesAsProperties() { - List>> predicates = Arrays.asList( - AddRequestHeaderGatewayFilterFactory.class, DedupeResponseHeaderGatewayFilterFactory.class, - FallbackHeadersGatewayFilterFactory.class, MapRequestHeaderGatewayFilterFactory.class, - JsonToGrpcGatewayFilterFactory.class, LocalResponseCacheGatewayFilterFactory.class); - - List resultNames = predicates.stream().map(NameUtils::normalizeFilterFactoryNameAsProperty) - .collect(Collectors.toList()); - - List expectedNames = Arrays.asList("add-request-header", "dedupe-response-header", "fallback-headers", - "map-request-header", "json-to-grpc", "local-response-cache"); - - assertThat(resultNames).isEqualTo(expectedNames); - } - - @Test - void shouldNormalizeGlobalFiltersNames() { - List> predicates = Arrays.asList(ForwardPathFilter.class, - AdaptCachedBodyGlobalFilter.class, WebsocketRoutingFilter.class); - - List resultNames = predicates.stream().map(NameUtils::normalizeGlobalFilterName) - .collect(Collectors.toList()); - - List expectedNames = Arrays.asList("ForwardPath", "AdaptCachedBody", "WebsocketRouting"); - - assertThat(resultNames).isEqualTo(expectedNames); - } - - @Test - void shouldNormalizeGlobalFiltersNamesAsProperties() { - List> predicates = Arrays.asList(ForwardPathFilter.class, - AdaptCachedBodyGlobalFilter.class, WebsocketRoutingFilter.class); - - List resultNames = predicates.stream().map(NameUtils::normalizeGlobalFilterNameAsProperty) - .collect(Collectors.toList()); - - List expectedNames = Arrays.asList("forward-path", "adapt-cached-body", "websocket-routing"); - - assertThat(resultNames).isEqualTo(expectedNames); - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/support/ServerWebExchangeUtilsTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/support/ServerWebExchangeUtilsTests.java deleted file mode 100644 index 80862695..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/support/ServerWebExchangeUtilsTests.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright 2013-2020 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.support; - -import java.nio.charset.StandardCharsets; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import org.assertj.core.api.Assertions; -import org.junit.Test; - -import org.springframework.core.io.buffer.DataBuffer; -import org.springframework.core.io.buffer.DefaultDataBuffer; -import org.springframework.http.HttpMethod; -import org.springframework.mock.http.server.reactive.MockServerHttpRequest; -import org.springframework.mock.web.server.MockServerWebExchange; -import org.springframework.web.reactive.function.server.HandlerStrategies; -import org.springframework.web.reactive.function.server.ServerRequest; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.CACHED_REQUEST_BODY_ATTR; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.expand; - -public class ServerWebExchangeUtilsTests { - - @Test - public void expandWorks() { - HashMap vars = new HashMap<>(); - vars.put("foo", "bar"); - vars.put("baz", "bam"); - - MockServerWebExchange exchange = mockExchange(vars); - - String expanded = expand(exchange, "my-{foo}-{baz}"); - assertThat(expanded).isEqualTo("my-bar-bam"); - - expanded = expand(exchange, "my-noop"); - assertThat(expanded).isEqualTo("my-noop"); - } - - @Test - public void missingVarThrowsException() { - MockServerWebExchange exchange = mockExchange(Collections.emptyMap()); - Assertions.assertThatThrownBy(() -> expand(exchange, "my-{foo}-{baz}")) - .isInstanceOf(IllegalArgumentException.class); - } - - @Test - public void defaultDataBufferHandling() { - MockServerWebExchange exchange = mockExchange(Collections.emptyMap()); - exchange.getAttributes().put(CACHED_REQUEST_BODY_ATTR, "foo"); - - ServerWebExchangeUtils - .cacheRequestBodyAndRequest(exchange, - (serverHttpRequest) -> ServerRequest - .create(exchange.mutate().request(serverHttpRequest).build(), - HandlerStrategies.withDefaults().messageReaders()) - .bodyToMono(DefaultDataBuffer.class)) - .block(); - } - - @Test - public void duplicatedCachingDataBufferHandling() { - MockServerWebExchange exchange = mockExchange(HttpMethod.POST, Collections.emptyMap()); - DataBuffer dataBufferBeforeCaching = exchange.getResponse().bufferFactory() - .wrap("Cached buffer".getBytes(StandardCharsets.UTF_8)); - exchange.getAttributes().put(CACHED_REQUEST_BODY_ATTR, dataBufferBeforeCaching); - - ServerWebExchangeUtils - .cacheRequestBodyAndRequest(exchange, - (serverHttpRequest) -> ServerRequest - .create(exchange.mutate().request(serverHttpRequest).build(), - HandlerStrategies.withDefaults().messageReaders()) - .bodyToMono(DefaultDataBuffer.class)) - .block(); - - DataBuffer dataBufferAfterCached = exchange.getAttribute(CACHED_REQUEST_BODY_ATTR); - - Assertions.assertThat(dataBufferBeforeCaching).isEqualTo(dataBufferAfterCached); - } - - private MockServerWebExchange mockExchange(Map vars) { - return mockExchange(HttpMethod.GET, vars); - } - - private MockServerWebExchange mockExchange(HttpMethod method, Map vars) { - - MockServerHttpRequest request = null; - if (HttpMethod.GET.equals(method)) { - request = MockServerHttpRequest.get("/get").build(); - } - else if (HttpMethod.POST.equals(method)) { - request = MockServerHttpRequest.post("/post").body("post body"); - } - - Assertions.assertThat(request).as("Method was not one of GET or POST").isNotNull(); - - MockServerWebExchange exchange = MockServerWebExchange.from(request); - ServerWebExchangeUtils.putUriTemplateVariables(exchange, vars); - return exchange; - } - -} 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 deleted file mode 100644 index c4cf3c80..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/support/ShortcutConfigurableNonRestrictiveTests.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright 2013-2020 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.support; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.gateway.support.ShortcutConfigurable.ShortcutType; -import org.springframework.context.annotation.Bean; -import org.springframework.core.env.ConfigurableEnvironment; -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") -public class ShortcutConfigurableNonRestrictiveTests { - - @Autowired - BeanFactory beanFactory; - - @Autowired - ConfigurableEnvironment env; - - private SpelExpressionParser parser; - - @Test - public void testNormalizeDefaultTypeWithSpelAndPropertyReferenceEnabled() { - parser = new SpelExpressionParser(); - ShortcutConfigurable shortcutConfigurable = new ShortcutConfigurable() { - @Override - public List shortcutFieldOrder() { - return Arrays.asList("bean", "arg1"); - } - }; - Map args = new HashMap<>(); - args.put("barproperty", "#{@bar.getInt}"); - args.put("arg1", "val1"); - Map map = ShortcutType.DEFAULT.normalize(args, shortcutConfigurable, parser, this.beanFactory); - assertThat(map).isNotNull().containsEntry("barproperty", 42).containsEntry("arg1", "val1"); - } - - @Test - public void testNormalizeDefaultTypeWithSpelAndMethodReferenceEnabled() { - parser = new SpelExpressionParser(); - ShortcutConfigurable shortcutConfigurable = new ShortcutConfigurable() { - @Override - public List shortcutFieldOrder() { - return Arrays.asList("bean", "arg1"); - } - }; - Map args = new HashMap<>(); - args.put("barmethod", "#{@bar.myMethod}"); - args.put("arg1", "val1"); - Map map = ShortcutType.DEFAULT.normalize(args, shortcutConfigurable, parser, this.beanFactory); - assertThat(map).isNotNull().containsEntry("barmethod", 42).containsEntry("arg1", "val1"); - } - - @SpringBootConfiguration - protected static class TestConfig { - - @Bean - public Integer foo() { - return 42; - } - - @Bean - public Bar bar() { - return new Bar(); - } - - } - - protected static class Bar { - - public int getInt() { - return 42; - } - - public int myMethod() { - return 42; - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/support/ShortcutConfigurableTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/support/ShortcutConfigurableTests.java deleted file mode 100644 index 597de9ec..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/support/ShortcutConfigurableTests.java +++ /dev/null @@ -1,212 +0,0 @@ -/* - * Copyright 2013-2020 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.support; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.gateway.support.ShortcutConfigurable.ShortcutType; -import org.springframework.context.annotation.Bean; -import org.springframework.core.env.ConfigurableEnvironment; -import org.springframework.expression.spel.SpelEvaluationException; -import org.springframework.expression.spel.standard.SpelExpressionParser; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -@SpringBootTest -public class ShortcutConfigurableTests { - - @Autowired - BeanFactory beanFactory; - - @Autowired - ConfigurableEnvironment env; - - private SpelExpressionParser parser; - - @Test - public void testNormalizeDefaultTypeWithSpelAndInvalidInputFails() { - parser = new SpelExpressionParser(); - ShortcutConfigurable shortcutConfigurable = new ShortcutConfigurable() { - @Override - public List shortcutFieldOrder() { - return Arrays.asList("bean", "arg1"); - } - }; - Map args = new HashMap<>(); - args.put("bean", "#{T(java.lang.Runtime).getRuntime().exec(\"touch /tmp/x\")}"); - args.put("arg1", "val1"); - assertThatThrownBy(() -> { - ShortcutType.DEFAULT.normalize(args, shortcutConfigurable, parser, this.beanFactory); - }).isInstanceOf(SpelEvaluationException.class); - } - - @Test - public void testNormalizeDefaultTypeWithSpelAndInvalidPropertyReferenceFails() { - parser = new SpelExpressionParser(); - ShortcutConfigurable shortcutConfigurable = new ShortcutConfigurable() { - @Override - public List shortcutFieldOrder() { - return Arrays.asList("bean", "arg1"); - } - }; - Map args = new HashMap<>(); - args.put("barproperty", "#{@bar.getInt}"); - args.put("arg1", "val1"); - assertThatThrownBy(() -> { - ShortcutType.DEFAULT.normalize(args, shortcutConfigurable, parser, this.beanFactory); - }).isInstanceOf(SpelEvaluationException.class); - } - - @Test - public void testNormalizeDefaultTypeWithSpelAndInvalidMethodReferenceFails() { - parser = new SpelExpressionParser(); - ShortcutConfigurable shortcutConfigurable = new ShortcutConfigurable() { - @Override - public List shortcutFieldOrder() { - return Arrays.asList("bean", "arg1"); - } - }; - Map args = new HashMap<>(); - args.put("barmethod", "#{@bar.myMethod}"); - args.put("arg1", "val1"); - assertThatThrownBy(() -> { - ShortcutType.DEFAULT.normalize(args, shortcutConfigurable, parser, this.beanFactory); - }).isInstanceOf(SpelEvaluationException.class); - } - - @Test - public void testNormalizeDefaultTypeWithSpel() { - parser = new SpelExpressionParser(); - ShortcutConfigurable shortcutConfigurable = new ShortcutConfigurable() { - @Override - public List shortcutFieldOrder() { - return Arrays.asList("bean", "arg1"); - } - }; - Map args = new HashMap<>(); - args.put("bean", "#{@foo}"); - args.put("arg1", "val1"); - Map map = ShortcutType.DEFAULT.normalize(args, shortcutConfigurable, parser, this.beanFactory); - assertThat(map).isNotNull().containsEntry("bean", 42).containsEntry("arg1", "val1"); - } - - @Test - @SuppressWarnings("unchecked") - public void testNormalizeGatherListTypeWithSpel() { - parser = new SpelExpressionParser(); - ShortcutConfigurable shortcutConfigurable = new ShortcutConfigurable() { - @Override - public List shortcutFieldOrder() { - return Arrays.asList("values"); - } - - @Override - public ShortcutType shortcutType() { - return ShortcutType.GATHER_LIST; - } - }; - Map args = new HashMap<>(); - args.put("1", "#{@foo}"); - args.put("2", "val1"); - args.put("3", "val2"); - Map map = ShortcutType.GATHER_LIST.normalize(args, shortcutConfigurable, parser, - this.beanFactory); - assertThat(map).isNotNull().containsKey("values"); - assertThat((List) map.get("values")).containsExactly(42, "val1", "val2"); - } - - @Test - public void testNormalizeGatherListTailFlagFlagExists() { - assertListTailFlag(true); - } - - @Test - public void testNormalizeGatherListTailFlagFlagMissing() { - assertListTailFlag(false); - } - - @SuppressWarnings("unchecked") - private void assertListTailFlag(boolean hasTailFlag) { - parser = new SpelExpressionParser(); - ShortcutConfigurable shortcutConfigurable = new ShortcutConfigurable() { - @Override - public List shortcutFieldOrder() { - return Arrays.asList("values", "flag"); - } - - @Override - public ShortcutType shortcutType() { - return ShortcutType.GATHER_LIST_TAIL_FLAG; - } - }; - Map args = new HashMap<>(); - args.put("1", "val0"); - args.put("2", "val1"); - args.put("3", "val2"); - if (hasTailFlag) { - args.put("4", "false"); - } - Map map = ShortcutType.GATHER_LIST_TAIL_FLAG.normalize(args, shortcutConfigurable, parser, - this.beanFactory); - assertThat(map).isNotNull().containsKey("values"); - assertThat((List) map.get("values")).containsExactly("val0", "val1", "val2"); - if (hasTailFlag) { - assertThat(map.get("flag")).isEqualTo("false"); - } - else { - assertThat(map).doesNotContainKeys("flag"); - } - } - - @SpringBootConfiguration - protected static class TestConfig { - - @Bean - public Integer foo() { - return 42; - } - - @Bean - public Bar bar() { - return new Bar(); - } - - } - - protected static class Bar { - - public int getInt() { - return 42; - } - - public int myMethod() { - return 42; - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/support/ipresolver/XForwardedRemoteAddressResolverTest.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/support/ipresolver/XForwardedRemoteAddressResolverTest.java deleted file mode 100644 index 8b3d41a9..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/support/ipresolver/XForwardedRemoteAddressResolverTest.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright 2013-2020 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.support.ipresolver; - -import java.net.InetSocketAddress; - -import org.junit.jupiter.api.Test; - -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; - -public class XForwardedRemoteAddressResolverTest { - - private final InetSocketAddress remote0000Address = InetSocketAddress.createUnresolved("0.0.0.0", 1234); - - private final XForwardedRemoteAddressResolver trustOne = XForwardedRemoteAddressResolver.maxTrustedIndex(1); - - private final XForwardedRemoteAddressResolver trustAll = XForwardedRemoteAddressResolver.trustAll(); - - @Test - public void maxIndexOneReturnsLastForwardedIp() { - ServerWebExchange exchange = buildExchange(oneTwoThreeBuilder()); - - InetSocketAddress address = trustOne.resolve(exchange); - - assertThat(address.getHostName()).isEqualTo("0.0.0.3"); - } - - @Test - public void maxIndexOneFallsBackToRemoteIp() { - ServerWebExchange exchange = buildExchange(remoteAddressOnlyBuilder()); - - InetSocketAddress address = trustOne.resolve(exchange); - - assertThat(address.getHostName()).isEqualTo("0.0.0.0"); - } - - @Test - public void maxIndexOneReturnsNullIfNoForwardedOrRemoteIp() { - ServerWebExchange exchange = buildExchange(emptyBuilder()); - - InetSocketAddress address = trustOne.resolve(exchange); - - assertThat(address).isEqualTo(null); - } - - @Test - public void trustOneFallsBackOnEmptyHeader() { - ServerWebExchange exchange = buildExchange(remoteAddressOnlyBuilder().header("X-Forwarded-For", "")); - - InetSocketAddress address = trustOne.resolve(exchange); - - assertThat(address.getHostName()).isEqualTo("0.0.0.0"); - - } - - @Test - public void trustOneFallsBackOnMultipleHeaders() { - ServerWebExchange exchange = buildExchange( - remoteAddressOnlyBuilder().header("X-Forwarded-For", "0.0.0.1").header("X-Forwarded-For", "0.0.0.2")); - - InetSocketAddress address = trustOne.resolve(exchange); - - assertThat(address.getHostName()).isEqualTo("0.0.0.0"); - } - - @Test - public void trustAllReturnsFirstForwardedIp() { - ServerWebExchange exchange = buildExchange(oneTwoThreeBuilder()); - - InetSocketAddress address = trustAll.resolve(exchange); - - assertThat(address.getHostName()).isEqualTo("0.0.0.1"); - } - - @Test - public void trustAllFinalFallsBackToRemoteIp() { - ServerWebExchange exchange = buildExchange(remoteAddressOnlyBuilder()); - - InetSocketAddress address = trustAll.resolve(exchange); - - assertThat(address.getHostName()).isEqualTo("0.0.0.0"); - } - - @Test - public void trustAllReturnsNullIfNoForwardedOrRemoteIp() { - ServerWebExchange exchange = buildExchange(emptyBuilder()); - - InetSocketAddress address = trustAll.resolve(exchange); - - assertThat(address).isEqualTo(null); - } - - @Test - public void trustAllFallsBackOnEmptyHeader() { - ServerWebExchange exchange = buildExchange(remoteAddressOnlyBuilder().header("X-Forwarded-For", "")); - - InetSocketAddress address = trustAll.resolve(exchange); - - assertThat(address.getHostName()).isEqualTo("0.0.0.0"); - - } - - @Test - public void trustAllFallsBackOnMultipleHeaders() { - ServerWebExchange exchange = buildExchange( - remoteAddressOnlyBuilder().header("X-Forwarded-For", "0.0.0.1").header("X-Forwarded-For", "0.0.0.2")); - - InetSocketAddress address = trustAll.resolve(exchange); - - assertThat(address.getHostName()).isEqualTo("0.0.0.0"); - } - - private MockServerHttpRequest.BaseBuilder emptyBuilder() { - return MockServerHttpRequest.get("someUrl"); - } - - private MockServerHttpRequest.BaseBuilder remoteAddressOnlyBuilder() { - return MockServerHttpRequest.get("someUrl").remoteAddress(remote0000Address); - } - - private MockServerHttpRequest.BaseBuilder oneTwoThreeBuilder() { - return MockServerHttpRequest.get("someUrl").remoteAddress(remote0000Address).header("X-Forwarded-For", - "0.0.0.1,0.0.0.2, 0.0.0.3"); - } - - private ServerWebExchange buildExchange(MockServerHttpRequest.BaseBuilder requestBuilder) { - return MockServerWebExchange.from(requestBuilder.build()); - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/support/tagsprovider/GatewayHttpTagsProviderTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/support/tagsprovider/GatewayHttpTagsProviderTests.java deleted file mode 100644 index 1faae05c..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/support/tagsprovider/GatewayHttpTagsProviderTests.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2013-2020 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.support.tagsprovider; - -import io.micrometer.core.instrument.Tags; -import org.junit.jupiter.api.Test; - -import org.springframework.http.server.reactive.ServerHttpResponseDecorator; -import org.springframework.mock.http.server.reactive.MockServerHttpRequest; -import org.springframework.mock.http.server.reactive.MockServerHttpResponse; -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.http.HttpStatus.OK; - -/** - * @author Ingyu Hwang - */ -public class GatewayHttpTagsProviderTests { - - private final GatewayHttpTagsProvider tagsProvider = new GatewayHttpTagsProvider(); - - private static final String ROUTE_URI = "http://gatewaytagsprovider.org:80"; - - private static final Tags DEFAULT_TAGS = Tags.of("outcome", OK.series().name(), "status", OK.name(), - "httpStatusCode", String.valueOf(OK.value()), "httpMethod", "GET"); - - @Test - public void httpTags() { - ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get(ROUTE_URI).build()); - exchange.getResponse().setStatusCode(OK); - - Tags tags = tagsProvider.apply(exchange); - assertThat(tags).isEqualTo(DEFAULT_TAGS); - } - - @Test - public void statusNotChanged() { - ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get(ROUTE_URI).build()); - - Tags tags = tagsProvider.apply(exchange); - assertThat(tags).isEqualTo( - Tags.of("outcome", "CUSTOM", "status", "CUSTOM", "httpStatusCode", "NA", "httpMethod", "GET")); - } - - @Test - public void notAbstractServerHttpResponse() { - ServerWebExchange mockExchange = mock(ServerWebExchange.class); - ServerHttpResponseDecorator responseDecorator = new ServerHttpResponseDecorator(new MockServerHttpResponse()); - responseDecorator.setStatusCode(OK); - - when(mockExchange.getRequest()).thenReturn(MockServerHttpRequest.get(ROUTE_URI).build()); - when(mockExchange.getResponse()).thenReturn(responseDecorator); - - Tags tags = tagsProvider.apply(mockExchange); - assertThat(tags).isEqualTo(DEFAULT_TAGS); - } - -} 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 deleted file mode 100644 index 0b8f550d..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/support/tagsprovider/GatewayPathTagsProviderTests.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright 2013-2020 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.support.tagsprovider; - -import java.util.Collections; -import java.util.List; - -import io.micrometer.core.instrument.Tags; -import org.junit.jupiter.api.Test; - -import org.springframework.cloud.gateway.handler.predicate.HostRoutePredicateFactory; -import org.springframework.cloud.gateway.handler.predicate.MethodRoutePredicateFactory; -import org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory; -import org.springframework.cloud.gateway.route.Route; -import org.springframework.http.HttpMethod; -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.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_PREDICATE_MATCHED_PATH_ATTR; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_PREDICATE_MATCHED_PATH_ROUTE_ID_ATTR; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR; - -/** - * @author Marta Medio - * @author Alberto C. Ríos - */ -public class GatewayPathTagsProviderTests { - - private final GatewayPathTagsProvider pathTagsProvider = new GatewayPathTagsProvider(); - - private static final String ROUTE_URI = "http://gatewaytagsprovider.org:80"; - - @Test - void addPathToRoutes() { - List pathList = Collections.singletonList("/git/**"); - - PathRoutePredicateFactory.Config pathConfig = new PathRoutePredicateFactory.Config().setPatterns(pathList); - HostRoutePredicateFactory.Config hostConfig = new HostRoutePredicateFactory.Config() - .setPatterns(Collections.singletonList("**.myhost.com")); - Route route = Route.async().id("git").uri(ROUTE_URI).predicate(new PathRoutePredicateFactory().apply(pathConfig) - .and(new HostRoutePredicateFactory().apply(hostConfig))).build(); - - ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get(ROUTE_URI).build()); - exchange.getAttributes().put(GATEWAY_ROUTE_ATTR, route); - exchange.getAttributes().put(GATEWAY_PREDICATE_MATCHED_PATH_ATTR, pathList.get(0)); - exchange.getAttributes().put(GATEWAY_PREDICATE_MATCHED_PATH_ROUTE_ID_ATTR, route.getId()); - - Tags tags = pathTagsProvider.apply(exchange); - assertThat(tags.stream().count()).isEqualTo(1); - assertThat(tags.stream().anyMatch(tag -> "path".equals(tag.getKey()) && tag.getValue().equals(pathList.get(0)))) - .isEqualTo(true); - } - - @Test - void addsMultiplePathToRoutes() { - List pathList = Collections.singletonList("/git/**"); - List pathList2 = Collections.singletonList("/git2/**"); - - PathRoutePredicateFactory.Config pathConfig = new PathRoutePredicateFactory.Config().setPatterns(pathList); - PathRoutePredicateFactory.Config pathConfig2 = new PathRoutePredicateFactory.Config().setPatterns(pathList2); - Route route = Route.async().id("git").uri(ROUTE_URI).predicate(new PathRoutePredicateFactory().apply(pathConfig) - .or(new PathRoutePredicateFactory().apply(pathConfig2))).build(); - - ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get(ROUTE_URI).build()); - exchange.getAttributes().put(GATEWAY_ROUTE_ATTR, route); - exchange.getAttributes().put(GATEWAY_PREDICATE_MATCHED_PATH_ATTR, pathList2.get(0)); - exchange.getAttributes().put(GATEWAY_PREDICATE_MATCHED_PATH_ROUTE_ID_ATTR, route.getId()); - - Tags tags = pathTagsProvider.apply(exchange); - assertThat(tags.stream().count()).isEqualTo(1); - assertThat( - tags.stream().anyMatch(tag -> "path".equals(tag.getKey()) && tag.getValue().equals(pathList2.get(0)))) - .isEqualTo(true); - } - - @Test - void ignoreRoutesWithoutPath() { - MethodRoutePredicateFactory.Config config = new MethodRoutePredicateFactory.Config(); - config.setMethods(HttpMethod.GET); - Route route = Route.async().id("empty").uri(ROUTE_URI) - .predicate(new MethodRoutePredicateFactory().apply(config)).build(); - - ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get(ROUTE_URI).build()); - exchange.getAttributes().put(GATEWAY_ROUTE_ATTR, route); - - Tags tags = pathTagsProvider.apply(exchange); - assertThat(tags.stream().count()).isEqualTo(0); - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/support/tagsprovider/GatewayRouteTagsProviderTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/support/tagsprovider/GatewayRouteTagsProviderTests.java deleted file mode 100644 index 053b487a..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/support/tagsprovider/GatewayRouteTagsProviderTests.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2013-2020 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.support.tagsprovider; - -import io.micrometer.core.instrument.Tags; -import org.junit.jupiter.api.Test; - -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.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR; - -/** - * @author Ingyu Hwang - */ -public class GatewayRouteTagsProviderTests { - - private final GatewayRouteTagsProvider tagsProvider = new GatewayRouteTagsProvider(); - - private static final String ROUTE_URI = "http://gatewaytagsprovider.org:80"; - - private static final String ROUTE_ID = "test-route"; - - private static final Route ROUTE = Route.async().id(ROUTE_ID).uri(ROUTE_URI).predicate(swe -> true).build(); - - private static final Tags DEFAULT_TAGS = Tags.of("routeId", ROUTE_ID, "routeUri", ROUTE_URI); - - @Test - public void routeTags() { - ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get(ROUTE_URI).build()); - exchange.getAttributes().put(GATEWAY_ROUTE_ATTR, ROUTE); - - Tags tags = tagsProvider.apply(exchange); - assertThat(tags).isEqualTo(DEFAULT_TAGS); - } - - @Test - public void emptyRoute() { - ServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get(ROUTE_URI).build()); - - Tags tags = tagsProvider.apply(exchange); - assertThat(tags).isEqualTo(Tags.empty()); - - } - -} 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 deleted file mode 100644 index 6af386c1..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/support/tagsprovider/PropertiesTagsProviderTests.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2013-2020 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.support.tagsprovider; - -import io.micrometer.core.instrument.Tags; -import org.junit.jupiter.api.Test; - -import org.springframework.boot.autoconfigure.AutoConfigurations; -import org.springframework.boot.test.context.runner.ApplicationContextRunner; -import org.springframework.cloud.gateway.config.GatewayMetricsAutoConfiguration; -import org.springframework.mock.http.server.reactive.MockServerHttpRequest; -import org.springframework.mock.web.server.MockServerWebExchange; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Ingyu Hwang - */ -public class PropertiesTagsProviderTests { - - private final ApplicationContextRunner contextRunner = new ApplicationContextRunner(); - - @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") - .run(context -> { - PropertiesTagsProvider provider = context.getBean(PropertiesTagsProvider.class); - Tags tags = provider.apply(MockServerWebExchange.from(MockServerHttpRequest.get("").build())); - assertThat(tags).isEqualTo(Tags.of("foo1", "bar1", "foo2", "bar2")); - }); - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/AdhocTestSuite.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/AdhocTestSuite.java deleted file mode 100644 index 5d1bf14f..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/AdhocTestSuite.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright 2013-2020 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.test; - -import org.junit.ClassRule; -import org.junit.rules.TestRule; -import org.junit.runner.Description; -import org.junit.runner.RunWith; -import org.junit.runners.Suite; -import org.junit.runners.Suite.SuiteClasses; -import org.junit.runners.model.Statement; - -import org.springframework.cloud.gateway.filter.GatewayMetricsFilterTests; -import org.springframework.cloud.gateway.filter.factory.RewriteLocationResponseHeaderGatewayFilterFactoryTests; - -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.is; -import static org.junit.Assume.assumeThat; - -/** - * To run this suite in an IDE, set env var GATEWAY_ADHOC_ENABLED=true in test runner. - * - * @author Spencer Gibb - */ -@RunWith(Suite.class) -@SuiteClasses({ org.springframework.cloud.gateway.handler.RoutePredicateHandlerMappingIntegrationTests.class, - org.springframework.cloud.gateway.handler.predicate.AfterRoutePredicateFactoryTests.class, - org.springframework.cloud.gateway.handler.predicate.CloudFoundryRouteServiceRoutePredicateFactoryIntegrationTests.class, - org.springframework.cloud.gateway.handler.predicate.HostRoutePredicateFactoryTests.class, - org.springframework.cloud.gateway.handler.predicate.BetweenRoutePredicateFactoryIntegrationTests.class, - org.springframework.cloud.gateway.handler.predicate.CookieRoutePredicateFactoryTests.class, - org.springframework.cloud.gateway.handler.predicate.MethodRoutePredicateFactoryTests.class, - org.springframework.cloud.gateway.handler.predicate.BetweenRoutePredicateFactoryTests.class, - org.springframework.cloud.gateway.handler.predicate.QueryRoutePredicateFactoryTests.class, - org.springframework.cloud.gateway.handler.predicate.WeightRoutePredicateFactoryIntegrationTests.class, - org.springframework.cloud.gateway.handler.predicate.HeaderRoutePredicateFactoryTests.class, - org.springframework.cloud.gateway.handler.predicate.BeforeRoutePredicateFactoryTests.class, - org.springframework.cloud.gateway.handler.predicate.RemoteAddrRoutePredicateFactoryTests.class, - org.springframework.cloud.gateway.handler.predicate.ReadBodyRoutePredicateFactoryTests.class, - org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactoryTests.class, - org.springframework.cloud.gateway.handler.predicate.CloudFoundryRouteServiceRoutePredicateFactoryTest.class, - org.springframework.cloud.gateway.handler.RoutePredicateHandlerMappingTests.class, - org.springframework.cloud.gateway.filter.factory.RewriteResponseHeaderGatewayFilterFactoryTests.class, - org.springframework.cloud.gateway.filter.factory.SecureHeadersGatewayFilterFactoryTests.class, - org.springframework.cloud.gateway.filter.factory.RedirectToGatewayFilterFactoryTests.class, - org.springframework.cloud.gateway.filter.factory.AddRequestParameterGatewayFilterFactoryTests.class, - org.springframework.cloud.gateway.filter.factory.RemoveRequestHeaderGatewayFilterFactoryTests.class, - org.springframework.cloud.gateway.filter.factory.SetPathGatewayFilterFactoryTests.class, - org.springframework.cloud.gateway.filter.factory.PrefixPathGatewayFilterFactoryTest.class, - org.springframework.cloud.gateway.filter.factory.RewriteResponseHeaderGatewayFilterFactoryUnitTests.class, - RewriteLocationResponseHeaderGatewayFilterFactoryTests.class, - org.springframework.cloud.gateway.filter.factory.RequestRateLimiterGatewayFilterFactoryTests.class, - org.springframework.cloud.gateway.filter.factory.RequestHeaderToRequestUriGatewayFilterFactoryIntegrationTests.class, - org.springframework.cloud.gateway.filter.factory.RemoveResponseHeaderGatewayFilterFactoryTests.class, - org.springframework.cloud.gateway.filter.factory.RewritePathGatewayFilterFactoryTests.class, - org.springframework.cloud.gateway.filter.factory.StripPrefixGatewayFilterFactoryIntegrationTests.class, - org.springframework.cloud.gateway.filter.factory.RetryGatewayFilterFactoryIntegrationTests.class, - org.springframework.cloud.gateway.filter.factory.SetRequestHeaderGatewayFilterFactoryTests.class, - org.springframework.cloud.gateway.filter.factory.StripPrefixGatewayFilterFactoryTests.class, - org.springframework.cloud.gateway.filter.factory.RequestHeaderToRequestUriGatewayFilterFactoryTests.class, - org.springframework.cloud.gateway.filter.factory.RequestSizeGatewayFilterFactoryTest.class, - org.springframework.cloud.gateway.filter.factory.SetStatusGatewayFilterFactoryTests.class, - org.springframework.cloud.gateway.filter.factory.SaveSessionGatewayFilterFactoryTests.class, - org.springframework.cloud.gateway.filter.factory.SetPathGatewayFilterFactoryIntegrationTests.class, - org.springframework.cloud.gateway.filter.factory.PreserveHostHeaderGatewayFilterFactoryTests.class, - org.springframework.cloud.gateway.filter.factory.RewritePathGatewayFilterFactoryIntegrationTests.class, - org.springframework.cloud.gateway.filter.factory.AddRequestHeaderGatewayFilterFactoryTests.class, - org.springframework.cloud.gateway.filter.factory.SetResponseHeaderGatewayFilterFactoryTests.class, - org.springframework.cloud.gateway.filter.factory.CacheRequestBodyGatewayFilterFactoryTests.class, - org.springframework.cloud.gateway.filter.factory.rewrite.ModifyResponseBodyGatewayFilterFactoryTests.class, - org.springframework.cloud.gateway.filter.WeightCalculatorWebFilterTests.class, - org.springframework.cloud.gateway.filter.RouteToRequestUrlFilterTests.class, - org.springframework.cloud.gateway.filter.headers.ForwardedHeadersFilterTests.class, - org.springframework.cloud.gateway.filter.headers.XForwardedHeadersFilterTests.class, - org.springframework.cloud.gateway.filter.headers.HttpStatusInResponseHeadersFilterTests.class, - org.springframework.cloud.gateway.filter.headers.HttpHeadersFilterMixedTypeTests.class, - org.springframework.cloud.gateway.filter.headers.HttpHeadersFilterTests.class, - org.springframework.cloud.gateway.filter.headers.NonStandardHeadersInResponseTests.class, - org.springframework.cloud.gateway.filter.headers.RemoveHopByHopHeadersFilterTests.class, - org.springframework.cloud.gateway.filter.WebsocketRoutingFilterTests.class, - org.springframework.cloud.gateway.filter.ratelimit.PrincipalNameKeyResolverIntegrationTests.class, - org.springframework.cloud.gateway.filter.ratelimit.RedisRateLimiterConfigTests.class, - org.springframework.cloud.gateway.filter.ratelimit.RedisRateLimiterTests.class, - org.springframework.cloud.gateway.filter.NettyRoutingFilterIntegrationTests.class, - GatewayMetricsFilterTests.class, org.springframework.cloud.gateway.filter.ForwardRoutingFilterTests.class, - org.springframework.cloud.gateway.route.RouteDefinitionRouteLocatorTests.class, - org.springframework.cloud.gateway.route.RouteTests.class, - org.springframework.cloud.gateway.route.CachingRouteLocatorTests.class, - org.springframework.cloud.gateway.route.RouteRefreshListenerTests.class, - org.springframework.cloud.gateway.route.builder.RouteDslTests.class, - org.springframework.cloud.gateway.route.builder.RouteBuilderTests.class, - org.springframework.cloud.gateway.route.builder.GatewayFilterSpecTests.class, - org.springframework.cloud.gateway.route.CachingRouteDefinitionLocatorTests.class, - org.springframework.cloud.gateway.actuate.GatewayControllerEndpointTests.class, - org.springframework.cloud.gateway.config.GatewayAutoConfigurationTests.class, - org.springframework.cloud.gateway.discovery.DiscoveryClientRouteDefinitionLocatorTests.class, - org.springframework.cloud.gateway.discovery.ReactiveGatewayDiscoveryClientAutoConfigurationTests.class, - org.springframework.cloud.gateway.discovery.DiscoveryClientRouteDefinitionLocatorIntegrationTests.class, - org.springframework.cloud.gateway.support.ShortcutConfigurableTests.class, - org.springframework.cloud.gateway.support.ipresolver.XForwardedRemoteAddressResolverTest.class, - org.springframework.cloud.gateway.cors.CorsGlobalTests.class, - org.springframework.cloud.gateway.cors.CorsPerRouteTests.class, - org.springframework.cloud.gateway.test.FormIntegrationTests.class, - org.springframework.cloud.gateway.test.ForwardTests.class, - org.springframework.cloud.gateway.test.PostTests.class, - org.springframework.cloud.gateway.test.ssl.SingleCertSSLTests.class, - org.springframework.cloud.gateway.test.ssl.MultiCertSSLTests.class, - org.springframework.cloud.gateway.test.ssl.SSLHandshakeTimeoutTests.class, - org.springframework.cloud.gateway.test.websocket.WebSocketIntegrationTests.class, - org.springframework.cloud.gateway.test.WebfluxNotIncludedTests.class, - org.springframework.cloud.gateway.test.HttpStatusTests.class, - org.springframework.cloud.gateway.test.GatewayIntegrationTests.class, - org.springframework.cloud.gateway.test.sse.SseIntegrationTests.class }) -public class AdhocTestSuite { - - @ClassRule - public static AdhocEnabled adhocEnabled = new AdhocEnabled(); - - static class AdhocEnabled implements TestRule { - - @Override - public Statement apply(Statement base, Description description) { - assumeThat("Adhoc Tests ignored", System.getenv("GATEWAY_ADHOC_ENABLED"), is(equalTo("true"))); - - return base; - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/BaseWebClientTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/BaseWebClientTests.java deleted file mode 100644 index e078d923..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/BaseWebClientTests.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright 2013-2020 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.test; - -import java.time.Duration; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.junit.jupiter.api.BeforeEach; -import reactor.core.publisher.Mono; - -import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.web.server.LocalServerPort; -import org.springframework.cloud.client.DefaultServiceInstance; -import org.springframework.cloud.gateway.filter.GatewayFilterChain; -import org.springframework.cloud.gateway.filter.GlobalFilter; -import org.springframework.cloud.gateway.route.Route; -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.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.core.annotation.Order; -import org.springframework.http.client.reactive.ClientHttpConnector; -import org.springframework.http.client.reactive.ReactorClientHttpConnector; -import org.springframework.test.web.reactive.server.WebTestClient; -import org.springframework.web.reactive.function.client.WebClient; -import org.springframework.web.server.ServerWebExchange; - -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_HANDLER_MAPPER_ATTR; -import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR; - -/** - * @author Spencer Gibb - */ -public class BaseWebClientTests { - - protected static final String HANDLER_MAPPER_HEADER = "X-Gateway-Handler-Mapper-Class"; - - protected static final String ROUTE_ID_HEADER = "X-Gateway-RouteDefinition-Id"; - - protected static final Duration DURATION = Duration.ofSeconds(5); - - public static final String SERVICE_ID = "testservice"; - - @LocalServerPort - protected int port = 0; - - protected WebTestClient testClient; - - protected WebClient webClient; - - protected String baseUri; - - @BeforeEach - public void setup() throws Exception { - setup(new ReactorClientHttpConnector(), "http://localhost:" + port); - } - - protected void setup(ClientHttpConnector httpConnector, String baseUri) { - this.baseUri = baseUri; - this.webClient = WebClient.builder().clientConnector(httpConnector).baseUrl(this.baseUri).build(); - this.testClient = WebTestClient.bindToServer(httpConnector).baseUrl(this.baseUri).build(); - } - - @Configuration(proxyBeanMethods = false) - @LoadBalancerClient(name = "testservice", configuration = TestLoadBalancerConfig.class) - @Import(PermitAllSecurityConfiguration.class) - public static class DefaultTestConfig { - - private static final Log log = LogFactory.getLog(DefaultTestConfig.class); - - @Bean - public HttpBinCompatibleController httpBinController() { - return new HttpBinCompatibleController(); - } - - @Bean - public RecursiveHttpbinFilter recursiveHttpbinFilter() { - return new RecursiveHttpbinFilter(); - } - - @Bean - @Order(500) - public GlobalFilter modifyResponseFilter() { - return (exchange, chain) -> { - log.info("modifyResponseFilter start"); - String value = exchange.getAttributeOrDefault(GATEWAY_HANDLER_MAPPER_ATTR, "N/A"); - if (!exchange.getResponse().isCommitted()) { - exchange.getResponse().getHeaders().add(HANDLER_MAPPER_HEADER, value); - } - Route route = exchange.getAttributeOrDefault(GATEWAY_ROUTE_ATTR, null); - if (route != null) { - if (!exchange.getResponse().isCommitted()) { - exchange.getResponse().getHeaders().add(ROUTE_ID_HEADER, route.getId()); - } - } - return chain.filter(exchange); - }; - } - - } - - public static class RecursiveHttpbinFilter implements GlobalFilter { - - @Override - public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { - if (exchange.getRequest().getPath().toString().contains("httpbin/httpbin")) { - return Mono.error(new IllegalStateException("recursive call to /httpbin")); - } - return chain.filter(exchange); - } - - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(DefaultTestConfig.class) - public static class MainConfig { - - } - - public static class TestLoadBalancerConfig { - - @LocalServerPort - protected int port = 0; - - @Bean - public ServiceInstanceListSupplier staticServiceInstanceListSupplier() { - return ServiceInstanceListSuppliers.from(SERVICE_ID, - new DefaultServiceInstance(SERVICE_ID + "-1", SERVICE_ID, "localhost", port, false)); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/CustomBlockHoundIntegration.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/CustomBlockHoundIntegration.java deleted file mode 100644 index c7ad30aa..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/CustomBlockHoundIntegration.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright 2013-2020 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.test; - -import reactor.blockhound.BlockHound; -import reactor.blockhound.integration.BlockHoundIntegration; - -/** - * @author Tim Ysewyn - */ -public class CustomBlockHoundIntegration implements BlockHoundIntegration { - - @Override - public void applyTo(BlockHound.Builder builder) { - // builder.blockingMethodCallback(it -> { - // Error error = new Error(it.toString()); - // error.printStackTrace(); - // throw error; - // }); - - // Uses Unsafe#park - builder.allowBlockingCallsInside("reactor.core.scheduler.SchedulerTask", "dispose"); - - // Uses - // ch.qos.logback.classic.spi.PackagingDataCalculator#getImplementationVersion - builder.allowBlockingCallsInside( - "org.springframework.boot.autoconfigure.web.reactive.error.AbstractErrorWebExceptionHandler", - "logError"); - builder.allowBlockingCallsInside("reactor.util.Loggers$Slf4JLogger", "debug"); - builder.allowBlockingCallsInside("reactor.util.Loggers$Slf4JLogger", "info"); - builder.allowBlockingCallsInside("reactor.util.Loggers$Slf4JLogger", "error"); - - // Uses org.springframework.util.JdkIdGenerator#generateId - // Uses UUID#randomUUID - builder.allowBlockingCallsInside("org.springframework.web.server.session.InMemoryWebSessionStore", - "lambda$createWebSession$0"); - - // Uses java.util.Random#nextInt - builder.allowBlockingCallsInside("org.springframework.util.MimeTypeUtils", "generateMultipartBoundary"); - - // SPRING DATA REDIS RELATED - - // Uses Unsafe#park - builder.allowBlockingCallsInside("org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory", - "getReactiveConnection"); - - // NETTY RELATED - - // Uses Thread#sleep - builder.allowBlockingCallsInside("io.netty.channel.nio.NioEventLoop", "handleLoopException"); - builder.allowBlockingCallsInside("io.netty.util.concurrent.SingleThreadEventExecutor", "confirmShutdown"); - - // Uses Unsafe#park - builder.allowBlockingCallsInside("io.netty.util.concurrent.GlobalEventExecutor", "execute"); - builder.allowBlockingCallsInside("io.netty.util.concurrent.SingleThreadEventExecutor$6", "run"); - // builder.allowBlockingCallsInside("io.netty.util.concurrent.GlobalEventExecutor", - // "takeTask"); - // builder.allowBlockingCallsInside("io.netty.util.concurrent.GlobalEventExecutor", - // "addTask"); - builder.allowBlockingCallsInside("io.netty.util.concurrent.FastThreadLocalRunnable", "run"); - - // SECURITY RELATED - - // For HTTPS traffic - builder.allowBlockingCallsInside("io.netty.handler.ssl.SslHandler", "channelActive"); - builder.allowBlockingCallsInside("io.netty.handler.ssl.SslHandler", "channelInactive"); - builder.allowBlockingCallsInside("io.netty.handler.ssl.SslHandler", "unwrap"); - builder.allowBlockingCallsInside("io.netty.handler.ssl.SslContext", "newClientContextInternal"); - - // Uses org.springframework.security.crypto.bcrypt.BCrypt#gensalt - // Uses java.security.SecureRandom#nextBytes - builder.allowBlockingCallsInside( - "org.springframework.security.authentication.AbstractUserDetailsReactiveAuthenticationManager", - "lambda$authenticate$4"); - - // Uses java.io.RandomAccessFile#readBytes - builder.allowBlockingCallsInside("org.springframework.context.annotation.ConfigurationClassParser", "parse"); - builder.allowBlockingCallsInside( - "org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader", "loadBeanDefinitions"); - builder.allowBlockingCallsInside("org.springframework.core.type.classreading.SimpleMetadataReader", - "getClassReader"); - - builder.allowBlockingCallsInside("io.micrometer.context.ContextRegistry", "loadContextAccessors"); - builder.allowBlockingCallsInside("io.micrometer.context.ContextRegistry", "loadThreadLocalAccessors"); - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/CustomBlockHoundIntegrationTest.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/CustomBlockHoundIntegrationTest.java deleted file mode 100644 index 69558c90..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/CustomBlockHoundIntegrationTest.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2013-2020 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.test; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.DisabledForJreRange; -import org.junit.jupiter.api.condition.JRE; -import reactor.core.publisher.Mono; -import reactor.core.scheduler.Schedulers; - -/** - * @author Tim Ysewyn - */ -public class CustomBlockHoundIntegrationTest { - - @Test - @DisabledForJreRange(min = JRE.JAVA_16) - public void shouldThrowErrorForBlockingCallWithCustomBlockHoundIntegration() { - Assertions.assertThrows(RuntimeException.class, () -> Mono.fromCallable(() -> { - Thread.sleep(1); - return null; - }).subscribeOn(Schedulers.parallel()).block()); - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/FormIntegrationTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/FormIntegrationTests.java deleted file mode 100644 index 9a105d6d..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/FormIntegrationTests.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright 2013-2020 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.test; - -import java.nio.charset.StandardCharsets; -import java.util.Map; - -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.web.client.TestRestTemplate; -import org.springframework.context.annotation.Import; -import org.springframework.core.io.ClassPathResource; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.http.client.MultipartBodyBuilder; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; -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; -import static org.springframework.cloud.gateway.test.TestUtils.getMap; -import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED; -import static org.springframework.http.MediaType.MULTIPART_FORM_DATA; - -@SpringBootTest(webEnvironment = RANDOM_PORT) -@DirtiesContext -@SuppressWarnings("unchecked") -class FormIntegrationTests extends BaseWebClientTests { - - public static final MediaType FORM_URL_ENCODED_CONTENT_TYPE = new MediaType(APPLICATION_FORM_URLENCODED, - StandardCharsets.UTF_8); - - @Test - void formUrlencodedWorks() { - LinkedMultiValueMap formData = new LinkedMultiValueMap<>(); - formData.add("foo", "bar"); - formData.add("baz", "bam"); - - // @formatter:off - testClient.post().uri("/post").contentType(FORM_URL_ENCODED_CONTENT_TYPE) - .body(BodyInserters.fromFormData(formData)) - .exchange() - .expectStatus().isOk() - .expectBody(Map.class).consumeWith(result -> { - Map map = result.getResponseBody(); - Map form = getMap(map, "form"); - assertThat(form).containsEntry("foo", "bar"); - assertThat(form).containsEntry("baz", "bam"); - }); - // @formatter:on - } - - @Test - void multipartFormDataWorksWebClient() { - MultiValueMap> formData = createMultipartData(); - - // @formatter:off - testClient.post().uri("/post").contentType(MULTIPART_FORM_DATA) - .bodyValue(formData) - .exchange() - .expectStatus().isOk() - .expectBody(Map.class) - .consumeWith(result -> assertMultipartData(result.getResponseBody())); - // @formatter:on - } - - @Test - void multipartFormDataWorksRestTemplate() { - MultiValueMap> formData = createMultipartData(); - TestRestTemplate rest = new TestRestTemplate(); - - ResponseEntity response = rest.postForEntity(baseUri + "/post", formData, Map.class); - - assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); - assertMultipartData(response.getBody()); - } - - private MultiValueMap> createMultipartData() { - ClassPathResource part = new ClassPathResource("1x1.png"); - MultipartBodyBuilder builder = new MultipartBodyBuilder(); - builder.part("imgpart", part, MediaType.IMAGE_PNG); - return builder.build(); - } - - private void assertMultipartData(Map responseBody) { - Map files = getMap(responseBody, "files"); - assertThat(files).containsKey("imgpart"); - String file = (String) files.get("imgpart"); - assertThat(file).startsWith("data:").contains(";base64,"); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(DefaultTestConfig.class) - static class TestConfig { - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/ForwardTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/ForwardTests.java deleted file mode 100644 index 8e169961..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/ForwardTests.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2013-2020 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.test; - -import java.util.Collections; -import java.util.Map; - -import org.junit.jupiter.api.BeforeEach; -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.web.server.LocalServerPort; -import org.springframework.context.annotation.Import; -import org.springframework.http.HttpHeaders; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.web.reactive.server.WebTestClient; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; - -@SpringBootTest(webEnvironment = RANDOM_PORT) -@DirtiesContext -@SuppressWarnings("unchecked") -public class ForwardTests { - - @LocalServerPort - protected int port = 0; - - protected WebTestClient client; - - @BeforeEach - public void setup() { - String baseUri = "http://localhost:" + port; - this.client = WebTestClient.bindToServer().baseUrl(baseUri).build(); - } - - @Test - public void forwardWorks() { - this.client.get().uri("/localcontroller").header(HttpHeaders.HOST, "www.forward.org").exchange().expectStatus() - .isOk().expectBody().json("{\"from\":\"localcontroller\"}"); - } - - @Test - public void forwardWithCorrectPath() { - this.client.get().uri("/foo").header(HttpHeaders.HOST, "www.forward.org").exchange().expectStatus().isOk() - .expectBody().json("{\"from\":\"localcontroller\"}"); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @RestController - @Import(PermitAllSecurityConfiguration.class) - public static class TestConfig { - - @GetMapping("/httpbin/localcontroller") - public Map localController() { - return Collections.singletonMap("from", "localcontroller"); - } - - } - -} 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 deleted file mode 100644 index 711f916c..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/GatewayIntegrationTests.java +++ /dev/null @@ -1,193 +0,0 @@ -/* - * Copyright 2013-2020 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.test; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.hamcrest.BaseMatcher; -import org.hamcrest.Description; -import org.junit.jupiter.api.Test; -import reactor.core.publisher.Mono; - -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.config.GatewayProperties; -import org.springframework.cloud.gateway.filter.GlobalFilter; -import org.springframework.cloud.gateway.filter.WeightCalculatorWebFilter; -import org.springframework.cloud.gateway.filter.headers.ForwardedHeadersFilter; -import org.springframework.cloud.gateway.filter.headers.XForwardedHeadersFilter; -import org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping; -import org.springframework.cloud.gateway.route.CachingRouteLocator; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationListener; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; -import org.springframework.context.support.AbstractApplicationContext; -import org.springframework.core.annotation.AnnotationAwareOrderComparator; -import org.springframework.core.annotation.Order; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.server.ServerWebExchange; - -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) -@DirtiesContext -@SuppressWarnings("unchecked") -class GatewayIntegrationTests extends BaseWebClientTests { - - @Autowired - private GatewayProperties properties; - - @Autowired - private ApplicationContext context; - - @Test - void listenersInOrder() { - assertThat(context).isInstanceOf(AbstractApplicationContext.class); - AbstractApplicationContext ctxt = (AbstractApplicationContext) context; - List> applicationListeners = new ArrayList<>(ctxt.getApplicationListeners()); - AnnotationAwareOrderComparator.sort(applicationListeners); - int weightFilterIndex = applicationListeners.indexOf(context.getBean(WeightCalculatorWebFilter.class)); - int routeLocatorIndex = applicationListeners.indexOf(context.getBean(CachingRouteLocator.class)); - assertThat(weightFilterIndex > routeLocatorIndex).as("CachingRouteLocator is after WeightCalculatorWebFilter") - .isTrue(); - } - - @Test - void complexContentTypeWorks() { - testClient.post().uri("/headers").contentType(MediaType.APPLICATION_JSON).bodyValue("testdata") - .header("Host", "www.complexcontenttype.org").exchange().expectStatus().isOk().expectBody(Map.class) - .consumeWith(result -> { - Map headers = getMap(result.getResponseBody(), "headers"); - assertThat(headers).containsEntry(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); - }); - } - - @Test - void forwardedHeadersWork() { - testClient.get().uri("/headers").exchange().expectStatus().isOk().expectBody(Map.class).consumeWith(result -> { - Map headers = getMap(result.getResponseBody(), "headers"); - assertThat(headers).containsKeys(ForwardedHeadersFilter.FORWARDED_HEADER, - XForwardedHeadersFilter.X_FORWARDED_FOR_HEADER, XForwardedHeadersFilter.X_FORWARDED_HOST_HEADER, - XForwardedHeadersFilter.X_FORWARDED_PORT_HEADER, XForwardedHeadersFilter.X_FORWARDED_PROTO_HEADER); - assertThat(headers.get(ForwardedHeadersFilter.FORWARDED_HEADER)).asString().contains("proto=http") - .contains("host=\"localhost:").contains("for=\"127.0.0.1:"); - assertThat(headers.get(XForwardedHeadersFilter.X_FORWARDED_HOST_HEADER)).asString() - .isEqualTo("localhost:" + this.port); - assertThat(headers.get(XForwardedHeadersFilter.X_FORWARDED_PORT_HEADER)).asString() - .isEqualTo("" + this.port); - assertThat(headers.get(XForwardedHeadersFilter.X_FORWARDED_PROTO_HEADER)).asString().isEqualTo("http"); - }); - } - - @Test - void compositeRouteWorks() { - testClient.get().uri("/headers?foo=bar&baz").header("Host", "www.foo.org").header("X-Request-Id", "123") - .cookie("chocolate", "chip").exchange().expectStatus().isOk().expectHeader() - .valueEquals(HANDLER_MAPPER_HEADER, RoutePredicateHandlerMapping.class.getSimpleName()).expectHeader() - .valueEquals(ROUTE_ID_HEADER, "host_foo_path_headers_to_httpbin").expectHeader() - .valueEquals("X-Response-Foo", "Bar"); - } - - @Test - void defaultFiltersWorks() { - assertThat(this.properties.getDefaultFilters()).isNotEmpty(); - - testClient.get().uri("/headers").header("Host", "www.addresponseheader.org").exchange().expectStatus().isOk() - .expectHeader().valueEquals("X-Response-Default-Foo", "Default-Bar").returnResult(Object.class) - .consumeWith(result -> { - HttpHeaders httpHeaders = result.getResponseHeaders(); - assertThat(httpHeaders.get("X-Response-Default-Foo")).hasSize(1); - }); - } - - @Test - void loadBalancerFilterWorks() { - testClient.get().uri("/get").header("Host", "www.loadbalancerclient.org").exchange().expectStatus().isOk() - .expectHeader().valueEquals(ROUTE_ID_HEADER, "load_balancer_client_test"); - } - - @Test - void loadBalancerFilterNoClientWorks() { - testClient.get().uri("/get").header("Host", "www.loadbalancerclientempty.org").exchange().expectStatus() - .value(new BaseMatcher() { - @Override - public boolean matches(Object item) { - if (Integer.class.isInstance(item)) { - Integer toMatch = (Integer) item; - return toMatch.intValue() == 503; - } - return false; - } - - @Override - public void describeTo(Description description) { - description.appendText("Expected 503"); - } - }); - } - - @Test - // gh-374 no content type/empty body causes NPR in NettyRoutingFilter - void noContentType() { - testClient.get().uri("/nocontenttype").exchange().expectStatus().is2xxSuccessful(); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(DefaultTestConfig.class) - @RestController - static class TestConfig { - - private static final Log log = LogFactory.getLog(TestConfig.class); - - private static Mono postFilterWork(ServerWebExchange exchange) { - log.info("postFilterWork"); - exchange.getResponse().getHeaders().add("X-Post-Header", "AddedAfterRoute"); - return Mono.empty(); - } - - @GetMapping("/httpbin/nocontenttype") - ResponseEntity nocontenttype() { - return ResponseEntity.status(204).build(); - } - - @Bean - @Order(-1) - GlobalFilter postFilter() { - return (exchange, chain) -> { - log.info("postFilter start"); - return chain.filter(exchange).then(postFilterWork(exchange)); - }; - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/GatewayTestApplication.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/GatewayTestApplication.java deleted file mode 100644 index 76b1ed9e..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/GatewayTestApplication.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2013-2020 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.test; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.cloud.client.discovery.EnableDiscoveryClient; -import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient; -import org.springframework.cloud.gateway.discovery.DiscoveryClientRouteDefinitionLocator; -import org.springframework.cloud.gateway.discovery.DiscoveryLocatorProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.context.annotation.Profile; - -@SpringBootConfiguration -@EnableAutoConfiguration -@Import(PermitAllSecurityConfiguration.class) -public class GatewayTestApplication { - - public static void main(String[] args) { - SpringApplication.run(GatewayTestApplication.class, args); - } - - /* - * TO test run `spring cloud configserver eureka`, then run this app with - * `--spring.profiles.active=discovery` should be able to hit - * http://localhost:8008/configserver/foo/default a normal configserver api - */ - @Configuration(proxyBeanMethods = false) - @EnableDiscoveryClient - @Profile("discovery") - protected static class GatewayDiscoveryConfiguration { - - @Bean - public DiscoveryClientRouteDefinitionLocator discoveryClientRouteLocator( - ReactiveDiscoveryClient discoveryClient, DiscoveryLocatorProperties properties) { - return new DiscoveryClientRouteDefinitionLocator(discoveryClient, properties); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/HttpBinCompatibleController.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/HttpBinCompatibleController.java deleted file mode 100644 index 7c6ff443..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/HttpBinCompatibleController.java +++ /dev/null @@ -1,228 +0,0 @@ -/* - * Copyright 2013-2020 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.test; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.time.Duration; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import java.util.zip.GZIPOutputStream; - -import com.fasterxml.jackson.databind.ObjectMapper; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -import org.springframework.core.io.buffer.DataBuffer; -import org.springframework.core.io.buffer.DataBufferFactory; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.http.codec.multipart.FilePart; -import org.springframework.http.codec.multipart.Part; -import org.springframework.http.server.reactive.ServerHttpResponse; -import org.springframework.util.FileCopyUtils; -import org.springframework.util.MultiValueMap; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PatchMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestHeader; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.server.ServerWebExchange; - -@RestController -@RequestMapping("/httpbin") -public class HttpBinCompatibleController { - - private static final Log log = LogFactory.getLog(HttpBinCompatibleController.class); - - private static final String HEADER_REQ_VARY = "X-Request-Vary"; - - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); - - @GetMapping("/") - public String home() { - return "httpbin compatible home"; - } - - @RequestMapping(path = "/headers", method = { RequestMethod.GET, RequestMethod.POST }, - produces = MediaType.APPLICATION_JSON_VALUE) - public Map headers(ServerWebExchange exchange) { - Map result = new HashMap<>(); - result.put("headers", getHeaders(exchange)); - return result; - } - - @PatchMapping("/headers") - public ResponseEntity> headersPatch(ServerWebExchange exchange, - @RequestBody Map headersToAdd) { - Map result = new HashMap<>(); - result.put("headers", getHeaders(exchange)); - ResponseEntity.BodyBuilder responseEntity = ResponseEntity.status(HttpStatus.OK); - headersToAdd.forEach(responseEntity::header); - - return responseEntity.body(result); - } - - @RequestMapping(path = "/multivalueheaders", method = { RequestMethod.GET, RequestMethod.POST }, - produces = MediaType.APPLICATION_JSON_VALUE) - public Map multiValueHeaders(ServerWebExchange exchange) { - Map result = new HashMap<>(); - result.put("headers", exchange.getRequest().getHeaders()); - return result; - } - - @GetMapping(path = "/delay/{sec}/**", produces = MediaType.APPLICATION_JSON_VALUE) - public Mono> delay(ServerWebExchange exchange, @PathVariable int sec) - throws InterruptedException { - int delay = Math.min(sec, 10); - return Mono.just(get(exchange)).delayElement(Duration.ofSeconds(delay)); - } - - @GetMapping(path = "/anything/{anything}", produces = MediaType.APPLICATION_JSON_VALUE) - public Map anything(ServerWebExchange exchange, @PathVariable(required = false) String anything) { - return get(exchange); - } - - @GetMapping(path = "/get", produces = MediaType.APPLICATION_JSON_VALUE) - public Map get(ServerWebExchange exchange) { - if (log.isDebugEnabled()) { - log.debug("httpbin /get"); - } - HashMap result = new HashMap<>(); - HashMap params = new HashMap<>(); - exchange.getRequest().getQueryParams().forEach((name, values) -> { - params.put(name, values.get(0)); - }); - result.put("args", params); - result.put("headers", getHeaders(exchange)); - return result; - } - - @PostMapping(value = "/post", consumes = MediaType.MULTIPART_FORM_DATA_VALUE, - produces = MediaType.APPLICATION_JSON_VALUE) - public Mono> postFormData(@RequestBody Mono> parts) { - // StringDecoder decoder = StringDecoder.allMimeTypes(true); - return parts.flux().flatMap(map -> Flux.fromIterable(map.values())).flatMap(Flux::fromIterable) - .filter(part -> part instanceof FilePart).reduce(new HashMap(), (files, part) -> { - MediaType contentType = part.headers().getContentType(); - long contentLength = part.headers().getContentLength(); - // TODO: get part data - files.put(part.name(), "data:" + contentType + ";base64," + contentLength); - return files; - }).map(files -> Collections.singletonMap("files", files)); - } - - @PostMapping(path = "/post", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE, - produces = MediaType.APPLICATION_JSON_VALUE) - public Mono> postUrlEncoded(ServerWebExchange exchange) throws IOException { - return post(exchange, null); - } - - @PostMapping(path = "/post", produces = MediaType.APPLICATION_JSON_VALUE) - public Mono> post(ServerWebExchange exchange, @RequestBody(required = false) String body) - throws IOException { - HashMap ret = new HashMap<>(); - ret.put("headers", getHeaders(exchange)); - ret.put("data", body); - HashMap form = new HashMap<>(); - ret.put("form", form); - - return exchange.getFormData().flatMap(map -> { - for (Map.Entry> entry : map.entrySet()) { - for (String value : entry.getValue()) { - form.put(entry.getKey(), value); - } - } - return Mono.just(ret); - }); - } - - @GetMapping("/status/{status}") - public ResponseEntity status(@PathVariable int status) { - return ResponseEntity.status(status).body("Failed with " + status); - } - - @RequestMapping(value = "/responseheaders/{status}", method = { RequestMethod.GET, RequestMethod.POST }) - public ResponseEntity> responseHeaders(@PathVariable int status, ServerWebExchange exchange) { - HttpHeaders httpHeaders = exchange.getRequest().getHeaders().entrySet().stream() - .filter(entry -> entry.getKey().startsWith("X-Test-")) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, - (list1, list2) -> Stream.concat(list1.stream(), list2.stream()).collect(Collectors.toList()), - HttpHeaders::new)); - - return ResponseEntity.status(status).headers(httpHeaders).body(Collections.singletonMap("status", status)); - } - - @PostMapping(path = "/post/empty", produces = MediaType.APPLICATION_JSON_VALUE) - public Mono emptyResponse() { - return Mono.empty(); - } - - @GetMapping(path = "/gzip", produces = MediaType.APPLICATION_JSON_VALUE) - public Mono gzip(ServerWebExchange exchange) throws IOException { - if (log.isDebugEnabled()) { - log.debug("httpbin /gzip"); - } - - String jsonResponse = OBJECT_MAPPER.writeValueAsString("httpbin compatible home"); - byte[] bytes = jsonResponse.getBytes(StandardCharsets.UTF_8); - - ServerHttpResponse response = exchange.getResponse(); - response.getHeaders().add(HttpHeaders.CONTENT_ENCODING, "gzip"); - DataBufferFactory dataBufferFactory = response.bufferFactory(); - response.setStatusCode(HttpStatus.OK); - - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - GZIPOutputStream is = new GZIPOutputStream(bos); - FileCopyUtils.copy(bytes, is); - - byte[] gzippedResponse = bos.toByteArray(); - DataBuffer wrap = dataBufferFactory.wrap(gzippedResponse); - return response.writeWith(Flux.just(wrap)); - } - - @GetMapping("/vary-on-header/**") - public ResponseEntity> varyOnAccept(ServerWebExchange exchange, - @RequestHeader(name = HEADER_REQ_VARY, required = false) String headerToVary) { - if (headerToVary == null) { - return ResponseEntity.badRequest().body(Map.of("error", HEADER_REQ_VARY + " header is mandatory")); - } - else { - var builder = ResponseEntity.ok(); - builder.varyBy(headerToVary); - return builder.body(headers(exchange)); - } - } - - public Map getHeaders(ServerWebExchange exchange) { - return exchange.getRequest().getHeaders().toSingleValueMap(); - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/HttpStatusTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/HttpStatusTests.java deleted file mode 100644 index 87fd38ea..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/HttpStatusTests.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright 2013-2020 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.test; - -import java.util.Map; - -import org.junit.jupiter.api.Test; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.web.client.TestRestTemplate; -import org.springframework.context.annotation.Import; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; - -@SpringBootTest(webEnvironment = RANDOM_PORT) -@DirtiesContext -@SuppressWarnings("unchecked") -public class HttpStatusTests extends BaseWebClientTests { - - public static void main(String[] args) { - new SpringApplication(TestConfig.class).run(args); - } - - @Test - void notFoundResponseWorks() { - testClient.get().uri("/status/404").exchange().expectStatus().isEqualTo(HttpStatus.NOT_FOUND) - .expectBody(String.class).isEqualTo("Failed with 404"); - } - - @Test - void nonStandardCodeWorks() { - ResponseEntity response = new TestRestTemplate().getForEntity(baseUri + "/status/432", String.class); - assertThat(response.getStatusCodeValue()).isEqualTo(432); - assertThat(response.getBody()).isEqualTo("Failed with 432"); - - /* - * testClient.get() .uri("/status/432") .exchange() .expectStatus().isEqualTo(432) - * .expectBody(String.class).isEqualTo("Failed with 432"); - */ - } - - @Test - void serverErrorResponseWorks() { - testClient.get().uri("/status/500").exchange().expectStatus().isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR) - .expectBody(String.class).isEqualTo("Failed with 500"); - } - - @Test - void normalErrorPageWorks() { - testClient.get().uri("/exception").exchange().expectStatus().isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR) - .expectBody(Map.class) - .consumeWith(result -> assertThat(result.getResponseBody()).hasSizeGreaterThanOrEqualTo(5) - .containsKeys("timestamp", "path", "status", "error", "message")); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @RestController - @Import(DefaultTestConfig.class) - static class TestConfig { - - @GetMapping("/httpbin/exception") - String exception() { - throw new RuntimeException("an error"); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/PermitAllSecurityConfiguration.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/PermitAllSecurityConfiguration.java deleted file mode 100644 index 78594f8d..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/PermitAllSecurityConfiguration.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2013-2020 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.test; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.web.server.ServerHttpSecurity; -import org.springframework.security.web.server.SecurityWebFilterChain; - -@Configuration(proxyBeanMethods = false) -public class PermitAllSecurityConfiguration { - - @Bean - SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http) throws Exception { - return http.authorizeExchange().anyExchange().permitAll().and().csrf().disable().build(); - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/PostTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/PostTests.java deleted file mode 100644 index acc260a6..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/PostTests.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2013-2020 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.test; - -import java.util.Map; - -import org.junit.jupiter.api.Test; -import reactor.core.publisher.Mono; -import reactor.test.StepVerifier; - -import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.annotation.Import; -import org.springframework.test.annotation.DirtiesContext; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; - -@SpringBootTest(webEnvironment = RANDOM_PORT) -@DirtiesContext -@SuppressWarnings("unchecked") -// TODO: why does this have to be in a separate test? -class PostTests extends BaseWebClientTests { - - @Test - void postWorks() { - Mono result = webClient.post().uri("/post").header("Host", "www.example.org").bodyValue("testdata") - .retrieve().bodyToMono(Map.class); - - StepVerifier.create(result).consumeNextWith(map -> assertThat(map).containsEntry("data", "testdata")) - .expectComplete().verify(DURATION); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(DefaultTestConfig.class) - static class TestConfig { - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/RouteConstructionIntegrationTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/RouteConstructionIntegrationTests.java deleted file mode 100644 index 13f25d1a..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/RouteConstructionIntegrationTests.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2013-2020 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.test; - -import org.junit.jupiter.api.Test; - -import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.builder.SpringApplicationBuilder; -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; -import org.springframework.context.annotation.Bean; - -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -public class RouteConstructionIntegrationTests { - - @Test - public void routesWithVerificationShouldFail() { - assertThatThrownBy(() -> new SpringApplicationBuilder(TestConfig.class).profiles("verification-route") - .run("--server.port=0")).hasMessageContaining("Stop right now!"); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - public static class TestConfig { - - @Bean - public TestFilterGatewayFilterFactory testFilterGatewayFilterFactory() { - return new TestFilterGatewayFilterFactory(); - } - - } - - public static class TestFilterGatewayFilterFactory - extends AbstractGatewayFilterFactory { - - public TestFilterGatewayFilterFactory() { - super(Config.class); - } - - @Override - public GatewayFilter apply(Config config) { - throw new AssertionError("Stop right now!"); - } - - public static class Config { - - private String arg1; - - public String getArg1() { - return arg1; - } - - public void setArg1(String arg1) { - this.arg1 = arg1; - } - - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/TestUtils.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/TestUtils.java deleted file mode 100644 index 9c453218..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/TestUtils.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2013-2020 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.test; - -import java.util.Map; - -import org.springframework.http.HttpStatusCode; -import org.springframework.web.reactive.function.client.ClientResponse; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Spencer Gibb - */ -public class TestUtils { - - @SuppressWarnings("unchecked") - public static Map getMap(Map response, String key) { - assertThat(response).containsKey(key).isInstanceOf(Map.class); - return (Map) response.get(key); - } - - public static void assertStatus(ClientResponse response, HttpStatusCode status) { - HttpStatusCode statusCode = response.statusCode(); - assertThat(statusCode).isEqualTo(status); - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/WebfluxNotIncludedTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/WebfluxNotIncludedTests.java deleted file mode 100644 index 2f6f61f7..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/WebfluxNotIncludedTests.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2013-2020 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.test; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.cloud.test.ClassPathExclusions; -import org.springframework.cloud.test.ModifiedClassPathRunner; - -@RunWith(ModifiedClassPathRunner.class) -@ClassPathExclusions({ "spring-webflux-*.jar" }) -public class WebfluxNotIncludedTests { - - @Test - public void noWebfluxWorks() { - new SpringApplication(Config.class).run(); - } - - @SpringBootConfiguration - @EnableAutoConfiguration - public static class Config { - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/sse/SseIntegrationTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/sse/SseIntegrationTests.java deleted file mode 100644 index 1cd3a243..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/sse/SseIntegrationTests.java +++ /dev/null @@ -1,281 +0,0 @@ -/* - * Copyright 2013-2020 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.test.sse; - -import java.time.Duration; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import reactor.core.publisher.Flux; -import reactor.test.StepVerifier; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.builder.SpringApplicationBuilder; -import org.springframework.cloud.gateway.route.RouteLocator; -import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; -import org.springframework.cloud.gateway.test.PermitAllSecurityConfiguration; -import org.springframework.cloud.gateway.test.support.HttpServer; -import org.springframework.cloud.gateway.test.support.ReactorHttpServer; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.core.ParameterizedTypeReference; -import org.springframework.core.ResolvableType; -import org.springframework.core.env.ConfigurableEnvironment; -import org.springframework.http.codec.ServerSentEvent; -import org.springframework.http.server.reactive.HttpHandler; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.reactive.DispatcherHandler; -import org.springframework.web.reactive.config.EnableWebFlux; -import org.springframework.web.reactive.function.client.WebClient; -import org.springframework.web.server.adapter.WebHttpHandlerBuilder; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.core.ResolvableType.forClassWithGenerics; -import static org.springframework.http.MediaType.TEXT_EVENT_STREAM; - -/** - * @author Sebastien Deleuze - */ -public class SseIntegrationTests { - - public HttpServer server; - - protected Log logger = LogFactory.getLog(getClass()); - - protected int serverPort; - - private AnnotationConfigApplicationContext wac; - - private WebClient webClient; - - private ConfigurableApplicationContext gatewayContext; - - private int gatewayPort; - - /** - * Return an interval stream of with n number of ticks and buffer the emissions to - * avoid back pressure failures (e.g. on slow CI server). - */ - public static Flux interval(Duration period, int count) { - return Flux.interval(period).take(count).onBackpressureBuffer(2); - } - - @BeforeEach - public void setup() throws Exception { - this.server = new ReactorHttpServer(); - this.server.setHandler(createHttpHandler()); - this.server.afterPropertiesSet(); - this.server.start(); - - // Set dynamically chosen port - this.serverPort = this.server.getPort(); - logger.info("SSE Port: " + this.serverPort); - - this.gatewayContext = new SpringApplicationBuilder(GatewayConfig.class) - .properties("sse.server.port:" + this.serverPort, "server.port=0", "spring.jmx.enabled=false").run(); - - ConfigurableEnvironment env = this.gatewayContext.getBean(ConfigurableEnvironment.class); - this.gatewayPort = Integer.valueOf(env.getProperty("local.server.port")); - - this.webClient = WebClient.create("http://localhost:" + this.gatewayPort + "/sse"); - - logger.info("Gateway Port: " + this.gatewayPort); - } - - @AfterEach - public void tearDown() throws Exception { - this.server.stop(); - this.serverPort = 0; - this.gatewayPort = 0; - this.gatewayContext.close(); - this.wac.close(); - } - - private HttpHandler createHttpHandler() { - this.wac = new AnnotationConfigApplicationContext(); - this.wac.register(TestConfiguration.class); - this.wac.refresh(); - - return WebHttpHandlerBuilder.webHandler(new DispatcherHandler(this.wac)).build(); - } - - @Test - public void sseAsString() { - Flux result = this.webClient.get().uri("/string").accept(TEXT_EVENT_STREAM).retrieve() - .bodyToFlux(String.class); - - StepVerifier.create(result).expectNext("foo 0").expectNext("foo 1").thenCancel().verify(Duration.ofSeconds(5L)); - } - - @Test - public void sseAsPerson() { - Flux result = this.webClient.get().uri("/person").accept(TEXT_EVENT_STREAM).retrieve() - .bodyToFlux(Person.class); - - StepVerifier.create(result).expectNext(new Person("foo 0")).expectNext(new Person("foo 1")).thenCancel() - .verify(Duration.ofSeconds(5L)); - } - - @Test - @SuppressWarnings("Duplicates") - public void sseAsEvent() { - ResolvableType type = forClassWithGenerics(ServerSentEvent.class, String.class); - Flux> result = this.webClient.get().uri("/event").accept(TEXT_EVENT_STREAM).retrieve() - .bodyToFlux(new ParameterizedTypeReference>() { - }); - - StepVerifier.create(result).consumeNextWith(event -> { - assertThat(event.id()).isEqualTo("0"); - assertThat(event.data()).isEqualTo("foo"); - assertThat(event.comment()).isEqualTo("bar"); - assertThat(event.event()).isNull(); - assertThat(event.retry()).isNull(); - }).consumeNextWith(event -> { - assertThat(event.id()).isEqualTo("1"); - assertThat(event.data()).isEqualTo("foo"); - assertThat(event.comment()).isEqualTo("bar"); - assertThat(event.event()).isNull(); - assertThat(event.retry()).isNull(); - }).thenCancel().verify(Duration.ofSeconds(5L)); - } - - @Test - @SuppressWarnings("Duplicates") - public void sseAsEventWithoutAcceptHeader() { - Flux> result = this.webClient.get().uri("/event").accept(TEXT_EVENT_STREAM).retrieve() - .bodyToFlux(new ParameterizedTypeReference>() { - }); - - StepVerifier.create(result).consumeNextWith(event -> { - assertThat(event.id()).isEqualTo("0"); - assertThat(event.data()).isEqualTo("foo"); - assertThat(event.comment()).isEqualTo("bar"); - assertThat(event.event()).isNull(); - assertThat(event.retry()).isNull(); - }).consumeNextWith(event -> { - assertThat(event.id()).isEqualTo("1"); - assertThat(event.data()).isEqualTo("foo"); - assertThat(event.comment()).isEqualTo("bar"); - assertThat(event.event()).isNull(); - assertThat(event.retry()).isNull(); - }).thenCancel().verify(Duration.ofSeconds(5L)); - } - - @RestController - @SuppressWarnings("unused") - static class SseController { - - private static final Flux INTERVAL = interval(Duration.ofMillis(100), 50); - - @GetMapping("/sse/string") - Flux string() { - return INTERVAL.map(l -> "foo " + l); - } - - @GetMapping("/sse/person") - Flux person() { - return INTERVAL.map(l -> new Person("foo " + l)); - } - - @GetMapping("/sse/event") - Flux> sse() { - return INTERVAL.map(l -> ServerSentEvent.builder("foo").id(Long.toString(l)).comment("bar").build()); - } - - } - - @Configuration(proxyBeanMethods = false) - @EnableWebFlux - @SuppressWarnings("unused") - static class TestConfiguration { - - @Bean - public SseController sseController() { - return new SseController(); - } - - } - - @Configuration(proxyBeanMethods = false) - @EnableAutoConfiguration - @Import(PermitAllSecurityConfiguration.class) - protected static class GatewayConfig { - - @Value("${sse.server.port}") - private int port; - - @Bean - public RouteLocator sseRouteLocator(RouteLocatorBuilder builder) { - return builder.routes().route("sse_route", r -> r.alwaysTrue().uri("http://localhost:" + this.port)) - .build(); - } - - } - - @SuppressWarnings("unused") - private static class Person { - - private String name; - - Person() { - } - - Person(String name) { - this.name = name; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - Person person = (Person) o; - return !(this.name != null ? !this.name.equals(person.name) : person.name != null); - } - - @Override - public int hashCode() { - return this.name != null ? this.name.hashCode() : 0; - } - - @Override - public String toString() { - return "Person{name='" + this.name + '\'' + '}'; - } - - } - -} 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 deleted file mode 100644 index 047215c3..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/ssl/ClientCertAuthSSLTests.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2013-2020 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.test.ssl; - -import java.net.URL; -import java.security.KeyStore; - -import javax.net.ssl.KeyManagerFactory; -import javax.net.ssl.SSLException; - -import io.netty.handler.ssl.SslContext; -import io.netty.handler.ssl.SslContextBuilder; -import io.netty.handler.ssl.util.InsecureTrustManagerFactory; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import reactor.netty.http.client.HttpClient; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.web.server.WebServerException; -import org.springframework.http.client.reactive.ReactorClientHttpConnector; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.util.ResourceUtils; - -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; - -@SpringBootTest(webEnvironment = RANDOM_PORT) -@DirtiesContext -@ActiveProfiles("client-auth-ssl") -public class ClientCertAuthSSLTests extends SingleCertSSLTests { - - @Value("${spring.cloud.gateway.httpclient.ssl.key-store}") - private String keyStore; - - @Value("${spring.cloud.gateway.httpclient.ssl.key-store-password}") - private String keyStorePassword; - - @Value("${spring.cloud.gateway.httpclient.ssl.key-password}") - private String keyPassword; - - @BeforeEach - public void setup() throws Exception { - KeyStore store = KeyStore.getInstance("JKS"); - - try { - URL url = ResourceUtils.getURL(keyStore); - store.load(url.openStream(), keyStorePassword != null ? keyStorePassword.toCharArray() : null); - } - catch (Exception e) { - throw new WebServerException("Could not load key store ' " + keyStore + "'", e); - } - - KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - char[] keyPasswordCharArray = keyPassword != null ? keyPassword.toCharArray() : null; - - if (keyPasswordCharArray == null && keyStorePassword != null) { - keyPasswordCharArray = keyStorePassword.toCharArray(); - } - - keyManagerFactory.init(store, keyPasswordCharArray); - - try { - SslContext sslContext = SslContextBuilder.forClient().trustManager(InsecureTrustManagerFactory.INSTANCE) - .keyManager(keyManagerFactory).build(); - HttpClient httpClient = HttpClient.create().secure(ssl -> ssl.sslContext(sslContext)); - setup(new ReactorClientHttpConnector(httpClient), "https://localhost:" + port); - } - catch (SSLException e) { - throw new RuntimeException(e); - } - } - - @Test - public void testSslTrust() { - testClient.get().uri("/ssltrust").exchange().expectStatus().is2xxSuccessful(); - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/ssl/MultiCertSSLTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/ssl/MultiCertSSLTests.java deleted file mode 100644 index 248c67a7..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/ssl/MultiCertSSLTests.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2013-2020 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.test.ssl; - -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.ActiveProfiles; - -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; - -@SpringBootTest(webEnvironment = RANDOM_PORT) -@DirtiesContext -@ActiveProfiles("multi-cert-ssl") -public class MultiCertSSLTests extends SingleCertSSLTests { - -} 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 deleted file mode 100644 index a0f5a479..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/ssl/SSLHandshakeTimeoutTests.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2013-2020 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.test.ssl; - -import org.junit.jupiter.api.Test; - -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.web.reactive.server.JsonPathAssertions; -import org.springframework.test.web.reactive.server.WebTestClient.ResponseSpec; - -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; - -// 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" }) -@DirtiesContext -@ActiveProfiles("ssl") -public class SSLHandshakeTimeoutTests extends SingleCertSSLTests { - - @Test - @Override // here we validate that it the handshake times out - public void testSslTrust() { - ResponseSpec responseSpec = testClient.get().uri("/ssltrust").exchange(); - responseSpec.expectStatus().is5xxServerError(); - JsonPathAssertions jsonPath = responseSpec.expectBody().jsonPath("message"); - jsonPath.isEqualTo("handshake timed out after 1ms"); - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/ssl/SingleCertSSLTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/ssl/SingleCertSSLTests.java deleted file mode 100644 index 15289a0f..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/ssl/SingleCertSSLTests.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2013-2020 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.test.ssl; - -import javax.net.ssl.SSLException; - -import io.netty.handler.ssl.SslContext; -import io.netty.handler.ssl.SslContextBuilder; -import io.netty.handler.ssl.util.InsecureTrustManagerFactory; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import reactor.netty.http.client.HttpClient; - -import org.springframework.boot.SpringBootConfiguration; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.gateway.test.BaseWebClientTests; -import org.springframework.context.annotation.Import; -import org.springframework.http.ResponseEntity; -import org.springframework.http.client.reactive.ReactorClientHttpConnector; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; - -@SpringBootTest(webEnvironment = RANDOM_PORT) -@DirtiesContext -@ActiveProfiles("single-cert-ssl") -public class SingleCertSSLTests extends BaseWebClientTests { - - @BeforeEach - public void setup() throws Exception { - try { - SslContext sslContext = SslContextBuilder.forClient().trustManager(InsecureTrustManagerFactory.INSTANCE) - .build(); - HttpClient httpClient = HttpClient.create().secure(ssl -> ssl.sslContext(sslContext)); - setup(new ReactorClientHttpConnector(httpClient), "https://localhost:" + port); - } - catch (SSLException e) { - throw new RuntimeException(e); - } - } - - @Test - public void testSslTrust() { - testClient.get().uri("/ssltrust").exchange().expectStatus().is2xxSuccessful(); - } - - @EnableAutoConfiguration - @SpringBootConfiguration - @Import(DefaultTestConfig.class) - @RestController - public static class TestConfig { - - @GetMapping("/httpbin/ssltrust") - public ResponseEntity nocontenttype() { - return ResponseEntity.status(204).build(); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/support/AbstractHttpServer.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/support/AbstractHttpServer.java deleted file mode 100644 index 3165cc32..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/support/AbstractHttpServer.java +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright 2013-2020 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.test.support; - -import java.util.LinkedHashMap; -import java.util.Map; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.http.server.reactive.ContextPathCompositeHandler; -import org.springframework.http.server.reactive.HttpHandler; -import org.springframework.util.Assert; -import org.springframework.util.StopWatch; - -/** - * @author Rossen Stoyanchev - */ -public abstract class AbstractHttpServer implements HttpServer { - - private final Object lifecycleMonitor = new Object(); - - protected Log logger = LogFactory.getLog(getClass().getName()); - - private String host = "0.0.0.0"; - - private int port = 0; - - private HttpHandler httpHandler; - - private Map handlerMap; - - private volatile boolean running; - - public String getHost() { - return host; - } - - @Override - public void setHost(String host) { - this.host = host; - } - - @Override - public int getPort() { - return this.port; - } - - @Override - public void setPort(int port) { - this.port = port; - } - - @Override - public void setHandler(HttpHandler handler) { - this.httpHandler = handler; - } - - public HttpHandler getHttpHandler() { - return this.httpHandler; - } - - public void registerHttpHandler(String contextPath, HttpHandler handler) { - if (this.handlerMap == null) { - this.handlerMap = new LinkedHashMap<>(); - } - this.handlerMap.put(contextPath, handler); - } - - public Map getHttpHandlerMap() { - return this.handlerMap; - } - - protected HttpHandler resolveHttpHandler() { - return (getHttpHandlerMap() != null ? new ContextPathCompositeHandler(getHttpHandlerMap()) : getHttpHandler()); - } - - // InitializingBean - - @Override - public final void afterPropertiesSet() throws Exception { - Assert.notNull(this.host, "Host must not be null"); - Assert.isTrue(this.port >= 0, "Port must not be a negative number"); - Assert.isTrue(this.httpHandler != null || this.handlerMap != null, "No HttpHandler configured"); - Assert.state(!this.running, "Cannot reconfigure while running"); - - synchronized (this.lifecycleMonitor) { - initServer(); - } - } - - protected abstract void initServer() throws Exception; - - // Lifecycle - - @Override - public final void start() { - synchronized (this.lifecycleMonitor) { - if (!isRunning()) { - String serverName = getClass().getSimpleName(); - if (logger.isDebugEnabled()) { - logger.debug("Starting " + serverName + "..."); - } - this.running = true; - try { - StopWatch stopWatch = new StopWatch(); - stopWatch.start(); - startInternal(); - long millis = stopWatch.getTotalTimeMillis(); - if (logger.isDebugEnabled()) { - logger.debug("Server started on port " + getPort() + "(" + millis + " millis)."); - } - } - catch (Throwable ex) { - throw new IllegalStateException(ex); - } - } - } - - } - - protected abstract void startInternal() throws Exception; - - @Override - public final void stop() { - synchronized (this.lifecycleMonitor) { - if (isRunning()) { - String serverName = getClass().getSimpleName(); - logger.debug("Stopping " + serverName + "..."); - this.running = false; - try { - StopWatch stopWatch = new StopWatch(); - stopWatch.start(); - stopInternal(); - logger.debug("Server stopped (" + stopWatch.getTotalTimeMillis() + " millis)."); - } - catch (Throwable ex) { - throw new IllegalStateException(ex); - } - finally { - reset(); - } - } - } - } - - protected abstract void stopInternal() throws Exception; - - @Override - public boolean isRunning() { - return this.running; - } - - private void reset() { - this.host = "0.0.0.0"; - this.port = 0; - this.httpHandler = null; - this.handlerMap = null; - resetInternal(); - } - - protected abstract void resetInternal(); - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/support/HttpServer.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/support/HttpServer.java deleted file mode 100644 index 0c7b20d7..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/support/HttpServer.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2013-2020 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.test.support; - -import org.springframework.beans.factory.InitializingBean; -import org.springframework.context.Lifecycle; -import org.springframework.http.server.reactive.HttpHandler; - -/** - * @author Rossen Stoyanchev - */ -public interface HttpServer extends InitializingBean, Lifecycle { - - void setHost(String host); - - int getPort(); - - void setPort(int port); - - void setHandler(HttpHandler handler); - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/support/ReactorHttpServer.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/support/ReactorHttpServer.java deleted file mode 100644 index 24d80198..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/support/ReactorHttpServer.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2013-2020 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.test.support; - -import java.util.concurrent.atomic.AtomicReference; - -import reactor.netty.DisposableServer; - -import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter; - -/** - * @author Stephane Maldini - */ -public class ReactorHttpServer extends AbstractHttpServer { - - private ReactorHttpHandlerAdapter reactorHandler; - - private reactor.netty.http.server.HttpServer reactorServer; - - private AtomicReference serverRef = new AtomicReference<>(); - - @Override - protected void initServer() { - this.reactorHandler = createHttpHandlerAdapter(); - this.reactorServer = reactor.netty.http.server.HttpServer.create().host(getHost()).port(getPort()); - - } - - private ReactorHttpHandlerAdapter createHttpHandlerAdapter() { - return new ReactorHttpHandlerAdapter(resolveHttpHandler()); - } - - @Override - protected void startInternal() { - DisposableServer server = this.reactorServer.handle(this.reactorHandler).bind().block(); - setPort(server.port()); - this.serverRef.set(server); - } - - @Override - protected void stopInternal() { - this.serverRef.get().dispose(); - } - - @Override - protected void resetInternal() { - this.reactorServer = null; - this.reactorHandler = null; - this.serverRef.set(null); - } - -} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/websocket/WebSocketIntegrationTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/websocket/WebSocketIntegrationTests.java deleted file mode 100644 index afb61ff1..00000000 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/test/websocket/WebSocketIntegrationTests.java +++ /dev/null @@ -1,417 +0,0 @@ -/* - * Copyright 2013-2020 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.test.websocket; - -import java.net.URI; -import java.net.URISyntaxException; -import java.time.Duration; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.atomic.AtomicReference; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; -import reactor.core.publisher.Sinks; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.builder.SpringApplicationBuilder; -import org.springframework.cloud.client.DefaultServiceInstance; -import org.springframework.cloud.gateway.route.RouteLocator; -import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; -import org.springframework.cloud.gateway.test.PermitAllSecurityConfiguration; -import org.springframework.cloud.gateway.test.support.HttpServer; -import org.springframework.cloud.gateway.test.support.ReactorHttpServer; -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.ConfigurableApplicationContext; -import org.springframework.context.Lifecycle; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.core.env.ConfigurableEnvironment; -import org.springframework.core.env.Environment; -import org.springframework.http.HttpHeaders; -import org.springframework.http.ResponseCookie; -import org.springframework.http.server.reactive.HttpHandler; -import org.springframework.util.StringUtils; -import org.springframework.web.reactive.DispatcherHandler; -import org.springframework.web.reactive.HandlerMapping; -import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping; -import org.springframework.web.reactive.socket.CloseStatus; -import org.springframework.web.reactive.socket.HandshakeInfo; -import org.springframework.web.reactive.socket.WebSocketHandler; -import org.springframework.web.reactive.socket.WebSocketMessage; -import org.springframework.web.reactive.socket.WebSocketSession; -import org.springframework.web.reactive.socket.client.ReactorNettyWebSocketClient; -import org.springframework.web.reactive.socket.client.WebSocketClient; -import org.springframework.web.reactive.socket.server.RequestUpgradeStrategy; -import org.springframework.web.reactive.socket.server.WebSocketService; -import org.springframework.web.reactive.socket.server.support.HandshakeWebSocketService; -import org.springframework.web.reactive.socket.server.support.WebSocketHandlerAdapter; -import org.springframework.web.reactive.socket.server.upgrade.ReactorNettyRequestUpgradeStrategy; -import org.springframework.web.server.WebFilter; -import org.springframework.web.server.adapter.WebHttpHandlerBuilder; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.cloud.gateway.filter.WebsocketRoutingFilter.SEC_WEBSOCKET_PROTOCOL; - -/** - * Original is here - * {@see https://github.com/spring-projects/spring-framework/blob/master/spring-webflux/src/test/java/org/springframework/web/reactive/socket/WebSocketIntegrationTests.java} - * Integration tests with server-side {@link WebSocketHandler}s. - * - * @author Rossen Stoyanchev - */ -@DisabledIfEnvironmentVariable(named = "GITHUB_ACTIONS", matches = "true") -public class WebSocketIntegrationTests { - - private static final Duration TIMEOUT = Duration.ofMillis(5000); - - private static final Log logger = LogFactory.getLog(WebSocketIntegrationTests.class); - - protected int serverPort; - - private WebSocketClient client; - - private HttpServer server; - - private ConfigurableApplicationContext gatewayContext; - - private int gatewayPort; - - private static final Sinks.One serverCloseStatusSink = Sinks.one(); - - @BeforeEach - public void setup() throws Exception { - this.client = new ReactorNettyWebSocketClient(); - - this.server = new ReactorHttpServer(); - this.server.setHandler(createHttpHandler()); - this.server.afterPropertiesSet(); - this.server.start(); - - // Set dynamically chosen port - this.serverPort = this.server.getPort(); - - if (this.client instanceof Lifecycle) { - ((Lifecycle) this.client).start(); - } - - this.gatewayContext = new SpringApplicationBuilder(GatewayConfig.class) - .properties("ws.server.port:" + this.serverPort, "server.port=0", "spring.jmx.enabled=false").run(); - - ConfigurableEnvironment env = this.gatewayContext.getBean(ConfigurableEnvironment.class); - this.gatewayPort = Integer.valueOf(env.getProperty("local.server.port")); - } - - @AfterEach - public void stop() throws Exception { - if (this.client instanceof Lifecycle) { - ((Lifecycle) this.client).stop(); - } - this.server.stop(); - if (this.gatewayContext != null) { - this.gatewayContext.stop(); - } - } - - private HttpHandler createHttpHandler() { - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); - context.register(WebSocketTestConfig.class); - context.refresh(); - return WebHttpHandlerBuilder.applicationContext(context).build(); - } - - protected URI getUrl(String path) throws URISyntaxException { - // return new URI("ws://localhost:" + this.serverPort + path); - return new URI("ws://localhost:" + this.gatewayPort + path); - } - - protected URI getHttpUrl(String path) throws URISyntaxException { - return new URI("http://localhost:" + this.gatewayPort + path); - } - - @Test - public void echo() throws Exception { - int count = 100; - Flux input = Flux.range(1, count).map(index -> "msg-" + index); - AtomicReference> actualRef = new AtomicReference<>(); - this.client.execute(getUrl("/echo"), - session -> session.send(input.map(session::textMessage)) - .thenMany(session.receive().take(count).map(WebSocketMessage::getPayloadAsText)).collectList() - .doOnNext(actualRef::set).then()) - .block(TIMEOUT); - assertThat(actualRef.get()).isNotNull(); - assertThat(actualRef.get()).isEqualTo(input.collectList().block()); - } - - @Test - public void echoForHttp() throws Exception { - int count = 100; - Flux input = Flux.range(1, count).map(index -> "msg-" + index); - AtomicReference> actualRef = new AtomicReference<>(); - this.client.execute(getHttpUrl("/echoForHttp"), session -> { - logger.debug("Starting to send messages"); - return session.send(input.doOnNext(s -> logger.debug("outbound " + s)).map(session::textMessage)) - .thenMany(session.receive().take(count).map(WebSocketMessage::getPayloadAsText)).collectList() - .doOnNext(actualRef::set).then(); - }).block(TIMEOUT); - assertThat(actualRef.get()).isNotNull(); - assertThat(actualRef.get()).isEqualTo(input.collectList().block()); - } - - @Test - public void subProtocol() throws Exception { - String protocol = "echo-v1"; - String protocol2 = "echo-v2"; - AtomicReference infoRef = new AtomicReference<>(); - AtomicReference protocolRef = new AtomicReference<>(); - - this.client.execute(getUrl("/sub-protocol"), new WebSocketHandler() { - @Override - public List getSubProtocols() { - return Arrays.asList(protocol, protocol2); - } - - @Override - public Mono handle(WebSocketSession session) { - infoRef.set(session.getHandshakeInfo()); - return session.receive().map(WebSocketMessage::getPayloadAsText).doOnNext(protocolRef::set) - .doOnError(protocolRef::set).then(); - } - }).block(TIMEOUT); - - HandshakeInfo info = infoRef.get(); - assertThat(info.getHeaders().getFirst("Upgrade")).isEqualToIgnoringCase("websocket"); - assertThat(info.getHeaders().getFirst("Sec-WebSocket-Protocol")).isEqualTo(protocol); - assertThat(info.getSubProtocol()).as("Wrong protocol accepted").isEqualTo(protocol); - assertThat(protocolRef.get()).as("Wrong protocol detected on the server side").isEqualTo(protocol); - } - - @Test - public void customHeader() throws Exception { - HttpHeaders headers = new HttpHeaders(); - headers.add("my-header", "my-value"); - AtomicReference headerRef = new AtomicReference<>(); - - this.client.execute(getUrl("/custom-header"), headers, session -> session.receive() - .map(WebSocketMessage::getPayloadAsText).doOnNext(headerRef::set).doOnError(headerRef::set).then()) - .block(TIMEOUT); - - assertThat(headerRef.get()).isEqualTo("my-header:my-value"); - } - - @Test - public void serverClosing() throws Exception { - AtomicReference> closeStatus = new AtomicReference<>(); - this.client.execute(getUrl("/server-close"), session -> { - logger.debug("Starting.."); - closeStatus.set(session.closeStatus()); - return session.receive().doOnNext(s -> logger.debug("inbound " + s)).then().doFinally(signalType -> { - logger.debug("Completed with: " + signalType); - }); - }).block(Duration.ofMillis(5000)); - assertThat(closeStatus.get().block(Duration.ofMillis(5000))) - .isEqualTo(CloseStatus.create(4999, "server-close")); - } - - @Test - public void clientClosing() throws Exception { - this.client.execute(getUrl("/client-close"), session -> session.close(CloseStatus.create(4999, "client-close"))) - .block(Duration.ofMillis(5000)); - assertThat(serverCloseStatusSink.asMono().block(Duration.ofMillis(5000))) - .isEqualTo(CloseStatus.create(4999, "client-close")); - } - - @Disabled - @Test - void cookie() throws Exception { - AtomicReference cookie = new AtomicReference<>(); - AtomicReference receivedCookieRef = new AtomicReference<>(); - this.client.execute(getUrl("/cookie"), session -> { - cookie.set(session.getHandshakeInfo().getHeaders().getFirst("Set-Cookie")); - return session.receive().map(WebSocketMessage::getPayloadAsText).doOnNext(receivedCookieRef::set) - .doOnError(receivedCookieRef::set).then(); - }).block(TIMEOUT); - assertThat(receivedCookieRef.get()).isEqualTo("cookie"); - assertThat(cookie.get()).isEqualTo("project=spring"); - } - - @Configuration(proxyBeanMethods = false) - static class WebSocketTestConfig { - - @Bean - public DispatcherHandler webHandler() { - return new DispatcherHandler(); - } - - @Bean - public WebSocketHandlerAdapter handlerAdapter() { - return new WebSocketHandlerAdapter(webSocketService()); - } - - @Bean - public WebSocketService webSocketService() { - return new HandshakeWebSocketService(getUpgradeStrategy()); - } - - protected RequestUpgradeStrategy getUpgradeStrategy() { - return new ReactorNettyRequestUpgradeStrategy(); - } - - @Bean - public HandlerMapping handlerMapping() { - Map map = new HashMap<>(); - map.put("/echo", new EchoWebSocketHandler()); - map.put("/echoForHttp", new EchoWebSocketHandler()); - map.put("/sub-protocol", new SubProtocolWebSocketHandler()); - map.put("/custom-header", new CustomHeaderHandler()); - map.put("/server-close", new ServerClosingHandler()); - map.put("/client-close", new ClientClosingHandler()); - map.put("/cookie", new CookieHandler()); - - SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping(); - mapping.setUrlMap(map); - return mapping; - } - - @Bean - public WebFilter cookieWebFilter() { - return (exchange, chain) -> { - if (exchange.getRequest().getPath().value().startsWith("/cookie")) { - exchange.getResponse().addCookie(ResponseCookie.from("project", "spring").build()); - } - return chain.filter(exchange); - }; - } - - } - - private static class EchoWebSocketHandler implements WebSocketHandler { - - @Override - public Mono handle(WebSocketSession session) { - // Use retain() for Reactor Netty - return session.send(session.receive().doOnNext(WebSocketMessage::retain)); - } - - } - - private static class SubProtocolWebSocketHandler implements WebSocketHandler { - - @Override - public List getSubProtocols() { - return Arrays.asList("echo-v1", "echo-v2"); - } - - @Override - public Mono handle(WebSocketSession session) { - String protocol = session.getHandshakeInfo().getSubProtocol(); - if (!StringUtils.hasText(protocol)) { - return Mono.error(new IllegalStateException("Missing protocol")); - } - List protocols = session.getHandshakeInfo().getHeaders().get(SEC_WEBSOCKET_PROTOCOL); - assertThat(protocols).contains("echo-v1,echo-v2"); - WebSocketMessage message = session.textMessage(protocol != null ? protocol : "none"); - return session.send(Mono.just(message)); - } - - } - - private static class CustomHeaderHandler implements WebSocketHandler { - - @Override - public Mono handle(WebSocketSession session) { - HttpHeaders headers = session.getHandshakeInfo().getHeaders(); - if (!headers.containsKey("my-header")) { - return Mono.error(new IllegalStateException("Missing my-header")); - } - String payload = "my-header:" + headers.getFirst("my-header"); - WebSocketMessage message = session.textMessage(payload); - return session.send(Mono.just(message)); - } - - } - - private static class ServerClosingHandler implements WebSocketHandler { - - @Override - public Mono handle(WebSocketSession session) { - return Flux.never().mergeWith(session.close(CloseStatus.create(4999, "server-close"))).then(); - } - - } - - private static class ClientClosingHandler implements WebSocketHandler { - - @Override - public Mono handle(WebSocketSession session) { - return session.closeStatus().doOnNext(serverCloseStatusSink::tryEmitValue).then(); - } - - } - - private static class CookieHandler implements WebSocketHandler { - - @Override - public Mono handle(WebSocketSession session) { - WebSocketMessage message = session.textMessage("cookie"); - return session.send(Mono.just(message)); - } - - } - - @Configuration(proxyBeanMethods = false) - @EnableAutoConfiguration - @Import(PermitAllSecurityConfiguration.class) - @LoadBalancerClient(name = "wsservice", configuration = LocalLoadBalancerClientConfiguration.class) - protected static class GatewayConfig { - - @Bean - public RouteLocator wsRouteLocator(RouteLocatorBuilder builder) { - return builder.routes().route(r -> r.path("/echoForHttp").uri("lb://wsservice")) - .route(r -> r.alwaysTrue().uri("lb:ws://wsservice")).build(); - } - - } - - public static class LocalLoadBalancerClientConfiguration { - - @Value("${ws.server.port}") - private int wsPort; - - @Bean - public ServiceInstanceListSupplier staticServiceInstanceListSupplier(Environment env) { - return ServiceInstanceListSuppliers.from("wsservice", - new DefaultServiceInstance("wsservice-1", "wsservice", "localhost", wsPort, false)); - } - - } - -} diff --git a/spring-cloud-gateway-server/src/test/kotlin/org/springframework/cloud/gateway/route/builder/RouteDslTests.kt b/spring-cloud-gateway-server/src/test/kotlin/org/springframework/cloud/gateway/route/builder/RouteDslTests.kt deleted file mode 100644 index ce14c50a..00000000 --- a/spring-cloud-gateway-server/src/test/kotlin/org/springframework/cloud/gateway/route/builder/RouteDslTests.kt +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright 2013-2020 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.route.builder - -import org.junit.jupiter.api.Test -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.autoconfigure.EnableAutoConfiguration -import org.springframework.boot.test.context.SpringBootTest -import org.springframework.cloud.gateway.support.ServerWebExchangeUtils -import org.springframework.context.annotation.Configuration -import org.springframework.mock.http.server.reactive.MockServerHttpRequest -import org.springframework.mock.web.server.MockServerWebExchange -import org.springframework.web.server.ServerWebExchange -import reactor.kotlin.core.publisher.toMono -import reactor.test.StepVerifier -import java.net.URI - -@SpringBootTest(classes = [Config::class]) -class RouteDslTests { - - @Autowired - lateinit var builder: RouteLocatorBuilder - - @Test - fun sampleRouteDsl() { - val routeLocator = builder.routes { - route(id = "test") { - host("**.abc.org") and path("/image/png") - filters { - addResponseHeader("X-TestHeader", "foobar") - } - uri("http://httpbin.org:80") - } - - route(id = "test2") { - path("/image/webp") or path("/image/anotherone") - filters { - addResponseHeader("X-AnotherHeader", "baz") - addResponseHeader("X-AnotherHeader-2", "baz-2") - } - uri("https://httpbin.org:443") - } - } - - StepVerifier - .create(routeLocator.routes) - .expectNextMatches({ - it.id == "test" && it.filters.size == 1 && it.uri == URI.create("http://httpbin.org:80") - }) - .expectNextMatches({ - it.id == "test2" && it.filters.size == 2 && it.uri == URI.create("https://httpbin.org:443") - }) - .expectComplete() - .verify() - - val sampleExchange: ServerWebExchange = MockServerWebExchange.from(MockServerHttpRequest.get("/image/webp") - .header("Host", "test.abc.org").build()) - - val filteredRoutes = routeLocator.routes.filter({ - sampleExchange.attributes.put(ServerWebExchangeUtils.GATEWAY_PREDICATE_ROUTE_ATTR, it.id) - it.predicate.apply(sampleExchange).toMono().block() - }) - - StepVerifier.create(filteredRoutes) - .expectNextMatches({ - it.id == "test2" && it.filters.size == 2 && it.uri == URI.create("https://httpbin.org:443") - }) - .expectComplete() - .verify() - } - - @Test - fun dslWithFunctionParameters() { - val routerLocator = builder.routes { - route(id = "test1", order = 10, uri = "http://httpbin.org") { - host("**.abc.org") - } - route(id = "test2", order = 10, uri = "http://someurl") { - host("**.abc.org") - uri("http://override-url") - } - } - - StepVerifier.create(routerLocator.routes) - .expectNextMatches({ - it.id == "test1" && - it.uri == URI.create("http://httpbin.org:80") && - it.order == 10 && - it.predicate.apply(MockServerWebExchange - .from(MockServerHttpRequest - .get("/someuri").header("Host", "test.abc.org"))) - .toMono().block() - }) - .expectNextMatches({ - it.id == "test2" && - it.uri == URI.create("http://override-url:80") && - it.order == 10 && - it.predicate.apply(MockServerWebExchange - .from(MockServerHttpRequest - .get("/someuri").header("Host", "test.abc.org"))) - .toMono().block() - }) - .expectComplete() - .verify() - } -} - -@Configuration(proxyBeanMethods = false) -@EnableAutoConfiguration -open class Config {} \ No newline at end of file diff --git a/spring-cloud-gateway-server/src/test/resources/1x1.png b/spring-cloud-gateway-server/src/test/resources/1x1.png deleted file mode 100644 index 134aa5b71e4f334e720c798450f264ccfe1d028b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1047 zcmZ{jJ#W)M7{{+gNX5{J1tE|uGuvmoZ_3GW)yB|9A|*_|Kw;>d#nKfHoM2cK)l!Fy|FHy zAM?{+a=->hhv4eRliy-+FFhVJ0Pa7O_Zs;0`4IqjzLMdHk9sfBgvM4lrDxbG;#9Z+ z@Cqb8W6VRnh@*s|qHQP#65mpWhOR99UE3J-`g(_ESl_X>E!*%lUDv&AIzxltMTLt| z+jz@)iXhDM+{(8snw>+Z)oMZ8g|2G~hsiDz9u{W8o|TKt*9kD2WF+N;Cc0cVJfj!9 zZ5T4p?N#kFPHzJxtjbE{2a7O;j%CAT5GS+6_Wq&d%t{OwWCbQ?N=y}_+@mKdih6WP zW|u+8vG3Z>o@u+Ly*qST$ac}T>%oR+k)fhH84fwkhLlErpAsr%4z>G#0a{1Q@;l9X4g=ol4YP^{D_vTDp zYyzWYKwYc>@)(e+((Gd1r)fCBvRn$ONlmwkGF}m-rdON?<$qlJ zugj)Us^}VD6X!YaKwsPcdGJj%<%4c;Fx+@L`Ezr#5AI&CUw`>-iiQTD`}`!hI(+l~ EFQ~UWDF6Tf diff --git a/spring-cloud-gateway-server/src/test/resources/META-INF/services/reactor.blockhound.integration.BlockHoundIntegration b/spring-cloud-gateway-server/src/test/resources/META-INF/services/reactor.blockhound.integration.BlockHoundIntegration deleted file mode 100644 index 89a00437..00000000 --- a/spring-cloud-gateway-server/src/test/resources/META-INF/services/reactor.blockhound.integration.BlockHoundIntegration +++ /dev/null @@ -1,17 +0,0 @@ -# -# Copyright 2013-2020 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. -# - -org.springframework.cloud.gateway.test.CustomBlockHoundIntegration 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 deleted file mode 100644 index 88d1b138..00000000 --- a/spring-cloud-gateway-server/src/test/resources/application-client-auth-ssl.yml +++ /dev/null @@ -1,38 +0,0 @@ -test: - uri: lb:https://testservice - -server: - ssl: - enabled: true - key-alias: scg - key-store-password: scg1234 - key-password: keyscg1234 - key-store: classpath:scg-keystore-with-different-key-password.jks - trust-store: classpath:scg-truststore.jks - trust-store-password: scg1234 - trust-store-type: JKS - key-store-type: JKS - client-auth: Need -spring: - cloud: - gateway: - httpclient: - ssl: - key-store: classpath:scg-keystore-with-different-key-password.jks - key-password: keyscg1234 - key-store-password: scg1234 - key-store-type: JKS - trustedX509Certificates: - - src/test/resources/single-cert-for-different-key-password.pem - default-filters: - - PrefixPath=/httpbin - routes: - - id: default_path_to_httpbin - uri: ${test.uri} - order: 10000 - predicates: - - name: Path - args: - pattern: /** - - 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 deleted file mode 100644 index f210add2..00000000 --- a/spring-cloud-gateway-server/src/test/resources/application-cors-global-config.yml +++ /dev/null @@ -1,10 +0,0 @@ -spring: - cloud: - gateway: - globalcors: - cors-configurations: - '[/**]': - maxAge: 10 - allowedOrigins: "*" - allowedMethods: - - GET 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 deleted file mode 100644 index 0017f4a6..00000000 --- a/spring-cloud-gateway-server/src/test/resources/application-cors-per-route-config.yml +++ /dev/null @@ -1,28 +0,0 @@ -spring: - cloud: - gateway: - routes: - - id: cors_preflight_test - uri: ${test.uri} - predicates: - - Path=/abc/** - metadata: - cors: - allowedOrigins: 'domain.com' - allowedMethods: [ GET, POST ] - allowedHeaders: '*' - allowCredentials: true - maxAge: 30 - - id: cors_test - uri: ${test.uri} - predicates: - - Path=/cors/** - filters: - - StripPrefix=1 - metadata: - cors: - allowedOrigins: https://test.com - allowedMethods: - - GET - - PUT - allowedHeaders: '*' \ No newline at end of file diff --git a/spring-cloud-gateway-server/src/test/resources/application-defaultid.yml b/spring-cloud-gateway-server/src/test/resources/application-defaultid.yml deleted file mode 100644 index ecb94e4c..00000000 --- a/spring-cloud-gateway-server/src/test/resources/application-defaultid.yml +++ /dev/null @@ -1,11 +0,0 @@ -spring: - cloud: - gateway: - default-filters: - - PrefixPath=/httpbin - routes: - - uri: https://defaultrouteid.example.org - order: 10000 - predicates: - - Path=/defaultrouteid/** - 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 deleted file mode 100644 index 7ebfe2f5..00000000 --- a/spring-cloud-gateway-server/src/test/resources/application-disable-components.yml +++ /dev/null @@ -1,26 +0,0 @@ -server: - error: - include-message: always - -spring: - config: - import: classpath:application-logging.yml - - cloud: - gateway: - globalcors: - cors-configurations: - '[/**]': - maxAge: 10 - allowedOrigins: "*" - allowedMethods: - - GET - default-filters: - #empty to prevent failures in tests disabling components - routes: - #empty to prevent failures in tests disabling components -management: - endpoints: - web: - exposure: - include: "*" diff --git a/spring-cloud-gateway-server/src/test/resources/application-forwardstatic.yml b/spring-cloud-gateway-server/src/test/resources/application-forwardstatic.yml deleted file mode 100644 index 35b5b4a0..00000000 --- a/spring-cloud-gateway-server/src/test/resources/application-forwardstatic.yml +++ /dev/null @@ -1,8 +0,0 @@ -spring: - cloud: - gateway: - routes: - - uri: forward:/docs.html - id: static_docs_route - predicates: - - Path=/mydocs diff --git a/spring-cloud-gateway-server/src/test/resources/application-logging.yml b/spring-cloud-gateway-server/src/test/resources/application-logging.yml deleted file mode 100644 index 4bda232b..00000000 --- a/spring-cloud-gateway-server/src/test/resources/application-logging.yml +++ /dev/null @@ -1,9 +0,0 @@ -logging: - level: - org.springframework.cloud.gateway: TRACE - org.springframework.http.server.reactive: DEBUG - org.springframework.web.reactive: DEBUG - org.springframework.boot.autoconfigure.web: DEBUG - org.springframework.cloud.gateway.actuate: DEBUG - reactor.netty: DEBUG - redisratelimiter: DEBUG \ No newline at end of file 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 deleted file mode 100644 index 5ab3bed3..00000000 --- a/spring-cloud-gateway-server/src/test/resources/application-multi-cert-ssl.yml +++ /dev/null @@ -1,29 +0,0 @@ -test: - uri: lb:https://testservice - -server: - ssl: - enabled: true - key-alias: scg - key-store-password: scg1234 - key-store: classpath:scg-keystore.p12 - key-store-type: PKCS12 - -spring: - cloud: - gateway: - httpclient: - ssl: - trustedX509Certificates: - - src/test/resources/multi-cert.pem - default-filters: - - PrefixPath=/httpbin - routes: - - id: default_path_to_httpbin - uri: ${test.uri} - order: 10000 - predicates: - - name: Path - args: - pattern: /** - 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 deleted file mode 100644 index 8f81445f..00000000 --- a/spring-cloud-gateway-server/src/test/resources/application-netty-routing-filter.yml +++ /dev/null @@ -1,95 +0,0 @@ -test: - hostport: httpbin.org:80 - uri: lb://testservice - -my.timeout: 3000 - -server: - error: - include-message: always - -spring: - profiles: - group: - - logging - - cloud: - gateway: - routes: - # ===================================== - - id: per_route_connect_timeout - uri: http://localhost:32167 - predicates: - - name: Path - args: - pattern: /connect/delay/{timeout} - metadata: - connect-timeout: 5 - - # ===================================== - - id: per_route_response_timeout - uri: ${test.uri} - predicates: - - name: Path - args: - pattern: /route/delay/{timeout} - filters: - - StripPrefix=1 - metadata: - response-timeout: 1000 - - # ===================================== - - id: per_route_response_timeout_disabled - uri: ${test.uri} - predicates: - - name: Path - args: - pattern: /disabledRoute/delay/{timeout} - filters: - - StripPrefix=1 - metadata: - response-timeout: -1 - - # ===================================== - - id: per_route_response_timeout_invalid - uri: ${test.uri} - predicates: - - name: Path - args: - pattern: /invalidRoute/delay/{timeout} - filters: - - StripPrefix=1 - metadata: - response-timeout: notANumber - - # ===================================== - - id: per_route_merge_response_headers - uri: ${test.uri} - predicates: - - Host=**.mergeresponseheader.org - - Path=/responseheaders/** - filters: - - AddResponseHeader=X-Test-SHOULD-MERGED-HEADER, value-from-gateway - metadata: - response-timeout: 1000 - - # ===================================== - - id: per_route_response_timeout_placeholder - uri: ${test.uri} - predicates: - - Host=**.responsetimeoutplaceholder.org - - Path=/responseheaders/** - metadata: - response-timeout: ${my.timeout} - - # ===================================== - # should be last and not follow alphabetical order - - id: default_path_to_httpbin - uri: ${test.uri} - order: 10000 - predicates: - - name: Path - args: - pattern: /** - httpclient: - response-timeout: 3s 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 deleted file mode 100644 index a6c5726d..00000000 --- a/spring-cloud-gateway-server/src/test/resources/application-redis-rate-limiter-config.yml +++ /dev/null @@ -1,26 +0,0 @@ -spring: - cloud: - gateway: - default-filters: - routes: - # ===================================== - - id: redis_rate_limiter_config_test - uri: ${test.uri} - predicates: - - Path=/ - filters: - - name: RequestRateLimiter - args: - redis-rate-limiter: - replenish-rate: 10 - burst-capacity: 20 - - id: redis_rate_limiter_minimal_config_test - uri: ${test.uri} - predicates: - - Path=/ - filters: - - name: RequestRateLimiter - args: - redis-rate-limiter: - replenish-rate: 2 - 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 deleted file mode 100644 index 94ae146a..00000000 --- a/spring-cloud-gateway-server/src/test/resources/application-redis-rate-limiter-default-config.yml +++ /dev/null @@ -1,16 +0,0 @@ -spring: - cloud: - gateway: - default-filters: - - name: RequestRateLimiter - args: - redis-rate-limiter: - replenish-rate: 70 - burst-capacity: 80 - requested-tokens: 10 - routes: - # ===================================== - - id: redis_rate_limiter_config_default_test - uri: ${test.uri} - predicates: - - Path=/default 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 deleted file mode 100644 index 3baeb637..00000000 --- a/spring-cloud-gateway-server/src/test/resources/application-redis-route-repository.yml +++ /dev/null @@ -1,5 +0,0 @@ -spring: - cloud: - gateway: - 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 deleted file mode 100644 index 12795440..00000000 --- a/spring-cloud-gateway-server/src/test/resources/application-remote-address.yml +++ /dev/null @@ -1,24 +0,0 @@ -spring: - cloud: - gateway: - default-filters: - routes: - # ===================================== - - id: remote_address_ipv6_test - uri: ${test.uri} - predicates: - - Path=/ok/httpbin/ - - RemoteAddr=2001:db8:abcd:0012::0/64,::1/32,127.0.0.1 - filters: - - SetPath=/httpbin/ - - SetStatus=200 - - # ===================================== - - id: remote_address_ipv6_test_other_ip - uri: ${test.uri} - predicates: - - Path=/nok/httpbin/ - - RemoteAddr=2001:db8:abcd:0012::0/64 - filters: - - SetPath=/httpbin/ - - SetStatus=200 diff --git a/spring-cloud-gateway-server/src/test/resources/application-removenonproxyheaders.yml b/spring-cloud-gateway-server/src/test/resources/application-removenonproxyheaders.yml deleted file mode 100644 index f95895db..00000000 --- a/spring-cloud-gateway-server/src/test/resources/application-removenonproxyheaders.yml +++ /dev/null @@ -1,16 +0,0 @@ -spring: - cloud: - gateway: - default-filters: - - RemoveNonProxyHeaders - - PrefixPath=/httpbin - routes: - # ===================================== - - id: remove_request_header_test - uri: lb://testservice - predicates: - - Host=**.removenonproxyheaders.org - - Path=/headers - filters: - - RemoveRequestHeader=X-Request-Foo - 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 deleted file mode 100644 index 021ac0af..00000000 --- a/spring-cloud-gateway-server/src/test/resources/application-request-header-web-filter.yml +++ /dev/null @@ -1,17 +0,0 @@ -spring: - cloud: - gateway: - routes: - - id: add_request_header_test - uri: ${test.uri} - predicates: - - Path=/headers - filters: - - AddRequestHeader=X-Request-Example, ValueA - - id: add_multiple_request_header_test - uri: ${test.uri} - predicates: - - Path=/multivalueheaders - filters: - - AddRequestHeader=X-Request-Example, ValueA - - AddRequestHeader=X-Request-Example, ValueB 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 deleted file mode 100644 index f75cf269..00000000 --- a/spring-cloud-gateway-server/src/test/resources/application-request-headers-if-not-present-web-filter.yml +++ /dev/null @@ -1,17 +0,0 @@ -spring: - cloud: - gateway: - routes: - - id: add_request_headers_if_not_present_test - uri: ${test.uri} - predicates: - - Path=/headers - filters: - - AddRequestHeadersIfNotPresent=X-Request-Example:ValueA - - id: add_multiple_requests_if_not_present_header_test - uri: ${test.uri} - predicates: - - Path=/multivalueheaders - filters: - - AddRequestHeadersIfNotPresent=X-Request-Example:ValueA,X-Request-Second-Example:ValueC - - AddRequestHeadersIfNotPresent=X-Request-Example:ValueB,X-Request-Second-Example:ValueD 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 deleted file mode 100644 index fefe27db..00000000 --- a/spring-cloud-gateway-server/src/test/resources/application-request-map-header-web-filter.yml +++ /dev/null @@ -1,16 +0,0 @@ -spring: - cloud: - gateway: - routes: - - id: map_request_header_test_singleresponse - uri: ${test.uri} - predicates: - - Path=/headers - filters: - - MapRequestHeader=a, X-Request-Example - - id: map_request_header_test_multiresponse - uri: ${test.uri} - predicates: - - Path=/multivalueheaders - filters: - - MapRequestHeader=a, X-Request-Example 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 deleted file mode 100644 index b0190007..00000000 --- a/spring-cloud-gateway-server/src/test/resources/application-request-parameter-web-filter.yml +++ /dev/null @@ -1,11 +0,0 @@ -spring: - cloud: - gateway: - routes: - - id: add_request_parameter_test - uri: ${test.uri} - predicates: - - Host=**.addrequestparameter.org - - Path=/get - filters: - - AddRequestParameter=example, ValueA diff --git a/spring-cloud-gateway-server/src/test/resources/application-retrytests.yml b/spring-cloud-gateway-server/src/test/resources/application-retrytests.yml deleted file mode 100644 index ea5e1be8..00000000 --- a/spring-cloud-gateway-server/src/test/resources/application-retrytests.yml +++ /dev/null @@ -1,31 +0,0 @@ -spring: - cloud: - gateway: - default-filters: - - PrefixPath=/httpbin - routes: - # ===================================== - - id: retry_test - uri: ${test.uri} - predicates: - - Path=/retry - filters: - - Retry - # ===================================== - - id: retry_post_once_test - uri: ${test.uri} - predicates: - - Host=**.retrypostonceconfig.org - filters: - - name: Retry - args: - methods: GET,POST - # ===================================== - - id: retry_post_test - uri: ${test.uri} - predicates: - - Host=**.retrypostconfig.org - filters: - - name: Retry - args: - methods: GET,POST 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 deleted file mode 100644 index 110bb930..00000000 --- a/spring-cloud-gateway-server/src/test/resources/application-save-session-web-filter.yml +++ /dev/null @@ -1,10 +0,0 @@ -spring: - cloud: - gateway: - routes: - - id: save_session_test - uri: ${test.uri} - predicates: - - Path=/get - filters: - - SaveSession 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 deleted file mode 100644 index 61866d25..00000000 --- a/spring-cloud-gateway-server/src/test/resources/application-single-cert-ssl.yml +++ /dev/null @@ -1,29 +0,0 @@ -test: - uri: lb:https://testservice - -server: - ssl: - enabled: true - key-alias: scg - key-store-password: scg1234 - key-store: classpath:scg-keystore.p12 - key-store-type: PKCS12 - -spring: - cloud: - gateway: - httpclient: - ssl: - trustedX509Certificates: - - src/test/resources/single-cert.pem - default-filters: - - PrefixPath=/httpbin - routes: - - id: default_path_to_httpbin - uri: ${test.uri} - order: 10000 - predicates: - - name: Path - args: - pattern: /** - 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 deleted file mode 100644 index eb648d38..00000000 --- a/spring-cloud-gateway-server/src/test/resources/application-verification-route.yml +++ /dev/null @@ -1,12 +0,0 @@ -server.port: 0 -spring: - cloud: - gateway: - routes: - - uri: https://example.com - predicates: - - Path=/verification/** - filters: - - name: TestFilter - args: - arg1: world \ No newline at end of file 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 deleted file mode 100644 index 49ffb6d5..00000000 --- a/spring-cloud-gateway-server/src/test/resources/application-weights-404.yml +++ /dev/null @@ -1,19 +0,0 @@ -spring: - cloud: - gateway: - default-filters: - - PrefixPath=/httpbin - routes: - # ===================================== - - id: weight_first_404_test_1 - uri: ${test.uri} - predicates: - - Host=**.weight4041.org - - Weight=group404, 8 - - # ===================================== - - id: weight_first_404_test_2 - uri: ${test.uri} - predicates: - - Host=**.weight4042.org - - Weight=group404, 2 diff --git a/spring-cloud-gateway-server/src/test/resources/application-weights-concurrent.yml b/spring-cloud-gateway-server/src/test/resources/application-weights-concurrent.yml deleted file mode 100644 index cdf2d4eb..00000000 --- a/spring-cloud-gateway-server/src/test/resources/application-weights-concurrent.yml +++ /dev/null @@ -1,8 +0,0 @@ -test: - concurrent: - execution: - timeInSeconds: 10 - -logging: - level: - org.springframework.cloud.gateway.filter.WeightCalculatorWebFilter: INFO diff --git a/spring-cloud-gateway-server/src/test/resources/application.yml b/spring-cloud-gateway-server/src/test/resources/application.yml deleted file mode 100644 index 9ca3a774..00000000 --- a/spring-cloud-gateway-server/src/test/resources/application.yml +++ /dev/null @@ -1,509 +0,0 @@ -test: - hostport: httpbin.org:80 - # hostport: localhost:5000 - # uri: http://${test.hostport} - uri: lb://testservice - -server: - error: - include-message: always - -spring: - profiles: - group: - - logging - - cloud: - gateway: - globalcors: - cors-configurations: - '[/**]': - maxAge: 10 - allowedOrigins: "*" - allowedMethods: - - GET - default-filters: - - AddResponseHeader=X-Response-Default-Foo, Default-Bar - - PrefixPath=/httpbin - - # TODO: breakup configuration for individual tests (though some are reused) - routes: - # ===================================== - - host_example_to_httpbin=${test.uri}, Host=**.example.org - - # ===================================== - - id: host_foo_path_headers_to_httpbin - uri: ${test.uri} - predicates: - - Host=**.foo.org - - Path=/headers - - Method=GET - - Header=X-Request-Id, \d+ - - Query=foo, ba. - - Query=baz - - Cookie=chocolate, ch.p - - After=1900-01-20T17:42:47.789-07:00[America/Denver] - filters: - - AddResponseHeader=X-Response-Foo, Bar - - # ===================================== - - id: add_request_header_test - uri: ${test.uri} - predicates: - - Host=**.addrequestheader.org - - Path=/headers - filters: - - AddRequestHeader=X-Request-Foo, Bar - - # ===================================== - - id: add_request_parameter_test - uri: ${test.uri} - predicates: - - Host=**.addrequestparameter.org - - Path=/get - filters: - - AddRequestParameter=foo, bar - - # ===================================== - - id: add_response_header_test - uri: ${test.uri} - predicates: - - Host=**.addresponseheader.org - - Path=/headers - filters: - - AddResponseHeader=X-Request-Foo, Bar - - - id: cache_request_body_test - uri: ${test.uri} - predicates: - - Host=**.cacherequestbody.org - filters: - - name: CacheRequestBody - args: - bodyClass: java.lang.String - - # ===================================== - - id: circuitbreaker_exception_fallback_test - uri: ${test.uri} - predicates: - - Host=**.circuitbreakerexceptionfallback.org - filters: - - name: CircuitBreaker - args: - name: fallbackcmd - fallbackUri: forward:/circuitbreakerExceptionFallback - - # ===================================== - - id: circuitbreaker_fallback_test - uri: ${test.uri} - predicates: - - Host=**.circuitbreakerfallback.org - filters: - - name: CircuitBreaker - args: - name: fallbackcmd - fallbackUri: forward:/circuitbreakerFallbackController - - # ===================================== - - id: circuitbreaker_fallback_test_variables - uri: ${test.uri} - predicates: - - Path=/delay/{*segments} - - Host={host}.circuitbreakervariables.org - filters: - - name: CircuitBreaker - args: - name: fallbackvarscmd - fallbackUri: forward:/circuitbreakerUriFallbackController/{segments}/{host} - # ===================================== - - id: circuitbreaker_fallback_test_path - uri: ${test.uri} - predicates: - - Path=/status/** - - Host=**.circuitbreakerfallbackpath.org - filters: - - name: CircuitBreaker - args: - name: fallbackcmd - statusCodes: - - 200 - fallbackUri: forward:/headers - - # ===================================== - - id: circuitbreaker_fallback_test_statuscode - uri: ${test.uri} - predicates: - - Host=**.circuitbreakerstatuscode.org - filters: - - name: CircuitBreaker - args: - name: fallbackcmd - statusCodes: - - 500 - - "NOT_FOUND" - fallbackUri: forward:/statusCodeFallbackController - - # ===================================== - - id: circuitbreaker_resume_without_error - uri: ${test.uri} - predicates: - - Host=**.circuitbreakerresumewithouterror.org - filters: - - name: CircuitBreaker - args: - name: resumewithouterror - statusCodes: - - 500 - - "NOT_FOUND" - resume-without-error: true - - # ===================================== - - id: change_uri_test - uri: ${test.uri} - predicates: - - Host=**.changeuri.org - - Header=X-CF-Forwarded-Url - filters: - - RequestHeaderToRequestUri=X-CF-Forwarded-Url - - # ===================================== - - id: cloudfoundry_routeservice_test - uri: ${test.uri} - predicates: - - CloudFoundryRouteService= - filters: - - RequestHeaderToRequestUri=X-CF-Forwarded-Url - - # ===================================== - - id: cookie_regex_test - uri: ${test.uri} - predicates: - - Host=**.cookieregex.org - - Cookie=mycookie, hello.* - - # ===================================== - - id: dedupe_response_header_test - uri: ${test.uri} - predicates: - - Host=**.deduperesponseheader.org - - Path=/headers - filters: - - DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin, RETAIN_FIRST - - DedupeResponseHeader=Scout-Cookie, RETAIN_LAST - - DedupeResponseHeader=Next-Week-Lottery-Numbers, RETAIN_UNIQUE - - AddResponseHeader=Access-Control-Allow-Credentials, false - - AddResponseHeader=Access-Control-Allow-Credentials, true - - AddResponseHeader=Access-Control-Allow-Origin, * - - AddResponseHeader=Access-Control-Allow-Origin, https://musk.mars - - AddResponseHeader=Scout-Cookie, S'mores - - AddResponseHeader=Scout-Cookie, Thin Mints - - AddResponseHeader=Next-Week-Lottery-Numbers, 42 - - AddResponseHeader=Next-Week-Lottery-Numbers, 2 - - AddResponseHeader=Next-Week-Lottery-Numbers, 2 - - AddResponseHeader=Next-Week-Lottery-Numbers, 4 - - # ===================================== - - id: forward_test - uri: forward:/localcontroller - predicates: - - Host=**.forward.org - - # ===================================== - - id: header_test - uri: ${test.uri} - predicates: - - Header=Foo, .* - - # ===================================== - - id: host_backwards_compatible_test - uri: ${test.uri} - predicates: - - name: Host - args: - pattern: '**.hostpatternarg.org' - - # ===================================== - - id: host_backwards_compatible_shortcut_test - uri: ${test.uri} - predicates: - - Host=**.hostpatternshortcut.org - - # ===================================== - - id: host_multi_test - uri: ${test.uri} - predicates: - - Host=**.hostmulti1.org,**.hostmulti2.org - - # ===================================== - - id: load_balancer_client_test - uri: lb://testservice - predicates: - - Host=**.loadbalancerclient.org - - # ===================================== - - id: load_balancer_client_empty_test - uri: lb://serviceshouldnotexistanywhere - predicates: - - Host=**.loadbalancerclientempty.org - - # ===================================== - - id: method_test - uri: ${test.uri} - predicates: - - Method=GET - - Host=**.method.org - - # ===================================== - - id: not_between_test - uri: ${test.uri} - predicates: - - Host=**.notbetween.org - - Between=1000-03-13T10:39:37.518-04:00[America/New_York],1002-03-13T10:39:37.518-04:00[America/New_York] - - # ===================================== - - id: path_test - uri: ${test.uri} - predicates: - - Path=/{org}/{scope}/function,false - - Host=**.path.org - filters: - - SetPath=/anything/{org}{scope} - - # ===================================== - - id: path_test_two - uri: ${test.uri} - predicates: - - Path=/{org}/{scope}/function,true - - Host=**.path.org - filters: - - SetStatus=404 - - # ===================================== - - id: path_multi - uri: ${test.uri} - predicates: - - Host=**.pathmulti.org - - Path=/anything/multi1{num},/anything/multi2{num} - filters: - - SetPath=/anything/multi{num} - - # ===================================== - - id: path_regex - uri: ${test.uri} - predicates: - - Host=**.pathregex.org - - Path=/regex/{digits:\d+} - filters: - - SetPath=/anything/{digits} - - # ===================================== - - id: redirect_to_test - uri: ${test.uri} - predicates: - - Host=**.redirectto.org - filters: - - RedirectTo=302, https://example.org - - # ===================================== - - id: remove_request_header_test - uri: ${test.uri} - predicates: - - Host=**.removerequestheader.org - - Path=/headers - filters: - - RemoveRequestHeader=X-Request-Foo - - # ===================================== - - id: remove_response_header_test - uri: ${test.uri} - predicates: - - Host=**.removereresponseheader.org - - Path=/headers - filters: - - RemoveResponseHeader=X-Request-Foo - - AddResponseHeader=X-Request-Foo, Bar - - # ===================================== - - id: request_header_size_test - uri: ${test.uri} - predicates: - - Host=**.requestheadersize.org - filters: - - RequestHeaderSize=46 - - # ===================================== - - id: rewrite_location_response_header_test - uri: ${test.uri} - predicates: - - Host=**.rewritelocationresponseheader.org - - Path=/headers - filters: - - RewriteLocationResponseHeader - - AddResponseHeader=Location, https://backend.org:443/v1/some/object/id - - # ===================================== - - id: rewrite_response_header_test - uri: ${test.uri} - predicates: - - Host=**.rewriteresponseheader.org - - Path=/headers - filters: - - RewriteResponseHeader=X-Request-Foo, password=[^&]+, password=*** - - AddResponseHeader=X-Request-Foo, /42?user=ford&password=omg!what&flag=true - - # ===================================== - - id: sccb_failure_test - uri: ${test.uri} - predicates: - - Host=**.sccbfailure.org - filters: - - CircuitBreaker=failcmd - - # ===================================== - - id: sccb_success_test - uri: ${test.uri} - predicates: - - Host=**.sccbsuccess.org - filters: - - CircuitBreaker=successcmd - - # ===================================== - - id: sccb_timeout_test - uri: ${test.uri} - predicates: - - Host=**.sccbtimeout.org - filters: - - CircuitBreaker=timeoutcmd - - # ===================================== - - id: secure_headers_test - uri: ${test.uri} - predicates: - - Host=**.secureheaders.org - - Path=/headers - filters: - - SecureHeaders - - # ===================================== - - id: set_request_host_header_test - uri: ${test.uri} - predicates: - - Host=**.setrequesthostheader.org - - Path=/headers - filters: - - SetRequestHostHeader=otherhost.io - - # ===================================== - - id: set_path_test - uri: ${test.uri} - predicates: - - Host=**.setpath.org - - Path=/foo/{segment} - filters: - - SetPath=/{segment} - - # ===================================== - - id: set_path_host_test - uri: ${test.uri} - predicates: - - Host={subdomain}.setpathhost.org - filters: - - SetPath=/{subdomain} - - # ===================================== - - id: set_response_header_test - uri: ${test.uri} - predicates: - - Host=**.setreresponseheader.org - - Path=/headers - filters: - - SetResponseHeader=X-Response-Foo, Bar - - AddResponseHeader=X-Response-Foo, Bar2 - - AddResponseHeader=X-Response-Foo, Bar1 - - # ===================================== - - id: set_status_int_test - uri: ${test.uri} - predicates: - - Host=**.setstatusint.org - - Path=/headers - filters: - - name: SetStatus - args: - status: 401 - - # ===================================== - - id: set_status_string_test - uri: ${test.uri} - predicates: - - Host=**.setstatusstring.org - - Path=/headers - filters: - - SetStatus=BAD_REQUEST - - # ===================================== - - id: strip_prefix_test - uri: ${test.uri} - predicates: - - Host=**.stripprefix.org - - Path=/foo/** - filters: - - StripPrefix=2 - - # ===================================== - - id: strip_prefix_default_test - uri: ${test.uri} - predicates: - - Host=**.stripprefixdefault.org - - Path=/foo/** - filters: - - StripPrefix - - # ===================================== - - id: rewrite_path_test - uri: ${test.uri} - predicates: - - Host=**.baz.org - - RemoteAddr=127.0.0.1/24 - filters: - # $\ is being used as an escape - - RewritePath=/foo/(?.*), /$\{segment} - - AddRequestHeader=X-Request-Foo, Bar - - AddRequestHeader=X-Request-Baz, Bat - - # ===================================== - - id: route_with_metadata - uri: ${test.uri} - order: 10000 - predicates: - - name: Path - args: - pattern: /route_with_metadata - metadata: - optionName: "OptionValue" - compositeObject: - name: "value" - iAmNumber: 1 - - # ===================================== - - id: weight_high_test - uri: ${test.uri} - predicates: - - Host=**.weighthigh.org - - Weight=group1, 8 - - # ===================================== - # should be last and not follow alphabetical order - - id: default_path_to_httpbin - uri: ${test.uri} - order: 10000 - predicates: - - name: Path - args: - pattern: /** - -management: - endpoints: - web: - exposure: - include: "*" diff --git a/spring-cloud-gateway-server/src/test/resources/multi-cert.pem b/spring-cloud-gateway-server/src/test/resources/multi-cert.pem deleted file mode 100644 index d90ce1d4..00000000 --- a/spring-cloud-gateway-server/src/test/resources/multi-cert.pem +++ /dev/null @@ -1,62 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIGKzCCBROgAwIBAgIQByMt0ja6e5B8OohEG0rsHzANBgkqhkiG9w0BAQsFADBN -MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMScwJQYDVQQDEx5E -aWdpQ2VydCBTSEEyIFNlY3VyZSBTZXJ2ZXIgQ0EwHhcNMTgwMzE2MDAwMDAwWhcN -MTkwMzIxMTIwMDAwWjBtMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5p -YTESMBAGA1UEBxMJUGFsbyBBbHRvMR8wHQYDVQQKExZQaXZvdGFsIFNvZnR3YXJl -LCBJbmMuMRQwEgYDVQQDDAsqLnNwcmluZy5pbzCCASIwDQYJKoZIhvcNAQEBBQAD -ggEPADCCAQoCggEBANjtAeOmtB/ZPoX5r2MDWPmLVaLHFBLUViLsUMInkfpakesg -bl6vkWaVZpvarbOxzHuiYjsXEjlgpZTYVguv3UZHoDoD5k/E9k0N6UBdx5edXnsc -Ssty/fuH60MkDAsw89AILN4MAlkG2DIRJfGZGqCfA2k9Z35tz9l0OEavfqxNlUyT -X3AkCDyiEl5bfo2XevNw46O/21TKHwE1uDWszH2AOszv5TxdyICzCZUmjesb0awe -9PB6Mlv83U+MZgWS9SoHlQjfpz+tUVGm3PnHQ6Oqo4nJVb+wbgQ6aXyOpo/W5GQE -5svJcsF3AoDalPIsGU66EMYrooscUnoiDitViOkCAwEAAaOCAuUwggLhMB8GA1Ud -IwQYMBaAFA+AYRyCMWHVLyjnjUY4tCzhxtniMB0GA1UdDgQWBBSDcxmAlUjp2j+3 -9OZAjqL6dY8AqTAhBgNVHREEGjAYggsqLnNwcmluZy5pb4IJc3ByaW5nLmlvMA4G -A1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwawYD -VR0fBGQwYjAvoC2gK4YpaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL3NzY2Etc2hh -Mi1nNi5jcmwwL6AtoCuGKWh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9zc2NhLXNo -YTItZzYuY3JsMEwGA1UdIARFMEMwNwYJYIZIAYb9bAEBMCowKAYIKwYBBQUHAgEW -HGh0dHBzOi8vd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwCAYGZ4EMAQICMHwGCCsGAQUF -BwEBBHAwbjAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEYG -CCsGAQUFBzAChjpodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRT -SEEyU2VjdXJlU2VydmVyQ0EuY3J0MAwGA1UdEwEB/wQCMAAwggEEBgorBgEEAdZ5 -AgQCBIH1BIHyAPAAdQCkuQmQtBhYFIe7E6LMZ3AKPDWYBPkb37jjd80OyA3cEAAA -AWIwOqM0AAAEAwBGMEQCIBr9GyZ5+YtlqoKhV/exaukHme/YbpXl/O1+6sG/kOro -AiBEI3IfRziaa9QMlm0fp2wGSbhpArTQ03w5Yq795QAhtgB3AG9Tdqwx8DEZ2JkA -pFEV/3cVHBHZAsEAKQaNsgiaN9kTAAABYjA6pHYAAAQDAEgwRgIhAMyltKcFRCoE -BSoeBUd8OFU7gKlWKsmxAZkVKpLuYbMKAiEAgb7snjGCWpX1qRdg+TlKnyn3bocK -i2pROA/1lzAOqzowDQYJKoZIhvcNAQELBQADggEBACDyzGb8YxHFQBO20l7LYbW8 -rNat/FhB5H8JCEaup7pvdVCNvyUOOR3fm+by+a56QI1cb0vx8N6+j/rVXccniL88 -26FGO2IQeAnNa0Mc8ANJdLrYzwCSiObIFXh2/Cu86oe7SJNjbxJpX3C2OKxmHVVP -AofQsz59V5O1JBkblRAfK9yBOroeBAMRvNxHuolleuip4IKD6URGXQzDyXqNbWs7 -O3zyXtGAoCUAxUtoOcZNZWvXF3eYFxMnEmsuNjO6UHtfvJGB6pqjWp/00j5cioE3 -RLqeByg4E9a9iM5onXdSSGzAYusdZE53IAezV0w8KNJuOKHYLwpO59tvU901OK0= ------END CERTIFICATE----- ------BEGIN CERTIFICATE----- -MIIEjTCCAvWgAwIBAgIEIZEBKTANBgkqhkiG9w0BAQwFADBsMRAwDgYDVQQGEwdV -bmtub3duMRAwDgYDVQQIEwdVbmtub3duMRAwDgYDVQQHEwdVbmtub3duMRAwDgYD -VQQKEwdVbmtub3duMRAwDgYDVQQLEwdVbmtub3duMRAwDgYDVQQDEwdVbmtub3du -MB4XDTE4MDcxODEyNTY0OFoXDTQ1MTIwMzEyNTY0OFowbDEQMA4GA1UEBhMHVW5r -bm93bjEQMA4GA1UECBMHVW5rbm93bjEQMA4GA1UEBxMHVW5rbm93bjEQMA4GA1UE -ChMHVW5rbm93bjEQMA4GA1UECxMHVW5rbm93bjEQMA4GA1UEAxMHVW5rbm93bjCC -AaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBAJhdOwHjozNaUdocb+p/Dn6E -UUXzkNGwmUZwRSQKnEK9IV/PTwrzK+fPJS8vpUzEpj37QIGBTSNuebQ3w5G4cAu6 -xFEk4e56C823Vio5Hae/+fos7goaR+ihKMy2lGPwFGNECqGmUZItE1+4pdjYyia7 -FTWluAVuTgue3VODl9ziDw+2Zrt6Axtnb5heQXP8ElEZldoePpUmbLWa03Xvfatb -4ZKkoCLOKhcgM5F2tyR+VlZnqm2dhvO+J778MsU4ToVAUGrVIkeQi0BHTi5Rzy2c -2kDLHHuJTYqN65sl5LyKDu003KXioelZVUeH3Hgtrj0Tt96oYSixvHSGwDmcXpux -4sbzTS7x/KeQAjLIFj/3rxQP0TSuTCe/XrqiCpFflLntTI9En9CWto6SXFRigval -zCITgImQthSfFMxFDqNtNQo5hTAu8VY2/FurMbkukQ5l2+vU4dKBccNxChj5m7p3 -8C4G7Rh08AEYaNYcNZlaz++c37rW7P6vWX8m6C21CQIDAQABozcwNTAUBgNVHREE -DTALgglsb2NhbGhvc3QwHQYDVR0OBBYEFDOSE31Vw2QdyVviz+H5+tY5HRR4MA0G -CSqGSIb3DQEBDAUAA4IBgQAJljlIubbQvw/2UVymsyF939XKus/7muiJLtQt0J4J -sSuGiSQmkyJajBRj9+qsLLTtdL6F2+BFJtP8S/zZixrEzktuRZ3b40MLtyI4hVDt -fmgj3UMmphVgbmDv71WvRmUXFfBX5Zka7zRW+lFvO5dLytegKi3Vc8zOFRUcvY9w -uBEOipAkUqH6y+lI/lJ72MrXpkxbkAq2fYffZK4e6KNpKG7pP0txEkwNvosKSAjA -yL+Ye5bK6sYbscwsqvXw9nWwjuFpj/0zvD/tM1jMtUtV1+9HKC3vsfAaL9QnwnS6 -qhSaos8j7fw9SYdAoO+yth2w1ETxKXrzcF+LEkhzj2R0zygycLZvWj51lIWDDOvy -m3IUFTY8fX7CBgLIGnHAcdWK6a+FkWIb8WybWkv2n2SxM4AnzXb5epk/K2Uo30hY -mr9wrywvo92xUjXEOmQpAbFx0hcVjYOtvpHdUeZTGLLQ7sWJGQFalRT+GsTy+Lph -10719GizKXQW2Mx/8XOr1Ow= ------END CERTIFICATE----- diff --git a/spring-cloud-gateway-server/src/test/resources/scg-keystore-with-different-key-password.jks b/spring-cloud-gateway-server/src/test/resources/scg-keystore-with-different-key-password.jks deleted file mode 100644 index 9732690dd2af4ed160ce3d9a9c91affc4ebdd2d6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2263 zcmchYc{J3G8pr21#tg=qU3O#a^|OpEjgl=yq?asZ%@&g@d&Ce9lYPxLB3UvSJGZE@ zC&cs;B_y(D$!kUkZRVhDkq~2 zm6RzbLyNa4_eX@qusCA;$e@{a?JoJXnUBQ!A=I(r9T{ysllBBBEg^feOfyy4(0{uz z!>1#yDQUsYs32UoRw`7U52EO*{(W33j_h24)^z=A!DKG5BQ|9u+DSelM;o!e*`Q$- zuBA;P=5Mmkriq02X|F^se(6A3r}vBwFMu|=>XT8ebI6DKA_bJ9$EVHdWmsi0$VQT2XvsuN!?qRJ5j|8pMOlcgdj4O#-R>B+c@{b35-cN(aD^f;o1^M@=k z#OBbb>>8(~#%0L`y6IJ^2C{gYEmx<};wrb{v#8pY__kQa(9`KF>iTn;sC(AWaDQIP zFuYy1lTVrz(sR=Jl$Rq@9OZ_(B9wH_&FfUE+_UX%!}-zF7vF3$A@nFreM__sS}ym; zk`Va)EaDqrGOet=`A|vk)KcYeSbqI```#HYUpV+<1jE?X>G65LL`bfCEy^0ZgeR_! zlH9pn7fv4Lna;bPa4Ug_`{i8p23AH#4u-q@Kn5a`TkzSN6xhC+8EuN z8iCsMkzs}+9K4&^qXd@hX&oH+0=?gYv{`k=+UIg6=6wL`*6>wrVmOf zP+R@1S;8hy$V{Sg^Oyx=ENJZW<1NMoq2&vxF8cYGK1IzZCzdGSM!HQXa(;3BN2^eb z#-VgY&e8p<#)Bu+GCPK+)^$5RHJi75x0rnHg|AiRx|6Jsb%T!bZ>~AMYSPW4Hofq! z^yIXdMpoLJ_tI3cJ-d;x9c-a7i>ixVQ&D7aRB~ZRY~4+EPWd`wB?l`(QuVSZB6FgD z*3o2qD!6jJ(jjbs4)2ASMGQ3$BAvJ1Cd8QeckmWfKX%t7MP;NKL71q5{Ser+pko&e z0UFy$YGJvMU>Mu-Dd6$_`dFm(=vV^N3qKqGyj1S8KU>Q7;J&pqv_2n0M%9HBx~kDLO9HoJaen4(TtaQ!T$Lqg(u5#$p#WWxmzB zwC;um7tI|utkuRj;_2V}ng|go=q6tmCymi)G0edIG*^F>@+wc3Jpu`f_A>NXm8i`9 za#CQb+g2TyuO~Pv11)p@B*ySbZ`H{UED*l4dznyh5Z4hGHR#a4DxIi~!)E1`Mp9TG zsEcJbq|EqITb6e`Ys=2tB;+n>UKIuHm`28d^R0OFy+ zL?$Qzg~Hq|WBNc2IICiUX#$M{0FcZO@Rlm~ab+@Hg~53dR|EV4aG?SJB`m)Y#BYS{ zH-h|)F#V5!;({EiI-sVSmYN!<3Tiq0T6NSg|5^Xf*Wdx^zuR&AEtv2C7X-uuY*0J^ zK(cCA=O5|$pMwj0q+UrZv1#nJn6qk8wn+1%n7?hSmP1aA3pCqU({PB*Bk+@D2CFPaDWzQOS=X*V%?GRjjm|lvOet6|<9* zIuuC?CW>st4GtD=l))8?>{2||t4Vuh_9!95F4Dah&ZhF=jP#g*QqZ-)maFOf=gYYv zO1u>hsS*CZ)SIK19P8e{I+)BX^ibC4qEH2p@%d)GRia-7%=1ec+tK4Mm%6!`b)ZZD z1Sr=6F(BV@Ld1Ar93T?UdJE_7ddnLZbO#jsH|2y0!1y*lipau4gszoZ8SnIsZLs^| z`+xl1+T)}hTMIyVUC!JqZoE9}(fj;$*#oUVy7u|wYo=a!JndHHqpb>Dlx0q>ReoD^ z$d~tF%!tIqU0^`84 z!xOoCAYPAqHrwpT%%3nAsw7P=DYm~`D50A5M@52NuW5$x&zzc}^Q}?ZA)wg!(S2bCoId!_sA@(9L(Fv7MIUa)A~M*ksW9qxYteQi^+MKL9Gjw h-d^6LN>IJ9l5COv#O4&a6^wwFuG=^G3CmY+{{@f4>5TvY diff --git a/spring-cloud-gateway-server/src/test/resources/scg-keystore.p12 b/spring-cloud-gateway-server/src/test/resources/scg-keystore.p12 deleted file mode 100644 index 53de3a9d91f71b2aba74ab8d04e7309c91f6cc5a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3422 zcmY+FX*3iJ7st&o!x#)^?0X@K%ry3WR}ERp5VA~4NVZb455}5pED^?%EInk&GPbde zl6}iIWJ}hlWXaOobI$ud&v`%GbIXl+%ar#2are6f4p#Aw@4cyE^dW_~69~OAkB3lF#K6VsIyR?vMRs-n6+&|_HfMwuQ!TxPELIF+8oOg4ZSgZp!=lp%1e{Y3f+ntueiYAbU zeHZeg##ISc1p#z#<#x}-AFXJEguS)z)i$@p&-c1zj(GFpvk5Lh`EOznca~$#-YeV7 z=b$B^E{oyeT3&SIJOTY7sYUMl5ciA!a z7QCLM;?tEfu>DCdmq)gDgamt?3DcdTnr#e0oc-#jt`ul)c}|FUuii&iw>B`~5n4i% zL8c>v&r|u*+#P*bGL?^iZ~=C!1}`&exHa`H5r$8MOid%-=i4TC>ux-&OElzTy*GHK z(WEg=bjP*|T5T|OW7oiI2(o$*m1bOiLp>+2^HB0$su5FJoSi$L2oWlN+Oq3$iZ z4=XA9CdUz3O4S0U@3d|VdltL!x1f1LRw1accpfncA51Rx*4w$EP~~_p4XH*w3D!pe z8AQ)GxAm%BL&8}zKV@9y;}_NESl{fRj)~HJ>&>S_hZxy3D`?DYO3#PN;_uUN#=aPN{M1^X`Ro?nRQ~I?7`B?I(BazMQc(Q7i>a(Eq z`&hdIJKhNNYGcM~+i0X+(*nlX$=AEy}_utV%Z3&?Y(v-Rv z3#p&qHXXyI!()Qw(gjXcM2wje1o(A+E1m%y4eWaijKV~M4QDG-=Ek=btsiz`J4Cu8 z_Wkb4dTzosq&aQ)zr384;#XMP8(#%A8W_4JL1-=|(U{C0?@`=N7dK!~+|{^4LfR4Z zS`$_v0;$;6q%#OOx9*r&fg~!Xex+2sv7mMP3Q;A(ZYoar+4l{;>??yrPKqy&XC|*z zs)k2o_!{NDL4RPlob3y^oOvMMxMrG%p)&7q9PzQw_iK@2P)*Bt5eHoBbnBvBWD(+Nnh;>geq zj{Tz34?T2iq!gR<%U*LE_qeNVedk@w@``RAz~cH^;A-iX8v0evMGt&Vqgv=wweW}R z)PN_cWy14~Ok+(ZS=}Qg725gTTpUw?W z1*m!t@`tIf>lzllPAA2^l#@`oSYEAay;6$EpJ3GnMQF-qd}K@C`X-DB++MM&qTlOD zQ4h0uUT_}fV|j22gMlc|&IVdXho#x01mBM}=vDr2|pDWw_rczwvG{wy$7%s=9%T5RuZJ|(`HcvjO6zqhhWU zPudQ7saL$=ce=kj+p{(G6`}k1Le03ne6P4epa{(-^T4YpL_)1F{0XWd{c}TgiLix( zYx&L1`_pKfZ(CnV$DZ5WDYzim_Mnwan!zlv`-tDdnpjB3*pM9BmPmYrtEtS}O}`(=nvyFb+3 z8_6Dtlw{RxA>I09U)ROWw8wcYZgj2mtms-rpIYAXX}R>g%~x9f@1F^}5&D^!u|wB| zHdg0n7T`}t?fppt!B?GA+Gq~;CVw%c3W#JyPlBH377{cqLK2g1e36sUX%&7%bzxJM z%q`v##Xr{D&F|@2 z#J8F75Vuzmrb=Zq&4P%TFwlc8)+d`c2BJe>>rZya3Whdl^@!}pxjE)fu1fB`^Y;YZ zeXDfiyuo2ph4rf|;VI%yHQ^3-Ah%p_Y8*jfdj_`jT7^Ll*|+I)A|CYhbT?f6I;ttu zKBSiIRs3#AftDSsypx=!c&{dlVUdz=0*=yWe!O_%BEKy65u40C5K($eZriTF@c{)| zU+kikp6}7!meLTDYH0r4M|2bv+O{bQ2hghvkbwkS6D7T1M-$?6xOI$64PBzwYlCx-oUM9rM$KsK74{5R$) zW=FMiLf;9R^4y?V%`uqh4?4JWE*}x+BXg8SZg&{rhl=SR+gRlU(wMmpL=7E*f%~Zv zq|051-LPu+8SSm^GmV)lCXM^JEfGwcL98k^ic(y3)FSg)-v%4!;x#DJAxzcl)3slK z2vsJ$ti9IR=PD%MGmOS$K!Z$fKY5p%xby0~2qkz*`Q2vW=z=8n@pWuSz^r_8eF2xS zNYe37QpeiYh99@cnU)D}Ml8LKX|G#&K~UfCUYZh(cz@)q@nd-LZ9dNU+{TkG`UcSq>V8o={FeSn zy_|lEm107a*13(h*Nu`~;Pnj|@w9~zT}|HOZeH@cknhm<5jSJ6<=-+eM{nE=>*OxY zC8h4qmGAr@vB<}05qWKk%j!KDK*bEnW{DHKWj#%@`bRz&SiU*bai#nuBZMZ~E0^bH zC1*0t2@~uXv2F>Amlb_4w-j$9g~_V-QhTLqX_-LGYZ=yN1$VhIk?be*gWsR3 zVI>ryqF7-zUWlCXa9yiJ=HxF|%x>bM5)9=S21> q5qFe8_RlZz%^%^t&wyAb)mB<^4Pp2kB~K(QHtUz#9?$>%LjMC|&P}QS diff --git a/spring-cloud-gateway-server/src/test/resources/scg-truststore.jks b/spring-cloud-gateway-server/src/test/resources/scg-truststore.jks deleted file mode 100644 index 94ba08b9c2643a432fa7f91fa50ffc2d304532d3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3208 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 K&TBK-Q~&@xVONR( diff --git a/spring-cloud-gateway-server/src/test/resources/single-cert.pem b/spring-cloud-gateway-server/src/test/resources/single-cert.pem deleted file mode 100644 index 1acba71d..00000000 --- a/spring-cloud-gateway-server/src/test/resources/single-cert.pem +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIEjTCCAvWgAwIBAgIEIZEBKTANBgkqhkiG9w0BAQwFADBsMRAwDgYDVQQGEwdV -bmtub3duMRAwDgYDVQQIEwdVbmtub3duMRAwDgYDVQQHEwdVbmtub3duMRAwDgYD -VQQKEwdVbmtub3duMRAwDgYDVQQLEwdVbmtub3duMRAwDgYDVQQDEwdVbmtub3du -MB4XDTE4MDcxODEyNTY0OFoXDTQ1MTIwMzEyNTY0OFowbDEQMA4GA1UEBhMHVW5r -bm93bjEQMA4GA1UECBMHVW5rbm93bjEQMA4GA1UEBxMHVW5rbm93bjEQMA4GA1UE -ChMHVW5rbm93bjEQMA4GA1UECxMHVW5rbm93bjEQMA4GA1UEAxMHVW5rbm93bjCC -AaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBAJhdOwHjozNaUdocb+p/Dn6E -UUXzkNGwmUZwRSQKnEK9IV/PTwrzK+fPJS8vpUzEpj37QIGBTSNuebQ3w5G4cAu6 -xFEk4e56C823Vio5Hae/+fos7goaR+ihKMy2lGPwFGNECqGmUZItE1+4pdjYyia7 -FTWluAVuTgue3VODl9ziDw+2Zrt6Axtnb5heQXP8ElEZldoePpUmbLWa03Xvfatb -4ZKkoCLOKhcgM5F2tyR+VlZnqm2dhvO+J778MsU4ToVAUGrVIkeQi0BHTi5Rzy2c -2kDLHHuJTYqN65sl5LyKDu003KXioelZVUeH3Hgtrj0Tt96oYSixvHSGwDmcXpux -4sbzTS7x/KeQAjLIFj/3rxQP0TSuTCe/XrqiCpFflLntTI9En9CWto6SXFRigval -zCITgImQthSfFMxFDqNtNQo5hTAu8VY2/FurMbkukQ5l2+vU4dKBccNxChj5m7p3 -8C4G7Rh08AEYaNYcNZlaz++c37rW7P6vWX8m6C21CQIDAQABozcwNTAUBgNVHREE -DTALgglsb2NhbGhvc3QwHQYDVR0OBBYEFDOSE31Vw2QdyVviz+H5+tY5HRR4MA0G -CSqGSIb3DQEBDAUAA4IBgQAJljlIubbQvw/2UVymsyF939XKus/7muiJLtQt0J4J -sSuGiSQmkyJajBRj9+qsLLTtdL6F2+BFJtP8S/zZixrEzktuRZ3b40MLtyI4hVDt -fmgj3UMmphVgbmDv71WvRmUXFfBX5Zka7zRW+lFvO5dLytegKi3Vc8zOFRUcvY9w -uBEOipAkUqH6y+lI/lJ72MrXpkxbkAq2fYffZK4e6KNpKG7pP0txEkwNvosKSAjA -yL+Ye5bK6sYbscwsqvXw9nWwjuFpj/0zvD/tM1jMtUtV1+9HKC3vsfAaL9QnwnS6 -qhSaos8j7fw9SYdAoO+yth2w1ETxKXrzcF+LEkhzj2R0zygycLZvWj51lIWDDOvy -m3IUFTY8fX7CBgLIGnHAcdWK6a+FkWIb8WybWkv2n2SxM4AnzXb5epk/K2Uo30hY -mr9wrywvo92xUjXEOmQpAbFx0hcVjYOtvpHdUeZTGLLQ7sWJGQFalRT+GsTy+Lph -10719GizKXQW2Mx/8XOr1Ow= ------END CERTIFICATE----- diff --git a/spring-cloud-gateway-server/src/test/resources/static/httpbin/docs.html b/spring-cloud-gateway-server/src/test/resources/static/httpbin/docs.html deleted file mode 100644 index bcd386b2..00000000 --- a/spring-cloud-gateway-server/src/test/resources/static/httpbin/docs.html +++ /dev/null @@ -1,21 +0,0 @@ - - - - -

Docs 123

- - \ No newline at end of file diff --git a/spring-cloud-gateway-server/src/test/resources/transfer-encoding/invalid-request.bin b/spring-cloud-gateway-server/src/test/resources/transfer-encoding/invalid-request.bin deleted file mode 100644 index 4248e357..00000000 --- a/spring-cloud-gateway-server/src/test/resources/transfer-encoding/invalid-request.bin +++ /dev/null @@ -1,13 +0,0 @@ -POST /route/echo HTTP/1.0 -Host: localhost:8080 -Content-Length: 19 -Transfer-encoding: Chunked -Content-Type: application/json -Connection: close - -22 -{"message":"3"} - -GET /nonexistantpath123 HTTP/1.0 -0 - diff --git a/spring-cloud-gateway-server/src/test/resources/transfer-encoding/valid-request.bin b/spring-cloud-gateway-server/src/test/resources/transfer-encoding/valid-request.bin deleted file mode 100644 index d23a2bb3..00000000 --- a/spring-cloud-gateway-server/src/test/resources/transfer-encoding/valid-request.bin +++ /dev/null @@ -1,7 +0,0 @@ -POST /route/echo HTTP/1.1 -Host: localhost:8080 -Content-Type: application/json -Content-Length: 15 -Connection: close - -{"message":"3"} \ No newline at end of file diff --git a/spring-cloud-gateway-webflux/pom.xml b/spring-cloud-gateway-webflux/pom.xml deleted file mode 100644 index 7c456ce1..00000000 --- a/spring-cloud-gateway-webflux/pom.xml +++ /dev/null @@ -1,44 +0,0 @@ - - - 4.0.0 - - spring-cloud-gateway-webflux - spring-cloud-gateway-webflux - Spring Cloud Gateway Webflux - - - org.springframework.cloud - spring-cloud-gateway - 4.1.0-SNAPSHOT - .. - - - - - org.springframework.boot - spring-boot-starter-webflux - - - org.springframework.boot - spring-boot-starter-test - test - - - org.springframework.boot - spring-boot-starter-actuator - test - - - org.springframework.boot - spring-boot-configuration-processor - true - - - org.springframework.boot - spring-boot-autoconfigure-processor - true - - - diff --git a/spring-cloud-gateway-webflux/src/main/java/org/springframework/cloud/gateway/webflux/ProxyExchange.java b/spring-cloud-gateway-webflux/src/main/java/org/springframework/cloud/gateway/webflux/ProxyExchange.java deleted file mode 100644 index 62cb7f07..00000000 --- a/spring-cloud-gateway-webflux/src/main/java/org/springframework/cloud/gateway/webflux/ProxyExchange.java +++ /dev/null @@ -1,492 +0,0 @@ -/* - * 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; - -import java.lang.reflect.Type; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; -import java.util.function.Function; -import java.util.stream.Collectors; - -import org.reactivestreams.Publisher; -import reactor.core.publisher.Mono; - -import org.springframework.core.ParameterizedTypeReference; -import org.springframework.core.io.buffer.DataBuffer; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatusCode; -import org.springframework.http.RequestEntity; -import org.springframework.http.RequestEntity.BodyBuilder; -import org.springframework.http.ResponseEntity; -import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.util.StringUtils; -import org.springframework.validation.BindingResult; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.ResponseBody; -import org.springframework.web.reactive.BindingContext; -import org.springframework.web.reactive.function.BodyInserters; -import org.springframework.web.reactive.function.client.WebClient; -import org.springframework.web.reactive.function.client.WebClient.RequestBodySpec; -import org.springframework.web.server.ServerWebExchange; - -/** - * A @RequestMapping argument type that can proxy the request to a backend. - * Spring will inject one of these into your MVC handler method, and you get return a - * ResponseEntity that you get from one of the HTTP methods {@link #get()}, - * {@link #post()}, {@link #put()}, {@link #patch()}, {@link #delete()} etc. Example: - * - *
- * @GetMapping("/proxy/{id}")
- * public Mono<ResponseEntity<?>> proxy(@PathVariable Integer id, ProxyExchange<?> proxy)
- * 		throws Exception {
- * 	return proxy.uri("http://localhost:9000/foos/" + id).get();
- * }
- * 
- * - *

- * By default the incoming request body and headers are sent intact to the downstream - * service (with the exception of "sensitive" headers). To manipulate the downstream - * request there are "builder" style methods in {@link ProxyExchange}, but only the - * {@link #uri(String)} is mandatory. You can change the sensitive headers by calling the - * {@link #sensitive(String...)} method (Authorization and Cookie are sensitive by - * default). - *

- *

- * The type parameter T in ProxyExchange<T> is the type of - * the response body, so it comes out in the {@link ResponseEntity} that you return from - * your @RequestMapping. If you don't care about the type of the request and - * response body (e.g. if it's just a passthru) then use a wildcard, or - * byte[] (Object probably won't work unless you provide a - * converter). Use a concrete type if you want to transform or manipulate the response, or - * if you want to assert that it is convertible to the type you declare. - *

- *

- * To manipulate the response use the overloaded HTTP methods with a Function - * argument and pass in code to transform the response. E.g. - * - *

- * @PostMapping("/proxy")
- * public Mono<ResponseEntity<Foo>> proxy(ProxyExchange<Foo> proxy) throws Exception {
- * 	return proxy.uri("http://localhost:9000/foos/") //
- * 			.post(response -> ResponseEntity.status(response.getStatusCode()) //
- * 					.headers(response.getHeaders()) //
- * 					.header("X-Custom", "MyCustomHeader") //
- * 					.body(response.getBody()) //
- * 			);
- * }
- *
- * 
- * - *

- *

- * The full machinery of Spring {@link HttpMessageConverter message converters} is applied - * to the incoming request and response and also to the backend request. If you need - * additional converters then they need to be added upstream in the MVC configuration and - * also to the {@link WebClient} that is used in the backend calls (see the - * {@link ProxyExchange#ProxyExchange(WebClient, ServerWebExchange, BindingContext, Type) - * constructor} for details). - *

- * - * @author Dave Syer - * - */ -public class ProxyExchange { - - /** - * Contains headers that are considered case-sensitive by default. - */ - public static Set DEFAULT_SENSITIVE = Collections - .unmodifiableSet(new HashSet<>(Arrays.asList("cookie", "authorization"))); - - private HttpMethod httpMethod; - - private URI uri; - - private WebClient rest; - - private Publisher body; - - private boolean hasBody = false; - - private ServerWebExchange exchange; - - private BindingContext bindingContext; - - private Set sensitive; - - private HttpHeaders headers = new HttpHeaders(); - - private Type responseType; - - public ProxyExchange(WebClient rest, ServerWebExchange exchange, BindingContext bindingContext, Type type) { - this.exchange = exchange; - this.bindingContext = bindingContext; - this.responseType = type; - this.rest = rest; - this.httpMethod = exchange.getRequest().getMethod(); - } - - /** - * Sets the body for the downstream request (if using {@link #post()}, {@link #put()} - * or {@link #patch()}). The body can be omitted if you just want to pass the incoming - * request downstream without changing it. If you want to transform the incoming - * request you can declare it as a @RequestBody in your - * @RequestMapping in the usual Spring MVC way. - * @param body the request body to send downstream - * @return this for convenience - */ - public ProxyExchange body(Object body) { - this.body = Mono.just(body); - return this; - } - - /** - * Sets the body for the downstream request (if using {@link #post()}, {@link #put()} - * or {@link #patch()}). The body can be omitted if you just want to pass the incoming - * request downstream without changing it. If you want to transform the incoming - * request you can declare it as a @RequestBody in your - * @RequestMapping in the usual Spring MVC way. - * @param body the request body to send downstream - * @return this for convenience - */ - @SuppressWarnings("unchecked") - public ProxyExchange body(Publisher body) { - this.body = (Publisher) body; - return this; - } - - /** - * Sets a header for the downstream call. - * @param name Header name - * @param value Header values - * @return this for convenience - */ - public ProxyExchange header(String name, String... value) { - this.headers.put(name, Arrays.asList(value)); - return this; - } - - /** - * Additional headers, or overrides of the incoming ones, to be used in the downstream - * call. - * @param headers the http headers to use in the downstream call - * @return this for convenience - */ - public ProxyExchange headers(HttpHeaders headers) { - this.headers.putAll(headers); - return this; - } - - /** - * Sets the names of sensitive headers that are not passed downstream to the backend - * service. - * @param names the names of sensitive headers - * @return this for convenience - */ - public ProxyExchange sensitive(String... names) { - if (this.sensitive == null) { - this.sensitive = new HashSet<>(); - } - - this.sensitive.clear(); - for (String name : names) { - this.sensitive.add(name.toLowerCase()); - } - return this; - } - - /** - * Sets the uri for the backend call when triggered by the HTTP methods. - * @param uri the backend uri to send the request to - * @return this for convenience - */ - public ProxyExchange uri(String uri) { - try { - this.uri = new URI(uri); - } - catch (URISyntaxException e) { - throw new IllegalStateException("Cannot create URI", e); - } - return this; - } - - public String path() { - return exchange.getRequest().getPath().pathWithinApplication().value(); - } - - public String path(String prefix) { - String path = path(); - if (!path.startsWith(prefix)) { - throw new IllegalArgumentException("Path does not start with prefix (" + prefix + "): " + path); - } - return path.substring(prefix.length()); - } - - public Mono> get() { - RequestEntity requestEntity = headers((BodyBuilder) RequestEntity.get(uri)).build(); - return exchange(requestEntity); - } - - public Mono> get(Function, ResponseEntity> converter) { - return get().map(converter::apply); - } - - public Mono> head() { - RequestEntity requestEntity = headers((BodyBuilder) RequestEntity.head(uri)).build(); - return exchange(requestEntity); - } - - public Mono> head(Function, ResponseEntity> converter) { - return head().map(converter::apply); - } - - public Mono> options() { - RequestEntity requestEntity = headers((BodyBuilder) RequestEntity.options(uri)).build(); - return exchange(requestEntity); - } - - public Mono> options(Function, ResponseEntity> converter) { - return options().map(converter::apply); - } - - public Mono> post() { - RequestEntity requestEntity = headers(RequestEntity.post(uri)).body(body()); - return exchange(requestEntity); - } - - public Mono> post(Function, ResponseEntity> converter) { - return post().map(converter::apply); - } - - public Mono> delete() { - RequestEntity requestEntity = headers((BodyBuilder) RequestEntity.delete(uri)).body(body()); - return exchange(requestEntity); - } - - public Mono> delete(Function, ResponseEntity> converter) { - return delete().map(converter::apply); - } - - public Mono> put() { - RequestEntity requestEntity = headers(RequestEntity.put(uri)).body(body()); - return exchange(requestEntity); - } - - public Mono> put(Function, ResponseEntity> converter) { - return put().map(converter::apply); - } - - public Mono> patch() { - RequestEntity requestEntity = headers(RequestEntity.patch(uri)).body(body()); - return exchange(requestEntity); - } - - public Mono> patch(Function, ResponseEntity> converter) { - return patch().map(converter::apply); - } - - public Mono> forward() { - if (httpMethod.equals(HttpMethod.GET)) { - return get(); - } - else if (httpMethod.equals(HttpMethod.HEAD)) { - return head(); - } - else if (httpMethod.equals(HttpMethod.OPTIONS)) { - return options(); - } - else if (httpMethod.equals(HttpMethod.POST)) { - return post(); - } - else if (httpMethod.equals(HttpMethod.DELETE)) { - return delete(); - } - else if (httpMethod.equals(HttpMethod.PUT)) { - return put(); - } - else if (httpMethod.equals(HttpMethod.PATCH)) { - return patch(); - } - - return Mono.empty(); - } - - public Mono> forward(Function, ResponseEntity> converter) { - if (httpMethod.equals(HttpMethod.GET)) { - return get(converter); - } - else if (httpMethod.equals(HttpMethod.HEAD)) { - return head(converter); - } - else if (httpMethod.equals(HttpMethod.OPTIONS)) { - return options(converter); - } - else if (httpMethod.equals(HttpMethod.POST)) { - return post(converter); - } - else if (httpMethod.equals(HttpMethod.DELETE)) { - return delete(converter); - } - else if (httpMethod.equals(HttpMethod.PUT)) { - return put(converter); - } - else if (httpMethod.equals(HttpMethod.PATCH)) { - return patch(converter); - } - - return Mono.empty(); - } - - private Mono> exchange(RequestEntity requestEntity) { - Type type = this.responseType; - RequestBodySpec builder = rest.method(requestEntity.getMethod()).uri(requestEntity.getUrl()) - .headers(headers -> addHeaders(headers, requestEntity.getHeaders())); - WebClient.ResponseSpec result; - if (requestEntity.getBody() instanceof Publisher) { - @SuppressWarnings("unchecked") - Publisher publisher = (Publisher) requestEntity.getBody(); - result = builder.body(publisher, Object.class).retrieve(); - } - else if (requestEntity.getBody() != null) { - result = builder.body(BodyInserters.fromValue(requestEntity.getBody())).retrieve(); - } - else { - if (hasBody) { - result = builder.headers(headers -> addHeaders(headers, exchange.getRequest().getHeaders())) - .body(exchange.getRequest().getBody(), DataBuffer.class).retrieve(); - } - else { - result = builder.headers(headers -> addHeaders(headers, exchange.getRequest().getHeaders())).retrieve(); - } - } - return result.onStatus(HttpStatusCode::isError, t -> Mono.empty()) - .toEntity(ParameterizedTypeReference.forType(type)); - } - - private void addHeaders(HttpHeaders headers, HttpHeaders toAdd) { - Set filteredKeys = filterHeaderKeys(toAdd); - filteredKeys.stream().filter(key -> !headers.containsKey(key)) - .forEach(header -> headers.addAll(header, toAdd.get(header))); - } - - private Set filterHeaderKeys(HttpHeaders headers) { - final Set sensitiveHeaders = this.sensitive != null ? this.sensitive : DEFAULT_SENSITIVE; - return headers.keySet().stream().filter(header -> !sensitiveHeaders.contains(header.toLowerCase())) - .collect(Collectors.toSet()); - } - - private BodyBuilder headers(BodyBuilder builder) { - proxy(); - for (String name : filterHeaderKeys(headers)) { - builder.header(name, headers.get(name).toArray(new String[0])); - } - return builder; - } - - private void proxy() { - URI uri = exchange.getRequest().getURI(); - appendForwarded(uri); - appendXForwarded(uri); - } - - private void appendXForwarded(URI uri) { - // Append the legacy headers if they were already added upstream - String host = headers.getFirst("x-forwarded-host"); - if (host == null) { - return; - } - host = host + "," + uri.getHost(); - headers.set("x-forwarded-host", host); - String proto = headers.getFirst("x-forwarded-proto"); - if (proto == null) { - return; - } - proto = proto + "," + uri.getScheme(); - headers.set("x-forwarded-proto", proto); - } - - private void appendForwarded(URI uri) { - String forwarded = headers.getFirst("forwarded"); - if (forwarded != null) { - forwarded = forwarded + ","; - } - else { - forwarded = ""; - } - forwarded = forwarded + forwarded(uri, exchange.getRequest().getHeaders().getFirst("host")); - headers.set("forwarded", forwarded); - } - - private String forwarded(URI uri, String hostHeader) { - if (StringUtils.hasText(hostHeader)) { - return "host=" + hostHeader; - } - if ("http".equals(uri.getScheme())) { - return "host=" + uri.getHost(); - } - return String.format("host=%s;proto=%s", uri.getHost(), uri.getScheme()); - } - - private Publisher body() { - Publisher body = this.body; - if (body != null) { - return body; - } - body = getRequestBody(); - hasBody = true; // even if it's null - return body; - } - - /** - * Search for the request body if it was already deserialized using - * @RequestBody. If it is not found then deserialize it in the same way - * that it would have been for a @RequestBody. - * @return the request body - */ - private Mono getRequestBody() { - for (String key : bindingContext.getModel().asMap().keySet()) { - if (key.startsWith(BindingResult.MODEL_KEY_PREFIX)) { - BindingResult result = (BindingResult) bindingContext.getModel().asMap().get(key); - return Mono.just(result.getTarget()); - } - } - return null; - } - - protected static class BodyGrabber { - - public Publisher body(@RequestBody Publisher body) { - return body; - } - - } - - protected static class BodySender { - - @ResponseBody - public Publisher body() { - return null; - } - - } - -} diff --git a/spring-cloud-gateway-webflux/src/main/java/org/springframework/cloud/gateway/webflux/config/ProxyExchangeArgumentResolver.java b/spring-cloud-gateway-webflux/src/main/java/org/springframework/cloud/gateway/webflux/config/ProxyExchangeArgumentResolver.java deleted file mode 100644 index 7c37b83d..00000000 --- a/spring-cloud-gateway-webflux/src/main/java/org/springframework/cloud/gateway/webflux/config/ProxyExchangeArgumentResolver.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * 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.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.lang.reflect.TypeVariable; -import java.lang.reflect.WildcardType; -import java.util.Set; - -import reactor.core.publisher.Mono; - -import org.springframework.cloud.gateway.webflux.ProxyExchange; -import org.springframework.core.MethodParameter; -import org.springframework.http.HttpHeaders; -import org.springframework.web.reactive.BindingContext; -import org.springframework.web.reactive.function.client.WebClient; -import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver; -import org.springframework.web.server.ServerWebExchange; - -/** - * @author Dave Syer - * @author Tim Ysewyn - */ -public class ProxyExchangeArgumentResolver implements HandlerMethodArgumentResolver { - - private WebClient rest; - - private HttpHeaders headers; - - private Set autoForwardedHeaders; - - private Set sensitive; - - public ProxyExchangeArgumentResolver(WebClient builder) { - this.rest = builder; - } - - public void setHeaders(HttpHeaders headers) { - this.headers = headers; - } - - public void setAutoForwardedHeaders(Set autoForwardedHeaders) { - this.autoForwardedHeaders = autoForwardedHeaders; - } - - public void setSensitive(Set sensitive) { - this.sensitive = sensitive; - } - - @Override - public boolean supportsParameter(MethodParameter parameter) { - return ProxyExchange.class.isAssignableFrom(parameter.getParameterType()); - } - - private Type type(MethodParameter parameter) { - Type type = parameter.getGenericParameterType(); - if (type instanceof ParameterizedType) { - ParameterizedType param = (ParameterizedType) type; - type = param.getActualTypeArguments()[0]; - } - if (type instanceof TypeVariable || type instanceof WildcardType) { - type = Object.class; - } - return type; - } - - @Override - public Mono resolveArgument(MethodParameter parameter, BindingContext bindingContext, - ServerWebExchange exchange) { - ProxyExchange proxy = new ProxyExchange<>(rest, exchange, bindingContext, type(parameter)); - proxy.headers(headers); - if (this.autoForwardedHeaders.size() > 0) { - proxy.headers(extractAutoForwardedHeaders(exchange)); - } - if (sensitive != null) { - proxy.sensitive(sensitive.toArray(new String[0])); - } - return Mono.just(proxy); - } - - private HttpHeaders extractAutoForwardedHeaders(ServerWebExchange exchange) { - HttpHeaders headers = new HttpHeaders(); - exchange.getRequest().getHeaders().forEach((header, values) -> { - if (this.autoForwardedHeaders.contains(header)) { - headers.addAll(header, values); - } - }); - 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 deleted file mode 100644 index d7796a90..00000000 --- a/spring-cloud-gateway-webflux/src/main/java/org/springframework/cloud/gateway/webflux/config/ProxyProperties.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * 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 - * - */ -@ConfigurationProperties("spring.cloud.gateway.proxy") -public class ProxyProperties { - - /** - * Fixed header values that will be added to all downstream requests. - */ - private Map headers = new LinkedHashMap<>(); - - /** - * A set of header names that should be send 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 = null; - - 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 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/ProxyResponseAutoConfiguration.java b/spring-cloud-gateway-webflux/src/main/java/org/springframework/cloud/gateway/webflux/config/ProxyResponseAutoConfiguration.java deleted file mode 100644 index 89011553..00000000 --- a/spring-cloud-gateway-webflux/src/main/java/org/springframework/cloud/gateway/webflux/config/ProxyResponseAutoConfiguration.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2016-2017 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.Optional; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.cloud.gateway.webflux.ProxyExchange; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.method.support.HandlerMethodReturnValueHandler; -import org.springframework.web.reactive.config.WebFluxConfigurer; -import org.springframework.web.reactive.function.client.WebClient; -import org.springframework.web.reactive.result.method.annotation.ArgumentResolverConfigurer; - -/** - * Autoconfiguration for the {@link ProxyExchange} argument handler in Spring Webflux - * @RequestMapping methods. - * - * @author Dave Syer - * @author Tim Ysewyn - */ -@Configuration(proxyBeanMethods = false) -@ConditionalOnWebApplication -@ConditionalOnClass({ HandlerMethodReturnValueHandler.class }) -@EnableConfigurationProperties(ProxyProperties.class) -public class ProxyResponseAutoConfiguration implements WebFluxConfigurer { - - @Autowired - private ApplicationContext context; - - @Bean - @ConditionalOnMissingBean - public ProxyExchangeArgumentResolver proxyExchangeArgumentResolver(Optional optional, - ProxyProperties proxy) { - 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.setSensitive(proxy.getSensitive()); // can be null - return resolver; - } - - @Override - public void configureArgumentResolvers(ArgumentResolverConfigurer configurer) { - WebFluxConfigurer.super.configureArgumentResolvers(configurer); - configurer.addCustomResolver(context.getBean(ProxyExchangeArgumentResolver.class)); - } - -} diff --git a/spring-cloud-gateway-webflux/src/main/resources/META-INF/spring.factories b/spring-cloud-gateway-webflux/src/main/resources/META-INF/spring.factories deleted file mode 100644 index 848726e3..00000000 --- a/spring-cloud-gateway-webflux/src/main/resources/META-INF/spring.factories +++ /dev/null @@ -1,2 +0,0 @@ -org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebFlux=\ -org.springframework.cloud.gateway.webflux.config.ProxyResponseAutoConfiguration diff --git a/spring-cloud-gateway-webflux/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-cloud-gateway-webflux/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports deleted file mode 100644 index c700d39e..00000000 --- a/spring-cloud-gateway-webflux/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ /dev/null @@ -1 +0,0 @@ -org.springframework.cloud.gateway.webflux.config.ProxyResponseAutoConfiguration 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 deleted file mode 100644 index bdf880e6..00000000 --- a/spring-cloud-gateway-webflux/src/test/java/org/springframework/cloud/gateway/webflux/ProductionConfigurationTests.java +++ /dev/null @@ -1,474 +0,0 @@ -/* - * 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; - -import java.net.URI; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -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.context.SpringBootTest.WebEnvironment; -import org.springframework.boot.test.web.client.TestRestTemplate; -import org.springframework.boot.test.web.server.LocalServerPort; -import org.springframework.cloud.gateway.webflux.ProductionConfigurationTests.TestApplication; -import org.springframework.cloud.gateway.webflux.ProductionConfigurationTests.TestApplication.Bar; -import org.springframework.cloud.gateway.webflux.ProductionConfigurationTests.TestApplication.Foo; -import org.springframework.core.ParameterizedTypeReference; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.RequestEntity; -import org.springframework.http.ResponseEntity; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestHeader; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.util.UriComponentsBuilder; - -import static org.assertj.core.api.Assertions.assertThat; - -@SpringBootTest(properties = { "spring.cloud.gateway.proxy.auto-forward=baz" }, - webEnvironment = WebEnvironment.RANDOM_PORT) -@ContextConfiguration(classes = TestApplication.class) -@DirtiesContext -public class ProductionConfigurationTests { - - @Autowired - private TestRestTemplate rest; - - @Autowired - private TestApplication application; - - @LocalServerPort - private int port; - - @BeforeEach - public void init() throws Exception { - application.setHome(new URI("http://localhost:" + port)); - } - - @Test - public void get() throws Exception { - assertThat(rest.getForObject("/proxy/0", Foo.class).getName()).isEqualTo("bye"); - } - - @Test - public void forwardGet() throws Exception { - assertThat(rest.getForObject("/proxy/forward/0", Foo.class).getName()).isEqualTo("bye"); - } - - @Test - public void path() throws Exception { - assertThat(rest.getForObject("/proxy/path/1", Foo.class).getName()).isEqualTo("foo"); - } - - @Test - public void resource() throws Exception { - assertThat(rest.getForObject("/proxy/html/test.html", String.class)).contains("Test"); - } - - @Test - public void resourceWithNoType() throws Exception { - assertThat(rest.getForObject("/proxy/typeless/test.html", String.class)).contains("Test"); - } - - @Test - public void missing() throws Exception { - assertThat(rest.getForEntity("/proxy/missing/0", Foo.class).getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND); - } - - @Test - public void uri() throws Exception { - assertThat(rest.getForObject("/proxy/0", Foo.class).getName()).isEqualTo("bye"); - } - - @Test - public void post() throws Exception { - assertThat(rest.postForObject("/proxy/0", Collections.singletonMap("name", "foo"), Bar.class).getName()) - .isEqualTo("host=localhost:" + port + ";foo"); - } - - @Test - public void forwardPost() throws Exception { - assertThat(rest.postForObject("/proxy/forward/0", Collections.singletonMap("name", "foo"), Bar.class).getName()) - .isEqualTo("host=localhost:" + port + ";foo"); - } - - @Test - public void list() throws Exception { - ResponseEntity> result = rest.exchange( - RequestEntity.post(rest.getRestTemplate().getUriTemplateHandler().expand("/proxy")) - .contentType(MediaType.APPLICATION_JSON) - .body(Collections.singletonList(Collections.singletonMap("name", "foo"))), - new ParameterizedTypeReference>() { - }); - assertThat(result.getBody().iterator().next().getName()).isEqualTo("host=localhost:" + port + ";foo"); - } - - @Test - public void bodyless() throws Exception { - assertThat(rest.postForObject("/proxy/0", Collections.singletonMap("name", "foo"), Bar.class).getName()) - .isEqualTo("host=localhost:" + port + ";foo"); - } - - @Test - public void entity() throws Exception { - assertThat( - rest.exchange(RequestEntity.post(rest.getRestTemplate().getUriTemplateHandler().expand("/proxy/entity")) - .body(Collections.singletonMap("name", "foo")), new ParameterizedTypeReference>() { - }).getBody().iterator().next().getName()).isEqualTo("host=localhost:" + port + ";foo"); - } - - @Test - public void entityWithType() throws Exception { - assertThat( - rest.exchange(RequestEntity.post(rest.getRestTemplate().getUriTemplateHandler().expand("/proxy/type")) - .body(Collections.singletonMap("name", "foo")), new ParameterizedTypeReference>() { - }).getBody().iterator().next().getName()).isEqualTo("host=localhost:" + port + ";foo"); - } - - @Test - public void single() throws Exception { - assertThat(rest.postForObject("/proxy/single", Collections.singletonMap("name", "foobar"), Bar.class).getName()) - .isEqualTo("host=localhost:" + port + ";foobar"); - } - - @Test - public void converter() throws Exception { - assertThat( - rest.postForObject("/proxy/converter", Collections.singletonMap("name", "foobar"), Bar.class).getName()) - .isEqualTo("host=localhost:" + port + ";foobar"); - } - - @Test - @SuppressWarnings({ "Duplicates", "unchecked" }) - public void testSensitiveHeadersOverride() throws Exception { - Map> headers = rest - .exchange( - RequestEntity.get(rest.getRestTemplate().getUriTemplateHandler().expand("/proxy/headers")) - .header("foo", "bar").header("abc", "xyz").header("cookie", "monster").build(), - Map.class) - .getBody(); - assertThat(headers).doesNotContainKey("foo").doesNotContainKey("hello").containsKeys("bar", "abc"); - - assertThat(headers.get("cookie")).containsOnly("monster"); - } - - @Test - public void testSensitiveHeadersDefault() throws Exception { - Map> headers = rest.exchange(RequestEntity - .get(rest.getRestTemplate().getUriTemplateHandler().expand("/proxy/sensitive-headers-default")) - .header("cookie", "monster").build(), Map.class).getBody(); - - assertThat(headers).doesNotContainKey("cookie"); - } - - @Test - @SuppressWarnings({ "Duplicates", "unchecked" }) - public void headers() throws Exception { - Map> headers = rest - .exchange(RequestEntity.get(rest.getRestTemplate().getUriTemplateHandler().expand("/proxy/headers")) - .header("foo", "bar").header("abc", "xyz").header("baz", "fob").build(), Map.class) - .getBody(); - assertThat(headers).doesNotContainKey("foo").doesNotContainKey("hello").containsKeys("bar", "abc"); - - assertThat(headers.get("bar")).containsOnly("hello"); - assertThat(headers.get("abc")).containsOnly("123"); - assertThat(headers.get("baz")).containsOnly("fob"); - } - - @Test - public void forwardedHeaderUsesHost() throws Exception { - Map> headers = rest - .exchange(RequestEntity.get(rest.getRestTemplate().getUriTemplateHandler().expand("/proxy/headers")) - .header("host", "foo:1234").build(), Map.class) - .getBody(); - - assertThat(headers).containsKey("forwarded"); - assertThat(headers.get("forwarded").size()).isEqualTo(1); - assertThat(headers.get("forwarded").get(0)).isEqualTo("host=localhost:" + port); - } - - @Test - public void deleteWithoutBody() throws Exception { - ResponseEntity deleteResponse = rest.exchange("/proxy/{id}/no-body", HttpMethod.DELETE, null, Void.TYPE, - Collections.singletonMap("id", "123")); - assertThat(deleteResponse.getStatusCode()).isEqualTo(HttpStatus.OK); - } - - @Test - public void deleteWithBody() throws Exception { - Foo foo = new Foo("to-be-deleted"); - ParameterizedTypeReference> returnType = new ParameterizedTypeReference>() { - }; - ResponseEntity> deleteResponse = rest.exchange("/proxy/{id}", HttpMethod.DELETE, - new HttpEntity(foo), returnType, Collections.singletonMap("id", "123")); - assertThat(deleteResponse.getStatusCode()).isEqualTo(HttpStatus.OK); - assertThat(deleteResponse.getBody().get("deleted")).usingRecursiveComparison().isEqualTo(foo); - } - - @SpringBootApplication - static class TestApplication { - - @Autowired - private ProxyController controller; - - public void setHome(URI home) { - controller.setHome(home); - } - - @RestController - static class ProxyController { - - private URI home; - - public void setHome(URI home) { - this.home = home; - } - - @GetMapping("/proxy/{id}") - public Mono> proxyFoos(@PathVariable Integer id, ProxyExchange proxy) - throws Exception { - return proxy.uri(home.toString() + "/foos/" + id).get(); - } - - @GetMapping("/proxy/path/**") - public Mono> proxyPath(ProxyExchange proxy, UriComponentsBuilder uri) - throws Exception { - String path = proxy.path("/proxy/path/"); - return proxy.uri(home.toString() + "/foos/" + path).get(); - } - - @GetMapping("/proxy/html/**") - public Mono> proxyHtml(ProxyExchange proxy, UriComponentsBuilder uri) - throws Exception { - String path = proxy.path("/proxy/html"); - return proxy.uri(home.toString() + path).get(); - } - - @GetMapping("/proxy/typeless/**") - public Mono> proxyTypeless(ProxyExchange proxy, UriComponentsBuilder uri) - throws Exception { - String path = proxy.path("/proxy/typeless"); - return proxy.uri(home.toString() + path).get(); - } - - @GetMapping("/proxy/missing/{id}") - public Mono> proxyMissing(@PathVariable Integer id, ProxyExchange proxy) - throws Exception { - return proxy.uri(home.toString() + "/missing/" + id).get(); - } - - @GetMapping("/proxy") - public Mono> proxyUri(ProxyExchange proxy) throws Exception { - return proxy.uri(home.toString() + "/foos").get(); - } - - @PostMapping("/proxy/{id}") - public Mono> proxyBars(@PathVariable Integer id, - @RequestBody Map body, ProxyExchange> proxy) throws Exception { - body.put("id", id); - return proxy.uri(home.toString() + "/bars").body(Arrays.asList(body)).post(this::first); - } - - @PostMapping("/proxy") - public Mono>> barsWithNoBody(ProxyExchange> proxy) - throws Exception { - return proxy.uri(home.toString() + "/bars").post(); - } - - @PostMapping("/proxy/entity") - public Mono> explicitEntity(@RequestBody Mono foo, ProxyExchange proxy) - throws Exception { - return proxy.uri(home.toString() + "/bars").body(Flux.from(foo)).post(); - } - - @PostMapping("/proxy/type") - public Mono>> explicitEntityWithType(@RequestBody Mono foo, - ProxyExchange> proxy) throws Exception { - return proxy.uri(home.toString() + "/bars").body(Flux.from(foo)).post(); - } - - @PostMapping("/proxy/single") - public Mono> implicitEntity(@RequestBody Mono foo, - ProxyExchange> proxy) throws Exception { - return proxy.uri(home.toString() + "/bars").body(Flux.from(foo)).post(this::first); - } - - @PostMapping("/proxy/converter") - public Mono> implicitEntityWithConverter(@RequestBody Foo foo, - ProxyExchange> proxy) throws Exception { - return proxy.uri(home.toString() + "/bars").body(Arrays.asList(foo)) - .post(response -> ResponseEntity.status(response.getStatusCode()).headers(response.getHeaders()) - .body(response.getBody().iterator().next())); - } - - @GetMapping("/proxy/headers") - public Mono>>> headers( - ProxyExchange>> proxy) { - proxy.sensitive("foo", "hello"); - proxy.header("bar", "hello"); - proxy.header("abc", "123"); - proxy.header("hello", "world"); - return proxy.uri(home.toString() + "/headers").get(); - } - - @GetMapping("/proxy/sensitive-headers-default") - public Mono>>> defaultSensitiveHeaders( - ProxyExchange>> proxy) { - proxy.header("bar", "hello"); - proxy.header("abc", "123"); - proxy.header("hello", "world"); - return proxy.uri(home.toString() + "/headers").get(); - } - - private ResponseEntity first(ResponseEntity> response) { - return ResponseEntity.status(response.getStatusCode()).headers(response.getHeaders()) - .body(response.getBody().iterator().next()); - } - - @GetMapping("/proxy/forward/{id}") - public Mono> proxyForwardFoos(@PathVariable Integer id, ProxyExchange proxy) - throws Exception { - return proxy.uri(home.toString() + "/foos/" + id).forward(); - } - - @PostMapping("/proxy/forward/{id}") - public Mono> proxyForwardBars(@PathVariable Integer id, - @RequestBody Map body, ProxyExchange> proxy) throws Exception { - body.put("id", id); - return proxy.uri(home.toString() + "/bars").body(Arrays.asList(body)).forward(this::first); - } - - @DeleteMapping("/proxy/{id}/no-body") - public Mono> deleteWithoutBody(@PathVariable Integer id, ProxyExchange proxy) - throws Exception { - return proxy.uri(home.toString() + "/foos/" + id + "/no-body").delete(); - } - - @DeleteMapping("/proxy/{id}") - public Mono> deleteWithBody(@PathVariable Integer id, @RequestBody Foo foo, - ProxyExchange proxy) throws Exception { - return proxy.uri(home.toString() + "/foos/" + id).body(foo).delete(response -> ResponseEntity - .status(response.getStatusCode()).headers(response.getHeaders()).body(response.getBody())); - } - - } - - @RestController - static class TestController { - - @GetMapping("/foos") - public List foos() { - return Arrays.asList(new Foo("hello")); - } - - @GetMapping("/foos/{id}") - public Foo foo(@PathVariable Integer id, @RequestHeader HttpHeaders headers) { - String custom = headers.getFirst("X-Custom"); - return new Foo(id == 1 ? "foo" : custom != null ? custom : "bye"); - } - - @PostMapping("/bars") - public List bars(@RequestBody List foos, @RequestHeader HttpHeaders headers) { - String custom = headers.getFirst("X-Custom"); - custom = custom == null ? "" : custom; - custom = headers.getFirst("forwarded") == null ? custom : headers.getFirst("forwarded") + ";" + custom; - return Arrays.asList(new Bar(custom + foos.iterator().next().getName())); - } - - @GetMapping("/headers") - public Map> headers(@RequestHeader HttpHeaders headers) { - return headers; - } - - @DeleteMapping("/foos/{id}/no-body") - public ResponseEntity deleteFoo(@PathVariable Integer id) { - return ResponseEntity.ok().build(); - } - - @DeleteMapping("/foos/{id}") - public ResponseEntity deleteFoo(@PathVariable Integer id, @RequestBody Foo foo) { - return ResponseEntity.ok().body(Collections.singletonMap("deleted", foo)); - } - - } - - @JsonIgnoreProperties(ignoreUnknown = true) - static class Foo { - - private String name; - - Foo() { - } - - Foo(String name) { - this.name = name; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - } - - @JsonIgnoreProperties(ignoreUnknown = true) - static class Bar { - - private String name; - - Bar() { - } - - Bar(String name) { - this.name = name; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - } - - } - -} diff --git a/spring-cloud-gateway-webflux/src/test/java/org/springframework/cloud/gateway/webflux/ReactiveTests.java b/spring-cloud-gateway-webflux/src/test/java/org/springframework/cloud/gateway/webflux/ReactiveTests.java deleted file mode 100644 index 9321860f..00000000 --- a/spring-cloud-gateway-webflux/src/test/java/org/springframework/cloud/gateway/webflux/ReactiveTests.java +++ /dev/null @@ -1,215 +0,0 @@ -/* - * 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; - -import java.util.Collections; -import java.util.List; -import java.util.stream.Collectors; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import org.junit.jupiter.api.Test; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -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.context.SpringBootTest.WebEnvironment; -import org.springframework.boot.test.web.client.TestRestTemplate; -import org.springframework.boot.test.web.server.LocalServerPort; -import org.springframework.cloud.gateway.webflux.ReactiveTests.TestApplication; -import org.springframework.cloud.gateway.webflux.ReactiveTests.TestApplication.Bar; -import org.springframework.cloud.gateway.webflux.ReactiveTests.TestApplication.Foo; -import org.springframework.core.ParameterizedTypeReference; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.RequestEntity; -import org.springframework.http.ResponseEntity; -import org.springframework.lang.Nullable; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.util.ObjectUtils; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestHeader; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.reactive.DispatcherHandler; -import org.springframework.web.server.ServerWebExchange; - -import static org.assertj.core.api.Assertions.assertThat; - -@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) -@ContextConfiguration(classes = TestApplication.class) -public class ReactiveTests { - - @Autowired - private TestRestTemplate rest; - - @LocalServerPort - private int port; - - @Test - public void postBytes() throws Exception { - ResponseEntity> result = rest.exchange(RequestEntity - .post(rest.getRestTemplate().getUriTemplateHandler().expand("/bytes")).body("hello foo".getBytes()), - new ParameterizedTypeReference>() { - }); - assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK); - assertThat(result.getBody().iterator().next().getName()).isEqualTo("hello foo"); - } - - @Test - public void post() throws Exception { - ResponseEntity> result = rest.exchange( - RequestEntity.post(rest.getRestTemplate().getUriTemplateHandler().expand("/bars")) - .body(Collections.singletonList(Collections.singletonMap("name", "foo"))), - new ParameterizedTypeReference>() { - }); - assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK); - assertThat(result.getBody().iterator().next().getName()).isEqualTo("hello foo"); - } - - @Test - public void postFlux() throws Exception { - ResponseEntity> result = rest.exchange( - RequestEntity.post(rest.getRestTemplate().getUriTemplateHandler().expand("/flux/bars")) - .body(Collections.singletonList(Collections.singletonMap("name", "foo"))), - new ParameterizedTypeReference>() { - }); - assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK); - assertThat(result.getBody().iterator().next().getName()).isEqualTo("hello foo"); - } - - @Test - public void get() throws Exception { - ResponseEntity> result = rest.exchange( - RequestEntity.get(rest.getRestTemplate().getUriTemplateHandler().expand("/foos")).build(), - new ParameterizedTypeReference>() { - }); - assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK); - assertThat(result.getBody().iterator().next().getName()).isEqualTo("hello"); - } - - @Test - public void forward() throws Exception { - ResponseEntity> result = rest.exchange( - RequestEntity.get(rest.getRestTemplate().getUriTemplateHandler().expand("/forward/foos")).build(), - new ParameterizedTypeReference>() { - }); - assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK); - assertThat(result.getBody().iterator().next().getName()).isEqualTo("hello"); - } - - @SpringBootApplication - static class TestApplication { - - @RestController - static class TestController { - - @Autowired - private DispatcherHandler handler; - - @PostMapping("/bars") - public List bars(@RequestBody List foos, @RequestHeader HttpHeaders headers) { - String custom = "hello "; - return foos.stream().map(foo -> new Bar(custom + foo.getName())).collect(Collectors.toList()); - } - - @PostMapping("/flux/bars") - public Flux fluxbars(@RequestBody Flux foos, @RequestHeader HttpHeaders headers) { - String custom = "hello "; - return foos.map(foo -> new Bar(custom + foo.getName())); - } - - @GetMapping("/foos") - public Flux foos() { - return Flux.just(new Foo("hello")); - } - - @GetMapping("/forward/foos") - public Mono forwardFoos(ServerWebExchange exchange) { - return handler.handle(exchange.mutate().request(request -> request.path("/foos").build()).build()); - } - - @PostMapping("/bytes") - public Flux forwardBars(@RequestBody Flux body) { - return Flux.from(body.reduce(this::concatenate).map(value -> new Foo(new String(value)))); - } - - byte[] concatenate(@Nullable byte[] array1, @Nullable byte[] array2) { - if (ObjectUtils.isEmpty(array1)) { - return array2; - } - if (ObjectUtils.isEmpty(array2)) { - return array1; - } - - byte[] newArr = new byte[array1.length + array2.length]; - System.arraycopy(array1, 0, newArr, 0, array1.length); - System.arraycopy(array2, 0, newArr, array1.length, array2.length); - return newArr; - } - - } - - @JsonIgnoreProperties(ignoreUnknown = true) - static class Foo { - - private String name; - - Foo() { - } - - Foo(String name) { - this.name = name; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - } - - @JsonIgnoreProperties(ignoreUnknown = true) - static class Bar { - - private String name; - - Bar() { - } - - Bar(String name) { - this.name = name; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - } - - } - -} diff --git a/spring-cloud-gateway-webflux/src/test/resources/application.properties b/spring-cloud-gateway-webflux/src/test/resources/application.properties deleted file mode 100644 index 39b304fd..00000000 --- a/spring-cloud-gateway-webflux/src/test/resources/application.properties +++ /dev/null @@ -1 +0,0 @@ -logging.level.org.springframework.web.reactive=DEBUG diff --git a/spring-cloud-gateway-webflux/src/test/resources/static/test.html b/spring-cloud-gateway-webflux/src/test/resources/static/test.html deleted file mode 100644 index 0cfed139..00000000 --- a/spring-cloud-gateway-webflux/src/test/resources/static/test.html +++ /dev/null @@ -1,4 +0,0 @@ - -Test - - \ No newline at end of file diff --git a/spring-cloud-starter-gateway-mvc/pom.xml b/spring-cloud-starter-gateway-mvc/pom.xml deleted file mode 100644 index eed50d4c..00000000 --- a/spring-cloud-starter-gateway-mvc/pom.xml +++ /dev/null @@ -1,42 +0,0 @@ - - - 4.0.0 - - org.springframework.cloud - spring-cloud-gateway - 4.1.0-SNAPSHOT - .. - - spring-cloud-starter-gateway-mvc - spring-cloud-starter-gateway-mvc - Spring Cloud Gateway MVC - https://projects.spring.io/spring-cloud - - Pivotal Software, Inc. - https://www.spring.io - - - ${basedir}/../.. - - - - org.springframework.cloud - spring-cloud-starter - - - org.springframework.cloud - spring-cloud-gateway-server-mvc - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.cloud - spring-cloud-starter-loadbalancer - true - - - diff --git a/spring-cloud-starter-gateway/pom.xml b/spring-cloud-starter-gateway/pom.xml deleted file mode 100644 index 2edc0f9d..00000000 --- a/spring-cloud-starter-gateway/pom.xml +++ /dev/null @@ -1,42 +0,0 @@ - - - 4.0.0 - - org.springframework.cloud - spring-cloud-gateway - 4.1.0-SNAPSHOT - .. - - spring-cloud-starter-gateway - spring-cloud-starter-gateway - Spring Cloud Starter - https://projects.spring.io/spring-cloud - - Pivotal Software, Inc. - https://www.spring.io - - - ${basedir}/../.. - - - - org.springframework.cloud - spring-cloud-starter - - - org.springframework.cloud - spring-cloud-gateway-server - - - org.springframework.boot - spring-boot-starter-webflux - - - org.springframework.cloud - spring-cloud-starter-loadbalancer - true - - - diff --git a/src/checkstyle/checkstyle-suppressions.xml b/src/checkstyle/checkstyle-suppressions.xml deleted file mode 100644 index ba81d455..00000000 --- a/src/checkstyle/checkstyle-suppressions.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/supplemental-ui/partials/nav-search.hbs b/supplemental-ui/partials/nav-search.hbs new file mode 100644 index 00000000..5e379552 --- /dev/null +++ b/supplemental-ui/partials/nav-search.hbs @@ -0,0 +1,11 @@ +{{#if env.ALGOLIA_API_KEY}} + +{{/if}} diff --git a/supplemental-ui/partials/search.hbs b/supplemental-ui/partials/search.hbs new file mode 100644 index 00000000..384e505f --- /dev/null +++ b/supplemental-ui/partials/search.hbs @@ -0,0 +1,27 @@ + +{{#if env.ALGOLIA_API_KEY}} + + + + + + + + +{{/if}}