SpringCloud Gateway 測試問題解決

本文針對(duì)于測試環(huán)境SpringCloud Gateway問題解決。

1.背景介紹

本文遇到的問題都是在測試環(huán)境真正遇到的問題,不一定試用于所有人,僅做一次記錄,便于遇到同樣問題的干掉這些問題。

使用版本:SpringCloud 2.0.0.RELEASE

1.1 Gateway配置

之前系統(tǒng)是由阿里云SLB直接分發(fā)到幾臺(tái)生產(chǎn)服務(wù)器,但是經(jīng)過研究,決定在中間加一層網(wǎng)關(guān),也就是阿里云SLB分發(fā)流量到Gateway到下游服務(wù)。但是又由于種種原因,決定使用Host方式進(jìn)行攔截處理,以下為部分配置代碼:

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

注意,其中部分內(nèi)容并非真實(shí)環(huán)境內(nèi)容,但是場景絕對(duì)真實(shí),如:

  • test_client:routes的ID。
  • uri:這里使用的Eureka內(nèi)的application name
  • Host:需要攔截的域名
  • filters:域名前綴

1.2 Gateway過濾器

過濾器內(nèi)容如下,稍后介紹:


@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();
            //將現(xiàn)在的request 變成 change對(duì)象
            ServerWebExchange build = exchange.mutate().request(host).build();
            return chain.filter(build);
        };
    }
}

1.3 下游服務(wù)攔截器

下游服務(wù)攔截器大致內(nèi)容如下,這段代碼是原有的代碼,這個(gè)功能大概就是加載公共的屬性basePath,用于加載靜態(tài)資源,比如前端的jquery.js,根據(jù)域名判斷,然后選擇是加載為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 = "http://" + serverName + path;
        }else {
            basePath = scheme + "://" + serverName + ":" + port + path;
        }
        if (logger.isDebugEnabled()) {
            logger.debug(basePath);
        }
        request.setAttribute("basePath", basePath);
        return true;
    }

}

1.4 下游服務(wù)用戶過濾器

這段代碼也是原有的代碼,用戶Session過濾器,這個(gè)完整內(nèi)容很多,只截取遇到問題的片段,大致內(nèi)容就是判斷用戶是否在其他地方登錄,如果登錄了就彈出的固定的提示頁面,內(nèi)容如下:

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 跳轉(zhuǎn)流程

跳轉(zhuǎn)如下:

1.域名指向了Gateway地址。
2.在瀏覽器使用域名訪問Gateway,被Gateway轉(zhuǎn)發(fā)到下游服務(wù),返回對(duì)應(yīng)響應(yīng)。

2.問題一 下游服務(wù)無法獲取域名

在使用上述配置后,使用request.getServerName()方法已經(jīng)無法獲取到域名了,經(jīng)過測試,獲取到的是服務(wù)器的ip地址,導(dǎo)致雖然頁面可以正常跳轉(zhuǎn),但是無法獲取到正確的域名,導(dǎo)致靜態(tài)資源加載有問題。

在網(wǎng)上請(qǐng)教了很多人,本想看看是不是什么地方?jīng)]有設(shè)置對(duì),但是后臺(tái)還是采取大多數(shù)人的建議,在header中加入一個(gè)域名信息,修改后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();
            //將現(xiàn)在的request 變成 change對(duì)象
            ServerWebExchange build = exchange.mutate().request(host).build();
            return chain.filter(build);
        };
    }
}

很容易看到,就是如下這句話:

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

下游服務(wù)過濾修改為:

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 = "http://" + realServerName + path;
            }
        }else {
            basePath = scheme + "://" + serverName + ":" + port + path;
        }
        if (logger.isDebugEnabled()) {
            logger.debug(basePath);
        }
        request.setAttribute("basePath", basePath);
        return true;
    }
}

其實(shí)大致內(nèi)容就是,使用如下方式獲取域名:

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

到此,問題解決了,大部分內(nèi)容跳轉(zhuǎn)正常。

3.問題二 NPE異常

部分請(qǐng)求,經(jīng)過路由訪問報(bào)如下錯(cuò)誤。

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]

遇到問題后,沒有很慌,打開了百度查了查(微笑)。百度沒讓我很失望,基本上沒啥答復(fù),然后谷歌了一下,看到了github上的一個(gè)issues,大致內(nèi)容感覺是SpringCloud Gateway 2.0.0.RELEASE版本有些問題,升級(jí)一下版本就好了,如圖。

Github issues地址:

https://github.com/spring-cloud/spring-cloud-gateway/issues/429
https://github.com/spring-cloud/spring-cloud-gateway/issues/374

image

說實(shí)話,感覺是版本問題,但是又看到了一篇國人的文章,地址是:http://xiaoqiangge.com/aritcle/1545889008833.html,問題大致類似,加了一下博主的微信,請(qǐng)教了一下,大致了解到了,升級(jí)了一下版本,問題解決。

感謝小強(qiáng)哥?。?!

4.問題三 下游用戶過濾器跳轉(zhuǎn)失效

問題是這樣的,剛剛介紹了,用戶在其他地方登錄會(huì)自動(dòng)跳轉(zhuǎn)至一個(gè)界面提示給用戶,發(fā)現(xiàn)問題是無法跳轉(zhuǎn)。

查看gateway日志,大概提示了這樣一句話,如下:

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

從內(nèi)容大致可以看出,重定向有問題,想到了在用戶過濾器中最后的重定向,決定在這里下手,修改后內(nèi)容如下:

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");

問題也解決了,目前還在踩坑測試中,如果大家有類似經(jīng)驗(yàn)可以一起探討。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 帥氣的小姑娘 第一次到我們學(xué)校來
    ZQJ_0280閱讀 115評(píng)論 0 0
  • 我是一個(gè)很普通的大學(xué)生,或者不算是個(gè)大學(xué)生,上的是大專,學(xué)校面臨升本,雖然只是一個(gè)二本,然而跟我并沒有多大關(guān)系。...
    299123閱讀 194評(píng)論 0 0

友情鏈接更多精彩內(nèi)容