6-GateWay服务网关


一、GateWay基本概念

1、GateWay说明

gateway之所以性能号,因为底层使用WebFlux,而webFlux底层使用netty通信(NIO)

  • GateWay能干啥

    1. 反向代理

    2. 鉴权

    3. 流量控制

    4. 熔断

    5. 日志监控

2、微服务架构中的网关在哪里

3、GateWay的特性

4、GateWay和Zuul区别

5、什么是webflux:

是一个非阻塞的web框架,类似springmvc这样的

6、Gate的一些基本概念

(1)路由

路由是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如断言为true则匹配该路由

(2)断言

参考的是Java8的java.util.function.Predicate

开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由

(3)过滤

指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改.

7、GateWay工作原理

二、模块搭建

1、pom

不可以加spring-boot-starter-webspring-boot-starter-actuator

<dependencies>
    <!--gateway-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    <!--eureka-client-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
    <dependency>
        <groupId>com.atguigu.springcloud</groupId>
        <artifactId>cloud-api-commons</artifactId>
        <version>${project.version}</version>
    </dependency>
    <!--一般基础配置类-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

2、application.yml配置文件配置网关

server:
  port: 9527

spring:
  application:
    name: cloud-gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由    
      # cors 配置
      globalcors:
        cors-configurations:
          '[/**]':
            allowedOrigins: "*"
            allowedMethods: "*"
            allowedHeaders: "*"    
      routes:
        - id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
          #uri: http://localhost:8001 #匹配后提供服务的路由地址
          uri: lb://cloud-payment-service #匹配后提供服务的路由地址(动态路由)
          predicates:
            - Path=/payment/get/** # 断言,路径相匹配的进行路由

        - id: payment_routh2 #路由的ID,没有固定规则但要求唯一,建议配合服务名
          #uri: http://localhost:8001 #匹配后提供服务的路由地址
          uri: lb://cloud-payment-service #匹配后提供服务的路由地址
          predicates:
            - Path=/payment/lb/** # 断言,路径相匹配的进行路由
            #- After=2020-02-21T15:51:37.485+08:00[Asia/Shanghai]
            #- Cookie=username,zzyy
            #- Header=X-Request-Id, \d+  # 请求头要有X-Request-Id属性并且值为整数的正则表达式

#使用nacos作为注册中心
    nacos:
      discovery:
        server-addr: localhost:8848 #配置Nacos地址
        namespace: 38efc944-744e-45bb-a814-e356fbd883c3

#使用euraka最为注册中心
eureka:
  instance:
    hostname: cloud-gateway-service
  client: #服务提供者provider注册进eureka服务列表内
    service-url:
      register-with-eureka: true
      fetch-registry: true
      defaultZone: http://eureka7001.com:7001/eureka

3、配置类配置网关

也可不采用yml配置,而采用配置类进行配置

@Configuration
public class GateWayConfig {

    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder)
    {
        RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();

        //将localhost:9527/guonei 路由映射到 百度http://news.baidu.com/guonei
        routes.route("path_route_atguigu",
                r -> r.path("/guonei")
                        .uri("http://news.baidu.com/guonei")).build();

        return routes.build();
    }
}

4、主启动类

@SpringBootApplication
@EnableEurekaClient        //euraka最为注册中心
@EnableDiscoveryClient    //Nacos作为注册中心
public class MainGateWay9527 {
    public static void main(String[] args) {
        SpringApplication.run(MainGateWay9527.class,args);
    }
}

5、测试

可在localhost:7001的Eureka图形化界面中看到9527的服务网关微服务

三、常用的Route Predicate(断言)

routes:
    - id: payment_routh #payment_route    #路由的ID,没有固定规则但要求唯一,建议配合服务名
        #uri: http://localhost:8001          #匹配后提供服务的路由地址
        uri: lb://cloud-payment-service #匹配后提供服务的路由地址
        predicates:
            - Path=/payment/get/**         # 断言,路径相匹配的进行路由
            - After=2020-02-21T15:51:37.485+08:00[Asia/Shanghai]      #在指定时间后执行
            - Cookie=username,zzyy    #含有响应的cookie键值对
            - Header=X-Request-Id, \d+  # 请求头有X-Request-Id属性并且值为整数的正则表达式
            - Host=**.com,**.org.com
            - Method=GET    #必须是get请求
            - Query=username,\d+   #有参数名并且只是整数

有先后 顺序,当前面的匹配到了,就不会匹配到后面的

1、Path(路径)

这个断言表示,如果外部访问路径是指定路径,就路由到指定微服务上

只有访问指定路径,才进行路由,比如访问,/abc才路由

2、After、Before、Between(时间)

  • After:可以指定,只有在指定时间,才可以路由到指定微服务
  • Before:可以指定,只有在指定时间,才可以路由到指定微服务
  • Between:可以指定,只有在指定时间,才可以路由到指定微服务(需指定2个时区,用,隔开)

时区时间串的获得方法

ZonedDateTime time = ZonedDateTime.now();
System.out.println(time);    //2021-01-05T22:19:46.948+08:00[Asia/Shanghai]

3、Cookie

  1. 两个参数,一个是Cookie name,一个是正则表达式,路由规则会根据对应的Cookie name和正则表达式进行匹配,匹配成功执行路由,否则不执行。

  2. 只有包含某些指定cookie(key,value),的请求才可以路由。

  3. 配置后直接通过路径无法访问。

需要有指定的Cookie(使用curl测试)

如果加入curl返回中文乱码,解决方法https://blog.csdn.net/leedee/article/details/82685636

4、 Header

只有包含指定请求头的请求,才可以路由。(与Cookie相似)(区别键和值用:隔开)

curl http://localhost:9527/payment/lb --cookie "username=zzyy" -H "X-Request-Id:123"

5、Host(防盗链)

只有指定主机的才可以访问,
比如我们当前的网站的域名是www.aa.com
那么这里就可以设置,只有用户是www.aa.com的请求,才进行路由

6、Method

只有指定请求才可以路由,比如get请求…

7、Query

必须带有请求参数才可以访问

四、Filter过滤器

1、基本概念

路由过滤器可用于修改进入的HTTP请求返回的HTTP响应

springcloud GateWay内置多种路由过滤器,都由GateWayFilter的工厂类产生

2、声明周期

  1. 在请求进入路由之前
  2. 处理请求完成,再次到达路由之前

3、种类

  1. GateWayFilter(单一过滤器)(31种)

https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/#gatewayfilter-factories

  1. GlobalFilter(全局过滤器)(10种)

https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/#global-filters

4、自定义过滤器

(1)作用

  1. 全局日志记录
  2. 统一网关鉴权

(2)在filter包下创建MyLogGateWayFilter

//该过滤器直接通过@Component加入到容器中,故不需要其他配置
@Component
@Slf4j
//必须实现2个接口
public class MyLogGateWayFilter implements GlobalFilter,Ordered
{

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain)
    {
        log.info("***********come in MyLogGateWayFilter:  "+new Date());

        //获取请求参数uname
        String uname = exchange.getRequest().getQueryParams().getFirst("uname");

        //如果name为空,直接过滤掉,不走路由
        if(uname == null)
        {
            log.info("*******用户名为null,非法用户,o(╥﹏╥)o");
            exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
            return exchange.getResponse().setComplete();
        }

        //该过滤器放行,调用下一个过滤器
        return chain.filter(exchange);
    }

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

(3)测试

http://localhost:9527/payment/lb无法访问

http://localhost:9527/payment/lb?uname=123可以访问

五、解决跨域问题

gateway网关微服务配置类

/**
 * 用于解决跨域问题
 * 用了此类后不要在在cobtroller中加 @CrossOrign 注解了,加了会出问题
 */
