Skip to content

Commit b6e665f

Browse files
committed
updated threaded client to match recent refactoring
1 parent 1af4628 commit b6e665f

5 files changed

Lines changed: 90 additions & 210 deletions

File tree

ws4py/client/__init__.py

Lines changed: 51 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,67 @@
22
from base64 import b64encode, b64encode
33
from hashlib import sha1
44
import os
5+
import socket
6+
import ssl
57
import types
68
from urlparse import urlsplit
79
import json
810

911
from ws4py import WS_KEY
10-
from ws4py.streaming import Stream
1112
from ws4py.exc import HandshakeError
13+
from ws4py.websocket import WebSocket, WS_VERSION
1214

1315
__all__ = ['WebSocketBaseClient']
1416

15-
class WebSocketBaseClient(object):
16-
def __init__(self, url, protocols=None, version='13'):
17-
self.stream = Stream(always_mask=True, expect_masking=False)
18-
self.url = url
19-
self.protocols = protocols
20-
self.version = version
17+
class WebSocketBaseClient(WebSocket):
18+
def __init__(self, url, protocols, extensions):
19+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
20+
WebSocket.__init__(self, sock, protocols=protocols, extensions=extensions)
21+
self.stream.always_mask = True
22+
self.stream.expect_masking = False
2123
self.key = b64encode(os.urandom(16))
22-
self.client_terminated = False
23-
self.server_terminated = False
24+
self.url = url
25+
26+
def connect(self):
27+
#self.sock.settimeout(3)
28+
parts = urlsplit(self.url)
29+
host, port = parts.netloc, 80
30+
if ':' in host:
31+
host, port = parts.netloc.split(':')
32+
self.sock.connect((host, int(port)))
33+
34+
if parts.scheme == "wss":
35+
self.sock = ssl.wrap_socket(self.sock)
2436

37+
self.sender(self.handshake_request)
38+
39+
response = ''
40+
while True:
41+
bytes = self.sock.recv(128)
42+
if not bytes:
43+
break
44+
response += bytes
45+
if '\r\n\r\n' in response:
46+
break
47+
48+
if not response:
49+
self.close_connection()
50+
raise HandshakeError("Invalid response")
51+
52+
headers, _, body = response.partition('\r\n\r\n')
53+
response_line, _, headers = headers.partition('\r\n')
54+
55+
self.__buffer = body
56+
57+
try:
58+
self.process_response_line(response_line)
59+
self.protocols, self.extensions = self.process_handshake_header(headers)
60+
except HandshakeError:
61+
self.close_connection()
62+
raise
63+
64+
self.handshake_ok()
65+
2566
@property
2667
def handshake_headers(self):
2768
parts = urlsplit(self.url)
@@ -35,7 +76,7 @@ def handshake_headers(self):
3576
('Upgrade', 'websocket'),
3677
('Sec-WebSocket-Key', self.key),
3778
('Sec-WebSocket-Origin', self.url),
38-
('Sec-WebSocket-Version', self.version)
79+
('Sec-WebSocket-Version', WS_VERSION)
3980
]
4081

4182
if self.protocols:
@@ -89,58 +130,3 @@ def process_handshake_header(self, headers):
89130
extensions = ','.join(value)
90131

91132
return protocols, extensions
92-
93-
def opened(self, protocols, extensions):
94-
pass
95-
96-
def received_message(self, m):
97-
pass
98-
99-
def closed(self, code, reason=None):
100-
pass
101-
102-
@property
103-
def terminated(self):
104-
return self.client_terminated is True and self.server_terminated is True
105-
106-
def close(self, reason='', code=1000):
107-
if not self.client_terminated:
108-
self.client_terminated = True
109-
self.write_to_connection(self.stream.close(code=code, reason=reason).single(mask=True))
110-
111-
def connect(self):
112-
raise NotImplemented()
113-
114-
def write_to_connection(self, bytes):
115-
raise NotImplemented()
116-
117-
def read_from_connection(self, amount):
118-
raise NotImplemented()
119-
120-
def close_connection(self):
121-
raise NotImplemented()
122-
123-
def send(self, payload, binary=False):
124-
message_sender = self.stream.binary_message if binary else self.stream.text_message
125-
126-
if isinstance(payload, basestring):
127-
self.write_to_connection(message_sender(payload).single(mask=True))
128-
129-
elif isinstance(payload, dict):
130-
self.write_to_connection(message_sender(json.dumps(payload)).single(mask=True))
131-
132-
elif type(payload) == types.GeneratorType:
133-
first = True
134-
last = False
135-
bytes = payload.next()
136-
137-
while not last:
138-
try:
139-
peeked_bytes = payload.next()
140-
except StopIteration:
141-
last = True
142-
143-
self.write_to_connection(message_sender(bytes).fragment(first=first, last=last, mask=True))
144-
first = False
145-
bytes = peeked_bytes
146-

