In the previous sections, I introduced the problems and solutions that arise when a system is split into microservices: how services are discovered and managed (Nacos Registry practice), how services communicate with services (Feign practice)

Today we are going to talk about another question: how do clients access?

In a single architecture, our system has only one entry point, which is very simple for the front end personnel to call.

But when we split it into a microservice system, each service has its own IP and port number, and we can’t say to the front end: Hey, you use this address when you call this interface.

Front end:

If that doesn’t work, can we use what we know to come up with a solution?

Not really a solution that works

In fact, we can easily imagine that our services have the ability to discover and communicate with each other. So, can we build a unified gateway service, where the front end only requests the service and the service invokes the Feign interface of the real service?

Here’s an example:

  • Get goods: localhost:8080/get/goods
  • The order placing interface for the order service: localhost:8081/order

There is now a gateway service with two interfaces: localhost:5555/ GET /goods and localhost:5555/order

When the front-end invokes the get goods interface, it visits: localhost:5555/ GET /goods, and then the gateway service invokes the Feign interface of the goods service

To place an order: visit: localhost:5555/order, and then the gateway service invokes the Feign interface of the order service

To summarize:

Does this solution solve the problem of service entry unification: yes

Does it work: Yes, but not completely

The problem is that for every interface the service writes, it needs to give a Feign interface to our gateway service call.

Real solutions

Spring Cloud provides a solution: the Spring Cloud Gateway

The Spring Cloud Gateway provides an API Gateway built on top of the Spring ecosystem that provides a simple and efficient way to route through the API and provide functions such as security and monitoring in a filter-based manner.

Spring Cloud Gateway is implemented by Spring Boot 2.x, Spring WebFlux and Reactor, and requires the Netty operating environment provided by Spring Boot and Spring WebFlux. It does not work in a traditional Servlet container, nor when built as a WAR.

IO /spring-clou…

concept

Route: The basic building block of a gateway, defined by an ID, a destination URI, a collection of assertions, and a collection of filters. If the collection assertion is true, the route is matched.

Predicate: Java 8 Predicate functions. The parameter type is Spring Framework ServerWebExchange. It allows developers to match anything in an HTTP request, such as header files or parameters.

Filter: An instance of a GatewayFilter built by a particular factory that, like a traditional Filter, can be processed before and after the request.

The working principle of

The client makes a request to the Spring Cloud Gateway. If the Gateway handler map determines that a request matches the route, it is sent to the Gateway Web handler. The handler runs the request through a chain of filters specific to the request.

Filters can run pre and POST logic before and after a proxy request is sent.

Simple to use

To prepare

Pre-prepare a service to test routes

Here I prepared a goods and services, and provides an interface: http://localhost:8082/goods/get-goods

Now, start writing the gateway service

Introduction of depend on

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
  <groupId>com.alibaba.cloud</groupId>
  <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
Copy the code

Write the configuration

bootstrap.yaml

server:
  port: 5555
spring:
  application:
    name: my-gateway
  cloud:
    nacos:
      discovery:
        server-addr: 127.0. 01.: 8848
        namespace: public
        username: nacos
        password: nacos

logging:
  level:
    org.springframework.cloud.gateway: info
    com.alibaba.nacos.client.naming: warn
Copy the code

application.yaml

