-
Notifications
You must be signed in to change notification settings - Fork 66
Getting Started
- Include Dependency
- Add Enable Annotation To You Application
- Choose-Plugin-Feature-To-Use
-
Specific Setting
- Enable Read Request Data
- The Grey Route
- Custom GlobalException Handler With Json
- Different Hystrix CommandKey For Each Route
- Use GatewayContext
- Dynamic Predicate With Existing Routes
Spring Cloud Gateway [Greenwich.SR1]
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--If you need to use grey route,you should add next dependency ,but grey route only can be used with eureka discover-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Gateway Plugin
<dependency>
<groupId>pro.chenggang</groupId>
<artifactId>spring-cloud-gateway-plugin</artifactId>
<version>${version}</version>
</dependency>
@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
spring:
main:
allow-bean-definition-overriding: true
By default,the GatewayContext Filter
is always into system
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
grey:
enable: false
grey-ribbon-rule: weight_response # assign grey ribbon rule
You Can Use GatewayContext To Get Cache JsonBody Or Form Body,Just Use
GatewayContext gatewayContext = exchange.getAttribute(GatewayContext.CACHE_GATEWAY_CONTEXT);
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
-
1 . Enable Read Request Data
Specific Setting To Enable Read Request Data,This is helpful because : Read all request data will loss a lot of performance.It will reduce the gateway traffic.
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
-
2 . The Grey Route
This feature only worked with eureka discover
- Setup Gateway Properties
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
#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
-
3 . How To Custom GlobalException Handler With Json
In Order To Handle Other Exception,You Can Define Specific Bean Implements
ExceptionHandlerStrategy
By default,plugin supplyDefaultExceptionHandlerStrategy
In Case Of None Strategy Exist@Slf4j public class DefaultExceptionHandlerStrategy implements ExceptionHandlerStrategy { @Override public Class getHandleClass() { return Throwable.class; } @Override public ExceptionHandlerResult handleException(Throwable throwable) { ResponseResult<String> 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.INTERNAL_SERVER_ERROR@Component public class DemoExceptionHandler { @ExceptionHandler({NotFoundException.class}) @ResponseStatus(HttpStatus.NOT_FOUND) public Map handlerException(ServerWebExchange exchange,TimeoutException throwable){ LinkedHashSet<URI> 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 your custom object ,it will be serialize to json return map; } @ExceptionHandler({ClientException.class, TimeoutException.class}) @ResponseStatus(HttpStatus.BAD_GATEWAY) public Map handlerException(ServerWebExchange exchange,TimeoutException throwable){ LinkedHashSet<URI> 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 your custom object ,it will be serialize to json return map; } }
-
4 . How To Use different Hystrix CommandKey For Each Route
Include Hystrix Dependency
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
Just Change the
HystrixGatewayFilterFactory
toRouteHystrixGatewayFilterFactory
The Official reference about is
HystrixGatewayFilterFactory
HystrixGatewayFilterFactoryThe properties setting just as below
spring.cloud.gateway.default-filters[0].name = RouteHystrix spring.cloud.gateway.default-filters[0].args.name = fallbackcmd spring.cloud.gateway.default-filters[0].args.fallbackUri = forward:/sys/fallback hystrix.command.default.execution.isolation.strategy = SEMAPHORE hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds = 90000 hystrix.command.default.execution.isolation.semaphore.maxConcurrentRequests = 10000 hystrix.command.default.circuitBreaker.requestVolumeThreshold = 5000 hystrix.command.default.circuitBreaker.errorThresholdPercentage = 80
You should set
hystrix.command.default
to custom hystrix settings. The Command Key isconfig.name+":"+ routeId
-
5 . How To Use GatewayContext
You Can Use GatewayContext To Get Cache JsonBody Or Form Body,Just Use
GatewayContext gatewayContext = exchange.getAttribute(GatewayContext.CACHE_GATEWAY_CONTEXT);
The
GatewayContext
Contain below fields@Getter @Setter @ToString public class GatewayContext { public static final String CACHE_GATEWAY_CONTEXT = "cacheGatewayContext"; /** * whether read request data */ private Boolean readRequestData = false; /** * whether read response data */ private Boolean readResponseData = false; /** * cache json body */ private String requestBody; /** * cache Response Body */ private Object responseBody; /** * request headers */ private HttpHeaders requestHeaders; /** * cache form data */ private MultiValueMap<String, String> formData; /** * cache all request data include:form data and query param */ private MultiValueMap<String, String> allRequestData = new LinkedMultiValueMap<>(0); }
-
6 . Dynamic Predicate With Existing Routes
- Define A Component Implements
DynamicRouteProcessor
,this processor process serverWebExchange for dynamic route predicate
the
DynamicRouteProcessor
definition/** * Process ServerWebExchange for dynamic route predicate * @author chenggang * @date 2019/07/17 */ public interface DynamicRouteProcessor<T> { /** * preprocess action * @param exchange ServerWebExchange * @return process Result ,if result is Optional.empty(),then dynamic predicate not working */ Optional<PreprocessResult<T>> preprocess(ServerWebExchange exchange); /** * process to unify config for predicate * @param preprocessResult pre process result * @param route current route * @return */ Optional<DynamicRouteConfig> processConfig(PreprocessResult<T> preprocessResult, Route route); /** * target predicate bean 's class * @return RoutePredicateFactory Class */ Optional<Class< ? extends AbstractRoutePredicateFactory>> targetPredicateBeanClass(); }
custom dynamic route processor
@Component public class CustomDynamicRouteProcessor implements DynamicRouteProcessor { @Override public Optional<PreprocessResult> 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<DynamicRouteConfig> 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<Class<? extends AbstractRoutePredicateFactory>> targetPredicateBeanClass() { return Optional.of(DemoDynamicRoutePredicateFactory.class); } }
Define a
AbstractRoutePredicateFactory
,theConfig
Class Must ImplementsDynamicRouteConfig
@Slf4j @Component public class DemoDynamicRoutePredicateFactory extends AbstractRoutePredicateFactory<DemoDynamicRoutePredicateFactory.Config> { public DemoDynamicRoutePredicateFactory() { super(Config.class); } @Override public Predicate<ServerWebExchange> 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
- Define A Component Implements
-
7 . Add ExtraData Into GatewayContext
a. Implement
GatewayContextExtraData<T>
With Your Own ExtraDatapackage pro.chenggang.plugin.springcloud.gateway.context; public interface GatewayContextExtraData<T> { /** * get context extra data * @return */ T getData(); }
b. Implement
ContextExtraDataGenerator<T>
,And Make It As a Spring Bean. This will used in Gatewaycontext Filterpackage pro.chenggang.plugin.springcloud.gateway.context; public interface ContextExtraDataGenerator<T> { /** * generate context extra data * @param serverWebExchange * @return */ GatewayContextExtraData<T> generateContextExtraData(ServerWebExchange serverWebExchange); }
@Bean public ContextExtraDataGenerator contextExtraDataGenerator(){ return new CustomeContextExtraDataGenerator(); }
c. Use GatewayContext To Get Your Own ExtraData
GatewayContext gatewayContext = exchange.getAttribute(GatewayContext.CACHE_GATEWAY_CONTEXT); Object extraData = gatewayContext.getGatewayContextExtraData().getData();