The introduction

At present, all the business services of our company are connected to the Spring Cloud Gateway, and dynamic routing is definitely involved in the connection process: the routing configuration is loaded from the database or Redis. We also implemented some custom filters of the GlobalFilter and GatewayFilter types.

  • GlobalFilterGlobal filter. All requests go through this type of filter.
  • GatewayFilterThe filter of a route can be mounted to a specific route.

Today we mainly introduce how to realize dynamic routing of Spring Cloud Gateway and how to realize a built-in filter based on GatewayFilter

Implementation – Dynamic routing

Before the realization of dynamic routing, we read the next Spring slightly Cloud Gateway’s source code, will find that have a InMemoryRouteDefinitionRepository class, it has three implementation method

  • saveSave the routing
  • deleteDelete the routing
  • getRouteDefinitionsTo get the routing

The object being operated on here is a RouteDefinition instance, which is just a Route definition. The Route implementation is Route.

Knowing how Spring Cloud Gateway handles routing, it’s easy to implement a set of dynamic routing ourselves. Here we have a new RedisRouteDefinitionLocator class, the key routing test under load code is as follows:

Public Mono<Void> refresh(){// Load the configured routing information from Redis List<SysRouteDTO> routes = cacheTemplate.valueGetList(CommonConstants.SYS_ROUTE_KEY, SysRouteDTO.class); For (SysRouteDTO route: routes){try {// Predicate List<PredicateDefinition> predicates = Lists. NewArrayList (); PredicateDefinition predicateDefinition = buildPredicateDefinition(route); predicates.add(predicateDefinition); // Filter List<FilterDefinition> filters = Lists. NewArrayList (); FilterDefinition stripPrefixFilterDefinition = buildStripPrefixFilterDefinition(route); filters.add(stripPrefixFilterDefinition); if(StringUtil.isNotEmpty(route.getFilters())){ List<FilterDefinition> customFilters = JsonUtil.fromListJson(route.getFilters(), FilterDefinition.class); this.reloadArgs(customFilters); filters.addAll(customFilters); } // Metadata Map<String, Object> metadata = this.buildMetadata(route); // Proxy path String targetUri = stringutil.isnotempty (route.geturl ())? route.getUrl() : "lb://" + route.getServiceId(); URI uri = UriComponentsBuilder.fromUriString(targetUri).build().toUri(); RouteDefinition RouteDefinition = new RouteDefinition(); routeDefinition.setId(route.getRouteName()); routeDefinition.setPredicates(predicates); routeDefinition.setUri(uri); routeDefinition.setFilters(filters); routeDefinition.setMetadata(metadata); this.repository.save(Mono.just(routeDefinition)).subscribe(); }catch (Exception ex) {log.error(" route load failed: name={}, error={}", ex.getMessage(), ex); } } return Mono.empty(); }Copy the code

Implementation – Built-in filters

In the Spring Cloud Gateway in fact already has a lot of built-in filters, for example: AddRequestParameterGatewayFilter, AddRequestHeaderGatewayFilter and so on. These built-in filters are of the GatewayFilter type. You need to configure a route to load the filter.

What about built-in filters in dynamic routing scenarios? In fact, it is very simple, similar to dynamic routing. We configure the information about the built-in filter into the database, and mount the built-in filter to the specific route when loading the route.

In our code to implement dynamic routing, we have the following code snippet

if(StringUtil.isNotEmpty(route.getFilters())){
                List<FilterDefinition> customFilters = JsonUtil.fromListJson(route.getFilters(), FilterDefinition.class);
                this.reloadArgs(customFilters);
                filters.addAll(customFilters);
            }
Copy the code

In the database, we configured the filter field of the route, which is actually a JSON string, deserialized with JSON to FilterDefinition, and configured the parameters of the built-in filter through reloadArgs.

Here we take an example of a built-in filter that validates captchas.

First, we implement a ValidateImageCodeGatewayFilterFactory class, the code is as follows:

public class ValidateImageCodeGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory {

    private CacheTemplate cacheTemplate;

    public ValidateImageCodeGatewayFilterFactory(CacheTemplate cacheTemplate) {
        this.cacheTemplate = cacheTemplate;
    }

    @Override
    public GatewayFilter apply(NameValueConfig config) {
        return new ValidateImageCodeGatewayFilter(config, cacheTemplate);
    }
}
Copy the code

This is actually a factory class that inherits the AbstractNameValueGatewayFilterFactory class, AbstractNameValueGatewayFilterFactory specifies the parameters of the analytic type: NameValueConfig, which is the parameter configuration of our custom filter in the database, will eventually be passed in through the NameValueConfig instance.

Then, we implement a ValidateImageCodeGatewayFilter classes again, the code is as follows:

public class ValidateImageCodeGatewayFilter implements GatewayFilter { private AbstractNameValueGatewayFilterFactory.NameValueConfig config; private static final String DEFAULT_SSO_LOGIN = "/sso/app/login"; private CacheTemplate cacheTemplate; public ValidateImageCodeGatewayFilter(AbstractNameValueGatewayFilterFactory.NameValueConfig config, CacheTemplate cacheTemplate) { this.config = config; this.cacheTemplate = cacheTemplate; } @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { String originPath = this.getOriginPath(exchange); String validatePath = StringUtil.isEmpty(this.config.getValue()) ? DEFAULT_SSO_LOGIN : this.config.getValue(); if(! validatePath.equals(originPath)){ return chain.filter(exchange); } try { this.check(exchange.getRequest()); }catch (CommonBusinessException ex){ ApiBaseResponse resp = new ApiBaseResponse(); resp.setResponseMessage(ex.getErrorMessage()); resp.setResponseCode(ex.getErrorCode()); Return mono.defer (() -> mono.just (exchange.getresponse ())).flatmap ((response) -> {return mono.defer (() -> mono.just (exchange.getresponse ())). response.setStatusCode(HttpStatus.OK); response.getHeaders().setContentType(MediaType.APPLICATION_JSON); DataBufferFactory dataBufferFactory = response.bufferFactory(); DataBuffer buffer = dataBufferFactory.wrap(JsonUtil.toJson(resp).getBytes(Charset.defaultCharset())); return response.writeWith(Mono.just(buffer)).doOnError((error) -> DataBufferUtils.release(buffer)); }); } return chain.filter(exchange); } /** * Verification code * @param Request */ private void Check (ServerHttpRequest Request){String code = request.getQueryParams().getFirst("code"); If (stringutil. isEmpty(code)){throw new CommonBusinessException("-1", "Verification code cannot be empty "); } String randomStr = request.getQueryParams().getFirst("randomStr"); If (stringutil. isEmpty(randomStr)){throw new CommonBusinessException("-1", "random number can't be empty "); } String key = CommonConstants.SYS_GATEWAY_CAPTCHA + randomStr; String text = cacheTemplate.valueGet(key, String.class); if(! Code.equals (text)){throw new Businessexception ("-1", "VERIFICATION code error "); } cacheTemplate.keyRemove(key); Private String getOriginPath(ServerWebExchange Exchange){private String getOriginPath(ServerWebExchange Exchange){ LinkedHashSet<URI> set = (LinkedHashSet<URI>)exchange.getAttributes().get(ServerWebExchangeUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR); if(CollectionUtils.isEmpty(set)){ return ""; } String originPath = ""; for(URI uri : set){ originPath = uri.getPath(); break; } return originPath; }}Copy the code

The filter verifies the validity of the verification code through the check method, and if an exception is thrown, it is returned to the front end. The getOriginPath method is to get the real URL. Because the filter is mounted to a specific route, in our scenario it is mounted to the SSO service and only the login interface requires verification of the verification code, other interfaces do not, so we need to get the real URL of the current request.

Next, we configure the built-in filter information in the database for a specific routing filters field as follows:

[{"name": "ValidateImageCode", "args": { "path":"/sso/app/login" } }]
Copy the code
  • The name field specifies the name of the filter, the full class nameValidateImageCodeGatewayFilterRemove GatewayFilter
  • Args is a map, key is path, and value is the path to be verified

When we run the Spring Cloud Gateway, we get an error because the NameValueConfig instance cannot get the correct configuration information.

After re-reading the Spring Cloud Gateway, we will find that we need to configure the following format to load the configuration correctly:

[{"name": "ValidateImageCode", "args": { "_genkey_0":"path", "_genkey_1": "/sso/app/login" } }]
Copy the code

The reloadArgs method does this when loading a dynamic route:

private void reloadArgs(List<FilterDefinition> filterDefinitions){ if(CollectionUtils.isEmpty(filterDefinitions)){ return; } for(FilterDefinition definition : filterDefinitions){ Map<String, String> args = new HashMap<>(); int i = 0; for(Map.Entry<String, String> entry : definition.getArgs().entrySet()){ args.put(NameUtils.generateName(i), entry.getKey()); args.put(NameUtils.generateName(i+1), entry.getValue()); i += 2; } definition.setArgs(args); }}Copy the code

The last

Have you learned how to handle dynamic routing and built-in filters in the Spring Cloud Gateway?

Anyin-center-gateway

Above, if there is something wrong, welcome to discuss.

Add your personal wechat friends to discuss below: DaydayCoupons