Spring Cloud Gateway practice 2: more routing configuration methods

Welcome to my GitHub

Here we classify and summarize all the original works of Xinchen (including supporting source code): https://github.com/zq2599/blog_demos

Overview of this article

  • This article is the second in the Spring Cloud Gateway practice series. Through the previous article, we learned that the core of Spring Cloud Gateway is routing configuration, and then in the local application A route is configured in YML, but this way of modifying the local configuration file lacks flexibility and may not be able to meet the flexible business requirements. Therefore, the purpose of this article is to find other configuration methods other than the local configuration to meet various actual requirements;
  • In general, the following three methods are commonly used:
  1. The target address supports the service name (replacing the previous IP + port);
  2. Support configuration on nacos;
  3. Support code writing configuration;
  • In addition, there is a more flexible configuration method: dynamic agent. Because it involves a lot of code, it will be introduced in detail in a separate article

Source download

  • The complete source code in this actual combat can be downloaded from GitHub. The address and link information are shown in the table below( https://github.com/zq2599/blog_demos):
namelinkremarks
Project Home https://github.com/zq2599/blog_demosThe project is on the GitHub home page
git warehouse address (https)https://github.com/zq2599/blog_demos.gitThe warehouse address of the source code of the project, https protocol
git warehouse address (ssh)git@github.com:zq2599/blog_demos.gitThe project source code warehouse address, ssh protocol
  • There are multiple folders in this git project. The source code of this article is in the spring cloud tutorials folder, as shown in the red box below:

preparation

  • A little more preparation needs to be done before the official start. In the whole Spring Cloud Gateway actual combat series, all requests will finally be routed to the provider Hello web. At present, the service has only one web interface / hello/str. now let's add another one to it, which will be used in the actual combat later
  • The newly added web interface comes from lbtest Java, which is very simple:
package com.bolingcavalry.provider.controller;

import com.bolingcavalry.common.Constants;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.text.SimpleDateFormat;
import java.util.Date;

@RestController
@RequestMapping("/lbtest")
public class LBTest {

    private String dateStr(){
        return new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date());
    }

    /**
     * Return string type
     * @return
     */
    @GetMapping("/str")
    public String helloStr() {
        return Constants.LB_PREFIX + ", " + dateStr();
    }
}
  • Constants in the above code LB_ Prefix from subproject common:
package com.bolingcavalry.common;

public interface Constants {
    String HELLO_PREFIX = "Hello World";
    String LB_PREFIX = "Load balance";
}
  • After writing the code, make sure that nacos is started
  • After the provider Hello project is started successfully, go to see nacos and confirm that it has been registered:
  • Ready to start the actual combat

The destination address supports the service name (replacing the previous IP + port)

  • Let's start with the simplest. Let's look at the routing configuration above, as shown in the red box below. The target address is IP + port:
  • After playing Spring Cloud, you naturally see the problem: no registration is found. Indeed, it is inappropriate to write the address and port in the configuration file. Let's solve this problem first;
  • Add a sub project named gateway by loadbalance, whose POM The dependencies in XML are as follows. It can be seen that the focus is spring cloud starter loadbalancer:
<dependencies>
        <dependency>
            <groupId>com.bolingcavalry</groupId>
            <artifactId>common</artifactId>
            <version>${project.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <dependency>
            <groupId>io.projectreactor</groupId>
            <artifactId>reactor-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- Routing policy usage lb The way is that this dependency must have -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
        </dependency>
        <!--nacos:Registration Center-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
    </dependencies>
  • The code of the startup class is omitted (as before)
  • The configuration information is as follows. The key point is that the uri value lb: / / provider Hello is prefixed with lb:, and the following provider Hello is the service name registered in nacos:
server:
  #Service port
  port: 8085
spring:
  application:
    name: gateway-by-loadbalance
  cloud:
    nacos:
      # Configuration of registry
      discovery:
        server-addr: 127.0.0.1:8848
    gateway:
      routes:
        - id: path_route_lb
          uri: lb://provider-hello
          predicates:
          - Path=/lbtest/**
  • Unit test class:
package com.bolingcavalry.gateway;

import com.bolingcavalry.common.Constants;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.reactive.server.WebTestClient;
import static org.junit.jupiter.api.Assertions.assertTrue;

@SpringBootTest
@ExtendWith(SpringExtension.class)
@AutoConfigureWebTestClient
public class HelloTest {

    @Autowired
    private WebTestClient webClient;

    @Test
    void testLoadBalance() {
        webClient.get()
                .uri("/lbtest/str")
                .accept(MediaType.APPLICATION_JSON)
                .exchange()
                // Verification status
                .expectStatus().isOk()
                // Verify the result. Note that the result is in string format
                .expectBody(String.class).consumeWith(result  -> assertTrue(result.getResponseBody().contains(Constants.LB_PREFIX)));
    }
}
  • Run the unit test. It can be seen that the above configuration can accurately find the service through the prefix lb:

Support configuration on nacos

  • Write all configuration information in application There is a problem in YML: it cannot be configured remotely, which is inconvenient in scenarios with a large number of applications. Fortunately, nacos provides the ability of remote configuration. After the application is started, you can get your own configuration information from nacos. Let's try
  • Add a sub project named gateway Nacos config, whose POM The dependencies in XML are as follows. Please pay attention to the Chinese notes inside. Each indicates the role of each dependency:
<dependencies>
        <dependency>
            <groupId>com.bolingcavalry</groupId>
            <artifactId>common</artifactId>
            <version>${project.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <dependency>
            <groupId>io.projectreactor</groupId>
            <artifactId>reactor-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- use bootstrap.yml This dependence must have -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bootstrap</artifactId>
        </dependency>
        <!-- Routing policy usage lb The way is that this dependency must have -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
        </dependency>
        <!--nacos:Configuration center-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
        <!--nacos:Registration Center-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
    </dependencies>
  • The local configuration file bootstrap YML, very simple, is the address and remote configuration information of nacos:
spring:
  application:
    name: gateway-nacos-config
  cloud:
    nacos:
      config:
        server-addr: 127.0.0.1:8848
        file-extension: yml
        group: DEFAULT_GROUP
  • Next, add a configuration file to nacos, as shown in the red box below:
  • Add a configuration. The points to note are as follows (the text of the configuration information will be given later for easy replication):
  • The complete configuration information in the figure above is as follows:
server:
  port: 8083
spring:
  cloud:
    gateway:
      routes:
        - id: path_route_addr
          uri: http://127.0.0.1:8082
          predicates:
          - Path=/hello/**
        - id: path_route_lb
          uri: lb://provider-hello
          predicates:
          - Path=/lbtest/**
  • The two test methods in the test class are as follows, which are no different from the previous ones:
@Test
    void testHelloPredicates() {
        webClient.get()
                .uri("/hello/str")
                .accept(MediaType.APPLICATION_JSON)
                .exchange()
                // Verification status
                .expectStatus().isOk()
                // Verify the result. Note that the result is in string format
                .expectBody(String.class).consumeWith(result  -> assertTrue(result.getResponseBody().contains(Constants.HELLO_PREFIX)));
    }

    @Test
    void testLoadBalance() {
        webClient.get()
                .uri("/lbtest/str")
                .accept(MediaType.APPLICATION_JSON)
                .exchange()
                // Verification status
                .expectStatus().isOk()
                // Verify the result. Note that the result is in string format
                .expectBody(String.class).consumeWith(result  -> assertTrue(result.getResponseBody().contains(Constants.LB_PREFIX)));
    }
  • Run the unit test class and pass the test, which proves that the configuration file obtained from nacos is successful:

Write code configuration

  • In the previous examples, the routing information is written in the configuration file. In fact, there is another way: write code to configure the routing. You can write your own code to configure it, which is more flexible
  • Add a sub project named gateway by code, whose POM The XML file can refer to the previous project
  • Next, the focus of this example is to add a RouteLocator type bean in the configuration class. You can add a route through the following code:
package com.bolingcavalry.gateway.cofig;

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;

@Configuration
public class RouteConfig {

    @Bean
    public RouteLocator customizeRoute(RouteLocatorBuilder builder) {
        return builder
                .routes()
                .route(
                            // The first parameter is the unique identity of the route
                        "path_route_lb",
                            // The second parameter is a lambda implementation,
                            // The matching conditions are set to match according to the request path and the forwarding address,
                            // Note that lb: / / indicates that this is a service name, which should be from
                            r -> r.path("/lbtest/**").uri("lb://provider-hello")
                )
                .build();
    }
}
  • Only one route is configured for the above code, and the other is in the configuration file, so you can verify whether the code and the configuration file can take effect at the same time:
server:
  #Service port
  port: 8084
spring:
  application:
    name: gateway-by-code
  cloud:
    nacos:
      discovery:
        # nacos service address
        server-addr: 127.0.0.1:8848
    gateway:
      routes:
        - id: path_route_addr
          uri: http://127.0.0.1:8082
          predicates:
          - Path=/hello/**
  • The test class is as like as two peas in the previous project, and it does not occupy space. It is still the two test method testHelloPredicates and testLoadBalance.
  • The execution unit test can be passed successfully, which proves that there is no problem with the code configuration Routing:
  • So far, we have tried examples of load balancing, nacos configuration and code configuration. Together, they will bring great convenience to the configuration of the actual living environment. I hope we can give you some reference

Defects and Solutions

  • Although there are many configuration methods mentioned above, there is a common problem: after the configuration changes, the Gateway application needs to be restarted to take effect, which is difficult to accept in the continuous production environment
  • In order to make the latest routing configuration take effect without restarting the Gateway application, let's explore how to implement dynamic routing in the next article

You're not alone. Xinchen's original accompanies you all the way

  1. Java series
  2. Spring collection
  3. Docker series
  4. kubernetes series
  5. Database + middleware series
  6. DevOps series

Keywords: Java Spring Cloud

Added by jonoc33 on Thu, 23 Dec 2021 11:18:41 +0200