Solve the problem of Max frame length of 65536 has been exceeded in websocket of spring cloud gateway

Published on with 0 views and 0 comments

I use gateway The version is 2020.0.1, It's used in communication websocket. It was used well , It turned out that something unusual happened one day :Max frame length of 65536 has been exceeded.

If you read the wrong information in the newspaper, you know it's because websocket The frame of is more than the default 65536 Limit , This limitation can be found in this class in the source code reactor.netty.http.websocket.WebsocketSpec You can see it in the picture .

I didn't want to solve it myself , Because this kind of problem, generally speaking, you may encounter , There should be a lot of solutions online . But in fact , I searched the Internet for a long time , The search results are almost the same article , And the process is pretty cumbersome , It's inheritance and rewriting again , It's also the configuration of the package , I've been pushed back .

therefore , I started my own exploration .

In my debug Dafa II , Starting with the upgrade handshake . Found our adapter , This is usually written in the configuration

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

There's a handle Method , To submit data

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());
}

be aware , There are also two construction methods , One of them can pass in a WebSocketService example , An example here is HandshakeWebSocketService example .

We follow the code , Enter into handleRequest Methods the internal

@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))
        );
}

be aware , Finally called. upgradeStrategy Of upgrade Methods , And this upgradeStrategy yes RequestUpgradeStrategy An implementation class , You can also create WebSocketService Instance is passed in .

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

that , We're going into this upgrade Methods take a look at , My example here is RequestUpgradeStrategy An implementation class 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);
                }));
}

Here we are. , I didn't find anything unusual . But notice the code

WebsocketServerSpec spec = buildSpec(subProtocol);

And this is called buildSpec The implementation of is in the class

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();
}

You can see ,WebsocketServerSpec Is written by a specBuilderSupplier Built , that specBuilderSupplier How did it come from ? The answer is in the construction method above the code . Yes , This specBuilderSupplier In addition to the default build , It can also come in from the outside

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

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

Point in this WebsocketServerSpec to glance at

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

Enter its parent class WebsocketSpec to glance at , Did we find what we wanted

public interface WebsocketSpec {
    ......

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

that , This long string of paths makes sense , If you don't understand , Please track the code yourself .

  1. WebSocketHandlerAdapter There's a WebSocketService example , call handleRequest Method submit data
  2. WebSocketService Inside the instance there is a RequestUpgradeStrategy example , call RequestUpgradeStrategy Of upgrade Method
  3. RequestUpgradeStrategy There's a WebsocketServerSpec.Builder Example , Used to build parameters
  4. also , And the most important , Every link , You can directly pass in the required instance as a construction parameter , Instead of using the default .

After an analysis , The final solution is simple , Modify the configuration in your configuration class to the following state

@Bean
public WebSocketHandlerAdapter handlerAdapter() {
  //  Here you can decide which implementation class and implementation method to use according to your actual situation , Numbers can be changed to configurable    Be careful , This websocketProperties It's my own configuration property class , Don't copy the past 
    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));
}

Come here , Your project should not be reported again Max frame length of 65536 has been exceeded This is the problem , I hope my solution can help you !


标题:Solve the problem of Max frame length of 65536 has been exceeded in websocket of spring cloud gateway
作者:woyehua
地址:https://blog.stormbirds.cn/articles/2022/01/21/1642729498503.html