Java学习者论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

手机号码,快捷登录

恭喜Java学习者论坛(https://www.javaxxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,购买链接:点击进入购买VIP会员
JAVA高级面试进阶视频教程Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程

Go语言视频零基础入门到精通

Java架构师3期(课件+源码)

Java开发全终端实战租房项目视频教程

SpringBoot2.X入门到高级使用教程

大数据培训第六期全套视频教程

深度学习(CNN RNN GAN)算法原理

Java亿级流量电商系统视频教程

互联网架构师视频教程

年薪50万Spark2.0从入门到精通

年薪50万!人工智能学习路线教程

年薪50万!大数据从入门到精通学习路线年薪50万!机器学习入门到精通视频教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程 MySQL入门到精通教程
查看: 866|回复: 0

[默认分类] SpringCloud Gateway 测试问题解决

[复制链接]
  • TA的每日心情
    开心
    2021-12-13 21:45
  • 签到天数: 15 天

    [LV.4]偶尔看看III

    发表于 2020-8-15 11:09:24 | 显示全部楼层 |阅读模式

    本文针对于测试环境SpringCloud Gateway问题解决。

    1.背景介绍
    本文遇到的问题都是在测试环境真正遇到的问题,不一定试用于所有人,仅做一次记录,便于遇到同样问题的干掉这些问题。
    使用版本:SpringCloud 2.0.0.RELEASE
    1.1 Gateway配置
    之前系统是由阿里云SLB直接分发到几台生产服务器,但是经过研究,决定在中间加一层网关,也就是阿里云SLB分发流量到Gateway到下游服务。但是又由于种种原因,决定使用Host方式进行拦截处理,以下为部分配置代码:
    1. [code]spring:
    2.   cloud:
    3.     gateway:
    4.       discovery:
    5.         locator:
    6.           enabled: true
    7.       routes:
    8.         - id: test_client
    9.           uri: lb://TEST-CLIENT
    10.           predicates:
    11.             - Host=www.dalaoyang.cn
    12.           order: 1
    13.           filters:
    14.             - DalaoyangAuth
    15.             
    复制代码
    [/code]
    注意,其中部分内容并非真实环境内容,但是场景绝对真实,如:

    test_client:routes的ID。
    uri:这里使用的Eureka内的application name
    Host:需要拦截的域名
    filters:域名前缀

    1.2 Gateway过滤器
    过滤器内容如下,稍后介绍:
    1. [code]
    2. @Component
    3. public class DalaoyangAuthFilterFactory  extends AbstractGatewayFilterFactory<Object> {
    4.     private static final Logger logger = LoggerFactory.getLogger(DalaoyangAuthFilterFactory.class);
    5.     @Override
    6.     public GatewayFilter apply(Object config) {
    7.         return (exchange, chain) -> {
    8.             ServerHttpRequest host = exchange.getRequest().mutate().headers(httpHeaders -> {
    9.                 httpHeaders.remove("gate_way_auth");
    10.                 httpHeaders.add("gate_way_auth", "yes");
    11.             }).build();
    12.             //将现在的request 变成 change对象
    13.             ServerWebExchange build = exchange.mutate().request(host).build();
    14.             return chain.filter(build);
    15.         };
    16.     }
    17. }
    复制代码
    [/code]
    1.3 下游服务拦截器
    下游服务拦截器大致内容如下,这段代码是原有的代码,这个功能大概就是加载公共的属性basePath,用于加载静态资源,比如前端的jquery.js,根据域名判断,然后选择是加载为http://127.0.0.1:8080/jquery.js还是https://www.dalaoyang.cn/jquery.js这种:
    1. [code]public class GlobalInterceptorAdapter extends HandlerInterceptorAdapter {
    2.     private static Logger logger = LoggerFactory.getLogger(GlobalInterceptorAdapter.class);
    3.     @Override
    4.     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws
    5.             Exception {
    6.         String scheme = request.getScheme();
    7.         String serverName = request.getServerName();
    8.         int port = request.getServerPort();
    9.         String path = request.getContextPath();
    10.         String basePath = "";
    11.         if(serverName.indexOf("www.dalaoyang.cn")!=-1){
    12.             basePath = "//" + serverName + path;
    13.         }else {
    14.             basePath = scheme + "://" + serverName + ":" + port + path;
    15.         }
    16.         if (logger.isDebugEnabled()) {
    17.             logger.debug(basePath);
    18.         }
    19.         request.setAttribute("basePath", basePath);
    20.         return true;
    21.     }
    22. }
    复制代码
    [/code]
    1.4 下游服务用户过滤器
    这段代码也是原有的代码,用户Session过滤器,这个完整内容很多,只截取遇到问题的片段,大致内容就是判断用户是否在其他地方登录,如果登录了就弹出的固定的提示页面,内容如下:
    1. [code]String url = null;
    2. ApplicationConfig applicationConfig0 = getApplicationConfig();
    3. if(applicationConfig0 != null) {
    4.     String scheme = applicationConfig0.getUrlScheme();
    5.     if(scheme != null) {
    6.         String requestUrl = request.getRequestURL().toString();
    7.             if(requestUrl != null && requestUrl.length() > 8) {
    8.                 requestUrl = requestUrl.substring(requestUrl.indexOf(":"),
    9.                                         requestUrl.indexOf("/", 8));
    10.                 url = scheme + requestUrl;
    11.             }
    12.     }
    13. }
    14. if(url != null) {
    15.     response.sendRedirect(url + request.getContextPath() + "/session-time-out");
    16. } else {
    17.     response.sendRedirect(request.getContextPath() + "/session-time-out");
    18. }
    复制代码
    [/code]
    1.5 跳转流程
    跳转如下:
    1.域名指向了Gateway地址。
    2.在浏览器使用域名访问Gateway,被Gateway转发到下游服务,返回对应响应。
    2.问题一 下游服务无法获取域名
    在使用上述配置后,使用request.getServerName()方法已经无法获取到域名了,经过测试,获取到的是服务器的ip地址,导致虽然页面可以正常跳转,但是无法获取到正确的域名,导致静态资源加载有问题。
    在网上请教了很多人,本想看看是不是什么地方没有设置对,但是后台还是采取大多数人的建议,在header中加入一个域名信息,修改后Gateway过滤器如下:
    1. [code]@Component
    2. public class DalaoyangAuthFilterFactory  extends AbstractGatewayFilterFactory<Object> {
    3.     private static final Logger logger = LoggerFactory.getLogger(DalaoyangAuthFilterFactory.class);
    4.     @Override
    5.     public GatewayFilter apply(Object config) {
    6.         return (exchange, chain) -> {
    7.             ServerHttpRequest host = exchange.getRequest().mutate().headers(httpHeaders -> {
    8.                 httpHeaders.remove("gate_way_auth");
    9.                 httpHeaders.add("gate_way_auth", "yes");
    10.                
    11.                 httpHeaders.add("realServerName",
    12.                 exchange.getRequest().getURI().getHost());
    13.                 logger.info("headers:" + httpHeaders.toString());
    14.             }).build();
    15.             //将现在的request 变成 change对象
    16.             ServerWebExchange build = exchange.mutate().request(host).build();
    17.             return chain.filter(build);
    18.         };
    19.     }
    20. }
    复制代码
    [/code]
    很容易看到,就是如下这句话:
    1. [code]httpHeaders.add("realServerName",
    2.               exchange.getRequest().getURI().getHost());
    复制代码
    [/code]
    下游服务过滤修改为:
    1. [code]public class GlobalInterceptorAdapter extends HandlerInterceptorAdapter {
    2.     private static Logger logger = LoggerFactory.getLogger(GlobalInterceptorAdapter.class);
    3.     private final String TEST_SERVERNAME = "www.dalaoyang.cn";
    4.     @Override
    5.     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws
    6.             Exception {
    7.         String scheme = request.getScheme();
    8.         String serverName = request.getServerName();
    9.         String realServerName = request.getHeader("realServerName");
    10.         int port = request.getServerPort();
    11.         String path = request.getContextPath();
    12.         String basePath = "";
    13.         if((!StringUtils.isBlank(realServerName))){
    14.             if(realServerName.contains(TEST_SERVERNAME)){
    15.                 basePath = "//" + realServerName + path;
    16.             }
    17.         }else {
    18.             basePath = scheme + "://" + serverName + ":" + port + path;
    19.         }
    20.         if (logger.isDebugEnabled()) {
    21.             logger.debug(basePath);
    22.         }
    23.         request.setAttribute("basePath", basePath);
    24.         return true;
    25.     }
    26. }
    复制代码
    [/code]
    其实大致内容就是,使用如下方式获取域名:
    1. [code]String realServerName = request.getHeader("realServerName");
    复制代码
    [/code]
    到此,问题解决了,大部分内容跳转正常。
    3.问题二 NPE异常
    部分请求,经过路由访问报如下错误。
    1. [code]2018-06-20 01:26:04.254 ERROR 1 --- [reactor-http-client-epoll-11] .a.w.r.e.DefaultErrorWebExceptionHandler : Failed to handle request [DELETE http://localhost:8080/entity/5b29ad2cb3cb1f00010a1546]
    2. java.lang.NullPointerException: null
    3.         at java.util.concurrent.ConcurrentHashMap.putVal(ConcurrentHashMap.java:1011) ~[na:1.8.0_111]
    4.         at java.util.concurrent.ConcurrentHashMap.put(ConcurrentHashMap.java:1006) ~[na:1.8.0_111]
    5.         at org.springframework.cloud.gateway.filter.NettyRoutingFilter.lambda$filter$3(NettyRoutingFilter.java:117) ~[spring-cloud-gateway-core-2.0.0.RELEASE.jar!/:2.0.0.RELEASE]
    6.         at reactor.core.publisher.FluxPeek$PeekSubscriber.onNext(FluxPeek.java:177) ~[reactor-core-3.1.8.RELEASE.jar!/:3.1.8.RELEASE]
    7.         at reactor.core.publisher.FluxMap$MapSubscriber.onNext(FluxMap.java:108) ~[reactor-core-3.1.8.RELEASE.jar!/:3.1.8.RELEASE]
    8.         at reactor.core.publisher.FluxRetryPredicate$RetryPredicateSubscriber.onNext(FluxRetryPredicate.java:81) ~[reactor-core-3.1.8.RELEASE.jar!/:3.1.8.RELEASE]
    9.         at reactor.core.publisher.MonoCreate$DefaultMonoSink.success(MonoCreate.java:146) ~[reactor-core-3.1.8.RELEASE.jar!/:3.1.8.RELEASE]
    10.         at reactor.ipc.netty.channel.PooledClientContextHandler.fireContextActive(PooledClientContextHandler.java:85) ~[reactor-netty-0.7.8.RELEASE.jar!/:0.7.8.RELEASE]
    11.         at reactor.ipc.netty.http.client.HttpClientOperations.onInboundNext(HttpClientOperations.java:578) ~[reactor-netty-0.7.8.RELEASE.jar!/:0.7.8.RELEASE]
    12.         at reactor.ipc.netty.channel.ChannelOperationsHandler.channelRead(ChannelOperationsHandler.java:136) ~[reactor-netty-0.7.8.RELEASE.jar!/:0.7.8.RELEASE]
    13.         at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) ~[netty-transport-4.1.25.Final.jar!/:4.1.25.Final]
    14.         at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) ~[netty-transport-4.1.25.Final.jar!/:4.1.25.Final]
    15.         at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340) ~[netty-transport-4.1.25.Final.jar!/:4.1.25.Final]
    16.         at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:438) ~[netty-transport-4.1.25.Final.jar!/:4.1.25.Final]
    17.         at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:310) ~[netty-codec-4.1.25.Final.jar!/:4.1.25.Final]
    18.         at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:284) ~[netty-codec-4.1.25.Final.jar!/:4.1.25.Final]
    19.         at io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:253) ~[netty-transport-4.1.25.Final.jar!/:4.1.25.Final]
    20.         at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) ~[netty-transport-4.1.25.Final.jar!/:4.1.25.Final]
    21.         at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) ~[netty-transport-4.1.25.Final.jar!/:4.1.25.Final]
    22.         at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340) ~[netty-transport-4.1.25.Final.jar!/:4.1.25.Final]
    23.         at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1434) ~[netty-transport-4.1.25.Final.jar!/:4.1.25.Final]
    24.         at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) ~[netty-transport-4.1.25.Final.jar!/:4.1.25.Final]
    25.         at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) ~[netty-transport-4.1.25.Final.jar!/:4.1.25.Final]
    26.         at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:965) ~[netty-transport-4.1.25.Final.jar!/:4.1.25.Final]
    27.         at io.netty.channel.epoll.AbstractEpollStreamChannel$EpollStreamUnsafe.epollInReady(AbstractEpollStreamChannel.java:808) ~[netty-transport-native-epoll-4.1.25.Final.jar!/:4.1.25.Final]
    28.         at io.netty.channel.epoll.EpollEventLoop.processReady(EpollEventLoop.java:408) ~[netty-transport-native-epoll-4.1.25.Final.jar!/:4.1.25.Final]
    29.         at io.netty.channel.epoll.EpollEventLoop.run(EpollEventLoop.java:308) ~[netty-transport-native-epoll-4.1.25.Final.jar!/:4.1.25.Final]
    30.         at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:884) ~[netty-common-4.1.25.Final.jar!/:4.1.25.Final]
    31.         at java.lang.Thread.run(Thread.java:745) ~[na:1.8.0_111]
    复制代码
    [/code]
    遇到问题后,没有很慌,打开了百度查了查(微笑)。百度没让我很失望,基本上没啥答复,然后谷歌了一下,看到了github上的一个issues,大致内容感觉是SpringCloud Gateway 2.0.0.RELEASE版本有些问题,升级一下版本就好了,如图。
    Github issues地址:
    https://github.com/spring-cloud/spring-cloud-gateway/issues/429
    https://github.com/spring-cloud/spring-cloud-gateway/issues/374

    说实话,感觉是版本问题,但是又看到了一篇国人的文章,地址是:http://xiaoqiangge.com/aritcle/1545889008833.html,问题大致类似,加了一下博主的微信,请教了一下,大致了解到了,升级了一下版本,问题解决。
    感谢小强哥!!!
    4.问题三 下游用户过滤器跳转失效
    问题是这样的,刚刚介绍了,用户在其他地方登录会自动跳转至一个界面提示给用户,发现问题是无法跳转。
    查看gateway日志,大概提示了这样一句话,如下:
    1. [code]Unhandled failure: Connection has been closed, response already set (status=302)
    复制代码
    [/code]
    从内容大致可以看出,重定向有问题,想到了在用户过滤器中最后的重定向,决定在这里下手,修改后内容如下:
    1. [code]String scheme = request.getScheme();
    2. String serverName = request.getServerName();
    3. String realServerName = request.getHeader("realServerName");
    4. int port = request.getServerPort();
    5. String path = request.getContextPath();
    6. String basePath = "";
    7. if((!StringUtils.isEmpty(realServerName))){
    8.         if(realServerName.contains(TEST_SERVERNAME)){
    9.         basePath = "https://" + realServerName + path;
    10.     }else {
    11.         basePath = scheme + "://" + serverName + ":" + port + path;
    12.     }
    13. response.sendRedirect(basePath + "/session-time-out");
    复制代码
    [/code]
    问题也解决了,目前还在踩坑测试中,如果大家有类似经验可以一起探讨。
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|Java学习者论坛 ( 声明:本站资料整理自互联网,用于Java学习者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-4-26 13:38 , Processed in 0.365013 second(s), 38 queries .

    Powered by Discuz! X3.4

    © 2001-2017 Comsenz Inc.

    快速回复 返回顶部 返回列表