Próba utworzenia strony WWW dla aplikacji wspieranej przez Python3. Aplikacja będzie wymagać dwukierunkowego przesyłania strumieniowego, co brzmi jak dobra okazja do zaglądania do stron internetowych.Implementacja Websocket w Pythonie 3
Moja pierwsza skłonność polegała na używaniu czegoś już istniejącego, a przykładowe aplikacje z mod-pywebsocket okazały się cenne. Niestety ich interfejs API nie wydaje się łatwo poddać rozszerzeniu, a jest to Python2.
Rozglądając się po blogosferze wiele osób napisało własny serwer websocket dla wcześniejszych wersji protokołu websocket, większość nie implementuje skrótu klucza bezpieczeństwa, więc nie działa.
Reading RFC 6455 postanowiłem wziąć ukłucie w nim siebie i wyszedł z następujących czynności:
#!/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()
Stosując tę podstawową stronę testową (która współpracuje z MOD-pywebsocket):
<!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>
Odbiera dane i demaskuje je poprawnie, ale nie mogę uzyskać ścieżki transmisji do działania.
Jako test napisać „cześć” do gniazda, program powyżej oblicza bajtów do zapisu do gniazda jak:
['0x81', '0x5', '0x48', '0x65', '0x6c', '0x6c', '0x6f']
które odpowiadają wartości podanych w section 5.7 sześciokątne z RFC. Niestety ramka nigdy nie pojawia się w Narzędziach dla programistów Chrome.
Masz pomysł, czego mi brakuje? Lub aktualnie działający przykład websocket Python3?
Tornado obsługuje zarówno websockets, jak i Python 3. http://www.tornadoweb.org/documentation/websocket.html –
Dzięki Thomas. Najpierw chciałbym jednak mieć samodzielną implementację - chodzi o zrozumienie protokołu jako rozwiązania problemu. Spojrzenie na [kod źródłowy tornada] (https://github.com/facebook/tornado/blob/master/tornado/websocket.py) Widzę jeden nagłówek ** Sec-WebSocket-Protocol ** wysyłany z serwer do klienta, ale [spec] (http://tools.ietf.org/html/rfc6455#section-4.2.2) mówi, że jest opcjonalne. – Hardbyte
Jeśli klient zażąda pod-protokołu, serwer powinien go wyświetlić (zawsze zakładając, że obsługuje pod-protokół). Niewykonanie tej czynności może spowodować błąd uzgadniania, więc prawdopodobnie nie jest to związane z problemami z wysyłaniem wiadomości. – simonc