插一嘴

  • 个人理解请求先进网关,网关根据url转发给某个服务,之后请求进负债均衡器,负债均衡器将请求转发给该服务的具体某个实例
  • 网关无论是请求还是返回都会经过网关

    依赖

  • Gateway 自己使用了 netty 实现了 Web 服务,此处『不需要引入 Spring Web』如果引入了,反而还会报冲突错误,无法启动。

    1. <dependency> <!-- 会自动引入 Ribbon 依赖。Gateway 整合了 Ribbon 。-->
    2. <groupId>org.springframework.cloud</groupId>
    3. <artifactId>spring-cloud-starter-gateway</artifactId>
    4. </dependency>

    配置路由(断言)

  • Gateway可以通过配置文件进行配置,也可以通过bean方式配置

  • 路由转发的路径不会变,变得只会是ip和端口(即变得只是服务),如原始请求是127.0.0.1:8080/api1,我们对api1进行监听然后转发给127.0.0.1:8082 请求地址变为127.0.0.1/8082/api1
  • 路由配置除了根据路径规则配置,还可以通过cookies,headers,方法名,请求时间等进行路由,总之非常自由,详见链接

    配置文件方式配置

  • gateway转发得url有三种形式:

    • websocket(ws):uri: ws://localhost:9000
    • http:uri: http://localhost:8130
    • 注册中心服务名(lb):uri: lb://brilliance-consumer ```properties

      下面的规则是: 请求/163 路径时,Gateway 将会跳转到163.com,因为163.com/163路径不存在,163.com进行了处理:跳转到首页

      spring.application.name=gateway-server #这个跟编码方式不知道怎么对应 spring.cloud.gateway.routes[0].id=163_route #路由名称 spring.cloud.gateway.routes[0].uri=http://www.163.com #转发路径 spring.cloud.gateway.routes[0].predicates[0]=Path=/163 #监听的路径,支持表达式,如/XXX/**

//更多配置规则 spring.cloud.gateway.routes[1].id= spring.cloud.gateway.routes[1].uri=<目标 URL> spring.cloud.gateway.routes[1].predicates[0]=Path=<匹配规则>

  1. ```yaml
  2. spring:
  3. application:
  4. name: gateway-server
  5. cloud:
  6. gateway:
  7. routes:
  8. - id: 163_route
  9. uri: http://www.163.com
  10. predicates:
  11. - Path=/163

bean编码方式配置

@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
 return     //下面的路由规则是:  输入/jd,gateway 会将请求导向到 http://www.jd.com            builder.routes().route(r>r.path("/jd").uri("http://www.jd.com").id("jd_route")).
         build();
    }

与服务注册发现组件结合

  • 使用如下配置启用服务注册发现组件结合。然后路由规则得url使用**lb**形式

    spring.cloud.gateway.discovery.locator.lowerCaseServiceId=true
    spring.cloud.gateway.discovery.locator.enabled=true
    

    跨域配置

  • 推荐使用nginx的方式

    spring:
    cloud:
      gateway:
        globalcors:
          corsConfigurations:
            '[/**]':
              allowedOriginPatterns: "*"
              allowed-methods: "*"
              allowed-headers: "*"
              allow-credentials: true
              exposedHeaders: "Content-Disposition,Content-Type,Cache-Control"
    

断言Predicate

  • Spring Cloud Gateway 是由很多的路由断言工厂组成。当 HTTP Request 请求进入 Spring Cloud Gateway 的时候,网关中的路由断言工厂就会根据配置的路由规则,对 HTTP Request 请求进行断言匹配。匹配成功则进行下一步处理,否则,断言失败直接返回错误信息。
    • 早期得断言配置都通过**@Bean**配置,后面才推出了配置文件配置配置方法
    • 断言即路由的实现方式
  • java8 中的断言函数。Spring Cloud Gateway 中的断言函数输入类型是 Spring 5.0 框架中的 ServerWebExchange。Spring Cloud Gateway 中的断言函数允许开发者去定义匹配来自 Http Request 中的任何信息,比如请求头和参数等。

    RewritePath过滤器

  • 过滤器得作用是可以修改url。如:将/xxx/yyy/zzz/hello修改为/hello再进行传递。

  • 过滤器分为2种类型:

    • Gateway Filter 和 Global Filter 。过滤器 Filter 将会对请求和响应进行修改处理。

      配置过滤器

  • 在路由规则后添加过滤器规则,即过滤器是配合路由断言一起使用。配置上面看起来就是在路由配置得基础上增加了**filters**

  • 路由配置的表达式:

    • 命名分组:(?<name>正则表达式) 与普通分组一样的功能,并且将匹配的子字符串捕获到一个组名称或者编号名称中。在获得匹配结果时,可通过分组名进行获取。
    • 引用捕获文本${name} 将名称为 name 的命名分组所匹配到的文本内容替换到此处。$\{segment}:将前面捕获到 segment 中的文本置换到此处,注意,\ 的出现是由于避免 YAML 认为这是一个变量而使用的转义字符。
      spring.cloud.gateway.routes[0].id=demo_route
      spring.cloud.gateway.routes[0].uri=http://localhost:8080
      spring.cloud.gateway.routes[0].predicates[0]=Path=/xxx-service/**
      spring.cloud.gateway.routes[0].filters[0]=RewritePath=/xxx-service/(?<segment>.*), /${segment}
      

      配置普通过滤器

  • 普通过滤器就是需要自己指定过滤哪个路由,全局过滤器则对所有路由生效。

  • 主要普通过滤器一般要设置优先级高于全局过滤器,否则可能失效
    • 定义好过滤器,**.routes.filters=过滤器类名**即可走自定义过滤器
  • 过滤器继承AbstractGatewayFilterFactory使用@Component标注,具体的看代码,我们可以自定义实现
  • 我们可以定义一个**ServerHttpRequestDecorator**并重写其方法修改请求头,见**ruoyi-xss过滤器** ```java @Component public class BlackListUrlFilter extends AbstractGatewayFilterFactory { @Override public GatewayFilter apply(Config config) {

      return (exchange, chain) -> {
         // 结束过滤写入返回信息,还有重载方法可以写入状态码
          return ServletUtils.webFluxResponseWriter(exchange.getResponse(), "请求地址不允许访问");
       //继续执行过滤
          return chain.filter(exchange);
      };
    

    }

    public BlackListUrlFilter() {

      super(Config.class);
    

    }

    public static class Config {

            // 可以定义一些自定义配置,如黑名单列表
    

    }

}

