SpingCloud-Gateway

一、简介

Spring Cloud GateWay 是Spring官方基于Spring5.0 SpringBoot2.0 和 Project Reactor等技术开发的网关,Spring Cloud GateWay zhi在为旨在为微服务架构提供一种简单有效的、统一的API路由管理方式。它不仅提供统一的路由方式,并且基于Filter链的方式提供了网关的基本功能,例如:安全、监控/埋点和限流等。

在Spring Cloud GateWay 有几个概念需要了解一下:

Route:是网关的基础元素,有ID 目标URI 、断言、过滤器组成。当请求到达网关时由GateWay Handler Mapping 通过断言进行路由匹配(Mapping) 当断言为真时匹配到路由。

Predicate: Predicate 时 java8 提供的一个函数。输入类型是Spring Framework ServerWebExchange,它允许开发人员匹配来自HTTP的请求。

Filter: Filter 是GateWay 中的过滤器,可以在请求发出前后进行一些业务的处理。

二、简单使用

创建一个springBoot的maven项目,添加下面的依赖

 <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
            <version> 2.2.2.RELEASE</version>
        </dependency>
<dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Hoxton.SR4</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

在配置文件进行如下配置即可进行路由转发

server:
  port: 2001
spring:
  application:
    name: spring-cloud-gateway
  cloud:
    gateway:
      routes:
        - id: path_route
          uri: https://www.baidu.com
          predicates:
            - Path=/user/**

这样只要访问 http://localhost:2001/user 就会跳转到 https://www.baidu.com

注意:-path中的user/** 的** 表示任意层级

三、整合Eureka

1. 整合 Eureka 路由

引入依赖

       <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            <version>2.2.2.RELEASE</version>
        </dependency>

配置文件的配置如下

spring:
  application:
    name: spring-cloud-gateway
  cloud:
    gateway:
      routes:
        - id: eureka-client-user-service
          uri: lb://eureka-client-user-service
          predicates:
            - Path=/user/**
eureka:
  instance:
    prefer-ip-address: true
    hostname: ${spring.cloud.client.ip-address}
    instance-id: ${spring.application.name}:${spring.cloud.client.ip-address}:${server.port}
  client:
    serviceUrl:
      defaultZone: http://tttt:123456@localhost:8761/eureka/

注意:uri 以lb://开头(lb 代表从注册中心获取服务),后面接的就是需要转发到的服务名称,这个服务名称必须跟Eureka中的对应,否则会找不到服务。

这时访问 http://localhost:2001/user/hello 就会转发到 eureka-client-user-service 的 /user/hello 的接口中。

2. 整合Eureka的默认路由

Spring Cloud GateWay 可以为所有的服务进行转发操作,我们只需要在访问路径上指定要访问的服务即可,通过这种方式就不用为每个服务都去配置转发规则,当新加了服务就不用去配置路由规则和重启网关,配置如下:

spring:
  application:
    name: spring-cloud-gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true
#          可以通过小写的服务名访问的配置
          lower-case-service-id: true

注意:如果在配置文件中不设置  lower-case-service-id: true 只能通过大写的服务名访问,设置了之后只能通过小写的服务名访问。

四、路由断言工厂的使用

1. path 路由断言工厂

spring:
  application:
    name: spring-cloud-gateway
  cloud:
    gateway:
      routes:
        - id: eureka-client-user-service
          uri: lb://eureka-client-user-service
          predicates:
            - Path=/user/**

2. Qyery 路由断言工厂

spring:
  application:
    name: spring-cloud-gateway
  cloud:
    gateway:
      routes:
        - id: query_route
          uri: https://www.baidu.com
          predicates:
            - Query=foo,ba.

如果请求包含一个值与ba匹配的foo查询参数,则次路由将会匹配,因此第二个参数是正则表达式。

测试连接:http://localhost:2001/?foo=bar

3. Method 路由断言工厂

spring:
  application:
    name: spring-cloud-gateway 
  cloud:
    gateway:
      routes:
        - id: method_route
          uri: https://www.baidu.com
          predicates:
            - Method=GET

只要是get请求都会被匹配。

其他更多的断言工厂方法请参考官方文档进行学习。

4. 自定义断言工厂

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

import java.util.function.Predicate;

@Component
public class CheckAuthRoutePredicateFactory extends AbstractRoutePredicateFactory<CheckAuthRoutePredicateFactory.Config> {
    public CheckAuthRoutePredicateFactory() {
        super(Config.class);
    }

    @Override
    public Predicate<ServerWebExchange> apply(Config config) {
        return exchange->{
          System.err.println("进入了 CheckAuthRoutePredicateFactory "+config.getName());
          if (config.getName().equals("1234")){
              return true;
          }
          return false;
        };
    }

    public static class Config{
        private String name;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }
}
  cloud:
    gateway:
      routes:
        - id: customer_route
          uri: https://www.baidu.com
          predicates:
            - name: CheckAuth
              args:
                name: 1234

五、过滤器使用

1. AddRequestHeader 过滤器的使用

  cloud:
    gateway:
      routes:
        - id: add_request_header_route
          uri: https://www.baidu.com
          filters:
            - AddRequestHeader=X-Request-Foo, Bar

匹配成功后将在headers里添加 X-Request-Foo:Bar,并将其传递到后端服务。

2. RemoveRequestHeader 过滤器的使用

  cloud:
    gateway:
      routes:
        - id: add_request_header_route
          uri: https://www.baidu.com
          predicates:
            - name: CheckAuth
              args:
                name: 456
          filters:
            - RemoveRequestHeader=X-Request-Foo, Bar

3. SetSatus 过滤器工厂

  cloud:
    gateway:
      routes:
        - id: add_request_header_route
          uri: https://www.baidu.com
          predicates:
            - name: CheckAuth
              args:
                name: 456
          filters:
            - SetSatus=401

更多的过滤器工厂请参考官方文档。

4. 自定义过滤器工厂

package com.example.gateway.springcloudgateway.common;

import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;

@Component
public class CheckAuth2GatewayFilterFactory extends AbstractGatewayFilterFactory<CheckAuth2GatewayFilterFactory.Config> {

    public CheckAuth2GatewayFilterFactory() {
        super(Config.class);
    }

    @Override
    public GatewayFilter apply(Config config) {
       return (exchange, chain) -> {
           System.err.println("进入了 CheckAuth2GatewayFilterFactory"+config.getName());
           ServerHttpRequest request = exchange.getRequest().mutate().build();
           return chain.filter(exchange.mutate().request(request).build());
       };
    }

    public static class Config{
        private String name;


        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }
}

使用

          filters:
             - name: CheckAuth2
               args:
                 name: 123

5. 全局过滤器使用

import com.alibaba.fastjson.JSONObject;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;

@Component
public class IPCheckFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        HttpHeaders headers = exchange.getRequest().getHeaders();
        if(getIp(headers).equals("127.0.0.2")){
            ServerHttpResponse response = exchange.getResponse();
            String data = "非法请求";
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("data",data);
            byte[] datas = new byte[0];
            try {
                datas = data.getBytes(String.valueOf(StandardCharsets.UTF_8));
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            DataBuffer buffer = response.bufferFactory().wrap(datas);
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            response.getHeaders().add("Content-Type","application/json;charset=UFT-8");
            Mono<Void> voidMono = response.writeWith(Mono.just(buffer));
            return voidMono;
        }
        return chain.filter(exchange);
    }

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

    public String getIp(HttpHeaders headers){
        return "127.0.0.1";
    }
}

六、熔断回退

引入依赖

       <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>

配置文件进行如下配置

spring:
  application:
    name: spring-cloud-gateway
  cloud:
    gateway:
      routes:
        - id: eureka-client-user-service
          uri: lb://eureka-client-user-service
          predicates:
            - Path=/user/**
          filters:
             - name: Hystrix
              args:
                name: fallbackcmd
                fallbackUri: forward:/fallback

上面配置了一个Hystrix的过滤器,该过滤器会使用 Hystrix 熔断与回退,原理是将请求包装成RouteHystrixCommand执行。

fallbackuri:是发生熔断时回退的uri 地址,目前只支持forward 模式的uri。如果服务被降级后该请求会被转发到该uri

    @GetMapping("/fallback")
    public String fallback(){

        return "fallback";
    }

七、跨域问题

package com.example.gateway.springcloudgateway.common;

import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.cors.reactive.CorsUtils;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;

@Component
public class CoresConfig {

    @Bean
    public WebFilter corsFilter() {
        return (ServerWebExchange ctx, WebFilterChain chain) -> {
            ServerHttpRequest request = ctx.getRequest();
            if (CorsUtils.isCorsRequest(request)) {
                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 (null != requestMethod) {
                    headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, requestMethod.name());
                }
                headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
                headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "*");
                if (request.getMethod() == HttpMethod.OPTIONS) {
                    response.setStatusCode(HttpStatus.OK);
                    return Mono.empty();
                }
            }
            return chain.filter(ctx);
        };
    }
}

八、统一异常处理

自定义异常处理逻辑代码如下:

package com.example.gateway.springcloudgateway.common;

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.error.ErrorAttributeOptions;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.cloud.gateway.support.NotFoundException;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpStatus;
import org.springframework.web.reactive.function.server.*;

import java.util.HashMap;
import java.util.Map;

public class JsonExceptionHandler extends DefaultErrorWebExceptionHandler {

    public JsonExceptionHandler(ErrorAttributes errorAttributes, ResourceProperties resourceProperties, ErrorProperties errorProperties, ApplicationContext applicationContext) {
        super(errorAttributes, resourceProperties, errorProperties, applicationContext);
    }

    /**
     * 获取异常属性
     * @param request
     * @param options
     * @return
     */
    @Override
    protected Map<String, Object> getErrorAttributes(ServerRequest request, ErrorAttributeOptions options) {
         int code = 500;
        Throwable error = super.getError(request);
        if(error instanceof NotFoundException){
            code = 404;
        }
        return response(code,this.buildMessage(request,error));
    }

    /**
     * 制定相应处理方法为json数据格式
     * @param errorAttributes
     * @return
     */
    @Override
    protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
        return RouterFunctions.route(RequestPredicates.all(),this::renderErrorResponse);
    }

    /**
     * 根据code获取对应的httpStatus
     * @param errorAttributes
     * @return
     */
    @Override
    protected int getHttpStatus(Map<String, Object> errorAttributes) {
        int statusCode = (int) errorAttributes.get("code");
        HttpStatus httpStatus = HttpStatus.valueOf(statusCode);
        return httpStatus.value();
    }

    /**
     * 构建异常信息
     * @param request
     * @param ex
     * @return
     */
    private String buildMessage(ServerRequest request, Throwable ex) {
        StringBuffer message = new StringBuffer("fail to handler request [");
        message.append(request.methodName());
        message.append(" ");
        message.append(request.uri());
        message.append("]");
        if(ex != null){
            message.append(": ");
            message.append(ex.getMessage());
        }
        return message.toString();
    }

    /**
     * 构建返回的json 数据格式
     * @param status
     * @param errorMessage
     * @return
     */
    public static Map<String,Object> response(int status,String errorMessage){
        Map<String,Object> map = new HashMap<>();
        map.put("code",status);
        map.put("message",errorMessage);
        map.put("data",null);
        return map;
    }
}

