-
Notifications
You must be signed in to change notification settings - Fork 3
- Improve WebSocket handler utility class #41
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…ssion management Signed-off-by: Mario Serrano <[email protected]>
…m.xml Signed-off-by: Mario Serrano <[email protected]>
…on in SchedulerUtil Signed-off-by: Mario Serrano <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR improves the WebSocket handler utility class by implementing bidirectional heartbeat support (PING/PONG), refactoring session management, and bumping the framework version to 5.4.10. The changes add server-side heartbeat broadcasting capabilities and improve error handling when sending WebSocket messages.
Key changes:
- Added bidirectional PING/PONG heartbeat mechanism between client and server
- Refactored WebSocket session management with a new
DeskstopWebSocketSessionrecord wrapper - Improved error handling with separate IOException handling and session cleanup
- Bumped all module versions from 5.4.9 to 5.4.10
Reviewed changes
Copilot reviewed 21 out of 21 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
| zk/src/main/resources/static/dynamia-tools/js/dynamia-tools-ws.js | Increased keep-alive ping interval from 30s to 60s; added server PING handling with PONG response |
| zk/src/main/java/tools/dynamia/zk/websocket/WebSocketPushSender.java | Added broadcastHeartbeat() method; improved error handling with IOException separation; changed getHandler() visibility to public |
| zk/src/main/java/tools/dynamia/zk/websocket/WebSocketGlobalCommandHandler.java | Refactored session storage with DeskstopWebSocketSession record; added PONG handling; simplified session lookup logic; added closeSession() method |
| integration/src/main/java/tools/dynamia/integration/scheduling/SchedulerUtil.java | Renamed method from scheduleWithFixedDelay to scheduleAtFixedRate with corrected documentation |
| Multiple pom.xml files | Version bump from 5.4.9 to 5.4.10 across all modules |
| LOGGER.error("IO Error sending push command '" + command + "' to Dekstop: " + desktop, e); | ||
| handler.closeSession(desktop); | ||
| } catch (Exception e) { | ||
| LOGGER.error("Error sending push command '" + command + "' to Dekstop: " + desktop, e); |
Copilot
AI
Dec 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Spelling error: "Dekstop" should be "Desktop"
| LOGGER.error("IO Error sending push command '" + command + "' to Dekstop: " + desktop, e); | |
| handler.closeSession(desktop); | |
| } catch (Exception e) { | |
| LOGGER.error("Error sending push command '" + command + "' to Dekstop: " + desktop, e); | |
| LOGGER.error("IO Error sending push command '" + command + "' to Desktop: " + desktop, e); | |
| handler.closeSession(desktop); | |
| } catch (Exception e) { | |
| LOGGER.error("Error sending push command '" + command + "' to Desktop: " + desktop, e); |
| LOGGER.error("IO Error sending push command '" + command + "' to Dekstop: " + desktop, e); | ||
| handler.closeSession(desktop); | ||
| } catch (Exception e) { | ||
| LOGGER.error("Error sending push command '" + command + "' to Dekstop: " + desktop, e); |
Copilot
AI
Dec 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Spelling error: "Dekstop" should be "Desktop"
| LOGGER.error("IO Error sending push command '" + command + "' to Dekstop: " + desktop, e); | |
| handler.closeSession(desktop); | |
| } catch (Exception e) { | |
| LOGGER.error("Error sending push command '" + command + "' to Dekstop: " + desktop, e); | |
| LOGGER.error("IO Error sending push command '" + command + "' to Desktop: " + desktop, e); | |
| handler.closeSession(desktop); | |
| } catch (Exception e) { | |
| LOGGER.error("Error sending push command '" + command + "' to Desktop: " + desktop, e); |
| log("Associating desktop " + desktopId + " with ws session " + session.getId()); | ||
| desktops.put(desktopId, session.getId()); | ||
| log("Associating desktop " + payload + " with ws session " + session.getId()); | ||
| sessions.put(session.getId(), new DeskstopWebSocketSession(payload, session)); |
Copilot
AI
Dec 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Spelling error in record name: "DeskstopWebSocketSession" should be "DesktopWebSocketSession"
| } | ||
| } | ||
| return session; | ||
| public DeskstopWebSocketSession findSession(Desktop desktop) { |
Copilot
AI
Dec 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Spelling error in record name: "DeskstopWebSocketSession" should be "DesktopWebSocketSession"
| */ | ||
| Collection<WebSocketSession> getAllSessions() { | ||
| return sessions.values(); | ||
| public Map<String, DeskstopWebSocketSession> getSessions() { |
Copilot
AI
Dec 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Spelling error in record name: "DeskstopWebSocketSession" should be "DesktopWebSocketSession"
| var oldSession = sessions.get(payload); | ||
| if (oldSession != null) { | ||
| oldSession.close(CloseStatus.NORMAL); |
Copilot
AI
Dec 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential bug in session replacement logic: When a desktop re-registers, the old session is retrieved using sessions.get(payload) where payload is the desktop ID, but sessions is now a map keyed by session ID, not desktop ID. This will always return null, preventing the cleanup of old sessions when a desktop reconnects. The old logic correctly looked up sessions by desktop ID first, then session ID.
| var oldSession = sessions.get(payload); | |
| if (oldSession != null) { | |
| oldSession.close(CloseStatus.NORMAL); | |
| DeskstopWebSocketSession oldSession = null; | |
| String oldSessionId = null; | |
| for (Map.Entry<String, DeskstopWebSocketSession> entry : sessions.entrySet()) { | |
| if (payload.equals(entry.getValue().getDesktopId())) { | |
| oldSession = entry.getValue(); | |
| oldSessionId = entry.getKey(); | |
| break; | |
| } | |
| } | |
| if (oldSession != null) { | |
| oldSession.close(CloseStatus.NORMAL); | |
| sessions.remove(oldSessionId); |
| broadcastCommand("PING"); | ||
| } | ||
|
|
||
|
|
Copilot
AI
Dec 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] API design: Changing the visibility of getHandler() from private to public makes an internal utility method part of the public API without documentation explaining when external code should use it. Consider adding Javadoc to explain the purpose and usage of this method, or evaluate if this visibility change is necessary.
| /** | |
| * Returns the global {@link WebSocketGlobalCommandHandler} instance used to manage all WebSocket sessions and commands. | |
| * <p> | |
| * This method allows external code to access the handler for advanced operations, such as querying sessions or sending custom commands. | |
| * Use with caution, as direct manipulation of the handler may affect all connected clients. | |
| * | |
| * @return the global {@link WebSocketGlobalCommandHandler} instance, or {@code null} if not available | |
| * | |
| * Example: | |
| * <pre>{@code | |
| * WebSocketGlobalCommandHandler handler = WebSocketPushSender.getHandler(); | |
| * if (handler != null) { | |
| * handler.getSessions().forEach((desktop, session) -> { | |
| * // Custom logic | |
| * }); | |
| * } | |
| * }</pre> | |
| */ |
| private final Map<String, String> desktops = new ConcurrentHashMap<>(); | ||
| private final Map<String, WebSocketSession> sessions = new ConcurrentHashMap<>(); | ||
| // Map of active WebSocket sessions by session ID | ||
| private final Map<String, DeskstopWebSocketSession> sessions = new ConcurrentHashMap<>(); |
Copilot
AI
Dec 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Spelling error in record name: "DeskstopWebSocketSession" should be "DesktopWebSocketSession". This typo appears in the type declaration and affects the entire class structure.
| } catch (IOException e) { | ||
| LOGGER.error("Error sending command " + command + " to WS Session: " + s); | ||
| LOGGER.error("IO Error sending command " + command + " to WS Session: " + s, e); | ||
| s.close(CloseStatus.NORMAL); |
Copilot
AI
Dec 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential resource leak: When an IOException occurs during broadcast, the session is closed with CloseStatus.NORMAL, but it's not removed from the sessions map. This differs from the pattern in the sendPushCommand method where handler.closeSession(desktop) is called to properly clean up the session. Consider using a similar cleanup approach here to ensure the session is removed from the map.
| s.close(CloseStatus.NORMAL); | |
| // Properly clean up the session by removing it from the handler | |
| handler.closeSession(s.getDesktop()); |
| public Map<String, DeskstopWebSocketSession> getSessions() { | ||
| return sessions; |
Copilot
AI
Dec 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
API design: The return type has changed from Collection<WebSocketSession> to Map<String, DeskstopWebSocketSession>, which is a breaking change for any code that calls this method. The new type exposes internal session IDs and the misspelled DeskstopWebSocketSession type. Consider whether this should return an unmodifiable collection or view to prevent external modification of the internal session map.
No description provided.