微服务门神-Gateway过滤器Filter

目录

引言

概念

局部过滤器

简单无法参数过滤器 

带参数过滤器

全局过滤器

转视频版


引言

书接上篇:微服务门神-Gateway路由,讲完了解Gateway路由规则之后,接下来看下Gateway第二核心组件:Filter

概念

过滤器就是在请求的传递过程中,对请求和响应做一些功能操作。

在Gateway中, Filter的生命周期只有两个:“pre” 和 “post”。

  • PRE: 前置过滤,这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。

  • POST:后置过滤,这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。

Gateway中,Filter的作用范围两种:

  • GatewayFilter:局部过滤器,应用到单个路由或者一个分组的路由上。

  • GlobalFilter:全局过滤器,应用到所有的路由上

当网关启动,gateway会对所有注册的过滤器进行整合,安装约定的规则与序号进行排序,当请求进入网关,网关会对全局,局部过滤器依照排好的规则一一筛选,满足条件放行,不满足条件拒绝。

局部过滤器

局部过滤器是针对单个路由的过滤器,在SpringCloud Gateway中内置了很多不同类型的网关路由过滤器,有兴趣同学可以自行了解,这里太多了,我们就不一一讲解,我们主要来讲一下自定义路由过滤器。

需求:统计订单服务调用耗时。

示意图

简单无法参数过滤器 

1>编写Filter类,需要实现GatewayFilter接口,重写filter方法

package com.langfeiyes.filters;

import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 * 计算时间过滤器
 */
public class TimeGatewayFilter implements GatewayFilter, Ordered  {
    /**
     * 过滤逻辑
     * @param exchange 转换器--封装了来自请求中所有信息,比如:请求方法,请求参数,请求路径,请求头,cookie等
     * @param chain 过滤器链--使用责任链模式,决定当前过滤器是放行还是拒绝
     * @return Mono 响应式编程的返回值规范,一般后置拦截才会用
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //统计时间:进来(pre前置)记当前时间, 出去(post后置)记当前时间,2者差值就是运行时间
        long begin = System.currentTimeMillis();
        System.out.println("前置当前时间:" + begin);
        return chain.filter(exchange).then(Mono.fromRunnable(()->{
            long end = System.currentTimeMillis();
            System.out.println("后置当前时间:" + end);
            System.out.println("两者时间差:" + (end-begin));
        }));
    }
    @Override
    public int getOrder() {
        return 0;
    }
}

2>往Gateway中注册自定义的filter类

/**
 * 注册时间统计过滤器
 * 要求:
 * 1>过滤器工厂名字必须按照规定格式,否则报错。
 *   格式: XxxGatewayFilterFactory, 其中Xxx 就是在yml中filters配置过滤器
 * 2>继承AbstractGatewayFilterFactory父类,明确指定一个泛型
 *   改泛型用于接受yml-filters配置的过滤器值
 */
@Component
public class TimeGatewayFilterFactory extends AbstractGatewayFilterFactory<Object> {
    @Override
    public GatewayFilter apply(Object config) {
        return new TimeGatewayFilter();
    }
}

3>配置使用自定义filter类

server:
  port: 9000
spring:
  application:
    name: shop-gateway
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    gateway:
      discovery:
        locator:
          enabled: true 
      routes:
        - id: product_route
          uri: lb://product-service 
          predicates:
            - Path=/product-serv/**
          filters:
            - StripPrefix=1
        - id: order_route
          uri: lb://order-service 
          predicates:
            - Path=/order-serv/**
          filters:
            - StripPrefix=1
            - Time

 4>测试,访问订单服务save方法

前置当前时间:1666065937548
后置当前时间:1666065943803
两者时间差:6255

这里关注

放行逻辑:

return chain.filter(exchange)

 后置逻辑:

chain.filter(exchange).then(Mono.fromRunnable(()->{
           //后置逻辑
        }))

带参数过滤器

如果想让过滤器实现参数配置,怎么办?

filters:
    - StripPrefix=1
    - Time=111,222

1>定制接受参数的对象

@Getter
@Setter
public class TimeGatewayFilterParam {
    private String value1;
    private String value2;
}

2>修改TimeGatewayFilter 过滤器类,获取Gateway传入的参数对象

/**
 * 计算时间过滤器
 */
public class TimeGatewayFilter implements GatewayFilter , Ordered  {

