Spring websocket timeout settings - spring

I'm using the Spring websocket support. My question is how to set the websocket connection timeout. Now the connection is closed automatically after several minutes. I want the connection never to be closed.
Here is my websocket handler:
public class MyHandler implements WebSocketHandler {
private Logger logger = LoggerFactory.getLogger(this.getClass());
class MyTimerTask extends TimerTask {
private WebSocketSession session;
public MyTimerTask(WebSocketSession session) {
this.session = session;
}
#Override
public void run() {
try {
String msg = ((int)(Math.random()*50)) + "";
this.session.sendMessage(new TextMessage(msg.toString()));
} catch (IOException e) {
e.printStackTrace();
}
}
}
#Autowired
private UserDao userDao;
#Autowired
private JdbcDaoImpl jdbcDaoImpl;
private Timer timer;
#Override
public void afterConnectionEstablished(WebSocketSession session)
throws Exception {
System.out.println("websocket????");
timer = new Timer();
timer.schedule(new MyTimerTask(session), 0, 1000);
logger.info("logger connection");
}
#Override
public void handleMessage(WebSocketSession session,
WebSocketMessage<?> message) throws Exception { }
#Override
public void handleTransportError(WebSocketSession session,
Throwable exception) throws Exception { }
#Override
public void afterConnectionClosed(WebSocketSession session,
CloseStatus closeStatus) throws Exception {
System.out.println("websocket????");
timer.cancel();
}
#Override
public boolean supportsPartialMessages() {
return false;
}
}
my websocket config:
<websocket:handlers>
<websocket:mapping path="/myHandler" handler="myHandler"/>
</websocket:handlers>
<bean id="myHandler" class="com.sdp.websocket.MyHandler"/>
and javascript client:
var webserver = 'ws://localhost:8080/authtest/myHandler';
var websocket = new WebSocket(webserver);
websocket.onopen = function (evt) { onOpen(evt) };
websocket.onclose = function (evt) { onClose(evt) };
websocket.onmessage = function (evt) { onMessage(evt) };
websocket.onerror = function (evt) { onError(evt) };
function onOpen(evt) {
console.log("Connected to WebSocket server.");
}
function onClose(evt) {
console.log("Disconnected");
}
function onMessage(evt) {
console.log('Retrieved data from server: ' + evt.data);
}
function onError(evt) {
console.log('Error occured: ' + evt.data);
}
debugger;
function sendMsg(){
websocket.send("{msg:'hello'}");
}

The websocket stays opened until either the server or the client decide to close it. However, websockets are affected by two timeouts:
HTTP session timeout;
proxy connection timeouts;
If all you have between your client and your server is a websocket connection, and you don't interact over HTTP with AJAX or requesting other pages, the HTTP session expires and some servers decide to invalidate it along with the websocket (Tomcat7 had a bug that did just that). Some other servers don't do that because they see there is activity on the websocket. See here for an extended discussion: Need some resolution or clarification for how and when HttpSession last access time gets updated.
The other timeout is with proxies. They see the connection and if there is no activity on the wire for a longer period of time, they just cut it because they think it hanged. To address this, while you don't send actual application data, you need to have a heartbeat or a ping/pong of messages from time to time to let the proxy know that the connection is still OK.
Other issues might also intervene, like a buggy browser support for websocket, how your network is configured, firewall rules, etc.
For available timeout options in Spring see the websocket documentation: Configuring the WebSocket Engine.

#Configuration
#EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
#Bean
public ServletServerContainerFactoryBean createWebSocketContainer() {
var container = new ServletServerContainerFactoryBean();
container.setMaxSessionIdleTimeout(...);
return container;
}
}

Related

Capturing a message from unity using WebSocketSharp on a Spring server

I can't solve the problem. It is necessary to send messages to the server through the socket and process them. I can intercept the subscription, unsubscribe, connection and disconnection events on the server. But I can’t understand how to send a message to a specific destonation and intercept it on the server. On the client I use the library https://github.com/sta/websocket-sharp
Server code
#Configuration
#EnableWebSocket
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer implements WebSocketConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/multiplayer");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/multiplayer").setAllowedOrigins("*").withSockJS();
}
#Autowired
MessageHandler messageHandler;
// message interception option
#Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {
webSocketHandlerRegistry.addHandler(messageHandler, "/result-game");
}
}
In the registerWebSocketHandlers method, I register a listener, it intercepts messages only if you specify "/multiplayer/websocket" in the "/result-game" place, the only trouble is that it intercepts everything in general and the #EventListaner listeners that intercepted my connection events stop working , shutdowns, etc. And when you try to send data to the client on the socket, the client does not receive them.
Client code
var DOMAIN = "1.1.0.1:8080";
_webSocket = new WebSocket($"ws://{DOMAIN}/multiplayer/websocket");
_webSocket.OnOpen += (sender, e) =>
{
// Sending a connection message
StompMessageSerializer serializer = new StompMessageSerializer();
var connect = new StompMessage("CONNECT");
connect["accept-version"] = "1.1";
connect["heart-beat"] = "10000,10000";
connect["playerId"] = _id;
var s = serializer.Serialize(connect);
SubscribeSocket(clientId.ToString(), $"/multiplayer/connect/{_id}", ConnectServer);
_webSocket.Send(s);
// Attempt to send messages to a specific destination
var content = new { Subject = "Stomp client", Message = "Hello World!!" };
var broad = new StompMessage("SEND", JsonConvert.SerializeObject(content));
broad["content-type"] = "application/json";
broad["destination"] = "/result-game";
_webSocket.Send(serializer.Serialize(broad));
Debug.LogError(TAG + "Connect open");
};
_webSocket.ConnectAsync();
On the server, in addition to the option above, there are two more attempts to intercept
First
#Slf4j
#Controller
#RequiredArgsConstructor
public class MessageController{
#MessageMapping("/result-game")
public void say(String message) throws InterruptedException {
Thread.sleep(30);
}
}
Second
#ServerEndpoint("/result-game")
public class MessageHandle {
#OnMessage
public void handleMessage(Session session, String message) {
// Do something with the message
System.out.println("Received message: " + message);
}
#OnMessage
public void processGreeting(String message, Session session) {
System.out.println("Greeting received:" + message);
}
}
Could someone tell me what I'm doing wrong with spring and working with sockets for the first time

Auto reconnect to Binance Websocket after 24 hours

