7

I'm trying to implement a websocket on using Spring Boot (1.5.13).

The messaging works fine but after about 30 minutes the connection is terminated by the server (Reason 1008 - "This connection was established under an authenticated HTTP session that has ended"). I've tried to set different timeouts but doesn't seem to have any effect.

@Service
@RequiredArgsConstructor
@Slf4j
public class OCPPSocketHandler extends TextWebSocketHandler {
 @Override
 public void handleTextMessage(WebSocketSession webSocketSession, TextMessage textMessage)
 throws IOException {
 ...
 }
}
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
 public static final String ENDPOINT = "/pp/v2.0";
 @Autowired
 private CustomSocketHandler socketHandler;
 public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
 registry.addHandler(
 new CustomExceptionWebSocketHandlerDecorator(socketHandler), ENDPOINT
 )
 .setAllowedOrigins("*");
 }
}

application.properties:

#6h as milliseconds
server.connection-timeout=3600000 
server.servlet.session.timeout=6h

A TextMessage (WebSocket) is sent every 30 minutes to keep the connection alive.

I've seen this question about session timeouts but I can't see a solution there

Michael
44.5k12 gold badges97 silver badges143 bronze badges
asked May 29, 2018 at 15:03
1
  • Reason 1008 being "POLICY_VIOLATION "1008 indicates that an endpoint is terminating the connection because it has received a message that violates its policy"? Have you checked your logs for if there is a policy violating message being sent? Commented May 29, 2018 at 15:58

4 Answers 4

4

I found my WebSocket closed after 30 minutes too.

The root reason is the http session will close after 30 minutes by default in SpringBoot.

In SpringBoot config property server.servlet.session.timeout will change the default behavior, but there might have some limit.

Besides, WebSocket connections have pingpong messages to keep alive, so the connection should never be closed, until the pingpong stops.

After some tracing, I found a way to solve this problem:

  1. The timer to close the connection is here io.undertow.server.session.InMemorySessionManager.SessionImpl in my case.
  2. As we can see io.undertow.server.session.InMemorySessionManager.SessionImpl#setMaxInactiveInterval will reset the timer.
  3. This method will be called by javax.servlet.http.HttpSession#setMaxInactiveInterval.
  4. So, once setMaxInactiveInterval is called before each timeout, the connection will never be closed.

Here is my implementation:

  1. Store the HandshakeRequest into the javax.websocket.EndpointConfig#getUserProperties in the Configurator javax.websocket.server.ServerEndpointConfig.Configurator#modifyHandshake
  2. Our clients will send String messages to keep alive, so in the onMessage method, fetch the HandshakeRequest from javax.websocket.Session#getUserProperties, then
HttpSession httpSession = (HttpSession) handshakeRequest.getHttpSession();
httpSession.setMaxInactiveInterval((int) (session.getMaxIdleTimeout() / 1000));

That's all, hope it helps.

answered Jul 29, 2019 at 14:37
Sign up to request clarification or add additional context in comments.

2 Comments

setMaxInactiveInterval will reset the timer does not work for me, but just switching to a 3h session is ok, since my client will auto connect when server brings down the session
Could you show us the detail implementation ?
1

From the Docs in 4.2.4. Server Configuration is the following, hope it can help you:

Each underlying WebSocket engine exposes configuration properties that control runtime characteristics, such as the size of message buffer sizes, idle timeout, and others.

For Tomcat, WildFly, and GlassFish, you can add a ServletServerContainerFactoryBean to your WebSocket Java config, as the following example shows:

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
 @Bean
 public ServletServerContainerFactoryBean createWebSocketContainer() {
 ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
 container.setMaxSessionIdleTimeout(120000L); // set the timeout to 2min
 // ...
 return container;
 }
}

For Jetty, you need to supply a pre-configured Jetty WebSocketServerFactory and plug that into Spring’s DefaultHandshakeHandler through your WebSocket Java config. The following example shows how to do so:

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
 @Override
 public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
 registry.addHandler(echoWebSocketHandler(),
 "/echo").setHandshakeHandler(handshakeHandler());
 }
 @Bean
 public DefaultHandshakeHandler handshakeHandler() {
 WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER);
 policy.setInputBufferSize(8192);
 policy.setIdleTimeout(600000); // set the timeout to 10min
 return new DefaultHandshakeHandler(
 new JettyRequestUpgradeStrategy(new WebSocketServerFactory(policy)));
 }
}
answered Feb 16, 2022 at 10:26

Comments

0

The connection can be closed if any between server or client closes it, and also by a firewall if it notes that there is no activity on the established connection.

So you need to check timeout also on the client side. It is reasanable that 30 minutes is the default timeout for this kind of connection so it seams that on the client side it uses default values.

