- support ABNF frame data transfer.
- not workable, yet.
This commit is contained in:
@@ -140,11 +140,11 @@ class WebSocketTest(unittest.TestCase):
|
|||||||
sock = ws.WebSocket()
|
sock = ws.WebSocket()
|
||||||
s = sock.io_sock = sock.sock = HeaderSockMock("data/header01.txt")
|
s = sock.io_sock = sock.sock = HeaderSockMock("data/header01.txt")
|
||||||
sock.send("Hello")
|
sock.send("Hello")
|
||||||
self.assertEquals(s.sent[0], "\x00Hello\xff")
|
#self.assertEquals(s.sent[0], "\x00Hello\xff")
|
||||||
sock.send("こんにちは")
|
sock.send("こんにちは")
|
||||||
self.assertEquals(s.sent[1], "\x00こんにちは\xff")
|
#self.assertEquals(s.sent[1], "\x00こんにちは\xff")
|
||||||
sock.send(u"こんにちは")
|
sock.send(u"こんにちは")
|
||||||
self.assertEquals(s.sent[1], "\x00こんにちは\xff")
|
#self.assertEquals(s.sent[1], "\x00こんにちは\xff")
|
||||||
|
|
||||||
def testRecv(self):
|
def testRecv(self):
|
||||||
sock = ws.WebSocket()
|
sock = ws.WebSocket()
|
||||||
@@ -163,7 +163,7 @@ class WebSocketTest(unittest.TestCase):
|
|||||||
self.assertEquals(data, "a" * 255)
|
self.assertEquals(data, "a" * 255)
|
||||||
|
|
||||||
def testWebSocket(self):
|
def testWebSocket(self):
|
||||||
s = ws.create_connection("ws://echo.websocket.org/")
|
s = ws.create_connection("ws://echo.websocket.org/") #ws://localhost:8080/echo")
|
||||||
self.assertNotEquals(s, None)
|
self.assertNotEquals(s, None)
|
||||||
s.send("Hello, World")
|
s.send("Hello, World")
|
||||||
result = s.recv()
|
result = s.recv()
|
||||||
|
147
websocket.py
147
websocket.py
@@ -22,6 +22,7 @@ Copyright (C) 2010 Hiroki Ohtani(liris)
|
|||||||
|
|
||||||
import socket
|
import socket
|
||||||
from urlparse import urlparse
|
from urlparse import urlparse
|
||||||
|
import struct
|
||||||
import uuid
|
import uuid
|
||||||
import sha
|
import sha
|
||||||
import base64
|
import base64
|
||||||
@@ -59,7 +60,6 @@ def setdefaulttimeout(timeout):
|
|||||||
global default_timeout
|
global default_timeout
|
||||||
default_timeout = timeout
|
default_timeout = timeout
|
||||||
|
|
||||||
|
|
||||||
def getdefaulttimeout():
|
def getdefaulttimeout():
|
||||||
"""
|
"""
|
||||||
Return the global timeout setting to connect.
|
Return the global timeout setting to connect.
|
||||||
@@ -148,6 +148,76 @@ class _SSLSocketWrapper(object):
|
|||||||
def send(self, payload):
|
def send(self, payload):
|
||||||
return self.ssl.write(payload)
|
return self.ssl.write(payload)
|
||||||
|
|
||||||
|
BOOL_VALUES = (0, 1)
|
||||||
|
def is_bool(*values):
|
||||||
|
for v in values:
|
||||||
|
if v not in BOOL_VALUES:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
class ABNF(object):
|
||||||
|
"""
|
||||||
|
ABNF frame class.
|
||||||
|
see http://tools.ietf.org/html/rfc5234
|
||||||
|
and http://tools.ietf.org/html/rfc6455#section-5.2
|
||||||
|
"""
|
||||||
|
OPCODE_TEXT = 0x1
|
||||||
|
OPCODE_BINARY = 0x2
|
||||||
|
OPCODE_CLOSE = 0x8
|
||||||
|
OPCODE_PING = 0x9
|
||||||
|
OPCODE_PONG = 0xa
|
||||||
|
OPTCODES = (OPCODE_TEXT, OPCODE_BINARY, OPCODE_CLOSE,
|
||||||
|
OPCODE_PING, OPCODE_PONG)
|
||||||
|
|
||||||
|
LENGTH_7 = 0x7d
|
||||||
|
LENGTH_16 = 1 << 16
|
||||||
|
LENGTH_63 = 1 << 63
|
||||||
|
|
||||||
|
def __init__(self, fin = 0, rsv1 = 0, rsv2 = 0, rsv3 = 0,
|
||||||
|
opcode = OPCODE_TEXT, mask = 0, data = ""):
|
||||||
|
self.fin = fin
|
||||||
|
self.rsv1 = rsv1
|
||||||
|
self.rsv2 = rsv2
|
||||||
|
self.rsv3 = rsv3
|
||||||
|
self.opcode = opcode
|
||||||
|
self.mask = mask
|
||||||
|
self.data = data
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create_frame(data, opcode):
|
||||||
|
if opcode == ABNF.OPCODE_TEXT and isinstance(data, unicode):
|
||||||
|
data = data.encode("utf-8")
|
||||||
|
return ABNF(1, 0, 0, 0, opcode, 0, data)
|
||||||
|
|
||||||
|
def format(self):
|
||||||
|
if not is_bool(self.fin, self.rsv1, self.rsv2, self.rsv3):
|
||||||
|
raise ValueError("not 0 or 1")
|
||||||
|
if self.opcode not in ABNF.OPTCODES:
|
||||||
|
raise ValueError("Invalid OPCODE")
|
||||||
|
length = len(self.data)
|
||||||
|
if length >= ABNF.LENGTH_63:
|
||||||
|
raise ValueError("data is too long")
|
||||||
|
|
||||||
|
frame_header = chr(self.fin << 7
|
||||||
|
| self.rsv1 << 6 | self.rsv2 << 5 | self.rsv3 << 4
|
||||||
|
| self.opcode)
|
||||||
|
if length < ABNF.LENGTH_7:
|
||||||
|
frame_header += chr(self.mask << 7 | length)
|
||||||
|
elif length <= ABNF.LENGTH_16:
|
||||||
|
frame_header += chr(self.mask << 7 | 0x7e)
|
||||||
|
frame_header += struct.pack("!H", length)
|
||||||
|
else:
|
||||||
|
frame_header += chr(self.mask << 7 | 0x7f)
|
||||||
|
frame_header += struct.pack("!Q", length)
|
||||||
|
|
||||||
|
if not self.mask:
|
||||||
|
return frame_header + self.data
|
||||||
|
|
||||||
|
raise NotImplementedError("masked format is not implemented")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class WebSocket(object):
|
class WebSocket(object):
|
||||||
"""
|
"""
|
||||||
Low level WebSocket interface.
|
Low level WebSocket interface.
|
||||||
@@ -291,13 +361,13 @@ class WebSocket(object):
|
|||||||
|
|
||||||
return status, headers
|
return status, headers
|
||||||
|
|
||||||
def send(self, payload):
|
def send(self, payload, binary = False):
|
||||||
"""
|
"""
|
||||||
Send the data as string. payload must be utf-8 string or unicoce.
|
Send the data as string. payload must be utf-8 string or unicoce.
|
||||||
"""
|
"""
|
||||||
if isinstance(payload, unicode):
|
frame = ABNF.create_frame(payload, ABNF.OPCODE_TEXT)
|
||||||
payload = payload.encode("utf-8")
|
data = frame.format()
|
||||||
data = "".join(["\x00", payload, "\xff"])
|
print repr(data)
|
||||||
self.io_sock.send(data)
|
self.io_sock.send(data)
|
||||||
if traceEnabled:
|
if traceEnabled:
|
||||||
logger.debug("send: " + repr(data))
|
logger.debug("send: " + repr(data))
|
||||||
@@ -306,40 +376,37 @@ class WebSocket(object):
|
|||||||
"""
|
"""
|
||||||
Receive utf-8 string data from the server.
|
Receive utf-8 string data from the server.
|
||||||
"""
|
"""
|
||||||
b = self._recv(1)
|
frame = self.read_frame()
|
||||||
if traceEnabled:
|
return frame.data
|
||||||
logger.debug("recv frame: " + repr(b))
|
|
||||||
frame_type = ord(b)
|
|
||||||
if frame_type == 0x00:
|
|
||||||
bytes = []
|
|
||||||
while True:
|
|
||||||
b = self._recv(1)
|
|
||||||
if b == "\xff":
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
bytes.append(b)
|
|
||||||
return "".join(bytes)
|
|
||||||
elif 0x80 < frame_type < 0xff:
|
|
||||||
# which frame type is valid?
|
|
||||||
length = self._read_length()
|
|
||||||
bytes = self._recv_strict(length)
|
|
||||||
return bytes
|
|
||||||
elif frame_type == 0xff:
|
|
||||||
self._recv(1)
|
|
||||||
self._closeInternal()
|
|
||||||
return None
|
|
||||||
else:
|
|
||||||
raise WebSocketException("Invalid frame type")
|
|
||||||
|
|
||||||
def _read_length(self):
|
def read_frame(self):
|
||||||
length = 0
|
header_bytes = self._recv(2)
|
||||||
while True:
|
b1 = ord(header_bytes[0])
|
||||||
b = ord(self._recv(1))
|
fin = b1 >> 7 & 1
|
||||||
length = length * (1 << 7) + (b & 0x7f)
|
rsv1 = b1 >> 6 & 1
|
||||||
if b < 0x80:
|
rsv2 = b1 >> 5 & 1
|
||||||
break
|
rsv3 = b1 >> 4 & 1
|
||||||
|
opcode = b1 & 0xf
|
||||||
|
|
||||||
|
b2 = ord(header_bytes[1])
|
||||||
|
mask = b2 >> 7 & 1
|
||||||
|
length = b2 & 0x7f
|
||||||
|
|
||||||
|
if length == 0x7e:
|
||||||
|
l = self._recv(2)
|
||||||
|
length = struct.unpack("!H", l)[0]
|
||||||
|
elif length == 0x7f:
|
||||||
|
l = self._recv(8)
|
||||||
|
length = struct.unpack("!Q", l)[0]
|
||||||
|
|
||||||
|
data = self._recv(length)
|
||||||
|
|
||||||
|
if mask:
|
||||||
|
raise NotImplementedError("masked data transfer is not implemented")
|
||||||
|
|
||||||
|
frame = ABNF(fin, rsv1, rsv2, rsv3, opcode, mask, data)
|
||||||
|
return frame
|
||||||
|
|
||||||
return length
|
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
"""
|
"""
|
||||||
@@ -369,8 +436,6 @@ class WebSocket(object):
|
|||||||
|
|
||||||
def _recv(self, bufsize):
|
def _recv(self, bufsize):
|
||||||
bytes = self.io_sock.recv(bufsize)
|
bytes = self.io_sock.recv(bufsize)
|
||||||
if not bytes:
|
|
||||||
raise ConnectionClosedException()
|
|
||||||
return bytes
|
return bytes
|
||||||
|
|
||||||
def _recv_strict(self, bufsize):
|
def _recv_strict(self, bufsize):
|
||||||
@@ -391,6 +456,10 @@ class WebSocket(object):
|
|||||||
break
|
break
|
||||||
return "".join(line)
|
return "".join(line)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class WebSocketApp(object):
|
class WebSocketApp(object):
|
||||||
"""
|
"""
|
||||||
Higher level of APIs are provided.
|
Higher level of APIs are provided.
|
||||||
|
Reference in New Issue
Block a user