项目要求,需要在web使用ssh或者控制台,就找有没有好的方法可以实现,找到了xterm.js+paramiko+websocket等实现交互,xtarm.js可以在前端展示一个终端,可以自定义,通过websocket和后端进行通信,后端可以自己写一个脚本程序,配置一个websocket的server端进行通信。
paramiko
在这里我本来想的是直接在python里调用终端命令执行,但是单独使用的时候可以,集成起来就不好用了,为什么不好用后面说,先说几种常用的终端命令的执行。
import os
os.system('ls')
会启动一个子进程调用command执行命令,在终端直接执行会显示结果,在程序中使用只会有返回值,结果不会返回。
os.popen(command,mode)
import os
os.popen('cat /proc/cpuinfo')
直接与command进程通信的一个管道,返回一个文件对象,mode可以指定模式,如果是’r’
,可以使用read()或者readlines()
方法读取返回的执行结果。
import commands
status=commands.getstatus('cat /proc/cpuinfo')
output=commands.getoutput('ls -l')
(status,output)=commands.getstatusoutput('ls -l')
会返回一个元组,包含执行结果和返回的状态。output中包含了控制台的输出信息和错误信息等。
是python2.4出现的一个模块,可以代替上面的模块方法,还集中了多个关于进程的操作。call()
完全替代了system()
,popen()
由Popen
类进行实现和完善。
这个没什么好介绍的(就是我不想写,直接百度都是,而且主要是看实现的过程,概念这些东西估计也没多少人想看)。
主要就是,它是socket的更高级版本吧,然后呢,可以双方都使用websocket进行通信,会比较方便;也可以使用socket进行,如python中使用socket实现服务端,客户端使用websocket进行连接。
但是会非常麻烦,需要自己处理握手通信以及数据的编码解码等处理操作,虽然网上可以找到很多,但是我看了好些综合了好几个博客的代码,都没有实现,接收到的数据都是乱码,而且我传输的都是英文,没有中文,也不涉及编码混乱的问题,因此呢,手动实现就可以放弃了。
然后我找到由第三方库实现websocket的功能。
这是github
但是好像需要python3版本才行,我是2,就放弃了。
这是github
这个我试了一下很好用,主要是很方便,把websocket的操作都封装好了,直接使用就行了,
官方示例如下,看代码也不用多解释,建立一个连接,然后新建一个类,有三个方法处理三个事件就好了。
from SimpleWebSocketServer import SimpleWebSocketServer, WebSocket
class SimpleEcho(WebSocket):
def handleMessage(self):
# echo message back to client
self.sendMessage(self.data)
def handleConnected(self):
print(self.address, 'connected')
def handleClose(self):
print(self.address, 'closed')
server = SimpleWebSocketServer('', 8000, SimpleEcho)
server.serveforever()
问题来了,就是在这里,单独用脚本执行commands.getstatusoutput
没有问题,但是放在这个框架里就有问题来了,所以在这里没法用。
然后呢,这个框架有点问题,如果你的程序在运行中突然断开连接了,或者刚连上就断开了,说明代码有问题,这个库代码出错是不会报错的,只会断开连接。善用print调试,定位出错位置。
ssh是一个协议,OpenSSH是其中一个开源实现,paramiko是Python的一个库,实现了SSHv2协议(底层使用cryptography)。
有了Paramiko以后,我们就可以在Python代码中直接使用SSH协议对远程服务器执行操作,而不是通过ssh命令对远程服务器进行操作。
安装的话,直接pip就行了
pip install paramiko
这里只介绍SSHClient
基本使用如下
def sshConnection():
ip = '192.168.1.104'
port = 22
user = 'root'
passwd= 'root'
cmd = 'cd /home/kang/Desktop'
# 设置记录日志
log_file = 'kali_ssh.log'
util.log_to_file(log_file)
# 生成ssh客户端实例
s = SSHClient()
s.set_missing_host_key_policy(AutoAddPolicy())
print ("[+] Start ssh into: "+ip)
s.connect(ip, port, user, passwd)
print ("[+] SSH established !")
s.exec_command('ls -l')
recv=s.recv(1024)
print(recv)
chan.close()
s.close()
但是有一个小问题,这里如果使用exec_command
,他是非交互式的,差不多就是每次只能执行一条,如果需要使用环境变量啥的,就不行了,得bash -ls 'ls -l’
才行。如果需要执行多条语句,使用;
分隔开。
而且如果出现比如进入python终端这种情况,exec_command
就无法执行了,会卡住,可能是无法判断出后台终端的响应。
比较推荐的是交互式的方式,相当于创建一个shell终端,在其中运行命令,但是这个的问题是,如何判断命令是否执行完成,如果等的时间太短可能获取不到结果,如果等的时间太长用户体验不好。
这里通过返回的SP1输入提示符进行判断,在返回结果的最后都会返回一个输入的提示符,而每个界面或者系统的提示符不同,可能会有#
结尾的,也可能是$
结尾的。因此可以把这些添加到一个list中,进行判断。
下面是一个示例
import paramiko
import time
hostname = '192.16.21.12'
port = 22
username = 'hadoop'
password = 'hadoop'
timeout = 10
def runCommand(chanT, command, endSymbol):
chanT.send(command + '\n') # 指令后加 '\n' 表示换行
results = ''
while True:
result = chanT.recv(1024).decode('utf-8')
results += result
if results[-2:] in endSymbol: # 判断最后两个字符是否是我们定义的结束符
break
re = results.split('\n')[1:] # 第一行是我们输入的指令,没用丢弃
print('\n'.join(re), end='')
return re[:-1] # 最后一行是linux的SP1输入提示符,没用丢弃
if __name__ == "__main__":
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(hostname, port, username, password)
chan = ssh.invoke_shell() # 创建一个交互式的shell窗口
chan.settimeout(1000)
time.sleep(3) # 刚进入linux服务器等待一会,否则直接通过chan.recv获取的信息不完整
loginInfo = chan.recv(1024).decode('utf-8') # Welcome to Ubuntu 16.04.6 LTS..等登录信息
print(loginInfo, end='')
endSymbol = ['$ ', '> ', '* '] # 设置我们定义的结束符
while True:
command = input(): # 等待用户输入指令
if command == 'quitshell' # 当用户输入quitshell指令时退出程序
print('Bye Bye!')
exit(0)
result = runCommand(chan, command, endSymbol)
可以使用多线程进一步优化性能。
这是github
xtarm.js的使用很简单,引入两个文件,xterm/css/xterm.css和xterm/lib/xterm.js
<html>
<head>
<link rel="stylesheet" href="node_modules/xterm/css/xterm.css" />
<script src="node_modules/xterm/lib/xterm.js">script>
head>
<body>
<div id="terminal">div>
<script>
var term = new Terminal();
term.open(document.getElementById('terminal'));
term.write('Hello from \x1B[1;3;31mxterm.js\x1B[0m $ ')
script>
body>
html>
现在放一个我差不多实现好的一个web terminal,后台连接是通过本地ssh连接的。
<div id="terminal">
div>
<script>
//terminal
var ContainerID='{{ ContainerID }}';
var ContainerName='{{ ContainerName }}';
var UserName='{{ UserName }}';
var Password='{{ Password }}';
var ProjectName='{{ ProjectName }}';
var input='';
var prefix='\r\n~$ ';
var term = new Terminal({
cursorStyle: 'underline', //光标样式
cursorBlink: true, // 光标闪烁
convertEol: true, //启用时,光标将设置为下一行的开头
disableStdin: false, //是否应禁用输入。
theme: {
foreground: 'yellow', //字体
background: '#060101', //背景色
cursor: 'help',//设置光标
}
});
term.open(document.getElementById('terminal'));
function runFakeTerminal() {
if (term._initialized) {
return;
}
term._initialized = true;
term.prompt = () => {
term.write(prefix);
};
term.writeln('Welcome to terminal');
prompt(term);
term.onKey(e => {
const printable = !e.domEvent.altKey && !e.domEvent.altGraphKey && !e.domEvent.ctrlKey && !e.domEvent.metaKey;
if (e.domEvent.keyCode === 13) {
//pressed enter key
//prompt(term);
ws.send(input);
input='';
} else if (e.domEvent.keyCode === 8) {
// Do not delete the prompt
if (term._core.buffer.x > 2) {
term.write('\b \b');
input=input.slice(0,-1);
}
} else if (printable) {
term.write(e.key);
input+=e.key;
}
console.log(e.key);
});
}
function prompt(term) {
term.write(prefix);
}
runFakeTerminal();
//websocket
var ws=new WebSocket('ws://127.0.0.1:6789');
ws.onopen=function(){
//Command='!sep#;export OS_USERNAME=demo!sep#;export OS_PASSWORD=demo!sep#;export OS_PROJECT_NAME=demo!sep#;export OS_USER_DOMAIN_NAME=Default!sep#;export OS_PROJECT_DOMAIN_NAME=Default!sep#;export OS_AUTH_URL=http://controller:5000/v3!sep#;export OS_IDENTITY_API_VERSION=3';
//ws.send(Command);
}
ws.onmessage = function (evt)
{
var RecvMsg = evt.data;
//alert("数据已接收..."+RecvMsg);
//var DataArray=RecvMsg.split('!sep#;');
var DataArray=RecvMsg.split('\n');
if (DataArray.length==1)
prefix='\r\n'+DataArray[0];
else{
for (var i=0;i<DataArray.length;i++){
if (i==0)
term.write('\r\n'+DataArray[i]);
else if (i<DataArray.length-1)
term.write(DataArray[i]+'\r\n');
else if (i==DataArray.length-1){
prefix='\r\n'+DataArray[i];
//term.write('\r\n');
}
else
term.write(DataArray[i]);
}
}
prompt(term);
};
script>
有不少没用的提示输出,自己看着删除吧,print调试大法好。
需要注意的是,如果想在这里面再进去其他的终端,要在self.EndSymbol
添加新的提示符,比如如果想进入docker,虽然执行docker exec -it c618 /bin/sh
然后提示符是 #
,别看也是#
,但是他并不普通字符,而是[J
,因此需要添加这个。
然后我发现,执行openstack appcontainer exec --interactive bbb /bin/sh
也是一样,需要添加提示符[J
。
而且还需要屏蔽一下exit
命令,不然访客就可以退出容器直接去你的终端了。
有一个小问题,我不知道为什么执行不了docker ps
命令,明明已经获取成功了,但是就是返回数据的时候报错,我不知道为什么。
# -*- coding: utf-8 -*-
from __future__ import print_function
from SimpleWebSocketServer import SimpleWebSocketServer, WebSocket
import commands
import os
from paramiko import util
from paramiko import SSHClient
from paramiko import AutoAddPolicy
import paramiko
class SimpleEcho(WebSocket):
def handleMessage(self):
# echo message back to client
print('[%s] - Get:%s' % (self.address,self.data))
result=self.runCommand(self.data)
print('main reply')
print(result)
self.sendMessage(result)
print('[%s] - Send:%s' % (self.address,result))
def handleConnected(self):
print(self.address, 'connected')
ip = '192.168.1.104'
port = 22
user = 'root'
passwd= 'root'
# 设置记录日志
log_file = 'centos_ssh.log'
util.log_to_file(log_file)
# 生成ssh客户端实例
self.ssh = SSHClient()
self.ssh.set_missing_host_key_policy(AutoAddPolicy())
print('get ssh client')
self.ssh.connect(ip,port,user,passwd)
print('ssh login successfully')
self.chan=self.ssh.invoke_shell()
print('get invoke_shell')
self.chan.settimeout(1000)
# 刚进入linux服务器等待一会,否则直接通过chan.recv获取的信息不完整
#
second=1
for a in range(second):
for i in range(30):
for j in range(1145):
for k in range(1000):
u=i*j
#
print('sleep 1s ')
LoginInfo=self.chan.recv(2048) # Welcome to Ubuntu 16.04.6 LTS..等登录信息
print(LoginInfo)
#=====================================================================
self.EndSymbol=['$ ','# ','> ','* '] # 设置我们定义的结束符
print('start transport')
def handleClose(self):
print(self.address, 'closed')
#接收指令的方法
def runCommand(self,Command):
print('exec command:'+Command)
self.chan.send(Command+'\n') # 指令后加 '\n' 表示换行
Result=''
print('waiting for reply')
while True:
Temp=self.chan.recv(4096)
Result+=Temp
if Result[-2:] in self.EndSymbol: # 判断最后两个字符是否是我们定义的结束符
break
Final=Result.split('\n')[1:] # 第一行是我们输入的指令,没用丢弃
print('开始输出结果')
print('\n'.join(Final),end='')
print('输出结果完成')
#return Final[:-1]
return '\n'.join(Final) # 最后一行是linux的SP1输入提示符,没用丢弃
print('start listening')
server = SimpleWebSocketServer('127.0.0.1', 6789, SimpleEcho)
server.serveforever()
Python3 使用Websocket通信 - qian99
python之SSH(交互式和非交互式) - 绿风1号
python3通过paramiko远程交互式控制Linux服务器 - 太阳花的小绿豆
第二篇:ssh.invoke_shell() 切换root出现的新问题 - FelixApff
Js 之xterm.js终端插件 - 样子2018