一、开始的话
使用python简单的实现websocket服务器,可以在浏览器上实时显示远程服务器的日志信息。
之前做了一个web版的发布系统,但没实现在线看日志,每次发布版本后,都需要登录到服务器上查看日志,非常麻烦,为了偷懒,能在页面点几下按钮完成工作,所以这几天查找了这方面的资料,实现了这个功能,瞬间觉的看日志什么的,太方便了,以后也可以给开发们查日志,再也不用麻烦运维了,废话少说,先看效果吧。
二、代码
在实现这功能前,看过别人的代码,发现很多都是只能在web上显示本地的日志,不能看远程主机上的日志,有些能看远程日志的是引用了其他框架(例如bottle,tornado)来实现的,而且所有这些都是重写thread的run方法来实现的,由于本人技术太菜,不知道怎么改成自己需要的样子,而且我是用django这个web框架的,不想引用太多框架,搞的太复杂,所以用python来实现websocket服务器。由于技术问题,代码有点粗糙,不过能实现功能就行,先将就着用吧。
执行下面命令启动django和websocketserver
nohup python manage.py runserver 10.1.12.110 & nohup python websocketserver.py &
启动websocket后,接收到请求,起一个线程和客户端握手,然后根据客户端发送的ip和type,去数据库查找对应的日志路径,用paramiko模块ssh登录到远程服务器上tail查看日志,再推送给浏览器,服务端完整代码如下:
1 # coding:utf-8 2 import struct 3 import base64 4 import hashlib 5 import socket 6 import threading 7 from public.public import get_ssh 8 import os 9 10 11 def recv_data(conn): # 服务器解析浏览器发送的信息 12 try: 13 all_data = conn.recv(1024) 14 if not len(all_data): 15 return False 16 except: 17 pass 18 else: 19 code_len = ord(all_data[1]) & 127 20 if code_len == 126: 21 masks = all_data[4:8] 22 data = all_data[8:] 23 elif code_len == 127: 24 masks = all_data[10:14] 25 data = all_data[14:] 26 else: 27 masks = all_data[2:6] 28 data = all_data[6:] 29 raw_str = "" 30 i = 0 31 for d in data: 32 raw_str += chr(ord(d) ^ ord(masks[i % 4])) 33 i += 1 34 return raw_str 35 36 37 def send_data(conn, data): # 服务器处理发送给浏览器的信息 38 if data: 39 data = str(data) 40 else: 41 return False 42 token = "\x81" 43 length = len(data) 44 if length < 126: 45 token += struct.pack("B", length) # struct为Python中处理二进制数的模块,二进制流为C,或网络流的形式。 46 elif length <= 0xFFFF: 47 token += struct.pack("!BH", 126, length) 48 else: 49 token += struct.pack("!BQ", 127, length) 50 data = ‘%s%s‘ % (token, data) 51 conn.send(data) 52 return True 53 54 55 def handshake(conn, address, thread_name): 56 headers = {} 57 shake = conn.recv(1024) 58 if not len(shake): 59 return False 60 61 print (‘%s : Socket start handshaken with %s:%s‘ % (thread_name, address[0], address[1])) 62 header, data = shake.split(‘\r\n\r\n‘, 1) 63 for line in header.split(‘\r\n‘)[1:]: 64 key, value = line.split(‘: ‘, 1) 65 headers[key] = value 66 67 if ‘Sec-WebSocket-Key‘ not in headers: 68 print (‘%s : This socket is not websocket, client close.‘ % thread_name) 69 conn.close() 70 return False 71 72 MAGIC_STRING = ‘258EAFA5-E914-47DA-95CA-C5AB0DC85B11‘ 73 HANDSHAKE_STRING = "HTTP/1.1 101 Switching Protocols\r\n" 74 "Upgrade:websocket\r\n" 75 "Connection: Upgrade\r\n" 76 "Sec-WebSocket-Accept: {1}\r\n" 77 "WebSocket-Origin: {2}\r\n" 78 "WebSocket-Location: ws://{3}/\r\n\r\n" 79 80 sec_key = headers[‘Sec-WebSocket-Key‘] 81 res_key = base64.b64encode(hashlib.sha1(sec_key + MAGIC_STRING).digest()) 82 str_handshake = HANDSHAKE_STRING.replace(‘{1}‘, res_key).replace(‘{2}‘, headers[‘Origin‘]).replace(‘{3}‘, headers[‘Host‘]) 83 conn.send(str_handshake) 84 print (‘%s : Socket handshaken with %s:%s success‘ % (thread_name, address[0], address[1])) 85 print ‘Start transmitting data...‘ 86 print ‘- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -‘ 87 return True 88 89 90 def dojob(conn, address, thread_name): 91 from mode import models 92 handshake(conn, address, thread_name) # 握手 93 log_info = recv_data(conn) 94 log_ip = log_info.split(":")[0] 95 log_type = log_info.split(":")[1] 96 97 auth_ = models.Authentication.objects.get(ip=log_ip) 98 user = auth_.a_user 99 pwd = auth_.a_password 100 try: 101 log_path = models.LogPath.objects.get(ip=log_ip, type_name__contains=log_type).log_path 102 except Exception, e: 103 send_data(conn, e) 104 conn.close() 105 print "Error:" + str(e) 106 print (‘%s : Socket close with %s:%s‘ % (thread_name, address[0], address[1])) 107 return 108 conn.setblocking(0) # 设置socket为非阻塞 109 ssh = get_ssh(log_ip, user, pwd) 110 ssh_t = ssh.get_transport() 111 chan = ssh_t.open_session() 112 chan.setblocking(0) # 设置非阻塞 113 chan.exec_command(‘tail -f %s‘ % log_path) 114 while True: 115 clientdata = recv_data(conn) 116 if clientdata is not None and ‘quit‘ in clientdata: 117 print (‘%s : Socket close with %s:%s‘ % (thread_name, address[0], address[1])) 118 send_data(conn, ‘close connect‘) 119 conn.close() 120 break 121 while True: 122 while chan.recv_ready(): 123 clientdata1 = recv_data(conn) 124 if clientdata1 is not None and ‘quit‘ in clientdata1: 125 print (‘%s : Socket close with %s:%s‘ % (thread_name, address[0], address[1])) 126 send_data(conn, ‘close connect‘) 127 conn.close() 128 break 129 log_msg = chan.recv(10000).strip() 130 print log_msg 131 send_data(conn, log_msg) 132 if chan.exit_status_ready(): 133 break 134 clientdata2 = recv_data(conn) 135 if clientdata2 is not None and ‘quit‘ in clientdata2: 136 print (‘%s : Socket close with %s:%s‘ % (thread_name, address[0], address[1])) 137 send_data(conn, ‘close connect‘) 138 conn.close() 139 break 140 break 141 142 143 def ws_service(): 144 145 os.environ.setdefault("DJANGO_SETTINGS_MODULE", "lbg.settings") 146 index = 1 147 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 148 sock.bind(("127.0.0.1", 12345)) 149 sock.listen(100) 150 151 print (‘\r\n\r\nWebsocket server start, wait for connect!‘) 152 print ‘- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -‘ 153 while True: 154 connection, address = sock.accept() 155 thread_name = ‘thread_%s‘ % index 156 print (‘%s : Connection from %s:%s‘ % (thread_name, address[0], address[1])) 157 t = threading.Thread(target=dojob, args=(connection, address, thread_name)) 158 t.start() 159 index += 1 160 161 162 ws_service()
get_ssh的代码如下:
import paramiko def get_ssh(ip, user, pwd): try: ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) ssh.connect(ip, 22, user, pwd, timeout=15) return ssh except Exception, e: print e return "False"
打开页面时,自动连接websocket服务器,完成握手,并发送ip和type给服务端,所以可以看不同类型,不同机器上的日志,
页面代码如下:
<!DOCTYPE html> <html> <head> {% include ‘head_script.html‘ %} <style> #log { width: 440px; height: 200px; border: 1px solid #7F9DB9; overflow: auto; } pre { margin: 0 0 0; padding: 0; border: hidden; background-color: #0c0c0c; color: #00ff00; } #btns { text-align: right; {# 按钮靠右 #} } </style> <script> var socket; function init() { var host = "ws://127.0.0.1:12345/"; var ips="{{ ip }}"; var types="{{ type }}"; var log_msg = ips +":"+ types; console.log(log_msg); try { socket = new WebSocket(host); socket.onopen = function () { log(‘Connected‘); socket.send(log_msg); }; socket.onmessage = function (msg) { log(msg.data); var obje = document.getElementById("log"); //日志过多时清屏 var textlength = obje.scrollHeight; if (textlength > 10000) { obje.innerHTML = ‘‘; } }; socket.onclose = function () { log("Lose Connection!"); $("#start").attr(‘disabled‘, false); $("#stop").attr(‘disabled‘, true); }; $("#start").attr(‘disabled‘, true); $("#stop").attr(‘disabled‘, false); } catch (ex) { log(ex); } } window.onbeforeunload = function () { try { socket.send(‘quit‘); socket.close(); socket = null; } catch (ex) { log(ex); } }; function log(msg) { var obje = document.getElementById("log"); obje.innerHTML += ‘<pre><code>‘ + msg + ‘</code></pre>‘; obje.scrollTop = obje.scrollHeight; //滚动条显示最新数据 } function stop() { try { log(‘Close connection!‘); socket.send(‘quit‘); socket.close(); socket = null; $("#start").attr(‘disabled‘, false); $("#stop").attr(‘disabled‘, true); } catch (ex) { log(ex); } } function closelayer() { try { log(‘Close connection!‘); socket.send(‘quit‘); socket.close(); socket = null; } catch (ex) { log(ex); } var index = parent.layer.getFrameIndex(window.name); //先得到当前iframe层的索引 parent.layer.close(index); //再执行关闭 } </script> </head> <body onload="init()"> <div class="row"> <div class="col-lg-12"> <div id="log" style="width: 100%;height:440px;background-color: #0c0c0c;overflow:scroll;overflow-x: auto;"></div> <br> </div> </div> <div class="row"> <div class="col-lg-12"> <div id="btns"> <input disabled="disabled" type="button" class="btn btn-primary btn-sm" value="start" id="start" onclick="init()"> <input disabled="disabled" type="button" class="btn btn-primary btn-sm" value="stop" id="stop" onclick="stop()" > <input type="button" class="btn btn-primary btn-sm" value="close" id="close" onclick="closelayer()" > </div> </div> </div> </body> {% include ‘foot_script.html‘ %} </html>
时间: 2024-10-05 14:19:43