spring:
  cloud:
    gateway:
      # Route configuration
      routes:
        # route ID ensures uniqueness
        - id: my-goods
          // Service name lb: Load balance, my-goods: service name
          uri: lb://my-goods
          # assertion
          predicates:
            Match the request at the beginning of goods
            - Path=/goods/**
Copy the code

Start the class

package com.my.micro.service.gateway;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/ * * *@author Zijian Liao
 * @since1.0.0 * /
@SpringBootApplication
public class GatewayApplication {

    public static void main(String[] args) { SpringApplication.run(GatewayApplication.class, args); }}Copy the code

test

Start the service, and visit: http://localhost:5555/goods/get-goods

As you can see, the service was successfully routed

A simple gateway service is completed in this way. Have you seen the concept of gateway more profound?

assertions

In the example above, we used an assertion factory: Path

In the Spring Cloud Gateway and all assertions factory is inherited in AbstractRoutePredicateFactory, and naming rules for: XxxRoutePredicateFactory, such as the Path class called: PathRoutePredicateFactory

So what assertion factories does Spring Cloud Gateway give us built-in?

Documents: the docs. Spring. IO/spring – clou…

Here are some examples of assertion factories that I think are commonly used

After

Matches requests after a certain time (ZonedDateTime)

spring:
  cloud:
    gateway:
      # Route configuration
      routes:
        # route ID ensures uniqueness
        - id: my-goods
          // Service name lb: Load balance, my-goods: service name
          uri: lb://my-goods
          # assertion
          predicates:
            Match the request at the beginning of goods
            - Path=/goods/**
            Match requests made after 23:05 minutes
            - After = 2021-08-08 T23:05:13. 605 + 08:00 Asia/Shanghai
Copy the code

We test at 23:03

Access failed

Before

Matches requests before a certain time (ZonedDateTime)

Similar to After, no longer demonstrated

Between

Matches requests at a certain time period (ZonedDateTime)

spring:
  cloud:
    gateway:
      # Route configuration
      routes:
        # route ID ensures uniqueness
        - id: my-goods
          // Service name lb: Load balance, my-goods: service name
          uri: lb://my-goods
          # assertion
          predicates:
            Match the request at the beginning of goods
            - Path=/goods/**
            Match request 23:05-23:10
            - Between = 2021-08-08 T23:05:13. 605 + 08:00 Asia/Shanghai, the 2021-08-08 T23: "yet. 605 + 08:00 Asia/Shanghai
Copy the code

Host

Matches a request to a Host

spring:
  cloud:
    gateway:
      # Route configuration
      routes:
        # route ID ensures uniqueness
        - id: my-goods
          // Service name lb: Load balance, my-goods: service name
          uri: lb://my-goods
          # assertion
          predicates:
            Match the request at the beginning of goods
            - Path=/goods/**
            Set host to 192.168.1.105
            - The Host = 192.168.1.105
Copy the code

Note that you need to change the port number to 80 when testing

Try making the call using 127.0.0.1

Call instead at 192.168.1.105

RemoteAddr

Matches the specified remote source address

spring:
  cloud:
    gateway:
      # Route configuration
      routes:
        # route ID ensures uniqueness
        - id: my-goods
          // Service name lb: Load balance, my-goods: service name
          uri: lb://my-goods
          # assertion
          predicates:
            Match the request at the beginning of goods
            - Path=/goods/**
            Set RemoteAddr to 192.168.1
            - RemoteAddr = 192.168.1.1/24
Copy the code

test

Enable Intranet penetration test

Access failed

The filter

I’ll give you an example about filters. For more information, please refer to the documentation

IO /spring-clou…

Here’s an example of a filter that’s used a lot:

StripPrefix

As the name implies, a prefixed filter removes the prefix from the matched request and forwards the removed request to the downstream service

spring:
  cloud:
    gateway:
      # Route configuration
      routes:
        # route ID ensures uniqueness
        - id: my-goods
          // Service name lb: Load balance, my-goods: service name
          uri: lb://my-goods
          # assertion
          predicates:
            Match the request at the beginning of goods
            - Path=/api/goods/**
          filters:
            # 1 removes a prefix
            - StripPrefix=1
Copy the code

Combination, the meaning is initiated when a client requests: http://localhost:5555/api/goods/get-goods, matching chain by, then remove the first prefix API, and then forwarded to goods and services, forwarding path for: working/goods/get – goods

test

Custom assertion factory

Mentioned above: that all factories are inherited in AbstractRoutePredicateFactory, and naming rules for: XxxRoutePredicateFactory, such as the Path class called: PathRoutePredicateFactory

Let’s now try to implement a custom request header assertion factory

Write the code

package com.my.micro.service.gateway.filter;

import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.GatewayPredicate;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;

/ * * *@author Zijian Liao
 * @since1.0.0 * /
@Component
public class MyHeaderRoutePredicateFactory extends AbstractRoutePredicateFactory<MyHeaderRoutePredicateFactory.Config> {

    /** * Header key. */
    public static final String HEADER_KEY = "header";

    /** * Regexp key. */
    public static final String REGEXP_KEY = "regexp";


    public MyHeaderRoutePredicateFactory(a) {
        super(MyHeaderRoutePredicateFactory.Config.class);
    }

    @Override
    public List<String> shortcutFieldOrder(a) {
        return Arrays.asList(HEADER_KEY, REGEXP_KEY);
    }

    @Override
    public Predicate<ServerWebExchange> apply(MyHeaderRoutePredicateFactory.Config config) {
        return new GatewayPredicate() {
            @Override
            public boolean test(ServerWebExchange exchange) {
                // Get the request header
                List<String> values = exchange.getRequest().getHeaders()
                        .getOrDefault(config.header, Collections.emptyList());
                if (values.isEmpty()) {
                    return false;
                }
                // Determine if the value in the request header matches the configuration
                return values.stream()
                        .anyMatch(value -> value.matches(config.regexp));
            }

            @Override
            public String toString(a) {
                return String.format("Header: %s=%s ", config.header, config.regexp); }}; }public static class Config {
        private String header;
        private String regexp;

        public String getHeader(a) {
            return header;
        }

        public void setHeader(String header) {
            this.header = header;
        }

        public String getRegexp(a) {
            return regexp;
        }

        public void setRegexp(String regexp) {
            this.regexp = regexp; }}}Copy the code

Write the configuration

