It was only half a year ago that WE wrote SpringCloud (10) : Service Gateway Zuul Primer. Now that it’s 2018, we continue to explore more advanced ways to use Zuul.

The previous article focused on Zuul gateway usage patterns and automatic forwarding mechanisms, but there are many more Zuul applications, such as authentication, traffic forwarding, request statistics, and so on, that can be implemented using Zuul.

The core of Zuul

Filter is the core of Zuul and is used to control external services. The Filter has four life cycles, namely PRE, ROUTING, POST, and ERROR. The whole life cycle is shown in the following figure.

Zuul implements most of its functionality through filters that correspond to the typical lifecycle of a request.

  • PRE: This filter is invoked before the request is routed. We can use this filter to authenticate, select requested microservices in the cluster, log debugging information, and so on.
  • **ROUTING: ** This filter routes the request to the microservice. This filter is used to build requests sent to microservices and request microservices using Apache HttpClient or Netfilx Ribbon.
  • **POST: ** This filter is executed after routing to the microservice. Such filters can be used to add standard HTTP headers to responses, collect statistics and metrics, send responses from microservices to clients, and so on.
  • **ERROR: ** This filter is executed when errors occur in other phases. In addition to the default filter types, Zuul also allows you to create custom filter types. For example, we could customize a STATIC type filter to generate the response directly in Zuul without forwarding the request to the microservice on the back end.

Filter implemented by default in Zuul

type The order The filter function
pre – 3 ServletDetectionFilter The tag handles the type of Servlet
pre 2 – Servlet30WrapperFilter Wrap the HttpServletRequest request
pre – 1 FormBodyWrapperFilter Wrapping request body
route 1 DebugFilter Debugging flag
route 5 PreDecorationFilter Process the request context for subsequent use
route 10 RibbonRoutingFilter ServiceId Forwards the request
route 100 SimpleHostRoutingFilter Url Request forwarding
route 500 SendForwardFilter Forward request forwarding
post 0 SendErrorFilter Handle request responses with errors
post 1000 SendResponseFilter Process normal request responses

The specified Filter is disabled

You can configure the filter to be disabled in application.yml in the following format:

zuul:
	FormBodyWrapperFilter:
		pre:
			disable: true
Copy the code

A custom Filter

To implement a custom Filter, you need to inherit ZuulFilter’s classes and override four of its methods.

public class MyFilter extends ZuulFilter {
    @Override
    String filterType(a) {
        return "pre"; // Define the filter type, including pre, route, post, and error
    }

    @Override
    int filterOrder(a) {
        return 10; // Define the filter order. The smaller the number, the higher the order and the earlier the execution
    }

    @Override
    boolean shouldFilter(a) {
        return true; // Indicates whether to run the filter command. True indicates that the filter command is executed. False indicates that the filter command is not executed
    }

    @Override
    Object run(a) {
        return null; //filter Indicates the operation to be performed}}Copy the code

Custom Filter example

We assume a scenario where the service gateway deals with all external requests. In order to avoid security risks, we need to make certain restrictions on requests. For example, if the request contains a Token, the request is allowed to continue, and if the request does not carry a Token, it is directly returned with a hint.

Start by customizing a Filter and verifying whether the argument contains a Token in the run() method.

public class TokenFilter extends ZuulFilter {

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

    @Override
    public String filterType(a) {
        return "pre"; // Can be called before the request is routed
    }

    @Override
    public int filterOrder(a) {
        return 0; // filter Execution sequence. The priority is 0. A larger number indicates a lower priority
    }

    @Override
    public boolean shouldFilter(a) {
        return true;// Whether to run the filter. If the value is true, filtering is required
    }

    @Override
    public Object run(a) {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();

        logger.info("--->>> TokenFilter {},{}", request.getMethod(), request.getRequestURL().toString());

        String token = request.getParameter("token");// Get the request parameters

        if (StringUtils.isNotBlank(token)) {
            ctx.setSendZuulResponse(true); // Route requests
            ctx.setResponseStatusCode(200);
            ctx.set("isSuccess".true);
            return null;
        } else {
            ctx.setSendZuulResponse(false); // Do not route it
            ctx.setResponseStatusCode(400);
            ctx.setResponseBody("token is empty");
            ctx.set("isSuccess".false);
            return null; }}}Copy the code

To add TokenFilter to the request interception queue, add the following code to the startup class:

@Bean
public TokenFilter tokenFilter(a) {
	return new TokenFilter();
}
Copy the code

This adds our custom Filter to the request interception.

test

We started the sample projects in turn: Spring-Cloud-Eureka, Spring-Cloud-producer, and Spring-Cloud-Zuul. These three projects are all examples from the previous article, and spring-Cloud-Zuul has been slightly modified.

Access the address: http://localhost:8888/spring-cloud-producer/hello? Name =neo, return: token is empty, request is intercepted and returned. Access the address: http://localhost:8888/spring-cloud-producer/hello? If name=neo&token=xx, hello neo, this is first messge is displayed, indicating that the request response is normal.

From the above example, we can see that we can use the Filter of “PRE” type to do a lot of verification work. In actual use, we can combine shiro, OAuth2.0 and other technologies to do authentication and verification.

Routing fusing

When an exception occurs in our back-end service, we do not want to throw the exception to the outermost layer, expecting the service to automatically degrade. Zuul provides us with such support. When a service fails, the default information is returned.

We use a custom fallback method and assign it to a route to implement the circuit breaker handling for the route access failure. ZuulFallbackProvider has two default methods, one to specify which service to intercept, and one to return custom content.

public interface ZuulFallbackProvider {
   /**
	 * The route this fallback will be used for.
	 * @return The route the fallback will be used for.
	 */
	public String getRoute(a);

	/**
	 * Provides a fallback response.
	 * @return The fallback response.
	 */
	public ClientHttpResponse fallbackResponse(a);
}
Copy the code

The implementation class tells Zuul which route definition it is responsible for by implementing the getRoute method. The fallbackResponse method tells Zuul what return value it will provide to process the request when a break occurs.

Spring has since extended this class to enrich the return method by adding exception information to what is returned, so the latest version recommends inheritting the FallbackProvider class directly.

Let’s take the spring-Cloud-Producer service above as an example and customize its circuit breaker return content.

@Component
public class ProducerFallback implements FallbackProvider {
    private final Logger logger = LoggerFactory.getLogger(FallbackProvider.class);

    // Specify the service to process.
    @Override
    public String getRoute(a) {
        return "spring-cloud-producer";
    }

    public ClientHttpResponse fallbackResponse(a) {
        return new ClientHttpResponse() {
            @Override
            public HttpStatus getStatusCode(a) throws IOException {
                return HttpStatus.OK;
            }

            @Override
            public int getRawStatusCode(a) throws IOException {
                return 200;
            }

            @Override
            public String getStatusText(a) throws IOException {
                return "OK";
            }

            @Override
            public void close(a) {}@Override
            public InputStream getBody(a) throws IOException {
                return new ByteArrayInputStream("The service is unavailable.".getBytes());
            }

            @Override
            public HttpHeaders getHeaders(a) {
                HttpHeaders headers = new HttpHeaders();
                headers.setContentType(MediaType.APPLICATION_JSON);
                returnheaders; }}; }@Override
    public ClientHttpResponse fallbackResponse(Throwable cause) {
        if(cause ! =null&& cause.getCause() ! =null) {
            String reason = cause.getCause().getMessage();
            logger.info("Excption {}",reason);
        }
        returnfallbackResponse(); }}Copy the code

If The service is abnormal, The system displays The exception information and returns “The service is unavailable.”.

Start the project Spring-Cloud-producer-2, at which point the service center will have two Spring-Cloud-producer projects, and we will restart the Zuul project. To manually close spring – the cloud – producer – 2 project, multiple access address: http://localhost:8888/spring-cloud-producer/hello? Name =neo&token=xx, will return alternately:

Hello neo, this is first messge The service is unavailable....Copy the code

According to The returned result, The circuit breaker has been enabled for The spring-cloud-producer-2 project, and The service is unavailable.

Zuul currently only supports service level fuses, not specific URL fuses.

Routing retry

Sometimes the service may be temporarily unavailable due to network or other reasons. In this case, we would like to Retry the service again. Zuul has implemented this feature for us, which needs to be combined with Spring Retry. Let’s take the above project as an example.

Add Spring Retry dependencies

Start by adding the Spring Retry dependency in the Spring-Cloud-Zuul project.

<dependency>
	<groupId>org.springframework.retry</groupId>
	<artifactId>spring-retry</artifactId>
</dependency>
Copy the code

Open Zuul Retry

Zuul Retry is configured in the reconfiguration file

# Whether to enable the retry function

zuul.retryable=true

Number of retries for the current service

ribbon.MaxAutoRetries=2

# Number of times to switch the same Server

ribbon.MaxAutoRetriesNextServer=0

Copy the code

This will enable Zuul retry.

test

We modified spring-Cloud-producer-2 by adding timing to the Hello method and printing parameters at the beginning of the request.

@RequestMapping("/hello")
public String index(@RequestParam String name) {
    logger.info("request two name is "+name);
    try{
        Thread.sleep(1000000);
    }catch ( Exception e){
        logger.error(" hello two error",e);
    }
    return "hello "+name+", this is two messge";
}
Copy the code

Restart spring-Cloud-producer-2 and Spring-Cloud-Zuul projects.

Access the address: http://localhost:8888/spring-cloud-producer/hello? Name =neo&token=xx, when The message The service is unavailable is displayed. View the background log of the project spring-Cloud-producer-2 as follows:

The 2018-01-22 19:50:32. 19488-401 the INFO [14] IO - 9001 - exec - O.S.C.N.Z.F.R oute. FallbackProvider: Request two name is neo 19:50:33 2018-01-22. 19488-402 the INFO [15] IO - 9001 - exec - O.S.C.N.Z.F.R oute. FallbackProvider: Request two name is neo 19:50:34 2018-01-22. 19488-404 the INFO/IO - 9001 - exec - 16 O.S.C.N.Z.F.R oute. FallbackProvider: request two name is neoCopy the code

Note Three requests were made, that is, two retries were made. This validates our configuration and completes Zuul’s retry function.

Pay attention to

Enabling retry can be problematic in some cases, such as when the pressure is so great that one instance stops responding and the route redirects traffic to another instance, potentially resulting in all instances eventually being overwhelmed. After all, one of the functions of a circuit breaker is to prevent failure or pressure diffusion. With Retry, a circuit breaker works only if all instances of the service fail. In such cases, the circuit breaker is more likely to provide a friendly error message or to give the user the illusion that the service is running properly.

Instead of retry, with load balancing and fuses only, you must consider whether you can accept short fuses between the shutdown of a single service instance and eureka refreshing the list of services. If acceptable, retry is not required.

Zuul high availability

The way we actually use Zuul is shown in the figure above. Different clients use different loads to distribute requests to Zuul on the back end, and Zuul calls the back end service through Eureka, and finally outputs. Therefore, in order to ensure high availability of Zuul, the front-end can start multiple Zuul instances at the same time for load, and use Nginx or F5 for load forwarding on Zuul’s front-end to achieve high availability.

Example code – Github

Example code – code cloud

Reference:

Spring Cloud (7) Service gateway Zuul Filter using Spring Cloud technology analysis (4) – Spring Cloud Zuul routing use