SpringCloud之Gateway,服务网关

当项目中服务细分至很多时,那么就需要一个统一的网关来控制请求、响应,在微服务框架中,Springcloud Gateway为了提升网关的性能,SpringCloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty。

代码地址:https://gitee.com/webprogram/springcloud_learn

Spring Cloud Gateway 的目标,不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流

Gateway服务网关

网关功能:

  • 身份认证和权限校验
  • 服务路由、负载均衡
  • 请求限流

网关技术实现:

  • Gateway
  • zuul

zuul是基于servlet的实现,属于阻塞式编程,而springcloud Gateway则是基于spring5中提供的webFlux,属于响应式编程的实现,具备更好的性能

搭建网关服务

搭建网关步骤

  1. 关键新的module,引入Gateway依赖和nacos服务发现依赖
   <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
  1. 路由配置及nacos注册
spring:
  application:
    name: gateway # 服务名称
  cloud:
    nacos:
      server-addr: 127.0.0.1:8848 # nacos服务地址
    gateway:
      routes: # 配置路由
        - id: user-service # 路由id,自定义,只要唯一即可
#          uri: http://127.0.0.1:8082 # 路由的目标地址
          uri: lb://userservice # 路由的目标地址 lb表示负载均衡://服务名
          predicates: # 路由断言即判断请求是否符合路由规则
            - Path=/user/** # 路径匹配规则,只要/user开头才合规

网关可配置内容包括:

  • 路由id:唯一标识
  • uri:路由目的地,支持lb和http两种
  • predicates:路由断言,判断请求是符合要求,符合才进行转发
  • filters:路由过滤器,处理请求或响应

通过Gateway访问userservice服务,127.0.0.1:10010/user即可访问到用户服务

服务网关结构图

在这里插入图片描述

路由断言

  • 在配置文件中写的断言规则只是字符串,这些字符串被Predicate factory读取并处理,转变为路由判断的条件
  • SpringCloud Gateway提供断言工厂有十几个

例如:Path=/user/**是按照路径匹配,这个规则是由PathRoutePredicateFactory来处理

11中基本的predicate工厂

名称说明示例
After某个时间点后的请求- After=2021-09-13T17:32:32.345-07:00(America/Denver)
Before某个时间点之前的请求- Beforer=2021-09-13T17:32:32.345+07:00(America/Denver)
Between某两个时间点之前的请求- between=2021-09-13T17:32:32.345+07:00(America/Denver),2021-09-13T17:32:32.345+07:00(America/Denver)
Cookie请求中必须包含cookie- Cookie=XXX
Header请求必须包含header-Header=token,X-Request
Host请求必须是访问某个host- Host=**,www.baidu.com
Method请求方式- Method=GET,POST
Path请求路径规则-Path=/test/**
Query请求必须包含指定参数-Query=name,XXX
RemoteAddr请求者的ip必须是指定范围- RemoteAddr=192.168.1.1/24
Weight权重处理

详细用法参考:https://www.cnblogs.com/wgslucky/p/11396579.html

官网解释:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gateway-request-predicates-factories

路由过滤器

Gateway Filter是网关中提供的一种过滤器,可以进入网关的请求和微服务返回的响应做处理:

在这里插入图片描述

Spring提供了31中不同的路由过滤工厂,例如:

名称说明
AddRequestHeader给当前请求添加一个请求头
RemoveRequestHeader移除请求中的请求头
AddResponseHeader给响应添加响应头
RemovResponseHeader移除响应的响应头
RequestRateLimiter限制请求的流量

官网解释:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gatewayfilter-factories

样例:给userservice添加请求头

spring:
  application:
    name: gateway # 服务名称
  cloud:
    nacos:
      server-addr: 127.0.0.1:8848 # nacos服务地址
    gateway:
      routes: # 配置路由
        - id: user-service # 路由id,自定义,只要唯一即可
#          uri: http://127.0.0.1:8082 # 路由的目标地址
          uri: lb://userservice # 路由的目标地址 lb表示负载均衡://服务名
          predicates: # 路由断言即判断请求是否符合路由规则
            - Path=/user/** # 路径匹配规则,只要/user开头才合规
          filters: # 过滤器
            - AddRequestHeader=Message,spring cloud is good # 添加请求头
        - id: order-service
          uri: http://127.0.0.1:8082
          predicates:
            - Path=/order/**
      default-filters: # 默认过滤器,会对所有路由起作用
        - AddRequestHeader=Message,spring cloud is good

在controller读取请求头做测试:

 @GetMapping("/{id}")
    public UserEntity getUser(@PathVariable String id, @RequestHeader("Message") String message){
        // 打印出请求头信息
        logger.info("请求头:"+ message);
    }

全局过滤器

GlobaFilter的作用是处理一切进入网关的请求和微服务响应,与GatewayFilter的作用一样,区别在于GatewayFilter通过配置定义,处理逻辑是固定的,而GlobalFilter的逻辑需要自己实现代码

定义的方式

实现GlobalFilter接口

/**
* 处理当前请求,
* @param exchange 请求上下文,里面可获取request、response等信息
* @param chain 用来把请求委托给下一个过滤器
* @return Mono<Void> 返回标识当前过滤器业务结束
*/
public interface GlobalFilter {
    Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
}

样例:定义全局拦截器,判断请求参数是否合规

  • 参数是否有authorization
  • authorization是否为admin

满足则放行,否则回写响应401