    private TimeGatewayFilterParam value;
    public TimeGatewayFilter(TimeGatewayFilterParam config){
        this.value = config;
    }

    /**
     * 过滤逻辑
     * @param exchange 转换器--封装了来自请求中所有信息,比如:请求方法,请求参数,请求路径,请求头,cookie等
     * @param chain 过滤器链--使用责任链模式,决定当前过滤器是放行还是拒绝
     * @return Mono 响应式编程的返回值规范,一般后置拦截才会用
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //统计时间:进来(前置)记当前时间, 出去(后置)记当前时间,2者差值就是运行时间
        long begin = System.currentTimeMillis();
        System.out.println(value.getValue1() + "前置当前时间:" + begin);
        return chain.filter(exchange).then(Mono.fromRunnable(()->{
            long end = System.currentTimeMillis();
            System.out.println(value.getValue2() + "后置当前时间:" + end);
            System.out.println("两者时间差:" + (end-begin));
        }));
    }
    @Override
    public int getOrder() {
        return 0;
    }
}

 3>在TimeGatewayFilterFactory 类中编写Gateway传入的参数对象逻辑

@Component
public class TimeGatewayFilterFactory extends AbstractGatewayFilterFactory<TimeGatewayFilterParam> {

    //指定获取配置参数之后封装成啥对象
    public TimeGatewayFilterFactory(){
        super(TimeGatewayFilterParam.class);
    }
    //将数据添加到哪个属性上
    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList("value1", "value2");
    }

    @Override
    public GatewayFilter apply(TimeGatewayFilterParam config) {
        return new TimeGatewayFilter(config);
    }
}

 4>测试,访问订单服务save方法

全局过滤器

全局过滤器作用于所有路由, 无需配置。通过全局过滤器可以实现对权限的统一校验,安全性验证等功能。

SpringCloud Gateway内部也是通过一系列的内置全局过滤器对整个路由转发进行处理如下:

 需求: 实现统一鉴权的功能,我们需要在网关判断请求中是否包含token且,如果没有则不转发路由,有则执行正常逻辑。

1>编写全局过滤类

package com.langfeiyes.filters;

import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.List;

@Component
public class AuthGlobalFilter  implements GlobalFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {


        String token = exchange.getRequest().getQueryParams().getFirst("token");
        ServerHttpResponse response = exchange.getResponse();
        if(!StringUtils.hasText(token)){
            //没登录
            System.out.println("没有登录");
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            //返回401状态
            //return response.setComplete();

            //但是一般访问接口应该返回json格式
            String ret = "{\"code\":401, \"msg\":\"没有登录\", \"data\":\"\"}";
            byte[] bits = ret.getBytes(StandardCharsets.UTF_8);
            //把字节数据转换成一个DataBuffer
            DataBuffer buffer = response.bufferFactory().wrap(bits);
            return response.writeWith(Mono.just(buffer));
        }

        return chain.filter(exchange);
    }
}

注意:如果中文乱码,需要加上响应头

HttpHeaders httpHeaders = response.getHeaders();
//返回数据格式
httpHeaders.setContentType(MediaType.APPLICATION_JSON);

 2>启动并测试

 

到这,本篇就结束了,欲知后事如何,请听下回分解~

转视频版

看文字不过瘾可以切换视频版:SpringCloud Alibaba极简入门

猜你喜欢

转载自blog.csdn.net/langfeiyes/article/details/129032024