解決spring-cloud-gateway的websocket出現Max frame length of 65536 has been exceeded的問題


我使用的gateway版本是2020.0.1,在通信時使用到了websocket。本來用得好好的,結果在某天出現了異常:Max frame length of 65536 has been exceeded。

看報錯信息就知道是因為websocket的幀超過了默認的65536限制,這個限制可以在源碼中的這個類  reactor.netty.http.websocket.WebsocketSpec 中看得到。

本來我也沒想着自己去解決,因為這種問題一般來說大家都可能會遇到,網上應該有很多的解決辦法。然而事實上,我在網上搜了半天,搜索到的結果幾乎都是同一篇文章,而且過程相當麻煩,又是繼承重寫,又是引包改配置,直接把我勸退了。

於是,我開始了自己的探索。

在我的debug大法下,從升級握手開始追蹤。找到了我們的適配器,這個一般是寫在配置中

@Bean
public WebSocketHandlerAdapter handlerAdapter() {
    return new WebSocketHandlerAdapter();
}

里面有個handle方法,用來提交數據

public WebSocketHandlerAdapter() {
    this(new HandshakeWebSocketService());
}

public WebSocketHandlerAdapter(WebSocketService webSocketService) {
    Assert.notNull(webSocketService, "'webSocketService' is required");
    this.webSocketService = webSocketService;
}

@Override
public Mono<HandlerResult> handle(ServerWebExchange exchange, Object handler) {
    WebSocketHandler webSocketHandler = (WebSocketHandler) handler;
    return getWebSocketService().handleRequest(exchange, webSocketHandler).then(Mono.empty());
}

注意到,還有兩個構造方法,其中一個可以傳入一個WebSocketService實例,這里舉例的實例是HandshakeWebSocketService實例。

我們按照代碼追蹤,進入到handleRequest方法內部

@Override
public Mono<Void> handleRequest(ServerWebExchange exchange, WebSocketHandler handler) {
    ServerHttpRequest request = exchange.getRequest();
    HttpMethod method = request.getMethod();
    HttpHeaders headers = request.getHeaders();

    if (HttpMethod.GET != method) {
        return Mono.error(new MethodNotAllowedException(
                    request.getMethodValue(), Collections.singleton(HttpMethod.GET)));
    }

    if (!"WebSocket".equalsIgnoreCase(headers.getUpgrade())) {
        return handleBadRequest(exchange, "Invalid 'Upgrade' header: " + headers);
    }

    List<String> connectionValue = headers.getConnection();
    if (!connectionValue.contains("Upgrade") && !connectionValue.contains("upgrade")) {
        return handleBadRequest(exchange, "Invalid 'Connection' header: " + headers);
    }

    String key = headers.getFirst(SEC_WEBSOCKET_KEY);
    if (key == null) {
        return handleBadRequest(exchange, "Missing \"Sec-WebSocket-Key\" header");
    }

    String protocol = selectProtocol(headers, handler);

    return initAttributes(exchange).flatMap(attributes ->
                this.upgradeStrategy.upgrade(exchange, handler, protocol,
                        () -> createHandshakeInfo(exchange, request, protocol, attributes))
        );
}

注意到,最后調用了upgradeStrategy的upgrade的方法,而這個upgradeStrategy是RequestUpgradeStrategy的一個實現類,也是是可以在創建WebSocketService實例的時候傳入的。

public HandshakeWebSocketService(RequestUpgradeStrategy upgradeStrategy) {
    Assert.notNull(upgradeStrategy, "RequestUpgradeStrategy is required");
    this.upgradeStrategy = upgradeStrategy;
}

那么,我們就進入這個upgrade方法看看,我這里舉例的實例是RequestUpgradeStrategy的一個實現類ReactorNettyRequestUpgradeStrategy。

@Override
public Mono<Void> upgrade(ServerWebExchange exchange, WebSocketHandler handler,
            @Nullable String subProtocol, Supplier<HandshakeInfo> handshakeInfoFactory) {

    ServerHttpResponse response = exchange.getResponse();
    HttpServerResponse reactorResponse = ServerHttpResponseDecorator.getNativeResponse(response);
    HandshakeInfo handshakeInfo = handshakeInfoFactory.get();
    NettyDataBufferFactory bufferFactory = (NettyDataBufferFactory) response.bufferFactory();
        URI uri = exchange.getRequest().getURI();

    // Trigger WebFlux preCommit actions and upgrade
    return response.setComplete()
                .then(Mono.defer(() -> {
                    WebsocketServerSpec spec = buildSpec(subProtocol);
                return reactorResponse.sendWebsocket((in, out) -> {
                        ReactorNettyWebSocketSession session =
                                new ReactorNettyWebSocketSession(
                                        in, out, handshakeInfo, bufferFactory, spec.maxFramePayloadLength());
                    return handler.handle(session).checkpoint(uri + " [ReactorNettyRequestUpgradeStrategy]");
                    }, spec);
                }));
}

