Spring Cloud Gateway (a) Beginners

1. gateway evolution is how come

  • After the split single application into multiple services, foreign need for a unified entrance, decoupling the client and internal service
    Here Insert Picture Description

2. The basic functions of the gateway

  • Gateway core function is to route forward, so do not have a time-consuming operation processing on the gateway, so fast-forwarding the request to the back-end services
  • Gateway can do a unified fusing, current limiting, certification, log monitoring, etc.
    Here Insert Picture Description

3. About Spring Cloud Gateway

Spring Cloud Gateway is based on technology developed by the spring of official gateway Spring5.0, Spring Boot2.0, Project Reactor, etc., use non-blocking API, Websockets supported the aim is to replace the original version of the Spring Cloud Netfilx Zuul, currently has open source Netfilx the Zuul2.0, but did not consider the integration of Spring, but launched its own development of Spring Cloud GateWay. It should be noted about the use of + webflux netty gateway to achieve, not to join a web-dependent (Do not quote webmvc), otherwise initialization error, you need to join webflux dependence.

Zuul simple comparison of the gateway: gateway using asynchronous request, Zuul a synchronization request, the gateway ServerWebExchange data encapsulation, the encapsulated RequestContext Zuul, the synchronization easy tuning, data can be encapsulated in a ThreadLocal transfer.

Spring Cloud Gateway has three core concepts: routing, asserted that filters
filter: gateway has two filter: GlobalFilter, GatewayFilter, the global default filter effect on all routes.
Documents Address: cloud.spring.io/spring-clou...

Gateway requests entry for all traffic in the actual production environment in order to ensure a highly reliable and avoid restart, the need to use dynamic routing configuration, routing configuration changes during the operation of the gateway

4. Code Practice

Need to use the three projects, eureka-server, gateway, consumer-service

  • 1.eureka-server registration service discovery, service instance code samples ip + port, when used in the foregoing blog gateway for forwarding the request
  • 2. Create a new gateway Gateway Project, a project reference as follows:
<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
123456789101112复制代码

On the main class enables service discovery comment @EnableDiscoveryClient registered
configuration file as follows:

server:
  port: 9999
spring:
  profiles:
    active: dev
  application:
    name: gateway-service
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true
          # 服务名小写
          lower-case-service-id: true
      routes:
      - id: consumer-service
        # lb代表从注册中心获取服务,且已负载均衡方式转发
        uri: lb://consumer-service
        predicates:
        - Path=/consumer/**
        # 加上StripPrefix=1,否则转发到后端服务时会带上consumer前缀
        filters:
        - StripPrefix=1

# 注册中心
eureka:
  instance:
    prefer-ip-address: true
  client:
    service-url:
      defaultZone: http://zy:zy123@localhost:10025/eureka/


# 暴露监控端点
management:
  endpoints:
    web:
      exposure:
        include: '*'
  endpoint:
    health:
      show-details: always
123456789101112131415161718192021222324252627282930313233343536373839404142复制代码

The above completes the gateway code portion below the new consumer-service

  • 3.consumer-service consumer services, forwarding services to consumers through the gateway routing, and returns the information back, so mvc project is a
    project reference as follows:
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
12345678复制代码

Enabling service on the main class found registered notes @EnableDiscoveryClient
add configuration in the configuration file:

server.port=9700
spring.application.name=consumer-service
eureka.instance.prefer-ip-address=true
# 配置eureka-server security的账户信息
eureka.client.serviceUrl.defaultZone=http://zy:zy123@localhost:10025/eureka/
12345复制代码

The IndexController new, add a hello method, passing the name parameter, return visit hi + name string

@RestController
public class IndexController {

    @RequestMapping("/hello")
    public String hello(String name){
        return "hi " + name;
    }
}
12345678复制代码
  • 4. The three projects were started, visit http: // localhost: 10025 to see the eureka gateway and consumer-service instance is registered, you can see already registered, respectively, in 9700,9999 port
    Here Insert Picture Description

Hello through the gateway of consumer-service access method, HTTP: // localhost: 9999 / Consumer / ZY hello name =? , The effect is as follows, indicating that the request has been forwarded to the service of the consumer-service
Here Insert Picture Description

This completes the basic code is the gateway, the following continue to introduce some commonly used filters, unified certification authentication, logging, and security testing through the filter

  • 5. Add GlobalFilter global filter in the gateway project, each print request url, code is as follows:
/**
 * 全局过滤器
 * 所有请求都会执行
 * 可拦截get、post等请求做逻辑处理
 */