Additionally it is a good design to check periodically the status of the connection, for example sending a sort of ping (message from the client) /pong (response from the server) message. If you do it every minute for example you have an idea of the connection status and the connection will never be closed for inactivity.

answered May 29, 2018 at 15:11

4 Comments

I've send a TextMessage for testing today every 2 minutes. The result stays the same. The connection is closed after ~30 minutes.
Very strange... And it closes the connection with what message?
Connection closed: /127.0.0.1:58859 Close Status: 1008 Reason: This connection was established under an authenticated HTTP session that has ended.
@Sebastian did you get any solution to resolve this issue? I'm facing the same issue. sending msg every 30s from the server to the client.
0
  1. In my case, timeout of HttpSession in SpringBoot is set to server.servlet.session.timeout: 1 (1 minute)

  2. Set websocket endpoint in Spring Security config to httpBasic

    @Override
    protected void configure(HttpSecurity http) throws Exception {
     http.csrf().disable();
     http.antMatcher("/ocpp/**")
     .authorizeRequests() 
     .anyRequest().permitAll()//.hasAnyRole("CS","SYSTEM_ADMIN")
     .and().httpBasic();
    }
    
  3. use basic authentication in websocket client

     String id = "testuser";
     String pw = "1234";
     String idpw = new String(Base64.getEncoder().encode((id+":"+pw).getBytes()));
     System.out.println("id["+id+"]pw["+pw+"]idpw["+idpw+"]");
     ;
     Map<String, String> httpHeaders = new HashMap<String, String>();
     httpHeaders.put("authorization", "Basic "+idpw);
     WebSocketClient webSocketClient = new WebSocketClient(new URI("ws://localhost:9112/ocpp/testuser")
     , new Draft_6455(Collections.emptyList(), Collections.singletonList(new Protocol("ocpp2.0.1")))
     , httpHeaders
     ) {
     ...
    

In this condition, if the endpoint is a protected resource, websocket close 1008 occurs when the HttpSession is terminated.

Reference: Read Paragraph 7.2

**The solution is to directly implement http basic processing in websocket addInterceptor. (Spring Security httpBasic not use) **

add addInterceptor in WebSocketConfig.java:

.addInterceptors(new HandshakeInterceptor() {
 @Override
 public boolean beforeHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
 log.debug("===============================================================");
 log.debug("beforeHandshake[]"+attributes);
 ServletServerHttpRequest ssreq = (ServletServerHttpRequest)serverHttpRequest;
 ServletServerHttpResponse ssres = (ServletServerHttpResponse)serverHttpResponse;
 HttpServletRequest req = ssreq.getServletRequest();
 HttpServletResponse res = ssres.getServletResponse();
 HttpSession session = req.getSession();
 log.debug("session["+session.getId());
 log.debug("session["+session.getMaxInactiveInterval());
 //authentication
 try {
 String header = req.getHeader("Authorization");
 log.debug("header[" + header + "]");
 if (header == null) {
 log.debug("The Authorization header is empty");
// throw new BadCredentialsException("The Authorization header is empty");
 } else {
 header = header.trim();
 if (!StringUtils.startsWithIgnoreCase(header, "Basic")) {
 log.debug("The Authorization header does not start with Basic.");
 } else if (header.equalsIgnoreCase("Basic")) {
 throw new BadCredentialsException("Empty basic authentication token");
 } else {
 byte[] base64Token = header.substring(6).getBytes(StandardCharsets.UTF_8);
 byte[] decoded;
 try {
 decoded = Base64.getDecoder().decode(base64Token);
 } catch (IllegalArgumentException var8) {
 throw new BadCredentialsException("Failed to decode basic authentication token");
 }
 String token = new String(decoded, "UTF-8");
 int delim = token.indexOf(":");
 if (delim == -1) {
 throw new BadCredentialsException("Invalid basic authentication token");
 } else {
 log.info("TOKEN [" +token+"]");
 String principal = token.substring(0, delim);
 String credencial = token.substring(delim + 1);
 //your 
 
 if(principal.equals("testuser") && credencial.equals("1234")){
 log.debug("login OK");
 }else{
 throw new BadCredentialsException("Invalid basic authentication token");
 }
 }
 }
 }
 }catch(Exception e){
 log.error("Basic Authentication error", e);
 res.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
 res.addHeader("WWW-Authenticate", "Basic realm=" + "Realm" + "");
 PrintWriter pw = res.getWriter();
 pw.println("Invalid status code received: 401 Status line: HTTP/1.1 401");
 return false;
 }
 return true;
 }
Ole Pannier
3,7719 gold badges26 silver badges35 bronze badges
answered Jul 24, 2021 at 13:23

Comments

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.