diff --git a/pom.xml b/pom.xml index 140630f..796a6ce 100644 --- a/pom.xml +++ b/pom.xml @@ -60,6 +60,14 @@ Java-WebSocket 1.5.6 + + + + org.jetbrains.pty4j + pty4j + 0.12.35 + + diff --git a/src/main/java/net/geedge/api/config/AdbShellProxyHandler.java b/src/main/java/net/geedge/api/config/AdbShellProxyHandler.java new file mode 100644 index 0000000..489b971 --- /dev/null +++ b/src/main/java/net/geedge/api/config/AdbShellProxyHandler.java @@ -0,0 +1,131 @@ +package net.geedge.api.config; + +import cn.hutool.log.Log; +import com.pty4j.PtyProcessBuilder; +import net.geedge.api.entity.EnvApiYml; +import net.geedge.api.util.AdbCommandBuilder; +import net.geedge.api.util.AdbUtil; +import net.geedge.common.T; +import org.springframework.web.socket.CloseStatus; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; +import org.springframework.web.socket.handler.TextWebSocketHandler; + +import java.io.InputStream; +import java.io.OutputStream; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class AdbShellProxyHandler extends TextWebSocketHandler { + + private static final Log log = Log.get(); + + private EnvApiYml.Adb adb; + + private Process process = null; + private InputStream inputStream = null; + private OutputStream outputStream = null; + private ExecutorService executorService = Executors.newFixedThreadPool(2); + + public AdbShellProxyHandler(EnvApiYml.Adb adb) { + this.adb = adb; + } + + @Override + public synchronized void afterConnectionEstablished(WebSocketSession session) throws Exception { + log.info("[afterConnectionEstablished] [WebSocket connection established] [websocket uri: {}]", session.getUri()); + super.afterConnectionEstablished(session); + + List cmd = AdbCommandBuilder.builder() + .serial(AdbUtil.getInstance(adb).getSerial()) + .buildShellCommand("shell") + .build(); + + Map env = new HashMap<>(System.getenv()); + env.put("TERM", "xterm"); + + // start process + process = new PtyProcessBuilder() + .setCommand(cmd.toArray(new String[]{})) + .setEnvironment(env) + .setRedirectErrorStream(true) + .start(); + +// process = new PtyProcessBuilder() +// .setCommand(new String[]{"cmd.exe", "/C", "D:\\softwares\\platform-tools\\platform-tools\\adb.exe shell"}) +// .setEnvironment(env) +// .setRedirectErrorStream(true) +// .start(); + + // stream + inputStream = process.getInputStream(); + outputStream = process.getOutputStream(); + + // server to client + executorService.submit(() -> { + byte[] buffer = new byte[1024 * 4]; + try { + while (session != null && session.isOpen()) { + int size = inputStream.read(buffer); + if (size == -1) { + try { + if (session.isOpen()) { + session.sendMessage(new TextMessage("Connection closed \r\n")); + } + } catch (Exception e) { + } + return; + } + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < size; i++) { + char chr = (char) (buffer[i] & 0xff); + sb.append(chr); + } + + String message = sb.toString(); + message = T.StrUtil.str(message.getBytes(T.DigestUtils.getEncoding(message)), "UTF-8"); + + session.sendMessage(new TextMessage(message)); + } + } catch (Exception e) { + log.error(e, "[serverToClient] [error]"); + } + }); + } + + @Override + protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { + super.handleTextMessage(session, message); + if (T.ObjectUtil.isNotNull(outputStream)) { + // write cmd byte + T.IoUtil.write(outputStream, false, message.getPayload().getBytes()); + T.IoUtil.flush(outputStream); + } + } + + @Override + public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { + log.info("[afterConnectionClosed] [WebSocket connection closed] [websocket uri: {}]", session.getUri()); + super.afterConnectionClosed(session, status); + + // close resources + this.closeResources(); + } + + /** + * close resources + */ + private void closeResources() { + try { + T.IoUtil.close(outputStream); + T.IoUtil.close(inputStream); + if (process != null) + process.destroy(); + } catch (Exception e) { + } + } + +} \ No newline at end of file diff --git a/src/main/java/net/geedge/api/config/WebSocketConfig.java b/src/main/java/net/geedge/api/config/WebSocketConfig.java index e92f971..b51a1e4 100644 --- a/src/main/java/net/geedge/api/config/WebSocketConfig.java +++ b/src/main/java/net/geedge/api/config/WebSocketConfig.java @@ -17,5 +17,6 @@ public class WebSocketConfig implements WebSocketConfigurer { @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(new VncProxyHandler(envApiYml.getVnc()), "/api/v1/env/novnc").setAllowedOrigins("*"); + registry.addHandler(new AdbShellProxyHandler(envApiYml.getAdb()), "/api/v1/env/terminal").setAllowedOrigins("*"); } } diff --git a/src/main/java/net/geedge/common/T.java b/src/main/java/net/geedge/common/T.java index c098b77..335d3d5 100644 --- a/src/main/java/net/geedge/common/T.java +++ b/src/main/java/net/geedge/common/T.java @@ -10,6 +10,8 @@ import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import java.util.StringTokenizer; public class T { @@ -127,6 +129,14 @@ public class T { public static class URLUtil extends cn.hutool.core.util.URLUtil { } + /** + * 对象工具类,包括判空、克隆、序列化等操作 + * + * @author Looly + */ + public static class ObjectUtil extends cn.hutool.core.util.ObjectUtil { + } + /** * CommandLineUtil * @@ -244,4 +254,24 @@ public class T { return file.getAbsolutePath(); } } + + public class DigestUtils { + private static final List ENCODE_LIST = Arrays.asList("ISO-8859-1", "GB2312", "UTF-8", "GBK"); + + public static String getEncoding(String text) { + for (String enc : ENCODE_LIST) { + try { + byte[] bytes = text.getBytes(enc); + String str = cn.hutool.core.util.StrUtil.str(bytes, enc); + Arrays.fill(bytes, (byte) 0); + if (cn.hutool.core.util.StrUtil.equals(text, str)) { + return enc; + } + } catch (UnsupportedEncodingException e) { + } + } + return null; + } + } + } \ No newline at end of file