spring-cloud-gateway: spring gateway lost websocket close status code and reason

I use spring gateway as a proxy websocket.

I had a problem with closing a connection with a specific status code (for example - 4401). If I connect directly my custom codes and reasons come to the client. If I use spring gateway, the code and reason are lost.

I tried different options:

  • Spring Boot 2.1.4 on client, gateway and server. Client receives: CloseStatus[code=1000, reason=null]
  • Spring Boot 2.1.4 on gateway, server and com.neovisionaries:nv-websocket-client. Client receives: WebSocketFrame(FIN=1,RSV1=0,RSV2=0,RSV3=0,Opcode=CLOSE,Length=0,CloseCode=1005,Reason=null)

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Comments: 15 (4 by maintainers)

Commits related to this issue

Most upvoted comments

Hi, any plan to fix the issue?

Hi @hamroune . I think the following steps will help you. You need to write own WebsocketRoutingFilter with fixes.

  1. Just copy code from original filter
  2. change order to
    @Override
    public int getOrder() {
        // Before NettyRoutingFilter since this routes certain http requests
        return Ordered.LOWEST_PRECEDENCE - 2;
    }
  1. and this handle method to
                @Override
                public Mono<Void> handle(WebSocketSession proxySession) {
                    Mono<Void> serverClose = proxySession.closeStatus().flatMap(session::close);
                    Mono<Void> proxyClose = session.closeStatus().flatMap(proxySession::close);
                    // Use retain() for Reactor Netty
                    Mono<Void> proxySessionSend = proxySession
                            .send(session.receive().doOnNext(WebSocketMessage::retain));
                    // .log("proxySessionSend", Level.FINE);
                    Mono<Void> serverSessionSend = session
                            .send(proxySession.receive().doOnNext(WebSocketMessage::retain));
                    // .log("sessionSend", Level.FINE);
                    return Mono.zip(proxySessionSend, serverSessionSend, serverClose, proxyClose).then();
                }
  1. create bean
    @Bean
    public YourWebSocketRoutingFilter myWebsocketRoutingFilter(WebSocketClient webSocketClient,
                                                           WebSocketService webSocketService,
                                                           ObjectProvider<List<HttpHeadersFilter>> headersFilters) {
        return new YourWebSocketRoutingFilter(webSocketClient, webSocketService, headersFilters);
    }

Hi, I’m have the same problem now. I guess no new solutions have been emerged…

Play is very simple.

Run the GatewayApplication Run the ServerApplication Run the ServerApplicationTests

test-gateway.zip

gateway_fail_pic

This issue can be reproduced quite easily.

  1. Open simple websocket directly to your Spring Boot application on port 8444 let’s say from JS: var ws = new WebSocket('ws://localhost:8444/myws;
  2. Then close it with some custom code and reason: ws.close(3000, "MY REASON");
  3. WS handler in Spring Boot application:
package com.myapp.handler;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

@Service
public class MyWsHandler extends TextWebSocketHandler {

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

    @Override
    public void afterConnectionClosed(WebSocketSession recordingSession, CloseStatus status) {
        logger.debug("[WS] Closing socket {} and status {}", recordingSession, status);
    }
}

will log following: 2019-05-29 18:27:21.735 DEBUG 5096 --- [io-8443-exec-11] c.m.handler.MyWsHandler : [WS] Closing socket StandardWebSocketSession[id=e29c3fba-b5a9-9616-086a-56ac4cbd6b57, uri=ws://localhost:8444/myws] and status CloseStatus[code=3000, reason=MY REASON]

  1. Then create simple Gateway application on port 8777 with following route config:
package com.myapp.gateway.config;

import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class TestRouteConfig {

    @Bean
    public RouteLocator routeLocator(RouteLocatorBuilder builder) {
        return builder.routes()
                .route("websocket", r -> r.path("/myws")
                        .uri("ws://localhost:8444"))
                .build();
    }
}
  1. And try to route websockets through it: var ws = new WebSocket('ws://localhost:8777/myws; and than close: ws.close(3000, "MY REASON");
  2. In Spring Boot application I see in logs that reason was cut out and status code was reset to 1000:
2019-05-30 11:34:42.367 DEBUG 5096 --- [nio-8443-exec-5] c.m.handler.MyWsHandler   : [WS] Closing socket StandardWebSocketSession[id=3035ca7a-a6d2-dadd-67e2-5167321202c2, uri=ws://localhost:8777/myws] and status CloseStatus[code=1000, reason=null]

Hope that helps.