JAVA-13321 New Gateway-2 module with spring-cloud-gateway article's subjects
This commit is contained in:
		
							parent
							
								
									f1b3f233dc
								
							
						
					
					
						commit
						a06a2415db
					
				| @ -5,9 +5,3 @@ This module contains articles about Spring Cloud Gateway | ||||
| ### Relevant Articles: | ||||
| 
 | ||||
| - [Exploring the New Spring Cloud Gateway](http://www.baeldung.com/spring-cloud-gateway) | ||||
| - [Writing Custom Spring Cloud Gateway Filters](https://www.baeldung.com/spring-cloud-custom-gateway-filters) | ||||
| - [Spring Cloud Gateway Routing Predicate Factories](https://www.baeldung.com/spring-cloud-gateway-routing-predicate-factories) | ||||
| - [Spring Cloud Gateway WebFilter Factories](https://www.baeldung.com/spring-cloud-gateway-webfilter-factories) | ||||
| - [Using Spring Cloud Gateway with OAuth 2.0 Patterns](https://www.baeldung.com/spring-cloud-gateway-oauth2) | ||||
| - [URL Rewriting With Spring Cloud Gateway](https://www.baeldung.com/spring-cloud-gateway-url-rewriting) | ||||
| - [Processing the Response Body in Spring Cloud Gateway](https://www.baeldung.com/spring-cloud-gateway-response-body) | ||||
|  | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -4,13 +4,14 @@ | ||||
|     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||||
|     <modelVersion>4.0.0</modelVersion> | ||||
|     <artifactId>gateway-2</artifactId> | ||||
|     <name>spring-cloud-gateway</name> | ||||
|     <name>gateway-2</name> | ||||
|     <packaging>jar</packaging> | ||||
| 
 | ||||
|     <parent> | ||||
|         <groupId>com.baeldung.spring.cloud</groupId> | ||||
|         <artifactId>spring-cloud-modules</artifactId> | ||||
|         <version>1.0.0-SNAPSHOT</version> | ||||
|         <groupId>com.baeldung</groupId> | ||||
|         <artifactId>parent-boot-2</artifactId> | ||||
|         <version>0.0.1-SNAPSHOT</version> | ||||
|         <relativePath>../../../parent-boot-2</relativePath> | ||||
|     </parent> | ||||
| 
 | ||||
|     <dependencyManagement> | ||||
| @ -44,107 +45,36 @@ | ||||
|             <groupId>org.springframework.cloud</groupId> | ||||
|             <artifactId>spring-cloud-starter-gateway</artifactId> | ||||
|         </dependency> | ||||
|         <!-- Circuit Breaker --> | ||||
|         <dependency> | ||||
|             <groupId>org.springframework.cloud</groupId> | ||||
|             <artifactId>spring-cloud-starter-circuitbreaker-reactor-resilience4j</artifactId> | ||||
|         </dependency> | ||||
|         <!-- Redis --> | ||||
|         <dependency> | ||||
|             <groupId>org.springframework.boot</groupId> | ||||
|             <artifactId>spring-boot-starter-data-redis-reactive</artifactId> | ||||
|         </dependency> | ||||
|         <!-- Embedded Redis --> | ||||
|         <dependency> | ||||
|             <groupId>it.ozimov</groupId> | ||||
|             <artifactId>embedded-redis</artifactId> | ||||
|             <version>${redis.version}</version> | ||||
|             <scope>test</scope> | ||||
|         </dependency> | ||||
|         <dependency> | ||||
|             <groupId>org.hibernate</groupId> | ||||
|             <artifactId>hibernate-validator-cdi</artifactId> | ||||
|             <version>${hibernate-validator.version}</version> | ||||
|         </dependency> | ||||
|         <dependency> | ||||
|             <groupId>javax.validation</groupId> | ||||
|             <artifactId>validation-api</artifactId> | ||||
|         </dependency> | ||||
|         <dependency> | ||||
|             <groupId>org.springframework.boot</groupId> | ||||
|             <artifactId>spring-boot-starter-actuator</artifactId> | ||||
|         </dependency> | ||||
|         <dependency> | ||||
|             <groupId>org.springframework.boot</groupId> | ||||
|             <artifactId>spring-boot-starter-test</artifactId> | ||||
|             <scope>test</scope> | ||||
|         </dependency> | ||||
|         <dependency> | ||||
|             <groupId>org.springframework.boot</groupId> | ||||
|             <artifactId>spring-boot-devtools</artifactId> | ||||
|         </dependency> | ||||
|         <!-- Spring security --> | ||||
|         <dependency> | ||||
|             <groupId>org.springframework.boot</groupId> | ||||
|             <artifactId>spring-boot-starter-oauth2-resource-server</artifactId> | ||||
|         </dependency> | ||||
|         <dependency> | ||||
|             <groupId>org.springframework.boot</groupId> | ||||
|             <artifactId>spring-boot-starter-oauth2-client</artifactId> | ||||
|         </dependency> | ||||
|     </dependencies> | ||||
| 
 | ||||
|     <build> | ||||
|         <plugins> | ||||
|             <plugin> | ||||
|                 <groupId>org.apache.maven.plugins</groupId> | ||||
|                 <artifactId>maven-compiler-plugin</artifactId> | ||||
|                 <version>3.8.1</version> | ||||
|                 <configuration> | ||||
|                     <source>${java.version}</source> | ||||
|                     <target>${java.version}</target> | ||||
|                 </configuration> | ||||
|             </plugin> | ||||
|             <plugin> | ||||
|                 <groupId>org.springframework.boot</groupId> | ||||
|                 <artifactId>spring-boot-maven-plugin</artifactId> | ||||
|                 <configuration> | ||||
|                     <excludes> | ||||
|                         <exclude> | ||||
|                             <groupId>org.projectlombok</groupId> | ||||
|                             <artifactId>lombok</artifactId> | ||||
|                         </exclude> | ||||
|                     </excludes> | ||||
|                 </configuration> | ||||
|             </plugin> | ||||
|         </plugins> | ||||
|     </build> | ||||
| 
 | ||||
|     <profiles> | ||||
|         <profile> | ||||
|             <id>gateway-url-rewrite</id> | ||||
|             <build> | ||||
|                 <plugins> | ||||
|                     <plugin> | ||||
|                         <groupId>org.springframework.boot</groupId> | ||||
|                         <artifactId>spring-boot-maven-plugin</artifactId> | ||||
|                         <configuration> | ||||
|                             <mainClass>com.baeldung.springcloudgateway.rewrite.URLRewriteGatewayApplication</mainClass> | ||||
|                             <jvmArguments>-Dspring.profiles.active=url-rewrite</jvmArguments> | ||||
|                         </configuration> | ||||
|                     </plugin> | ||||
|                 </plugins> | ||||
|             </build> | ||||
|         </profile> | ||||
|     </profiles> | ||||
| <!--    <build>--> | ||||
| <!--        <plugins>--> | ||||
| <!--            <plugin>--> | ||||
| <!--                <groupId>org.apache.maven.plugins</groupId>--> | ||||
| <!--                <artifactId>maven-compiler-plugin</artifactId>--> | ||||
| <!--                <version>3.8.1</version>--> | ||||
| <!--                <configuration>--> | ||||
| <!--                    <source>${java.version}</source>--> | ||||
| <!--                    <target>${java.version}</target>--> | ||||
| <!--                </configuration>--> | ||||
| <!--            </plugin>--> | ||||
| <!--            <plugin>--> | ||||
| <!--                <groupId>org.springframework.boot</groupId>--> | ||||
| <!--                <artifactId>spring-boot-maven-plugin</artifactId>--> | ||||
| <!--                <configuration>--> | ||||
| <!--                    <excludes>--> | ||||
| <!--                        <exclude>--> | ||||
| <!--                            <groupId>org.projectlombok</groupId>--> | ||||
| <!--                            <artifactId>lombok</artifactId>--> | ||||
| <!--                        </exclude>--> | ||||
| <!--                    </excludes>--> | ||||
| <!--                </configuration>--> | ||||
| <!--            </plugin>--> | ||||
| <!--        </plugins>--> | ||||
| <!--    </build>--> | ||||
| 
 | ||||
|     <properties> | ||||
|         <!-- <spring-cloud-dependencies.version>Hoxton.SR3</spring-cloud-dependencies.version> --> | ||||
|         <!-- <spring-boot.version>2.2.6.RELEASE</spring-boot.version> --> | ||||
|         <hibernate-validator.version>6.0.2.Final</hibernate-validator.version> | ||||
|         <redis.version>0.7.2</redis.version> | ||||
|         <oauth2-oidc-sdk.version>9.19</oauth2-oidc-sdk.version> | ||||
|         <!-- <junit-bom.version>5.5.2</junit-bom.version> --> | ||||
|         <spring-cloud-dependencies.version>2021.0.3</spring-cloud-dependencies.version> | ||||
|     </properties> | ||||
| 
 | ||||
| </project> | ||||
| @ -1,15 +0,0 @@ | ||||
| package com.baeldung.springcloudgateway.customfilters.gatewayapp; | ||||
| 
 | ||||
| import org.springframework.boot.SpringApplication; | ||||
| import org.springframework.boot.autoconfigure.SpringBootApplication; | ||||
| import org.springframework.context.annotation.PropertySource; | ||||
| 
 | ||||
| @SpringBootApplication | ||||
| @PropertySource("classpath:customfilters-global-application.properties") | ||||
| public class CustomFiltersGatewayApplication { | ||||
| 
 | ||||
|     public static void main(String[] args) { | ||||
|         SpringApplication.run(CustomFiltersGatewayApplication.class, args); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -1,16 +0,0 @@ | ||||
| package com.baeldung.springcloudgateway.customfilters.gatewayapp.config; | ||||
| 
 | ||||
| import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
| import org.springframework.web.reactive.function.client.WebClient; | ||||
| 
 | ||||
| @Configuration | ||||
| public class WebClientConfig { | ||||
| 
 | ||||
|     @Bean | ||||
|     WebClient client() { | ||||
|         return WebClient.builder() | ||||
|             .build(); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -1,89 +0,0 @@ | ||||
| package com.baeldung.springcloudgateway.customfilters.gatewayapp.filters.factories; | ||||
| 
 | ||||
| import java.util.Arrays; | ||||
| import java.util.List; | ||||
| import java.util.Locale.LanguageRange; | ||||
| import java.util.stream.Collectors; | ||||
| 
 | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| import org.springframework.cloud.gateway.filter.GatewayFilter; | ||||
| import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; | ||||
| import org.springframework.stereotype.Component; | ||||
| import org.springframework.web.reactive.function.client.WebClient; | ||||
| 
 | ||||
| import reactor.core.publisher.Mono; | ||||
| 
 | ||||
| @Component | ||||
| public class ChainRequestGatewayFilterFactory extends AbstractGatewayFilterFactory<ChainRequestGatewayFilterFactory.Config> { | ||||
| 
 | ||||
|     final Logger logger = LoggerFactory.getLogger(ChainRequestGatewayFilterFactory.class); | ||||
| 
 | ||||
|     private final WebClient client; | ||||
| 
 | ||||
|     public ChainRequestGatewayFilterFactory(WebClient client) { | ||||
|         super(Config.class); | ||||
|         this.client = client; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public List<String> shortcutFieldOrder() { | ||||
|         return Arrays.asList("languageServiceEndpoint", "defaultLanguage"); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public GatewayFilter apply(Config config) { | ||||
|         return (exchange, chain) -> { | ||||
|             return client.get() | ||||
|                 .uri(config.getLanguageServiceEndpoint()) | ||||
|                 .exchange() | ||||
|                 .flatMap(response -> { | ||||
|                     return (response.statusCode() | ||||
|                         .is2xxSuccessful()) ? response.bodyToMono(String.class) : Mono.just(config.getDefaultLanguage()); | ||||
|                 }) | ||||
|                 .map(LanguageRange::parse) | ||||
|                 .map(range -> { | ||||
|                     exchange.getRequest() | ||||
|                         .mutate() | ||||
|                         .headers(h -> h.setAcceptLanguage(range)); | ||||
| 
 | ||||
|                     String allOutgoingRequestLanguages = exchange.getRequest() | ||||
|                         .getHeaders() | ||||
|                         .getAcceptLanguage() | ||||
|                         .stream() | ||||
|                         .map(r -> r.getRange()) | ||||
|                         .collect(Collectors.joining(",")); | ||||
| 
 | ||||
|                     logger.info("Chain Request output - Request contains Accept-Language header: " + allOutgoingRequestLanguages); | ||||
| 
 | ||||
|                     return exchange; | ||||
|                 }) | ||||
|                 .flatMap(chain::filter); | ||||
| 
 | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     public static class Config { | ||||
|         private String languageServiceEndpoint; | ||||
|         private String defaultLanguage; | ||||
| 
 | ||||
|         public Config() { | ||||
|         } | ||||
| 
 | ||||
|         public String getLanguageServiceEndpoint() { | ||||
|             return languageServiceEndpoint; | ||||
|         } | ||||
| 
 | ||||
|         public void setLanguageServiceEndpoint(String languageServiceEndpoint) { | ||||
|             this.languageServiceEndpoint = languageServiceEndpoint; | ||||
|         } | ||||
| 
 | ||||
|         public String getDefaultLanguage() { | ||||
|             return defaultLanguage; | ||||
|         } | ||||
| 
 | ||||
|         public void setDefaultLanguage(String defaultLanguage) { | ||||
|             this.defaultLanguage = defaultLanguage; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -1,85 +0,0 @@ | ||||
| package com.baeldung.springcloudgateway.customfilters.gatewayapp.filters.factories; | ||||
| 
 | ||||
| import java.util.Arrays; | ||||
| import java.util.List; | ||||
| 
 | ||||
| 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.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; | ||||
| import org.springframework.stereotype.Component; | ||||
| 
 | ||||
| import reactor.core.publisher.Mono; | ||||
| 
 | ||||
| @Component | ||||
| public class LoggingGatewayFilterFactory extends AbstractGatewayFilterFactory<LoggingGatewayFilterFactory.Config> { | ||||
| 
 | ||||
|     final Logger logger = LoggerFactory.getLogger(LoggingGatewayFilterFactory.class); | ||||
| 
 | ||||
|     public static final String BASE_MSG = "baseMessage"; | ||||
|     public static final String PRE_LOGGER = "preLogger"; | ||||
|     public static final String POST_LOGGER = "postLogger"; | ||||
| 
 | ||||
|     public LoggingGatewayFilterFactory() { | ||||
|         super(Config.class); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public List<String> shortcutFieldOrder() { | ||||
|         return Arrays.asList(BASE_MSG, PRE_LOGGER, POST_LOGGER); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public GatewayFilter apply(Config config) { | ||||
|         return new OrderedGatewayFilter((exchange, chain) -> { | ||||
|             if (config.isPreLogger()) | ||||
|                 logger.info("Pre GatewayFilter logging: " + config.getBaseMessage()); | ||||
|             return chain.filter(exchange) | ||||
|                 .then(Mono.fromRunnable(() -> { | ||||
|                     if (config.isPostLogger()) | ||||
|                         logger.info("Post GatewayFilter logging: " + config.getBaseMessage()); | ||||
|                 })); | ||||
|         }, 1); | ||||
|     } | ||||
| 
 | ||||
|     public static class Config { | ||||
|         private String baseMessage; | ||||
|         private boolean preLogger; | ||||
|         private boolean postLogger; | ||||
| 
 | ||||
|         public Config() { | ||||
|         }; | ||||
| 
 | ||||
|         public Config(String baseMessage, boolean preLogger, boolean postLogger) { | ||||
|             super(); | ||||
|             this.baseMessage = baseMessage; | ||||
|             this.preLogger = preLogger; | ||||
|             this.postLogger = postLogger; | ||||
|         } | ||||
| 
 | ||||
|         public String getBaseMessage() { | ||||
|             return this.baseMessage; | ||||
|         } | ||||
| 
 | ||||
|         public boolean isPreLogger() { | ||||
|             return preLogger; | ||||
|         } | ||||
| 
 | ||||
|         public boolean isPostLogger() { | ||||
|             return postLogger; | ||||
|         } | ||||
| 
 | ||||
|         public void setBaseMessage(String baseMessage) { | ||||
|             this.baseMessage = baseMessage; | ||||
|         } | ||||
| 
 | ||||
|         public void setPreLogger(boolean preLogger) { | ||||
|             this.preLogger = preLogger; | ||||
|         } | ||||
| 
 | ||||
|         public void setPostLogger(boolean postLogger) { | ||||
|             this.postLogger = postLogger; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -1,92 +0,0 @@ | ||||
| package com.baeldung.springcloudgateway.customfilters.gatewayapp.filters.factories; | ||||
| 
 | ||||
| import java.util.Arrays; | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
| import java.util.Locale; | ||||
| import java.util.Optional; | ||||
| import java.util.stream.Collectors; | ||||
| 
 | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| import org.springframework.cloud.gateway.filter.GatewayFilter; | ||||
| import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; | ||||
| import org.springframework.stereotype.Component; | ||||
| import org.springframework.util.LinkedMultiValueMap; | ||||
| import org.springframework.web.server.ServerWebExchange; | ||||
| import org.springframework.web.util.UriComponentsBuilder; | ||||
| 
 | ||||
| @Component | ||||
| public class ModifyRequestGatewayFilterFactory extends AbstractGatewayFilterFactory<ModifyRequestGatewayFilterFactory.Config> { | ||||
| 
 | ||||
|     final Logger logger = LoggerFactory.getLogger(ModifyRequestGatewayFilterFactory.class); | ||||
| 
 | ||||
|     public ModifyRequestGatewayFilterFactory() { | ||||
|         super(Config.class); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public List<String> shortcutFieldOrder() { | ||||
|         return Arrays.asList("defaultLocale"); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public GatewayFilter apply(Config config) { | ||||
|         return (exchange, chain) -> { | ||||
|             if (exchange.getRequest() | ||||
|                 .getHeaders() | ||||
|                 .getAcceptLanguage() | ||||
|                 .isEmpty()) { | ||||
| 
 | ||||
|                 String queryParamLocale = exchange.getRequest() | ||||
|                     .getQueryParams() | ||||
|                     .getFirst("locale"); | ||||
| 
 | ||||
|                 Locale requestLocale = Optional.ofNullable(queryParamLocale) | ||||
|                     .map(l -> Locale.forLanguageTag(l)) | ||||
|                     .orElse(config.getDefaultLocale()); | ||||
| 
 | ||||
|                 exchange.getRequest() | ||||
|                     .mutate() | ||||
|                     .headers(h -> h.setAcceptLanguageAsLocales(Collections.singletonList(requestLocale))); | ||||
|             } | ||||
| 
 | ||||
|             String allOutgoingRequestLanguages = exchange.getRequest() | ||||
|                 .getHeaders() | ||||
|                 .getAcceptLanguage() | ||||
|                 .stream() | ||||
|                 .map(range -> range.getRange()) | ||||
|                 .collect(Collectors.joining(",")); | ||||
| 
 | ||||
|             logger.info("Modify request output - Request contains Accept-Language header: {}", allOutgoingRequestLanguages); | ||||
| 
 | ||||
|             ServerWebExchange modifiedExchange = exchange.mutate() | ||||
|                 .request(originalRequest -> originalRequest.uri(UriComponentsBuilder.fromUri(exchange.getRequest() | ||||
|                     .getURI()) | ||||
|                     .replaceQueryParams(new LinkedMultiValueMap<String, String>()) | ||||
|                     .build() | ||||
|                     .toUri())) | ||||
|                 .build(); | ||||
| 
 | ||||
|             logger.info("Removed all query params: {}", modifiedExchange.getRequest() | ||||
|                 .getURI()); | ||||
| 
 | ||||
|             return chain.filter(modifiedExchange); | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     public static class Config { | ||||
|         private Locale defaultLocale; | ||||
| 
 | ||||
|         public Config() { | ||||
|         } | ||||
| 
 | ||||
|         public Locale getDefaultLocale() { | ||||
|             return defaultLocale; | ||||
|         } | ||||
| 
 | ||||
|         public void setDefaultLocale(String defaultLocale) { | ||||
|             this.defaultLocale = Locale.forLanguageTag(defaultLocale); | ||||
|         }; | ||||
|     } | ||||
| } | ||||
| @ -1,48 +0,0 @@ | ||||
| package com.baeldung.springcloudgateway.customfilters.gatewayapp.filters.factories; | ||||
| 
 | ||||
| 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.factory.AbstractGatewayFilterFactory; | ||||
| import org.springframework.http.server.reactive.ServerHttpResponse; | ||||
| import org.springframework.stereotype.Component; | ||||
| 
 | ||||
| import reactor.core.publisher.Mono; | ||||
| 
 | ||||
| @Component | ||||
| public class ModifyResponseGatewayFilterFactory extends AbstractGatewayFilterFactory<ModifyResponseGatewayFilterFactory.Config> { | ||||
| 
 | ||||
|     final Logger logger = LoggerFactory.getLogger(ModifyResponseGatewayFilterFactory.class); | ||||
| 
 | ||||
|     public ModifyResponseGatewayFilterFactory() { | ||||
|         super(Config.class); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public GatewayFilter apply(Config config) { | ||||
|         return (exchange, chain) -> { | ||||
|             return chain.filter(exchange) | ||||
|                 .then(Mono.fromRunnable(() -> { | ||||
|                     ServerHttpResponse response = exchange.getResponse(); | ||||
| 
 | ||||
|                     Optional.ofNullable(exchange.getRequest() | ||||
|                         .getQueryParams() | ||||
|                         .getFirst("locale")) | ||||
|                         .ifPresent(qp -> { | ||||
|                             String responseContentLanguage = response.getHeaders() | ||||
|                                 .getContentLanguage() | ||||
|                                 .getLanguage(); | ||||
| 
 | ||||
|                             response.getHeaders() | ||||
|                                 .add("Bael-Custom-Language-Header", responseContentLanguage); | ||||
|                             logger.info("Added custom header to Response"); | ||||
|                         }); | ||||
|                 })); | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     public static class Config { | ||||
|     } | ||||
| } | ||||
| @ -1,110 +0,0 @@ | ||||
| package com.baeldung.springcloudgateway.customfilters.gatewayapp.filters.factories; | ||||
| 
 | ||||
| import java.util.Arrays; | ||||
| import java.util.List; | ||||
| import java.util.regex.Pattern; | ||||
| 
 | ||||
| import org.reactivestreams.Publisher; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| import org.springframework.cloud.gateway.filter.GatewayFilter; | ||||
| import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; | ||||
| import org.springframework.cloud.gateway.filter.factory.rewrite.ModifyResponseBodyGatewayFilterFactory; | ||||
| import org.springframework.cloud.gateway.filter.factory.rewrite.RewriteFunction; | ||||
| import org.springframework.stereotype.Component; | ||||
| import org.springframework.web.server.ServerWebExchange; | ||||
| 
 | ||||
| import com.fasterxml.jackson.core.TreeNode; | ||||
| import com.fasterxml.jackson.databind.JsonNode; | ||||
| import com.fasterxml.jackson.databind.node.ArrayNode; | ||||
| import com.fasterxml.jackson.databind.node.ObjectNode; | ||||
| import com.fasterxml.jackson.databind.node.TextNode; | ||||
| 
 | ||||
| import reactor.core.publisher.Mono; | ||||
| 
 | ||||
| @Component | ||||
| public class ScrubResponseGatewayFilterFactory extends AbstractGatewayFilterFactory<ScrubResponseGatewayFilterFactory.Config> { | ||||
| 
 | ||||
|     final Logger logger = LoggerFactory.getLogger(ScrubResponseGatewayFilterFactory.class); | ||||
|     private ModifyResponseBodyGatewayFilterFactory modifyResponseBodyFilterFactory; | ||||
| 
 | ||||
|     public ScrubResponseGatewayFilterFactory(ModifyResponseBodyGatewayFilterFactory modifyResponseBodyFilterFactory) { | ||||
|         super(Config.class); | ||||
|         this.modifyResponseBodyFilterFactory = modifyResponseBodyFilterFactory; | ||||
|     } | ||||
|      | ||||
|     @Override | ||||
|     public List<String> shortcutFieldOrder() { | ||||
|         return Arrays.asList("fields", "replacement"); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     @Override | ||||
|     public GatewayFilter apply(Config config) { | ||||
|          | ||||
|         return modifyResponseBodyFilterFactory | ||||
|           .apply(c -> c.setRewriteFunction(JsonNode.class, JsonNode.class, new Scrubber(config))); | ||||
|     } | ||||
| 
 | ||||
|     public static class Config { | ||||
|          | ||||
|         private String fields; | ||||
|         private String replacement; | ||||
|          | ||||
|          | ||||
|         public String getFields() { | ||||
|             return fields; | ||||
|         } | ||||
|         public void setFields(String fields) { | ||||
|             this.fields = fields; | ||||
|         } | ||||
|         public String getReplacement() { | ||||
|             return replacement; | ||||
|         } | ||||
|         public void setReplacement(String replacement) { | ||||
|             this.replacement = replacement; | ||||
|         } | ||||
|     } | ||||
|      | ||||
|      | ||||
|     public static class Scrubber implements RewriteFunction<JsonNode,JsonNode> { | ||||
|         private final Pattern fields; | ||||
|         private final String replacement; | ||||
|          | ||||
|         public Scrubber(Config config) { | ||||
|             this.fields = Pattern.compile(config.getFields()); | ||||
|             this.replacement = config.getReplacement(); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public Publisher<JsonNode> apply(ServerWebExchange t, JsonNode u) { | ||||
|             return Mono.just(scrubRecursively(u)); | ||||
|         } | ||||
| 
 | ||||
|         private JsonNode scrubRecursively(JsonNode u) { | ||||
|             if ( !u.isContainerNode()) { | ||||
|                 return u; | ||||
|             } | ||||
|              | ||||
|             if ( u.isObject()) { | ||||
|                 ObjectNode node = (ObjectNode)u; | ||||
|                 node.fields().forEachRemaining((f) -> { | ||||
|                     if ( fields.matcher(f.getKey()).matches() && f.getValue().isTextual()) { | ||||
|                         f.setValue(TextNode.valueOf(replacement)); | ||||
|                     } | ||||
|                     else { | ||||
|                         f.setValue(scrubRecursively(f.getValue())); | ||||
|                     } | ||||
|                 }); | ||||
|             } | ||||
|             else if ( u.isArray()) { | ||||
|                 ArrayNode array = (ArrayNode)u; | ||||
|                 for ( int i = 0 ; i < array.size() ; i++ ) { | ||||
|                     array.set(i, scrubRecursively(array.get(i))); | ||||
|                 } | ||||
|             } | ||||
|              | ||||
|             return u; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -1,31 +0,0 @@ | ||||
| package com.baeldung.springcloudgateway.customfilters.gatewayapp.filters.global; | ||||
| 
 | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| import org.springframework.cloud.gateway.filter.GatewayFilterChain; | ||||
| import org.springframework.cloud.gateway.filter.GlobalFilter; | ||||
| import org.springframework.core.Ordered; | ||||
| import org.springframework.stereotype.Component; | ||||
| import org.springframework.web.server.ServerWebExchange; | ||||
| 
 | ||||
| import reactor.core.publisher.Mono; | ||||
| 
 | ||||
| @Component | ||||
| public class FirstPreLastPostGlobalFilter implements GlobalFilter, Ordered { | ||||
| 
 | ||||
|     final Logger logger = LoggerFactory.getLogger(FirstPreLastPostGlobalFilter.class); | ||||
| 
 | ||||
|     @Override | ||||
|     public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { | ||||
|         logger.info("First Pre Global Filter"); | ||||
|         return chain.filter(exchange) | ||||
|             .then(Mono.fromRunnable(() -> { | ||||
|                 logger.info("Last Post Global Filter"); | ||||
|             })); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public int getOrder() { | ||||
|         return -1; | ||||
|     } | ||||
| } | ||||
| @ -1,47 +0,0 @@ | ||||
| package com.baeldung.springcloudgateway.customfilters.gatewayapp.filters.global; | ||||
| 
 | ||||
| import org.springframework.boot.context.properties.ConfigurationProperties; | ||||
| 
 | ||||
| @ConfigurationProperties("logging.global") | ||||
| public class LoggingGlobalFilterProperties { | ||||
|      | ||||
|     private boolean enabled; | ||||
|     private boolean requestHeaders; | ||||
|     private boolean requestBody; | ||||
|     private boolean responseHeaders; | ||||
|     private boolean responseBody; | ||||
|      | ||||
|     public boolean isEnabled() { | ||||
|         return enabled; | ||||
|     } | ||||
|     public void setEnabled(boolean enabled) { | ||||
|         this.enabled = enabled; | ||||
|     } | ||||
|     public boolean isRequestHeaders() { | ||||
|         return requestHeaders; | ||||
|     } | ||||
|     public void setRequestHeaders(boolean requestHeaders) { | ||||
|         this.requestHeaders = requestHeaders; | ||||
|     } | ||||
|     public boolean isRequestBody() { | ||||
|         return requestBody; | ||||
|     } | ||||
|     public void setRequestBody(boolean requestBody) { | ||||
|         this.requestBody = requestBody; | ||||
|     } | ||||
|     public boolean isResponseHeaders() { | ||||
|         return responseHeaders; | ||||
|     } | ||||
|     public void setResponseHeaders(boolean responseHeaders) { | ||||
|         this.responseHeaders = responseHeaders; | ||||
|     } | ||||
|     public boolean isResponseBody() { | ||||
|         return responseBody; | ||||
|     } | ||||
|     public void setResponseBody(boolean responseBody) { | ||||
|         this.responseBody = responseBody; | ||||
|     } | ||||
|      | ||||
|      | ||||
| 
 | ||||
| } | ||||
| @ -1,25 +0,0 @@ | ||||
| package com.baeldung.springcloudgateway.customfilters.gatewayapp.filters.global; | ||||
| 
 | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| import org.springframework.cloud.gateway.filter.GlobalFilter; | ||||
| import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
| 
 | ||||
| import reactor.core.publisher.Mono; | ||||
| 
 | ||||
| @Configuration | ||||
| public class LoggingGlobalFiltersConfigurations { | ||||
| 
 | ||||
|     final Logger logger = LoggerFactory.getLogger(LoggingGlobalFiltersConfigurations.class); | ||||
| 
 | ||||
|     @Bean | ||||
|     public GlobalFilter postGlobalFilter() { | ||||
|         return (exchange, chain) -> { | ||||
|             return chain.filter(exchange) | ||||
|                 .then(Mono.fromRunnable(() -> { | ||||
|                     logger.info("Global Post Filter executed"); | ||||
|                 })); | ||||
|         }; | ||||
|     } | ||||
| } | ||||
| @ -1,22 +0,0 @@ | ||||
| package com.baeldung.springcloudgateway.customfilters.gatewayapp.filters.global; | ||||
| 
 | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| import org.springframework.cloud.gateway.filter.GatewayFilterChain; | ||||
| import org.springframework.cloud.gateway.filter.GlobalFilter; | ||||
| import org.springframework.stereotype.Component; | ||||
| import org.springframework.web.server.ServerWebExchange; | ||||
| 
 | ||||
| import reactor.core.publisher.Mono; | ||||
| 
 | ||||
| @Component | ||||
| public class LoggingGlobalPreFilter implements GlobalFilter { | ||||
| 
 | ||||
|     final Logger logger = LoggerFactory.getLogger(LoggingGlobalPreFilter.class); | ||||
| 
 | ||||
|     @Override | ||||
|     public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { | ||||
|         logger.info("Global Pre Filter executed"); | ||||
|         return chain.filter(exchange); | ||||
|     } | ||||
| } | ||||
| @ -1,28 +0,0 @@ | ||||
| package com.baeldung.springcloudgateway.customfilters.gatewayapp.routes; | ||||
| 
 | ||||
| import org.springframework.cloud.gateway.route.RouteLocator; | ||||
| import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; | ||||
| import org.springframework.context.annotation.Bean; | ||||
| 
 | ||||
| import com.baeldung.springcloudgateway.customfilters.gatewayapp.filters.factories.LoggingGatewayFilterFactory; | ||||
| import com.baeldung.springcloudgateway.customfilters.gatewayapp.filters.factories.LoggingGatewayFilterFactory.Config; | ||||
| 
 | ||||
| /** | ||||
|  * Note: We want to keep this as an example of configuring a Route with a custom filter | ||||
|  * | ||||
|  * This corresponds with the properties configuration we have | ||||
|  */ | ||||
| // @Configuration | ||||
| public class ServiceRouteConfiguration { | ||||
| 
 | ||||
|     @Bean | ||||
|     public RouteLocator routes(RouteLocatorBuilder builder, LoggingGatewayFilterFactory loggingFactory) { | ||||
| 
 | ||||
|         return builder.routes() | ||||
|             .route("service_route_java_config", r -> r.path("/service/**") | ||||
|                 .filters(f -> f.rewritePath("/service(?<segment>/?.*)", "$\\{segment}") | ||||
|                     .filter(loggingFactory.apply(new Config("My Custom Message", true, true)))) | ||||
|                 .uri("http://localhost:8081")) | ||||
|             .build(); | ||||
|     } | ||||
| } | ||||
| @ -1,15 +0,0 @@ | ||||
| package com.baeldung.springcloudgateway.customfilters.secondservice; | ||||
| 
 | ||||
| import org.springframework.boot.SpringApplication; | ||||
| import org.springframework.boot.autoconfigure.SpringBootApplication; | ||||
| import org.springframework.context.annotation.PropertySource; | ||||
| 
 | ||||
| @SpringBootApplication | ||||
| @PropertySource("classpath:secondservice-application.properties") | ||||
| public class SecondServiceApplication { | ||||
| 
 | ||||
|     public static void main(String[] args) { | ||||
|         SpringApplication.run(SecondServiceApplication.class, args); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -1,18 +0,0 @@ | ||||
| package com.baeldung.springcloudgateway.customfilters.secondservice.web; | ||||
| 
 | ||||
| import org.springframework.http.ResponseEntity; | ||||
| import org.springframework.web.bind.annotation.GetMapping; | ||||
| import org.springframework.web.bind.annotation.RestController; | ||||
| 
 | ||||
| import reactor.core.publisher.Mono; | ||||
| 
 | ||||
| @RestController | ||||
| public class SecondServiceRestController { | ||||
| 
 | ||||
|     @GetMapping("/resource/language") | ||||
|     public Mono<ResponseEntity<String>> getResource() { | ||||
|         return Mono.just(ResponseEntity.ok() | ||||
|             .body("es")); | ||||
| 
 | ||||
|     } | ||||
| } | ||||
| @ -1,15 +0,0 @@ | ||||
| package com.baeldung.springcloudgateway.customfilters.service; | ||||
| 
 | ||||
| import org.springframework.boot.SpringApplication; | ||||
| import org.springframework.boot.autoconfigure.SpringBootApplication; | ||||
| import org.springframework.context.annotation.PropertySource; | ||||
| 
 | ||||
| @SpringBootApplication | ||||
| @PropertySource("classpath:service-application.properties") | ||||
| public class ServiceApplication { | ||||
| 
 | ||||
|     public static void main(String[] args) { | ||||
|         SpringApplication.run(ServiceApplication.class, args); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -1,22 +0,0 @@ | ||||
| package com.baeldung.springcloudgateway.customfilters.service.web; | ||||
| 
 | ||||
| import java.util.Locale; | ||||
| 
 | ||||
| import org.springframework.http.HttpHeaders; | ||||
| import org.springframework.http.ResponseEntity; | ||||
| import org.springframework.web.bind.annotation.GetMapping; | ||||
| import org.springframework.web.bind.annotation.RestController; | ||||
| 
 | ||||
| import reactor.core.publisher.Mono; | ||||
| 
 | ||||
| @RestController | ||||
| public class ServiceRestController { | ||||
| 
 | ||||
|     @GetMapping("/resource") | ||||
|     public Mono<ResponseEntity<String>> getResource() { | ||||
|         return Mono.just(ResponseEntity.ok() | ||||
|             .header(HttpHeaders.CONTENT_LANGUAGE, Locale.ENGLISH.getLanguage()) | ||||
|             .body("Service Resource")); | ||||
| 
 | ||||
|     } | ||||
| } | ||||
| @ -1,25 +0,0 @@ | ||||
| /** | ||||
|  *  | ||||
|  */ | ||||
| package com.baeldung.springcloudgateway.rewrite; | ||||
| 
 | ||||
| import org.springframework.boot.autoconfigure.SpringBootApplication; | ||||
| import org.springframework.boot.builder.SpringApplicationBuilder; | ||||
| import org.springframework.cloud.gateway.filter.GatewayFilter; | ||||
| 
 | ||||
| import reactor.core.publisher.Mono; | ||||
| 
 | ||||
| /** | ||||
|  * @author Baeldung | ||||
|  * | ||||
|  */ | ||||
| @SpringBootApplication | ||||
| public class URLRewriteGatewayApplication { | ||||
|      | ||||
|     public static void main(String[] args) { | ||||
|         new SpringApplicationBuilder(URLRewriteGatewayApplication.class) | ||||
|           .profiles("url-rewrite") | ||||
|           .run(args); | ||||
|     } | ||||
|      | ||||
| } | ||||
| @ -1,43 +0,0 @@ | ||||
| package com.baeldung.springcloudgateway.rewrite.routes; | ||||
| 
 | ||||
| import org.springframework.beans.factory.annotation.Value; | ||||
| 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.Configuration; | ||||
| import org.springframework.context.annotation.Profile; | ||||
| import org.springframework.http.server.reactive.ServerHttpRequest; | ||||
| 
 | ||||
| import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR; | ||||
| import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.addOriginalRequestUrl; | ||||
| 
 | ||||
| import java.util.Random; | ||||
| 
 | ||||
| @Configuration | ||||
| @Profile("url-rewrite") | ||||
| public class DynamicRewriteRoute { | ||||
|      | ||||
|     @Value("${rewrite.backend.uri}") | ||||
|     private String backendUri; | ||||
|     private static Random rnd = new Random(); | ||||
|      | ||||
|     @Bean | ||||
|     public RouteLocator dynamicZipCodeRoute(RouteLocatorBuilder builder) { | ||||
|         return builder.routes() | ||||
|           .route("dynamicRewrite", r -> | ||||
|              r.path("/v2/zip/**") | ||||
|               .filters(f -> f.filter((exchange, chain) -> { | ||||
|                   ServerHttpRequest req = exchange.getRequest(); | ||||
|                   addOriginalRequestUrl(exchange, req.getURI()); | ||||
|                   String path = req.getURI().getRawPath(); | ||||
|                   String newPath = path.replaceAll( | ||||
|                     "/v2/zip/(?<zipcode>.*)",  | ||||
|                     "/api/zip/${zipcode}-" + String.format("%03d", rnd.nextInt(1000))); | ||||
|                   ServerHttpRequest request = req.mutate().path(newPath).build(); | ||||
|                   exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, request.getURI()); | ||||
|                   return chain.filter(exchange.mutate().request(request).build()); | ||||
|               })) | ||||
|               .uri(backendUri)) | ||||
|           .build(); | ||||
|     } | ||||
| } | ||||
| @ -1,15 +0,0 @@ | ||||
| package com.baeldung.springcloudgateway.webfilters; | ||||
| 
 | ||||
| import org.springframework.boot.autoconfigure.SpringBootApplication; | ||||
| import org.springframework.boot.builder.SpringApplicationBuilder; | ||||
| 
 | ||||
| @SpringBootApplication | ||||
| public class WebFilterGatewayApplication { | ||||
| 
 | ||||
|     public static void main(String[] args) { | ||||
|         new SpringApplicationBuilder(WebFilterGatewayApplication.class) | ||||
|           .profiles("url-rewrite") | ||||
|           .run(args); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -1,49 +0,0 @@ | ||||
| package com.baeldung.springcloudgateway.webfilters.config; | ||||
| 
 | ||||
| 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.Configuration; | ||||
| import org.springframework.http.MediaType; | ||||
| 
 | ||||
| import reactor.core.publisher.Mono; | ||||
| 
 | ||||
| @Configuration | ||||
| public class ModifyBodyRouteConfig { | ||||
| 
 | ||||
|     @Bean | ||||
|     public RouteLocator routes(RouteLocatorBuilder builder) { | ||||
|         return builder.routes() | ||||
|           .route("modify_request_body", r -> r.path("/post") | ||||
|             .filters(f -> f.modifyRequestBody(String.class, Hello.class, MediaType.APPLICATION_JSON_VALUE, | ||||
|               (exchange, s) -> Mono.just(new Hello(s.toUpperCase())))).uri("https://httpbin.org")) | ||||
|           .build(); | ||||
|     }   | ||||
| 
 | ||||
|     @Bean | ||||
|     public RouteLocator responseRoutes(RouteLocatorBuilder builder) { | ||||
|         return builder.routes() | ||||
|           .route("modify_response_body", r -> r.path("/put/**") | ||||
|             .filters(f -> f.modifyResponseBody(String.class, Hello.class, MediaType.APPLICATION_JSON_VALUE, | ||||
|               (exchange, s) -> Mono.just(new Hello("New Body")))).uri("https://httpbin.org")) | ||||
|           .build(); | ||||
|     }  | ||||
| 
 | ||||
|     static class Hello { | ||||
|         String message; | ||||
| 
 | ||||
|         public Hello() { } | ||||
| 
 | ||||
|         public Hello(String message) { | ||||
|             this.message = message; | ||||
|         } | ||||
| 
 | ||||
|         public String getMessage() { | ||||
|             return message; | ||||
|         } | ||||
| 
 | ||||
|         public void setMessage(String message) { | ||||
|             this.message = message; | ||||
|         } | ||||
|     }  | ||||
| } | ||||
| @ -1,16 +0,0 @@ | ||||
| package com.baeldung.springcloudgateway.webfilters.config; | ||||
| 
 | ||||
| import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver; | ||||
| import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
| 
 | ||||
| import reactor.core.publisher.Mono; | ||||
| 
 | ||||
| @Configuration | ||||
| public class RequestRateLimiterResolverConfig { | ||||
|      | ||||
|     @Bean | ||||
|     KeyResolver userKeyResolver() { | ||||
|         return exchange -> Mono.just("1"); | ||||
|     } | ||||
| } | ||||
| @ -1,8 +0,0 @@ | ||||
| # Enable this profile to disable security | ||||
| spring: | ||||
|   autoconfigure: | ||||
|     exclude: | ||||
|      - org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration | ||||
|      - org.springframework.boot.actuate.autoconfigure.ManagementSecurityAutoConfiguration | ||||
|      - org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration | ||||
|      - org.springframework.boot.actuate.autoconfigure.security.reactive.ReactiveManagementWebSecurityAutoConfiguration | ||||
| @ -1,12 +0,0 @@ | ||||
| spring: | ||||
|   cloud: | ||||
|     gateway: | ||||
|       routes: | ||||
|       - id: rewrite_with_scrub | ||||
|         uri: ${rewrite.backend.uri:http://example.com} | ||||
|         predicates: | ||||
|         - Path=/v1/customer/** | ||||
|         filters: | ||||
|         - RewritePath=/v1/customer/(?<segment>.*),/api/$\{segment} | ||||
|         - ScrubResponse=ssn,*** | ||||
|          | ||||
| @ -1,11 +0,0 @@ | ||||
| spring: | ||||
|   cloud: | ||||
|     gateway: | ||||
|       routes: | ||||
|       - id: rewrite_v1 | ||||
|         uri: ${rewrite.backend.uri:http://example.com} | ||||
|         predicates: | ||||
|         - Path=/v1/customer/** | ||||
|         filters: | ||||
|         - RewritePath=/v1/customer/(?<segment>.*),/api/$\{segment} | ||||
|          | ||||
| @ -1,102 +0,0 @@ | ||||
| logging: | ||||
|   level: | ||||
|     org.springframework.cloud.gateway: INFO | ||||
|     reactor.netty.http.client: INFO | ||||
|      | ||||
| spring: | ||||
|   redis: | ||||
|     host: localhost | ||||
|     port: 6379 | ||||
|   cloud: | ||||
|     gateway: | ||||
|       routes: | ||||
|       - id: request_header_route | ||||
|         uri: https://httpbin.org | ||||
|         predicates: | ||||
|         - Path=/get/** | ||||
|         filters: | ||||
|         - AddRequestHeader=My-Header-Good,Good | ||||
|         - AddRequestHeader=My-Header-Remove,Remove | ||||
|         - AddRequestParameter=var, good | ||||
|         - AddRequestParameter=var2, remove | ||||
|         - MapRequestHeader=My-Header-Good, My-Header-Bad | ||||
|         - MapRequestHeader=My-Header-Set, My-Header-Bad | ||||
|         - SetRequestHeader=My-Header-Set, Set  | ||||
|         - RemoveRequestHeader=My-Header-Remove | ||||
|         - RemoveRequestParameter=var2 | ||||
|         - PreserveHostHeader | ||||
|          | ||||
|       - id: response_header_route | ||||
|         uri: https://httpbin.org | ||||
|         predicates: | ||||
|         - Path=/header/post/** | ||||
|         filters: | ||||
|         - AddResponseHeader=My-Header-Good,Good | ||||
|         - AddResponseHeader=My-Header-Set,Good | ||||
|         - AddResponseHeader=My-Header-Rewrite, password=12345678 | ||||
|         - DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin | ||||
|         - AddResponseHeader=My-Header-Remove,Remove | ||||
|         - SetResponseHeader=My-Header-Set, Set | ||||
|         - RemoveResponseHeader=My-Header-Remove | ||||
|         - RewriteResponseHeader=My-Header-Rewrite, password=[^&]+, password=*** | ||||
|         - RewriteLocationResponseHeader=AS_IN_REQUEST, Location, , | ||||
|         - StripPrefix=1 | ||||
|          | ||||
|       - id: path_route | ||||
|         uri: https://httpbin.org | ||||
|         predicates: | ||||
|         - Path=/new/post/** | ||||
|         filters: | ||||
|         - RewritePath=/new(?<segment>/?.*), $\{segment} | ||||
|         - SetPath=/post | ||||
|          | ||||
|       - id: redirect_route | ||||
|         uri: https://httpbin.org | ||||
|         predicates: | ||||
|         - Path=/fake/post/** | ||||
|         filters: | ||||
|         - RedirectTo=302, https://httpbin.org | ||||
|          | ||||
|       - id: status_route | ||||
|         uri: https://httpbin.org | ||||
|         predicates: | ||||
|         - Path=/delete/** | ||||
|         filters: | ||||
|         - SetStatus=401 | ||||
|          | ||||
|       - id: size_route | ||||
|         uri: https://httpbin.org | ||||
|         predicates: | ||||
|         - Path=/anything | ||||
|         filters: | ||||
|         - name: RequestSize | ||||
|           args: | ||||
|             maxSize: 5000000 | ||||
|   | ||||
|       - id: retry_test | ||||
|         uri: https://httpbin.org | ||||
|         predicates: | ||||
|         - Path=/status/502 | ||||
|         filters: | ||||
|         - name: Retry | ||||
|           args: | ||||
|             retries: 3 | ||||
|             statuses: BAD_GATEWAY | ||||
|             methods: GET,POST | ||||
|             backoff: | ||||
|               firstBackoff: 10ms | ||||
|               maxBackoff: 50ms | ||||
|               factor: 2 | ||||
|               basedOnPreviousValue: false | ||||
|                | ||||
|       - id: request_rate_limiter | ||||
|         uri: https://httpbin.org | ||||
|         predicates: | ||||
|         - Path=/redis/get/** | ||||
|         filters: | ||||
|         - StripPrefix=1 | ||||
|         - name: RequestRateLimiter | ||||
|           args: | ||||
|             redis-rate-limiter.replenishRate: 10 | ||||
|             redis-rate-limiter.burstCapacity: 5 | ||||
|             key-resolver: "#{@userKeyResolver}" | ||||
| @ -1,19 +0,0 @@ | ||||
| spring.cloud.gateway.routes[0].id=service_route | ||||
| spring.cloud.gateway.routes[0].uri=http://localhost:8081 | ||||
| spring.cloud.gateway.routes[0].predicates[0]=Path=/service/** | ||||
| spring.cloud.gateway.routes[0].filters[0]=RewritePath=/service(?<segment>/?.*), $\{segment} | ||||
| spring.cloud.gateway.routes[0].filters[1]=Logging=My Custom Message, true, true | ||||
| # Or, as an alternative: | ||||
| #spring.cloud.gateway.routes[0].filters[1].name=Logging | ||||
| #spring.cloud.gateway.routes[0].filters[1].args[baseMessage]=My Custom Message | ||||
| #spring.cloud.gateway.routes[0].filters[1].args[preLogger]=true | ||||
| #spring.cloud.gateway.routes[0].filters[1].args[postLogger]=true | ||||
| 
 | ||||
| spring.cloud.gateway.routes[0].filters[2]=ModifyResponse | ||||
| spring.cloud.gateway.routes[0].filters[3]=ModifyRequest=en | ||||
| spring.cloud.gateway.routes[0].filters[4]=ChainRequest=http://localhost:8082/resource/language, fr | ||||
| 
 | ||||
| management.endpoints.web.exposure.include=* | ||||
| 
 | ||||
| server.port=80 | ||||
| 
 | ||||
| @ -1 +0,0 @@ | ||||
| server.port=8082 | ||||
| @ -1 +0,0 @@ | ||||
| server.port=8081 | ||||
| @ -1,112 +0,0 @@ | ||||
| package com.baeldung.springcloudgateway.customfilters.gatewayapp; | ||||
| 
 | ||||
| import static org.assertj.core.api.Assertions.assertThat; | ||||
| 
 | ||||
| import org.assertj.core.api.Condition; | ||||
| import org.junit.jupiter.api.BeforeEach; | ||||
| import org.junit.jupiter.api.Test; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.boot.test.context.SpringBootTest; | ||||
| import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; | ||||
| import org.springframework.boot.web.server.LocalServerPort; | ||||
| import org.springframework.test.web.reactive.server.WebTestClient; | ||||
| import org.springframework.test.web.reactive.server.WebTestClient.ResponseSpec; | ||||
| 
 | ||||
| import com.baeldung.springcloudgateway.customfilters.gatewayapp.utils.LoggerListAppender; | ||||
| 
 | ||||
| import ch.qos.logback.classic.spi.ILoggingEvent; | ||||
| 
 | ||||
| /** | ||||
|  * This test requires: | ||||
|  * * the service in com.baeldung.service running | ||||
|  * * the 'second service' in com.baeldung.secondservice running | ||||
|  * | ||||
|  */ | ||||
| @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) | ||||
| public class CustomFiltersLiveTest { | ||||
| 
 | ||||
|     @LocalServerPort | ||||
|     String port; | ||||
| 
 | ||||
|     @Autowired | ||||
|     private WebTestClient client; | ||||
| 
 | ||||
|     @BeforeEach | ||||
|     public void clearLogList() { | ||||
|         LoggerListAppender.clearEventList(); | ||||
|         client = WebTestClient.bindToServer() | ||||
|             .baseUrl("http://localhost:" + port) | ||||
|             .build(); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void whenCallServiceThroughGateway_thenAllConfiguredFiltersGetExecuted() { | ||||
|         ResponseSpec response = client.get() | ||||
|             .uri("/service/resource") | ||||
|             .exchange(); | ||||
| 
 | ||||
|         response.expectStatus() | ||||
|             .isOk() | ||||
|             .expectHeader() | ||||
|             .doesNotExist("Bael-Custom-Language-Header") | ||||
|             .expectBody(String.class) | ||||
|             .isEqualTo("Service Resource"); | ||||
| 
 | ||||
|         assertThat(LoggerListAppender.getEvents()) | ||||
|             // Global Pre Filter | ||||
|             .haveAtLeastOne(eventContains("Global Pre Filter executed")) | ||||
|             // Global Post Filter | ||||
|             .haveAtLeastOne(eventContains("Global Post Filter executed")) | ||||
|             // Global Pre and Post Filter | ||||
|             .haveAtLeastOne(eventContains("First Pre Global Filter")) | ||||
|             .haveAtLeastOne(eventContains("Last Post Global Filter")) | ||||
|             // Logging Filter Factory | ||||
|             .haveAtLeastOne(eventContains("Pre GatewayFilter logging: My Custom Message")) | ||||
|             .haveAtLeastOne(eventContains("Post GatewayFilter logging: My Custom Message")) | ||||
|             // Modify Request | ||||
|             .haveAtLeastOne(eventContains("Modify request output - Request contains Accept-Language header:")) | ||||
|             .haveAtLeastOne(eventContainsExcept("Removed all query params: ", "locale")) | ||||
|             // Modify Response | ||||
|             .areNot(eventContains("Added custom header to Response")) | ||||
|             // Chain Request | ||||
|             .haveAtLeastOne(eventContains("Chain Request output - Request contains Accept-Language header:")); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void givenRequestWithLocaleQueryParam_whenCallServiceThroughGateway_thenAllConfiguredFiltersGetExecuted() { | ||||
|         ResponseSpec response = client.get() | ||||
|             .uri("/service/resource?locale=en") | ||||
|             .exchange(); | ||||
| 
 | ||||
|         response.expectStatus() | ||||
|             .isOk() | ||||
|             .expectHeader() | ||||
|             .exists("Bael-Custom-Language-Header") | ||||
|             .expectBody(String.class) | ||||
|             .isEqualTo("Service Resource"); | ||||
| 
 | ||||
|         assertThat(LoggerListAppender.getEvents()) | ||||
|             // Modify Response | ||||
|             .haveAtLeastOne(eventContains("Added custom header to Response")) | ||||
|             .haveAtLeastOne(eventContainsExcept("Removed all query params: ", "locale")); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * This condition will be successful if the event contains a substring | ||||
|      */ | ||||
|     private Condition<ILoggingEvent> eventContains(String substring) { | ||||
|         return new Condition<ILoggingEvent>(entry -> (substring == null || (entry.getFormattedMessage() != null && entry.getFormattedMessage() | ||||
|             .contains(substring))), String.format("entry with message '%s'", substring)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * This condition will be successful if the event contains a substring, but not another one | ||||
|      */ | ||||
|     private Condition<ILoggingEvent> eventContainsExcept(String substring, String except) { | ||||
|         return new Condition<ILoggingEvent>(entry -> (substring == null || (entry.getFormattedMessage() != null && entry.getFormattedMessage() | ||||
|             .contains(substring) | ||||
|             && !entry.getFormattedMessage() | ||||
|                 .contains(except))), | ||||
|             String.format("entry with message '%s'", substring)); | ||||
|     } | ||||
| } | ||||
| @ -1,61 +0,0 @@ | ||||
| package com.baeldung.springcloudgateway.customfilters.gatewayapp.filters.factories; | ||||
| 
 | ||||
| import static org.junit.jupiter.api.Assertions.*; | ||||
| 
 | ||||
| import org.junit.jupiter.api.Test; | ||||
| 
 | ||||
| import com.baeldung.springcloudgateway.customfilters.gatewayapp.filters.factories.ScrubResponseGatewayFilterFactory.Config; | ||||
| import com.baeldung.springcloudgateway.customfilters.gatewayapp.filters.factories.ScrubResponseGatewayFilterFactory.Scrubber; | ||||
| import com.fasterxml.jackson.core.JsonFactory; | ||||
| import com.fasterxml.jackson.core.JsonParser; | ||||
| import com.fasterxml.jackson.core.ObjectCodec; | ||||
| import com.fasterxml.jackson.core.TreeNode; | ||||
| import com.fasterxml.jackson.databind.JsonNode; | ||||
| import com.fasterxml.jackson.databind.ObjectMapper; | ||||
| 
 | ||||
| import reactor.core.publisher.Mono; | ||||
| 
 | ||||
| class ScrubResponseGatewayFilterFactoryUnitTest { | ||||
|      | ||||
|     private static final String JSON_WITH_FIELDS_TO_SCRUB = "{\r\n" | ||||
|         + "  \"name\" : \"John Doe\",\r\n" | ||||
|         + "        \"ssn\"  : \"123-45-9999\",\r\n" | ||||
|         + "        \"account\" : \"9999888877770000\"\r\n" | ||||
|         + "}"; | ||||
| 
 | ||||
|      | ||||
|     @Test | ||||
|     void givenJsonWithFieldsToScrub_whenApply_thenScrubFields() throws Exception{ | ||||
|          | ||||
|         JsonFactory jf = new JsonFactory(new ObjectMapper()); | ||||
|         JsonParser parser = jf.createParser(JSON_WITH_FIELDS_TO_SCRUB); | ||||
|         JsonNode root = parser.readValueAsTree(); | ||||
|          | ||||
|         Config config = new Config(); | ||||
|         config.setFields("ssn|account"); | ||||
|         config.setReplacement("*"); | ||||
|         Scrubber scrubber = new ScrubResponseGatewayFilterFactory.Scrubber(config); | ||||
|          | ||||
|         JsonNode scrubbed = Mono.from(scrubber.apply(null, root)).block(); | ||||
|         assertNotNull(scrubbed); | ||||
|         assertEquals("*", scrubbed.get("ssn").asText()); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     void givenJsonWithoutFieldsToScrub_whenApply_theBodUnchanged() throws Exception{ | ||||
|          | ||||
|         JsonFactory jf = new JsonFactory(new ObjectMapper()); | ||||
|         JsonParser parser = jf.createParser(JSON_WITH_FIELDS_TO_SCRUB); | ||||
|         JsonNode root = parser.readValueAsTree(); | ||||
|          | ||||
|         Config config = new Config(); | ||||
|         config.setFields("xxxx"); | ||||
|         config.setReplacement("*"); | ||||
|         Scrubber scrubber = new ScrubResponseGatewayFilterFactory.Scrubber(config); | ||||
|          | ||||
|         JsonNode scrubbed = Mono.from(scrubber.apply(null, root)).block(); | ||||
|         assertNotNull(scrubbed); | ||||
|         assertNotEquals("*", scrubbed.get("ssn").asText()); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -1,135 +0,0 @@ | ||||
| package com.baeldung.springcloudgateway.customfilters.gatewayapp.filters.factories; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| import java.net.InetSocketAddress; | ||||
| import java.util.Collections; | ||||
| 
 | ||||
| import org.junit.AfterClass; | ||||
| import org.junit.BeforeClass; | ||||
| import org.junit.jupiter.api.Test; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.boot.CommandLineRunner; | ||||
| import org.springframework.boot.test.context.SpringBootTest; | ||||
| import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; | ||||
| import org.springframework.boot.test.context.TestConfiguration; | ||||
| import org.springframework.boot.web.server.LocalServerPort; | ||||
| import org.springframework.cloud.gateway.filter.factory.SetPathGatewayFilterFactory; | ||||
| 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.http.MediaType; | ||||
| import org.springframework.http.client.reactive.ReactorClientHttpConnector; | ||||
| import org.springframework.security.config.web.server.ServerHttpSecurity; | ||||
| import org.springframework.security.web.server.SecurityWebFilterChain; | ||||
| import org.springframework.test.web.reactive.server.WebTestClient; | ||||
| 
 | ||||
| import com.sun.net.httpserver.HttpServer; | ||||
| 
 | ||||
| import reactor.netty.http.client.HttpClient; | ||||
| 
 | ||||
| @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) | ||||
| public class ScrubResponseGatewayFilterLiveTest { | ||||
|      | ||||
|     private static Logger log = LoggerFactory.getLogger(ScrubResponseGatewayFilterLiveTest.class); | ||||
|      | ||||
|     private static final String JSON_WITH_FIELDS_TO_SCRUB = "{\r\n" | ||||
|         + "  \"name\" : \"John Doe\",\r\n" | ||||
|         + "        \"ssn\"  : \"123-45-9999\",\r\n" | ||||
|         + "        \"account\" : \"9999888877770000\"\r\n" | ||||
|         + "}"; | ||||
| 
 | ||||
|     private static final String JSON_WITH_SCRUBBED_FIELDS = "{\r\n" | ||||
|         + "  \"name\" : \"John Doe\",\r\n" | ||||
|         + "        \"ssn\"  : \"*\",\r\n" | ||||
|         + "        \"account\" : \"9999888877770000\"\r\n" | ||||
|         + "}"; | ||||
|      | ||||
|     @LocalServerPort | ||||
|     String port; | ||||
|      | ||||
|     @Autowired | ||||
|     private WebTestClient client; | ||||
|      | ||||
|     @Autowired HttpServer server; | ||||
|      | ||||
|     @Test | ||||
|     public void givenRequestToScrubRoute_thenResponseScrubbed() { | ||||
|                  | ||||
|         client.get() | ||||
|           .uri("/scrub") | ||||
|           .accept(MediaType.APPLICATION_JSON) | ||||
|           .exchange() | ||||
|           .expectStatus() | ||||
|             .is2xxSuccessful() | ||||
|           .expectHeader() | ||||
|             .contentType(MediaType.APPLICATION_JSON) | ||||
|           .expectBody() | ||||
|             .json(JSON_WITH_SCRUBBED_FIELDS); | ||||
|     } | ||||
|      | ||||
|      | ||||
|     @TestConfiguration | ||||
|     public static class TestRoutesConfiguration { | ||||
|          | ||||
|          | ||||
|         @Bean | ||||
|         public RouteLocator scrubSsnRoute(RouteLocatorBuilder builder, ScrubResponseGatewayFilterFactory scrubFilterFactory, SetPathGatewayFilterFactory pathFilterFactory, HttpServer server ) { | ||||
|              | ||||
|             log.info("[I92] Creating scrubSsnRoute..."); | ||||
|              | ||||
|             int mockServerPort = server.getAddress().getPort(); | ||||
|             ScrubResponseGatewayFilterFactory.Config config = new ScrubResponseGatewayFilterFactory.Config(); | ||||
|             config.setFields("ssn"); | ||||
|             config.setReplacement("*"); | ||||
|              | ||||
|             SetPathGatewayFilterFactory.Config pathConfig = new SetPathGatewayFilterFactory.Config(); | ||||
|             pathConfig.setTemplate("/customer"); | ||||
|              | ||||
|             return builder.routes() | ||||
|               .route("scrub_ssn", | ||||
|                  r -> r.path("/scrub") | ||||
|                    .filters(  | ||||
|                       f -> f | ||||
|                         .filter(scrubFilterFactory.apply(config)) | ||||
|                         .filter(pathFilterFactory.apply(pathConfig))) | ||||
|                    .uri("http://localhost:" + mockServerPort )) | ||||
|               .build(); | ||||
|         } | ||||
|          | ||||
|         @Bean | ||||
|         public SecurityWebFilterChain testFilterChain(ServerHttpSecurity http ) { | ||||
|              | ||||
|             // @formatter:off | ||||
|             return http.authorizeExchange() | ||||
|               .anyExchange() | ||||
|                 .permitAll() | ||||
|               .and() | ||||
|               .build(); | ||||
|             // @formatter:on | ||||
|         } | ||||
|          | ||||
|         @Bean | ||||
|         public HttpServer mockServer() throws IOException { | ||||
|              | ||||
|             log.info("[I48] Starting mock server..."); | ||||
|              | ||||
|             HttpServer server = HttpServer.create(new InetSocketAddress(0),0); | ||||
|             server.createContext("/customer", (exchange) ->  { | ||||
|                 exchange.getResponseHeaders().set("Content-Type", "application/json"); | ||||
|                  | ||||
|                 byte[] response = JSON_WITH_FIELDS_TO_SCRUB.getBytes("UTF-8"); | ||||
|                 exchange.sendResponseHeaders(200,response.length); | ||||
|                 exchange.getResponseBody().write(response); | ||||
|             }); | ||||
|              | ||||
|             server.setExecutor(null); | ||||
|             server.start(); | ||||
|              | ||||
|             log.info("[I65] Mock server started. port={}", server.getAddress().getPort()); | ||||
|             return server; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -1,28 +0,0 @@ | ||||
| package com.baeldung.springcloudgateway.customfilters.secondservice; | ||||
| 
 | ||||
| import org.junit.jupiter.api.Test; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration; | ||||
| import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest; | ||||
| import org.springframework.test.web.reactive.server.WebTestClient; | ||||
| 
 | ||||
| import com.baeldung.springcloudgateway.customfilters.secondservice.web.SecondServiceRestController; | ||||
| 
 | ||||
| @WebFluxTest(controllers = SecondServiceRestController.class, | ||||
|         excludeAutoConfiguration = ReactiveSecurityAutoConfiguration.class) | ||||
| public class SecondServiceIntegrationTest { | ||||
| 
 | ||||
|     @Autowired | ||||
|     private WebTestClient webClient; | ||||
| 
 | ||||
|     @Test | ||||
|     public void whenResourceLanguageEndpointCalled_thenRetrievesSpanishLanguageString() throws Exception { | ||||
|         this.webClient.get() | ||||
|             .uri("/resource/language") | ||||
|             .exchange() | ||||
|             .expectStatus() | ||||
|             .isOk() | ||||
|             .expectBody(String.class) | ||||
|             .isEqualTo("es"); | ||||
|     } | ||||
| } | ||||
| @ -1,12 +0,0 @@ | ||||
| package com.baeldung.springcloudgateway.customfilters.secondservice; | ||||
| 
 | ||||
| import org.junit.jupiter.api.Test; | ||||
| import org.springframework.boot.test.context.SpringBootTest; | ||||
| 
 | ||||
| @SpringBootTest(classes = SecondServiceApplication.class) | ||||
| public class SpringContextTest { | ||||
| 
 | ||||
|     @Test | ||||
|     public void whenSpringContextIsBootstrapped_thenNoExceptions() { | ||||
|     } | ||||
| } | ||||
| @ -1,31 +0,0 @@ | ||||
| package com.baeldung.springcloudgateway.customfilters.service; | ||||
| 
 | ||||
| import org.junit.jupiter.api.Test; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration; | ||||
| import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest; | ||||
| import org.springframework.http.HttpHeaders; | ||||
| import org.springframework.test.web.reactive.server.WebTestClient; | ||||
| 
 | ||||
| import com.baeldung.springcloudgateway.customfilters.service.web.ServiceRestController; | ||||
| 
 | ||||
| @WebFluxTest(controllers = ServiceRestController.class, | ||||
|         excludeAutoConfiguration = ReactiveSecurityAutoConfiguration.class) | ||||
| public class ServiceIntegrationTest { | ||||
| 
 | ||||
|     @Autowired | ||||
|     private WebTestClient webClient; | ||||
| 
 | ||||
|     @Test | ||||
|     public void whenResourceEndpointCalled_thenRetrievesResourceStringWithContentLanguageHeader() throws Exception { | ||||
|         this.webClient.get() | ||||
|             .uri("/resource") | ||||
|             .exchange() | ||||
|             .expectStatus() | ||||
|             .isOk() | ||||
|             .expectHeader() | ||||
|             .valueEquals(HttpHeaders.CONTENT_LANGUAGE, "en") | ||||
|             .expectBody(String.class) | ||||
|             .isEqualTo("Service Resource"); | ||||
|     } | ||||
| } | ||||
| @ -1,12 +0,0 @@ | ||||
| package com.baeldung.springcloudgateway.customfilters.service; | ||||
| 
 | ||||
| import org.junit.jupiter.api.Test; | ||||
| import org.springframework.boot.test.context.SpringBootTest; | ||||
| 
 | ||||
| @SpringBootTest(classes = ServiceApplication.class) | ||||
| public class SpringContextTest { | ||||
| 
 | ||||
|     @Test | ||||
|     public void whenSpringContextIsBootstrapped_thenNoExceptions() { | ||||
|     } | ||||
| } | ||||
| @ -1,4 +1,4 @@ | ||||
| package com.baeldung.springcloudgateway.customfilters.gatewayapp.utils; | ||||
| package com.baeldung.springcloudgateway.introduction; | ||||
| 
 | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
| @ -1,109 +0,0 @@ | ||||
| package com.baeldung.springcloudgateway.rewrite; | ||||
| 
 | ||||
| import static org.junit.Assert.assertTrue; | ||||
| import static org.junit.jupiter.api.Assertions.assertEquals; | ||||
| 
 | ||||
| import java.io.OutputStream; | ||||
| import java.net.InetSocketAddress; | ||||
| 
 | ||||
| import org.junit.AfterClass; | ||||
| import org.junit.jupiter.api.Test; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.boot.test.context.SpringBootTest; | ||||
| import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; | ||||
| import org.springframework.boot.web.server.LocalServerPort; | ||||
| import org.springframework.test.context.ActiveProfiles; | ||||
| import org.springframework.test.context.DynamicPropertyRegistry; | ||||
| import org.springframework.test.context.DynamicPropertySource; | ||||
| import org.springframework.test.web.reactive.server.WebTestClient; | ||||
| 
 | ||||
| import com.sun.net.httpserver.HttpServer; | ||||
| 
 | ||||
| @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) | ||||
| @ActiveProfiles({ "nosecurity", "url-rewrite" }) | ||||
| class URLRewriteGatewayApplicationLiveTest { | ||||
| 
 | ||||
|     // NOTE for Eclipse users: By default, Eclipse will complain about com.sun.** classes. | ||||
|     // To solve this issue, follow instructions available at the : | ||||
|     // https://stackoverflow.com/questions/13155734/eclipse-cant-recognize-com-sun-net-httpserver-httpserver-package | ||||
|     private static HttpServer mockServer; | ||||
|     private static Logger log = LoggerFactory.getLogger(URLRewriteGatewayApplicationLiveTest.class); | ||||
| 
 | ||||
|     // Create a running HttpServer that echoes back the request URL. | ||||
|     private static HttpServer startTestServer() { | ||||
| 
 | ||||
|         try { | ||||
|             log.info("[I26] Starting mock server"); | ||||
|             mockServer = HttpServer.create(); | ||||
|             mockServer.bind(new InetSocketAddress(0), 0); | ||||
|             mockServer.createContext("/api", (xchg) -> { | ||||
|                 String uri = xchg.getRequestURI() | ||||
|                     .toString(); | ||||
|                 log.info("[I23] Backend called: uri={}", uri); | ||||
|                 xchg.getResponseHeaders() | ||||
|                     .add("Content-Type", "text/plain"); | ||||
|                 xchg.sendResponseHeaders(200, 0); | ||||
|                 OutputStream os = xchg.getResponseBody(); | ||||
|                 os.write(uri.getBytes()); | ||||
|                 os.flush(); | ||||
|                 os.close(); | ||||
|             }); | ||||
| 
 | ||||
|             mockServer.start(); | ||||
|             InetSocketAddress localAddr = mockServer.getAddress(); | ||||
|             log.info("[I36] mock server started: local address={}", localAddr); | ||||
|              | ||||
|             return mockServer; | ||||
|         } catch (Exception ex) { | ||||
|             throw new RuntimeException(ex); | ||||
|         } | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     // TIP: https://www.baeldung.com/spring-dynamicpropertysource | ||||
|     @DynamicPropertySource | ||||
|     static void registerBackendServer(DynamicPropertyRegistry registry) { | ||||
|         registry.add("rewrite.backend.uri", () -> { | ||||
|             HttpServer s = startTestServer(); | ||||
|             return "http://localhost:" + s.getAddress().getPort(); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     @AfterClass | ||||
|     public static void stopMockBackend() throws Exception { | ||||
|         log.info("[I40] Shutdown mock http server"); | ||||
|         mockServer.stop(5); | ||||
|     } | ||||
| 
 | ||||
|     @LocalServerPort | ||||
|     private int localPort; | ||||
| 
 | ||||
|     @Test | ||||
|     void testWhenApiCall_thenRewriteSuccess(@Autowired WebTestClient webClient) { | ||||
|         webClient.get() | ||||
|           .uri("http://localhost:" + localPort + "/v1/customer/customer1") | ||||
|           .exchange() | ||||
|           .expectBody() | ||||
|           .consumeWith((result) -> { | ||||
|               String body = new String(result.getResponseBody()); | ||||
|               log.info("[I99] body={}", body); | ||||
|               assertEquals("/api/customer1", body); | ||||
|           }); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     void testWhenDslCall_thenRewriteSuccess(@Autowired WebTestClient webClient) { | ||||
|         webClient.get() | ||||
|           .uri("http://localhost:" + localPort + "/v2/zip/123456") | ||||
|           .exchange() | ||||
|           .expectBody() | ||||
|           .consumeWith((result) -> { | ||||
|               String body = new String(result.getResponseBody()); | ||||
|               log.info("[I99] body={}", body); | ||||
|               assertTrue(body.matches("/api/zip/123456-\\d{3}")); | ||||
|           }); | ||||
|     } | ||||
|      | ||||
| } | ||||
| @ -1,64 +0,0 @@ | ||||
| package com.baeldung.springcloudgateway.webfilters; | ||||
| 
 | ||||
| import org.junit.After; | ||||
| import org.junit.Before; | ||||
| import org.junit.jupiter.api.RepeatedTest; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.beans.factory.annotation.Value; | ||||
| import org.springframework.boot.test.context.SpringBootTest; | ||||
| import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; | ||||
| import org.springframework.boot.test.context.TestConfiguration; | ||||
| import org.springframework.boot.test.web.client.TestRestTemplate; | ||||
| import org.springframework.boot.web.server.LocalServerPort; | ||||
| import org.springframework.http.ResponseEntity; | ||||
| import org.springframework.test.context.ActiveProfiles; | ||||
| 
 | ||||
| import redis.embedded.RedisServer; | ||||
| 
 | ||||
| @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) | ||||
| @ActiveProfiles("webfilters") | ||||
| @TestConfiguration | ||||
| public class RedisWebFilterFactoriesLiveTest { | ||||
|      | ||||
|     private static final Logger LOGGER = LoggerFactory.getLogger(RedisWebFilterFactoriesLiveTest.class); | ||||
|      | ||||
|     private RedisServer redisServer; | ||||
|      | ||||
|     public RedisWebFilterFactoriesLiveTest() { | ||||
|     } | ||||
| 
 | ||||
|     @Before | ||||
|     public void postConstruct() { | ||||
|         this.redisServer = new RedisServer(6379); | ||||
|         redisServer.start();     | ||||
|     } | ||||
|      | ||||
|     @LocalServerPort | ||||
|     String port; | ||||
| 
 | ||||
|     @Autowired | ||||
|     private TestRestTemplate restTemplate; | ||||
| 
 | ||||
|     @Autowired | ||||
|     TestRestTemplate template; | ||||
|   | ||||
|     @RepeatedTest(25) | ||||
|     public void whenCallRedisGetThroughGateway_thenOKStatusOrIsReceived() { | ||||
|         String url = "http://localhost:" + port + "/redis/get"; | ||||
| 
 | ||||
|         ResponseEntity<String> r = restTemplate.getForEntity(url, String.class); | ||||
|         // assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); | ||||
| 
 | ||||
|         LOGGER.info("Received: status->{}, reason->{}, remaining->{}",  | ||||
|           r.getStatusCodeValue(), r.getStatusCode().getReasonPhrase(),  | ||||
|           r.getHeaders().get("X-RateLimit-Remaining")); | ||||
|     } | ||||
|      | ||||
|     @After | ||||
|     public void preDestroy() { | ||||
|         redisServer.stop(); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -1,136 +0,0 @@ | ||||
| package com.baeldung.springcloudgateway.webfilters; | ||||
| 
 | ||||
| import static org.assertj.core.api.Assertions.assertThat; | ||||
| import static org.junit.Assert.assertFalse; | ||||
| import static org.junit.Assert.assertNull; | ||||
| import static org.junit.Assert.assertTrue; | ||||
| 
 | ||||
| import org.assertj.core.api.Condition; | ||||
| import org.json.JSONException; | ||||
| import org.json.JSONObject; | ||||
| import org.junit.jupiter.api.BeforeEach; | ||||
| import org.junit.jupiter.api.Test; | ||||
| import org.springframework.beans.factory.annotation.Autowired; | ||||
| 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.web.server.LocalServerPort; | ||||
| 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.context.ActiveProfiles; | ||||
| import org.springframework.test.web.reactive.server.WebTestClient; | ||||
| import org.springframework.test.web.reactive.server.WebTestClient.ResponseSpec; | ||||
| 
 | ||||
| @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) | ||||
| @ActiveProfiles("webfilters") | ||||
| public class WebFilterFactoriesLiveTest { | ||||
| 
 | ||||
|     @LocalServerPort | ||||
|     String port; | ||||
| 
 | ||||
|     @Autowired | ||||
|     private WebTestClient client; | ||||
| 
 | ||||
|     @Autowired | ||||
|     private TestRestTemplate restTemplate; | ||||
| 
 | ||||
|     @BeforeEach | ||||
|     public void configureClient() { | ||||
|         client = WebTestClient.bindToServer() | ||||
|           .baseUrl("http://localhost:" + port) | ||||
|           .build(); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void whenCallGetThroughGateway_thenAllHTTPRequestHeadersParametersAreSet() throws JSONException { | ||||
|         String url = "http://localhost:" + port + "/get"; | ||||
|         ResponseEntity<String> response = restTemplate.getForEntity(url, String.class); | ||||
|         assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); | ||||
| 
 | ||||
|         JSONObject json = new JSONObject(response.getBody()); | ||||
|         JSONObject headers = json.getJSONObject("headers"); | ||||
|         assertThat(headers.getString("My-Header-Good")).isEqualTo("Good"); | ||||
|         assertThat(headers.getString("My-Header-Bad")).isEqualTo("Good"); | ||||
|         assertThat(headers.getString("My-Header-Set")).isEqualTo("Set"); | ||||
|         assertTrue(headers.isNull("My-Header-Remove")); | ||||
|         JSONObject vars = json.getJSONObject("args"); | ||||
|         assertThat(vars.getString("var")).isEqualTo("good"); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void whenCallHeaderPostThroughGateway_thenAllHTTPResponseHeadersAreSet() { | ||||
|         ResponseSpec response = client.post() | ||||
|           .uri("/header/post") | ||||
|           .exchange(); | ||||
| 
 | ||||
|         response.expectStatus() | ||||
|           .isOk() | ||||
|           .expectHeader() | ||||
|           .valueEquals("My-Header-Rewrite", "password=***") | ||||
|           .expectHeader() | ||||
|           .valueEquals("My-Header-Set", "Set") | ||||
|           .expectHeader() | ||||
|           .valueEquals("My-Header-Good", "Good") | ||||
|           .expectHeader() | ||||
|           .doesNotExist("My-Header-Remove"); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void whenCallPostThroughGateway_thenBodyIsRetrieved() throws JSONException { | ||||
|         String url = "http://localhost:" + port + "/post"; | ||||
| 
 | ||||
|         HttpEntity<String> entity = new HttpEntity<>("content", new HttpHeaders()); | ||||
| 
 | ||||
|         ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, entity, String.class); | ||||
|         assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); | ||||
| 
 | ||||
|         JSONObject json = new JSONObject(response.getBody()); | ||||
|         JSONObject data = json.getJSONObject("json"); | ||||
|         assertThat(data.getString("message")).isEqualTo("CONTENT"); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void whenCallPutThroughGateway_thenBodyIsRetrieved() throws JSONException { | ||||
|         String url = "http://localhost:" + port + "/put"; | ||||
| 
 | ||||
|         HttpEntity<String> entity = new HttpEntity<>("CONTENT", new HttpHeaders()); | ||||
| 
 | ||||
|         ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.PUT, entity, String.class); | ||||
|         assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); | ||||
| 
 | ||||
|         JSONObject json = new JSONObject(response.getBody()); | ||||
|         assertThat(json.getString("message")).isEqualTo("New Body"); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void whenCallDeleteThroughGateway_thenIsUnauthorizedCodeIsSet() { | ||||
|         ResponseSpec response = client.delete() | ||||
|           .uri("/delete") | ||||
|           .exchange(); | ||||
| 
 | ||||
|         response.expectStatus() | ||||
|           .isUnauthorized(); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void whenCallFakePostThroughGateway_thenIsUnauthorizedCodeIsSet() { | ||||
|         ResponseSpec response = client.post() | ||||
|           .uri("/fake/post") | ||||
|           .exchange(); | ||||
| 
 | ||||
|         response.expectStatus() | ||||
|           .is3xxRedirection(); | ||||
|     } | ||||
| 
 | ||||
|     @Test | ||||
|     public void whenCallStatus504ThroughGateway_thenCircuitBreakerIsExecuted() throws JSONException { | ||||
|         String url = "http://localhost:" + port + "/status/504"; | ||||
|         ResponseEntity<String> response = restTemplate.getForEntity(url, String.class); | ||||
| 
 | ||||
|         JSONObject json = new JSONObject(response.getBody()); | ||||
|         assertThat(json.getString("url")).contains("anything"); | ||||
|     } | ||||
| } | ||||
| @ -1,203 +0,0 @@ | ||||
| { | ||||
| 	"info": { | ||||
| 		"_postman_id": "b3d00e23-c2cd-40ce-a90b-673efb25e5c0", | ||||
| 		"name": "Baeldung - OAuth", | ||||
| 		"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" | ||||
| 	}, | ||||
| 	"item": [ | ||||
| 		{ | ||||
| 			"name": "Token", | ||||
| 			"event": [ | ||||
| 				{ | ||||
| 					"listen": "test", | ||||
| 					"script": { | ||||
| 						"exec": [ | ||||
| 							"var jsonData = pm.response.json();\r", | ||||
| 							"pm.environment.set(\"access_token\", jsonData.access_token);\r", | ||||
| 							"pm.environment.set(\"refresh_token\", jsonData.refresh_token);\r", | ||||
| 							"pm.environment.set(\"backend_token\", \"Bearer \" + jsonData.access_token);" | ||||
| 						], | ||||
| 						"type": "text/javascript" | ||||
| 					} | ||||
| 				} | ||||
| 			], | ||||
| 			"request": { | ||||
| 				"method": "POST", | ||||
| 				"header": [], | ||||
| 				"body": { | ||||
| 					"mode": "urlencoded", | ||||
| 					"urlencoded": [ | ||||
| 						{ | ||||
| 							"key": "client_id", | ||||
| 							"value": "{{client_id}}", | ||||
| 							"type": "text" | ||||
| 						}, | ||||
| 						{ | ||||
| 							"key": "client_secret", | ||||
| 							"value": "{{client_secret}}", | ||||
| 							"type": "text" | ||||
| 						}, | ||||
| 						{ | ||||
| 							"key": "grant_type", | ||||
| 							"value": "password", | ||||
| 							"type": "text" | ||||
| 						}, | ||||
| 						{ | ||||
| 							"key": "scope", | ||||
| 							"value": "email roles profile", | ||||
| 							"type": "text" | ||||
| 						}, | ||||
| 						{ | ||||
| 							"key": "username", | ||||
| 							"value": "maxwell.smart", | ||||
| 							"type": "text" | ||||
| 						}, | ||||
| 						{ | ||||
| 							"key": "password", | ||||
| 							"value": "1234", | ||||
| 							"type": "text" | ||||
| 						} | ||||
| 					] | ||||
| 				}, | ||||
| 				"url": { | ||||
| 					"raw": "{{keycloack_base}}/token", | ||||
| 					"host": [ | ||||
| 						"{{keycloack_base}}" | ||||
| 					], | ||||
| 					"path": [ | ||||
| 						"token" | ||||
| 					] | ||||
| 				} | ||||
| 			}, | ||||
| 			"response": [] | ||||
| 		}, | ||||
| 		{ | ||||
| 			"name": "Quote", | ||||
| 			"protocolProfileBehavior": { | ||||
| 				"disabledSystemHeaders": { | ||||
| 					"accept": true | ||||
| 				} | ||||
| 			}, | ||||
| 			"request": { | ||||
| 				"auth": { | ||||
| 					"type": "bearer", | ||||
| 					"bearer": [ | ||||
| 						{ | ||||
| 							"key": "token", | ||||
| 							"value": "{{access_token}}", | ||||
| 							"type": "string" | ||||
| 						} | ||||
| 					] | ||||
| 				}, | ||||
| 				"method": "GET", | ||||
| 				"header": [ | ||||
| 					{ | ||||
| 						"key": "Accept", | ||||
| 						"value": "application/json", | ||||
| 						"type": "text" | ||||
| 					} | ||||
| 				], | ||||
| 				"url": { | ||||
| 					"raw": "http://localhost:8085/quotes/:symbol", | ||||
| 					"protocol": "http", | ||||
| 					"host": [ | ||||
| 						"localhost" | ||||
| 					], | ||||
| 					"port": "8085", | ||||
| 					"path": [ | ||||
| 						"quotes", | ||||
| 						":symbol" | ||||
| 					], | ||||
| 					"variable": [ | ||||
| 						{ | ||||
| 							"key": "symbol", | ||||
| 							"value": "IBM" | ||||
| 						} | ||||
| 					] | ||||
| 				} | ||||
| 			}, | ||||
| 			"response": [] | ||||
| 		}, | ||||
| 		{ | ||||
| 			"name": "Quote via Gateway", | ||||
| 			"protocolProfileBehavior": { | ||||
| 				"disabledSystemHeaders": { | ||||
| 					"accept": true | ||||
| 				} | ||||
| 			}, | ||||
| 			"request": { | ||||
| 				"auth": { | ||||
| 					"type": "bearer", | ||||
| 					"bearer": [ | ||||
| 						{ | ||||
| 							"key": "token", | ||||
| 							"value": "{{access_token}}", | ||||
| 							"type": "string" | ||||
| 						} | ||||
| 					] | ||||
| 				}, | ||||
| 				"method": "GET", | ||||
| 				"header": [ | ||||
| 					{ | ||||
| 						"key": "Accept", | ||||
| 						"value": "application/json", | ||||
| 						"type": "text" | ||||
| 					} | ||||
| 				], | ||||
| 				"url": { | ||||
| 					"raw": "http://localhost:8086/quotes/:symbol", | ||||
| 					"protocol": "http", | ||||
| 					"host": [ | ||||
| 						"localhost" | ||||
| 					], | ||||
| 					"port": "8086", | ||||
| 					"path": [ | ||||
| 						"quotes", | ||||
| 						":symbol" | ||||
| 					], | ||||
| 					"variable": [ | ||||
| 						{ | ||||
| 							"key": "symbol", | ||||
| 							"value": "IBM" | ||||
| 						} | ||||
| 					] | ||||
| 				} | ||||
| 			}, | ||||
| 			"response": [] | ||||
| 		} | ||||
| 	], | ||||
| 	"event": [ | ||||
| 		{ | ||||
| 			"listen": "prerequest", | ||||
| 			"script": { | ||||
| 				"type": "text/javascript", | ||||
| 				"exec": [ | ||||
| 					"" | ||||
| 				] | ||||
| 			} | ||||
| 		}, | ||||
| 		{ | ||||
| 			"listen": "test", | ||||
| 			"script": { | ||||
| 				"type": "text/javascript", | ||||
| 				"exec": [ | ||||
| 					"" | ||||
| 				] | ||||
| 			} | ||||
| 		} | ||||
| 	], | ||||
| 	"variable": [ | ||||
| 		{ | ||||
| 			"key": "keycloack_base", | ||||
| 			"value": "http://localhost:8083/auth/realms/baeldung/protocol/openid-connect" | ||||
| 		}, | ||||
| 		{ | ||||
| 			"key": "client_id", | ||||
| 			"value": "quotes-client" | ||||
| 		}, | ||||
| 		{ | ||||
| 			"key": "client_secret", | ||||
| 			"value": "56be94c8-b20a-4374-899c-e39cb022d3f8" | ||||
| 		} | ||||
| 	] | ||||
| } | ||||
| @ -1,7 +1,7 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <configuration> | ||||
|     <appender name="LISTAPPENDER" | ||||
|         class="com.baeldung.springcloudgateway.customfilters.gatewayapp.utils.LoggerListAppender"> | ||||
|         class="com.baeldung.springcloudgateway.introduction.LoggerListAppender"> | ||||
|     </appender> | ||||
|     <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> | ||||
|         <encoder> | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user