当前位置:   article > 正文

springcloud gateway转发websocket请求的404问题定位_websocket gateway 404

websocket gateway 404

一、问题

前端小程序通过springcloud gateway接入并访问后端的诸多微服务,几十个微服务相关功能均正常,只有小程序到后端推送服务的websocket连接建立不起来,使用whireshark抓包,发现在小程序通过 GET ws://192.168.6.100:8888/websocket发起的连接请求,网关返回404错误,并且发现在推送服务并未收到网关转发的任何数据,所以判断是网关的路由出了问题。通过搜索找到了springcloud gateway的关键类DispacherHandler,并首先从其handler方法入手,使用DEBUG手段,定位到问题为:

。以下针对关键代码进行梳理。

二、源码分析

1)DispacherHandler的handle方法

@Override
public Mono<Void> handle(ServerWebExchange exchange) {
   if (this.handlerMappings == null) {
      return createNotFoundError();
   }
   return Flux.fromIterable(this.handlerMappings)
         .concatMap(mapping -> mapping.getHandler(exchange))
         .next()
         .switchIfEmpty(createNotFoundError())
         .flatMap(handler -> invokeHandler(exchange, handler))
         .flatMap(result -> handleResult(exchange, result));
}

1)调用Flux.fromIterable把handlerMapping转换为一个Flux流,

2)针对流中每个元素(RouteFunctionMapping、RequestMappingHandlerMapping和RoutePredictHandlerMapping)调用getHandler方法(该方法在AbstractHandlerMapping中实现),具体代码如下:

public Mono<Object> getHandler(ServerWebExchange exchange) {
   return getHandlerInternal(exchange).map(handler -> {
      if (logger.isDebugEnabled()) {
         logger.debug(exchange.getLogPrefix() + "Mapped to " + handler);
      }
      ServerHttpRequest request = exchange.getRequest();
      if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
         CorsConfiguration config = (this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(exchange) : null);
         CorsConfiguration handlerConfig = getCorsConfiguration(handler, exchange);
         config = (config != null ? config.combine(handlerConfig) : handlerConfig);
         if (!this.corsProcessor.process(config, exchange) || CorsUtils.isPreFlightRequest(request)) {
            return REQUEST_HANDLED_HANDLER;
         }
      }
      return handler;
   });
}

3)调用RoutePredictHandlerMapping本类的getHandlerInternel方法

4)getHandlerInternel方法调用本类的lookupRoute方法,lookupRoute方法关键代码如下:

protected Mono<Route> lookupRoute(ServerWebExchange exchange) {
   return this.routeLocator.getRoutes()
         // individually filter routes so that filterWhen error delaying is not a
         // problem
         .concatMap(route -> Mono.just(route).filterWhen(r -> {
            // add the current route we are testing
            exchange.getAttributes().put(GATEWAY_PREDICATE_ROUTE_ATTR, r.getId());
            return r.getPredicate().apply(exchange);
         })

filterWhen的条件为:

(r -> {
   // add the current route we are testing
   exchange.getAttributes().put(GATEWAY_PREDICATE_ROUTE_ATTR, r.getId());
   return r.getPredicate().apply(exchange);
})

其中r是路由,r.getPredicate()为AsyncPredicate,其apply方法为:

public Publisher<Boolean> apply(T t) {
   return Mono.just(delegate.test(t));
}

其中的delegate为PathRoutePredictFactory,t为DefaultServerWebExchange,PathRoutePredictFactory的test方法为:

return new GatewayPredicate() {
   @Override
   public boolean test(ServerWebExchange exchange) {
      PathContainer path = parsePath(
            exchange.getRequest().getURI().getRawPath());
      PathPattern match = null;
      for (int i = 0; i < pathPatterns.size(); i++) {
         PathPattern pathPattern = pathPatterns.get(i);
         if (pathPattern.matches(path)) {
           match = pathPattern;
           break;
         }
      }

其中path从Request提取的URI的rawPath,pathPatterns是application.yml配置的path,遍历查找,命中则跳出并返回true,否则返回false。debug代码发现请求地址的path匹配成功。至此未发现问题。

5.转机出现

在application.yml中意外的发现针对WebSocket的请求,不知是哪位大神增加了一个ReadBodyString断言,从网上看到使用ReadBodyString断言时,如果请求的HTTP BODY为空,则返回404错误,这给了我提示。正好手上有websocket建立建立失败的网络抓包报文,报文中的BODY确实为空,具体如下:

按图索骥,找到了ReadBodyPredicateFactory类的applyAsync方法,在其中针对BODY为空的处理逻辑如下:

public AsyncPredicate<ServerWebExchange> applyAsync(Config config) {

……

return ServerWebExchangeUtils.cacheRequestBodyAndRequest(exchange,
      (serverHttpRequest) -> ServerRequest
            .create(exchange.mutate().request(serverHttpRequest)
                  .build(), messageReaders)
            .bodyToMono(inClass)  #问题在这里!!!!
            .doOnNext(objectValue -> exchange.getAttributes().put(
                  CACHE_REQUEST_BODY_OBJECT_KEY, objectValue))
            .map(objectValue -> config.getPredicate()
                  .test(objectValue)));

继续跟踪其中的bodyToMono方法,代码如下:

public <T> Mono<T> bodyToMono(Class<? extends T> elementClass) {
   Mono<T> mono = body(BodyExtractors.toMono(elementClass)); #问题在这里!!!
   return mono.onErrorMap(UnsupportedMediaTypeException.class, ERROR_MAPPER)
         .onErrorMap(DecodingException.class, DECODING_MAPPER);
}

其中的body方法用来解析BODY,但因为BODY为空,所以抛出了unsupportedMediaTypeException,在onErrorMap中处理该异常,所以最终路由未找到,客户端收到404错误。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Li_阴宅/article/detail/858223
推荐阅读
相关标签
  

闽ICP备14008679号