Skip to content

Commit 8739f89

Browse files
committed
removed the threadedhandler model in favor of pure gevent coroutines all the way. Works rather well but as a drawback with CherryPy as we need to monkey_patch all with gevent first. Need to change this behavior
1 parent adb3719 commit 8739f89

11 files changed

Lines changed: 137 additions & 160 deletions

File tree

example/echo_cherrypy_server.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
# -*- coding: utf-8 -*-
2+
# for now I can't avoid this
3+
from gevent import monkey; monkey.patch_all()
4+
25
import argparse
36
import random
47
import os
58

69
import cherrypy
710

811
from ws4py.server.cherrypyserver import WebSocketPlugin, WebSocketTool
9-
from ws4py.server.handler.threadedhandler import WebSocketHandler, EchoWebSocketHandler
12+
from ws4py.websocket import WebSocket
1013

11-
class ChatWebSocketHandler(WebSocketHandler):
14+
class ChatWebSocketHandler(WebSocket):
1215
def received_message(self, m):
1316
cherrypy.engine.publish('websocket-broadcast', m)
1417

setup.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,13 @@
55
from distutils.core import setup
66

77
setup(name = "ws4py",
8-
version = '0.1.6',
8+
version = '0.2.0',
99
description = "WebSocket library for Python",
1010
maintainer = "Sylvain Hellegouarch",
1111
maintainer_email = "sh@defuze.org",
1212
url = "https://github.com/Lawouach/WebSocket-for-Python",
1313
download_url = "http://www.defuze.org/oss/ws4py/",
14-
packages = ["ws4py", "ws4py.client", "ws4py.server",
15-
"ws4py.server.handler", "ws4py.server.wsgi"],
14+
packages = ["ws4py", "ws4py.client", "ws4py.server", "ws4py.server.wsgi"],
1615
platforms = ["any"],
1716
license = 'BSD',
1817
long_description = "WebSocket library for Python",

ws4py/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
#
2929

3030
__author__ = "Sylvain Hellegouarch"
31-
__version__ = "0.1.6"
31+
__version__ = "0.2.0"
3232
__all__ = ['WS_KEY']
3333

3434
WS_KEY = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"

ws4py/client/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
class WebSocketBaseClient(object):
1616
def __init__(self, url, protocols=None, version='13'):
17-
self.stream = Stream()
17+
self.stream = Stream(always_mask=True, expect_masking=False)
1818
self.url = url
1919
self.protocols = protocols
2020
self.version = version

ws4py/client/threadedclient.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,11 +114,13 @@ def _receive(self):
114114

115115
for ping in s.pings:
116116
self.write_to_connection(s.pong(str(ping.data)))
117-
s.pings = []
117+
else:
118+
s.pings = []
118119

119120
for pong in s.pongs:
120121
self.ponged(pong)
121-
s.pongs = []
122+
else:
123+
s.pongs = []
122124

123125
except:
124126
print "".join(traceback.format_exception(*exc_info()))

ws4py/framing.py

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -132,16 +132,16 @@ def _parser(self):
132132
if self.rsv1 or self.rsv2 or self.rsv3:
133133
raise ProtocolException()
134134

135+
# control frames between 3 and 7 as well as above 0xA are currently reserved
136+
if 2 < self.opcode < 8 or self.opcode > 0xA:
137+
raise ProtocolException()
138+
135139
# control frames cannot be fragmented
136140
if self.opcode > 0x7 and self.fin == 0:
137141
raise ProtocolException()
138142

139143
# do we already have enough bytes to continue?
140-
if bytes and len(bytes) > 1:
141-
buf = bytes[1:]
142-
bytes = buf
143-
else:
144-
bytes = ''
144+
bytes = bytes[1:] if bytes and len(bytes) > 1 else ''
145145

146146
# Yield until we get the second header's byte
147147
while not bytes or len(bytes) < 1:
@@ -162,17 +162,14 @@ def _parser(self):
162162
buf = ''
163163
bytes = ''
164164

165-
# The spec doesn't disallow putting a value in 0x0-0xFFFF into the
166-
# 8-octet extended payload length field (or 0x0-0xFD in 2-octet field).
167-
# So, we don't check the range of extended_payload_length.
168165
if self.payload_length == 127:
169166
if len(buf) < 8:
170167
nxt_buf_size = 8 - len(buf)
171168
bytes = (yield nxt_buf_size)
172169
bytes = buf + (bytes or '')
173170
while len(bytes) < 8:
174171
b = (yield 8 - len(bytes))
175-
if isinstance(b, basestring):
172+
if b is not None:
176173
bytes = bytes + b
177174
if len(bytes) > 8:
178175
buf = bytes[8:]
@@ -191,7 +188,7 @@ def _parser(self):
191188
bytes = buf + (bytes or '')
192189
while len(bytes) < 2:
193190
b = (yield 2 - len(bytes))
194-
if isinstance(b, basestring):
191+
if b is not None:
195192
bytes = bytes + b
196193
if len(bytes) > 2:
197194
buf = bytes[2:]
@@ -209,7 +206,7 @@ def _parser(self):
209206
bytes = buf + (bytes or '')
210207
while not bytes or len(bytes) < 4:
211208
b = (yield 4 - len(bytes))
212-
if isinstance(b, basestring):
209+
if b is not None:
213210
bytes = bytes + b
214211
if len(bytes) > 4:
215212
buf = bytes[4:]
@@ -225,13 +222,17 @@ def _parser(self):
225222
while len(bytes) < self.payload_length:
226223
l = self.payload_length - len(bytes)
227224
b = (yield l)
228-
if isinstance(b, basestring):
225+
if b is not None:
229226
bytes = bytes + b
230227
else:
231-
bytes = buf[:self.payload_length]
232-
228+
if self.payload_length == len(buf):
229+
bytes = buf
230+
else:
231+
bytes = buf[:self.payload_length]
232+
233233
self.body = bytes
234-
yield
234+
235+
#yield
235236

236237
def mask(self, data):
237238
"""

ws4py/messaging.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
'PingControlMessage', 'PongControlMessage']
1010

1111
class Message(object):
12-
def __init__(self, opcode, data='', encoding='utf-8'):
12+
def __init__(self, opcode, data='', encoding='utf-8', size=None):
1313
"""
1414
A WebSocket message is made of an opcode defining its type
1515
and some bytes.
@@ -20,7 +20,7 @@ def __init__(self, opcode, data='', encoding='utf-8'):
2020
"""
2121
self.opcode = opcode
2222
self._completed = False
23-
23+
2424
if isinstance(data, basestring):
2525
if isinstance(data, unicode):
2626
data = data.encode(encoding)

ws4py/server/cherrypyserver.py

Lines changed: 22 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,12 @@ def ws(self):
7373
from cherrypy.process import plugins
7474
from cherrypy.wsgiserver import HTTPConnection, HTTPRequest
7575

76+
import gevent
77+
from gevent.pool import Pool
78+
7679
from ws4py import WS_KEY
7780
from ws4py.exc import HandshakeError
78-
from ws4py.server.handler.threadedhandler import WebSocketHandler
81+
from ws4py.websocket import WebSocket
7982

8083
__all__ = ['WebSocketTool', 'WebSocketPlugin']
8184

@@ -96,7 +99,7 @@ def _setup(self):
9699
hooks.attach('on_end_request', self.start_handler,
97100
priority=70)
98101

99-
def upgrade(self, protocols=None, extensions=None, version=13, handler_cls=WebSocketHandler):
102+
def upgrade(self, protocols=None, extensions=None, version=13, handler_cls=WebSocket):
100103
"""
101104
Performs the upgrade of the connection to the WebSocket
102105
protocol.
@@ -194,8 +197,6 @@ def upgrade(self, protocols=None, extensions=None, version=13, handler_cls=WebSo
194197
addr = (request.remote.ip, request.remote.port)
195198
ws_conn = request.rfile.rfile._sock
196199
request.ws_handler = handler_cls(ws_conn, ws_protocols, ws_extensions)
197-
# Start tracking the handler
198-
cherrypy.engine.publish('handle-websocket', request.ws_handler, addr)
199200

200201
def complete(self):
201202
"""
@@ -227,13 +228,17 @@ def start_handler(self):
227228
request = cherrypy.request
228229
if not hasattr(request, 'ws_handler'):
229230
return
230-
231-
request.ws_handler.opened()
231+
232+
addr = (request.remote.ip, request.remote.port)
233+
ws_handler = request.ws_handler
232234
request.ws_handler = None
235+
delattr(request, 'ws_handler')
233236
# By doing this we detach the socket from
234237
# the CherryPy stack avoiding memory leaks
235238
request.rfile.rfile._sock = None
236239

240+
cherrypy.engine.publish('handle-websocket', ws_handler, addr)
241+
237242
def _set_internal_flags(self):
238243
"""
239244
CherryPy has two internal flags that we are interested in
@@ -269,20 +274,19 @@ def _set_internal_flags(self):
269274
class WebSocketPlugin(plugins.SimplePlugin):
270275
def __init__(self, bus):
271276
plugins.SimplePlugin.__init__(self, bus)
272-
self.handlers = []
277+
self.pool = Pool()
273278

274279
def start(self):
275280
cherrypy.log("Starting WebSocket processing")
276281
self.bus.subscribe('handle-websocket', self.handle)
277282
self.bus.subscribe('websocket-broadcast', self.broadcast)
278-
self.bus.subscribe('main', self.cleanup)
279283

280284
def stop(self):
281285
cherrypy.log("Terminating WebSocket processing")
282-
self.bus.unsubscribe('main', self.cleanup)
286+
self.pool.kill()
287+
self.pool.join()
283288
self.bus.unsubscribe('handle-websocket', self.handle)
284289
self.bus.unsubscribe('websocket-broadcast', self.broadcast)
285-
self.cleanup()
286290

287291
def handle(self, ws_handler, peer_addr):
288292
"""
@@ -292,28 +296,13 @@ def handle(self, ws_handler, peer_addr):
292296
@param peer_addr: remote peer address for tracing purpose
293297
"""
294298
cherrypy.log("Managing WebSocket connection from %s:%d" % (peer_addr[0], peer_addr[1]))
295-
self.handlers.append((ws_handler, peer_addr))
296-
297-
def cleanup(self):
298-
"""
299-
Performs a bit of cleanup on tracked handlers
300-
by closing connection of terminated streams then
301-
removing them from the tracked list.
302-
"""
303-
handlers = self.handlers[:]
304-
for peer in handlers:
305-
handler, addr = peer
306-
if handler.terminated:
307-
cherrypy.log("Removing WebSocket connection from peer: %s:%d" % (addr[0], addr[1]))
308-
try:
309-
handler.close_connection()
310-
except:
311-
cherrypy.log(traceback=True)
312-
finally:
313-
if handler._th.is_alive():
314-
handler._th.join()
315-
self.handlers.remove(peer)
299+
ws_handler.link(self.unhandle)
300+
self.pool.start(ws_handler)
316301

302+
def unhandle(self, ws_handler):
303+
cherrypy.log("Removing WebSocket connection")
304+
self.pool.discard(ws_handler)
305+
317306
def broadcast(self, message, binary=False):
318307
"""
319308
Broadcasts a message to all connected clients known to
@@ -323,13 +312,8 @@ def broadcast(self, message, binary=False):
323312
of the connected handler.
324313
@param binary: whether or not the message is a binary one
325314
"""
326-
handlers = self.handlers[:]
327-
for peer in handlers:
328-
try:
329-
handler, addr = peer
330-
handler.send(message, binary)
331-
except:
332-
cherrypy.log(traceback=True)
315+
for ws_handler in self.pool:
316+
ws_handler.send(message, message.is_binary)
333317

334318
if __name__ == '__main__':
335319
import random

ws4py/server/geventserver.py

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1+
2+
from gevent import monkey; monkey.patch_all()
3+
14
import gevent.pywsgi
25
from gevent import version_info
36
IS_GEVENT_V10 = version_info[0] == 1
47
del version_info
58

69
from ws4py.server.wsgi.middleware import WebSocketUpgradeMiddleware
10+
from ws4py.websocket import WebSocket
711

812
class UpgradableWSGIHandler(gevent.pywsgi.WSGIHandler):
913
"""Upgradable version of gevent.pywsgi.WSGIHandler class
@@ -85,25 +89,31 @@ def start_response_for_upgrade(status, headers, exc_info=None):
8589
class WebSocketServer(gevent.pywsgi.WSGIServer):
8690
handler_class = UpgradableWSGIHandler
8791

88-
def __init__(self, *args, **kwargs):
89-
gevent.pywsgi.WSGIServer.__init__(self, *args, **kwargs)
92+
def __init__(self, address, *args, **kwargs):
93+
9094
protocols = kwargs.pop('websocket_protocols', [])
9195
extensions = kwargs.pop('websocket_extensions', [])
92-
self.application = WebSocketUpgradeMiddleware(self.application,
93-
protocols=protocols,
94-
extensions=extensions)
96+
websocket = kwargs.pop('websocket_class', WebSocket)
97+
98+
gevent.pywsgi.WSGIServer.__init__(self, address, *args, **kwargs)
99+
self.application = WebSocketUpgradeMiddleware(protocols=protocols,
100+
extensions=extensions,
101+
websocket_class=websocket)
95102

96103
if __name__ == '__main__':
97-
def echo_handler(websocket, environ):
98-
try:
99-
while True:
100-
msg = websocket.receive(msg_obj=True)
101-
if msg is not None:
102-
websocket.send(msg.data, msg.is_binary)
103-
else:
104-
break
105-
finally:
106-
websocket.close()
107-
108-
server = WebSocketServer(('127.0.0.1', 9001), echo_handler)
104+
import logging
105+
import sys
106+
logging.basicConfig(format='%(asctime)s %(message)s')
107+
logger = logging.getLogger()
108+
logger.setLevel(logging.DEBUG)
109+
h = logging.StreamHandler()
110+
h.setLevel(logging.DEBUG)
111+
logger.addHandler(h)
112+
113+
class EchoWebSocket(WebSocket):
114+
def received_message(self, message):
115+
self.send(message, message.is_binary)
116+
117+
server = WebSocketServer(('127.0.0.1', 9001), websocket_class=EchoWebSocket)
109118
server.serve_forever()
119+

0 commit comments

Comments
 (0)