What is a service gateway

We’ve seen springBoot as the basis for building microservices, and we can also use SpringBoot to build services. Let’s talk about SpringCloud based on SpringBoot. The springcloud is not a specific technology, but an ecosystem of microservices. Such as the gateway, registry, configuration center, etc. Today we will first have a look at microservice gateways. There are many kinds of microservice gateways. This time we will use the mainstream Spring Cloud Gateway to explain. In the microservice system, each service is an independent module and a component running independently. A complete microservice system is composed of several independent services, each of which completes its own business module functions. For example, the user service provides services and functions related to user information, and the payment module provides functions related to payment. The services communicate with each other via the REST API or RPC (more on that later), and generally we want stateless communication in microservers. The implementation of microservices will also bring some inconveniences in some aspects. If the web side or app side needs to request to modify the shipping address, and we need to pay after shopping, in this scenario:Here are some problems:

  • The client has to initiate multiple requests for services corresponding to different domain names, which increases the communication cost and the complexity of maintaining the client code.
  • Service authentication is performed separately for each service. If the authentication logic of each service is different, repeated client authentication may occur.
  • In addition, if different protocols are used by different services, it can be disastrous for the client.

Based on the above, we need a middle layer, let the client to request the middle layer, as for the need to request the service to be requested by the middleware, and finally the results will be summarized back to the client, this middle layer is the gateway.

Why use gateways

Using a gateway serves several purposes:

Unified authentication

There are two types of authentication on the gateway: 1. Authenticates the identity of the requesting client. 2. Access control is to determine whether a user has access to a resource after confirming the user’s identity. Once we in the applications of monomer, the client request to verify identity and for resource permissions constraints is simpler, by requesting the session can obtain corresponding user and permission information, but under the micro service architecture, all services are split into a single micro service and deploy this kind of situation will become complicated, Because if you were to use the session again in the distributed case each request would not necessarily be on the same machine, and that would invalidate the session. We need to do extra work to ensure that the sessions in the cluster are consistent. So we handle authentication uniformly at the gateway layer:

logging

When a client requests come in after we need to record the current request time depend on the source address, IP or other information, so that we can be unified in the gateway level to intercept access, then output to the log file output through the ELK components, record contents can be multidimensional unified records without the need for more information to specific for each service record.

Request distribution and filtering

For the gateway, the request matching distribution is the most important function. In fact, the common nGINx component has the function of request forwarding and filtering. For the gateway, the request can be pre-filtered and post-filtered.

  • Request distribution: Receives the client’s request, maps the request to the following microservices and requests the microservices. Because the microservices are fine-grained, the gateway can integrate the microservices functionally and finally send them back to the client.
  • Filtering: The gateway intercepts all requests and is equivalent to a horizontal aspect of SPRING AOP on which to perform authentication, traffic limiting, authentication, and so on.

Gray released

The general company’s Internet products are very fast iteration, are basically small steps fast run. It’s basically a weekly release iteration. In this case, there will be risks, such as compatibility, functional integrity, short time, bugs and eventually accidents. So usually when we release a new feature, we release it to a specific machine and we take a small amount of traffic and see what happens. So the gateway serves as the entry point for the request.

Common gateway solutions

There are several commonly used gateways such as OpenResty, Zuul, Gateway, Kong, Tyk and so on. We mainly use the framework of Spring system, so we explain the Gateway in this article, and do not focus on the implementation of other Gateway. OpenResty is a Web server integrated with Nginx + Lua, which integrates many third-party libraries and modules. In fact, Zuul was also using Spring Cloud in the early stage of integration. At that time, due to the performance problems caused by his threading model strategy, Spring finally chose the Spring Cloud Gateway developed by himself.

Environment to prepare

In this article, we will use a simple case to demonstrate how to use spring Cloud Gateway. First, we need to use two Spring Boot applications. For details, please refer to the second article of this topic.

  • Spring-cloud-gateway-service1 This is a microservice
  • Spring-cloud-gateway-wangguan Gateway microservice

We created two services based on the previous project and the first service we added a controller

@RequestMapping(value = {"/getUser"}, method = RequestMethod.GET)
    public String getUser(a) {
        Map<String ,String> user = new HashMap<>();
        user.put("name"."Zhang");
        user.put("age"."45");
        String s = JSONObject.toJSONString(user);
        return s;
    }
Copy the code

The second gateway service we add to the POM dependency

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
            <version>2.04..RELEASE</version>
        </dependency>
Copy the code

Add the gateway route to application.yml

spring:
  cloud:
    gateway:
      routes:
      - predicates:
            - Path =/gateway/ * * # matching rules uri: http://localhost:8099/getUser # 1 service access address filters: - StripRrefix: 1 # remove prefixes server: port: 8077Copy the code

