• spingcloud(8) gateway


    一。简介

    springcloud是第二代网关,取代zuul网关。具有强大的智能路由,过滤器功能。常见的功能有路由转发、权限校验、限流控制等作用

    Spring Cloud Gateway 具有如下特性:

    • 基于Spring Framework 5, Project Reactor 和 Spring Boot 2.0 进行构建;
    • 动态路由:能够匹配任何请求属性;
    • 可以对路由指定 Predicate(断言)和 Filter(过滤器);
    • 集成Hystrix的断路器功能;
    • 集成 Spring Cloud 服务发现功能;
    • 易于编写的 Predicate(断言)和 Filter(过滤器);
    • 请求限流功能;
    • 支持路径重写。

    相关概念

    • Route(路由):路由是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如果断言为true则匹配该路由;
    • Predicate(断言):匹配请求中的信息,与断言匹配成功则进行路由。
    • Filter(过滤器):指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前后对请求进行修改。

    二。代码

    pom.xml添加依赖

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

    有两种配置路由的方式:一种是在yml文件来配置,另一种是通过java bean来配置。下面例子大部分用yml

    java bean配置

    SpringBootApplication
    @RestController
    public class SpringcloudGatewayApplication {
        public static void main(String[] args) {
            SpringApplication.run(SpringcloudGatewayApplication.class, args);
        }
    
        /**
         * java配置路由会覆盖application.yml中的配置
         * @param builder
         * @return
         */
       @Bean
        public RouteLocator myRoutes(RouteLocatorBuilder builder){
            String httpUri = "http://httpbin.org:80";
            return builder.routes()  //route来做路由
                    .route("rout1",p -> p
                      .path("/get") // 对/get请求做处理
                      .filters(f->f.addRequestHeader("hello", "yy")) //过滤器增加header
                      .uri(httpUri)) //转发到这个请求上
                    .build();
        }
    }

    可以看到请求/get 的header添加了参数hello=yy

    1.predicate实战

    After Route Predicate Factory

    可配置在指定时间后的请求,都交给router处理。否则不通过路由

    server:
      port: 8081
    spring:
      profiles:
        active: add_request_header_route
    ---
    spring:
      cloud:
        gateway:
          routes:
            - id: after_route
              uri: http://httpbin.org:80/get #前面的路径替换为该值即http://xxx:8081这段
              filters: #增加请求header的过滤器
                - AddRequestHeader=X-Request-Foo, Bar
              predicates:
                - After=2017-01-20T17:42:47.789-07:00[America/Denver]
      profiles: add_request_header_route

    启动工程,在浏览器上访问http://localhost:8081/,会显示http://httpbin.org:80/get返回的结果,此时gateway路由到了配置的uri。

    Header Route Predicate Factory

    Header Route Predicate Factory需要两个参数,header名称和值,当断言匹配了请求中的header名称和值后,断言通过,就能进入路由规则

    spring:
      profiles:
        active: header_route
    ---
    spring:
      cloud:
        gateway:
          routes:
            - id: header_route
              uri: https://www.baidu.com
              predicates:
                - Header=X-Request-Id, d+ #当请求的Header中有X-Request-Id的header名,且header值为数字时
      profiles: header_route

    测试:

    curl -H "X-Request-Id:1" localhost:8081

    返回页面请求就通过了,404则没有通过

    spring:
      profiles:
        active: cookie_route
    ---
    spring:
      cloud:
        gateway:
          routes:
            - id: cookie_route
              uri: http://httpbin.org:80/get
              predicates:
                - Cookie=name, yy
      profiles: cookie_route

    请求带有cookie名称为name,值为yy就能通过,被转发到http://httpbin.org:80/get

    测试

    curl -H "Cookie:name=forezp" localhost:8081

    Host Route Predicate Factory

    需要以一个参数即hostname,会匹配请求头中的host值,匹配则转发

    spring:
      profiles:
        active: host_route
    ---
    spring:
      cloud:
        gateway:
          routes:
            - id: host_route
              uri: http://httpbin.org:80/get
              predicates:
                - Host=**.yy.com
      profiles: host_route

    测试:curl -H "Host:www.fangzhipeng.com" localhost:8081

    Method Route Predicate Factory

    需要一个参数请求类型,GET

    spring:
      profiles:
        active: method_route
    ---
    spring:
      cloud:
        gateway:
          routes:
            - id: method_route
              uri: http://httpbin.org:80/get
              predicates:
                - Method=GET
      profiles: method_route

    测试:curl localhost:8081

    模拟POST请求:curl -XPOST localhost:8081

    Path Route Predicate Factory

    需要一个参数匹配路径

    spring:
      profiles:
        active: path_route
    ---
    spring:
      cloud:
        gateway:
          routes:
            - id: path_route
              uri: http://localhost:9600/
              predicates: 
                - Path=/hello
      profiles: path_route

    Query Route Predicate Factory

    需要两个参数,参数名和参数值,也可以只有一个参数即参数名

    spring:
      profiles:
        active: query_route
    ---
    spring:
      cloud:
        gateway:
          routes:
            - id: query_route
              uri: http://httpbin.org:80/get
              predicates:
                - Query=foo, ba.
      profiles: query_route

    测试:curl localhost:8081?foo=bar

    Predict作为断言,它决定了请求会被路由到哪个router 中。在断言之后,请求会被进入到filter过滤器的逻辑

    2.filter实战

     确定哪个路由执行后,在路由处理之前,先经过”pre“类型的过滤器,路由处理后,经过”post“类型的过滤器

    ”pre“类型的过滤器可以做参数校验,权限校验,流量监控,日志输出,协议转换等

    ”post“类型的过滤器可以做响应内容,响应头修改,日志输出,流量监控等。

    springcloud包含许多内置的GatewayFilter工厂,和predicate一样配置在application.yml,遵循了约定大于配置的原则,只需要配置filter工厂的名称,不需要配置全类名。

    比如AddRequestHeaderGatewayFilterFactory只需要在配置文件中写AddRequestHeader,而不是全部类名

    AddRequestHeader GatewayFilter Factory

    server:
      port: 8081
    spring:
      profiles:
        active: add_request_header_route #指定配置文件
    ---
    spring:
      cloud:
        gateway:
          routes:
          - id: add_request_header_route
            uri: http://httpbin.org:80/get
            filters:
            - AddRequestHeader=X-Request-Foo, Bar #AddRequestHeaderGatewayFilterFactory
            predicates:
            - After=2017-01-20T17:42:47.789-07:00[America/Denver] #AfterPredictFactory
      profiles: add_request_header_route

    模拟请求:curl localhost:8081

    最终显示了从 http://httpbin.org:80/get得到了请求,响应如下:

    {
      "args": {},
      "headers": {
        "Accept": "*/*",
        "Connection": "close",
        "Forwarded": "proto=http;host="localhost:8081";for="0:0:0:0:0:0:0:1:56248"",
        "Host": "httpbin.org",
        "User-Agent": "curl/7.58.0",
        "X-Forwarded-Host": "localhost:8081",
        "X-Request-Foo": "Bar"
      },
      "origin": "0:0:0:0:0:0:0:1, 210.22.21.66",
      "url": "http://localhost:8081/get"
    }

    从响应知道,请求头中加入了 "X-Request-Foo": "Bar"

    RewritePath GatewayFilter Factory

    重写路径的功能,类似nginx的功能

    spring:
      profiles:
        active: rewritepath_route
    ---
    spring:
      cloud:
        gateway:
          routes:
            - id: rewritepath_route
              uri: http://localhost:9600/
              predicates:
                - Path=/foo/**
              filters: #将/foo/(?.*)重写为{segment},然后转发
                - RewritePath=/foo/(?<segment>.*), /${segment}
      profiles: rewritepath_route

    比如在网页上请求localhost:8081/foo/forezp,此时会将请求转发到https://blog.csdn.net/forezp的页面

    Hystrix GatewayFilter

    允许将断路器功能添加到网关中,使服务免受级联故障??这个如何实现??, 提供服务降级处理

    pom.xml增加hystrix

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    </dependency>
    • 增加服务降级处理类
    • @RestController
      public class FallbackController {
      
          @GetMapping("/fallback")
          public Object fallback() {
              Map<String,Object> result = new HashMap<>();
              result.put("data",null);
              result.put("message","Get request fallback!");
              result.put("code",500);
              return result;
          }
      }
    • 在application-filter.yml中添加相关配置,当路由出错时会转发到服务降级处理的控制器上:
    spring:
      cloud:
        gateway:
          routes:
            - id: hystrix_route
              uri: http://localhost:9600
              predicates:
                - Method=GET
              filters:
                - name: Hystrix
                  args:
                    name: fallbackcmd
                    fallbackUri: forward:/fallback

    访问http://localhost:8081/user转发到http://localhost:9600/user, 404错误转发到服务降级处理的控制器上,返回降级处理的信息

    现在yml中配置测试不行,在java bean中配置可以返回

    @Bean
        public RouteLocator myRoutes(RouteLocatorBuilder builder){
            String httpUri = "http://httpbin.org:80";
            return builder.routes()  //route来做断言
                    .route(p -> p
                      .host("*.hystrix.com")
                      .filters(f->f.hystrix(config -> config
                                .setName("mycmd")
                                .setFallbackUri("forward:/fallback")))
                      .uri(httpUri))
                    .build();
        }

    测试: curl -H "Host:www.hystrix.com" localhost:8081?token=1
    返回: {"code":500,"data":null,"message":"Get request fallback!"}

    自定义过滤器

     springcloud内置了19种过滤器工厂,也可以自动逸过滤器。在spring Cloud Gateway中,过滤器需要实现GatewayFilter和Ordered2个接口

    /**
     * 自定义实现过滤器
     */
    public class RequestTimeFilter implements GatewayFilter, Ordered {
        private static final Log log = LogFactory.getLog(RequestTimeFilter.class);
        private static final String REQUEST_TIME_BEGIN = "reqTimeBegin";
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            // 操作前执行
            exchange.getAttributes().put(REQUEST_TIME_BEGIN, System.currentTimeMillis());
            return chain.filter(exchange).then(
                    // 操作后执行
                    Mono.fromRunnable(()->{
                        Long startTime = exchange.getAttribute(REQUEST_TIME_BEGIN);
                        if(startTime!=null){
                            log.info(exchange.getRequest().getURI().getRawPath() + ":" + (System.currentTimeMillis() - startTime) + "ms");
                        }
                    }));
        }
    
        /**
         * 给过滤器设定优先级别的,值越大则优先级越低
         * @return
         */
        @Override
        public int getOrder() {
            return 0;
        }
    }

    java中配置路由,使用自定义RequestTimeFilter

     @Bean
        public RouteLocator customerRouteLocator(RouteLocatorBuilder builder) {
            // @formatter:off
            return builder.routes()
                    .route(r -> r.path("/customer/**")
                            .filters(f -> f.filter(new RequestTimeFilter())
                                    .addResponseHeader("X-Response-Default-Foo", "Default-Bar"))
                            .uri("http://httpbin.org:80/get")
                            .order(0)
                            .id("customer_filter_router")
                    )
                    .build();
            // @formatter:on
        }

    测试:curl localhost:8081/customer/123

    控制台输出请求时间的日志:

    2020-10-14 22:17:48.528  INFO 107912 --- [ctor-http-nio-8] com.yy.gateway.RequestTimeFilter         : /customer:28ms 

    自定义过滤器工厂

    这样就能在配置文件中配置,自定义过滤器了

    public class RequestTimeGatewayFilterFactory extends AbstractGatewayFilterFactory<RequestTimeGatewayFilterFactory.Config> {
        private static final Log log = LogFactory.getLog(RequestTimeGatewayFilterFactory.class);
        private static final String REQUEST_TIME_BEGIN = "requestTimeBegin";
        private static final String KEY = "withParams";
    
        @Override
        public List<String> shortcutFieldOrder() {
            return Arrays.asList(KEY); //给config传参
        }
    
        public RequestTimeGatewayFilterFactory() {
            super(Config.class);
        }
    
        @Override
        public GatewayFilter apply(Config config) {
            return ((exchange, chain) -> {
    // 操作前执行
                exchange.getAttributes().put(REQUEST_TIME_BEGIN, System.currentTimeMillis());
                return chain.filter(exchange).then(
                        // 操作后执行
                        Mono.fromRunnable(()->{
                            Long startTime = exchange.getAttribute(REQUEST_TIME_BEGIN);
                            if(startTime!=null){
                                StringBuilder sb = new StringBuilder(exchange.getRequest().getURI().getRawPath())
                                        .append(":")
                                        .append(System.currentTimeMillis() - startTime)
                                        .append("ms");
                                if(config.isWithParams()){
                                    sb.append("params:").append(exchange.getRequest().getQueryParams());
                                }
                                System.out.println(sb.toString());
                                log.info(sb.toString());
                            }
                        }));
            });
        }
    
        /**
         * 接收filter参数
         */
        public static class Config{
            private boolean withParams;
    
            public boolean isWithParams() {
                return withParams;
            }
    
            public void setWithParams(boolean withParams) {
                this.withParams = withParams;
            }
        }
    }

    静态内部类类Config就是为了接收参数的,里边的变量名可以随意写,但是要重写List shortcutFieldOrder()这个方法‘

    注册RequestTimeGatewayFilterFactory  bean

        @Bean
        public RequestTimeGatewayFilterFactory elapsedGatewayFilterFactory() {
            return new RequestTimeGatewayFilterFactory();
        }

    application.yml 增加配置

    spring:
      profiles:
        active: elapse_route
    ---
    spring:
      cloud:
        gateway:
          routes:
            - id: elapse_route
              uri: http://httpbin.org:80/get #前面的路径替换即http://xxx:8081这段
              filters: #增加自定义Factory的filter
                - RequestTime=true
              predicates:
                - Method=GET
      profiles: elapse_route

    测试:curl localhost:8081/customer/123

    控制台输出请求时间的日志

    global filter

    全局过滤器,不用在配置文件中配置,作用在所有路由上

    自定义全局路由,不包含token的请求不转发,需要实现GlobalFilter和Ordered接口

    /**
     * 全局过滤器,如果不包含token参数直接返回不路由
     */
    public class TokenGlobalFilter implements GlobalFilter, Ordered {
        private static final Log log = LogFactory.getLog(TokenGlobalFilter.class);
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            String token = exchange.getRequest().getQueryParams().getFirst("token");
            if(token == null || token.isEmpty()){
                log.info("token is empty");
                exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
                return exchange.getResponse().setComplete();
            }
            return chain.filter(exchange);
        }
    
        @Override
        public int getOrder() {
            return -100;
        }
    }

    注入到容器

    /**
         * 将自定义全局过滤器注入到IOC容器
         * @return
         */
        @Bean
        public TokenGlobalFilter tokenGlobalFilter(){
            return new TokenGlobalFilter();
        }

    测试: curl localhost:8081

    控制台打印日志:2020-10-14 22:29:50.985  INFO 107912 --- [ctor-http-nio-2] com.yy.gateway.TokenGlobalFilter         : token is empty

    Spring Cloud Gateway限流

    RequestRateLimiterGatewayFilterFactory这个类,RequestRateLimiter 过滤器可以用于限流,使用RateLimiter实现来确定是否允许当前请求继续进行,如果请求太大默认会返回HTTP 429-太多请求状态。使用Redis和lua脚本实现了令牌桶的方式

    pom.xml引入redis依赖

     <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifatId>spring-boot-starter-data-redis-reactive</artifactId>
    </dependency>

    application.yml

    ---
    spring:
      cloud:
        gateway:
          routes:
            - id: limit_route
              uri: http://localhost:9600/ #前面的路径替换即http://xxx:8081这段
              predicates:
                - Method=GET
              filters:
                - name: RequestRateLimiter
                  args:
                    key-resolver: "#{@hostAddrKeyResolver}" #用于限流的键的解析器的 Bean 对象的名字。它使用 SpEL 表达式根据#{@beanName}从 Spring 容器中获取 Bean 对象
                    redis-rate-limiter.replenishRate: 1 # 令牌桶每秒填充平均速率, 即平均访问速率每秒
                    redis-rate-limiter.burstCapacity: 2 #令牌桶总容量 即每秒最大访问速率
      profiles: limit_route
      redis:
        host: 192.168.31.211
        port: 6379

    KeyResolver需要实现resolve方法,比如根据Hostname进行限流,则需要用hostAddress去判断。实现完KeyResolver之后,需要将这个类的Bean注册到Ioc容器中。 

    /**
     * 根据Hostname进行限流,则需要用hostAddress去判断
     */
    public class HostAddrKeyResolver implements KeyResolver {
        Log log = LogFactory.getLog(HostAddrKeyResolver.class);
        @Override
        public Mono<String> resolve(ServerWebExchange exchange) {return Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
        }
    }
    @Bean
        public HostAddrKeyResolver hostAddrKeyResolver(){
            return new HostAddrKeyResolver();
        }

    多次访问http://localhost:8081,会返回429错误

    可以用其他维度限流,例如用户维度

        @Bean
        KeyResolver userKeyResolver(){
            return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("user"));
        }

    三。服务的注册与发现

     上面都是用硬编码方式进行路由转发,gateway可以配合注册中心进行路由转发

    pom.xml

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

    application.yml

    server:
      port: 8081
    
    spring:
      application:
        name: sc-gateway-service
      cloud:
        gateway:
          discovery:
            locator:
              enabled: true
              lowerCaseServiceId: true
              
    eureka:
      client:
        service-url:
          defaultZone: http://localhost:8001/eureka/

    spring.cloud.gateway.discovery.locator.enabled为true,表明gateway开启服务注册和发现的功能,并且自动根据服务发现为每一个服务创建了一个router,这个router将以服务名开头的请求路径转发到对应的服务。

    spring.cloud.gateway.discovery.locator.lowerCaseServiceId是将请求路径上的服务名配置为小写(因为服务注册的时候,向注册中心注册时将服务名转成大写的了),

    测试:http://localhost:8081/springcloud-eureka-client/hello 就被转发到http://localhost:9400/helllo

    如果不想写服务名来路由,自定义请求路劲

    server:
      port: 8081
    spring:
      application:
        name: sc_gateway_client
      cloud:
        gateway:
          discovery:
            locator:
              enabled: false #gateway开启服务注册和发现的功能,并且自动根据服务发现为每一个服务创建了一个router
              lower-case-service-id: true #请求路径上的服务名配置为小写
          routes:
            - id: springcloud-eureka-client
              uri: lb://SPRINGCLOUD-EUREKA-CLIENT #服务名
              predicates:
                - Path=/client/**
              filters:
                - StripPrefix=1 # 在转发之前将/client去掉
    eureka:
      client:
        service-url:
          defaultZone: http://localhost:8001/eureka

    现在http://localhost:8081/client/hello 会被转发到http://localhost:9400/hello

     
  • 相关阅读:
    java 3大特性
    注解@JsonIgnore和注解@JsonIgnoreProperties
    spring bean 的介绍
    Spring注解的实现原理和Spring常用注解介绍
    Spring中 @Component @Controller @Repository @Service 注解
    Java Collection集合中List,Set,Queue以及Map的使用
    Spring Security PasswordEncoder 密码校验和密码加密
    java中System.out.print()与System.out.println()与System.out.printf()的差别
    Arrays.fill 数组填充工具类
    Hashmap与Hashset的区别
  • 原文地址:https://www.cnblogs.com/t96fxi/p/13817922.html
Copyright © 2020-2023  润新知