如何实现JS前端与Python后台的结合

  这篇笔记是帮助那些,想把自己的python模型展示在web网页上的朋友们,具体来说就是在javascript的前端与python的后台之间实现数据的传输。
  我自己的网站就是用这个方法写的,感兴趣的朋友可以戳:http://www.gwylab.com/works.html。

  先说明一下,对于我们这种穷学生,网页服务器用的是空间(我是租不起GPU服务器的。。),也就是云虚拟主机的分割,仅支持php,不支持python和java,所以训练好的python模型没法在网站后台跑。。
  但是python模型在自己/实验室的电脑上是能跑的,于是我们就可以想办法把前端数据跨域传给本地计算机上的python接口,下面就要介绍一种用websocket进行数据传输的方法。
  其实js对于websocket的支持是很不错的,传输、解码都很快(毕竟WebSocket是HTML5出的),但是python对于web就有很多问题,所以这篇经验的重点在于python对websocket的传输与加、解码。我们先来看一下websocket的协议格式:
如何实现JS前端与Python后台的结合_第1张图片
  根据这张图我们便能在python端一步步写出读取数据的方法:

  第一步get opcode:

	def getOpcode(self):
		first8Bit = self.con.recv(1)
		first8Bit = struct.unpack('B', first8Bit)[0]
		opcode = first8Bit & 0b00001111
		return opcode

  第二步get datalength:

	def getDataLength(self):
	    second8Bit = self.con.recv(1)
	    second8Bit = struct.unpack('B', second8Bit)[0]
	    masking = second8Bit >> 7
	    dataLength = second8Bit & 0b01111111
	    #print("dataLength:",dataLength)
	    if dataLength <= 125:
	        payDataLength = dataLength
	    elif dataLength == 126:
			payDataLength = struct.unpack('H', self.con.recv(2))[0]
	    elif dataLength == 127:
	        payDataLength = struct.unpack('Q', self.con.recv(8))[0]
	    self.masking = masking
	    self.payDataLength = payDataLength

  第三步get clientdata:

	def readClientData(self):
	
	    if self.masking == 1:
	        maskingKey = self.con.recv(4)
	    data=self.con.recv(self.payDataLength)
	
	    if self.masking == 1:
	        i = 0
	        trueData = ''
	        for d in data:
	            trueData += chr(d ^ maskingKey[i % 4])
	            i += 1
	        return trueData
	    else:
	        return data

  同样可以写出一个发送数据的方法sendDataToClient:

	def sendDataToClient(self, text):
	    sendData = ''
	    sendData = struct.pack('!B', 0x81)
	
	    length = len(text)
	    if length <= 125:
	        sendData += struct.pack('!B', length)
	    elif length <= 65536:
	        sendData += struct.pack('!B', 126)
	        sendData += struct.pack('!H', length)
	    elif length == 127:
	        sendData += struct.pack('!B', 127)
	        sendData += struct.pack('!Q', length)
	
	    sendData += struct.pack('!%ds' % (length), text.encode())
	    dataSize = self.con.send(sendData)

  下面我给出一个完整的代码,它是一个js前端与python后台交流数据的demo:
  
  【前端Javascript】

	<!DOCTYPE html>
	</html>
	<head>
	    <meta charset="utf-8">
	</head>
	<body>
	<h3>WebSocketTest</h3>
	<div id="login">
	    <div>
	        <input id="serverIP" type="text" placeholder="服务器IP" value="127.0.0.1" autofocus="autofocus" />
	        <input id="serverPort" type="text" placeholder="服务器端口" value="9999" />
	        <input id="btnConnect" type="button" value="连接" onclick="connect()"/>
	    </div>
	    <div>
	        <input id="sendText" type="text" placeholder="发送文本" value="TC| I'm Websocket client!" />
	        <input id="btnSend" type="button" value="发送" onclick="send()" disabled=true/>
	    </div>
	    <div>
	        <div>
	            来自服务端的消息
	        </div>
	        <textarea id="txtContent" cols="50" rows="10" readonly="readonly"></textarea>
	    </div>
	</div>
	</body>
	<script>
	    function sleep(ms) {
	            return new Promise(resolve => setTimeout(resolve, ms));
	        }
	
	    var socket;
	    var current=0;
	    var total;
	    var beforetime;
	
	    function connect() {
	        var host = "ws://" + $("serverIP").value + ":" + $("serverPort").value + "/";
	        document.getElementById("btnConnect").value = "连接中";
	        document.getElementById("btnConnect").disabled = true;
	        socket = new WebSocket(host);
	        try {
	
	            socket.onopen = function (msg) {
	                document.getElementById("btnConnect").value = "连接成功";
	                document.getElementById("btnSend").disabled = "";
	                //alert("连接成功!");
	            };
	
	            socket.onmessage = function (msg) {
	                if (typeof msg.data == "string") {
	                    displayContent(msg.data);
	                    if(msg.data=="Receive:100%"){
	                        current=0;
	                        total=0;
	                    }
	                    else if(msg.data.substr(0,7)=="Receive"){
	                        var str1=msg.data.split(':')[1];
	                        var str2=str1.split('/')[0];
	                        picsend(parseInt(str2))
	                    }
	                }
	                else {
	                    alert("非文本消息");
	                }
	            };
	
	            socket.onerror = function (error) { alert("Error:"+ error.name); };
	
	            socket.onclose = function (msg) {
	                document.getElementById("btnConnect").value = "连接";
	                document.getElementById("btnConnect").disabled = "";
	                document.getElementById("btnSend").disabled = true;//alert("连接关闭!");
	                 };
	        }
	        catch (ex) {
	            log(ex);
	        }
	    }
	
	    async function send() {
	        var str = document.getElementById("sendText").value;
	        socket.send(str);
	    }
	
	    async function picsend(pos){
	        beforetime=new Date().getTime();
	        current=pos;
	        socket.close();
	        connect();
	        while(document.getElementById("btnConnect").value != "连接成功") {await sleep(200);}
	        var str = document.getElementById("sendText").value;
	        socket.send(str.substring(pos));
	    }
	
	    window.onbeforeunload = function () {
	        try {
	            socket.close();
	            socket = null;
	        }
	        catch (ex) {
	        }
	    };
	
	    function $(id) { return document.getElementById(id); }
	
	    Date.prototype.Format = function (fmt) { //author: meizz
	        var o = {
	            "M+": this.getMonth() + 1, //月份
	            "d+": this.getDate(), //日
	            "h+": this.getHours(), //小时
	            "m+": this.getMinutes(), //分
	            "s+": this.getSeconds(), //秒
	            "q+": Math.floor((this.getMonth() + 3) / 3), //季度
	            "S": this.getMilliseconds() //毫秒
	        };
	        if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
	        for (var k in o)
	            if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
	        return fmt;
	    }
	
	    function displayContent(msg) {
	        $("txtContent").value += "\r\n" +new Date().Format("yyyy/MM/dd hh:mm:ss")+ ":  " + msg;
	        $("txtContent").scrollTop = $("txtContent").scrollHeight;
	    }
	    function onkey(event) { if (event.keyCode == 13) { send(); } }
	</script>
	</html>

  【后台Python】:

	from threading import Thread
	import struct
	import time
	import hashlib
	import base64
	import socket
	import time
	import types
	import multiprocessing
	import os
	mode = "initialize"
	pic_size = 0
	pic_receive = 0
	pic = ""
	pic_repeat = []
	
	class returnCrossDomain(Thread):
	    def __init__(self, connection):
	        Thread.__init__(self)
	        self.con = connection
	        self.isHandleShake = False
	
	    def run(self):
	        global mode
	        global pic_size
	        global pic_receive
	        global pic
	        global pic_repeat
	        while True:
	            if not self.isHandleShake:
	                # 开始握手阶段
	                header = self.analyzeReq()
	                secKey = header['Sec-WebSocket-Key'];
	
	                acceptKey = self.generateAcceptKey(secKey)
	
	                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\r\n" % (acceptKey.decode('utf-8'))
	                self.con.send(response.encode())
	                self.isHandleShake = True
	                if(mode=="initialize"):
	                    mode = "get_order"
	                print('response:\r\n' + response)
	                # 握手阶段结束
	
	                #读取命令阶段
	            elif mode == "get_order":
	                opcode = self.getOpcode()
	                if opcode == 8:
	                    self.con.close()
	                self.getDataLength()
	                clientData = self.readClientData()
	                print('客户端数据:' + str(clientData))
	                # 处理数据
	                ans = self.answer(clientData)
	                self.sendDataToClient(ans)
	                if (ans != "Unresolvable Command!" and ans != "hello world"):
	                    pic_size = int(clientData[3:])
	                    pic_receive = 0
	                    pic = ""
	                    pic_repeat=[]
	                    print("需要接收的数据大小:", pic_size)
	                    mode = "get_pic"
	
	                #读取图片阶段
	            elif mode == "get_pic":
	                opcode = self.getOpcode()
	                if opcode == 8:
	                    self.con.close()
	                self.getDataLength()
	                clientData = self.readClientData()
	                print('客户端数据:' + str(clientData))
	                pic_receive += len(clientData)
	                pic += clientData
	                if pic_receive < pic_size:
	                    self.sendDataToClient("Receive:"+str(pic_receive)+"/"+str(pic_size))
	                    print("图片接收情况:",pic_receive,"/",pic_size)
	                    #print("当前图片数据:",pic)
	                else:
	                    print("完整图片数据:",pic)
	                    self.sendDataToClient("Receive:100%")
	                    result = self.process(pic)
	                    self.sendDataToClient(result)
	                    pic_size = 0
	                    pic_receive = 0
	                    pic = ""
	                    pic_repeat=[]
	                    mode = "get_order"
	                # 处理数据
	
	                # self.sendDataToClient(clientData)
	
	    def legal(self, string):  # python总会胡乱接收一些数据。。只好过滤掉
	        if len(string) == 0:
	            return 0
	        elif len(string) <= 100:
	            if self.loc(string) != len(string):
	                return 0
	            else:
	                if mode != "get_pic":
	                    return 1
	                elif len(string) + pic_receive == pic_size:
	                    return 1
	                else:
	                    return 0
	        else:
	            if self.loc(string) > 100:
	                if mode != "get_pic":
	                    return 1
	                elif string[0:100] not in pic_repeat:
	                    pic_repeat.append(string[0:100])
	                    return 1
	                else:
	                    return -1  # 收到重复数据,需要重定位
	            else:
	                return 0
	
	    def loc(self, string):
	        i = 0
	        while(i<len(string) and self.rightbase64(string[i])):
	            i = i+1
	        return i
	
	    def rightbase64(self, ch):
	        if (ch >= "a") and (ch <= "z"):
	            return 1
	        elif (ch >= "A") and (ch <= "Z"):
	            return 1
	        elif (ch >= "0") and (ch <= "9"):
	            return 1
	        elif ch == '+' or ch == '/' or ch == '|' or ch == '=' or ch == ' ' or ch == "'" or ch == '!' or ch == ':':
	            return 1
	        else:
	            return 0
	
	    def analyzeReq(self):
	        reqData = self.con.recv(1024).decode()
	        reqList = reqData.split('\r\n')
	        headers = {}
	        for reqItem in reqList:
	            if ': ' in reqItem:
	                unit = reqItem.split(': ')
	                headers[unit[0]] = unit[1]
	        return headers
	
	    def generateAcceptKey(self, secKey):
	        sha1 = hashlib.sha1()
	        sha1.update((secKey + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11').encode())
	        sha1_result = sha1.digest()
	        acceptKey = base64.b64encode(sha1_result)
	        return acceptKey
	
	    def getOpcode(self):
	        first8Bit = self.con.recv(1)
	        first8Bit = struct.unpack('B', first8Bit)[0]
	        opcode = first8Bit & 0b00001111
	        return opcode
	
	    def getDataLength(self):
	        second8Bit = self.con.recv(1)
	        second8Bit = struct.unpack('B', second8Bit)[0]
	        masking = second8Bit >> 7
	        dataLength = second8Bit & 0b01111111
	        #print("dataLength:",dataLength)
	        if dataLength <= 125:
	            payDataLength = dataLength
	        elif dataLength == 126:
	            payDataLength = struct.unpack('H', self.con.recv(2))[0]
	        elif dataLength == 127:
	            payDataLength = struct.unpack('Q', self.con.recv(8))[0]
	        self.masking = masking
	        self.payDataLength = payDataLength
	        #print("payDataLength:", payDataLength)
	
	
	
	    def readClientData(self):
	
	        if self.masking == 1:
	            maskingKey = self.con.recv(4)
	        data = self.con.recv(self.payDataLength)
	
	        if self.masking == 1:
	            i = 0
	            trueData = ''
	            for d in data:
	                trueData += chr(d ^ maskingKey[i % 4])
	                i += 1
	            return trueData
	        else:
	            return data
	
	    def sendDataToClient(self, text):
	        sendData = ''
	        sendData = struct.pack('!B', 0x81)
	
	        length = len(text)
	        if length <= 125:
	            sendData += struct.pack('!B', length)
	        elif length <= 65536:
	            sendData += struct.pack('!B', 126)
	            sendData += struct.pack('!H', length)
	        elif length == 127:
	            sendData += struct.pack('!B', 127)
	            sendData += struct.pack('!Q', length)
	
	        sendData += struct.pack('!%ds' % (length), text.encode())
	        dataSize = self.con.send(sendData)
	
	    def answer(self,data):
	        if(data[0:3]=="TC|"):
	            return "hello world"
	        elif(data[0:3]=="GS|"):
	            return "Gaosi Deblur Survice"
	        elif (data[0:3] == "DT|"):
	            return "DongTai Deblur Survice"
	        else:
	            return "Unresolvable Command!"
	
	    def padding(self,data):
	        missing_padding = 4 - len(data) % 4
	        if missing_padding:
	            data += '='*missing_padding
	        return data
	
	    def process(self,pic):
	
	        #此处是图片处理阶段
	
	        return pic
	
	def main():
	    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
	    sock.bind(('127.0.0.1', 9999))
	    sock.listen(5)
	    while True:
	        try:
	            connection, address = sock.accept()
	            returnCrossDomain(connection).start()
	        except:
	            time.sleep(1)
	
	if __name__ == "__main__":
	    main()

  【运行示例】
  先运行python代码,然后在浏览器上打开javascript(前端就是一个html),然后就能实现如下的互动:
  浏览器上:
如何实现JS前端与Python后台的结合_第2张图片
  Python后台:
如何实现JS前端与Python后台的结合_第3张图片

  那上述代码其实只是一个最初用来测试的demo,我最终的网站代码与这有许多不同,不过核心的网络传输方法就是上述代码内容了。

你可能感兴趣的:(JS前端,Python后台)