上一篇完成基本功能,这一片来补充连接docker容器的方法
1、服务端
需要新增一个专门的处理类
1、处理类接口
public interface WebDockerService {
/**
* 创建与 docker 容器建立 exec 的连接
* @return Socket
*/
void createExec(WebSocketSession session) throws Exception;
/**
* 接收消息处理
* @param buffer 消息对象
* @param session
*/
void recvHandle(String buffer, WebSocketSession session) throws IOException;
}
2、处理类接口实现
@Service
public class WebDockerServiceImpl implements WebDockerService {
private final Logger logger = LoggerFactory.getLogger(WebDockerServiceImpl.class);
private ExecutorService executorService = Executors.newCachedThreadPool();
private Map<String,Object> map = new HashMap<>(2);
private final static String SOCKET = "socket";
public static final String EXEC_ID = "exec_id";
@Value("${docker.connect.host}")
private String ip;
@Value("${docker.connect.port}")
private Integer port;
@Override
public void createExec(WebSocketSession session) throws Exception {
String containerId=session.getAttributes().get(Constants.CONTAINER_ID).toString();
String execId = DockerUtil.query(ip, docker -> {
ExecCreation execCreation = docker.execCreate(containerId, new String[]{"/bin/bash"},
DockerClient.ExecCreateParam.attachStdin(), DockerClient.ExecCreateParam.attachStdout(), DockerClient.ExecCreateParam.attachStderr(),
DockerClient.ExecCreateParam.tty(true));
return execCreation.id();
});
map.put(EXEC_ID, execId);
}
@Override
public void recvHandle(String buffer, WebSocketSession session) throws IOException {
ObjectMapper objectMapper = new ObjectMapper();
String execId = (String) map.get(EXEC_ID);
WebSSHData webSSHData;
try {
//转换前端发送的JSON
webSSHData = objectMapper.readValue(buffer, WebSSHData.class);
} catch (IOException e) {
logger.error("Json转换异常");
logger.error("异常信息:{}", e.getMessage());
return;
}
if (Constants.OPERATE_CONNECT.equals(webSSHData.getOperate())) {
//如果是连接请求
//启动线程异步处理
executorService.execute(new Runnable() {
@SneakyThrows
@Override
public void run() {
try {
//连接到终端
connectExec(session, execId);
} catch (IOException e) {
logger.error("container连接异常");
logger.error("异常信息:{}", e.getMessage());
session.close();
}
}
});
} else if (Constants.OPERATE_COMMAND.equals(webSSHData.getOperate())) {
//如果是发送命令的请求
Socket socket = (Socket) map.get(SOCKET);
String command = webSSHData.getCommand();
try {
//发送命令到终端
transToContainer(socket, command);
} catch (IOException e) {
logger.error("container连接异常");
logger.error("异常信息:{}", e.getMessage());
socket.close();
session.close();
}
} else {
logger.error("不支持的操作");
session.close();
}
}
/**
* 以 /bin/bash 方式建立连接
* @param execId 命令id
*/
private void connectExec(WebSocketSession session, String execId) throws IOException {
Socket socket = new Socket(ip,port);
socket.setKeepAlive(true);
OutputStream out = socket.getOutputStream();
StringBuffer pw = new StringBuffer();
pw.append("POST /exec/"+execId+"/start HTTP/1.1\r\n");
pw.append("Host: "+ip+":"+port+" \r\n");
pw.append("Content-Type: application/json\r\n");
pw.append("Connection: keep-alive\r\n");
JSONObject obj = new JSONObject();
obj.put("Detach",false);
obj.put("Tty",false);
String json=obj.toJSONString();
pw.append("Content-Length: "+json.length()+"\r\n");
pw.append("\r\n");
pw.append(json);
out.write(pw.toString().getBytes(StandardCharsets.UTF_8));
out.flush();
map.put(SOCKET, socket);
// 监听终端返回的数据
InputStream inputStream = socket.getInputStream();
//循环读取
byte[] buffer = new byte[1024];
int i = 0;
//如果没有数据来,线程会一直阻塞在这个地方等待数据。
while ((i = inputStream.read(buffer)) != -1) {
this.sendMessage(session, Arrays.copyOfRange(buffer, 0, i));
}
}
/**
* 将消息发送给客户端
* @param session
* @param buffer
* @throws IOException
*/
private void sendMessage(WebSocketSession session, byte[] buffer) throws IOException {
session.sendMessage(new TextMessage(buffer));
}
/**
* 将消息转发到终端
*/
private void transToContainer(Socket socket, String command) throws IOException {
if (socket != null) {
OutputStream outputStream = socket.getOutputStream();
outputStream.write(command.getBytes());
outputStream.flush();
}
}
}
建立与docker的连接为什么是用这个请求
POST /exec/"+execId+"/start HTTP/1.1
问题的答案只能去官方文档查看
3、Docker工具类
public class DockerUtil {
public static void execute(String ip,DockerAction dockerAction)throws Exception{
DockerClient docker = DefaultDockerClient.builder().uri("http://".concat(ip).concat(":2375")).apiVersion("v1.41").build();
dockerAction.action(docker);
docker.close();
}
public static <T> T query(String ip,DockerQuery<T> dockerQuery)throws Exception{
DockerClient docker = DefaultDockerClient.builder().uri("http://".concat(ip).concat(":2375")).apiVersion("v1.41").build();
T result=dockerQuery.action(docker);
docker.close();
return result;
}
public interface DockerAction {
void action(DockerClient docker) throws Exception;
}
public interface DockerQuery<T> {
T action(DockerClient docker) throws Exception;
}
}
ip是docker容器服务器的ip
为什么是2375端口?
答案还是在官方文档里面,已经规定好了
2、前端部分
与上次的组件大同小异
<template>
<div class="father">
<vue-drag-resize :is-resizable="false">
<div class="window">
<div class="header"></div>
<div id="terminal" class="terminal"/>
</div>
</vue-drag-resize>
</div>
</template>
<!--
仅供学习参考
以 exec -it 的方式进入容器
-->
<script>
import 'xterm/css/xterm.css'
import { Terminal } from 'xterm'
import { FitAddon } from 'xterm-addon-fit'
import SockJS from "sockjs-client";
import VueDragResize from 'vue-drag-resize'
export default {
name: "docker-exec",
components: {
VueDragResize
},
mounted() {
this.initTerm();
},
beforeDestroy() {
this.socket.close();
this.term.dispose();
},
data() {
return {
containerId: this.$route.query.containerId,
term: null,
socket: null,
}
},
methods: {
//初始化Xterm
initTerm() {
const term = new Terminal({
cursorBlink: true, // 光标闪烁
cursorStyle: "block", // 光标样式 null | 'block' | 'underline' | 'bar'
scrollback: 800, //回滚
tabStopWidth: 8, //制表宽度
screenKeys: true,
fontFamily: "Consolas",
fontSize: 22,
fontWeightBold: "bold",
theme: {
foreground: "#24cc3d"
},
});
const fitAddon = new FitAddon();
term.loadAddon(fitAddon);
term.open(document.getElementById('terminal'));
fitAddon.fit();
term.focus();
// 窗口尺寸变化时,终端尺寸自适应
window.onresize = function () {
fitAddon.fit()
}
this.term = term;
this.initSocket();
},
//初始化websocket
initSocket() {
let url = `http://localhost:8080/stomp/websocketJS?containerId=${this.containerId}`;
if (window.WebSocket) {
// 创建WebSocket对象
this.socket = new SockJS(url)
} else {
this.term.write('Error: WebSocket Not Supported\r\n');//否则报错
return;
}
this.socketOnOpen();
this.socketOnMessage();
this.socketOnClose();
this.socketOnError();
},
socketOnOpen() {
this.socket.onopen = () => {
this.term.write('Connecting...\r\n');
this.socket.send(JSON.stringify({operate: 'connect'}));
// 监听键盘输入
this.term.onData((data) => {
//发送指令
this.socket.send(JSON.stringify({"operate": "command", "command": data}));
});
}
},
// 回显字符
socketOnMessage() {
let _this = this;
this.socket.onmessage = (evt) => {
let data = evt.data.toString();
_this.term.write(data);
}
},
socketOnClose() {
this.socket.onclose = () => {
// console.log('close socket')
}
},
socketOnError() {
let _this = this;
this.socket.onerror = (error) => {
//连接失败回调
_this.term.write('Error: ' + error + '\r\n');
}
},
}
}
</script>
<style scoped>
* {
margin: 0;
padding: 0;
}
.window {
width: 1000px;
margin: 0 auto;
}
.header {
background-color: #E0E0E0;
border-top-left-radius: 6px;
border-top-right-radius: 6px;
padding: 10px;
}
.header::before {
content: '';
display: inline-block;
width: 12px;
height: 12px;
border-radius: 50%;
background-color: #fd6458;
box-shadow: 20px 0 0 #ffbf2b, 40px 0 0 #24cc3d;
margin-left: -950px;
}
.window .terminal {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
background-color: #F5F5F5;
border-bottom-left-radius: 6px;
border-bottom-right-radius: 6px;
}
</style>
详细情况根据需要查看吧。
我的gitee项目地址