feat: ASW-74 新增 adb shell websocket 接口

This commit is contained in:
shizhendong
2024-09-14 14:34:33 +08:00
parent 139efeec0b
commit 8d88639655
4 changed files with 170 additions and 0 deletions

View File

@@ -60,6 +60,14 @@
<artifactId>Java-WebSocket</artifactId> <artifactId>Java-WebSocket</artifactId>
<version>1.5.6</version> <version>1.5.6</version>
</dependency> </dependency>
<!-- https://mvnrepository.com/artifact/org.jetbrains.pty4j/pty4j -->
<dependency>
<groupId>org.jetbrains.pty4j</groupId>
<artifactId>pty4j</artifactId>
<version>0.12.35</version>
</dependency>
</dependencies> </dependencies>
<build> <build>

View File

@@ -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<String> cmd = AdbCommandBuilder.builder()
.serial(AdbUtil.getInstance(adb).getSerial())
.buildShellCommand("shell")
.build();
Map<String, String> 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) {
}
}
}

View File

@@ -17,5 +17,6 @@ public class WebSocketConfig implements WebSocketConfigurer {
@Override @Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new VncProxyHandler(envApiYml.getVnc()), "/api/v1/env/novnc").setAllowedOrigins("*"); registry.addHandler(new VncProxyHandler(envApiYml.getVnc()), "/api/v1/env/novnc").setAllowedOrigins("*");
registry.addHandler(new AdbShellProxyHandler(envApiYml.getAdb()), "/api/v1/env/terminal").setAllowedOrigins("*");
} }
} }

View File

@@ -10,6 +10,8 @@ import java.io.IOException;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.net.URLDecoder; import java.net.URLDecoder;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.StringTokenizer; import java.util.StringTokenizer;
public class T { public class T {
@@ -127,6 +129,14 @@ public class T {
public static class URLUtil extends cn.hutool.core.util.URLUtil { public static class URLUtil extends cn.hutool.core.util.URLUtil {
} }
/**
* 对象工具类,包括判空、克隆、序列化等操作
*
* @author Looly
*/
public static class ObjectUtil extends cn.hutool.core.util.ObjectUtil {
}
/** /**
* CommandLineUtil * CommandLineUtil
* *
@@ -244,4 +254,24 @@ public class T {
return file.getAbsolutePath(); return file.getAbsolutePath();
} }
} }
public class DigestUtils {
private static final List<String> 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;
}
}
} }