Zuul service gateway ✧

Posted by www.phphub.com on Sat, 11 Dec 2021 14:26:42 +0100

Zuul service gateway ☣

Summary of prerequisite knowledge points (not part of the text)

1, What is a gateway?

API Gateway (APIGW / API Gateway) , as its name implies, is an API oriented, serial and centralized strong control service that appears on the system boundary. The boundary here is the boundary of the enterprise IT system. It can be understood as an enterprise application firewall, which mainly plays the role of isolating external access from internal systems. Before the popularity of the concept of microservice, API Gateway has been born, such as banking, securities and other fields A common front-end computer system, which also solves the problems of access authentication, message conversion, access statistics, etc. (the gateway is the first gateway for users to access the system. All service addresses in the register center are recorded in the gateway, authenticated through the gateway and requested to be distributed)

  • Nginx is suitable for being a portal gateway. It is the kind of gateway that is in the outermost layer externally. Zuul is a business gateway, which is mainly used to provide services corresponding to different clients and aggregate services. Each micro service is deployed independently and has a single responsibility. When providing services externally, there needs to be something to aggregate services.
  • Zuul can realize functions such as fusing and retry, which Nginx does not have.

2, Zuul implements API gateway

Official website documents:
Prepare an aggregation project. There are two registries, one consumer and one service provider in a parent project. As mentioned in the previous article, start the project test to see whether the service is registered in the registry:

① Add sub module zuul server

Create a java project and introduce dependency and configuration

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.yjxxt</groupId>
    <artifactId>zuul-server</artifactId>
    <version>1.0-SNAPSHOT</version>

    <!-- Inherit parent dependency -->
    <parent>
        <groupId>com.yjxxt</groupId>
        <artifactId>zuul-demo</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <!-- Project dependency -->
    <dependencies>
        <!-- spring cloud netflix zuul rely on -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
        </dependency>
    </dependencies>

</project>

application.yml configuration

server:
  port: 9000 # port

spring:
  application:
    name: zuul-server # apply name

Startup class

@SpringBootApplication
// Open Zuul annotation
@EnableZuulProxy
public class ZuulServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(ZuulServerApplication.class, args);
    }
}

Here the zuul module is established

② Configure routing

Service request assumptions after routing configuration: http://localhost:7070/order/10 , after configuring the route: http://localhost:7070/product-service/order/10, which specifies the interface under a service

application.yml configuration

# Routing rules
zuul:
  routes:
    product-service:              # Route id customization
      path: /product-service/**   # Configure the mapping path of the request url
      url: http://localhost:7070 / # map the microservice address corresponding to the path

Start test:

The disadvantage is that you need to modify it manually every time you change the service

③ Service name routing
  • Add Eureka Client dependency
<!-- netflix eureka client rely on -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
  • Configure registry and routing rules
# Routing rules
zuul:
  routes:
    product-service:              # Route id customization
      path: /product-service/**   # Configure the mapping path of the request url
      serviceId: product-service  # Automatically obtain the service address from the registry according to the serviceId and forward the request

# Configure Eureka Server registry
eureka:
  instance:
    prefer-ip-address: true       # Whether to register with ip address
    instance-id: ${spring.cloud.client.ip-address}:${server.port} # ip:port
  client:
    service-url:                  # Set service registry address
      defaultZone: http://localhost:8761/eureka/,http://localhost:8762/eureka/
  • Startup class
package com.yjxxt;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;

@SpringBootApplication
// Open Zuul annotation
@EnableZuulProxy
// Open the Eureka client annotation. If the Eureka registry is configured in the current version, the annotation will be opened by default
//@EnableEurekaClient
public class ZuulServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(ZuulServerApplication.class, args);
    }

}

④ Simplified routing

Zuul provides the default routing configuration for your convenience: the routing id is consistent with the microservice name, and the default path corresponds to / microservice name / * *, so the following configuration is unnecessary.

# Routing rules
zuul:
  routes:
    product-service:              # Route id customization
      path: /product-service/**   # Configure the mapping path of the request url
      serviceId: product-service  # Automatically obtain the service address from the registry according to the serviceId and forward the request

You can also exclude route addresses, route names, and route prefixes, which are not resolved here

3, Gateway filter

There are four filters. The pre filter is mainly used for request mapping and forwarding services, and some have routing filters for forwarding. The post filter is called after the routing and error filters to return the results. The error filter is triggered only when an error occurs

① Getting started Demo

Create class filter / customfilter. Java filter
The filter implemented in Spring Cloud Netflix Zuul must contain four basic features: filter type, execution order, execution condition and action (specific operation). These steps are the four abstract methods defined in the ZuulFilter interface:

/**
 * Gateway filter
 */