ws4py/client/threadedclient.py

Lines changed: 14 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -1,143 +1,23 @@
11
# -*- coding: utf-8 -*-
2-
import os
3-
import ssl
4-
from urlparse import urlsplit
5-
import socket
62
import threading
7-
import traceback
8-
from sys import exc_info
93

104
from ws4py.client import WebSocketBaseClient
11-
from ws4py.exc import HandshakeError
125

136
__all__ = ['WebSocketClient']
147

158
class WebSocketClient(WebSocketBaseClient):
16-
def __init__(self, url, sock=None, protocols=None, version='13'):
17-
WebSocketBaseClient.__init__(self, url, protocols=protocols, version=version)
18-
if not sock:
19-
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
20-
sock.settimeout(3)
21-
self.sock = sock
22-
23-
self.running = True
24-
25-
self._lock = threading.Lock()
26-
self._th = threading.Thread(target=self._receive)
27-
28-
def write_to_connection(self, bytes):
29-
return self.sock.sendall(bytes)
30-
31-
def read_from_connection(self, amount):
32-
return self.sock.recv(amount)
33-
34-
def close_connection(self):
35-
try:
36-
self.sock.shutdown(socket.SHUT_RDWR)
37-
self.sock.close()
38-
except:
39-
pass
40-
41-
def connect(self):
42-
parts = urlsplit(self.url)
43-
host, port = parts.netloc, 80
44-
if ':' in host:
45-
host, port = parts.netloc.split(':')
46-
self.sock.connect((host, int(port)))
47-
48-
if parts.scheme == "wss":
49-
self.sock = ssl.wrap_socket(self.sock)
50-
51-
self.write_to_connection(self.handshake_request)
52-
53-
response = ''
54-
while True:
55-
bytes = self.sock.recv(128)
56-
if not bytes:
57-
break
58-
response += bytes
59-
if '\r\n\r\n' in response:
60-
break
61-
62-
if not response:
63-
self.close_connection()
64-
raise HandshakeError("Invalid response")
65-
66-
headers, _, body = response.partition('\r\n\r\n')
67-
response_line, _, headers = headers.partition('\r\n')
68-
69-
self.__buffer = body
70-
71-
try:
72-
self.process_response_line(response_line)
73-
protocols, extensions = self.process_handshake_header(headers)
74-
except HandshakeError:
75-
self.close_connection()
76-
raise
9+
def __init__(self, url, protocols=None, extensions=None):
10+
WebSocketBaseClient.__init__(self, url, protocols, extensions)
11+
self._th = threading.Thread(target=self.run, name='WebSocketClient')
12+
#self._th.daemon = True
7713

14+
def handshake_ok(self):
7815
self._th.start()
79-
self.opened(protocols, extensions)
80-
81-
def _receive(self):
82-
next_size = 2
83-
try:
84-
self.sock.setblocking(1)
85-
while self.running:
86-
if self.__buffer:
87-
bytes, self.__buffer = self.__buffer[:next_size], self.__buffer[next_size:]
88-
else:
89-
bytes = self.read_from_connection(next_size)
90-
91-
with self._lock:
92-
s = self.stream
93-
next_size = s.parser.send(bytes)
94-
95-
if s.closing is not None:
96-
if not self.client_terminated:
97-
next_size = 2
98-
else:
99-
self.server_terminated = True
100-
self.running = False
101-
break
102-
103-
elif s.errors:
104-
errors = s.errors[:]
105-
for error in s.errors:
106-
self.close(error.code, error.reason)
107-
s.errors.remove(error)
108-
break
109-
110-
elif s.has_message:
111-
self.received_message(s.message)
112-
s.message.data = None
113-
s.message = None
114-
115-
for ping in s.pings:
116-
self.write_to_connection(s.pong(str(ping.data)))
117-
else:
118-
s.pings = []
119-
120-
for pong in s.pongs:
121-
self.ponged(pong)
122-
else:
123-
s.pongs = []
124-
125-
except:
126-
print "".join(traceback.format_exception(*exc_info()))
127-
finally:
128-
pass
129-
130-
if self.stream.closing:
131-
self.closed(self.stream.closing.code, self.stream.closing.reason)
132-
else:
133-
self.closed(1006)
134-
16+
self._th.join()
17+
13518
if __name__ == '__main__':
136-
import time
137-
13819
class MyClient(WebSocketClient):
139-
def opened(self, protocols, extensions):
140-
WebSocketClient.opened(self, protocols, extensions)
20+
def opened(self):
14121
def data_provider():
14222
for i in range(1, 200, 25):
14323
yield "#" * i
@@ -146,14 +26,19 @@ def data_provider():
14626