The meanings of the preceding configuration are as follows:

  • Uri: indicates the address of the target service. You can configure URI and LB: // name of the application service
  • Predicates: matches whether to request the route based on the rules
  • Filters: The filters include pre – and post-filters,
  • StripPrefix=1, indicating that the prefix is removed, that is, ‘gateway’ is removed when forwarding the destination URL.

Netty started on port(s): 8077 Our service is successful and the gateway relies on NettyServer to start several service listeners. We visit:

curl http://localhost:8077/gateway/getUser

When configured correctly, the result returned by the service is returned.

Spring Cloud Gateway principles

Above is the schematic diagram given by gateway official, which may not be easy to understand. Let’s draw a diagram to help us understand:Here are a few concepts:

  • A Route is a component of a gateway. It consists of ids, URIs, predicate, and filters.
  • Predicate: matches the content of the HTTP request. If true is returned, the current router is forwarded.
  • Filter: Provides pre – and post-filtering for requests.

When the client sends a request to the gateway, the gateway determines which route to access based on the results of a series of Predicate matches, and then processes the request based on a filter that can be executed before and after the request is sent to the back-end service.

Routing rules

Spring Cloud Gateway provides a route matching mechanism, such as Path=/gateway/**. The Path attribute matches requests whose URL prefix is /gateway/. In fact, the Spring Cloud Gateway provides us with a number of rules to use. The use of each Predicate, you can understand, is forwarded only when these conditions are met, and if there are multiple of them, then all of them are met. These Predict source in org. Springframework. Cloud. Gateway. Handler. The predicate in the package we simply look at:

Dynamic routing

There are two ways to configure routes through the gateway: 1. Use the YML configuration file; 2. Write it in code. The route configuration cannot be modified after the yML or code configuration is enabled. If a new service is to be enabled, you need to offline the gateway, modify the YML configuration, and restart the gateway. This approach, without graceful outages at the gateway, would not be acceptable. Gateway When the gateway is started, the routing information is loaded into the memory by default. The routing information is encapsulated in the RouteDefinition object. Configure multiple RouteDefinitions to form the routing system of the gateway. The properties in RouteDefinition correspond to the properties configured in the code above:Then we need our dynamic routing to solve this problem. The Spring Cloud Gateway provides an Endpoint to expose routing information. There are methods such as obtaining all routes, refreshing routes, viewing single routes, and deleting routes. Specific implementation class org. Springframework. Cloud. Gateway. Actuate. GatewayControllerEndpoint, want to visit the endpoint method need to add the spring – the boot – starter – physical annotations, And expose all endpoints in the configuration file. Writing dynamic routing implementation class, it is necessary to realize ApplicationEventPublisherAware interface.

/** * Dynamic routing service */
@Service
public class GoRouteServiceImpl implements ApplicationEventPublisherAware {
    @Autowired
    private RouteDefinitionWriter routeDefinitionWriter;
    private ApplicationEventPublisher publisher;
    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.publisher = applicationEventPublisher;
    }
    // Add a route
    public String add(RouteDefinition definition) {
        routeDefinitionWriter.save(Mono.just(definition)).subscribe();
        this.publisher.publishEvent(new RefreshRoutesEvent(this));
        return "success";
    }
    // Update the route
    public String update(RouteDefinition definition) {
        try {
            delete(definition.getId());
        } catch (Exception e) {
            return "update fail,not find route routeId: "+definition.getId();
        }
        try {
            routeDefinitionWriter.save(Mono.just(definition)).subscribe();
            this.publisher.publishEvent(new RefreshRoutesEvent(this));
            return "success";
        } catch (Exception e) {
            return "update route fail"; }}// Delete the route
    public Mono<ResponseEntity<Object>> delete(String id) {
        return this.routeDefinitionWriter.delete(Mono.just(id)).then(Mono.defer(() -> {
            return Mono.just(ResponseEntity.ok().build());
        })).onErrorResume((t) -> {
            return t instanceof NotFoundException;
        }, (t) -> {
            returnMono.just(ResponseEntity.notFound().build()); }); }}Copy the code

Write Rest interfaces through which you can implement dynamic routing.