@Configuration
public class CorsConfig {
    @Bean
    public CorsWebFilter corsFilter() {
        CorsConfiguration config = new CorsConfiguration();
        config.addAllowedMethod("*");
        config.addAllowedOrigin("*");
        config.addAllowedHeader("*");

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
        source.registerCorsConfiguration("/**", config);

        return new CorsWebFilter(source);
    }
}

六、权限控制(鉴权)

gateway网关微服务过滤器

/**
 * 全局Filter,统一处理会员登录与外部不允许访问的服务
 */
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {

    private AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getURI().getPath();
        //谷粒学院api接口,校验用户必须登录
        if(antPathMatcher.match("/api/**/auth/**", path)) {
            List<String> tokenList = request.getHeaders().get("token");
            if(null == tokenList) {
                ServerHttpResponse response = exchange.getResponse();
                return out(response);
            } else {
//                Boolean isCheck = JwtUtils.checkToken(tokenList.get(0));
//                if(!isCheck) {
                    ServerHttpResponse response = exchange.getResponse();
                    return out(response);
//                }
            }
        }
        //内部服务接口,不允许外部访问
        if(antPathMatcher.match("/**/inner/**", path)) {
            ServerHttpResponse response = exchange.getResponse();
            return out(response);
        }
        return chain.filter(exchange);
    }

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

    private Mono<Void> out(ServerHttpResponse response) {
        JsonObject message = new JsonObject();
        message.addProperty("success", false);
        message.addProperty("code", 28004);
        message.addProperty("data", "鉴权失败");
        byte[] bits = message.toString().getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = response.bufferFactory().wrap(bits);
        //response.setStatusCode(HttpStatus.UNAUTHORIZED);
        //指定编码,否则在浏览器中会中文乱码
        response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        return response.writeWith(Mono.just(buffer));
    }
}