@Component
public class RequestGlobalFilter implements GlobalFilter,Ordered {

    //执行逻辑
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest serverHttpRequest= exchange.getRequest();
        String uri = serverHttpRequest.getURI().toString();
        System.out.println(" uri : " + uri);//打印每次请求的url
        String method = serverHttpRequest.getMethodValue();
        if ("POST".equals(method)) {
            //从请求里获取Post请求体
            String bodyStr = resolveBodyFromRequest(serverHttpRequest);
            //TODO 得到Post请求的请求参数后,做你想做的事
 
            //下面的将请求体再次封装写回到request里,传到下一级,否则,由于请求体已被消费,后续的服务将取不到值
            URI uri = serverHttpRequest.getURI();
            ServerHttpRequest request = serverHttpRequest.mutate().uri(uri).build();
            DataBuffer bodyDataBuffer = stringBuffer(bodyStr);
            Flux<DataBuffer> bodyFlux = Flux.just(bodyDataBuffer);
 
            request = new ServerHttpRequestDecorator(request) {
                @Override
                public Flux<DataBuffer> getBody() {
                    return bodyFlux;
                }
            };
            //封装request,传给下一级
            return chain.filter(exchange.mutate().request(request).build());
        } else if ("GET".equals(method)) {
            Map requestQueryParams = serverHttpRequest.getQueryParams();
            //TODO 得到Get请求的请求参数后,做你想做的事
 
            return chain.filter(exchange);
        }
        return chain.filter(exchange);
    }
	/**
     * 从Flux<DataBuffer>中获取字符串的方法
     * @return 请求体
     */
    private String resolveBodyFromRequest(ServerHttpRequest serverHttpRequest) {
        //获取请求体
        Flux<DataBuffer> body = serverHttpRequest.getBody();
 
        AtomicReference<String> bodyRef = new AtomicReference<>();
        body.subscribe(buffer -> {
            CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer());
            DataBufferUtils.release(buffer);
            bodyRef.set(charBuffer.toString());
        });
        //获取request body
        return bodyRef.get();
    }
 
    private DataBuffer stringBuffer(String value) {
        byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
 
        NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT);
        DataBuffer buffer = nettyDataBufferFactory.allocateBuffer(bytes.length);
        buffer.write(bytes);
        return buffer;
    }

    //执行顺序
    @Override
    public int getOrder() {
        return 1;
    }
}
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475复制代码

Rerun the Gateway project, and access to HTTP: // localhost: 9999 / Consumer / ZY the Hello name =? , View the console, you can see uri journal is printed out
Here Insert Picture Description

  • 6. Add the gateway project GatewayFilter filters, we give consumer-service authentication token add filters, and global filters u is the same, GatewayFilter need to specify the service in a configuration file using this filter, as follows:
/**
 * 可对客户端header 中的 Authorization 信息进行认证
 */
@Component
public class TokenAuthenticationFilter extends AbstractGatewayFilterFactory {

    private static final String Bearer_ = "Bearer ";