I'm currently experimenting on Binance Websocket (https://binance-docs.github.io/apidocs/spot/en/#websocket-market-streams), streaming the candlestick data for processing.
As documented, the stream will randomly close after 24 hours. What's the best way to continue the session without interruption? I mean disconnect/reconnect after 23 hours so the program will continue without losing its state.
Here's what I did. I'm using the binance-java-api https://github.com/binance-exchange/binance-java-api.
And here's how I connect:
BinanceApiWebSocketClient client =
BinanceApiClientFactory.newInstance(
appConfig.getApiKey(),
appConfig.getApiSecret(),
appConfig.isUseTestNet(),
appConfig.isUseTestNet())
.newWebSocketClient();
client.onCandlestickEvent(cryptoPair.toLowerCase(), getCandlestickInterval(),
new BinanceApiCallback<>() {
#Override
public void onResponse(final CandlestickEvent evt) {}
To solve this issue, I have used a scheduler/timer to reconnect the session every 12 hours. Since I'm using the Quarkus framework, it's readily available.
Solution:
SessionManager class:
#Singleton
#Slf4j
#RequiredArgsConstructor
public class SessionManagerScheduler {
final BinanceEventHandler binanceEventHandler;
#Scheduled(cron = "0 2 */12 * * ?")
public void reconnectSession() {
log.info("Keep-Alive: Binance Session Via WebSocket -------------------------");
binanceEventHandler.timeout();
}
}
The Binance event handler:
#ApplicationScoped
#Slf4j
#RequiredArgsConstructor
public class BinanceEventHandler {
final AppConfig appConfig;
final CandlestickAccumulator candlestickAccumulator;
final CandlestickMapper candlestickMapper;
private Closeable candleStream = null;
public void start() {
streamCandleEvent();
}
public void timeout() {
try {
candleStream.close();
streamCandleEvent();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private void streamCandleEvent() {
String cryptoPair = String.join(",", appConfig.getCryptoPairs());
log.info("Start listening to cryptoPair={}", cryptoPair);
candleStream = getClient().onCandlestickEvent(cryptoPair.toLowerCase(), getCandlestickInterval(),
new BinanceApiCallback<>() {
#Override
public void onResponse(final CandlestickEvent evt) {
if (!evt.getBarFinal()) {
return;
}
log.debug("Processing cryptoPair={}, event={}", cryptoPair, evt);
Candlestick candlestick = candlestickMapper.asCandleStick(evt);
candlestickAccumulator.processCandlestickEvent(candlestick);
}
#Override
public void onFailure(final Throwable cause) {
Application.hasError = true;
log.error("Fail connecting to Binance API {}", cause.getMessage());
}
}
);
}
private BinanceApiWebSocketClient getClient() {
return BinanceApiClientFactory.newInstance(
appConfig.getApiKey(),
appConfig.getApiSecret(),
appConfig.isUseTestNet(),
appConfig.isUseTestNet())
.newWebSocketClient();
}
private CandlestickInterval getCandlestickInterval() {
return CandlestickInterval.valueOf(appConfig.getCandlestickInterval());
}
}

Spring websocket establishing connection is stuck at 'opening connection'

I am using spring-boot-websocket (spring-boot version 1.5.10) in my project. I have configured it as below,
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig extends WebSocketMessageBrokerConfigurationSupport
implements WebSocketMessageBrokerConfigurer {
#Value( "${rabbitmq.host}" )
private String rabbitmqHost;
#Value( "${rabbitmq.stomp.port}" )
private int rabbitmqStompPort;
#Value( "${rabbitmq.username}" )
private String rabbitmqUserName;
#Value( "${rabbitmq.password}" )
private String rabbitmqPassword;
#Override
public void configureMessageBroker( MessageBrokerRegistry registry )
{
registry.enableStompBrokerRelay("/topic", "/queue").setRelayHost(rabbitmqHost).setRelayPort(rabbitmqStompPort)
.setSystemLogin(rabbitmqUserName).setSystemPasscode(rabbitmqPassword);
registry.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints( StompEndpointRegistry stompEndpointRegistry )
{
stompEndpointRegistry.addEndpoint("/ws").setAllowedOrigins("*").withSockJS();
}
#Bean
#Override
public WebSocketHandler subProtocolWebSocketHandler()
{
return new CustomSubProtocolWebSocketHandler(clientInboundChannel(), clientOutboundChannel());
}
#Override
public void configureWebSocketTransport( WebSocketTransportRegistration registry )
{
super.configureWebSocketTransport(registry);
}
#Override
public boolean configureMessageConverters( List<MessageConverter> messageConverters )
{
return super.configureMessageConverters(messageConverters);
}
#Override
public void configureClientInboundChannel( ChannelRegistration registration )
{
super.configureClientInboundChannel(registration);
}
#Override
public void configureClientOutboundChannel( ChannelRegistration registration )
{
super.configureClientOutboundChannel(registration);
}
#Override
public void addArgumentResolvers( List<HandlerMethodArgumentResolver> argumentResolvers )
{
super.addArgumentResolvers(argumentResolvers);
}
#Override
public void addReturnValueHandlers( List<HandlerMethodReturnValueHandler> returnValueHandlers )
{
super.addReturnValueHandlers(returnValueHandlers);
}
}
public class CustomSubProtocolWebSocketHandler extends SubProtocolWebSocketHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(CustomSubProtocolWebSocketHandler.class);
#Autowired
private UserCommons userCommons;
CustomSubProtocolWebSocketHandler(MessageChannel clientInboundChannel,
SubscribableChannel clientOutboundChannel) {
super(clientInboundChannel, clientOutboundChannel);
}
#Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
LOGGER.info("************************************************************************************************************************New webSocket connection was established: {}", session);
String token = session.getUri().getQuery().replace("token=", "");
try
{
String user = Jwts.parser().setSigningKey(TokenConstant.SECRET)
.parseClaimsJws(token.replace(TokenConstant.TOKEN_PREFIX, "")).getBody().getSubject();
Optional<UserModel> userModelOptional = userCommons.getUserByEmail(user);
if( !userModelOptional.isPresent() )
{
LOGGER.error(
"************************************************************************************************************************Invalid token is passed with web socket request");
throw new DataException(GeneralConstants.EXCEPTION, "Invalid user", HttpStatus.BAD_REQUEST);
}
}
catch( Exception e )
{
LOGGER.error(GeneralConstants.ERROR, e);
}
super.afterConnectionEstablished(session);
}
#Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
LOGGER.error("************************************************************************************************************************webSocket connection was closed");
LOGGER.error("Reason for closure {} Session: {} ", closeStatus.getReason(),session.getId() );
super.afterConnectionClosed(session, closeStatus);
}
#Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
LOGGER.error("************************************************************************************************************************Connection closed unexpectedly");
LOGGER.error(GeneralConstants.ERROR, exception);
super.handleTransportError(session, exception);
}
}
From the client-side, I am creating a SockJS object to establish the connection,
let url = `/ws?token=${localStorage.getItem("access_token")}`;
// Web Socket connection
/* eslint-disable */
let sockJS = new SockJS(url);
let stompClient = Stomp.over(sockJS);
debugger
this.setState({
stompObject : stompClient,
});
But the connection is not getting established consistently, most of the times it is stuck at Opening the connection, in the backend log, I can see the connection getting established and a session is created. But, in the browser console, I can see client-side sending message to the server but the server is not acknowledging the message.
Sometimes, when I refresh the browser for 10-15 times, the connection is getting established successfully. Is there any mistake in my configuration?
Thank You.
Given that you can "hit refresh 10 or 15 times and then get a connection," I'm curious if you dealing with a cookie issue? I know Chrome is famous for that sort of thing. Anyway close out all browser windows and stop the browser, then start the browser, and tell it to clear browsing history and then attempt the connection. Also, be SURE you read the version of the spring-boot docs for the version of spring-boot you are that you are actually using, and also specify the SB version in your questions and when looking for answers.

How to do unit test websocket with embedded jetty?

I want to write Junit UT case for my websocket serverendpoint code using embedded Jetty.
i tried things explained in below link:
JUnit test with javax.websocket on embedded Jetty throws RejectedExecutionException: NonBlockingThread
I want to test my onMessage callback for websocket.
If i dont use server.join() method then the connection closes as soon as it opens.
If i use server.join() method nothing happens after joining.
Below is My code.
Server startup code::
public class EmbeddedJettyServer {
private final int port;
private Server server;
public EmbeddedJettyServer(int port) {
this.port = port;
}
public void start() throws Exception {
server = new Server();
ServerConnector connector = new ServerConnector(server);
connector.setPort(8080);
server.addConnector(connector);
// Setup the basic application "context" for this application at "/"
// This is also known as the handler tree (in jetty speak)
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
context.setContextPath("/");
server.setHandler(context);
try {
// Initialize javax.websocket layer
ServerContainer wscontainer = WebSocketServerContainerInitializer.configureContext(context);
// Add WebSocket endpoint to javax.websocket layer
wscontainer.addEndpoint(WebSocketServer.class);
System.out.println("Begin start");
server.start();
server.dump(System.err);
server.join();
} catch (Throwable t) {
t.printStackTrace(System.err);
}
}
public void stop() throws Exception {
server.stop();
LOGGER.info("Jetty server stopped");
}
public URI getWebsocketUri(Class<WebSocketServer> class1) {
return server.getURI();
}
}
Client Code:
#ClientEndpoint()
public class WebSocketClientJetty {
WebSocketContainer container;
public Session connect(URI uri) throws Exception {
WebSocketContainer container = ContainerProvider.getWebSocketContainer();
try {
// Attempt Connect
Session session = container.connectToServer(WebSocketClientJetty.class,uri);
// return container.connectToServer(WebSocketClientJetty.class, uri);
session.getBasicRemote().sendText("Hello");
// Close session
// session.close();
return session;
} finally {
}
}
public void stop() throws Exception{
if (container instanceof LifeCycle) {
((LifeCycle) container).stop();
}
}
#OnOpen
public void onWebSocketConnect(Session sess)
{
System.out.println("Socket Connected: " + sess);
}
#OnMessage
public void onWebSocketText(String message)
{
System.out.println("Received TEXT message: " + message);
}
#OnClose
public void onWebSocketClose(CloseReason reason)
{
System.out.println("Socket Closed: " + reason);
}
#OnError
public void onWebSocketError(Throwable cause)
{
cause.printStackTrace(System.err);
}
}
Serverendpoint code:
#ServerEndpoint(value = "/echo",
encoders={JsonEncoder.class})
public class WebSocketServer {
private static final Logger LOGGER =
#OnOpen
public void onOpen(Session session){
System.out.println("onopen");
some code....
}
#OnMessage
public void onMessage(String message, Session session) throws IOException{
System.out.println("onmessage");
....
}
#OnClose
public void onClose(Session session){
System.out.println("onClose");
...
}
}
Ut case:
public class WebSocketJettyTest {
private static EmbeddedJettyServer server;
#ClassRule
public static final ExternalResource integrationServer = new ExternalResource() {
#Override
protected void before() throws Throwable {
System.out.println("Starting...");
server = new EmbeddedJettyServer(8080);
server.start();
System.out.println("Started");
}
};
#Before
public void setUp() throws Exception {
}
#After
public void shutdown() throws Exception {
server.stop();
}
#Test
public void testSocket() throws Exception {
/*URI uri = server.getWebsocketUri(WebSocketServer.class);*/
URI uri = URI.create("ws://localhost:8080/echo");
WebSocketClientJetty client = new WebSocketClientJetty();
Session session = client.connect(uri);
session.getBasicRemote().sendText("hello");
Thread.sleep(6000);
client.stop();
}
}
Drop the call to
server.join();
That just makes the current thread wait until the server thread stops.
Which is making it difficult for you.

websockets spring glassfish 4

I am trying to build a simple didactic websocket application using spring 4.0, jsf and glassfish 4.0.
I have created a maven web project (because this app has another web component(jsf)), and from this app i`m trying to setup some websockets.
#Configuration
#EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
#Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(echoHandler(), "/echo");
}
#Bean
public WebSocketHandler echoHandler() {
return new EchoHandler();
}
}
and
public class EchoHandler extends TextWebSocketHandler {
#Override
public void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
session.sendMessage(message);
}
}
and on the client side a very simple connect:
<script>
/* <![CDATA[ */
var endPointURL = "ws://localhost:8080/liveasterisk/echo";
var chatClient = null;
function connect () {
chatClient = new WebSocket(endPointURL);
chatClient.onmessage = function (event) {
alert(event);
};
}
function disconnect () {
chatClient.close();
}
function sendMessage() {
chatClient.send("xxx");
}
connect();
/* ]]> */
</script>
The problem is that when the connect() method fires i get a 404 response.
I guess that i have to somehow train jsf to respond to handshake request.
All my *.xhtml are mapped to jsf servlet.
So what I`m I missing here ?
I have solved the problem like this:
#ServerEndpoint(value = "/keepalive", configurator = SpringConfigurator.class)
public class KeepAliveEndpoint {
private static Logger logger = Logger.getLogger(KeepAliveEndpoint.class);
#Autowired
private KeepAliveService keepAliveService;
private List<Session> sessions = new ArrayList<Session>();
#OnOpen
public void onOpen(Session session) {
sessions.add(session);
System.out.println("onOpen: " + session.getId()+" list size: " + sessions.size());
}
#OnClose
public void onClose(Session session) {
System.out.println("onClose: " + session.getId());
sessions.remove(session);
}
#OnMessage
public void handleMessage(Session session, String message) {
try{
Long userId = Long.parseLong(message);
keepAliveService.keepAlive(userId);
}catch(NumberFormatException nfe){
try {
session.getBasicRemote().sendText("Cannot perform live update for your status");
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
so now I have a sockets exposed via jsf and I can inject "services" with #Autowired in this endpoint.
And with this js code:
<script type="text/javascript">
var host = "ws://localhost:8080/myApp/keepalive";
var wSocket = new WebSocket(host);
var browserSupport = ("WebSocket" in window) ? true : false;
// called body onLoad()
function initializeReception() {
if (browserSupport) {
wSocket.onopen = function(){
setInterval(function(){wSocket.send('<h:outputText value="#{managedBean.userDTO.id}" />')}, 300000);
};
// called when a message is received
wSocket.onmessage = function(event) {
alert(event.data);
};
// on error handler
wSocket.onError = function(event) {
alert('An error has occured '+event.data+'.');
};
// called when socket closes
wSocket.onclose = function(){
// websocket is closed.
//alert("Connection is closed...");
};
}
else {
// The browser doesn't support WebSocket
alert("WebSocket is NOT supported by your Browser!");
}
}
initializeReception();
</script>
The above configuration is for use with Spring MVC's DispatcherServlet. Do you have one configured in the web application? Depending on the servlet mapping (not shown above) you'll most likely need one more part added to the URL to match the servlet mapping.
The longer explanation is that #EnableWebSocket creates a HandlerMapping that maps "/echo" to the WebSocketHandler. That HandlerMapping needs to reside in the configuration of the DispatcherServlet in order for the HTTP handshake to be processed.

Resources