栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

纯java实现浏览器操作Linux服务器

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

纯java实现浏览器操作Linux服务器

纯java实现浏览器操作Linux服务器 技术选型

由于webssh需要实时数据交互,所以会选用长连接的WebSocket,为了开发的方便,框架选用SpringBoot,另外还自己了解了Java用户连接ssh的jsch和实现前端shell页面的xterm.js.
所以,最终的技术选型就是 SpringBoot+Websocket+jsch+xterm.js。

实现思路 导入依赖
  
        
        
            org.springframework.boot
            spring-boot-starter-web
        
        
        
            com.jcraft
            jsch
            0.1.54
        
        
        
            org.springframework.boot
            spring-boot-starter-websocket
        
        
        
            commons-io
            commons-io
            1.4
        
        
            commons-fileupload
            commons-fileupload
            1.3.3
        
    
    
    
        WebSSH
        
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
    
引入xterm.js

xterm.js是一个基于WebSocket的容器,它可以帮助我们在前端实现命令行的样式。就像是我们平常再用SecureCRT或者XShell连接服务器时一样。

点击下载依赖资源, 文件结构如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jHqis98r-1634208108807)(http://bed.thunisoft.com:9000/ibed/2021/05/26/CMUuq85pI.png)]

后端实现

由于xterm只要只是实现了前端的样式,并不能真正地实现与服务器交互,与服务器交互主要还是靠我们Java后端来进行控制的,所以我们从后端开始,使用jsch+websocket实现这部分内容。

WebSocket配置

由于消息实时推送到前端需要用到WebSocket,不了解WebSocket的同学可以先去自行了解一下,这里就不过多介绍了,我们直接开始进行WebSocket的配置。

@Configuration
@EnableWebSocket
public class WebSSHWebSocketConfig implements WebSocketConfigurer{
    @Autowired
    WebSSHWebSocketHandler webSSHWebSocketHandler;
    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {
        //socket通道
        //指定处理器和路径,并设置跨域
        webSocketHandlerRegistry.addHandler(webSSHWebSocketHandler, "/webssh")
                .addInterceptors(new WebSocketInterceptor())
                .setAllowedOrigins("*");
    }
}
处理器(Handler)和拦截器(Interceptor)的实现

刚才我们完成了WebSocket的配置,并指定了一个处理器和拦截器。所以接下来就是处理器和拦截器的实现。

  • 拦截器:
public class WebSocketInterceptor implements HandshakeInterceptor {
    
    @Override
    public boolean beforeHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Map map) throws Exception {
        if (serverHttpRequest instanceof ServletServerHttpRequest) {
            ServletServerHttpRequest request = (ServletServerHttpRequest) serverHttpRequest;
            //生成一个UUID,这里由于是独立的项目,没有用户模块,所以可以用随机的UUID
            //但是如果要集成到自己的项目中,需要将其改为自己识别用户的标识
            String uuid = UUID.randomUUID().toString().replace("-","");
            //将uuid放到websocketsession中
            map.put(ConstantPool.USER_UUID_KEY, uuid);
            return true;
        } else {
            return false;
        }
    }

    @Override
    public void afterHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Exception e) {

    }
}
  • 处理器:
@Component
public class WebSSHWebSocketHandler implements WebSocketHandler{
    @Autowired
    private WebSSHService webSSHService;
    private Logger logger = LoggerFactory.getLogger(WebSSHWebSocketHandler.class);

    
    @Override
    public void afterConnectionEstablished(WebSocketSession webSocketSession) throws Exception {
        logger.info("用户:{},连接WebSSH", webSocketSession.getAttributes().get(ConstantPool.USER_UUID_KEY));
        //调用初始化连接
        webSSHService.initConnection(webSocketSession);
    }

    
    @Override
    public void handleMessage(WebSocketSession webSocketSession, WebSocketMessage webSocketMessage) throws Exception {
        if (webSocketMessage instanceof TextMessage) {
            logger.info("用户:{},发送命令:{}", webSocketSession.getAttributes().get(ConstantPool.USER_UUID_KEY), webSocketMessage.toString());
            //调用service接收消息
            webSSHService.recvHandle(((TextMessage) webSocketMessage).getPayload(), webSocketSession);
        } else if (webSocketMessage instanceof BinaryMessage) {

        } else if (webSocketMessage instanceof PongMessage) {

        } else {
            System.out.println("Unexpected WebSocket message type: " + webSocketMessage);
        }
    }

    
    @Override
    public void handleTransportError(WebSocketSession webSocketSession, Throwable throwable) throws Exception {
        logger.error("数据传输错误");
    }

    
    @Override
    public void afterConnectionClosed(WebSocketSession webSocketSession, CloseStatus closeStatus) throws Exception {
        logger.info("用户:{}断开webssh连接", String.valueOf(webSocketSession.getAttributes().get(ConstantPool.USER_UUID_KEY)));
        //调用service关闭连接
        webSSHService.close(webSocketSession);
    }

    @Override
    public boolean supportsPartialMessages() {
        return false;
    }
}