@Component
public class CustomFilter extends ZuulFilter {

    private static final Logger logger = LoggerFactory.getLogger(CustomFilter.class);

    /**
     * Filter type
     *      pre
     *      routing
     *      post
     *      error
     *
     * @return
     */
    @Override
    public String filterType() {
        return "pre";
    }

    /**
     * Execution sequence
     *      The lower the value, the higher the priority
     *
     * @return
     */
    @Override
    public int filterOrder() {
        return 0;
    }

    /**
     * conditions for execution
     *      true open
     *      false close
     *
     * @return
     */
    @Override
    public boolean shouldFilter() {
        return true;
    }

    /**
     * Action (specific operation)
     *      Concrete logic
     *
     * @return
     * @throws ZuulException
     */
    @Override
    public Object run() throws ZuulException {
        // Get request context
        RequestContext rc = RequestContext.getCurrentContext();
        HttpServletRequest request = rc.getRequest();
        logger.warn("CustomFilter...method={}, url={}",
                request.getMethod(),
                request.getRequestURL().toString());
        return null;
    }

}

test http://localhost:9000/product-service/product/521

Print request log

② Unified authentication

It is similar to authentication, that is, token. Otherwise, you have no permission to access the service

Modify filter:

 /*Certified by filter*/
    @Override
    public Object run() throws ZuulException {
        // Get request context
        RequestContext rc = RequestContext.getCurrentContext();
        HttpServletRequest request = rc.getRequest();
        // Get the token in the form
        String token = request.getParameter("token");
        // Business logic processing
        if (null == token) {
            logger.warn("token is null...");
            // The request ends, and the downward request is not continued.
            rc.setSendZuulResponse(false);
            // In response to the status code, the HTTP 401 error indicates that the user does not have access rights
            rc.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
            // Response type
            rc.getResponse().setContentType("application/json; charset=utf-8");
            PrintWriter writer = null;
            try {
                writer = rc.getResponse().getWriter();
                // Response content
                writer.print("{\"message\":\"" + HttpStatus.UNAUTHORIZED.getReasonPhrase() + "\"}");
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (null != writer)
                    writer.close();
            }
        } else {
            // Use token for authentication
            logger.info("token is OK!");
        }
        return null;
    }

test http://localhost:9000/product-service/product/521

③ Unified handling of gateway filter exceptions

Modify filter:

/*Manage exceptions uniformly through filters*/
    @Override
    public Object run() throws ZuulException {
        RequestContext rc = RequestContext.getCurrentContext();
        Throwable throwable = rc.getThrowable();
        logger.error("ErrorFilter..." + throwable.getCause().getMessage(), throwable);
        // Response status code, HTTP 500 server error
        rc.setResponseStatusCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
        // Response type
        rc.getResponse().setContentType("application/json; charset=utf-8");
        PrintWriter writer = null;
        try {
            writer = rc.getResponse().getWriter();
            // Response content
            writer.print("{\"message\":\"" + HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase() + "\"}");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != writer)
                writer.close();
        }
        return null;
    }

Create an exception and add a class accessfilter. Java (propose a class for the authentication filter)

@Component
public class AccessFilter extends ZuulFilter {

    private static final Logger logger = LoggerFactory.getLogger(AccessFilter.class);

    @Override
    public String filterType() {
        return "pre";
    }

    @Override
    public int filterOrder() {
        return 1;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() throws ZuulException {
        // Simulation anomaly
         Integer.parseInt("zuul");

        // Get request context
        RequestContext rc = RequestContext.getCurrentContext();
        HttpServletRequest request = rc.getRequest();
        // Get the token in the form
        String token = request.getParameter("token");
        // Business logic processing
        if (null == token) {
            logger.warn("token is null...");
            // The request ends, and the downward request is not continued.
            rc.setSendZuulResponse(false);
            // In response to the status code, the HTTP 401 error indicates that the user does not have access rights
            rc.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
            // Response type
            rc.getResponse().setContentType("application/json; charset=utf-8");
            PrintWriter writer = null;
            try {
                writer = rc.getResponse().getWriter();
                // Response content
                writer.print("{\"message\":\"" + HttpStatus.UNAUTHORIZED.getReasonPhrase() + "\"}");
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (null != writer)
                    writer.close();
            }
        } else {
            // Use token for authentication
            logger.info("token is OK!");
        }
        return null;
    }
}

Add exception handling filter

/**
 * Exception filter
 */
@Component
public class ErrorFilter extends ZuulFilter {