七、异常处理

1、覆盖默认的异常处理

/**
 * 覆盖默认的异常处理
 */
@Configuration
@EnableConfigurationProperties({ServerProperties.class, ResourceProperties.class})
public class ErrorHandlerConfig {

    private final ServerProperties serverProperties;

    private final ApplicationContext applicationContext;

    private final ResourceProperties resourceProperties;

    private final List<ViewResolver> viewResolvers;

    private final ServerCodecConfigurer serverCodecConfigurer;

    public ErrorHandlerConfig(ServerProperties serverProperties,
                                     ResourceProperties resourceProperties,
                                     ObjectProvider<List<ViewResolver>> viewResolversProvider,
                                        ServerCodecConfigurer serverCodecConfigurer,
                                     ApplicationContext applicationContext) {
        this.serverProperties = serverProperties;
        this.applicationContext = applicationContext;
        this.resourceProperties = resourceProperties;
        this.viewResolvers = viewResolversProvider.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.viewResolvers);
        exceptionHandler.setMessageWriters(this.serverCodecConfigurer.getWriters());
        exceptionHandler.setMessageReaders(this.serverCodecConfigurer.getReaders());
        return exceptionHandler;
    }
}

2、自定义异常处理

/**
 * 自定义异常处理
 *
 * <p>异常时用JSON代替HTML异常信息<p>
 */
public class JsonExceptionHandler extends DefaultErrorWebExceptionHandler {

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

    /**
     * 获取异常属性
     */
    @Override
    protected Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
        Map<String, Object> map = new HashMap<>();
        map.put("success", false);
        map.put("code", 20005);
        map.put("message", "网关失败");
        map.put("data", null);
        return map;
    }

    /**
     * 指定响应处理方法为JSON处理的方法
     * @param errorAttributes
     */
    @Override
    protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
        return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
    }

    /**
     * 根据code获取对应的HttpStatus
     * @param errorAttributes
     */
    @Override
    protected int getHttpStatus(Map<String, Object> errorAttributes) {
        return 200;
    }
}

八、全局熔断降级Hystrix

1、依赖

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

2、添加配置

spring:
  application:
    name: cloud-gateway-hystrix
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由

      # 超时设置,配合熔断降级使用
      httpclient:
        # 链接超时,单位毫秒,默认45秒
        connect-timeout: 30000
        # 响应超时,30秒
        response-timeout: 30s

      # 全局熔断降级配置
      default-filters:
        - name: Hystrix
          args:
            name: default
            fallbackUri: 'forward:/fallback'

3、降级路由配置

import com.rewind.gateway.handler.HystrixFallbackHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.RequestPredicates;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;

/**
 * 路由配置信息
 */
@Configuration
public class RouterFunctionConfiguration {

    @Autowired
    private HystrixFallbackHandler hystrixFallbackHandler;

    @Bean
    public RouterFunction routerFunction() {
        return RouterFunctions.route(RequestPredicates.path("/fallback").and(RequestPredicates.accept(MediaType.TEXT_PLAIN)), hystrixFallbackHandler);
    }

}

4、降级方法

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.HandlerFunction;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;

import java.util.HashMap;
import java.util.Optional;

import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR;

/**
 * Hystrix 降级处理
 */
@Component
public class HystrixFallbackHandler implements HandlerFunction<ServerResponse> {

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

    @Override
    public Mono<ServerResponse> handle(ServerRequest serverRequest) {
        Optional<Object> originalUris = serverRequest.attribute(GATEWAY_ORIGINAL_REQUEST_URL_ATTR);
        originalUris.ifPresent(originalUri -> logger.error("Gateway execution request: {} fail, Hystrix service downgrades", originalUri));
        HashMap<String, Object> result = new HashMap<>();
        result.put("success", false);
        result.put("code", HttpStatus.SERVICE_UNAVAILABLE.value());
        result.put("message", "服务繁忙,请稍后重试");
        result.put("data", null);

        return ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR.value())
                .contentType(MediaType.APPLICATION_JSON)
                .body(BodyInserters.fromValue(result));
    }

}

  目录