到了這里,並沒有發現什么異樣。但是注意到這樣一句代碼

WebsocketServerSpec spec = buildSpec(subProtocol);

而這個叫buildSpec的實現就就在類里面

WebsocketServerSpec buildSpec(@Nullable String subProtocol) {
    WebsocketServerSpec.Builder builder = this.specBuilderSupplier.get();
    if (subProtocol != null) {
        builder.protocols(subProtocol);
    }
    if (this.maxFramePayloadLength != null) {
        builder.maxFramePayloadLength(this.maxFramePayloadLength);
    }
    if (this.handlePing != null) {
        builder.handlePing(this.handlePing);
    }
    return builder.build();
}

可以看到,WebsocketServerSpec是由一個specBuilderSupplier構建出來的,那么specBuilderSupplier它是怎么來的呢?答案就在代碼上面的構造方法中。是的,這個specBuilderSupplier他除了默認的構建之外,也能從外部傳進來

public ReactorNettyRequestUpgradeStrategy() {
    this(WebsocketServerSpec::builder);
}

public ReactorNettyRequestUpgradeStrategy(Supplier<WebsocketServerSpec.Builder> builderSupplier) {
    Assert.notNull(builderSupplier, "WebsocketServerSpec.Builder is required");
    this.specBuilderSupplier = builderSupplier;
}

點進這個WebsocketServerSpec看一下

public interface WebsocketServerSpec extends WebsocketSpec {
    static WebsocketServerSpec.Builder builder() {
        return new WebsocketServerSpec.Builder();
    }

    public static final class Builder extends reactor.netty.http.websocket.WebsocketSpec.Builder<WebsocketServerSpec.Builder> {
        private Builder() {
        }

        public final WebsocketServerSpec build() {
            return new WebsocketServerSpecImpl(this);
        }
    }
}

進入其父類WebsocketSpec看一下,是不是發現了我們想要的東西

public interface WebsocketSpec {
    ......

    public static class Builder<SPEC extends WebsocketSpec.Builder<SPEC>> implements Supplier<SPEC> {
        int maxFramePayloadLength = 65536;
        ......
    }
    ......
}

那么,這一長串的路徑就理通暢了,如果沒看明白的話,請自己去追蹤一下代碼。

  1. WebSocketHandlerAdapter內部有一個WebSocketService實例,調用handleRequest方法提交數據
  2. WebSocketService實例內部有一個RequestUpgradeStrategy實例,調用RequestUpgradeStrategy的upgrade方法
  3. RequestUpgradeStrategy內部有一個WebsocketServerSpec.Builder的實例,用來構建參數
  4. 並且,也是最重要的,每一環,都可以將需要的實例作為構造參數直接傳進去,而不使用默認的。

一通分析之后,最終的解決辦法也很簡單,在你的配置類中修改配置成如下狀態

@Bean
public WebSocketHandlerAdapter handlerAdapter() {
  // 這里可以根據自己的實際情況決定使用哪種實現類和實現方式,數字可以改成可配置的 注意,這個websocketProperties是我自己的寫的配置屬性類,不要照搬過去啊
int frameSizeLimit = webSocketProperties().getFrameSizeLimit() <= 0 ? 65536 : webSocketProperties().getFrameSizeLimit(); WebsocketServerSpec.Builder builder = WebsocketServerSpec.builder().maxFramePayloadLength(frameSizeLimit); RequestUpgradeStrategy upgradeStrategy = new ReactorNettyRequestUpgradeStrategy(builder); return new WebSocketHandlerAdapter(new HandshakeWebSocketService(upgradeStrategy)); }

有沒有更加更加簡單的辦法呢?當然有了!springboot嘛,springcloud嘛,配置文件走起啊!

spring:
  cloud:
    gateway:
      httpclient:
        websocket:
          max-frame-payload-length: xxxxxx  # 看你的需要咯

上述兩種方式,看你的喜好咯!

到這里,你的項目應該不會再報出Max frame length of 65536 has been exceeded這種問題了,希望我的解決辦法能夠幫到大家!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM