2022-02-10 Springmvc+Websocket实现Web方式执行系统命令和结果显示

本文在 Springmvc+Websocket 发送JSON消息实例 的基础上,添加了异步运行 (使用Thread) 的自制 Shell 类封装包和异步运行时用到的 AsyncShellCallback.java (它是Shell包里Callback接口的执行类),主要修改了 Home.jsp 和 WSTextHandler.java。


1. 开发环境

2. 在 IDEA上创建项目

3. 使用 tomcat7-maven-plugin, 将 tomcat 内嵌运行

4. 导入 spring-webmvc, Servlet & JSTL, websocket, fastjson

5. 支持 SpringMVC

6. 支持静态资源 (html/js/css/images)

    以上步骤 1 到 6,请参考 Springmvc+Websocket 发送JSON消息实例

7. Shell 类封装包

    1) 添加 src/main/java/com/example/shell/Platform.java

package com.example.shell;

public enum Platform {

    WINDOWS, LINUX, MACOS

}

    2) 添加 src/main/java/com/example/shell/Callback.java

package com.example.shell;

public interface Callback {

    public void showMessage(String str) throws Exception;

    public void showError(String str) throws Exception;

    public void showFinish(String str) throws Exception;

}

    3) 添加 src/main/java/com/example/shell/AsyncShellExecute.java

package com.example.shell;

import java.io.BufferedReader;

import java.io.IOException;

import java.io.InputStreamReader;

public class AsyncShellExecute extends Thread {

    volatile int processStatus = 0;    // 0 - PIPE ready;  1 - PIPE busying; 2 - PIPE stopping;

    volatile int closeStatus = 0;

    private String strCommand = "";

    private Platform platform = null;

    public void setCommand(String strCmd, Platform platform) {

        this.strCommand =  strCmd;

        this.platform = platform;

    }

    @Override

    public void run() {

        String errorMessage = "";

        try {

            String strCmd = "";

            if (this.platform == Platform.WINDOWS) {

                strCmd = "cmd /c " + this.strCommand;

            } else if (platform == Platform.MACOS) {

                strCmd = this.strCommand;

            } else {

                strCmd = this.strCommand;

            }

            this.processStatus = 1;

            Runtime rt = Runtime.getRuntime();

            Process p = rt.exec(strCmd);

            String line;

            BufferedReader dataReader = new BufferedReader(new InputStreamReader(p.getInputStream(), "GBK"));

            while ((line = dataReader.readLine()) != null) {

                if (this.processStatus == 2) {

                    //System.out.println("ShellAsyncExecute -> run(1): this.processStatus == 2");

                    break;

                }

                this.callback.showMessage(line);

            }

            if (this.processStatus != 2) {

                BufferedReader errorReader = new BufferedReader(new InputStreamReader(p.getErrorStream(), "GBK"));

                while ((line = errorReader.readLine()) != null) {

                    if (this.processStatus == 2) {

                        //System.out.println("ShellAsyncExecute -> run(2): this.processStatus == 2");

                        break;

                    }

                    this.callback.showMessage(line);

                }

            }

            //System.out.println("AsyncShellExecute -> run(3): this.closeStatus = " + this.closeStatus);

            if (this.closeStatus == 0) {

                if (this.processStatus != 2) {

                    int ret = p.waitFor();

                    if (ret != 0) {

                        this.callback.showError("Execute '" + this.strCommand + "' failed");

                    } else {

                        this.callback.showFinish("Finish after execute '" + this.strCommand + "' successfully");

                    }

                } else {

                    this.callback.showFinish("Finish after terminate process");

                }

            }

            p.destroy();

        } catch (IOException e) {

            errorMessage = e.getMessage();

        } catch (Exception e2) {

            errorMessage = e2.getMessage();

        }

        if (!errorMessage.isEmpty() && this.closeStatus == 0) {

            try {

                this.callback.showError(errorMessage);

            } catch (Exception e) {

                e.printStackTrace();

            }

        }

        this.processStatus = 0;

        this.closeStatus = 0;

    }

    public void terminate(int closeStatus) {

        if (this.processStatus == 1)

            this.processStatus = 2;

        this.closeStatus = closeStatus;

    }

    public int GetProcessStatus() {

        return this.processStatus;

    }

    public Callback callback;

}


8. 支持 spring-websocket

    1) 添加 src/main/java/com/example/ws/AsyncShellCallback.java

package com.example.ws;

import java.util.Map;

import java.util.HashMap;

import org.springframework.web.socket.TextMessage;

import org.springframework.web.socket.WebSocketSession;

import com.alibaba.fastjson.JSON;

import com.example.shell.Callback;

public class AsyncShellCallback implements Callback {

    private WebSocketSession session = null;

    public AsyncShellCallback(WebSocketSession session) {

        this.session = session;

    }

    @Override

    public void showMessage(String str) throws Exception {

        Map<String, Object> msgMap = new HashMap<>();

        msgMap.put("ret", "data");

        msgMap.put("message", str);

        session.sendMessage(new TextMessage(JSON.toJSON(msgMap).toString()));

    }

    @Override

    public void showError(String str) throws Exception {

        Map<String, Object> errMap = new HashMap<>();

        errMap.put("ret", "error");

        errMap.put("description", str);

        session.sendMessage(new TextMessage(JSON.toJSON(errMap).toString()));

    }

    @Override

    public void showFinish(String str) throws Exception {

        Map<String, Object> errMap = new HashMap<>();

        errMap.put("ret", "finish");

        errMap.put("description", str);

        session.sendMessage(new TextMessage(JSON.toJSON(errMap).toString()));

    }

}

    2) 添加 src/main/java/com/example/ws/WSInterceptor.java

package com.example.ws;

import org.springframework.http.server.ServerHttpRequest;

import org.springframework.http.server.ServerHttpResponse;

import org.springframework.web.socket.WebSocketHandler;

import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;

import java.util.Map;

public class WSInterceptor extends HttpSessionHandshakeInterceptor {

    @Override

    public boolean beforeHandshake(ServerHttpRequest request,

                                  ServerHttpResponse response,

                                  WebSocketHandler wsHandler,

                                  Map<String, Object> attributes) throws Exception {

        return super.beforeHandshake(request, response, wsHandler, attributes);

    }

    @Override

    public void afterHandshake(ServerHttpRequest request,

                              ServerHttpResponse response,

                              WebSocketHandler wsHandler,

                              Exception ex) {

        super.afterHandshake(request, response, wsHandler, ex);

    }

}

    3) 添加  src/main/java/com/example/ws/WSTextHandler.java

package com.example.ws;

import java.util.List;

import java.util.Map;

import java.util.HashMap;

import java.util.concurrent.CopyOnWriteArrayList;

import org.springframework.web.socket.CloseStatus;

import org.springframework.web.socket.TextMessage;

import org.springframework.web.socket.WebSocketMessage;

import org.springframework.web.socket.WebSocketSession;

import org.springframework.web.socket.handler.TextWebSocketHandler;

import com.alibaba.fastjson.JSON;

import com.alibaba.fastjson.JSONObject;

import com.example.shell.Platform;

import com.example.shell.AsyncShellExecute;

public class WSTextHandler extends TextWebSocketHandler{

    private List<WebSocketSession> clientSessions = new CopyOnWriteArrayList<>();

    private AsyncShellExecute asyncShellExecute = null;

    @Override

    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {

        clientSessions.remove(session);

        //System.out.println("Client (" + session.getId() + ") closed, status code: " + status.getCode());

        if (asyncShellExecute != null) {

            asyncShellExecute.terminate(status.getCode());

        }

    }

    @Override

    public void afterConnectionEstablished(WebSocketSession session) throws Exception {

        clientSessions.add(session);

        //System.out.println("Client (" + session.getId() + ") connected ... ");

    }

    @Override

    public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {

        Map<String, Object> retMap = new HashMap<>();

        JSONObject recvJson = (JSONObject) JSON.parse(message.getPayload().toString());

        String opt = recvJson.getString("operation");

        if ("command".equals(opt)) {

            JSONObject paramObj = recvJson.getJSONObject("param");

            if (paramObj == null) {

                retMap.put("ret", "error");

                retMap.put("description", "Invalid command format");

                session.sendMessage(new TextMessage(JSON.toJSON(retMap).toString()));

                return;

            }

            String osType = paramObj.getString("os_type");

            String cmdStr = paramObj.getString("cmd_str");

            Platform platform = Platform.LINUX;

            if ("windows".equals(osType)) {

                platform = Platform.WINDOWS;

            }

            if (asyncShellExecute == null || asyncShellExecute.GetProcessStatus() == 0) {

                asyncShellExecute = new AsyncShellExecute();

                asyncShellExecute.callback = new AsyncShellCallback(session);

                asyncShellExecute.setCommand(cmdStr, platform);

                asyncShellExecute.start();

            } else {

                retMap.put("ret", "error");

                retMap.put("description", "Previous execution process is running, can NOT run two processes at the same time");

                session.sendMessage(new TextMessage(JSON.toJSON(retMap).toString()));

            }

        } else if ("close".equals(opt)) {

            if (asyncShellExecute == null || asyncShellExecute.GetProcessStatus() == 0) {

                retMap.put("ret", "finish");

                retMap.put("description", "Finish directly");

                session.sendMessage(new TextMessage(JSON.toJSON(retMap).toString()));

            } else {

                asyncShellExecute.terminate(0);

            }

        } else {

            retMap.put("ret", "error");

            retMap.put("description", "Invalid data format");

            session.sendMessage(new TextMessage(JSON.toJSON(retMap).toString()));

        }

    }

}

    4) 添加 src/main/java/com/example/ws/WSConfig.java

package com.example.ws;

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;

import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;

@Configuration

@EnableWebSocket

public class WSConfig implements WebSocketConfigurer{

    @Override

    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {

        registry.addHandler(new WSTextHandler(), "/websocket")

                .addInterceptors(new HttpSessionHandshakeInterceptor())

                .setAllowedOrigins("*"); // Allow cross site

    }

}


9. 视图和控制器

    1) 添加 src/main/webapp/WEB-INF/jsp/home.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"  isELIgnored="false" %>

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

<html>

<head>

    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

    <title>Home Page</title>

    <script language="javascript" src="${pageContext.request.contextPath}/static/js/jquery-1.12.2.min.js"></script>

</head>

<body>

    <c:if test="${not empty message}">

        <p style="color: blue;">${message}</p>

    </c:if>

    <p>&nbsp;</p>

    <form action="" method="post">

        <p>

            <label><strong>System Environment:</strong></label><br>

            <select name="os_type" id="os_type" class="form-control" style="width: 50%; height: 32px;">

                <option value="windows">Server run on Windows</option>

                <option value="linux">Server run on Linux</option>

            </select>

        </p>

        <p>

            <label><strong>Command or shell:</strong></label><br>

            <textarea name="cmd_str" id="cmd_str" class="form-control" style="width: 50%; height: 54px;">dir c:\</textarea>

        </p>

        <p>

            <label><strong>Websocket:</strong></label><br>

            <input type="text" name="ws_url" id="ws_url" value="ws://${pageContext.request.getServerName()}:${pageContext.request.getServerPort()}${pageContext.request.contextPath}/websocket" class="form-control" style="width: 50%; height: 32px;" />

        <p>

            <button type="button" id="btn_execute" class="btn btn-default btn-sm" onClick="javascript: connectWebsocket();">

                Execute

            </button>

            <button type="button" id="btn_close" class="btn btn-default btn-sm" onClick="javascript: closeWebsocket();" style="display: none;">

                Close Websocket

            </button>

        </p>

    </form>

    <p>&nbsp;</p>

    <div id="cmd_result" style="padding: 15px; background-color: #e2e2e2; width: 50%;  font-size: 12px; min-height: 120px;">

    </div>

    <p>&nbsp;</p>

    <script type="text/javascript">

        var globalSocket = null;

        $(document).ready(function() {

            console.log("Home Page");

          changeType();

          $("#os_type").change(function(e) {

              changeType();

          });

        });

        function changeType() {

            var osType = $("#os_type").val();

            var cmdStrNode = $("#cmd_str");

            if (osType == "windows") {

                cmdStrNode.val("dir C:\\");

            } else if (osType == "linux") {

                cmdStrNode.val("ls /");

            } else {

                cmdStrNode.val("ls /");

            }

        }

        function connectWebsocket() {

            var wsUrl = $("#ws_url").val();

            if (wsUrl == '') {

                alert("Please enter url");

                $("#ws_url").focus();

                return;

            }

            if (globalSocket == null) {

                $("#cmd_result").html('');

                $("#btn_execute").attr("disabled", "disabled");

                createWebsocket(wsUrl);

            } else {

/*

                var data = {

                    "operation": "command",

                    "param": {

                        "os_type": $("#os_type").val(),

                        "cmd_str": $("#cmd_str").val(),

                    }

                }

                globalSocket.send(JSON.stringify(data));

            */

            }

        }

        function createWebsocket(url) {

            if (globalSocket != null || url == '')

                return;

            //console.log("createWebsocket(): url = ", url);

            globalSocket = new WebSocket(url);

            globalSocket.onopen = funcWSOpen;

            globalSocket.onclose = funcWSClose;

            globalSocket.onerror = funcWSError;

            globalSocket.onmessage = funcWSMessage;

        }

        function closeWebsocket() {

            if (globalSocket != null) {

                //console.log("closeWebsocket(): send close");

                globalSocket.send(JSON.stringify({ "operation": "close"}));

                $("#btn_close").attr("disabled", "disabled");

            }

        }

        function funcWSOpen(e) {

            //console.log("funcWSOpen(): ", e);

            $("#cmd_result").html("Executing ... <br><br>");

            $("#btn_close").removeAttr("disabled");

            $("#btn_close").css("display", "");

            var data = {

                "operation": "command",

                "param": {

                    "os_type": $("#os_type").val(),

                    "cmd_str": $("#cmd_str").val(),

                }

            }

            globalSocket.send(JSON.stringify(data));

        }

        function funcWSClose(e) {

            //console.log("funcWSClose(): ", e);

            $("#cmd_result").append("<br>Websocket: Close<br>");

            $("#btn_execute").removeAttr("disabled");

            $("#btn_close").css("display", "none");

            globalSocket = null;

        }

        function funcWSError(e) {

            //console.error("funcWSError(): ", e);

            $("#cmd_result").append("<br>Websocket: Error<br>");

            $("#btn_execute").removeAttr("disabled");

            $("#btn_close").css("display", "none");

            globalSocket = null;

        }

        function funcWSMessage(e) {

            //console.log("funcWSMessage(): e.data = ", e.data);

            var dataObj = JSON.parse(e.data);

            if (dataObj['ret'] == "data") {

                $("#cmd_result").append(dataObj['message'] + "<br>");

            } else if (dataObj['ret'] == "finish") {

                console.log("funcWSMessage(): ", dataObj['description'])

                $("#cmd_result").append("<br>Websocket: " + dataObj['description'] + "<br>");

                globalSocket.close(3009)

            } else if (dataObj['ret'] == "error") {

                console.log("funcWSMessage(): ", dataObj['description']);

                $("#cmd_result").append("<br>Websocket: " + dataObj['description'] + "<br>");

            } else {

                $("#cmd_result").append("<br>Websocket: Invalid data format<br>");

            }

        }

    </script>

</body>

</html>

    2) 添加 src/main/java/com/example/controller/IndexController.java

package com.example.controller;

import org.springframework.stereotype.Controller;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RequestMethod;

import org.springframework.ui.ModelMap;

@Controller

@RequestMapping("/")

public class IndexController {

    @RequestMapping(method = RequestMethod.GET)

    public String home(ModelMap modelMap) {

        modelMap.addAttribute("message", "Springmvc Websocket Demo");

        return "home";

    }

}

    3) 删除 src/main/webapp/index.jsp


10. 运行

    参考第 3 步

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容