文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Spring Cloud Gateway如何优雅地进行feign调用

2023-09-12 16:33

关注

之前写过一篇文章,介绍微服务场景下的权限处理,方案如下:

微服务鉴权

在实践中,上面的网关选型为Spring Cloud Gateway,所以这里就存在一个问题,即网关如何调用用户服务进行鉴权的问题。

在微服务场景下,服务间的调用可以通过feign的方式,但这里的问题是,网关是reactor模式,即异步调用模式,而feign调用为同步方式,这里直接通过feign调用会报错。

那Spring Cloud Gateway如何优雅的进行feign调用呢,今天的文章带大家来看下。

1 Spring Cloud Gateway直接进行feign调用

不做特殊处理,在Spring Cloud Gateway中直接进行feign调用的代码如下(这里贴出整个鉴权的GatewayFilterFactory代码以方便理解):

@SuppressWarnings("rawtypes")@Component@Slf4jpublic class ApiAuthGatewayFilterFactory extends AbstractGatewayFilterFactory {    private static final String USER_HEADER_NAME = "User-Info";    @Autowired    private UserClient userClient;    public ApiAuthGatewayFilterFactory() {        super(Config.class);    }    @Override    public List shortcutFieldOrder() {        return Collections.singletonList("checkAuth");    }    @Override    public GatewayFilter apply(Config config) {        return (exchange, chain) -> {            if (config.checkAuth) {                String cookie = exchange.getRequest().getHeaders().getFirst("Cookie");                String url = exchange.getRequest().getPath().toString();                String httpMethod = exchange.getRequest().getMethodValue();                // 这里调用了feign接口,到用户模块进行鉴权                ResultResponse resultResponse = userClient.checkPermission(url, httpMethod, cookie);                if (resultResponse.isSuccess()) {                    // 鉴权通过,则将用户信息放入header中,传到下游服务                    ServerHttpRequest request = exchange.getRequest().mutate().header(USER_HEADER_NAME, JSON.toJSONString(resultResponse.getData())).build();                    return chain.filter(exchange.mutate().request(request).build());                } else {                    return Mono.defer(() -> {                        exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);                        final ServerHttpResponse response = exchange.getResponse();                        byte[] bytes = JSON.toJSONString(resultResponse).getBytes(StandardCharsets.UTF_8);                        DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);                        return response.writeWith(Flux.just(buffer));                    });                }            } else {                return chain.filter(exchange);            }        };    }    @NoArgsConstructor    @Getter    @Setter    @ToString    public static class Config {        private boolean checkAuth;    }}

不出意外的话,你将会出现如下错误:

java.lang.IllegalStateException: block()/blockFirst()/blockLast() are blocking, which is not supported in thread reactor-http-nio-3at reactor.core.publisher.BlockingSingleSubscriber.blockingGet(BlockingSingleSubscriber.java:83)Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: Error has been observed at the following site(s):|_ checkpoint ⇢ org.springframework.web.cors.reactive.CorsWebFilter [DefaultWebFilterChain]|_ checkpoint ⇢ org.springframework.cloud.gateway.filter.WeightCalculatorWebFilter [DefaultWebFilterChain]|_ checkpoint ⇢ org.springframework.boot.actuate.metrics.web.reactive.server.MetricsWebFilter [DefaultWebFilterChain]|_ checkpoint ⇢ HTTP GET "/api/v1/users/getUserInfo" [ExceptionHandlingWebHandler]

上述错误则说明了,不能再Spring Cloud Gateway中使用同步调用,而普通的feign调用又是同步的,所以会有问题。

2 如何解决Spring Cloud Gateway同步调用feign问题

一、通过线程池来将feign同步调用转为异步调用
在搜索引擎上搜索关于Spring Cloud Gateway调用feign的问题,你可能大概率会得到下面的解决方案,及通过将feign同步调用封装成异步调用来解决。

关键代码如下:

        // 将feign调用封装成异步任务,通过线程池的方式提交        Future future = executorService.submit(() -> {            userClient.checkPermission(url, httpMethod, cookie);        });        try {            // 通过future方式获取结果            ResultResponse resultResponse = (ResultResponse) future.get();            if (resultResponse.isSuccess()) {                ServerHttpRequest request = exchange.getRequest().mutate().header(USER_HEADER_NAME, JSON.toJSONString(resultResponse.getData())).build();                return chain.filter(exchange.mutate().request(request).build());            } else {                return Mono.defer(() -> {                    exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);                    final ServerHttpResponse response = exchange.getResponse();                    byte[] bytes = JSON.toJSONString(resultResponse).getBytes(StandardCharsets.UTF_8);                    DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);                    return response.writeWith(Flux.just(buffer));                });            }        } catch (InterruptedException | ExecutionException e) {            // ignore exception        }        // 异常返回        return Mono.defer(() -> {                    exchange.getResponse().setStatusCode(HttpStatus.BAD_REQUEST);                    final ServerHttpResponse response = exchange.getResponse();                    byte[] bytes = JSON.toJSONString("ERROR").getBytes(StandardCharsets.UTF_8);                    DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);                    return response.writeWith(Flux.just(buffer));                });

遗憾的是,上述代码我在调试的时候虽然能够解决上面block的报错,但是并没有调通,还是会报错,初步定位是异步任务调用获取返回值的时候有问题,因为此处只是作为一个解决思路展示,而且最终也没有采用上述方案,就没有继续花时间去解决了。各位如果有解决该问题的欢迎指教。

二、真正的异步调用——ReactiveFeign
排除方案一的调试问题,假设方案一可以解决feign同步调用的问题,那么该方案有什么问题呢?
在我看来方案一的问题有二:一是并不是真正意义上的异步调用,只不过通过线程池强行提交了feign调用,而且获取feign调用返回结果的future.get()方法也是同步的;二是此种方式实在算不上优雅。

实际上feign无法进行异步调用的问题,早已被程序员们注意到,并且现在已经有了比较成熟的解决方案,即feign-reactive项目,项目地址:GitHub - PlaytikaOSS/feign-reactive
该项目通过Spring WebClient实现了feign的功能,实现了真正意义上的异步feign调用。

下面就让我们通过使用ReactiveFeign来解决Spring Cloud Gateway调用feign接口的问题,直接看代码(这里贴出整个鉴权的GatewayFilterFactory代码以方便理解):

@Component@Slf4jpublic class ApiAuthGatewayFilterFactory extends AbstractGatewayFilterFactory {    private static final String USER_HEADER_NAME = "User-Info";    @Autowired    private UserReactiveClient userReactiveClient;    public ApiAuthGatewayFilterFactory() {        super(Config.class);    }    @Override    public List shortcutFieldOrder() {        return Collections.singletonList("checkAuth");    }    @Override    public GatewayFilter apply(Config config) {        return (exchange, chain) -> {            if (config.checkAuth) {                String cookie = exchange.getRequest().getHeaders().getFirst("Cookie");                String url = exchange.getRequest().getPath().toString();                String httpMethod = exchange.getRequest().getMethodValue();                // ReactiveFeign异步调用,获取鉴权结果                return userReactiveClient.checkPermission(url, httpMethod, cookie).flatMap(commonResponse -> {                    // 鉴权不通过则返回异常                    if (!commonResponse.isSuccess()) {                        return Mono.defer(() -> {exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);final ServerHttpResponse response = exchange.getResponse();byte[] bytes = JSON.toJSONString(commonResponse).getBytes(StandardCharsets.UTF_8);DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);return response.writeWith(Flux.just(buffer));                        });                    } else {                        // 鉴权通过将用户信息带入后端                        log.info("User-Info: [{}]", JSON.toJSONString(commonResponse.getData()));                        ServerHttpRequest request = exchange.getRequest().mutate().header(USER_HEADER_NAME, JSON.toJSONString(commonResponse.getData())).build();                        return chain.filter(exchange.mutate().request(request).build());                    }                });            } else {                return chain.filter(exchange);            }        };    }    @NoArgsConstructor    @Getter    @Setter    @ToString    public static class Config {        private boolean checkAuth;    }}

上述方案,完美解决了Spring Cloud Gateway同步feign调用的问题,而且看起来也要优雅的多,符合异步编程的风格(上述方案的完整代码,将会在文末给出)。

写在最后

Spring Cloud Gateway通过WebFlux响应式框架实现了全异步处理,看过Spring Cloud Gateway源码的同学应该都深有体会,响应式编程的代码有多么难理解。

正因为Spring Cloud Gateway的响应式编程,导致它直接调用feign会有问题,因为feign的调用是同步调用。

遇到feign同步调用的问题,直接通过线程池强制将feign调用转成异步调用,简单粗暴,在我看来也并不是一个好的方案。

继续深入探究,找到解决feign同步调用问题的根本解决方案,才是一个合格程序员应该做的事。

通过使用ReactiveFeign,可以优雅地解决Spring Cloud Gateway feign同步调用的问题。

完整示例代码,请关注公众号:WU双,对话框回复【网关】即可获取。

完整示例代码除了包含网关的ReactiveFeign异步调用,还包含了XSS过滤器,缓存请求体等网关常用功能。

来源地址:https://blog.csdn.net/sslulu520/article/details/130127048

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     807人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     351人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     314人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     433人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     221人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