2012-10-14 29 views
7

में वेबस्केट कार्यान्वयन एक पायथन 3 समर्थित अनुप्रयोग के लिए वेब-फ्रंट एंड बनाने की कोशिश कर रहा है। आवेदन को द्वि-दिशात्मक स्ट्रीमिंग की आवश्यकता होगी जो वेबसाइकिलों को देखने के लिए एक अच्छा अवसर की तरह लग रहा है।पायथन 3

मेरा पहला झुकाव पहले से मौजूद कुछ का उपयोग करना था, और mod-pywebsocket से उदाहरण अनुप्रयोगों ने मूल्यवान साबित कर दिया है। दुर्भाग्य से उनका एपीआई आसानी से एक्सटेंशन में उधार नहीं देता है, और यह पायथन 2 है।

ब्लॉगोस्फीयर के चारों ओर देखकर कई लोगों ने वेबसाईट प्रोटोकॉल के पुराने संस्करणों के लिए अपना स्वयं का वेबस्केट सर्वर लिखा है, अधिकांश सुरक्षा कुंजी हैश को लागू नहीं करते हैं, इसलिए काम नहीं करते हैं।

RFC 6455 पढ़ना मैं अपने आप को इस पर एक वार लेने का फैसला किया और के साथ आया था निम्नलिखित:

#!/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() 

इस बुनियादी परीक्षण पृष्ठ (जो आधुनिक-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> 

यह डेटा प्राप्त करता है और इसे सही तरीके से डेमस्क करता है, लेकिन मुझे काम करने के लिए प्रेषण पथ नहीं मिल सकता है।

एक परीक्षण "हैलो" सॉकेट से लिखने के लिए के रूप में, कार्यक्रम ऊपर की गणना करता है बाइट के रूप में सॉकेट से लिखे जाने की:

['0x81', '0x5', '0x48', '0x65', '0x6c', '0x6c', '0x6f'] 

कौन सा आरएफसी की section 5.7 में दिए गए स मान से मेल खाते हैं। दुर्भाग्यवश फ्रेम क्रोम के डेवलपर टूल्स में कभी दिखाई नहीं देता है।

कोई विचार जो मुझे याद आ रहा है? या वर्तमान में काम कर रहे Python3 websocket उदाहरण?

+0

टोरनाडो दोनों websockets और पायथन 3 का समर्थन करता है। Http://www.tornadoweb.org/documentation/websocket.html –

+0

धन्यवाद थॉमस। हालांकि मैं पहले एक स्टैंडअलोन कार्यान्वयन करना चाहता हूं - यह मेरे लिए एक समस्या को हल करने के रूप में प्रोटोकॉल को समझने के बारे में बहुत कुछ है। [टर्ननाडो स्रोत कोड] पर एक नज़र डालें (https://github.com/facebook/tornado/blob/master/tornado/websocket.py) मुझे एक हेडर दिखाई देता है ** सेक-वेबस्केट-प्रोटोकॉल ** से भेजा जा रहा है क्लाइंट के लिए सर्वर, लेकिन [spec] (http://tools.ietf.org/html/rfc6455#section-4.2.2) कहता है कि यह वैकल्पिक है। – Hardbyte

+0

यदि कोई ग्राहक उप-प्रोटोकॉल का अनुरोध करता है, तो सर्वर से इसे प्रतिबिंबित करने की उम्मीद है (हमेशा यह मानते हुए कि यह सब-प्रोटोकॉल का समर्थन करता है)। ऐसा करने में विफलता एक हैंडशेक त्रुटि का कारण बनती है, इसलिए यह शायद आपके संदेश भेजने की समस्याओं से संबंधित नहीं है। – simonc

उत्तर

7

जब मैं शेर पर सफारी 6.0.1 से अपने अजगर कोड से बात की कोशिश मैं Javascript कंसोल में

Unexpected LF in Value at ... 

मिलता है। मुझे पाइथन कोड से IndexError अपवाद भी मिलता है।

जब मैं क्रोम संस्करण 24.0.1290.1 ​​से आपके पायथन कोड से बात करता हूं शेर पर देव मुझे कोई जावास्क्रिप्ट त्रुटियां नहीं मिलती हैं। आपके जावास्क्रिप्ट में onopen() और onclose() विधियों को बुलाया जाता है, लेकिन onmessage() नहीं। पाइथन कोड किसी भी अपवाद को फेंक नहीं देता है और ऐसा लगता है कि संदेश प्राप्त हुआ है और इसकी प्रतिक्रिया भेजी गई है, यानी आप वास्तव में व्यवहार देख रहे हैं।

के बाद से सफारी अपने शीर्षक में पीछे वामो पसंद नहीं आया मैं इसे हटाने की कोशिश की, यानी

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

जब मैं इस परिवर्तन क्रोम यानी आपकी प्रतिक्रिया संदेश देखने के लिए सक्षम है

got: Hello 

जावास्क्रिप्ट कंसोल में दिखाता है।

सफारी अभी भी काम नहीं करता है। जब मैं एक संदेश भेजने का प्रयास करता हूं तो अब यह एक अलग मुद्दा उठाता है। जावास्क्रिप्ट WebSocket ईवेंट हैंडलर्स की

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. 

कोई भी कभी आग और मैं अभी भी अजगर से IndexError अपवाद दिखाई दे रही है।

निष्कर्ष में। आपका पाइथन कोड आपके हेडर प्रतिक्रिया में अतिरिक्त एलएफ की वजह से क्रोम के साथ काम नहीं कर रहा था। अभी भी कुछ और चल रहा है क्योंकि क्रोम के साथ काम कोड सफारी के साथ काम नहीं करता है।

अद्यतन

मैं अंतर्निहित मुद्दे पर काम किया है और अब गया है उदाहरण सफारी और क्रोम में काम कर रहा है।

base64.encodestring() हमेशा इसकी वापसी के लिए एक पीछे \n जोड़ता है। यह एलएफ का स्रोत है कि सफारी शिकायत कर रहा था।

के वापसी मूल्य पर .strip() पर कॉल करें और अपने मूल हेडर टेम्पलेट का उपयोग सफारी और क्रोम पर सही ढंग से काम करता है।

+0

बहुत बढ़िया उस अतिरिक्त सीआरएलएफ को हटाकर अब यह फ़ायरफ़ॉक्स और क्रोम के लिए काम करता है। – Hardbyte