//@Order(-1)
@Component
public class AuthorizationFilter implements GlobalFilter, Ordered {
    private static final Logger logger = LoggerFactory.getLogger(AuthorizationFilter.class);
    /**
     * @description: 校验的用户
     */
    private static final String ADMIN_NAME = "admin";
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 1. 获取请求参数
        ServerHttpRequest request = exchange.getRequest();
        MultiValueMap<String, String> queryParams = request.getQueryParams();
        // 2.获取参数值
        String authorization = queryParams.getFirst("authorization");
        // 3. 判断是否为admin
        if (!ADMIN_NAME.equals(authorization)) {
            // 4. 不符合条件,回写响应
            ServerHttpResponse response = exchange.getResponse();
            Map<String, Object> responseData = Maps.newHashMap();
            responseData.put("code", 401);
            responseData.put("message", "非法请求");
            responseData.put("cause", "user is not admin");
            try {
                // 将信息转换为 JSON
                ObjectMapper objectMapper = new ObjectMapper();
                byte[] data = objectMapper.writeValueAsBytes(responseData);
                // 输出错误信息到页面
                DataBuffer buffer = response.bufferFactory().wrap(data);
                response.setStatusCode(HttpStatus.UNAUTHORIZED);
                response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
                return response.writeWith(Mono.just(buffer));
            } catch (Exception e) {
                logger.error(e.getMessage(), e);
            }
        }
        // 5.符合规则,放过
        return chain.filter(exchange);

    }

    /**
     * @description: 定义过滤器执行顺序,数值越小,优先级越高
     */
    @Override
    public int getOrder() {
        return -1;
    }
}

启动项目,访问路径http://localhost:10010/user/1,报错{“code”:401,“cause”:“user is not admin”,“message”:“非法请求”}

重新访问:http://localhost:10010/user/1?authorization=admin,成功获取结果{“userId”:“002”,“name”:“李四”}

在实际项目中会有多个过滤器,如何控制它们的执行顺序呢?(参考上例代码)

  • @Order(XX) 在过滤器类上添加该注解

    XX为具体数值,数值越小优先级越高

  • 实现Ordered接口,重写getOrder()

   /**
       * @description: 定义过滤器执行顺序,数值越小,优先级越高
       */
      @Override
      public int getOrder() {
          return -1;
      }

上面两种方式等效,选择其中一个使用即可

过滤器执行顺序

请求进入网关会碰到三类过滤器:

  • 当前路由过滤器
  • DefaultFilter
  • GlobalFilter

请求路由后,会将当前路由过滤器和DefaultFilter、GlobalFilter合并到一个过滤器链(集合)中,排序后依次执行每个过滤器

在这里插入图片描述

三者不是同类型的过滤器,如何合并?

当前路由过滤器和DefaultFilter实际就是GatewayFilter,而GlobalFilter则会通过GatewayFilterAdapter适配器转换为GatewayFilter,进而将三者合并到一个过滤器链中

总结

  • 每个过滤器必须制定一个int型额order,order越小优先级越高
  • GlobalFilter通过实现Ordered接口,或者@Order注解来自定义order值
  • 路由过滤器和DefaultFilter的order由spring指定,默认从1开始递增
  • 当过滤器order值一样时,会按照DefaultFilter > 路由过滤器 > GlobalFilter的顺序执行

参考一下源码查看:
org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator中getFilters方法,先加载defaultFilter,然后加载某个route的filters,最后合并

private List<GatewayFilter> getFilters(RouteDefinition routeDefinition) {
        List<GatewayFilter> filters = new ArrayList();
        if (!this.gatewayProperties.getDefaultFilters().isEmpty()) {
            filters.addAll(this.loadGatewayFilters("defaultFilters", new ArrayList(this.gatewayProperties.getDefaultFilters())));
        }

        if (!routeDefinition.getFilters().isEmpty()) {
            filters.addAll(this.loadGatewayFilters(routeDefinition.getId(), new ArrayList(routeDefinition.getFilters())));
        }

        AnnotationAwareOrderComparator.sort(filters);
        return filters;
    }

org.springframework.cloud.gateway.handler.FilteringWebHandler中handle方法,加载全局过滤器GlobalFilter,然后和前面的过滤器合并,根据order排序,组成过滤器链

public Mono<Void> handle(ServerWebExchange exchange) {
        Route route = (Route)exchange.getRequiredAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
        List<GatewayFilter> gatewayFilters = route.getFilters();
        List<GatewayFilter> combined = new ArrayList(this.globalFilters);
        combined.addAll(gatewayFilters);
        AnnotationAwareOrderComparator.sort(combined);
        if (logger.isDebugEnabled()) {
            logger.debug("Sorted gatewayFilterFactories: " + combined);
        }

        return (new FilteringWebHandler.DefaultGatewayFilterChain(combined)).filter(exchange);
    }

跨域

域名不一致就是跨域,包括:

  • 域名不同
  • 端口不同

跨域问题:浏览器禁止请求的发起者与服务者发生跨域的ajax请求,请求会被浏览器拦截

解决方案:CORS

配置如下:

spring:
  cloud:
    gateway:
      globalcors: # 全局跨域处理
        add-to-simple-url-handler-mapping: true # 解决options请求被拦截的问题
        cors-configurations:
          '[/**]':
            allowedOrigins: # 允许那些网站跨域
              - "http://localhost:8090"
              - "http://www.baidu.com"
            allowedMethods: # 允许跨域ajax的请求方式
              - "GET"
              - "POST"
              - "PUT"
            allowedHeaders: "*" # 允许跨域的请求头信息
            allowedCredentials: true # 是否允许携带Cookie
            maxAge: 36000 # 本次跨域检测的有效期

更多配置请参考官方文档

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值