Der Versuch, ein Web-Frontend für eine Python3 gesicherte Anwendung zu erstellen. Die Anwendung erfordert eine bidirektionale Streaming, die wie eine gute Gelegenheit, klang in WebSockets zu suchen.Websocket Implementierung in Python 3
Meine erste Neigung war es, etwas bereits vorhandenes zu verwenden, und die Beispielanwendungen von mod-pywebsocket haben sich als wertvoll erwiesen. Leider ihre API scheint nicht leicht, sich zu Erweiterung zu verleihen, und es ist Python2.
rund um die Blogosphäre Blick viele Menschen ihre eigenen websocket Server für frühere Versionen des WebSocket-Protokoll geschrieben, die meisten nicht implementieren die Sicherheitsschlüssel-Hash so dont‘Arbeit.
Lese RFC 6455 entschied ich mich an ihn einen Stich zu nehmen und kam mit dem folgenden:
#!/usr/bin/env python3
"""
A partial implementation of RFC 6455
http://tools.ietf.org/pdf/rfc6455.pdf
Brian Thorne 2012
"""
import socket
import threading
import time
import base64
import hashlib
def calculate_websocket_hash(key):
magic_websocket_string = b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
result_string = key + magic_websocket_string
sha1_digest = hashlib.sha1(result_string).digest()
response_data = base64.encodestring(sha1_digest)
response_string = response_data.decode('utf8')
return response_string
def is_bit_set(int_type, offset):
mask = 1 << offset
return not 0 == (int_type & mask)
def set_bit(int_type, offset):
return int_type | (1 << offset)
def bytes_to_int(data):
# note big-endian is the standard network byte order
return int.from_bytes(data, byteorder='big')
def pack(data):
"""pack bytes for sending to client"""
frame_head = bytearray(2)
# set final fragment
frame_head[0] = set_bit(frame_head[0], 7)
# set opcode 1 = text
frame_head[0] = set_bit(frame_head[0], 0)
# payload length
assert len(data) < 126, "haven't implemented that yet"
frame_head[1] = len(data)
# add data
frame = frame_head + data.encode('utf-8')
print(list(hex(b) for b in frame))
return frame
def receive(s):
"""receive data from client"""
# read the first two bytes
frame_head = s.recv(2)
# very first bit indicates if this is the final fragment
print("final fragment: ", is_bit_set(frame_head[0], 7))
# bits 4-7 are the opcode (0x01 -> text)
print("opcode: ", frame_head[0] & 0x0f)
# mask bit, from client will ALWAYS be 1
assert is_bit_set(frame_head[1], 7)
# length of payload
# 7 bits, or 7 bits + 16 bits, or 7 bits + 64 bits
payload_length = frame_head[1] & 0x7F
if payload_length == 126:
raw = s.recv(2)
payload_length = bytes_to_int(raw)
elif payload_length == 127:
raw = s.recv(8)
payload_length = bytes_to_int(raw)
print('Payload is {} bytes'.format(payload_length))
"""masking key
All frames sent from the client to the server are masked by a
32-bit nounce value that is contained within the frame
"""
masking_key = s.recv(4)
print("mask: ", masking_key, bytes_to_int(masking_key))
# finally get the payload data:
masked_data_in = s.recv(payload_length)
data = bytearray(payload_length)
# The ith byte is the XOR of byte i of the data with
# masking_key[i % 4]
for i, b in enumerate(masked_data_in):
data[i] = b^masking_key[i%4]
return data
def handle(s):
client_request = s.recv(4096)
# get to the key
for line in client_request.splitlines():
if b'Sec-WebSocket-Key:' in line:
key = line.split(b': ')[1]
break
response_string = calculate_websocket_hash(key)
header = '''HTTP/1.1 101 Switching Protocols\r
Upgrade: websocket\r
Connection: Upgrade\r
Sec-WebSocket-Accept: {}\r
\r
'''.format(response_string)
s.send(header.encode())
# this works
print(receive(s))
# this doesn't
s.send(pack('Hello'))
s.close()
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(('', 9876))
s.listen(1)
while True:
t,_ = s.accept()
threading.Thread(target=handle, args = (t,)).start()
diese grundlegenden Testseite verwenden (die mit mod-pywebsocket funktioniert):
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Web Socket Example</title>
<meta charset="UTF-8">
</head>
<body>
<div id="serveroutput"></div>
<form id="form">
<input type="text" value="Hello World!" id="msg" />
<input type="submit" value="Send" onclick="sendMsg()" />
</form>
<script>
var form = document.getElementById('form');
var msg = document.getElementById('msg');
var output = document.getElementById('serveroutput');
var s = new WebSocket("ws://"+window.location.hostname+":9876");
s.onopen = function(e) {
console.log("opened");
out('Connected.');
}
s.onclose = function(e) {
console.log("closed");
out('Connection closed.');
}
s.onmessage = function(e) {
console.log("got: " + e.data);
out(e.data);
}
form.onsubmit = function(e) {
e.preventDefault();
msg.value = '';
window.scrollTop = window.scrollHeight;
}
function sendMsg() {
s.send(msg.value);
}
function out(text) {
var el = document.createElement('p');
el.innerHTML = text;
output.appendChild(el);
}
msg.focus();
</script>
</body>
</html>
Dies empfängt Daten und demaskiert es korrekt, aber ich kann den Sendepfad nicht zum Arbeiten bringen.
Als Test „Hallo“ an die Steckdose zu schreiben, berechnet das Programm über das Bytes in Bezug auf die Buchse geschrieben werden:
['0x81', '0x5', '0x48', '0x65', '0x6c', '0x6c', '0x6f']
den in section 5.7 des RFC den Hex-Wert angegeben entsprechen. Leider wird der Rahmen in den Chrome-Entwicklertools nicht angezeigt.
Jede Idee, was ich vermisst habe? Oder ein aktuell funktionierendes Python3-Websocket-Beispiel? Ich bekomme
Unexpected LF in Value at ...
in der Javascript-Konsole
Tornado unterstützt beide Websockets und Python 3. http://www.tornadoweb.org/documentation/websocket.html –
Danke Thomas. Ich hätte gerne zuerst eine eigenständige Implementierung - es geht darum, das Protokoll zu verstehen und ein Problem für mich zu lösen. Betrachtet man den [Tornado-Quellcode] (https://github.com/facebook/tornado/blob/master/tornado/websocket.py) sehe ich einen Header ** Sec-WebSocket-Protocol **, der von der Server zu dem Client, aber die [spec] (http://tools.ietf.org/html/rfc6455#section-4.2.2) sagt, dass dies optional ist. – Hardbyte
Wenn ein Client ein Unterprotokoll anfordert, wird erwartet, dass der Server es zurückmeldet (immer vorausgesetzt, dass es das Unterprotokoll unterstützt). Andernfalls könnte ein Handshake-Fehler auftreten, der wahrscheinlich nicht mit den Problemen beim Senden von Nachrichten zusammenhängt. – simonc