#php是天下最好的语言,没有之一。 我非常喜欢php,我听过一个高手的讲座,讲php的编译原理,发现如果就php语言开发而言的确技术上有高低之分。一比较才发现自己差了十万八千里,自己不努力是不行的。好了不多说了,我来说说今天我分享的话题。 ##背景 2015年,我开始接触docker,那个时候发现一个管理平台shipyard,shipyard中有一个可以直接在浏览器上操作的web终端很吸引我,我想自己实现这样的一个服务,我翻看了其中的技术细节,发现有一个shipyard/controller/api/hijack.go中一堆的操作,当我开始用php模拟来写的时候,我发现我对php的认识还处于低级阶段,看起来很一个很艰难的工作。
##整体的架构
浏览器上主要的通信技术就是websocket技术,我以前玩过一个原型. ##前端代码
< !doctype html > <title > term.js < /title>
<!--
term.js
Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
-->
<style>
html {
background: #555;
}
h1 {
margin-bottom: 20px;
font: 20px/1.5 sans - serif;
} < /style>
<h1>term.js</h1 > <script src = "http://apps.bdimg.com/libs/term.js/0.0.2/term.js" > </script>
<script>
;(function() {
window.onload = function() {
var socket = new WebSocket('ws:/ / myblog.com / wsapp / ');
socket.onopen = function() {
var term = new Terminal({
cols: 80,
rows: 24,
useStyle: true,
screenKeys: true,
cursorBlink: false
});
term.on('data ', function(data) {
socket.send(data);
});
term.on('title ', function(title) {
document.title = title;
});
term.open(document.body);
term.write('\x1b[31mWelcome to term.js ! \x1b[m\r\n ');
socket.onmessage = function(message) {
term.write(message.data);
}
socket.onclose = function() {
term.destroy();
}
socket.onerror = function() {
console.log("connect error");
term.destroy();
}
}
};
}).call(this);
</script>
这里用到了term.js的一个前端的终端组件,传送门 所有的通信都是走的websocket ##nginx的配置
location /wsapp/ {
proxy_pass http://127.0.0.1:9501;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
}
没什么好讲的,这里就是连接到一个websocket的服务而已 ##后端服务
< ?php
function create_exec($container_id = '9e75217f2a89') {
$url = "http://0.0.0.0:2375/containers/".$container_id."/exec";
$post_data = ["AttachStdin" = >true, "AttachStdout" = >true, "AttachStderr" = >true, "Cmd" = >["/bin/bash"], "DetachKeys" = >"ctrl-p,ctrl-q", "Privileged" = >true, "Tty" = >true, "User" = >"root:root"];
$header = ["Content-Type: application/json"];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($post_data, true));
$output = curl_exec($ch);
curl_close($ch);
return $output;
}
//websocket 执行
$server = new swoole_websocket_server("127.0.0.1", 9501);
$client = null;
$exec_id = null;
$host = "192.168.254.128:2375";
$re = null;
$server - >on('open',
function(swoole_websocket_server $server, $request) {
echo "server: handshake success with fd{$request->fd}\n";
global $exec_id;
global $re;
$re = $request;
$exec = json_decode(create_exec(), true);
$exec_id = $exec['Id'];
print_r("exec id:".$exec_id);
//socket async
global $client;
$client = new swoole_client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_ASYNC);
$client - >on("connect",
function(swoole_client $cli) {
global $exec_id;
global $host;
$url = '/exec/'.$exec_id."/start";
$data = '{"Detach":false, "Tty":true}';
$out = "POST ${url} HTTP/1.1\r\n";
$out. = "Remote Address:${host}\r\n";
$out. = "Content-Type: application/json\r\n";
$out. = "Connection: Upgrade\r\n";
$out. = "Content-Length: ".strlen($data)."\r\n";
$out. = "Upgrade: tcp\r\n\r\n";
$out. = $data."\r\n\r\n";
$cli - >send($out);
});
$client - >on("receive",
function(swoole_client $cli, $data) {
echo "Receive docker daemon: $data";
//接受到的docker daemon数据发送到web客户端
global $server;
global $re;
$server - >push($re - >fd, $data);
//$cli->send(str_repeat('A', 100)."\n");
});
$client - >on("error",
function(swoole_client $cli) {
echo "error\n";
});
$client - >on("close",
function(swoole_client $cli) {
echo "Connection close\n";
});
//连接docker
$client - >connect('192.168.254.128', 2375);
});
$server - >on('message',
function(swoole_websocket_server $server, $frame) {
//接收到的web客户端数据发送给docker daemon
global $client;
echo "receive from {$frame->fd}:{$frame->data},opcode:{$frame->opcode},fin:{$frame->finish}\n";
$client - >send($frame - >data);
//$server->push($frame->fd, "this is server");
});
$server - >on('close',
function($ser, $fd) {
echo "client {$fd} closed\n";
});
$server - >start();
docker的exec这里要先通过exec接口获取一个执行id,然后通过这个id,调用start接口才能进行服务,不过docker的start接口这里的返回并不是一个curl能处理的请求,而是一个流,而且是一个可读可写的流,所以这里必须采用socket的方式来进行服务发送,还有一个需要注意的,就是这个socket的通信并不能采用同步的方式进行,因为如果同步阻塞数据就没办法读写了,读的时候程序直接卡住了,没办法继续下去了。这就是php需要的异步io技术。 swoole这块最大的问题就是参数的传递,我不得不定义好多global的变量,不然异步socket和websocket服务嵌套的用,代码看起来好丑陋。
ps: 我查了一下,php从5.3开始解决了这个问题,php这个版本之后支持闭包参数传递,有了use关键字的支持,另外对this关键字进行了改装,可以有效的简化这种写法,我闲的时候玩玩 ##最后,看看成果
参考资料:https://hui.lu/shi-yong-tornado-cao-zuo-docker-api/#webssh-with-frontend