@RestController
@RequestMapping("/changeRoute")
public class ChangeRouteController {
    @Autowired
    private GoRouteServiceImpl goRouteServiceImpl;
    // Add a route
    @PostMapping("/add")
    public String add(@RequestBody GatewayRouteDefinition gwdefinition) {
        String flag = "fail";
        try {
            RouteDefinition definition = assembleRouteDefinition(gwdefinition);
            flag = this.goRouteService.add(definition);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return flag;
    }
    // Delete the route
    @DeleteMapping("/routes/{id}")
    public Mono<ResponseEntity<Object>> delete(@PathVariable String id) {
        try {
            return this.goRouteService.delete(id);
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }
    // Update the route
    @PostMapping("/update")
    public String update(@RequestBody GatewayRouteDefinition gwdefinition) {
        RouteDefinition definition = assembleRouteDefinition(gwdefinition);
        return this.goRouteService.update(definition);
    }
    // Convert the passed parameters into routing objects
    private RouteDefinition assembleRouteDefinition(GatewayRouteDefinition gwdefinition) {
        RouteDefinition definition = new RouteDefinition();
        definition.setId(gwdefinition.getId());
        definition.setOrder(gwdefinition.getOrder());
        // Set the assertion
        List<PredicateDefinition> pdList=new ArrayList<>();
        List<GatewayPredicateDefinition> gatewayPredicateDefinitionList=gwdefinition.getPredicates();
        for (GatewayPredicateDefinition gpDefinition: gatewayPredicateDefinitionList) {
            PredicateDefinition predicate = new PredicateDefinition();
            predicate.setArgs(gpDefinition.getArgs());
            predicate.setName(gpDefinition.getName());
            pdList.add(predicate);
        }
        definition.setPredicates(pdList);
        // Set the filter
        List<FilterDefinition> filters = new ArrayList();
        List<GatewayFilterDefinition> gatewayFilters = gwdefinition.getFilters();
        for(GatewayFilterDefinition filterDefinition : gatewayFilters){
            FilterDefinition filter = new FilterDefinition();
            filter.setName(filterDefinition.getName());
            filter.setArgs(filterDefinition.getArgs());
            filters.add(filter);
        }
        definition.setFilters(filters);
        URI uri = null;
        if(gwdefinition.getUri().startsWith("http")){
            uri = UriComponentsBuilder.fromHttpUrl(gwdefinition.getUri()).build().toUri();
        }else{
            // Use the following method when the URI is lb://consumer-service
            uri = URI.create(gwdefinition.getUri());
        }
        definition.setUri(uri);
        returndefinition; }}Copy the code

In fact, we rarely use API to call REST service to add or delete routing information. In general, we mainly integrate nacOS config function to dynamically add routes. Integration with NACOS we’ll talk about later.

The filter

Gateway Filter The gateway Filter is classified into Pre Filter and Post Filter. Execute before the specific request is forwarded to the back-end microservice and before the result is returned to the client. There are about 19 types of built-in GatewayFilter, such as:

  • AddRequestHeader GatewayFilter Factory,
  • AddRequestParameter GatewayFilter Factory,
  • AddResponseHeader GatewayFilter Factory

Without giving too many examples and being easy to use, let’s focus on how to customize the filter:

  1. Global filters: Global filters, which are valid for all routes and do not need to be configured in the configuration file, implement the GlobalFilter and Ordered interfaces and register the filters in the Spring container.
@Service
@Slf4j
public class AllDefineFilter implements GlobalFilter.Ordered{
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.info("[pre]-Enter AllDefineFilter");
        return chain.filter(exchange).then(Mono.fromRunnable(()->{
            log.info("[post]-Return Result");
        }));
    }
    @Override
    public int getOrder(a) {
        return 0; }}Copy the code
  1. Local filter: This filter takes effect only after it is configured in the configuration file. Main realization GatewayFilter, Ordered interfaces, and through AbstractGatewayFilterFactory subclasses of registration to the spring container, of course also can inherit AbstractGatewayFilterFactory directly, Write filter logic inside, and you can read external data from configuration files.
@Component
@Slf4j
public class UserDefineGatewayFilter extends AbstractGatewayFilterFactory<UserDefineGatewayFilter.GpConfig>{

    public UserDefineGatewayFilter(a){
        super(GpConfig.class);
    }

    @Override
    public GatewayFilter apply(GpConfig config) {
        return ((exchange, chain) -> {
            log.info("[Pre] Filter Request,name:"+config.getName());
            return chain.filter(exchange).then(Mono.fromRunnable(()->{
                log.info("[Post] Response Filter");
            }));
        });
    }

    public static class UserConfig{
        private String name;

        public String getName(a) {
            return name;
        }

        public void setName(String name) {
            this.name = name; }}}Copy the code

Here’s a piece of attention:

  • Class names must always end with GatewayFiterFactory, because by default filter names are prefixed with the custom class. Here name=UserDefine, which is the name value of the filters in YML.
  • In the apply method, include both Pre and Post filtering. In the THEN method is post-processing after the request has finished executing.
  • UserConfig is a configuration class that has only one attribute, name. This property can be used in the YM file.
  • This class needs to be loaded into the Spring IoC container, implemented here with the @Component annotation.

In fact, the whole Spring Cloud Gateway is well integrated with Spring Cloud Alibaba. It can be integrated with NACOS and Sentinel for stream limiting. We will explain this separately in the later stage.