    @Override
    public GatewayFilter apply(Object config) {
        return (exchange, chain) -> {
            ServerHttpRequest request = exchange.getRequest();
            ServerHttpRequest.Builder mutate = request.mutate();
            ServerHttpResponse response = exchange.getResponse();
            try {
                //String token = exchange.getRequest().getQueryParams().getFirst("authToken");
                //1.获取header中的Authorization
                String header = request.getHeaders().getFirst("Authorization");
                if (header == null || !header.startsWith(Bearer_)) {
                    throw new RuntimeException("请求头中Authorization信息为空");
                }
                //2.截取Authorization Bearer
                String token = header.substring(7);
                //可把token存到redis中,此时直接在redis中判断是否有此key,有则校验通过,否则校验失败
                if(!StringUtils.isEmpty(token)){
                    System.out.println("验证通过");
                    //3.有token,把token设置到header中,传递给后端服务
                    mutate.header("userDetails",token).build();
                }else{
                    //4.token无效
                    System.out.println("token无效");
                    DataBuffer bodyDataBuffer = responseErrorInfo(response , HttpStatus.UNAUTHORIZED.toString() ,"无效的请求");
                    return response.writeWith(Mono.just(bodyDataBuffer));
                }
            }catch (Exception e){
                //没有token
                DataBuffer bodyDataBuffer = responseErrorInfo(response , HttpStatus.UNAUTHORIZED.toString() ,e.getMessage());
                return response.writeWith(Mono.just(bodyDataBuffer));
            }
            ServerHttpRequest build = mutate.build();
            return chain.filter(exchange.mutate().request(build).build());
        };
    }

    /**
     * 自定义返回错误信息
     * @param response
     * @param status
     * @param message
     * @return
     */
    public DataBuffer responseErrorInfo(ServerHttpResponse response , String status ,String message){
        HttpHeaders httpHeaders = response.getHeaders();
        httpHeaders.add("Content-Type", "application/json; charset=UTF-8");
        httpHeaders.add("Cache-Control", "no-store, no-cache, must-revalidate, max-age=0");

        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        Map<String,String> map = new HashMap<>();
        map.put("status",status);
        map.put("message",message);
        DataBuffer bodyDataBuffer = response.bufferFactory().wrap(map.toString().getBytes());
        return bodyDataBuffer;
    }
}
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364复制代码

Specify consumer-service use TokenAuthenticationFilter service in the configuration file, configuration is as follows:

routes:
- id: consumer-service
  uri: lb://consumer-service
  predicates:
  - Path=/consumer/**
  filters:
  # 进行token过滤
  - TokenAuthenticationFilter
  - StripPrefix=1
123456789复制代码

Run the project again visit http: // localhost: 9999 / consumer / hello name = zy?
Here Insert Picture Description

  • Item 7. The front and rear ends separated cross-domain gateway to solve the problem, the following code in the main category gateway:
@Bean
	public WebFilter corsFilter() {
		return (ServerWebExchange ctx, WebFilterChain chain) -> {
			ServerHttpRequest request = ctx.getRequest();
			if (!CorsUtils.isCorsRequest(request)) {
				return chain.filter(ctx);
			}

			HttpHeaders requestHeaders = request.getHeaders();
			ServerHttpResponse response = ctx.getResponse();
			HttpMethod requestMethod = requestHeaders.getAccessControlRequestMethod();
			HttpHeaders headers = response.getHeaders();
			headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, requestHeaders.getOrigin());
			headers.addAll(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, requestHeaders.getAccessControlRequestHeaders());
			if (requestMethod != null) {
				headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, requestMethod.name());
			}
			headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
			headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "all");
			headers.add(HttpHeaders.ACCESS_CONTROL_MAX_AGE, "3600");
			if (request.getMethod() == HttpMethod.OPTIONS) {
				response.setStatusCode(HttpStatus.OK);
				return Mono.empty();
			}
			return chain.filter(ctx);
		};
	}
123456789101112131415161718192021222324252627复制代码

Code has been uploaded to the cloud code, source code , version information of the project are as follows:

- SpringBoot 2.0.6.RELEASE
- SpringCloud Finchley.SR2复制代码

Links: blog.csdn.net/zhuyu199110...


Guess you like

Origin juejin.im/post/5dca16f9e51d4578283482fe