覆盖默认的配置代码如下:

package com.example.gateway.springcloudgateway.common;

import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
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.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.Collections;
import java.util.List;

@Configuration
@EnableConfigurationProperties({ServerProperties.class, ResourceProperties.class})
public class ErrorHandlerConfiguration {

    private final ServerProperties serverProperties;

    private final ApplicationContext applicationContext;

    private final ResourceProperties resourceProperties;

    private final List<ViewResolver> viewResolverList;

    private final ServerCodecConfigurer serverCodecConfigurer;

    public ErrorHandlerConfiguration(ServerProperties serverProperties, ApplicationContext applicationContext, ResourceProperties resourceProperties,
                                     ObjectProvider<List<ViewResolver>> viewResolverProvider, ServerCodecConfigurer serverCodecConfigurer) {
        this.serverProperties = serverProperties;
        this.applicationContext = applicationContext;
        this.resourceProperties = resourceProperties;
        this.viewResolverList = viewResolverProvider.getIfAvailable(Collections::emptyList);
        this.serverCodecConfigurer = serverCodecConfigurer;
    }

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes){
        JsonExceptionHandler exceptionHandler = new JsonExceptionHandler(errorAttributes,this.resourceProperties,
                this.serverProperties.getError(),this.applicationContext);
        exceptionHandler.setViewResolvers(this.viewResolverList);
        exceptionHandler.setMessageWriters(this.serverCodecConfigurer.getWriters());
        exceptionHandler.setMessageReaders(this.serverCodecConfigurer.getReaders());
        return exceptionHandler;
    }
}

九、重试机制

spring:
  application:
    name: spring-cloud-gateway
  cloud:
    gateway:
      routes:
        - id: eureka-client-user-service
          uri: lb://eureka-client-user-service
          predicates:
            - Path=/user/**
          filters:
            - name: Retry
              args:
                retries: 3
                series: SERVER_ERROR

retries:重试次数,默认值是3次

series:状态码配置(分段),符合某段状态码才会进行重试逻辑,默认值是 SERVER_ERROR 值是5,也就是5XX(5开头的状态码),共有5个值,代码如下:

    public static enum Series {
        INFORMATIONAL(1),
        SUCCESSFUL(2),
        REDIRECTION(3),
        CLIENT_ERROR(4),
        SERVER_ERROR(5);
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章