SpringCloud Gateway 测试问题解决

简介: 本文针对于测试环境SpringCloud Gateway问题解决。1.背景介绍本文遇到的问题都是在测试环境真正遇到的问题,不一定试用于所有人,仅做一次记录,便于遇到同样问题的干掉这些问题。使用版本:SpringCloud 2.0.0.RELEASE1.1 Gateway配置之前系统是由阿里云SLB直接分发到几台生产服务器,但是经过研究,决定在中间加一层网关,也就是阿里云SLB分发流量到Gateway到下游服务。

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

1.背景介绍

本文遇到的问题都是在测试环境真正遇到的问题,不一定试用于所有人,仅做一次记录,便于遇到同样问题的干掉这些问题。

使用版本:SpringCloud 2.0.0.RELEASE

1.1 Gateway配置

之前系统是由阿里云SLB直接分发到几台生产服务器,但是经过研究,决定在中间加一层网关,也就是阿里云SLB分发流量到Gateway到下游服务。但是又由于种种原因,决定使用Host方式进行拦截处理,以下为部分配置代码:

spring:
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true
      routes:
        - id: test_client
          uri: lb://TEST-CLIENT
          predicates:
            - Host=www.dalaoyang.cn
          order: 1
          filters:
            - DalaoyangAuth
            

注意,其中部分内容并非真实环境内容,但是场景绝对真实,如:

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

1.2 Gateway过滤器

过滤器内容如下,稍后介绍:


@Component
public class DalaoyangAuthFilterFactory  extends AbstractGatewayFilterFactory<Object> {
    private static final Logger logger = LoggerFactory.getLogger(DalaoyangAuthFilterFactory.class);

    @Override
    public GatewayFilter apply(Object config) {
        return (exchange, chain) -> {
            ServerHttpRequest host = exchange.getRequest().mutate().headers(httpHeaders -> {
                httpHeaders.remove("gate_way_auth");
                httpHeaders.add("gate_way_auth", "yes");
            }).build();
            //将现在的request 变成 change对象
            ServerWebExchange build = exchange.mutate().request(host).build();
            return chain.filter(build);
        };
    }
}

1.3 下游服务拦截器

下游服务拦截器大致内容如下,这段代码是原有的代码,这个功能大概就是加载公共的属性basePath,用于加载静态资源,比如前端的jquery.js,根据域名判断,然后选择是加载为http://127.0.0.1:8080/jquery.js还是https://www.dalaoyang.cn/jquery.js这种:

public class GlobalInterceptorAdapter extends HandlerInterceptorAdapter {

    private static Logger logger = LoggerFactory.getLogger(GlobalInterceptorAdapter.class);

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws
            Exception {
        String scheme = request.getScheme();
        String serverName = request.getServerName();
        int port = request.getServerPort();
        String path = request.getContextPath();
        String basePath = "";
        if(serverName.indexOf("www.dalaoyang.cn")!=-1){
            basePath = "//" + serverName + path;
        }else {
            basePath = scheme + "://" + serverName + ":" + port + path;
        }
        if (logger.isDebugEnabled()) {
            logger.debug(basePath);
        }
        request.setAttribute("basePath", basePath);
        return true;
    }

}

1.4 下游服务用户过滤器

这段代码也是原有的代码,用户Session过滤器,这个完整内容很多,只截取遇到问题的片段,大致内容就是判断用户是否在其他地方登录,如果登录了就弹出的固定的提示页面,内容如下:

String url = null;
ApplicationConfig applicationConfig0 = getApplicationConfig();
if(applicationConfig0 != null) {
    String scheme = applicationConfig0.getUrlScheme();
    if(scheme != null) {
        String requestUrl = request.getRequestURL().toString();
            if(requestUrl != null && requestUrl.length() > 8) {
                requestUrl = requestUrl.substring(requestUrl.indexOf(":"), 
                                        requestUrl.indexOf("/", 8));
                url = scheme + requestUrl;
            }
    }
}
if(url != null) {
    response.sendRedirect(url + request.getContextPath() + "/session-time-out");
} else {
    response.sendRedirect(request.getContextPath() + "/session-time-out");
}

1.5 跳转流程

跳转如下:

1.域名指向了Gateway地址。
2.在浏览器使用域名访问Gateway,被Gateway转发到下游服务,返回对应响应。

