package com.litongjava.deploy.handler;
import com.litongjava.deploy.service.ShellService;
import com.litongjava.tio.boot.http.TioRequestContext;
import com.litongjava.tio.core.Tio;
import com.litongjava.tio.http.common.HttpRequest;
import com.litongjava.tio.http.common.HttpResponse;
import com.litongjava.tio.http.common.sse.SsePacket;
import com.litongjava.tio.http.server.util.CORSUtils;
import com.litongjava.tio.http.server.util.SseEmitter;
import com.litongjava.tio.utils.SystemTimer;
public class ShellHandler {
public HttpResponse index(HttpRequest request) {
String shell = request.getBodyString();
HttpResponse response = TioRequestContext.getResponse();
CORSUtils.enableCORS(response);
response.addServerSentEventsHeader();
Tio.send(request.channelContext, response);
response.setSend(false);
Tio.send(request.channelContext, new SsePacket("start", SystemTimer.currTime + ""));
ShellService shellService = new ShellService();
try {
shellService.execute(shell, request.channelContext);
} finally {
Tio.send(request.channelContext, new SsePacket("end", SystemTimer.currTime + ""));
SseEmitter.closeSeeConnection(request.channelContext);
}
return response;
}
}
package com.litongjava.deploy.service;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import com.litongjava.tio.core.ChannelContext;
import com.litongjava.tio.core.Tio;
import com.litongjava.tio.http.common.sse.SsePacket;
import com.litongjava.tio.utils.crypto.Md5Utils;
import com.litongjava.tio.utils.hutool.FileUtil;
import com.litongjava.tio.utils.thread.TioThreadUtils;
public class ShellService {
private static final long TIMEOUT_MINUTES = 10;
public void execute(String shellCode, ChannelContext channelContext) {
String md5 = Md5Utils.getMD5(shellCode);
File scriptDir = new File("script");
if (!scriptDir.exists()) {
scriptDir.mkdirs();
}
String suffix = isWindows() ? ".bat" : ".sh";
String scriptPath = scriptDir.getPath() + File.separator + md5 + suffix;
File scriptFile = new File(scriptPath);
try {
FileUtil.writeString(shellCode, scriptPath, "UTF-8");
if (!isWindows()) {
scriptFile.setExecutable(true);
}
} catch (IOException e) {
Tio.send(channelContext, new SsePacket("error", "Save script failed: " + e.getMessage()));
return;
}
String absolutePath = scriptFile.getAbsolutePath();
List<String> command = new ArrayList<>();
if (isWindows()) {
command.add("cmd");
command.add("/c");
command.add(absolutePath);
} else {
command.add("sh");
command.add(absolutePath);
}
Process process;
try {
ProcessBuilder pb = new ProcessBuilder(command);
process = pb.start();
} catch (IOException e) {
Tio.send(channelContext, new SsePacket("error", "Failed to start process: " + e.getMessage()));
return;
}
Future<?> stdoutFuture = TioThreadUtils.submit(() -> streamToSse(process.getInputStream(), channelContext, "stdout"));
Future<?> stderrFuture = TioThreadUtils.submit(() -> streamToSse(process.getErrorStream(), channelContext, "stderr"));
boolean finished;
try {
finished = process.waitFor(TIMEOUT_MINUTES, TimeUnit.MINUTES);
if (!finished) {
process.destroyForcibly();
Tio.send(channelContext, new SsePacket("error", "Execution timed out and was forcibly terminated."));
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
Tio.send(channelContext, new SsePacket("error", "Execution interrupted: " + e.getMessage()));
finished = true;
} finally {
stdoutFuture.cancel(true);
stderrFuture.cancel(true);
}
try {
int exitCode = process.exitValue();
Tio.send(channelContext, new SsePacket("exit", String.valueOf(exitCode)));
} catch (IllegalThreadStateException ignored) {
}
}
* 将流中的每一行以 SSE 形式发给客户端
*/
private void streamToSse(InputStream input, ChannelContext ctx, String eventType) {
Charset charset = isWindows() ? Charset.forName("GBK") : StandardCharsets.UTF_8;
try (BufferedReader reader = new BufferedReader(new InputStreamReader(input, charset))) {
String line;
while ((line = reader.readLine()) != null) {
Tio.send(ctx, new SsePacket(eventType, line));
}
} catch (IOException ignored) {
}
}
private boolean isWindows() {
String os = System.getProperty("os.name");
return os != null && os.toLowerCase().contains("win");
}
}
curl -X POST --data-binary "ping openai.com" http://localhost:10000/api/shell