Note that there are some explanatory texts on larger screens.

plurals
  1. POWebsocket implementation in Python 3
    primarykey
    data
    text
    <p>Trying to create a web-front end for a Python3 backed application. The application will require bi-directional streaming which sounded like a good opportunity to look into websockets.</p> <p>My first inclination was to use something already existing, and the example applications from <em>mod-pywebsocket</em> have proved valuable. Unfortunately their API doesn't appear to easily lend itself to extension, and it is Python2.</p> <p>Looking around the blogosphere many people have written their own websocket server for earlier versions of the websocket protocol, most don't implement the security key hash so dont' work.</p> <p>Reading <a href="http://tools.ietf.org/html/rfc6455">RFC 6455</a> I decided to take a stab at it myself and came up with the following:</p> <pre><code>#!/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 &lt;&lt; offset return not 0 == (int_type &amp; mask) def set_bit(int_type, offset): return int_type | (1 &lt;&lt; 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) &lt; 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 -&gt; text) print("opcode: ", frame_head[0] &amp; 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] &amp; 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() </code></pre> <p>Using this basic test page (which works with mod-pywebsocket):</p> <pre><code>&lt;!DOCTYPE html&gt; &lt;html xmlns="http://www.w3.org/1999/xhtml"&gt; &lt;head&gt; &lt;title&gt;Web Socket Example&lt;/title&gt; &lt;meta charset="UTF-8"&gt; &lt;/head&gt; &lt;body&gt; &lt;div id="serveroutput"&gt;&lt;/div&gt; &lt;form id="form"&gt; &lt;input type="text" value="Hello World!" id="msg" /&gt; &lt;input type="submit" value="Send" onclick="sendMsg()" /&gt; &lt;/form&gt; &lt;script&gt; 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(); &lt;/script&gt; &lt;/body&gt; &lt;/html&gt; </code></pre> <p>This receives data and demasks it correctly, but I can't get the transmit path to work.</p> <p>As a test to write "Hello" to the socket, the program above calculates the bytes to be written to the socket as:</p> <pre><code>['0x81', '0x5', '0x48', '0x65', '0x6c', '0x6c', '0x6f'] </code></pre> <p>Which match the hex values given in <a href="http://tools.ietf.org/html/rfc6455#section-5.7">section 5.7</a> of the RFC. Unfortunately the frame never shows up in Chrome's Developer Tools.</p> <p>Any idea what I'm missing? Or a currently working Python3 websocket example?</p>
    singulars
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload