Skip to content

Commit 0f1305b

Browse files
committed
optimization
1 parent 56605ec commit 0f1305b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+15013
-295
lines changed

README.md

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
# 部署安装
77

8-
主机操作系统为 Centos 7.5,python 版本 3.7.2,docker 版本 1.13.1。windows 上就不建议部署了,那是作死的事情。
8+
主机操作系统为 Centos 7.5,python 版本 3.7.2,docker 版本 1.13.1,项目目录为 `/home/workspace/devops` 。windows 上就不建议部署了,那是作死的事情。
99

1010
**安装依赖**
1111
```bash
@@ -14,6 +14,7 @@ yum install -y sshpass python3-devel mysql-devel
1414
```
1515
- ansible 连接插件 connection 使用 ssh 模式(还可以使用 paramiko 等模式)并且密码连接主机时需要使用 sshpass
1616
- python3-devel 与 mysql-devel 为 mysqlclient 库的依赖
17+
- 不建议使用 pymysql 代替 mysqlclient ,因为 pymysql 为纯 python 编写的库,性能较低
1718

1819
**安装 redis(docker 方式)**
1920
```bash
@@ -94,7 +95,7 @@ nohup gunicorn -c gunicorn.cfg devops.wsgi:application > logs/gunicorn.log 2>&1
9495
- sshd 为 ssh 代理服务器,监听 2222 端口,提供调用 securecrt、xshell、putty 以及 winscp 客户端支持,非必须
9596
- celery 后台任务处理进程,`export PYTHONOPTIMIZE=1` 此环境变量非常重要,不设置无法后台运行 ansible api
9697
- celery_beat 定时任务处理进程,读取 `devops/settings.py` 中设置的 `CELERY_BEAT_SCHEDULE` 定时任务,详见 v1.8.8 升级日志
97-
- 需要停止时 kill 相应的进程即可
98+
- 需要停止时 kill 相应的进程,然后删除 logs 目录下所有的 pid 文件
9899

99100
**nginx 前端代理**
100101
```
@@ -254,21 +255,38 @@ systemctl start nginx
254255
1. web 终端(包括 webssh,webtelnet)在使用 chrome 浏览器打开时,很大机率会出现一片空白无法显示 xterm.js 终端的情况。
255256
解决方法是改变一下 chrome 的缩放比例就好了(ctrl + 鼠标滚轮),在 firefox 下也有无此问题,但出现的机率小一些,具体原因未知。
256257

257-
2. webssh,clissh,webtelnet 记录命令功能有瑕疵:
258-
- 无法很好记录 tab 补全命令,暂时无解决方案
259-
- 无法很好记录使用上下箭头调用的历史命令,暂时无解决方案
260-
- 无法很好记录包括控制字符的命令(使用了退格、del 删除、关闭移动等操作),这个应该可以使用 gnu-readline 是解决
261-
- 无法识别进入和退出文本编辑模式(使用 vi、vim、emacs 等类似编辑器编辑文本),从而错误地把文本输入也记录为命令,
262-
看了国内比较出名的开源堡垒机 jumpserver 组件 koko 的源码也有类似问题,至少目前为止他们也是无法解决这个问题的
263-
- 正在寻找解决方案
258+
2. 关于前后端分离,有同学建议使用 vue 做前后端分离。这段时间也抽空看了一下 vue 全家桶的教程,看完后体会到是属于入门到放弃那种。
259+
而且我感觉前端的代码结构语法这些有点奇葩啊,太不习惯了,人老了也实时感觉是学不动了,遂暂时作罢(指定那天心血来潮又研究起来呢!)。
260+
即使要用 vue 也得先把后端 api 搞起来啥,所以还是先研究 django rest framework 吧。
264261

265262
# 升级日志
266263

264+
### ver1.9.0
265+
优化 webssh 与 webtelnet 终端大小自动调整功能;
266+
267+
优化 webssh、webtelnet 与 clissh 命令记录功能;
268+
- 记录执行时间
269+
- 支持控制字符
270+
- 支持历史命令
271+
- 支持识别 vi/vim 与 emacs 编辑器(使用 dumb 等无颜色的终端类型时会识别错误,
272+
所以就限制 webssh 使用 xterm 终端类型, clissh 使用 linux\ansi\xterm 终端类型)
273+
- 支持识别 ctrl + c 与 ctrl + z(其他特殊操作可能部分会不兼容)
274+
- tab 自动补全命令无法正常识别(tab 补全操作太复杂,不好判断)
275+
- 无法识别 top 等类似命令中输入的操作命令
276+
- 无法识别中文命令
277+
- 识别命令的准确率只能尽可能的提高,应该没有那个堡垒机敢说自己准确率100%
278+
- 真正想实现 100% 准确率,应该只有修改 shell 源码或者调用 shell 历史命令了(但是这些方式都有很多弊端)
279+
280+
优化 webrdp 下载文件时录像数据记录逻辑(不保存下载的文件内容到录像结果)
281+
267282
### ver1.8.9
268283
修正 clissh 与 webssh 使用 zmodem 时传输中途执行了取消操作后会丢失后续操作记录的 bug;
269284

270285
新增 webrdp 与 webvnc 挂载文件系统实现上传和下载文件;
271286
- 下载文件的方法是将需要下载的文件拖动到挂载的文件系统中的 `download` 文件夹中
287+
,简单测试了下,在 chrome(版本 81)下下载 300MB 的文件会导致 chrome 占用过大内存而崩溃;
288+
而 firefox(版本 72)也会占用较大内存(比 chrome 稍微小一些),但是不会崩溃,可以正常下载文件。
289+
所以需要下载大文件时建议先在远程主机上分卷压缩一下,然后批量下载小的分卷文件即可。
272290
- 上传文件的方法是通过点击浏览器上传文件,上传好的文件会在挂载的文件系统根目录中
273291
- 仅测试过 webrdp
274292