2.问题一 下游服务无法获取域名

在使用上述配置后,使用request.getServerName()方法已经无法获取到域名了,经过测试,获取到的是服务器的ip地址,导致虽然页面可以正常跳转,但是无法获取到正确的域名,导致静态资源加载有问题。

在网上请教了很多人,本想看看是不是什么地方没有设置对,但是后台还是采取大多数人的建议,在header中加入一个域名信息,修改后Gateway过滤器如下:

@Component
public class DalaoyangAuthFilterFactory  extends AbstractGatewayFilterFactory<Object> {
    private static final Logger logger = LoggerFactory.getLogger(DalaoyangAuthFilterFactory.class);

    @Override
    public GatewayFilter apply(Object config) {
        return (exchange, chain) -> {
            ServerHttpRequest host = exchange.getRequest().mutate().headers(httpHeaders -> {
                httpHeaders.remove("gate_way_auth");
                httpHeaders.add("gate_way_auth", "yes");
                
                httpHeaders.add("realServerName",
                exchange.getRequest().getURI().getHost());
                logger.info("headers:" + httpHeaders.toString());
            }).build();
            //将现在的request 变成 change对象
            ServerWebExchange build = exchange.mutate().request(host).build();
            return chain.filter(build);
        };
    }
}

很容易看到,就是如下这句话:

httpHeaders.add("realServerName",
              exchange.getRequest().getURI().getHost());

下游服务过滤修改为:

public class GlobalInterceptorAdapter extends HandlerInterceptorAdapter {
    private static Logger logger = LoggerFactory.getLogger(GlobalInterceptorAdapter.class);
    private final String TEST_SERVERNAME = "www.dalaoyang.cn";

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws
            Exception {
        String scheme = request.getScheme();
        String serverName = request.getServerName();
        String realServerName = request.getHeader("realServerName");
        int port = request.getServerPort();
        String path = request.getContextPath();
        String basePath = "";
        if((!StringUtils.isBlank(realServerName))){
            if(realServerName.contains(TEST_SERVERNAME)){
                basePath = "//" + realServerName + path;
            }
        }else {
            basePath = scheme + "://" + serverName + ":" + port + path;
        }
        if (logger.isDebugEnabled()) {
            logger.debug(basePath);
        }
        request.setAttribute("basePath", basePath);
        return true;
    }
}

其实大致内容就是,使用如下方式获取域名:

String realServerName = request.getHeader("realServerName");

到此,问题解决了,大部分内容跳转正常。

3.问题二 NPE异常

部分请求,经过路由访问报如下错误。

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]

