2012-10-14 9 views
7

Python3にバックアップされたアプリケーションのWebフロントエンドを作成しようとしています。このアプリケーションには双方向のストリーミングが必要です。これはウェブソケットを調べる良い機会のように思えます。Python 3でWebsocketが実装されている

私が最初に気に入ったのは、既に存在するものを使用することでした。mod-pywebsocketのサンプルアプリケーションは価値があることが分かりました。残念ながら、彼らのAPIは拡張に役立つようには見えず、Python2です。

多くの人がwebsocketプロトコルの以前のバージョン用に独自のwebsocketサーバーを作成していますが、ほとんどの場合、セキュリティキーのハッシュは実装されていません。

(MOD-pywebsocketで動作します)は、この基本的なテスト・ページを使用して
#!/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() 

<!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> 

私はそれを自分自身を刺しを取ることにしたと、次のを思い付いたRFC 6455を読む

これは、データを受信し、それを正しくデマスクするが、私は送信経路を動作させることができない。

ソケットに「ハロー」を書き込むための試験として、上記プログラムは、のようにソケットに書き込まれるバイト数計算:

section 5.7 RFCの中で与えられた六角値と一致
['0x81', '0x5', '0x48', '0x65', '0x6c', '0x6c', '0x6f'] 

を。残念ながら、フレームはChromeのデベロッパーツールには表示されません。

私が迷っているものは何ですか?または、現在動作しているPython3 WebSocketの例ですか?

+0

竜巻はWebソケットとPython 3の両方をサポートしています。http://www.tornadoweb.org/documentation/websocket.html –

+0

Thomasに感謝します。私はスタンドアロンの実装を最初にしたいと思っています - これは私にとって問題を解決することと同じくらいプロトコルを理解することです。 [竜巻のソースコード](https://github.com/facebook/tornado/blob/master/tornado/websocket.py)を見てみましょう**私は1つのヘッダ** Sec-WebSocket-Protocol **がサーバーをクライアントに接続することができますが、[spec](http://tools.ietf.org/html/rfc6455#section-4.2.2)には、これはオプションです。 – Hardbyte

+0

クライアントがサブプロトコルを要求した場合、サーバーはそれをエコーすることが予想されます(常にサブプロトコルをサポートしていると仮定します)。そうしないとハンドシェイクエラーが発生するので、おそらくメッセージの送信には関係しません。 – simonc

答えて

7

私はライオンのSafari 6.0.1からあなたのpythonコードに話をしようとしたとき、私はJavaScriptコンソールで

Unexpected LF in Value at ... 

を取得します。私はまた、IndexError例外をPythonコードから取得します。

LionのChromeバージョン24.0.1290.1デベロッパーからPythonコードに連絡すると、Javascriptエラーは発生しません。あなたのJavaScriptでは、onopen()onclose()メソッドが呼び出されますが、onmessage()は呼び出されません。 Pythonコードは例外をスローせず、メッセージを受信して​​いるように見えます。レスポンス、つまりあなたの見ている動作とまったく同じです。

Safariは、私はそれを削除しようとしたあなたのヘッダーで、末尾のLFを好きではなかったので、私はこの変更を行うとき、すなわち

header = '''HTTP/1.1 101 Switching Protocols\r 
Upgrade: websocket\r 
Connection: Upgrade\r 
Sec-WebSocket-Accept: {}\r 
'''.format(response_string) 

Chromeは

got: Hello 

つまり、あなたの応答メッセージを見ることができjavascriptコンソールに表示されます。

Safariはまだ動作しません。今私はメッセージを送信しようとすると別の問題が発生します。

websocket.html:36 INVALID_STATE_ERR: DOM Exception 11: An attempt was made to use an object that is not, or is no longer, usable. 

のjavascriptのWebSocketイベントハンドラのなしこれまでに火災と私はまだのpythonからIndexError例外を見ています。結論として、ヘッダレスポンスに余分なLFがあるため、PythonコードがChromeで動作しませんでした。 Chromeで動作するコードがSafariで動作しないため、まだ何かが続いています。

更新

私は、根本的な問題を働いたし、今のSafariとChromeでの作業例を持っていました。

base64.encodestring()は、常にそれに戻る末尾に\nを追加します。これは、Safariが不満を持っていたLFのソースです。

calculate_websocket_hashという戻り値を呼び出し、オリジナルのヘッダーテンプレートを使用すると、SafariとChromeで正常に動作します。

+0

その余分なCRLFを削除すると、FirefoxとChromeでうまく動作します。 – Hardbyte

関連する問題