gateway 简介

如果没有gateway,网页需要分别访问 house contract服务。 各服务还需要分别实现安全,流量控制。

一,基础使用

网关的作用-》 路由; 安全; 流量控制

1.添加依赖

<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>

2.路由配置

2.1 通过配置文件配置路由

spring:
  application:
    name: gateway-server
  cloud:
    gateway:
      routes:
        - id: user
#          uri: http://localhost:8083  #直接指定服务器地址,不推荐
          uri: lb://user # 指定服务器注册实例名
          filters: #在请求被转向后端服务之前可以进行更改。
            - StripPrefix=1 # 去掉前缀过滤器
          predicates: #断言,只有满足断言的请求才会被放过去。
            - Path=/services/**  # 如果访问路径以services开头,则执行本路由规则

eureka: # 顶格:另外记得入口类添加@EnableEurekaClient
  instance:
    ip-address: 127.0.0.1
    prefer-ip-address: true
  client:
    fetch-registry: true
    register-with-eureka: true
    service-url:
      defaultZone: http://localhost:8761/eureka/

2.2 通过代码配置路由

一般不使用,了解就好。

@Configuration
public class GatewayConfig {

    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
        StripPrefixGatewayFilterFactory factory = new StripPrefixGatewayFilterFactory();
        StripPrefixGatewayFilterFactory.Config config = new StripPrefixGatewayFilterFactory.Config();
        config.setParts(1);
        GatewayFilter stripFilter = factory.apply(config);
        return builder.routes()
                .route("user", r-> r.path("/services/**")
                        .filters(gatewayFilterSpec -> gatewayFilterSpec.filter(stripFilter))
                        .uri("lb://user"))
                .build();
    }
}

2.3 概念解析:

路由中涉及到两个重要的新概念,断言和过滤器。

断言

断言就是判断,用来决定某个请求是不是应该由当前配置中的服务器来处理。我们最常用的就是Path断言,它是根据请求路径进行判断的。

过滤器

过滤器是对将要交给后端服务的请求进行进一步处理的地方。常用的是StripPrefix,一般和Path断言配合使用,用来将为了路由请求添加的路径去掉。

2.4 负载均衡

可以使用ribbon对路由进行负载均衡算法的自定义:

message:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

3. 实例讲解

假设我们有house服务,有如下配置和代码:

server:
  port: 8081 

spring:
  application:
    name: house

以上配置确定house服务的访问端口是8081。 以下代码决定了访问路径为 /house/list

@RestController
@RequestMapping("/house")
public class HouseController {
    
    @GetMapping("/list") 
    public Result<IPage<House>> list(@RequestParam Integer page, @RequestParam(required = false) Integer size) {
       int _size = size != null ? size : defaultSize;
        IPage<House> ipage = new Page<>(page, _size);
        houseService.page(ipage);
        return Result.ok(ipage);
    }

综合以上信息我们可以得出,通过house服务自己的端口访问list的url应该为:

http://localhost:8091/house/list?page=1&size=10

如果要通过网关访问,则再假设我们的gateway网关路由配置如下:

server:
  port: 8080
spring:
  application:
    name: gateway
  
  cloud:
    gateway:
      routes:
        - id: house
          #          uri: http://localhost:8083  #直接指定服务器地址,不推荐
          uri: lb://house # 指定服务器注册实例名
          filters: #对请求进行进一步处理
            - StripPrefix=1 # 去掉前缀过滤器
          predicates: #断言
            - Path=/order/**  # 如果访问路径以services开头,则执行本路由规则

则最终通过网关的访问路径为

http://localhsot:8080/house/house/list?page=1&size=10

当这样一个请求到达网关时具体的处理流程是怎样的呢?

  • 首先经过断言判断,因为请求路径 /house/house/list?page=1&size=10 满足 正则表达式 */house/** *,所以该请求将会被转到house服务处理
  • 过滤器StripPrefix=1,表示要将请求路径的前1级路径去掉,则经过处理后请求路径变为 /house/list?page=1&size=10
  • 因为已经没有别的过滤器,所以请求被转到house服务
  • house服务刚好有处理 house/list?page=1&size=10 请求的方法,则访问成功被处理。

二,Route Predicate 一些常见断言

  • Path Route Predicate
     - Path=/user/{id} # curl http://localhost:8080/services/another/1
    
  • Method Route Predicate 访问方法断言
    - Method=GET # curl http://localhost:8080/services/another/1
    
  • Header Route Predicate 请求头路由
      - Header=X-Request-Id,\d+ # curl http://localhost:8080/services/another/1 -H X-Request-Id:88
    
  • Cookie Route Predicate Cookie路由断言
      - Cookie=sessionId,test # curl http://localhost:8080/services/another/1 --cookie sessionId=test
    
  • Query Route Predicate 请求参数路由
      - Query=name,pu. # http://localhost:8080/services/another/5?name=pu1 参数以name命名,值以pu开始共三位 
    
  • RemoteAddr Route Predicate 远端地址路由断言
- RemoteAddr=192.168.1.1/24

  • Host Route Predicate 主机地址路由
- Host=**.baidu.com #
  • Before Route Predicate
- Before=2022-10-24T16:30:00+08:00[Asia/Shanghai]
  • After Route Predicate
- After=2019-09-24T16:30:00+08:00[Asia/Shanghai]
  • Between Route Predicate
- Between=2019-09-24T16:30:00+08:00[Asia/Shanghai],2023-09-24T16:30:00+08:00[Asia/Shanghai] 

三,Filter

部分过滤器

  • AddRequestParameter
  cloud:
    gateway:
      routes:
        - id: user
          uri: lb://user
          filters:
            - StripPrefix=1
            - AddRequestParameter=username,bw00 
  • StripPrefix
  cloud:
    gateway:
      routes:
        - id: user
          uri: lb://user
          filters:
            - StripPrefix=1
  • PrefixPath
cloud:
  gateway:
    routes:
      - id: user
        uri: lb://user
        filters:
          - StripPrefix=1
          - PrefixPath=/another
        predicates:
          - Path=/services/**
  • Hystrix 除了之前Hystrix在单独的微服务中使用以外,Hystrix还可以在网关中使用。先添加依赖:
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency> 

编写降级处理代码:

@RestController
@RequestMapping
public class IndexController {

    @GetMapping("/badthinghappend")
    public ResponseEntity fallback4Another() {
        return ResponseEntity.ok("badthinghappend ");
    }
}

然后通过配置确定当异常发生时将请求降级到上面的路径来处理:

          filters:
            - StripPrefix=1
            - name: Hystrix
              args:
                name: fallbackcmd
                fallbackUri: forward:/badthinghappend
  • RequestRateLimiter 请求速率限制是一个重要的技能点,需要重点学习下。因为速率限制需要redis记录一些数据,所以需要先添加依赖:
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>

然后还得添加redis配置:

spring:
  redis:
    host: localhost
    port: 6379

最后在配置中添加过相关配置

    filters:
      - name: RequestRateLimiter
        args:
          redis-rate-limiter.replenishRate: 1 # 放令牌的速率
          redis-rate-limiter.burstCapacity: 2 # 令牌桶最大放多少个
          redis-rate-limiter.requestedTokens: 20 #每次请求消耗几个令牌(3.0以上支持)
          key-resolver: "#{@ipKeyResolver}" # 根据userName来限流

上面的ipKeyResolver会引用项目中定义的实例,该实例需要在项目中定义,如下::

@Configuration
public class RedisRateLimiterConfig {

  @Bean
  KeyResolver ipKeyResolver() {
    return new KeyResolver(){

      @Override
      public Mono<String> resolve(ServerWebExchange exchange) {
        return Mono.just(exchange.getRequest().getRemoteAddress().getHostString());
      }
    };
  }
}

四,全局过滤器(Global Filters)

添加global Filter定义

@Slf4j
@Component
public class LogGlobalFilter implements GlobalFilter, Ordered {
    @Override
    public int getOrder() {
        return 10;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String path = exchange.getRequest().getPath().value();
        log.info("有人访问了: " + path);
        return chain.filter(exchange);
    }
}

在网关判断用户是否登录:

@Component
public class JWTFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String currentUrl = exchange.getRequest().getPath().value();
        if(!"/user/user/login".equals(currentUrl)) {//登录url不要求用户已经登录
            MultiValueMap<String, HttpCookie> cookies = exchange.getRequest().getCookies();
            if(cookies != null && cookies.getFirst("token") != null) {
                HttpCookie token = cookies.getFirst("token"); //获取jwt数据
                String jwt =  token.getValue();
                boolean verify = JWTUtil.verify(jwt, "1234".getBytes()); //校验jwt是否合法
                if(!verify) {
                    return needLogin(exchange);
                }

            }else {
                return needLogin(exchange);
            }
        }
        return chain.filter(exchange);
    }

    private Mono<Void> needLogin(ServerWebExchange exchange) {
        exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
        return exchange.getResponse().setComplete();
    }

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


五,CORS 配置

注意!!!,在网关添加了跨域后,后面微服务自己添加的跨域需要去掉,否则会出错。

spring:
  cloud:
    gateway:
      globalcors:
        cors-configurations:
          '[/**]':
            allowedOrigins: "*"
            allowedHeaders: "*"
            allowedMethods: "*"
            allowCredentials: true
            maxAge: 360000

六,常见错误

  1. 在gateway的pom文件或父pom文件中引入web依赖:
<!--gateway中不能引入!-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>