# spring-cloud-gateway-plugin
**Repository Path**: sdhz518/spring-cloud-gateway-plugin
## Basic Information
- **Project Name**: spring-cloud-gateway-plugin
- **Description**: Spring Cloud Gateway Extra Plugin
- **Primary Language**: Unknown
- **License**: Apache-2.0
- **Default Branch**: 2.1.SR1.x
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 1
- **Created**: 2021-08-02
- **Last Updated**: 2021-08-02
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# spring-cloud-gateway-plugin
Spring Cloud Gateway Extra Plugin
[](https://travis-ci.com/chenggangpro/spring-cloud-gateway-plugin)
## Current Version
|Gateway Version|Plugin Version|
|:--|:--|
|`Greenwich.SR1`|`2.1.SR1.3.RELEASE`|
|`Greenwich.SR2`|`2.1.SR2.2.RELEASE`|
> More Detail See [Wiki](https://github.com/chenggangpro/spring-cloud-gateway-plugin/wiki)
### This Plugin Support Below Features:
* [x] Cache Request Body And Form Body
* [x] Add Request Log Filter To Log Request And Response
* [x] Add Read Json Response Body And Log Response Body
* [x] Add Global Exception Handler With Json
* [x] Add Custom Exception Handler
* [x] Add Grey Route With Ribbon And Eureka
* [x] Each Route Use different Hystrix CommandKey
* [x] Support Dynamic Predicate With Existing Routes
### How To Use This Feature
##### Note:
* This Dependency Base Spring Cloud Gateway[`Greenwich.SR1`],Suggest To Update To This SpringCloud Version,Official Resolve Some Issues , Fix Some Bugs.
* The SpringBoot Version need to update to [`2.1.6.RELEASE`],It fix reactor-netty issues
* This Dependency Is Now In Maven Central.
* The Feature To Read Request And Response Json Data Will Loss A Lot Of Performance,It Will Reduce The Gateway Traffic.
##### Step
* 1 . Include Dependency
> Spring Cloud Gateway
```xml
org.springframework.cloud
spring-cloud-starter-gateway
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
org.springframework.cloud
spring-cloud-dependencies
Greenwich.SR1
pom
import
```
> Gateway Plugin
```xml
pro.chenggang
spring-cloud-gateway-plugin
2.1.SR1.1.RELEASE
```
* 2 . Add Enable Annotation To You Application
```java
@EnableGatewayPlugin
public class SpringCloudGatewayPluginApplication {
public static void main(String[] args) {
SpringApplication.run(SpringCloudGatewayPluginApplication.class, args);
}
}
```
Note: If You User SpringBoot 2.1.0+,You should set `allow-bean-definition-overriding` to `true`
```yaml
spring:
main:
allow-bean-definition-overriding: true
```
* 3 . Choose Plugin Feature To Use
By use this annotation `@EnableGatewayPlugin` to enable the plugin,the plugin support switch to choose feature
By default,the `GatewayContext Filter` is always into system
```yaml
spring:
profiles:
active: dev
cloud:
gateway:
plugin:
config:
log-request: true
read-request-data: true # this setting will read all request data
read-response-data: true
exception-json-handler: true
enable-dynamic-route: true
grey:
enable: false
grey-ribbon-rule: weight_response
```
* 4 . Specific Setting To Enable Read Request Data
```yaml
spring:
cloud:
gateway:
plugin:
config:
read-request-data-service-id-list: #set specific serviceId from discover to read request Data
- serviceId1
- serviceId2
read-request-data-path-list: #set specific path to read request data
- /service/path1/*
- /service/path2/**
- /service/path3
```
* 5 . User GatewayContext
You Can Use GatewayContext To Get Cache JsonBody Or Form Body,Just Use
```java
GatewayContext gatewayContext = exchange.getAttribute(GatewayContext.CACHE_GATEWAY_CONTEXT);
```
* 6 . The Deference Between `GreyRibbonRule.DEFAULT` And `GreyRibbonRule.WeightResponse`
The Default GreyRibbonRule Just Use Round Rule As Base Ribbon Rule
The WeightResponse GreyRibbonRule Use WeightResponse Rule As Base Ribbon Rule
* 7 . The Grey Route
* Setup Gateway Properties
```yaml
spring:
cloud:
gateway:
plugin:
grey:
grey-rule-list:
- service-id: privider1
version: 2.0.0
operation: OR
rules:
- key: key1
value:
- value1
- value2
- value3
- key: key2
value:
- value4
- value5
- value6
- service-id: provider2
version: 2.0.0
operation: AND
rules:
- key: keya
value:
- value1a
- value2a
- value3a
- key: keyb
value:
- value4b
- value5b
- value6b
```
* Set Up Service MetaInfo
```yaml
#proiver1
eureka:
instance:
metadata-map:
version: 2.0.0
```
* The Properties Active Rule Principle
When Request URL Route To Provider1,When The Request JsonBody Or Form Data Contain The Key ->`Key1` And Match Any Of The Value->[`value1`,`value2`,`value3`]
The Route The Request To The Service Which Setup The MetaInfo With Specific Version Which Match The Gateway Grey Setup
* 8 . How To Custom GlobalException Handler With Json
In Order To Handle Other Exception,You Can Define Specific Bean Implements `ExceptionHandlerStrategy`
By default,plugin supply `DefaultExceptionHandlerStrategy` In Case Of None Strategy Exist
```java
@Slf4j
public class DefaultExceptionHandlerStrategy implements ExceptionHandlerStrategy {
@Override
public Class getHandleClass() {
return Throwable.class;
}
@Override
public ExceptionHandlerResult handleException(Throwable throwable) {
ResponseResult responseResult = new ResponseResult<>(SystemResponseInfo.GATEWAY_ERROR,throwable.getMessage());
ExceptionHandlerResult result = new ExceptionHandlerResult(HttpStatus.INTERNAL_SERVER_ERROR, JSON.toJSONString(responseResult));
log.debug("[DefaultExceptionHandlerStrategy]Handle Exception:{},Result:{}",throwable.getMessage(),result);
log.error("[DefaultExceptionHandlerStrategy]Log Exception In Error Level,Exception Message:{}",throwable.getMessage());
return result;
}
}
```
Or You Can Use `@ExceptionHandler` just like below,
The`@ResponseStatus` is optional,if you don't add `@ResponseStatus`,the default HttpStatus is HttpStatus.BAD_GATEWAY
```java
@Component
public class DemoExceptionHandler {
@ExceptionHandler({NotFoundException.class})
@ResponseStatus(HttpStatus.NOT_FOUND)
public Map handlerException(ServerWebExchange exchange,TimeoutException throwable){
LinkedHashSet originalRequestUris = exchange.getAttributeOrDefault(ServerWebExchangeUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR,null);
Map map = Maps.newHashMapWithExpectedSize(2);
map.put("URI",originalRequestUris);
map.put("ExceptionMessage",throwable.getClass().getSimpleName());
return map;
}
@ExceptionHandler({ClientException.class, TimeoutException.class})
@ResponseStatus(HttpStatus.BAD_GATEWAY)
public Map handlerException(ServerWebExchange exchange,TimeoutException throwable){
LinkedHashSet originalRequestUris = exchange.getAttributeOrDefault(ServerWebExchangeUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR,null);
Map map = Maps.newHashMapWithExpectedSize(2);
map.put("URI",originalRequestUris);
map.put("ExceptionMessage",throwable.getClass().getSimpleName());
return map;
}
}
```
* 9 . How To Use Dynamic Predicate With Existing Routes
* Define A Component Implements `DynamicRouteProcessor`,this processor process serverWebExchange for dynamic route predicate
> the `DynamicRouteProcessor` definition
```java
/**
* Process ServerWebExchange for dynamic route predicate
* @author chenggang
* @date 2019/07/17
*/
public interface DynamicRouteProcessor {
/**
* preprocess action
* @param exchange ServerWebExchange
* @return process Result ,if result is Optional.empty(),then dynamic predicate not working
*/
Optional> preprocess(ServerWebExchange exchange);
/**
* process to unify config for predicate
* @param preprocessResult pre process result
* @param route current route
* @return
*/
Optional processConfig(PreprocessResult preprocessResult, Route route);
/**
* target predicate bean 's class
* @return RoutePredicateFactory Class
*/
Optional> targetPredicateBeanClass();
}
```
> custom dynamic route processor
```java
@Component
public class CustomDynamicRouteProcessor implements DynamicRouteProcessor {
@Override
public Optional preprocess(ServerWebExchange exchange) {
String route = exchange.getRequest().getHeaders().getFirst("route");
if(StringUtils.isBlank(route)){
return Optional.of(PreprocessResult.builder().result(false).build());
}
return Optional.of(PreprocessResult.builder().result(true).resultData(route).build());
}
@Override
public Optional processConfig(PreprocessResult preprocessResult, Route route) {
if(!preprocessResult.getResult()){
return Optional.empty();
}
Object resultData = preprocessResult.getResultData();
if(!(resultData instanceof String)){
return Optional.empty();
}
String data = (String) resultData;
DemoDynamicRoutePredicateFactory.Config config = DemoDynamicRoutePredicateFactory.Config.builder().header(data).route(route).build();
return Optional.of(config);
}
@Override
public Optional> targetPredicateBeanClass() {
return Optional.of(DemoDynamicRoutePredicateFactory.class);
}
}
```
> Define a `AbstractRoutePredicateFactory` ,the `Config` Class Must Implements `DynamicRouteConfig`
```java
@Slf4j
@Component
public class DemoDynamicRoutePredicateFactory extends AbstractRoutePredicateFactory {
public DemoDynamicRoutePredicateFactory() {
super(Config.class);
}
@Override
public Predicate apply(Config config) {
return exchange -> {
Route route = config.getRoute();
if(Objects.isNull(route.getUri())){
log.debug("Route Uri Is NUll Return False,RouteID:{}",route.getId());
return false;
}
String routeUriHost = route.getUri().getHost();
String headerData = config.getHeader();
if(StringUtils.isBlank(routeUriHost) || StringUtils.isBlank(headerData)){
log.debug("Route Uri Host Or HeaderData Is Blank Return False,RouteID:{}",route.getId());
return false;
}
if(routeUriHost.equalsIgnoreCase(headerData)){
log.debug("Route Uri Host Matched Header Data Return True,RouteID:{}",route.getId());
route.getFilters();
return true;
}
log.debug("Route Uri Not Matched Return False,RouteID:{}",route.getId());
return false;
};
}
@Getter
@Setter
@Builder
@ToString
@AllArgsConstructor
public static class Config implements DynamicRouteConfig {
private Route route;
private String header;
}
}
```
This Feature Support dynamic predicate with existing routes,Fox example: You can according the custom header to match the loadbalance route,
* More logical detail to see `DynamicRoutePredicateHandlerMapping`