<a name="LoG3j"></a>
## 定义全局Filter

- **自定义全局过滤器要实现 **`**GlobalFilter**`** 接口。全局过滤器不需要指定对哪个路由生效,它对所有路由都生效。**
- **还可以单独对过滤器方法定义为bean实现全局过滤器**
   - **全局过滤器可以配置多个,他们会自动被gateway使用到。**
      - **可以在每个全局过滤器头部添加**`**@Order**`
- **过滤器在路由转发之前进行并生效(过滤器也可以始终放行)  如使用全局过滤器实现认证与鉴权**
```java
public class XxxGlobalFilter implements GlobalFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
      // 逻辑代码 ...

      if (...) {
        // 符合条件就继续走下一个过滤器,直到走到路由转发处为止,如果不符合,则不进行路由转发
        return chain.filtergyiokvqi(exchange);
        //return chain.filter(exchange);  //还有一种继续执行过滤的写法是这个,不清楚区别
      } else {
        // 否则流程终止,拒绝路由。
        exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
        return exchange.getResponse().setComplete();
      };
    }
}
@Bean
public GlobalFilter xxxGlobalFilter() {
    return new XxxGlobalFilter();
}
@Bean
@Order(-100) // 注解可以去控制全局过滤器的先后顺序。值越小,优先级越高。
public GlobalFilter xxxGlobalFilter {
    return ...;
}
//也可以使用@Component配置,同时实现2个接口,更加方便
public class XxxGlobalFilter implements GlobalFilter,Ordered {
    @Override
    public Mono<Void> filter(){...}
    @Override
    public int getOrder() {
        return 0;
    }
}

获得/修改请求信息

  • 可以通过网关作所有微服务的统一检验,鉴权的中心
  • 百度得知:微服务架构下检验一般2种形式:
    • 每个微服务自己进行校验
    • 统一由网关进行校验:网关校验无法做到精细的控制,并且性能比服务里校验更差。但是更简单,如果服务与模块不是很多时,可以采用网关校验。服务与模块多需要频繁检验时推荐服务内部进行,可以抽取出一个公共的服务进行校验
  • 有的观点认为网关仅仅应该用作转发,不应该用于进行校验等业务功能
  • Spring Cloud Gateway(读取、修改 Request Body详解)
  • ServerHttpRequest是一个类似HttpServletRequest的请求信息,ServerWebExchange源码解读 ```java ServerHttpRequest request = exchange.getRequest();

log.info(“{}”, request.getMethod()); //获得请求类型 log.info(“{}”, request.getURI());//获取url log.info(“{}”, request.getPath()); log.info(“{}”, request.getQueryParams()); // 获取Get请求时参数

//解析请求头 HttpHeaders headers = request.getHeaders(); for (Map.Entry> header:headers.entrySet()) { String key = header.getKey(); List values = header.getValue(); }

    //简写为:
    request.getHeaders().keySet().forEach(key -> {
        log.info("{}: {}", key, request.getHeaders().get(key))
    });

// 添加请求头 request.mutate().header(“age”, “20”).build();

<a name="Zocri"></a>
## 返回JSON格式信息

- **未通过过滤器,过滤器返回的失败信息是以**`**http**`**的错误码形式返回的,如**`**4xx**``**5xx**`**的格式**。如果我们需要指定自定义的信息规则,如`成功时返回200`,失败时返回自定义错误码和错误信息...
```java
String jsonStr = "{\"status\":\"-1\", \"msg\":\"error\"}";
byte[] bytes = jsonStr.getBytes(StandardCharsets.UTF_8);
DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);
return exchange.getResponse().writeWith(Flux.just(buffer));

过滤器返回后执行增强代码

  • 过滤器除了在请求执行前进行是否继续路由的检验,还可以在请求执行并返回结果时也执行一些代码
    • 要在返回后继续执行代码,在chain.filter(exchange)后继续执行.then()
      @Override
      public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
      long before = System.currentTimeMillis();
       log.info("现在是目标微服务【执行前】");
      return chain.filter(exchange)
         .then(Mono.fromRunnable(() -> {
             long after = System.currentTimeMillis();
              log.info("现在是目标微服务【执行后】");
             System.out.println("请求执行耗时: " + (after - before) / 1000.0  + " 秒");
         }));
      }
      

      统一异常处理