需要注意的是,在拦截器中加入的用户标识是使用了随机的UUID,这是因为作为一个独立的websocket项目,没有用户模块,如果需要将这个项目集成到自己的项目中,需要修改这部分代码,将其改为自己项目中识别一个用户所用的用户标识。

梳理下处理逻辑

1.首先我们得先连接上终端(初始化连接)

2.其次我们的服务端需要处理来自前端的消息(接收并处理前端消息)

3.我们需要将终端返回的消息回写到前端(数据回写前端)

4.关闭连接
按照这个逻辑,定义个接口。
public interface WebSSHService {
    
    public void initConnection(WebSocketSession session);

    
    public void recvHandle(String buffer, WebSocketSession session);

    
    public void sendMessage(WebSocketSession session, byte[] buffer) throws IOException;

    
    public void close(WebSocketSession session);
}
实现功能
  • 初始化连接

由于我们的底层是依赖jsch实现的,所以这里是需要使用jsch去建立连接的。而所谓初始化连接,实际上就是将我们所需要的连接信息,保存在一个Map中,这里并不进行任何的真实连接操作。为什么这里不直接进行连接?因为这里前端只是连接上了WebSocket,但是我们还需要前端给我们发来linux终端的用户名和密码,没有这些信息,我们是无法进行连接的。

public void initConnection(WebSocketSession session) {
        JSch jSch = new JSch();
        SSHConnectInfo sshConnectInfo = new SSHConnectInfo();
        sshConnectInfo.setjSch(jSch);
        sshConnectInfo.setWebSocketSession(session);
        String uuid = String.valueOf(session.getAttributes().get(ConstantPool.USER_UUID_KEY));
        //将这个ssh连接信息放入map中
        sshMap.put(uuid, sshConnectInfo);
}
  • 处理客户端发送的数据

在这一步骤中,我们会分为两个分支。

第一个分支:如果客户端发来的是终端的用户名和密码等信息,那么我们进行终端的连接。

第二个分支:如果客户端发来的是操作终端的命令,那么我们就直接转发到终端并且获取终端的执行结果。

具体代码实现:

public void recvHandle(String buffer, WebSocketSession session) {
        ObjectMapper objectMapper = new ObjectMapper();
        WebSSHData webSSHData = null;
        try {
            //转换前端发送的JSON
            webSSHData = objectMapper.readValue(buffer, WebSSHData.class);
        } catch (IOException e) {
            logger.error("Json转换异常");
            logger.error("异常信息:{}", e.getMessage());
            return;
        }
    //获取刚才设置的随机的uuid
        String userId = String.valueOf(session.getAttributes().get(ConstantPool.USER_UUID_KEY));
        if (ConstantPool.WEBSSH_OPERATE_CONNECT.equals(webSSHData.getOperate())) {
            //如果是连接请求
            //找到刚才存储的ssh连接对象
            SSHConnectInfo sshConnectInfo = (SSHConnectInfo) sshMap.get(userId);
            //启动线程异步处理
            WebSSHData finalWebSSHData = webSSHData;
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        //连接到终端
                        connectToSSH(sshConnectInfo, finalWebSSHData, session);
                    } catch (JSchException | IOException e) {
                        logger.error("webssh连接异常");
                        logger.error("异常信息:{}", e.getMessage());
                        close(session);
                    }
                }
            });
        } else if (ConstantPool.WEBSSH_OPERATE_COMMAND.equals(webSSHData.getOperate())) {
            //如果是发送命令的请求
            String command = webSSHData.getCommand();
            SSHConnectInfo sshConnectInfo = (SSHConnectInfo) sshMap.get(userId);
            if (sshConnectInfo != null) {
                try {
                    //发送命令到终端
                    transToSSH(sshConnectInfo.getChannel(), command);
                } catch (IOException e) {
                    logger.error("webssh连接异常");
                    logger.error("异常信息:{}", e.getMessage());
                    close(session);
                }
            }
        } else {
            logger.error("不支持的操作");
            close(session);
        }
}

  • 数据通过websocket发送到前端
public void sendMessage(WebSocketSession session, byte[] buffer) throws IOException {
        session.sendMessage(new TextMessage(buffer));
}
  • 关闭连接
public void close(WebSocketSession session) {
    //获取随机生成的uuid
        String userId = String.valueOf(session.getAttributes().get(ConstantPool.USER_UUID_KEY));
        SSHConnectInfo sshConnectInfo = (SSHConnectInfo) sshMap.get(userId);
        if (sshConnectInfo != null) {
            //断开连接
            if (sshConnectInfo.getChannel() != null) sshConnectInfo.getChannel().disconnect();
            //map中移除该ssh连接信息
            sshMap.remove(userId);
        }
}

前端实现

前端工作主要分为这么几个步骤:

  • 页面的实现
  • 连接WebSocket并完成数据的接收并回写
  • 数据的发送
页面实现

页面的实现很简单,我们只不过需要在一整个屏幕上都显示终端那种大黑屏幕,所以我们并不用写什么样式,只需要创建一个div,之后将terminal实例通过xterm放到这个div中,就可以实现了。




    WebSSH
    




连接WebSocket并完成数据的发送、接收、回写

效果展示

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/325066.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号