14727
for i in range(0, 200, 25):
14828
self.send("*" * i)
29+
30+
def closed(self, code, reason=None):
31+
print code, reason
14932

15033
def received_message(self, m):
15134
print m, len(str(m))
15235
if len(str(m)) == 175:
15336
self.close()
15437

15538
try:
156-
ws = MyClient('http://localhost:9000/', protocols=['http-only', 'chat'])
39+
ws = MyClient('http://localhost:9000/ws', protocols=['http-only', 'chat'])
15740
ws.connect()
15841
except KeyboardInterrupt:
15942
ws.close()
43+
44+
print "terminated"

ws4py/server/cherrypyserver.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -335,7 +335,10 @@ def broadcast(self, message, binary=False):
335335
@param binary: whether or not the message is a binary one
336336
"""
337337
for ws_handler in self.pool:
338-
ws_handler.send(message, message.is_binary)
338+
try:
339+
ws_handler.send(message, message.is_binary)
340+
except:
341+
cherrypy.log(traceback=True)
339342

340343
if __name__ == '__main__':
341344
import random

ws4py/streaming.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -185,16 +185,21 @@ def receiver(self):
185185
if frame.masking_key and self.expect_masking:
186186
bytes = frame.unmask(bytes)
187187
elif not frame.masking_key and self.expect_masking:
188-
self.errors.append(CloseControlMessage(code=1002))
188+
msg = CloseControlMessage(code=1002, reason='Missing masking when expected')
189+
self.errors.append(msg)
189190
break
190191
elif frame.masking_key and not self.expect_masking:
191-
self.errors.append(CloseControlMessage(code=1002))
192+
msg = CloseControlMessage(code=1002, reason='Masked when not expected')
193+
self.errors.append()
192194
break
195+
else:
196+
bytes = bytearray(bytes)
193197

194198
if frame.opcode == OPCODE_TEXT:
195199
if self.message and not self.message.completed:
196200
# We got a text frame before we completed the previous one
197-
self.errors.append(CloseControlMessage(code=1002))
201+
msg = CloseControlMessage(code=1002, reason='Received a new message before completing previous')
202+
self.errors.append(msg)
198203
break
199204

200205
is_valid = True
@@ -206,7 +211,7 @@ def receiver(self):
206211
m.completed = (frame.fin == 1)
207212
self.message = m
208213
elif not is_valid and frame.fin == 1:
209-
self.errors.append(CloseControlMessage(code=1007))
214+
self.errors.append(CloseControlMessage(code=1007, reason='Invalid UTF-8 bytes'))
210215

211216
elif frame.opcode == OPCODE_BINARY:
212217
m = BinaryMessage(bytes)
@@ -216,7 +221,7 @@ def receiver(self):
216221
elif frame.opcode == OPCODE_CONTINUATION:
217222
m = self.message
218223
if m is None:
219-
self.errors.append(CloseControlMessage(code=1002))
224+
self.errors.append(CloseControlMessage(code=1002, reason='Message not started yet'))
220225
break
221226

222227
m.completed = (frame.fin == 1)
@@ -228,7 +233,7 @@ def receiver(self):
228233
if is_valid:
229234
m.extend(bytes)
230235
else:
231-
self.errors.append(CloseControlMessage(code=1007))
236+
self.errors.append(CloseControlMessage(code=1007, reason='Invalid UTF-8 bytes'))
232237
else:
233238
m.extend(bytes)
234239

@@ -238,7 +243,7 @@ def receiver(self):
238243
if frame.payload_length == 0:
239244
self.closing = CloseControlMessage(code=1000)
240245
elif frame.payload_length == 1:
241-
self.closing = CloseControlMessage(code=1002)
246+
self.closing = CloseControlMessage(code=1002, reason='Payload has invalid length')
242247
else:
243248
try:
244249
code = int(struct.unpack("!H", str(bytes[0:2]))[0])

0 commit comments

Comments
 (0)