由于要在页面上调用后台的一个shell程序,但是这个shell执行时间很长,如果异步获取shell的输出?而不必漫长的等待shell执行完毕才会一下把数据全部输出?
我们知道原生的http协议不可能完成这个要求,除非你把输出更新到一个文本里,然后用js伦询去取,这不扯淡吗
因为是内部的程序,当然有这样要求的程序基本都是内部,或者小团体使用,所以,尝试一下websocket吧
先研究一下websocket协议
首先是握手 handleshake
GET /chat HTTP/1.1 Host: server.example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== Origin: http://example.com Sec-WebSocket-Protocol: chat, superchat Sec-WebSocket-Version: 13客户端会发送这么一个头给服务器
然后服务器会返回这么一个头给客户端
HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= Sec-WebSocket-Protocol: chat
其中有一个注意点
Sec-WebSocket-Key和
Sec-WebSocket-Accept前者是客户端,也就是客户端生成的,那么后者咋生成咧
公式如下
Sec-WebSocket-Accept = base64(sha1(Sec-WebSocket-Key+258EAFA5-E914-47DA-95CA-C5AB0DC85B11))
"+"号不算在内
具体python代码如下
# -*- coding: utf8 -*-
import socket
import time
from threading import Thread
import hashlib
import base64
class returnCrossDomain(Thread):
def __init__(self,connection):
Thread.__init__(self)
self.con = connection
self.isHandleShake = False
def run(self):
while True:
if not self.isHandleShake: #握手
clientData = self.con.recv(1024)
dataList = clientData.split("\r\n")
header = {}
print clientData
for data in dataList:
if ": " in data:
unit = data.split(": ")
header[unit[0]] = unit[1]
secKey = header['Sec-WebSocket-Key'];
resKey = base64.encodestring(hashlib.new("sha1",secKey+"258EAFA5-E914-47DA-95CA-C5AB0DC85B11").digest()).replace('\n','');
response = '''HTTP/1.1 101 Switching Protocols\r\n'''
response += '''Upgrade: websocket\r\n'''
response += '''Connection: Upgrade\r\n'''
response += '''Sec-WebSocket-Accept: %s\r\n'''%(resKey,)
response += '''Sec-WebSocket-Protocol: chat\r\n\r\n'''
self.con.send(response)
self.isHandleShake = True
else:
data = self.con.recv(1024)
print data
def main():
sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sock.bind(('',88))
sock.listen(100)
while True:
try:
connection,address = sock.accept()
returnCrossDomain(connection).start()
except:
time.sleep(1)
if __name__=="__main__":
main()
js代码如下
var socket = new WebSocket('ws://localhost:88');
socket.onopen = function() {
alert(1);
}
顺利弹出1
下边开始研究数据传输 有一个问题折腾了我好久,就是因为我复制的时候没有把这个头给去掉,导致浏览器一直收不到信息,我郁闷阿
response += '''Sec-WebSocket-Protocol: chat\r\n\r\n'''
这句话的意思就是,使用chat这个协议,而不使用websocket协议,所以我按照websocket协议来,肯定怎么都收不到数据,去掉之后,果断成功
websocket的数据传输部分有点小麻烦,老版本的websocket 是很简单的,每条消息都是 一个0xFF打头,0x00结尾,很容易,但是肯定会引发安全问题
新版本的协议如下
服务端从客户端读数据
1.读1Byte字节,注意是Byte=8bits,这个要注意,奶奶的,刚看说明文档的时候,看过了,怎么也看不懂
这1个Byte分 8个bit ,前4个bit 不去研究,后4个bit代表这个数据段的作用,比如是一条数据,一条socket关闭信息等,
获取这四个字节很简单
data_head = self.con.recv(1)
header = struct.unpack("B",data_head)[0]
opcode = header & 0b00001111
* %x0 denotes a continuation frame * %x1 denotes a text frame * %x2 denotes a binary frame * %x3-7 are reserved for further non-control frames * %x8 denotes a connection close * %x9 denotes a ping * %xA denotes a pong * %xB-F are reserved for further control frames
2.再读取1个byte注意是byte=8个bits
这8个bits分两端,第一bit代表是否masking,我的理解就是是否加密
后7个bits代表着真正数据的长度payloadlength,如果payloadlength<=125那么数据长度就是payloadlength,如果payloadlength=126,那么就再去2Bytes的unsiged integer,来代表数据长度,如果payloadlength = 127 那么 就再读8个字节的unsigned long long,具体代码如下
data_length = self.con.recv(1)
data_lengths= struct.unpack("B",data_length)
data_length = data_lengths[0]& 0b01111111
print bin(data_lengths[0])
masking = data_lengths[0] >> 7
if data_length<=125:
payloadLength = data_length
elif data_length==126:
payloadLength = struct.unpack("H",self.con.recv(2))[0]
elif data_length==127:
payloadLength = struct.unpack("Q",self.con.recv(8))[0]
print "字符串长度是:%d"%(data_length,)
3.上一步 读取到的masking 用来判断是否加密
如果masking==1
那么就读取4个Bytes
然后按照长度读取数据,进行解密工作,具体代码和算法如下
如果masking==0
那么就直接读取刚才计算得到的数据长度的数据了
if masking==1:
print "是masking"
maskingKey = self.con.recv(4)
self.maskingKey = maskingKey
data = self.con.recv(payloadLength)
i = 0
true_data = ''
for d in data:
true_data += chr(ord(d) ^ ord(maskingKey[i%4]))
i += 1
self.onData(true_data)
发送数据很简单,和接收数据一样的格式,不过我们就不用masking 加密了,所以第二个字节的第一个bit设为0
具体代码如下
def sendData(self,text) :
print "给客户端发送信息%s"%(text,)
#头
self.con.send(struct.pack("!B",0x81))
#计算长度
length = len(text)
# masking = 0b00000000;
if length<=125:
self.con.send(struct.pack("!B",length))
elif length<=65536:
self.con.send(struct.pack("!B",126))
self.con.send(struct.pack("!H",length))
else:
self.con.send(struct.pack("!B",127))
self.con.send(struct.pack("!Q",length))
self.con.send(struct.pack("!%ds"%(length,),text))
具体的代码可以去我的git上下载,我已经封装好了模块,对应的js也有一个写好的类,里边有demo 欢迎大家尝试
https://github.com/suxianbaozi/pywebsocketserver