@@ -360,7 +378,7 @@ webssh 新增 zmodem(sz, rz) 上传下载文件支持(webtelnet 理论上也
360378

361379
个人信息中新增调用本地 SSH 客户端与本地 SFTP 客户端相关配置;
362380

363-
webssh 终端页面新增文件上传与下载功能(支持 5GB 以下文件,分段上传,不占用服务器内存);
381+
webssh 终端页面新增文件上传与下载功能(支持 4GB 以下文件,分段上传,不占用服务器内存);
364382

365383
修正 clissh 连接后无法使用 sz 下载文件和 rz 上传文件的 BUG(Zmodem 只适合上传下载小文件);
366384

apps/user/models.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ class User(models.Model):
6060
'clissh': [
6161
{
6262
'name': 'securecrt',
63-
'path': 'C:\\Program Files\\VanDyke Software\\Clients\\SecureCRT.exe',
63+
'path': 'C:\\Program Files (x86)\\VanDyke Software\\Clients\\SecureCRT.exe',
6464
'args': '/T /N "{username}@{host}-{hostname}" /SSH2 /L {login_user} /PASSWORD {login_passwd} {login_host} /P {port}',
6565
'enable': True
6666
},
@@ -72,7 +72,7 @@ class User(models.Model):
7272
},
7373
{
7474
'name': 'putty',
75-
'path': 'C:\\Users\\xx\\AppData\\Roaming\\TP4A\\Teleport-Assist\\tools\\putty\\putty.exe',
75+
'path': 'C:\\Program Files (x86)\\putty\\putty.exe',
7676
'args': '-l {login_user} -pw {login_passwd} {login_host} -P {port}',
7777
'enable': False
7878
},
@@ -86,7 +86,7 @@ class User(models.Model):
8686
'clisftp': [
8787
{
8888
'name': 'winscp',
89-
'path': 'C:\\Program Files\\winscp\\WinSCP.exe',
89+
'path': 'C:\\Program Files (x86)\\winscp\\WinSCP.exe',
9090
'args': '/sessionname="{username}@{host}-{hostname}" {login_user}:{login_passwd}@{login_host}:{port}',
9191
'enable': True
9292
},

apps/webguacamole/guacamoleclient.py

Lines changed: 64 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99
from guacamole.instruction import GuacamoleInstruction as Instruction
1010
import sys
1111
import os
12+
import base64
13+
import logging
14+
logging.basicConfig(level=logging.INFO, format='[%(asctime)s] - %(name)s - %(levelname)s - %(message)s')
15+
logger = logging.getLogger(__name__)
1216

1317

1418
# 重写 hanshake 方法以支持加入现有连接操作,实现查看会话功能
@@ -105,6 +109,8 @@ def __init__(self, websocker):
105109
tmp_date2 + '_' + gen_rand_char(16) + '.txt'
106110
self.res = []
107111
self.guacamoleclient = None
112+
self.file_index = {}
113+
self.file_cmd = ''
108114

109115
def connect(self, protocol, hostname, port, username, password, width, height, dpi, **kwargs):
110116
try:
@@ -130,21 +136,22 @@ def connect(self, protocol, hostname, port, username, password, width, height, d
130136
elif protocol == 'rdp':
131137
self.guacamoleclient.handshake(
132138
protocol=protocol,
133-
hostname=hostname,
134139
port=port,
135140
username=username,
136141
password=password,
142+
hostname=hostname,
137143
width=width,
138144
height=height,
139145
dpi=dpi,
140-
security="tls", # rdp,nla,tls,any
146+
security='tls', # rdp,nla,tls,any
141147
ignore_cert="true",
142148
disable_audio="true",
143149
client_name="devops",
144150
**kwargs,
145151
)
146152
Thread(target=self.websocket_to_django).start()
147153
except Exception:
154+
logger.error(traceback.format_exc())
148155
self.websocker.close(3001)
149156

150157
def django_to_guacd(self, data):
@@ -159,22 +166,62 @@ def websocket_to_django(self):
159166
# time.sleep(0.00001)
160167
data = self.guacamoleclient.receive()
161168
if not data:
162-
return
169+
message = str(base64.b64encode('连接被断开或者协议不支持'.encode('utf-8')), 'utf-8')
170+
self.websocker.send('6.toastr,1.2,{0}.{1};'.format(len(message), message))
171+
break
172+
173+
save_res = True
174+
175+
if data.startswith("4.file,"):
176+
tmp = data.split(",")
177+
file_index = tmp[1].split(".")[1]
178+
file_type = tmp[2].split(".")[1]
179+
file_name_tmp = tmp[3].rstrip(";").split(".")
180+
del file_name_tmp[0]
181+
file_name = '.'.join(file_name_tmp)
182+
self.file_index[file_index] = [file_name, file_type]
183+
message = str(base64.b64encode('开始下载文件 - {}'.format(file_name).encode('utf-8')), 'utf-8')
184+
self.websocker.send('6.toastr,1.3,{0}.{1};'.format(len(message), message))
185+
save_res = False
186+
187+
if self.file_index:
188+
if data.startswith("4.blob,"):
189+
tmp = data.split(",")
190+
index = tmp[1].split(".")[1]
191+
if index in self.file_index:
192+
# lenx = tmp[2].split(".")[0]
193+
# logger.info("file: {} len: {}".format(self.file_index[index][0], lenx))
194+
save_res = False
195+
196+
if data.startswith("3.end,"):
197+
tmp = data.split(",")
198+
index = tmp[1].rstrip(";").split(".")[1]
199+
if index in self.file_index:
200+
cmd_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(int(time.time())))
201+
self.file_cmd += cmd_time + "\t" + '下载文件 - {}'.format(self.file_index[index][0]) + '\n'
202+
message = str(base64.b64encode('文件下载完成 - {}'.format(self.file_index[index][0]).encode('utf-8')), 'utf-8')
203+
self.websocker.send('6.toastr,1.3,{0}.{1};'.format(len(message), message))
204+
save_res = False
205+
del self.file_index[index]
206+
163207
if self.websocker.send_flag == 0:
164208
self.websocker.send(data)
165209
elif self.websocker.send_flag == 1:
166210
async_to_sync(self.websocker.channel_layer.group_send)(self.websocker.group, {
167211
"type": "group.message",
168212
"text": data,
169213
})
170-
self.res.append(data)
171-
# 指定条结果或者指定秒数就保存一次
172-
if len(self.res) > 2000 or int(time.time() - self.last_save_time) > 60 or \
173-
sys.getsizeof(self.res) > 2097152:
174-
tmp = list(self.res)
175-
self.res = []
176-
self.last_save_time = time.time()
177-
res(self.res_file, tmp, False)
214+
215+
if save_res: # 不保存下载文件时的数据到录像
216+
self.res.append(data)
217+
# 指定条结果或者指定秒数就保存一次
218+
if len(self.res) > 2000 or int(time.time() - self.last_save_time) > 60 or \
219+
sys.getsizeof(self.res) > 2097152:
220+
tmp = list(self.res)
221+
self.res = []
222+
self.last_save_time = time.time()
223+
res(self.res_file, tmp, False)
224+
178225
except Exception:
179226
if self.websocker.send_flag == 0:
180227
self.websocker.send('0.;')
@@ -187,8 +234,11 @@ def websocket_to_django(self):
187234
self.close()
188235

189236
def close(self):
190-
self.websocker.close()
191-
self.guacamoleclient.close()
237+
try:
238+
self.websocker.close()
239+
self.guacamoleclient.close()
240+
except Exception:
241+
logger.error(traceback.format_exc())
192242

193243
def shell(self, data):
194244
self.django_to_guacd(data)
@@ -228,7 +278,7 @@ def websocket_to_django(self):
228278
# time.sleep(0.00001)
229279
data = self.guacamoleclient.receive()
230280
if not data:
231-
return
281+
break
232282
self.websocker.send(data)
233283
except Exception:
234284
self.websocker.send('0.;')

apps/webguacamole/views_api.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
from ratelimit import ALL
66
from util.rate import rate, key
77
from util.tool import login_required, post_required, file_combine
8+
from channels.layers import get_channel_layer
9+
from asgiref.sync import async_to_sync
810
import os
911
import hashlib
1012
# Create your views here.
@@ -51,6 +53,13 @@ def session_upload(request):
5153
"filename": file_name,
5254
"complete": complete,
5355
}
56+
if complete:
57+
channel_layer = get_channel_layer()
58+
async_to_sync(channel_layer.group_send)(group, {
59+
"type": "upload.message",
60+
"text": file_name,
61+
})
62+
5463
return JsonResponse(mess) # fileinput 分片上传
5564
except Exception:
5665
error_message = '上传文件错误!'

apps/webguacamole/websocket_layer.py

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,11 @@
1313
import re
1414
import base64
1515
from django.http.request import QueryDict
16-
import sys
1716
import os
1817
import shutil
18+
import logging
19+
logging.basicConfig(level=logging.INFO, format='[%(asctime)s] - %(name)s - %(levelname)s - %(message)s')
20+
logger = logging.getLogger(__name__)
1921

2022

2123
try:
@@ -164,6 +166,10 @@ def disconnect(self, close_code):
164166
except Exception:
165167
pass
166168
try:
169+
async_to_sync(self.channel_layer.group_send)(self.group, { # 关闭 viewer
170+
"type": "close.viewer",
171+
"text": "",
172+
})
167173
async_to_sync(self.channel_layer.group_discard)(self.group, self.channel_name)
168174
if close_code != 3001:
169175
self.guacamoleclient.close()
@@ -186,7 +192,7 @@ def disconnect(self, close_code):
186192
self.remote_host.get_protocol_display(),
187193
self.remote_host.port,
188194
self.remote_host.remote_user.username,
189-
'',
195+
self.guacamoleclient.file_cmd,
190196
self.guacamoleclient.res_file,
191197
self.client,
192198
self.user_agent,
@@ -256,6 +262,11 @@ def check_timeout(self, sleep_time=3):
256262

257263
time.sleep(sleep_time)
258264

265+
def upload_message(self, data):
266+
cmd_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(int(time.time())))
267+
filename = data['text']
268+
self.guacamoleclient.file_cmd += cmd_time + "\t" + '上传文件 - {}'.format(filename) + '\n'
269+
259270

