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

这篇具有很好参考价值的文章主要介绍了Spring Cloud Gateway如何优雅地进行feign调用。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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

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

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

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

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

1 Spring Cloud Gateway直接进行feign调用

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

@SuppressWarnings("rawtypes")
@Component
@Slf4j
public class ApiAuthGatewayFilterFactory extends AbstractGatewayFilterFactory<ApiAuthGatewayFilterFactory.Config> {

    private static final String USER_HEADER_NAME = "User-Info";

    @Autowired
    private UserClient userClient;

    public ApiAuthGatewayFilterFactory() {
        super(Config.class);
    }

    @Override
    public List<String> 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-3
	at 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
@Slf4j
public class ApiAuthGatewayFilterFactory extends AbstractGatewayFilterFactory<ApiAuthGatewayFilterFactory.Config> {

    private static final String USER_HEADER_NAME = "User-Info";

    @Autowired
    private UserReactiveClient userReactiveClient;

    public ApiAuthGatewayFilterFactory() {
        super(Config.class);
    }

    @Override
    public List<String> 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://www.toymoban.com/news/detail-478110.html

到了这里,关于Spring Cloud Gateway如何优雅地进行feign调用的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • spring cloud整合spring boot,整合nacos、gateway、open-feign等组件

    想看具体详情的可以看我的github链接:codeking01/platform-parent: spring cloud整合spring boot、nacos、gateway、open feign等组件 (github.com) 由于我升级了jdk17,所以用上了spring boot 3.0.2了。 踩坑无数,一堆无用文章,写来写去,本文主要是提供给有基础的开发者再次快速搭建使用(确定版本

    2024年02月11日
    浏览(54)
  • spring cloud gateway k8s优雅启停

    通过配置readiness探针和preStop hook,实现优雅启动和停止(滚动部署) 1. k8s工作负载配置 2. 网关改造 经过测试发现,可以实现请求0失败

    2024年03月22日
    浏览(50)
  • 【云原生】Spring Cloud Alibaba 之 Feign 远程调用 实战

    在分布式领域中,一个系统由很多服务组成,不同的服务由各自的进程单独负责。因此,远程调用在分布式通信中尤为重要。 远程调用可分如下两类: 本地过程调用(Local Procedure Call,LPC) ,是指同一台机器上运行的不同进程之间的互相通信,即在多进程操作系统中,运行

    2023年04月09日
    浏览(44)
  • Spring Cloud Alibaba全家桶(四)——微服务调用组件Feign

    本文小新为大家带来 微服务调用组件Feign 的相关知识,具体内容包含 什么是Feign , Spring Cloud Alibaba快速整合OpenFeign , Spring Cloud Feign的自定义配置及使用 (包括: 日志配置 、 契约配置 、 自定义拦截器实现认证逻辑 、 超时时间配置 、 客户端组件配置 、 GZIP 压缩配置 )等

    2024年02月19日
    浏览(43)
  • Spring Cloud Gateway:新一代微服务 API 网关,用起来真优雅!

    如果没有网关,难道不行吗?功能上是可以的,我们直接调用提供的接口就可以了。那为什么还需要网关? 因为网关的作用不仅仅是转发请求而已。我们可以试想一下,如果需要做一个请求认证功能,我们可以接入到 API 服务中。但是倘若后续又有服务需要接入,我们又需要

    2024年02月09日
    浏览(49)
  • 【Spring Cloud】基于 Feign 实现远程调用,深入探索 Feign 的自定义配置、性能优化以及最佳实践方案

    在微服务架构中,服务之间的通信是至关重要的,而远程调用则成为实现这种通信的一种常见方式。在 Java 中,使用 RestTemplate 是一种传统的远程调用方式,但它存在一些问题,如代码可读性差、编程体验不一致以及参数复杂URL难以维护等。 在本文中,我们将探讨如何通过使

    2024年02月04日
    浏览(50)
  • Spring Cloud Feign调用异常:feign.RetryableException: connect timed out executing POST http://xxx

    本机JUnit单元测试时,调用部署在开发环境(Centos7)的服务时,报feign.RetryableException: connect timed out 直接访问服务地址时,提示网络无法访问 http://192.168.1.15:7002/comDictionary/getDictionaryById?dictionaryId=GIVE_LOGIN 发现端口没有打开。需要打开linux防火墙的端口 firewall-cmd --permanent --zon

    2024年03月22日
    浏览(63)
  • 【spring Cloud】微服务通信的三种方式RestTemplate、Feign远程调用与Dubbo的使用

    目录 一、通过RestTemplate调用微服务 二、通过Feign远程调用 三、Dubbo  分布式中的远程调用大概分为两种 RESTful接口  REST,即Representational State Transfer的缩写,如果一个架构符合REST原则,就称它为RESTful架构。 每一个URI代表一种资源; 客户端和服务器之间,传递这种资源的某种

    2024年04月11日
    浏览(45)
  • 优化 spring cloud gateway+nacos时服务恢复调用太慢问题

    问题描述 在使用 spring cloud gateway + nacos 做服务发现时,会发现当下游的服务器恢复了,但是还有经过一段时间 gateway 才成功转发请求到刚恢复的下游服务上。于是我就深入源码进行企图通过修改相关配置的方式优化gateway服务发现的恢复时间。 相关依赖版本 源码 经过漫长的

    2024年02月01日
    浏览(57)
  • Spring Cloud Gateway集成sentinel进行网关限流

    本文使用版本如下:

    2024年02月09日
    浏览(48)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包