spring:
  cloud:
    gateway:
      # Route configuration
      routes:
        # route ID ensures uniqueness
        - id: my-goods
          // Service name lb: Load balance, my-goods: service name
          uri: lb://my-goods
          # assertion
          predicates:
            Match the request at the beginning of goods
            - Path=/api/goods/**
            # match request whose header is name=aljian
            - MyHeader=name,ajian
          filters:
            # 1 removes a prefix
            - StripPrefix=1
Copy the code

test

Access directly from the browser

Use Postman access instead

Custom filter

You customize filters in much the same way you customize assertion factories, So filter inheritance in AbstractGatewayFilterFactory or AbstractNameValueGatewayFilterFactory, naming rules for XxxGatewayFilterFactory

Such as the built-in add request header filter

public class AddRequestHeaderGatewayFilterFactory
		extends AbstractNameValueGatewayFilterFactory {

	@Override
	public GatewayFilter apply(NameValueConfig config) {
		return new GatewayFilter() {
			@Override
			public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // Get the header value to be added
				String value = ServerWebExchangeUtils.expand(exchange, config.getValue());
				// Add header to request
        ServerHttpRequest request = exchange.getRequest().mutate()
						.header(config.getName(), value).build();
				// Rebuild an Exchange
				return chain.filter(exchange.mutate().request(request).build());
			}

			@Override
			public String toString(a) {
				return filterToStringCreator(AddRequestHeaderGatewayFilterFactory.this) .append(config.getName(), config.getValue()).toString(); }}; }}Copy the code

Global filter

While the above is for each router, the Spring Cloud Gateway provides a global filter for all routers

The implementation is as follows

package com.my.micro.service.gateway.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/ * * *@author Zijian Liao
 * @since1.0.0 * /
@Slf4j
@Component
public class MyGlobalFilter implements GlobalFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String path = exchange.getRequest().getURI().getPath();
        log.info("Enter global filter, request path: {}", path);
        Write any logic you want to implement, such as permissions verification
        returnchain.filter(exchange); }}Copy the code

test

Custom exception handlers

As you may have noticed, the Spring Cloud Gateway does not return an elegant exception to the client when it encounters an error, so we need to customize the exception handling

Write custom exception handlers

package com.my.micro.service.gateway.exception;

import com.my.micro.service.gateway.result.BaseResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWebExceptionHandler;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.lang.NonNull;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.RequestPredicates;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;

/ * * *@author Zijian Liao
 */
@Slf4j
public class JsonExceptionHandler extends DefaultErrorWebExceptionHandler {

    /**
     * Create a new {@code DefaultErrorWebExceptionHandler} instance.
     *
     * @param errorAttributes    the error attributes
     * @param resourceProperties the resources configuration properties
     * @param errorProperties    the error configuration properties
     * @param applicationContext the current application context
     */
    public JsonExceptionHandler(ErrorAttributes errorAttributes, ResourceProperties resourceProperties, ErrorProperties errorProperties, ApplicationContext applicationContext) {
        super(errorAttributes, resourceProperties, errorProperties, applicationContext);
    }

    @Override
    protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
        return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
    }

    @NonNull
    @Override
    protected Mono<ServerResponse> renderErrorResponse(ServerRequest request) {
        Throwable throwable = getError(request);
        return ServerResponse.status(HttpStatus.OK)
                .contentType(MediaType.APPLICATION_JSON)
                .body(BodyInserters.fromValue(this.handleError(throwable)));
    }

    private BaseResult<Void> handleError(Throwable throwable){
        returnBaseResult.failure(throwable.getMessage()); }}Copy the code

BaseResult

package com.my.micro.service.gateway.result;

import lombok.Data;

/ * * *@author Zijian Liao
 * @since1.0.0 * /
@Data
public class BaseResult<T> {

    private Integer code;

    private String message;

    public BaseResult(Integer code, String message){
        this.code = code;
        this.message = message;
    }

    public static <T> BaseResult<T> failure(String message){
        return new BaseResult<>(-1, message); }}Copy the code

Writing configuration classes

package com.my.micro.service.gateway.exception;

import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWebExceptionHandler;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.reactive.result.view.ViewResolver;

import java.util.stream.Collectors;

/ * * *@author Zijian Liao
 * @since1.0.0 * /
@Configuration
public class ExceptionConfiguration {

    @Primary
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes, ServerProperties serverProperties, ResourceProperties resourceProperties, ObjectProvider
       
         viewResolversProvider, ServerCodecConfigurer serverCodecConfigurer, ApplicationContext applicationContext)
        {
        DefaultErrorWebExceptionHandler exceptionHandler = new JsonExceptionHandler(errorAttributes,
                resourceProperties, serverProperties.getError(), applicationContext);
        exceptionHandler.setViewResolvers(viewResolversProvider.orderedStream().collect(Collectors.toList()));
        exceptionHandler.setMessageWriters(serverCodecConfigurer.getWriters());
        exceptionHandler.setMessageReaders(serverCodecConfigurer.getReaders());
        returnexceptionHandler; }}Copy the code

test

summary

This part introduces a solution for how clients access microservices architecture: Spring Cloud Gateway

It introduces the three core concepts of Gateway: Route, Predicate, and Filter. I showed you how to configure and use them, and I also showed you how to customize Predicate and Filter.

Finally, we introduced the Spring Cloud Gateway’s global filters and how to implement custom exception handling.

The above

If you want to know more exciting content, welcome to pay attention to the public account: programmer AH Jian, AH Jian in the public account welcome your arrival ~

Personal blog space: zijiancode. Cn/archives/ga…

Gittee: gitee.com/lzj960515/m…