    private static final Logger logger = LoggerFactory.getLogger(ErrorFilter.class);

    @Override
    public String filterType() {
        return "error";
    }

    @Override
    public int filterOrder() {
        return 0;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }


    @Override
    public Object run() throws ZuulException {
        RequestContext rc = RequestContext.getCurrentContext();
        ZuulException exception = this.findZuulException(rc.getThrowable());
        logger.error("ErrorFilter..." + exception.errorCause, exception);

        HttpStatus httpStatus = null;
        if (429 == exception.nStatusCode)
            httpStatus = HttpStatus.TOO_MANY_REQUESTS;

        if (500 == exception.nStatusCode)
            httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;

        // Response status code
        rc.setResponseStatusCode(httpStatus.value());
        // Response type
        rc.getResponse().setContentType("application/json; charset=utf-8");
        PrintWriter writer = null;
        try {
            writer = rc.getResponse().getWriter();
            // Response content
            writer.print("{\"message\":\"" + httpStatus.getReasonPhrase() + "\"}");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != writer)
                writer.close();
        }
        return null;
    }

    private ZuulException findZuulException(Throwable throwable) {
        if (throwable.getCause() instanceof ZuulRuntimeException)
            return (ZuulException) throwable.getCause().getCause();

        if (throwable.getCause() instanceof ZuulException)
            return (ZuulException) throwable.getCause();

        if (throwable instanceof ZuulException)
            return (ZuulException) throwable;
        return new ZuulException(throwable, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, null);
    }

}

Turn off the built-in exception handling

zuul:
  # Disable Zuul's default exception handling filter
	SendErrorFilter:
     error:
       disable: true

Test results:

④ Declaration cycle of Zuul request
  1. HTTP send request to Zuul gateway
  2. Zuul gateway first passes through pre filter
  3. After passing the verification, enter the routing filter, and then forward the request to the remote service. The remote service returns the result after execution. If there is an error, execute the error filter
  4. Continue to execute post filter
  5. Finally, the response is returned to the HTTP client

4, Seamless combination of Zuul and Hystrix

To realize network monitoring, you need to configure Hystrix. Zuul's dependencies include the related jar packages of Hystrix, so we don't need to add additional Hystrix dependencies in the project. However, for projects that need to start data monitoring, we need to add dashboard dependencies.

① Prepare environment
# Introduce dependency
<!-- spring cloud netflix hystrix dashboard rely on -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
# to configure
# Metrics monitoring and health check
management:
  endpoints:
    web:
      exposure:
        include: hystrix.stream

Start class / / open Zuul annotation @ EnableZuulProxy

Start project test access: http://localhost:9000/hystrix


Listening service address http://localhost:9000/actuator/hystrix.stream

② Gateway fuse

Before the Edgware version, Zuul provided the interface ZuulFallbackProvider to implement fallback processing. Since the Edgware version, Zuul provided the interface FallbackProvider to provide fallback processing. Write the implementation class: productproviderfallback (the fuse will fuse only when the service is down without any feedback, and the service can be suspended during all tests)

/**
 * Fault tolerance for goods and services
 */
@Component
public class ProductProviderFallback implements FallbackProvider {

    /**
     * return - Returns which service fallback handles. The name of the service is returned.
     * Recommendation - defines the characterized fallback logic for the specified service.
     * Recommended - provide a fallback logic that handles all services.
     * Benefit - if a service times out, the specified fallback logic is executed. If a new service is launched and fallback logic is not provided, there is a general service.
     */
    @Override
    public String getRoute() {
        return "product-service";
    }

