diff --git a/CHANGELOG.md b/CHANGELOG.md index 924de3ff8..8fbc6bc1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ (感谢 [@LeonChen21](https://gitee.com/leonchen21) [Gitee issues I67C3C](https://gitee.com/dromara/Jpom/issues/I67C3C) ) 7. 【server】优化 websocket 控制台操作日志记录 8. 【server】修复 超级管理的 websocket 操作日志记录工作空间不正确 +9. 【agent】优化 插件端删除 spring-boot-starter-websocket 依赖 ### ❌ 不兼容功能 diff --git a/modules/agent-transport/agent-transport-http/pom.xml b/modules/agent-transport/agent-transport-http/pom.xml index 7bb728907..0e6cbf2fe 100644 --- a/modules/agent-transport/agent-transport-http/pom.xml +++ b/modules/agent-transport/agent-transport-http/pom.xml @@ -60,12 +60,17 @@ true - com.alibaba.fastjson2 fastjson2 true + + + org.springframework.boot + spring-boot-starter-websocket + true + diff --git a/modules/agent/src/main/java/io/jpom/socket/AgentFileTailWatcher.java b/modules/agent/src/main/java/io/jpom/socket/AgentFileTailWatcher.java index 65da9fc9a..bec0ce45e 100644 --- a/modules/agent/src/main/java/io/jpom/socket/AgentFileTailWatcher.java +++ b/modules/agent/src/main/java/io/jpom/socket/AgentFileTailWatcher.java @@ -24,6 +24,7 @@ package io.jpom.socket; import cn.hutool.core.io.FileUtil; import io.jpom.util.BaseFileTailWatcher; +import io.jpom.util.SocketSessionUtil; import lombok.extern.slf4j.Slf4j; import javax.websocket.Session; @@ -151,6 +152,14 @@ public class AgentFileTailWatcher extends BaseFileTailWatcher { } + @Override + protected void send(T session, String msg) { + try { + SocketSessionUtil.send((Session) session, msg); + } catch (Exception e) { + log.error("发送消息异常", e); + } + } /** * 关闭 diff --git a/modules/agent/src/main/java/io/jpom/socket/AgentWebSocketConfig.java b/modules/agent/src/main/java/io/jpom/socket/AgentWebSocketConfig.java index 32e5109d8..517906cdc 100644 --- a/modules/agent/src/main/java/io/jpom/socket/AgentWebSocketConfig.java +++ b/modules/agent/src/main/java/io/jpom/socket/AgentWebSocketConfig.java @@ -22,9 +22,21 @@ */ package io.jpom.socket; -import org.springframework.context.annotation.Bean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.SmartInitializingSingleton; +import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Configuration; -import org.springframework.web.socket.server.standard.ServerEndpointExporter; +import org.springframework.util.Assert; +import org.springframework.web.context.support.WebApplicationObjectSupport; + +import javax.websocket.DeploymentException; +import javax.websocket.server.ServerContainer; +import javax.websocket.server.ServerEndpoint; +import javax.websocket.server.ServerEndpointConfig; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; /** * 插件端socket 配置 @@ -33,10 +45,91 @@ import org.springframework.web.socket.server.standard.ServerEndpointExporter; * @since 2019/4/19 */ @Configuration -public class AgentWebSocketConfig { +public class AgentWebSocketConfig extends WebApplicationObjectSupport implements InitializingBean, SmartInitializingSingleton { - @Bean - public ServerEndpointExporter serverEndpointExporter() { - return new ServerEndpointExporter(); - } + + @Override + public void afterPropertiesSet() throws Exception { + //Assert.state(this.serverContainer != null, "javax.websocket.server.ServerContainer not available"); + } + + +// @Override +// protected void initServletContext(ServletContext servletContext) { +// if (this.serverContainer == null) { +// this.serverContainer = (ServerContainer) servletContext.getAttribute("javax.websocket.server.ServerContainer"); +// } +// } + + private ServerContainer getServerContainer() { + return (ServerContainer) Objects.requireNonNull(getServletContext()).getAttribute("javax.websocket.server.ServerContainer"); + } + + @Override + protected boolean isContextRequired() { + return true; + } + + @Override + public void afterSingletonsInstantiated() { + registerEndpoints(); + } + + protected void registerEndpoints() { + Set> endpointClasses = new LinkedHashSet<>(); + + ApplicationContext context = getApplicationContext(); + if (context != null) { + String[] endpointBeanNames = context.getBeanNamesForAnnotation(ServerEndpoint.class); + for (String beanName : endpointBeanNames) { + endpointClasses.add(context.getType(beanName)); + } + } + + for (Class endpointClass : endpointClasses) { + registerEndpoint(endpointClass); + } + + if (context != null) { + Map endpointConfigMap = context.getBeansOfType(ServerEndpointConfig.class); + for (ServerEndpointConfig endpointConfig : endpointConfigMap.values()) { + registerEndpoint(endpointConfig); + } + } + } + + private void registerEndpoint(Class endpointClass) { + ServerContainer serverContainer = getServerContainer(); + Assert.state(serverContainer != null, + "No ServerContainer set. Most likely the server's own WebSocket ServletContainerInitializer " + + "has not run yet. Was the Spring ApplicationContext refreshed through a " + + "org.springframework.web.context.ContextLoaderListener, " + + "i.e. after the ServletContext has been fully initialized?"); + try { + if (logger.isDebugEnabled()) { + logger.debug("Registering @ServerEndpoint class: " + endpointClass); + } + serverContainer.addEndpoint(endpointClass); + } catch (DeploymentException ex) { + throw new IllegalStateException("Failed to register @ServerEndpoint class: " + endpointClass, ex); + } + } + + private void registerEndpoint(ServerEndpointConfig endpointConfig) { + ServerContainer serverContainer = this.getServerContainer(); + Assert.state(serverContainer != null, "No ServerContainer set"); + try { + if (logger.isDebugEnabled()) { + logger.debug("Registering ServerEndpointConfig: " + endpointConfig); + } + serverContainer.addEndpoint(endpointConfig); + } catch (DeploymentException ex) { + throw new IllegalStateException("Failed to register ServerEndpointConfig: " + endpointConfig, ex); + } + } + +// @Bean +// public ServerEndpointExporter serverEndpointExporter() { +// return new ServerEndpointExporter(); +// } } diff --git a/modules/agent/src/main/java/io/jpom/util/SocketSessionUtil.java b/modules/agent/src/main/java/io/jpom/util/SocketSessionUtil.java new file mode 100644 index 000000000..9c7c9ef62 --- /dev/null +++ b/modules/agent/src/main/java/io/jpom/util/SocketSessionUtil.java @@ -0,0 +1,89 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Code Technology Studio + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package io.jpom.util; + +import cn.hutool.core.thread.ThreadUtil; +import cn.hutool.core.util.StrUtil; +import lombok.extern.slf4j.Slf4j; + +import javax.websocket.Session; +import java.io.IOException; + +/** + * socket 会话对象 + * + * @author jiangzeyin + * @since 2018/9/29 + */ +@Slf4j +public class SocketSessionUtil { + /** + * 锁 + */ + private static final KeyLock LOCK = new KeyLock<>(); + /** + * 错误尝试次数 + */ + private static final int ERROR_TRY_COUNT = 10; + + /** + * 发送消息 + * + * @param session 会话对象 + * @param msg 消息 + * @throws IOException 异常 + */ + public static void send(final Session session, String msg) throws IOException { + if (StrUtil.isEmpty(msg)) { + return; + } + if (!session.isOpen()) { + throw new RuntimeException("session close "); + } + try { + LOCK.lock(session.getId()); + IOException exception = null; + int tryCount = 0; + do { + tryCount++; + if (exception != null) { + // 上一次有异常、休眠 500 + ThreadUtil.sleep(500); + } + try { + session.getBasicRemote().sendText(msg); + exception = null; + break; + } catch (IOException e) { + log.error("发送消息失败:" + tryCount, e); + exception = e; + } + } while (tryCount <= ERROR_TRY_COUNT); + if (exception != null) { + throw exception; + } + } finally { + LOCK.unlock(session.getId()); + } + } +} diff --git a/modules/common/pom.xml b/modules/common/pom.xml index 916d88076..f5e71158b 100644 --- a/modules/common/pom.xml +++ b/modules/common/pom.xml @@ -82,10 +82,7 @@ cn.hutool hutool-cache - - org.springframework.boot - spring-boot-starter-websocket - + org.springframework.boot diff --git a/modules/common/src/main/java/io/jpom/common/ILoadEvent.java b/modules/common/src/main/java/io/jpom/common/ILoadEvent.java index cc38093c2..e3f5fda4e 100644 --- a/modules/common/src/main/java/io/jpom/common/ILoadEvent.java +++ b/modules/common/src/main/java/io/jpom/common/ILoadEvent.java @@ -1,3 +1,25 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Code Technology Studio + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ package io.jpom.common; import org.springframework.context.ApplicationContext; diff --git a/modules/common/src/main/java/io/jpom/util/BaseFileTailWatcher.java b/modules/common/src/main/java/io/jpom/util/BaseFileTailWatcher.java index 57e91c427..38479cabb 100644 --- a/modules/common/src/main/java/io/jpom/util/BaseFileTailWatcher.java +++ b/modules/common/src/main/java/io/jpom/util/BaseFileTailWatcher.java @@ -26,14 +26,10 @@ import cn.hutool.core.date.DateUnit; import cn.hutool.core.io.file.Tailer; import cn.hutool.core.util.ReflectUtil; import cn.hutool.core.util.StrUtil; -import io.jpom.system.JpomRuntimeException; import lombok.extern.slf4j.Slf4j; import org.springframework.util.Assert; -import org.springframework.web.socket.WebSocketSession; -import javax.websocket.Session; import java.io.File; -import java.io.IOException; import java.lang.reflect.Method; import java.nio.charset.Charset; import java.util.HashSet; @@ -73,18 +69,19 @@ public abstract class BaseFileTailWatcher { this.charset = charset; } - protected void send(T session, String msg) { - try { - if (session instanceof Session) { - SocketSessionUtil.send((Session) session, msg); - } else if (session instanceof WebSocketSession) { - SocketSessionUtil.send((WebSocketSession) session, msg); - } else { - throw new JpomRuntimeException("没有对应类型"); - } - } catch (IOException ignored) { - } - } + protected abstract void send(T session, String msg); + //{ +// try { +// if (session instanceof Session) { +// SocketSessionUtil.send((Session) session, msg); +// } else if (session instanceof WebSocketSession) { +// SocketSessionUtil.send((WebSocketSession) session, msg); +// } else { +// throw new JpomRuntimeException("没有对应类型"); +// } +// } catch (IOException ignored) { +// } + //} /** * 有新的日志 diff --git a/modules/common/src/main/java/io/jpom/util/SocketSessionUtil.java b/modules/common/src/main/java/io/jpom/util/SocketSessionUtil.java deleted file mode 100644 index a0a90397e..000000000 --- a/modules/common/src/main/java/io/jpom/util/SocketSessionUtil.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * The MIT License (MIT) - * - * Copyright (c) 2019 Code Technology Studio - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -package io.jpom.util; - -import cn.hutool.core.thread.ThreadUtil; -import cn.hutool.core.util.StrUtil; -import lombok.extern.slf4j.Slf4j; -import org.springframework.web.socket.TextMessage; -import org.springframework.web.socket.WebSocketSession; - -import javax.websocket.Session; -import java.io.IOException; - -/** - * socket 会话对象 - * - * @author jiangzeyin - * @since 2018/9/29 - */ -@Slf4j -public class SocketSessionUtil { - /** - * 锁 - */ - private static final KeyLock LOCK = new KeyLock<>(); - /** - * 错误尝试次数 - */ - private static final int ERROR_TRY_COUNT = 10; - - /** - * 发送消息 - * - * @param session 会话对象 - * @param msg 消息 - * @throws IOException 异常 - */ - public static void send(final Session session, String msg) throws IOException { - if (StrUtil.isEmpty(msg)) { - return; - } - if (!session.isOpen()) { - throw new RuntimeException("session close "); - } - try { - LOCK.lock(session.getId()); - IOException exception = null; - int tryCount = 0; - do { - tryCount++; - if (exception != null) { - // 上一次有异常、休眠 500 - ThreadUtil.sleep(500); - } - try { - session.getBasicRemote().sendText(msg); - exception = null; - break; - } catch (IOException e) { - log.error("发送消息失败:" + tryCount, e); - exception = e; - } - } while (tryCount <= ERROR_TRY_COUNT); - if (exception != null) { - throw exception; - } - } finally { - LOCK.unlock(session.getId()); - } - } - - public static void send(WebSocketSession session, String msg) throws IOException { - if (StrUtil.isEmpty(msg)) { - return; - } - if (!session.isOpen()) { - throw new RuntimeException("session close "); - } - try { - LOCK.lock(session.getId()); - IOException exception = null; - int tryCount = 0; - do { - tryCount++; - if (exception != null) { - // 上一次有异常、休眠 500 - ThreadUtil.sleep(500); - } - try { - session.sendMessage(new TextMessage(msg)); - exception = null; - break; - } catch (IOException e) { - log.error("发送消息失败:" + tryCount, e); - exception = e; - } - } while (tryCount <= ERROR_TRY_COUNT); - if (exception != null) { - throw exception; - } - } finally { - LOCK.unlock(session.getId()); - } - } -} diff --git a/modules/server/pom.xml b/modules/server/pom.xml index 575fd9638..5c091ac1e 100644 --- a/modules/server/pom.xml +++ b/modules/server/pom.xml @@ -126,6 +126,11 @@ agent-transport-http ${project.version} + + + org.springframework.boot + spring-boot-starter-websocket + diff --git a/modules/server/src/main/java/io/jpom/socket/ServiceFileTailWatcher.java b/modules/server/src/main/java/io/jpom/socket/ServiceFileTailWatcher.java index cec1fe8d6..d0e105d11 100644 --- a/modules/server/src/main/java/io/jpom/socket/ServiceFileTailWatcher.java +++ b/modules/server/src/main/java/io/jpom/socket/ServiceFileTailWatcher.java @@ -24,6 +24,7 @@ package io.jpom.socket; import cn.hutool.core.io.FileUtil; import io.jpom.util.BaseFileTailWatcher; +import io.jpom.util.SocketSessionUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.web.socket.WebSocketSession; @@ -140,6 +141,15 @@ public class ServiceFileTailWatcher extends BaseFileTailWatcher { } } + @Override + protected void send(T session, String msg) { + try { + SocketSessionUtil.send((WebSocketSession) session, msg); + } catch (Exception e) { + log.error("发送消息异常", e); + } + } + /** * 关闭 */ diff --git a/modules/server/src/main/java/io/jpom/util/SocketSessionUtil.java b/modules/server/src/main/java/io/jpom/util/SocketSessionUtil.java new file mode 100644 index 000000000..1abc7a770 --- /dev/null +++ b/modules/server/src/main/java/io/jpom/util/SocketSessionUtil.java @@ -0,0 +1,83 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2019 Code Technology Studio + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package io.jpom.util; + +import cn.hutool.core.thread.ThreadUtil; +import cn.hutool.core.util.StrUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; + +import java.io.IOException; + +/** + * socket 会话对象 + * + * @author jiangzeyin + * @since 2018/9/29 + */ +@Slf4j +public class SocketSessionUtil { + /** + * 锁 + */ + private static final KeyLock LOCK = new KeyLock<>(); + /** + * 错误尝试次数 + */ + private static final int ERROR_TRY_COUNT = 10; + + public static void send(WebSocketSession session, String msg) throws IOException { + if (StrUtil.isEmpty(msg)) { + return; + } + if (!session.isOpen()) { + throw new RuntimeException("session close "); + } + try { + LOCK.lock(session.getId()); + IOException exception = null; + int tryCount = 0; + do { + tryCount++; + if (exception != null) { + // 上一次有异常、休眠 500 + ThreadUtil.sleep(500); + } + try { + session.sendMessage(new TextMessage(msg)); + exception = null; + break; + } catch (IOException e) { + log.error("发送消息失败:" + tryCount, e); + exception = e; + } + } while (tryCount <= ERROR_TRY_COUNT); + if (exception != null) { + throw exception; + } + } finally { + LOCK.unlock(session.getId()); + } + } +}