前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring Cloud Gateway-自定义GlobalFilter

Spring Cloud Gateway-自定义GlobalFilter

作者头像
Throwable
发布2020-06-23 16:27:52
4.4K0
发布2020-06-23 16:27:52
举报

前提

GlobalFilter的作用域是所有的路由配置,我们可以通过自定义GlobalFilter,做额外的扩展,用来实现一些全局的功能。

如何自定义GlobalFilter

org.springframework.cloud.gateway.filter.GlobalFilter的接口定义如下:

public interface GlobalFilter {

    Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
}    

我们只需要实现org.springframework.cloud.gateway.filter.GlobalFilter接口,并且把实现类注册到Spring的容器中即可,官方例子如下:

@Bean
@Order(-1)
public GlobalFilter a() {
    return (exchange, chain) -> {
        log.info("first pre filter");
        return chain.filter(exchange).then(Mono.fromRunnable(() -> {
            log.info("third post filter");
        }));
    };
}

@Bean
@Order(0)
public GlobalFilter b() {
    return (exchange, chain) -> {
        log.info("second pre filter");
        return chain.filter(exchange).then(Mono.fromRunnable(() -> {
            log.info("second post filter");
        }));
    };
}

@Bean
@Order(1)
public GlobalFilter c() {
    return (exchange, chain) -> {
        log.info("third pre filter");
        return chain.filter(exchange).then(Mono.fromRunnable(() -> {
            log.info("first post filter");
        }));
    };
}

实践

我们通过自定义实现一个GlobalFilter,实现类似Nginx的Access Log的功能,也就是对每一个请求都记录请求的一些核心参数和响应的一些核心参数。注意的是,我们实现的这个GlobalFilter是pre类型同时是post类型。

@Slf4j
@Component
public class AccessLogGlobalFilter implements GlobalFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getPath().pathWithinApplication().value();
        InetSocketAddress remoteAddress = request.getRemoteAddress();
        return chain.filter(exchange).then(Mono.fromRunnable(() -> {
            ServerHttpResponse response = exchange.getResponse();
            HttpStatus statusCode = response.getStatusCode();
            log.info("请求路径:{},远程IP地址:{},响应码:{}", path, remoteAddress, statusCode);
        }));
    }
}

上面的例子中,我们只打印了:

  • 请求的路径。
  • 请求的远程IP地址。
  • 响应码。
curl http://localhost:9090/order/remote

日志输出如下:

2019-05-04 19:13:19.101  INFO 25388 --- [ctor-http-nio-7] c.t.route.support.AccessLogGlobalFilter  : 请求路径:/order/remote,远程IP地址:/0:0:0:0:0:0:0:1:63861,响应码:200 OK

这样显然不够详细,我们接着尝试添加下面的参数:

  • 如果是GET请求,则提取它的Query参数,如果是POST请求,则尝试读取RequestBody的参数,打印请求的参数。
  • 请求方法。
  • 目标URI。

修改代码如下:

@Slf4j
@Component
public class AccessLogGlobalFilter implements GlobalFilter {

    private final ObjectMapper mapper = new ObjectMapper();
    private final DataBufferFactory dataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT);

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getPath().pathWithinApplication().value();
        HttpMethod method = request.getMethod();
        StringBuilder builder = new StringBuilder();
        URI targetUri = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR);
        if (HttpMethod.GET.equals(method)) {
            MultiValueMap<String, String> queryParams = request.getQueryParams();
            try {
                builder.append(mapper.writeValueAsString(queryParams));
            } catch (JsonProcessingException e) {
                log.error(e.getMessage(), e);
            }
        } else if (HttpMethod.POST.equals(method)) {
            Flux<DataBuffer> body = request.getBody();
            ServerHttpRequest serverHttpRequest = request.mutate().uri(request.getURI()).build();
            body.subscribe(dataBuffer -> {
                InputStream inputStream = dataBuffer.asInputStream();
                try {
                    builder.append(StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8));
                } catch (IOException e) {
                    log.error(e.getMessage(), e);
                }
            });
            // 重写请求体,因为请求体数据只能被消费一次
            request = new ServerHttpRequestDecorator(serverHttpRequest) {
                @Override
                public Flux<DataBuffer> getBody() {
                    return Flux.just(dataBufferFactory.wrap(builder.toString().getBytes(StandardCharsets.UTF_8)));
                }
            };
        }
        InetSocketAddress remoteAddress = request.getRemoteAddress();
        return chain.filter(exchange.mutate().request(request).build()).then(Mono.fromRunnable(() -> {
            ServerHttpResponse response = exchange.getResponse();
            HttpStatus statusCode = response.getStatusCode();
            log.info("请求路径:{},远程IP地址:{},请求方法:{},请求参数:{},目标URI:{},响应码:{}",
                    path, remoteAddress, method, builder.toString(), targetUri, statusCode);
        }));
    }
}
curl -X POST -d "name=doge" localhost:9090/order/remote

由于下游服务只接受GET方法请求,网关打印日志如下:

请求路径:/order/remote,远程IP地址:/0:0:0:0:0:0:0:1:65158,请求方法:POST,请求参数:name=doge,目标URI:http://localhost:9091/order/remote,响应码:405 METHOD_NOT_ALLOWED

小结

其实,GlobalFilter既然会对所有的路由配置都生效,我们扩展它实现的功能是一般全局的功能。上面的例子中涉及到重新装饰请求对象,解析请求参数的操作会有一定的性能损耗,具体要看实际的应用场景。

(c-1-d e-a-20190505)

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019年5月5日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前提
  • 如何自定义GlobalFilter
  • 实践
  • 小结
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档