260271
class WebGuacamole_view(WebsocketConsumer):
261272
def __init__(self, *args, **kwargs):
@@ -299,10 +310,23 @@ def connect(self):
299310
dpi=dpi,
300311
enable_font_smoothing="true",
301312
)
313+
# 发送分辨率信息到查看模式的客户端
314+
self.send("7.display,{0}.{1},{2}.{3},{4}.{5};".format(len(width), width, len(height), height, len(dpi), dpi))
315+
316+
async_to_sync(self.channel_layer.group_add)(self.group, self.channel_name) # 加入组
302317

303318
def disconnect(self, close_code):
319+
async_to_sync(self.channel_layer.group_discard)(self.group, self.channel_name)
304320
self.guacamoleclient.close()
305321

306322
def receive(self, text_data=None, bytes_data=None):
307323
if text_data.startswith('4.sync') or text_data.startswith('3.nop'):
308324
self.guacamoleclient.shell(text_data)
325+
326+
def close_viewer(self, data):
327+
message = str(base64.b64encode('会话已关闭'.encode('utf-8')), 'utf-8')
328+
self.send('6.toastr,1.2,{0}.{1};'.format(len(message), message))
329+
self.close()
330+
331+
def upload_message(self, data):
332+
pass

0 commit comments

Comments
 (0)