I find websocket will closed when my client(Chrome 65.0.3325.181 windows10 x64) offline,this is I expected. But is not close in my server(JavaWeb project, Tomcat 7.0.79 & 8.5.24).
How can I close the websocket in server when the client offline?
JS code:
var ws;
var wsUrl = 'wss://' + location.host + '/test-websocket';
window.onload = function () {
createWebSocket(wsUrl);
};
window.onbeforeunload = function () {
ws.close();
};
function createWebSocket(url) {
ws = new WebSocket(url);
initEventHandle();
}
function initEventHandle() {
ws.onerror = function () {
console.log("error");
};
ws.onclose = function () {
console.log("close");
};
ws.onopen = function () {
setInterval(function () {
var message = {
id: "ping"
};
sendMessage(message);
}, 5000);
};
ws.onmessage = function (message) {
console.info('Received message: ' + message.data);
}
}
function sendMessage(message) {
const jsonMessage = JSON.stringify(message);
console.log('Senging message: ' + jsonMessage);
ws.send(jsonMessage);
}
Java Code:
private static final Gson GSON = new GsonBuilder().create();
#OnMessage
public void onMessage(String message, final Session session) throws IOException {
JsonObject jsonMessage = GSON.fromJson(message, JsonObject.class);
System.out.println(jsonMessage);
session.getBasicRemote().sendText("pong");
}
#OnError
public void onError(Throwable error) {
error.printStackTrace();
}
#OnClose
public void onClose(CloseReason reason) {
System.out.println(reason.toString());
}
In general close, for example close webpage, I can see "CloseReason: code [1000], reason [null]" in server output.
But when client offline, the onclose will work in client, log "close" in console. But nothing will print in server output.
This is why I think websocket onclose not work in Java when client offline.
So, how can I close the websocket in server when the client offline?
Related
In Spring Boot, when we try to send a Server Sent Event, it only sends an error event containing data: {"timeout":-1} when we try to connect, and the connection closes. The Spring Boot class is as follows
#RestController
#CrossOrigin(origins = "*")
public class SsePushNotificationRestController {
private static final Logger log = LoggerFactory.getLogger(SsePushNotificationRestController.class);
private SseEmitter emitter;
#GetMapping("/test")
public String getString(){
try {
emitter.send("User connected");
log.info("User connected");
emitter.complete();
} catch (Exception e) {
log.info("Error while sending message to client: " + e.getMessage());
}
return "placeholder";
}
#GetMapping("/emitter")
public SseEmitter eventEmitter(#RequestParam String userId) {
emitter = new SseEmitter(-1L);
return emitter;
}
}
And our client code is as follows:
const eventSource = new EventSource('http://localhost:8080/emitter?userId=testUser');
eventSource.addEventListener("message", (event) => {
console.log(event);
});
eventSource.addEventListener("open", (event) => {
console.log("connection opened");
});
eventSource.addEventListener("error", (e) => {
if (e.readyState === EventSource.CLOSED) {
console.log('closed');
}
else {
console.log(e);
}
e.target.close();
});
document.getElementById("btn").onclick = e => {
fetch('http://localhost:8080/test').then( data => console.log(data)).catch(data => console.log(data));
};
Immediately, an error is created before we can click the button to generate an event.
What could be wrong?
What does your Spring boot terminal say? I think I need that information to address your program's error. By the way allowing cross origin resources sharing for requests from any sources (using wildcard) is a very very bad practice.
One possible reason of error is something's wrong when you create an instance of SSEemitter. (new SSeEmitter(-1L))
SSeEmitter(Long timeout) is creating server side event with set timeout it says. So if timeout is -1, I guess it would immediately be timed out and return timeout response. So it wouldn't be error, just working as written
I need to establish connection between client websocket threw my backend on spring java to another websocket where my backend is a client, I established connection as client but can't figure out how to send it back as soon as my client send me message,
My Client Endpoint works as I need
#Service
#ClientEndpoint
public class ClientEndpoint {
Session userSession = null;
private MessageHandler messageHandler;
public WbsClientEndpoint(#Value("${url}") String url) {
try {
WebSocketContainer container = ContainerProvider.getWebSocketContainer();
container.connectToServer(this, new URI(url));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
#OnOpen
public void onOpen(Session userSession) {
System.out.println("opening web socket");
this.userSession = userSession;
}
#OnClose
public void onClose(Session userSession, CloseReason reason) {
System.out.println("closing web socket");
this.userSession = null;
}
#OnMessage
public void onMessage(String message) {
if (this.messageHandler != null) {
this.messageHandler.handleMessage(message);
}
}
public void addMessageHandler(MessageHandler msgHandler) {
this.messageHandler = msgHandler;
}
public void sendMessage(String message) {
this.userSession.getAsyncRemote().sendText(message);
}
public interface MessageHandler {
void handleMessage(String message);
}
}
and example of method when I send my message as client, it does what I need but now I only printing the message cause cannot connect it to my message handler:
#Override
public void addDevice(DeviceTransfer deviceTransfer) {
clientEndPoint.addMessageHandler(message -> {
System.out.println("Response: " + message);
});
clientEndPoint.sendMessage(JSON.toJSONString(deviceTransfer));
}
Also I wrote a websockethandler for messages that comes to my backend:
#Component
public class WebSocketHandler extends AbstractWebSocketHandler {
#Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws IOException {
System.out.println("New Text Message Received" + message + " ___ " + session);
String clientMessage = message.getPayload();
if (clientMessage.startsWith("/addDevice")) {
//Here I don't know how to send this clientMessage and wait for result from my addDevice method to return it back
}
}
}
And I need to connect both of the realizations to establish connection in real time.
When client sends message to me I must send this message to another server as client.
My client code on JavaScript as example, when I press button it establish connection to my web socket and send my message:
const connectBtn = document.getElementById('connect');
if (connectBtn) {
connectBtn.addEventListener('click', function () {
window.socket1 = new WebSocket("ws://localhost:8081/ws");
socket1.onopen = function(e) {
console.log("[open]");
socket1.send(JSON.stringify({"command": "subscribe","identifier":"{\"channel\":\"/topic/addDevice\"}"}))
};
socket1.onmessage = function(event) {
console.log(`[message]: ${event.data}`);
};
socket1.onclose = function(event) {
if (event.wasClean) {
console.log(`[close],code=${event.code}reason=${event.reason}`);
} else {
console.log('[close]');
}
};
socket1.onerror = function(error) {
console.log(`[error] ${error.message}`);
};
});
}
It seems to me like you need to keep track of your ClientEndpoint instances:
public class ClientEndpoint {
private HashSet<ClientEndpoint> endpoints = new HashSet<>();
// or perhaps a HashMap using `userSession.id` or similar
private HashMap<string, ClientEndpoint> userToEndpoint = new HahsMap<>();
#OnOpen
public void onOpen(Session userSession) {
System.out.println("opening web socket");
this.userSession = userSession;
this.endpoints.add(this);
this.userToEndpoint.put(userSession.id, this);
}
#OnClose
public void onClose(Session userSession, CloseReason reason) {
System.out.println("closing web socket");
this.endpoints.remove(this);
this.userToEndpoint.delete(userSession.id);
this.userSession = null;
}
}
You can use endpoints/userToEndpoint to find all connected clients, perhaps filter by which rooms they're in (I assume that's what you're trying to accomplish with that subscribe command), and do whatever you want with that information. E.g. a broadcast to everyone except the sender:
for (ClientEnpoint endpoint : this.endpoints) {
if (endpoint == sender) continue; // Don't send to sender
endpoint.sendMessage("message");
}
I build up with Vertx SockJs an Eventbus Bridge.
This is the code for my verticle:
#Override
public void start() throws Exception {
Router router = Router.router(vertx);
SockJSHandler sockJSHandler = SockJSHandler.create(vertx);
BridgeOptions options = new BridgeOptions();
options.addInboundPermitted(new PermittedOptions().setAddress("test"));
options.addOutboundPermitted(new PermittedOptions().setAddress("test"));
options.addInboundPermitted(new PermittedOptions().setAddress("test2"));
options.addOutboundPermitted(new PermittedOptions().setAddress("test2"));
sockJSHandler.bridge(options);
router.route("/eventbus/*").handler(sockJSHandler);
vertx.createHttpServer().requestHandler(router::accept).listen(8600);
vertx.setTimer(5000, id -> {
vertx.eventBus().send("test", "hallo!", async -> {
if (async.succeeded()) {
System.out.println("Success!");
} else {
System.out.println("Failure!");
System.out.println(async.cause());
}
});
System.out.println("SEND!");
});
}
This is the code of ClientHtml:
var eb = new EventBus('http://localhost:8600/eventbus');
eb.onError=function() {
console.log('error');
}
eb.onopen = function() {
console.log('connected');
// set a handler to receive a message
eb.registerHandler('test', function(error, message) {
console.log('received a message: ' + JSON.stringify(message));
$( "#text" ).html(JSON.stringify(message));
});
eb.registerHandler('test2', function(error, message) {
console.log('received a message: ' + JSON.stringify(message));
console.log("Error: "+error);
$( "#text2" ).html(JSON.stringify(message));
});
}
eb.onclose = function() {
console.log("disconnected");
eb = null;
};
Now what Im concerned about:
After my verticle created a connection with the client, all is ok. But when Im restarting my verticle Im getting NO_HANDLER errors, because there is likely no new instance of Eventbus? Is there a way to handle this?
You can put your setup code in a method called after the page is loaded. In the onclose callback, cleanup all reply handlers (you will never get the server response) and call your setup method again.
function setupEventBus() {
var eb = new EventBus(window.location.protocol + "//" + window.location.host + "/eventbus");
eb.onclose = function (e) {
// Cleanup reply handlers
var replyHandlers = eb.replyHandlers;
for (var address in replyHandlers) {
if (replyHandlers.hasOwnProperty(address)) {
replyHandlers[address]({
failureCode: -1,
failureType: "DISCONNECT",
message: "EventBus closed"
});
}
}
// Setup the EventBus object again (after some time)
setTimeout(setupEventBus, 1000);
};
eb.onopen = function () {
// Register your handlers here
};
}
I'm trying to do a connection between a server in Java and a JavaScript client but I'm getting this error on client side:
WebSocket connection to 'ws://127.0.0.1:4444/' failed: Connection closed before receiving a handshake response
It maybe stays on OPENNING state because the connection.onopen function is never called. The console.log('Connected!') isn't being called.
Could someone let me know what is going wrong here?
Server
import java.io.IOException;
import java.net.ServerSocket;
public class Server {
public static void main(String[] args) throws IOException {
try (ServerSocket serverSocket = new ServerSocket(4444)) {
GameProtocol gp = new GameProtocol();
ServerThread player= new ServerThread(serverSocket.accept(), gp);
player.start();
} catch (IOException e) {
System.out.println("Could not listen on port: 4444");
System.exit(-1);
}
}
}
ServerThread
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
public class ServerThread extends Thread{
private Socket socket = null;
private GameProtocol gp;
public ServerThread(Socket socket, GameProtocol gp) {
super("ServerThread");
this.socket = socket;
this.gp = gp;
}
public void run() {
try (
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
BufferedReader in = new BufferedReader(
new InputStreamReader(
socket.getInputStream()));
) {
String inputLine, outputLine;
while ((inputLine = in.readLine()) != null) {
outputLine = gp.processInput(inputLine);
System.out.println(outputLine);
}
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
GameProtocol
public class GameProtocol {
public String processInput(String theInput) {
String theOutput = null;
theOutput = theInput;
return theOutput;
}
}
Client
var connection = new WebSocket('ws://127.0.0.1:4444');
connection.onopen = function () {
console.log('Connected!');
connection.send('Ping'); // Send the message 'Ping' to the server
};
// Log errors
connection.onerror = function (error) {
console.log('WebSocket Error ' + error);
};
// Log messages from the server
connection.onmessage = function (e) {
console.log('Server: ' + e.data);
};
To start with, both your code looks identical the Java and JavaScript one. Both work for what they are design to, but the facts is that you are trying to connect a WebSocket client to a socket server.
As I know they are two different things regarding this answer.
I have never tried it your way. That said if I have a network application that use socket than it would be pure client/server socket, and if it was a web application than I would use WebSocket on both side as well.
So far so good..
To make this work, this answer suggests to use any available WebSocket on server side and your problem is solved.
I am using WebSocket for Java and here is a sample implementation that I have tested with your client code and it works, both on client and server side.
import org.java_websocket.WebSocket;
import org.java_websocket.handshake.ClientHandshake;
import org.java_websocket.server.WebSocketServer;
import java.net.InetSocketAddress;
import java.util.HashSet;
import java.util.Set;
public class WebsocketServer extends WebSocketServer {
private static int TCP_PORT = 4444;
private Set<WebSocket> conns;
public WebsocketServer() {
super(new InetSocketAddress(TCP_PORT));
conns = new HashSet<>();
}
#Override
public void onOpen(WebSocket conn, ClientHandshake handshake) {
conns.add(conn);
System.out.println("New connection from " + conn.getRemoteSocketAddress().getAddress().getHostAddress());
}
#Override
public void onClose(WebSocket conn, int code, String reason, boolean remote) {
conns.remove(conn);
System.out.println("Closed connection to " + conn.getRemoteSocketAddress().getAddress().getHostAddress());
}
#Override
public void onMessage(WebSocket conn, String message) {
System.out.println("Message from client: " + message);
for (WebSocket sock : conns) {
sock.send(message);
}
}
#Override
public void onError(WebSocket conn, Exception ex) {
//ex.printStackTrace();
if (conn != null) {
conns.remove(conn);
// do some thing if required
}
System.out.println("ERROR from " + conn.getRemoteSocketAddress().getAddress().getHostAddress());
}
}
On your main method just:
new WebsocketServer().start();
You might need to manipulate your code to fit it with this implementation, but that should be part of the job.
Here is the test output with 2 tests:
New connection from 127.0.0.1
Message from client: Ping
Closed connection to 127.0.0.1
New connection from 127.0.0.1
Message from client: Ping
here is WebSocket maven configuration, otherwise download the JAR file/s manually and import it in your IDE/development environment:
<!-- https://mvnrepository.com/artifact/org.java-websocket/Java-WebSocket -->
<dependency>
<groupId>org.java-websocket</groupId>
<artifactId>Java-WebSocket</artifactId>
<version>1.3.0</version>
</dependency>
Link to WebSocket.
Given a websocket configuration:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfiguration extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.setApplicationDestinationPrefixes("/app");
registry.enableSimpleBroker("/queue", "/topic");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/add").withSockJS();
}
}
and client javascript:
<script type="text/javascript">
console.log('begin javascript');
var stompClient = null;
function connect() {
var socket = new SockJS('/myapp/add');
stompClient = Stomp.over(socket);
stompClient.connect({}, function(frame) {
console.log('Connected to STOMP: ' + frame);
stompClient.subscribe('/user/topic/abc', function(calResult) {
console.log('*** Got it ***');
});
});
}
connect();
</script>
and sending this message from the server:
messagingTemplate.convertAndSendToUser(username, "/topic/abc", "hello");
the callback never gets fired.
The javascript console shows that the connection is made:
Connected to STOMP: CONNECTED user-name:jschmoe heart-beat:0,0
version:1.1
SUBSCRIBE id:sub-0 destination:/user/topic/abc
and the tomcat console shows:
Processing SUBSCRIBE destination=/topic/abc-useryl3ovhr2
subscriptionId=sub-0 session=yl3ovhr2 user=jschmoe payload=byte[0]
and then when the message is sent:
Processing MESSAGE destination=/topic/abc-useryl3ovhr2 session=null
payload=hello
Seems like everything works except for the callback.
In my case the problem was caused by XML configuration, once I switched to Java config with #EnableWebSocketMessageBroker annotation I received the messages on client side.