java.lang.NullPointerException: null
        at java.util.concurrent.ConcurrentHashMap.putVal(ConcurrentHashMap.java:1011) ~[na:1.8.0_111]
        at java.util.concurrent.ConcurrentHashMap.put(ConcurrentHashMap.java:1006) ~[na:1.8.0_111]
        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]
        at reactor.core.publisher.FluxPeek$PeekSubscriber.onNext(FluxPeek.java:177) ~[reactor-core-3.1.8.RELEASE.jar!/:3.1.8.RELEASE]
        at reactor.core.publisher.FluxMap$MapSubscriber.onNext(FluxMap.java:108) ~[reactor-core-3.1.8.RELEASE.jar!/:3.1.8.RELEASE]
        at reactor.core.publisher.FluxRetryPredicate$RetryPredicateSubscriber.onNext(FluxRetryPredicate.java:81) ~[reactor-core-3.1.8.RELEASE.jar!/:3.1.8.RELEASE]
        at reactor.core.publisher.MonoCreate$DefaultMonoSink.success(MonoCreate.java:146) ~[reactor-core-3.1.8.RELEASE.jar!/:3.1.8.RELEASE]
        at reactor.ipc.netty.channel.PooledClientContextHandler.fireContextActive(PooledClientContextHandler.java:85) ~[reactor-netty-0.7.8.RELEASE.jar!/:0.7.8.RELEASE]
        at reactor.ipc.netty.http.client.HttpClientOperations.onInboundNext(HttpClientOperations.java:578) ~[reactor-netty-0.7.8.RELEASE.jar!/:0.7.8.RELEASE]
        at reactor.ipc.netty.channel.ChannelOperationsHandler.channelRead(ChannelOperationsHandler.java:136) ~[reactor-netty-0.7.8.RELEASE.jar!/:0.7.8.RELEASE]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) ~[netty-transport-4.1.25.Final.jar!/:4.1.25.Final]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) ~[netty-transport-4.1.25.Final.jar!/:4.1.25.Final]
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340) ~[netty-transport-4.1.25.Final.jar!/:4.1.25.Final]
        at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:438) ~[netty-transport-4.1.25.Final.jar!/:4.1.25.Final]
        at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:310) ~[netty-codec-4.1.25.Final.jar!/:4.1.25.Final]
        at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:284) ~[netty-codec-4.1.25.Final.jar!/:4.1.25.Final]
        at io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:253) ~[netty-transport-4.1.25.Final.jar!/:4.1.25.Final]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) ~[netty-transport-4.1.25.Final.jar!/:4.1.25.Final]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) ~[netty-transport-4.1.25.Final.jar!/:4.1.25.Final]
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340) ~[netty-transport-4.1.25.Final.jar!/:4.1.25.Final]
        at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1434) ~[netty-transport-4.1.25.Final.jar!/:4.1.25.Final]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362) ~[netty-transport-4.1.25.Final.jar!/:4.1.25.Final]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348) ~[netty-transport-4.1.25.Final.jar!/:4.1.25.Final]
        at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:965) ~[netty-transport-4.1.25.Final.jar!/:4.1.25.Final]
        at io.netty.channel.epoll.AbstractEpollStreamChannel$EpollStreamUnsafe.epollInReady(AbstractEpollStreamChannel.java:808) ~[netty-transport-native-epoll-4.1.25.Final.jar!/:4.1.25.Final]
        at io.netty.channel.epoll.EpollEventLoop.processReady(EpollEventLoop.java:408) ~[netty-transport-native-epoll-4.1.25.Final.jar!/:4.1.25.Final]
        at io.netty.channel.epoll.EpollEventLoop.run(EpollEventLoop.java:308) ~[netty-transport-native-epoll-4.1.25.Final.jar!/:4.1.25.Final]
        at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:884) ~[netty-common-4.1.25.Final.jar!/:4.1.25.Final]
        at java.lang.Thread.run(Thread.java:745) ~[na:1.8.0_111]

遇到问题后,没有很慌,打开了百度查了查(微笑)。百度没让我很失望,基本上没啥答复,然后谷歌了一下,看到了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日志,大概提示了这样一句话,如下:

Unhandled failure: Connection has been closed, response already set (status=302)

从内容大致可以看出,重定向有问题,想到了在用户过滤器中最后的重定向,决定在这里下手,修改后内容如下:

String scheme = request.getScheme();
String serverName = request.getServerName();
String realServerName = request.getHeader("realServerName");
int port = request.getServerPort();
String path = request.getContextPath();
String basePath = "";
if((!StringUtils.isEmpty(realServerName))){
        if(realServerName.contains(TEST_SERVERNAME)){
        basePath = "https://" + realServerName + path;
    }else {
        basePath = scheme + "://" + serverName + ":" + port + path;
    }
response.sendRedirect(basePath + "/session-time-out");

问题也解决了,目前还在踩坑测试中,如果大家有类似经验可以一起探讨。

相关实践学习
基于函数计算快速搭建Hexo博客系统
本场景介绍如何使用阿里云函数计算服务命令行工具快速搭建一个Hexo博客。
目录
相关文章
|
8天前
|
负载均衡 Java API
Java一分钟之-Spring Cloud OpenFeign:声明式服务调用
【6月更文挑战第9天】Spring Cloud OpenFeign是声明式服务调用库,简化了微服务间调用。通过动态代理,它允许开发者用Java接口调用HTTP服务,支持服务发现、负载均衡。本文介绍了OpenFeign的基本概念,展示了如何添加依赖、开启客户端和定义服务接口。还讨论了接口调用失败、超时重试和日志配置等问题及其解决方案,并提供了自定义Feign配置的代码示例。通过学习,读者可以更好地在微服务架构中使用OpenFeign进行服务通信。
165 4
|
9天前
|
消息中间件 负载均衡 Java
Java一分钟之-Spring Cloud:微服务架构工具集
【6月更文挑战第8天】本文介绍了Spring Cloud的核心组件,包括Eureka(服务注册与发现)、Ribbon(客户端负载均衡)、Zuul(API网关)、Hystrix(断路器)、Spring Cloud Config(配置中心)和Spring Cloud Bus(事件总线)。文中强调了各组件的易错点,如Eureka的服务注册失败、Ribbon的配置、Zuul的路由错误、Hystrix的启用及配置、Config Server的加载失败和Bus的通讯问题,并给出了相应的代码示例和解决建议。在实际开发中,关注日志和使用调试工具是保证微服务系统稳定运行的关键。
90 6
|
1天前
|
Java 测试技术 持续交付
Java一分钟之-Spring Cloud Contract:契约测试
【6月更文挑战第16天】Spring Cloud Contract是微服务契约测试框架,通过DSL定义接口行为,使用WireMock生成存根进行独立开发验证。常见问题包括契约编写不清晰、未集成到CI/CD和契约版本控制混乱。例如,定义一个`GET /greeting`返回JSON响应的契约,Spring Cloud Contract会自动生成测试代码,帮助确保服务间接口一致性,提升开发效率和系统稳定性。
25 7
|
3天前
|
Java 数据库 开发者
深入解析 Spring Cloud Seata:分布式事务的全面指南
深入解析 Spring Cloud Seata:分布式事务的全面指南
15 1
|
3天前
|
监控 Java API
深入解析 Spring Cloud Sentinel:分布式系统流量控制与熔断降级的全面指南
深入解析 Spring Cloud Sentinel:分布式系统流量控制与熔断降级的全面指南
7 0
深入解析 Spring Cloud Sentinel:分布式系统流量控制与熔断降级的全面指南
|
3天前
|
Java Nacos 数据格式
Spring Cloud Nacos 详解:服务注册与发现及配置管理平台
Spring Cloud Nacos 详解:服务注册与发现及配置管理平台
16 3
|
3天前
|
负载均衡 Java API
Spring Cloud Gateway 详解:构建高效的API网关解决方案
Spring Cloud Gateway 详解:构建高效的API网关解决方案
11 0
|
5天前
|
负载均衡 前端开发 Java
OpenFeign:Spring Cloud声明式服务调用组件
该文本是关于OpenFeign在Spring Cloud中的使用的问答总结。涉及的问题包括:OpenFeign是什么,Feign与OpenFeign的区别,如何使用OpenFeign进行远程服务调用,OpenFeign的超时控制以及日志增强。OpenFeign被描述为Spring官方的声明式服务调用和负载均衡组件,它支持使用注解进行接口定义和服务调用,如@FeignClient和@EnableFeignClients。OpenFeign与Feign的主要区别在于OpenFeign支持Spring MVC注解。超时控制通过Ribbon进行设置,默认超时时间为1秒。
|
7天前
|
Java API 开发者
Java一分钟之-Spring Cloud Gateway:API网关
【6月更文挑战第10天】Spring Cloud Gateway是Spring Cloud生态中的API网关组件,基于Spring Framework 5、Reactor和Spring Boot 2.0,支持响应式编程。它提供路由转发、过滤器链(包括预处理、路由和后处理)和断言功能。快速入门涉及添加相关依赖和配置路由规则。常见问题包括路由冲突、过滤器顺序和性能瓶颈。通过动态路由和过滤器示例,展示了其灵活性。Spring Cloud Gateway是微服务架构的有力工具,可提升系统稳定性和开发效率。
114 0
|
8天前
|
监控 Java UED
Java一分钟之-Spring Cloud Netflix Hystrix:容错管理
【6月更文挑战第9天】Spring Cloud Hystrix是用于微服务容错管理的库,通过断路器模式防止服务雪崩。本文介绍了Hystrix的基本概念,如断路器、线程隔离和fallback机制,并展示了如何快速上手,包括添加依赖、启用注解和编写Hystrix命令。此外,还讨论了常见问题(如断路器打开、资源泄漏和不当的Fallback策略)及其解决方案。通过自定义Hystrix指标监控,可以进一步优化系统性能。理解Hystrix工作原理并适时调整配置,对于构建健壮的微服务至关重要。
113 3

热门文章

最新文章