前言:
当前大家对“apacheabwebsocket”都比较注重,大家都需要了解一些“apacheabwebsocket”的相关文章。那么小编也在网络上搜集了一些对于“apacheabwebsocket””的相关文章,希望朋友们能喜欢,咱们快快来了解一下吧!文章总共分为三篇,分别是:
springboot+websocket实现基于xterm.js的终端terminal(一)springboot+websocket实现Html端整合Xterm.js实现客户端(二)springboot+websocket实现vue整合Xtermjs实现客户端(三)
请查看本篇文章上下文!!!
什么是Xterm.js
Xterm.js 是一个用 TypeScript 编写的前端组件,它允许应用程序在浏览器中将功能齐全的终端带给用户。 它被 VS Code、Hyper 和 Theia 等流行项目使用。
springboot整合websocket实现服务端1、引入pom依赖
此处主要引入websocket依赖和其他辅助工具
<!--websocket依赖--><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId></dependency> <!--ssh2依赖--><dependency> <groupId>ch.ethz.ganymed</groupId> <artifactId>ganymed-ssh2</artifactId> <version>262</version></dependency><!-- --><dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.79</version></dependency><!-- --><dependency> <groupId>com.jcraft</groupId> <artifactId>jsch</artifactId> <version>0.1.55</version></dependency><dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.3.7</version></dependency>2、新建SshHandler-websocket处理类
package com.qingfeng.framework.ssh;import cn.hutool.core.io.IoUtil;import cn.hutool.core.thread.ThreadUtil;import cn.hutool.core.util.StrUtil;import cn.hutool.extra.ssh.ChannelType;import cn.hutool.extra.ssh.JschUtil;import com.jcraft.jsch.ChannelShell;import com.jcraft.jsch.JSchException;import com.jcraft.jsch.Session;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.stereotype.Component;import javax.annotation.PostConstruct;import javax.websocket.*;import javax.websocket.server.ServerEndpoint;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import java.util.Arrays;import java.util.concurrent.ConcurrentHashMap;import java.util.concurrent.CopyOnWriteArraySet;import java.util.concurrent.atomic.AtomicInteger;/*** @ProjectName SshHandler* @author qingfeng* @version 1.0.0* @Description ssh 处理* @createTime 2022/5/2 0002 15:26*/@ServerEndpoint(value = "/ws/ssh")@Componentpublic class SshHandler { private static final ConcurrentHashMap<String, HandlerItem> HANDLER_ITEM_CONCURRENT_HASH_MAP = new ConcurrentHashMap<>(); @PostConstruct public void init() { System.out.println("websocket 加载"); } private static Logger log = LoggerFactory.getLogger(SshHandler.class); private static final AtomicInteger OnlineCount = new AtomicInteger(0); // concurrent包的线程安全Set,用来存放每个客户端对应的Session对象。 private static CopyOnWriteArraySet<javax.websocket.Session> SessionSet = new CopyOnWriteArraySet<javax.websocket.Session>(); /** * 连接建立成功调用的方法 */ @OnOpen public void onOpen(javax.websocket.Session session) throws Exception { SessionSet.add(session); SshModel sshItem = new SshModel(); sshItem.setHost("127.0.0.1"); sshItem.setPort(22); sshItem.setUser("root"); sshItem.setPassword("root"); int cnt = OnlineCount.incrementAndGet(); // 在线数加1 log.info("有连接加入,当前连接数为:{},sessionId={}", cnt,session.getId()); SendMessage(session, "连接成功,sessionId="+session.getId()); HandlerItem handlerItem = new HandlerItem(session, sshItem); handlerItem.startRead(); HANDLER_ITEM_CONCURRENT_HASH_MAP.put(session.getId(), handlerItem); } /** * 连接关闭调用的方法 */ @OnClose public void onClose(javax.websocket.Session session) { SessionSet.remove(session); int cnt = OnlineCount.decrementAndGet(); log.info("有连接关闭,当前连接数为:{}", cnt); } /** * 收到客户端消息后调用的方法 * @param message * 客户端发送过来的消息 */ @OnMessage public void onMessage(String message, javax.websocket.Session session) throws Exception { log.info("来自客户端的消息:{}",message); // SendMessage(session, "收到消息,消息内容:"+message); HandlerItem handlerItem = HANDLER_ITEM_CONCURRENT_HASH_MAP.get(session.getId()); this.sendCommand(handlerItem, message); } /** * 出现错误 * @param session * @param error */ @OnError public void onError(javax.websocket.Session session, Throwable error) { log.error("发生错误:{},Session ID: {}",error.getMessage(),session.getId()); error.printStackTrace(); } private void sendCommand(HandlerItem handlerItem, String data) throws Exception { if (handlerItem.checkInput(data)) { handlerItem.outputStream.write(data.getBytes()); } else { handlerItem.outputStream.write("没有执行相关命令权限".getBytes()); handlerItem.outputStream.flush(); handlerItem.outputStream.write(new byte[]{3}); } handlerItem.outputStream.flush(); } /** * 发送消息,实践表明,每次浏览器刷新,session会发生变化。 * @param session * @param message */ public static void SendMessage(javax.websocket.Session session, String message) { try { // session.getBasicRemote().sendText(String.format("%s (From Server,Session ID=%s)",message,session.getId())); session.getBasicRemote().sendText(message); session.getBasicRemote().sendText("anxingtao>$"); } catch (IOException e) { log.error("发送消息出错:{}", e.getMessage()); e.printStackTrace(); } } private class HandlerItem implements Runnable { private final javax.websocket.Session session; private final InputStream inputStream; private final OutputStream outputStream; private final Session openSession; private final ChannelShell channel; private final SshModel sshItem; private final StringBuilder nowLineInput = new StringBuilder(); HandlerItem(javax.websocket.Session session, SshModel sshItem) throws IOException { this.session = session; this.sshItem = sshItem; this.openSession = JschUtil.openSession(sshItem.getHost(), sshItem.getPort(), sshItem.getUser(), sshItem.getPassword()); this.channel = (ChannelShell) JschUtil.createChannel(openSession, ChannelType.SHELL); this.inputStream = channel.getInputStream(); this.outputStream = channel.getOutputStream(); } void startRead() throws JSchException { this.channel.connect(); ThreadUtil.execute(this); } /** * 添加到命令队列 * * @param msg 输入 * @return 当前待确认待所有命令 */ private String append(String msg) { char[] x = msg.toCharArray(); if (x.length == 1 && x[0] == 127) { // 退格键 int length = nowLineInput.length(); if (length > 0) { nowLineInput.delete(length - 1, length); } } else { nowLineInput.append(msg); } return nowLineInput.toString(); } public boolean checkInput(String msg) { String allCommand = this.append(msg); boolean refuse; if (StrUtil.equalsAny(msg, StrUtil.CR, StrUtil.TAB)) { String join = nowLineInput.toString(); if (StrUtil.equals(msg, StrUtil.CR)) { nowLineInput.setLength(0); } refuse = SshModel.checkInputItem(sshItem, join); } else { // 复制输出 refuse = SshModel.checkInputItem(sshItem, msg); } return refuse; } @Override public void run() { try { byte[] buffer = new byte[1024]; int i; //如果没有数据来,线程会一直阻塞在这个地方等待数据。 while ((i = inputStream.read(buffer)) != -1) { sendBinary(session, new String(Arrays.copyOfRange(buffer, 0, i), sshItem.getCharsetT())); } } catch (Exception e) { if (!this.openSession.isConnected()) { return; } SshHandler.this.destroy(this.session); } } } public void destroy(javax.websocket.Session session) { HandlerItem handlerItem = HANDLER_ITEM_CONCURRENT_HASH_MAP.get(session.getId()); if (handlerItem != null) { IoUtil.close(handlerItem.inputStream); IoUtil.close(handlerItem.outputStream); JschUtil.close(handlerItem.channel); JschUtil.close(handlerItem.openSession); } IoUtil.close(session); HANDLER_ITEM_CONCURRENT_HASH_MAP.remove(session.getId()); } private static void sendBinary(javax.websocket.Session session, String msg) { // if (!session.isOpen()) { // // 会话关闭不能发送消息 @author jzy 21-08-04 // return; // } // synchronized (session.getId()) { // BinaryMessage byteBuffer = new BinaryMessage(msg.getBytes()); try { System.out.println("#####:"+msg); session.getBasicRemote().sendText(msg); } catch (IOException e) { } // } }}3、创建SshModel实体类
package com.qingfeng.framework.ssh;import cn.hutool.core.io.FileUtil;import cn.hutool.core.util.CharsetUtil;import cn.hutool.core.util.EnumUtil;import cn.hutool.core.util.StrUtil;import com.alibaba.fastjson.JSONArray;import java.nio.charset.Charset;import java.util.Arrays;import java.util.List;/** * @ProjectName SshModel * @author Administrator * @version 1.0.0 * @Description SshModel实体类 * @createTime 2022/5/2 0002 15:29 */public class SshModel { private String name; private String host; private Integer port; private String user; private String password; /** * 编码格式 */ private String charset; /** * 文件目录 */ private String fileDirs; /** * ssh 私钥 */ private String privateKey; private String connectType; /** * 不允许执行的命令 */ private String notAllowedCommand; /** * 允许编辑的后缀文件 */ private String allowEditSuffix; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getNotAllowedCommand() { return notAllowedCommand; } public void setNotAllowedCommand(String notAllowedCommand) { this.notAllowedCommand = notAllowedCommand; } public ConnectType connectType() { return EnumUtil.fromString(ConnectType.class, this.connectType, ConnectType.PASS); } public String getConnectType() { return connectType; } public void setConnectType(String connectType) { this.connectType = connectType; } public String getPrivateKey() { return privateKey; } public void setPrivateKey(String privateKey) { this.privateKey = privateKey; } public String getFileDirs() { return fileDirs; } public void setFileDirs(String fileDirs) { this.fileDirs = fileDirs; } public List<String> fileDirs() { return StringUtil.jsonConvertArray(this.fileDirs, String.class); } public void fileDirs(List<String> fileDirs) { if (fileDirs != null) { for (int i = fileDirs.size() - 1; i >= 0; i--) { String s = fileDirs.get(i); fileDirs.set(i, FileUtil.normalize(s)); } this.fileDirs = JSONArray.toJSONString(fileDirs); } else { this.fileDirs = null; } } public String getHost() { return host; } public void setHost(String host) { this.host = host; } public Integer getPort() { return port; } public void setPort(Integer port) { this.port = port; } public String getUser() { return user; } public void setUser(String user) { this.user = user; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getCharset() { return charset; } public void setCharset(String charset) { this.charset = charset; } public Charset getCharsetT() { Charset charset; try { charset = Charset.forName(this.getCharset()); } catch (Exception e) { charset = CharsetUtil.CHARSET_UTF_8; } return charset; } public List<String> allowEditSuffix() { return StringUtil.jsonConvertArray(this.allowEditSuffix, String.class); } public void allowEditSuffix(List<String> allowEditSuffix) { if (allowEditSuffix == null) { this.allowEditSuffix = null; } else { this.allowEditSuffix = JSONArray.toJSONString(allowEditSuffix); } } public String getAllowEditSuffix() { return allowEditSuffix; } public void setAllowEditSuffix(String allowEditSuffix) { this.allowEditSuffix = allowEditSuffix; } /** * 检查是否包含禁止命令 * * @param sshItem 实体 * @param inputItem 输入的命令 * @return false 存在禁止输入的命令 */ public static boolean checkInputItem(SshModel sshItem, String inputItem) { // 检查禁止执行的命令 String notAllowedCommand = StrUtil.emptyToDefault(sshItem.getNotAllowedCommand(), StrUtil.EMPTY).toLowerCase(); if (StrUtil.isEmpty(notAllowedCommand)) { return true; } List<String> split = Arrays.asList(StrUtil.split(notAllowedCommand, StrUtil.COMMA)); inputItem = inputItem.toLowerCase(); List<String> commands = Arrays.asList(StrUtil.split(inputItem, StrUtil.CR)); commands.addAll(Arrays.asList(StrUtil.split(inputItem, "&"))); for (String s : split) { // boolean anyMatch = commands.stream().anyMatch(item -> StrUtil.startWithAny(item, s + StrUtil.SPACE, ("&" + s + StrUtil.SPACE), StrUtil.SPACE + s + StrUtil.SPACE)); if (anyMatch) { return false; } // anyMatch = commands.stream().anyMatch(item -> StrUtil.equals(item, s)); if (anyMatch) { return false; } } return true; } public enum ConnectType { /** * 账号密码 */ PASS, /** * 密钥 */ PUBKEY }}4、新建StringUtil工具类
package com.qingfeng.framework.ssh;import cn.hutool.core.date.DateField;import cn.hutool.core.date.DateTime;import cn.hutool.core.date.DateUtil;import cn.hutool.core.io.FileUtil;import cn.hutool.core.lang.Validator;import cn.hutool.core.util.StrUtil;import cn.hutool.system.SystemUtil;import com.alibaba.fastjson.JSON;import java.io.File;import java.util.List;/** * @ProjectName StringUtil * @author qingfeng * @version 1.0.0 * @Description 方法运行参数工具 * @createTime 2022/5/2 0002 15:29 */public class StringUtil { /** * 支持的压缩包格式 */ public static final String[] PACKAGE_EXT = new String[]{"tar.bz2", "tar.gz", "tar", "bz2", "zip", "gz"}; /** * 获取启动参数 * @param args 所有参数 * @param name 参数名 * @return 值 */ public static String getArgsValue(String[] args, String name) { if (args == null) { return null; } for (String item : args) { item = StrUtil.trim(item); if (item.startsWith("--" + name + "=")) { return item.substring(name.length() + 3); } } return null; } /** * id输入规则 * * @param value 值 * @param min 最短 * @param max 最长 * @return true */ public static boolean isGeneral(CharSequence value, int min, int max) { String reg = "^[a-zA-Z0-9_-]{" + min + StrUtil.COMMA + max + "}$"; return Validator.isMatchRegex(reg, value); } /** * 删除文件开始的路径 * * @param file 要删除的文件 * @param startPath 开始的路径 * @param inName 是否返回文件名 * @return /test/a.txt /test/ a.txt */ public static String delStartPath(File file, String startPath, boolean inName) { String newWhitePath; if (inName) { newWhitePath = FileUtil.getAbsolutePath(file.getAbsolutePath()); } else { newWhitePath = FileUtil.getAbsolutePath(file.getParentFile()); } String itemAbsPath = FileUtil.getAbsolutePath(new File(startPath)); itemAbsPath = FileUtil.normalize(itemAbsPath); newWhitePath = FileUtil.normalize(newWhitePath); String path = StrUtil.removePrefix(newWhitePath, itemAbsPath); //newWhitePath.substring(newWhitePath.indexOf(itemAbsPath) + itemAbsPath.length()); path = FileUtil.normalize(path); if (path.startsWith(StrUtil.SLASH)) { path = path.substring(1); } return path; } /** * 获取jdk 中的tools jar文件路径 * * @return file */ public static File getToolsJar() { File file = new File(SystemUtil.getJavaRuntimeInfo().getHomeDir()); return new File(file.getParentFile(), "lib/tools.jar"); } /** * 指定时间的下一个刻度 * * @return String */ public static String getNextScaleTime(String time, Long millis) { DateTime dateTime = DateUtil.parse(time); if (millis == null) { millis = 30 * 1000L; } DateTime newTime = dateTime.offsetNew(DateField.SECOND, (int) (millis / 1000)); return DateUtil.formatTime(newTime); }// /**// * 删除 yml 文件内容注释// *// * @param content 配置内容// * @return 移除后的内容// */// public static String deleteComment(String content) {// List<String> split = StrUtil.split(content, StrUtil.LF);// split = split.stream().filter(s -> {// if (StrUtil.isEmpty(s)) {// return false;// }// s = StrUtil.trim(s);// return !StrUtil.startWith(s, "#");// }).collect(Collectors.toList());// return CollUtil.join(split, StrUtil.LF);// } /** * json 字符串转 bean,兼容普通json和字符串包裹情况 * * @param jsonStr json 字符串 * @param cls 要转为bean的类 * @param <T> 泛型 * @return data */ public static <T> T jsonConvert(String jsonStr, Class<T> cls) { if (StrUtil.isEmpty(jsonStr)) { return null; } try { return JSON.parseObject(jsonStr, cls); } catch (Exception e) { return JSON.parseObject(JSON.parse(jsonStr).toString(), cls); } } /** * json 字符串转 bean,兼容普通json和字符串包裹情况 * * @param jsonStr json 字符串 * @param cls 要转为bean的类 * @param <T> 泛型 * @return data */ public static <T> List<T> jsonConvertArray(String jsonStr, Class<T> cls) { try { if (StrUtil.isEmpty(jsonStr)) { return null; } return JSON.parseArray(jsonStr, cls); } catch (Exception e) { Object parse = JSON.parse(jsonStr); return JSON.parseArray(parse.toString(), cls); } }}5、创建WebSocketConfig
给spring容器注入这个ServerEndpointExporter对象
package com.qingfeng.framework.configure;import org.springframework.boot.autoconfigure.EnableAutoConfiguration;import org.springframework.boot.web.servlet.ServletContextInitializer;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.ComponentScan;import org.springframework.context.annotation.Configuration;import org.springframework.web.socket.server.standard.ServerEndpointExporter;import org.springframework.web.util.WebAppRootListener;import javax.servlet.ServletContext;import javax.servlet.ServletException;/** * @author Administrator * @version 1.0.0 * @ProjectName com.qingfeng * @Description WebSocketConfig * @createTime 2021年08月10日 16:51:00 */@Configuration@ComponentScan@EnableAutoConfigurationpublic class WebSocketConfig implements ServletContextInitializer { /** * 给spring容器注入这个ServerEndpointExporter对象 * 相当于xml: * <beans> * <bean id="serverEndpointExporter" class="org.springframework.web.socket.server.standard.ServerEndpointExporter"/> * </beans> * <p> * 检测所有带有@serverEndpoint注解的bean并注册他们。 * * @return */ @Bean public ServerEndpointExporter serverEndpointExporter() { System.out.println("我被注入了"); return new ServerEndpointExporter(); } @Override public void onStartup(ServletContext servletContext) throws ServletException { servletContext.addListener(WebAppRootListener.class); servletContext.setInitParameter("org.apache.tomcat.websocket.textBufferSize","52428800"); servletContext.setInitParameter("org.apache.tomcat.websocket.binaryBufferSize","52428800"); }}6、修改shiro拦截控制
如果是其他的拦截器,也需要设置请求过滤。
filterChainDefinitionMap.put("/ws/**", "anon");
至此后端整合完毕。
版权声明:
本站文章均来自互联网搜集,如有侵犯您的权益,请联系我们删除,谢谢。
标签: #apacheabwebsocket