From 7a936b6187890be3c35b48aac4fb0423de17df67 Mon Sep 17 00:00:00 2001 From: zhangshuai Date: Tue, 3 Sep 2024 14:12:38 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20ASW-52=20device=20novnc=20websocket=20?= =?UTF-8?q?=E4=BB=A3=E7=90=86=E6=8E=A5=E5=8F=A3=E5=BC=80=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../websocket/DeviceWebSocketHandler.java | 128 ++++++++++++++++++ .../config/websocket/WebSocketConfig.java | 23 ++++ .../websocket/WebSocketInterceptor.java | 36 +++++ .../net/geedge/asw/common/util/Constants.java | 4 + 4 files changed, 191 insertions(+) create mode 100644 src/main/java/net/geedge/asw/common/config/websocket/DeviceWebSocketHandler.java create mode 100644 src/main/java/net/geedge/asw/common/config/websocket/WebSocketConfig.java create mode 100644 src/main/java/net/geedge/asw/common/config/websocket/WebSocketInterceptor.java diff --git a/src/main/java/net/geedge/asw/common/config/websocket/DeviceWebSocketHandler.java b/src/main/java/net/geedge/asw/common/config/websocket/DeviceWebSocketHandler.java new file mode 100644 index 0000000..9fee936 --- /dev/null +++ b/src/main/java/net/geedge/asw/common/config/websocket/DeviceWebSocketHandler.java @@ -0,0 +1,128 @@ +package net.geedge.asw.common.config.websocket; + + +import cn.hutool.json.JSONObject; +import cn.hutool.log.Log; +import net.geedge.asw.common.util.Constants; +import net.geedge.asw.common.util.T; +import net.geedge.asw.module.device.entity.DeviceEntity; +import net.geedge.asw.module.device.service.IDeviceService; +import org.springframework.stereotype.Component; +import org.springframework.web.socket.*; +import org.springframework.web.socket.handler.TextWebSocketHandler; + +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.WebSocket; +import java.nio.ByteBuffer; +import java.util.concurrent.CompletionStage; + +@Component +public class DeviceWebSocketHandler extends TextWebSocketHandler { + private static final Log log = Log.get(); + + /** + * device id + */ + private String id; + + /** + * device token + */ + private String token; + + + private IDeviceService deviceService; + + public DeviceWebSocketHandler(IDeviceService deviceService) { + this.deviceService = deviceService; + } + + private void initFieldVal(WebSocketSession session) { + this.id = (String) session.getAttributes().get("id"); + this.token = (String) session.getAttributes().get("token"); + } + + @Override + public void afterConnectionEstablished(WebSocketSession session) throws Exception { + super.afterConnectionEstablished(session); + + this.initFieldVal(session); + log.info("WebSocket connectioned. after connection established open device begin... device id: {}", id); + + + DeviceEntity deviceEntity = deviceService.queryInfo(id); + JSONObject paramJSONObject = deviceEntity.getParamJSONObject(); + + if (T.StrUtil.equals(token, paramJSONObject.getStr("token"))) { + log.warn("WebSocket connectioned error. device token exception. device id: {}, token: {}", id, token); + session.sendMessage(new TextMessage("Token error, Please config device token")); + session.close(); + return; + } + + String urlStr = String.format("ws://%s:%s%s", paramJSONObject.getStr("host"), paramJSONObject.getStr("port"), Constants.DEVICE_API_WEBSOCKET_PATH); + + HttpClient client = HttpClient.newHttpClient(); + WebSocket webSocket = client.newWebSocketBuilder() + .buildAsync(URI.create(urlStr), new WebSocketListener(session)) + .get(); + + log.info("[afterConnectionEstablished] [device server: {}]", T.JSONUtil.toJsonStr(paramJSONObject)); + session.getAttributes().put("deviceWebsocket", webSocket); + + } + + // WebSocket 监听器实现 + private static class WebSocketListener implements WebSocket.Listener { + private WebSocketSession session; + + public WebSocketListener(WebSocketSession session) { + this.session = session; + } + + + @Override + public CompletionStage onBinary(WebSocket webSocket, ByteBuffer data, boolean last) { + + try { + // device -> asw + session.sendMessage(new BinaryMessage(data, true)); + } catch (IOException e) { + throw new RuntimeException(e); + } + return WebSocket.Listener.super.onBinary(webSocket, data, last); + } + + @Override + public CompletionStage onClose(WebSocket webSocket, int statusCode, String reason) { + log.info("Device webSocket connection closed, Status: " + statusCode + ", Reason: " + reason); + return WebSocket.Listener.super.onClose(webSocket, statusCode, reason); + } + } + + @Override + protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) { + try { + // asw -> device api + WebSocket vncSocket = (WebSocket) session.getAttributes().get("deviceWebsocket"); + if (vncSocket != null) { + vncSocket.sendBinary(message.getPayload(), true); + } + } catch (Exception e) { + log.error(e, "[handleBinaryMessage] [error]"); + } + } + + @Override + public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { + log.info("[afterConnectionClosed] [WebSocket connection closed] [websocket uri: {}]", session.getUri()); + WebSocket deviceWebsocket = (WebSocket) session.getAttributes().get("deviceWebsocket"); + if (deviceWebsocket != null) { + deviceWebsocket.sendClose(WebSocket.NORMAL_CLOSURE, "Normal closure"); + } + super.afterConnectionClosed(session, status); + } + +} \ No newline at end of file diff --git a/src/main/java/net/geedge/asw/common/config/websocket/WebSocketConfig.java b/src/main/java/net/geedge/asw/common/config/websocket/WebSocketConfig.java new file mode 100644 index 0000000..6100c3e --- /dev/null +++ b/src/main/java/net/geedge/asw/common/config/websocket/WebSocketConfig.java @@ -0,0 +1,23 @@ +package net.geedge.asw.common.config.websocket; + +import net.geedge.asw.module.device.service.IDeviceService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.socket.config.annotation.EnableWebSocket; +import org.springframework.web.socket.config.annotation.WebSocketConfigurer; +import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; + +@Configuration +@EnableWebSocket +public class WebSocketConfig implements WebSocketConfigurer { + + @Autowired + private IDeviceService deviceService; + + @Override + public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { + registry.addHandler(new DeviceWebSocketHandler(deviceService), "/api/v1/device/{id}/novnc") + .addInterceptors(new WebSocketInterceptor()) + .setAllowedOrigins("*"); + } +} diff --git a/src/main/java/net/geedge/asw/common/config/websocket/WebSocketInterceptor.java b/src/main/java/net/geedge/asw/common/config/websocket/WebSocketInterceptor.java new file mode 100644 index 0000000..cd60747 --- /dev/null +++ b/src/main/java/net/geedge/asw/common/config/websocket/WebSocketInterceptor.java @@ -0,0 +1,36 @@ +package net.geedge.asw.common.config.websocket; + +import jakarta.servlet.http.HttpSession; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.http.server.ServletServerHttpRequest; +import org.springframework.stereotype.Component; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor; +import org.springframework.web.util.UriTemplate; + + +import java.util.Map; + +@Component +public class WebSocketInterceptor extends HttpSessionHandshakeInterceptor { + + @Override + public synchronized boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map attributes) throws Exception { + if (request instanceof ServletServerHttpRequest) { + ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request; + String servletPath = servletRequest.getServletRequest().getServletPath(); + UriTemplate template = new UriTemplate("/api/v1/device/{id}/novnc"); + Map variables = template.match(servletPath); + attributes.put("id", variables.get("id")); + attributes.put("token", servletRequest.getServletRequest().getParameter("token")); + } + return super.beforeHandshake(request, response, wsHandler, attributes); + } + + @Override + public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) { + super.afterHandshake(request, response, wsHandler, exception); + } + +} diff --git a/src/main/java/net/geedge/asw/common/util/Constants.java b/src/main/java/net/geedge/asw/common/util/Constants.java index a44fc3c..4cb4ea1 100644 --- a/src/main/java/net/geedge/asw/common/util/Constants.java +++ b/src/main/java/net/geedge/asw/common/util/Constants.java @@ -69,4 +69,8 @@ public class Constants { .put("Access-Control-Max-Age", "18000").put("Access-Control-Allow-Origin", "*").build(); + /** + * device api websocket path + */ + public static final String DEVICE_API_WEBSOCKET_PATH = "/api/v1/device/websocket"; }