    /**
     * Fault tolerance for goods and services
     *
     * @param route Fault tolerant service name
     * @param cause Service exception information
     * @return
     */
    @Override
    public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
        return new ClientHttpResponse() {
            /**
             * Set the header information of the response
             * @return
             */
            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders header = new HttpHeaders();
                header.setContentType(new MediaType("application", "json", Charset.forName("utf-8")));
                return header;
            }

            /**
             * Set response body
             * Zuul The input stream data returned by this method will be read and output to the client through the output stream of HttpServletResponse.
             * @return
             */
            @Override
            public InputStream getBody() throws IOException {
                return new ByteArrayInputStream("{\"message\":\"Goods and services are unavailable. Please try again later.\"}".getBytes());
            }

            /**
             * ClientHttpResponse The status code of the fallback returns HttpStatus
             * @return
             */
            @Override
            public HttpStatus getStatusCode() throws IOException {
                return HttpStatus.INTERNAL_SERVER_ERROR;
            }

            /**
             * ClientHttpResponse The status code of the fallback of returns int
             * @return
             */
            @Override
            public int getRawStatusCode() throws IOException {
                return this.getStatusCode().value();
            }

            /**
             * ClientHttpResponse The status code of the fallback returns String
             * @return
             */
            @Override
            public String getStatusText() throws IOException {
                return this.getStatusCode().getReasonPhrase();
            }

            /**
             * Resource recovery method
             * It is used to reclaim the resource object opened by the current fallback logic.
             */
            @Override
            public void close() {
            }
        };
    }

}

The refresh will occur only after the service is down:

③ Gateway current limiting

It is to specify the number of requests within the specified time. Once it exceeds the standard, it will not accept the request, and more than the request will be returned directly. There are three current limiting algorithms:

  • Counter algorithm
  • Leaky Bucket algorithm
  • Token Bucket algorithm

Counter

Set a counter. For example, 100 requests are allowed in a minute. Therefore, if the counter is less than 100 in a minute, it will be counted again. The disadvantage is that 59s100 requests and 100 requests in one minute lead to the collapse of the service, which is a vulnerability and easy to idle

Leaky bucket algorithm

It is to use a hole under a bucket to store a large number of requests, and then process the requests one by one through a small hole. Once the request is larger than the bucket, it will directly fall back, similar to message queue

Token Bucket

When a token is stored in a bucket, the request will get the token, and the token will always be generated. The redundant token will be discarded directly. The request to get the token can access the resources. The request to get the token will be executed, otherwise it will fall back or cache

Code implementation:

//Zuul's current limiting protection needs to additionally rely on the spring cloud zuul ratelimit component. The current limiting data is stored in Redis, so Redis component should be added.
//[official website document of RateLimit]: https://github.com/marcosbarbero/spring-cloud-zuul-ratelimit
<!-- spring cloud zuul ratelimit rely on -->
<dependency>
    <groupId>com.marcosbarbero.cloud</groupId>
    <artifactId>spring-cloud-zuul-ratelimit</artifactId>
    <version>2.3.0.RELEASE</version>
</dependency>
<!-- spring boot data redis rely on -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- commons-pool2 Object pool dependency -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

Global current limiting configuration
Using the global current limit configuration, Zuul will provide current limit protection for all services of the agent (remember to open the redis cache database).

server:
  port: 9000 # port

spring:
  application:
    name: zuul-server # apply name
  # redis cache
  redis:
    host: 192.168.10.106  # Redis server address

# Configure Eureka Server registry
eureka:
  instance:
    prefer-ip-address: true       # Whether to register with ip address
    instance-id: ${spring.cloud.client.ip-address}:${server.port} # ip:port
  client:
    service-url:                  # Set service registry address
      defaultZone: http://localhost:8761/eureka/,http://localhost:8762/eureka/

zuul:
  # Service current limiting
  ratelimit:
    # Open current limiting protection
    enabled: true
    # Current limiting data storage mode
    repository: REDIS
    # Default policy list is configured by default and takes effect globally
    default-policy-list:
      - limit: 3
        refresh-interval: 60    # If the request exceeds 3 times within 60s, the server will throw an exception, and the normal request can be restored after 60s
        type:
          - origin
          - url
          - user

Start test

The number of requests exceeds the threshold set by us, so there are too many return requests, followed by local flow restriction, user-defined flow restriction, gateway tuning, Zuul and Sentinel integration, etc. there are a lot of contents, so let's stop here first.

Topics: Java security gateway Hystrix zuul