Skip to content

Commit 506dcb8

Browse files
committed
updated asyncio code... all autobahn testsuite green lighted now
1 parent 6337a75 commit 506dcb8

4 files changed

Lines changed: 121 additions & 40 deletions

File tree

test/autobahn_test_servers.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,26 @@ def run_gevent_server(host="127.0.0.1", port=9001):
7171
server.serve_forever()
7272

7373

74+
def run_python3_asyncio(host="127.0.0.1", port=9007):
75+
"""
76+
Runs a server using asyncio and Python 3.3+
77+
"""
78+
import asyncio
79+
from ws4py.async_websocket import EchoWebSocket
80+
from ws4py.server.tulipserver import WebSocketProtocol
81+
82+
loop = asyncio.get_event_loop()
83+
84+
def start_server():
85+
proto_factory = lambda: WebSocketProtocol(EchoWebSocket)
86+
return loop.create_server(proto_factory, host, port)
87+
88+
s = loop.run_until_complete(start_server())
89+
logger = logging.getLogger('asyncio_testsuite')
90+
logger.warning("Serving asyncio server on %s:%s" % s.sockets[0].getsockname())
91+
loop.run_forever()
92+
93+
7494

7595
def run_tornado_server(host="127.0.0.1", port=9007):
7696
"""
@@ -136,6 +156,8 @@ class ServerFactory(WebSocketServerFactory):
136156
help='Run the Tornado server backend')
137157
parser.add_argument('--run-autobahn-server', dest='run_autobahn', action='store_true',
138158
help='Run the Autobahn server backend')
159+
parser.add_argument('--run-asyncio-server', dest='run_asyncio', action='store_true',
160+
help='Run the asyncio server backend')
139161
args = parser.parse_args()
140162

141163
if args.run_all:
@@ -144,6 +166,7 @@ class ServerFactory(WebSocketServerFactory):
144166
args.run_gevent = True
145167
args.run_tornado = True
146168
args.run_autobahn = True
169+
args.run_asyncio = True
147170

148171
procs = []
149172
logger.warning("CherryPy server: %s" % args.run_cherrypy)
@@ -188,6 +211,12 @@ class ServerFactory(WebSocketServerFactory):
188211
p6.daemon = True
189212
procs.append(p6)
190213

214+
logger.warning("asyncio server on Python 3: %s" % args.run_asyncio)
215+
if args.run_asyncio:
216+
p7 = Process(target=run_python3_asyncio)
217+
p7.daemon = True
218+
procs.append(p7)
219+
191220
for p in procs:
192221
p.start()
193222
logging.info("Starting process... %d" % p.pid)

test/fuzzingclient.json

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,31 +5,35 @@
55
"servers": [
66

77
{"agent": "ws4py (CherryPy 3.2.4) for Python 2.7.3",
8-
"url": "ws://127.0.0.1:9000",
8+
"url": "ws://127.0.0.1:9000/",
99
"options": {"version": 18}},
1010

1111
{"agent": "ws4py (CherryPy 3.2.4)/wsaccel for Python 2.7.3",
12-
"url": "ws://127.0.0.1:9006",
12+
"url": "ws://127.0.0.1:9006/",
1313
"options": {"version": 18}},
1414

1515
{"agent": "ws4py (CherryPy 3.2.4) for Python 3.3.2",
16-
"url": "ws://127.0.0.1:9004",
16+
"url": "ws://127.0.0.1:9004/",
1717
"options": {"version": 18}},
1818

1919
{"agent": "ws4py (CherryPy 3.2.4) for PyPy 2.0",
20-
"url": "ws://127.0.0.1:9005",
20+
"url": "ws://127.0.0.1:9005/",
2121
"options": {"version": 18}},
2222

2323
{"agent": "ws4py (gevent 1.0.0dev)",
24-
"url": "ws://127.0.0.1:9001",
24+
"url": "ws://127.0.0.1:9001/",
2525
"options": {"version": 18}},
2626

2727
{"agent": "Tornado 3.1.1",
28-
"url": "ws://127.0.0.1:9007",
28+
"url": "ws://127.0.0.1:9007/",
2929
"options": {"version": 18}},
3030

3131
{"agent": "Autobahn 0.5.14",
32-
"url": "ws://127.0.0.1:9003",
32+
"url": "ws://127.0.0.1:9003/",
33+
"options": {"version": 18}},
34+
35+
{"agent": "ws4py (asyncio)",
36+
"url": "ws://127.0.0.1:9007/",
3337
"options": {"version": 18}}
3438

3539
],

ws4py/async_websocket.py

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@
1616
delegated_generator_websocket_on_top_of_asyncio.py
1717
"""
1818
import asyncio
19+
import types
1920

2021
from ws4py.websocket import WebSocket as _WebSocket
22+
from ws4py.messaging import Message
2123

2224
__all__ = ['WebSocket', 'EchoWebSocket']
2325

@@ -40,16 +42,16 @@ class is still coupled a bit to the socket interface,
4042
some day this will be cleaned out.
4143
"""
4244
_WebSocket.__init__(self, None)
43-
self.proto = proto
4445
self.started = False
46+
self.proto = proto
4547

4648
@property
4749
def local_address(self):
4850
"""
4951
Local endpoint address as a tuple
5052
"""
5153
if not self._local_address:
52-
self._local_address = self.proto.transport.get_extra_info('sockname')
54+
self._local_address = self.proto.reader.transport.get_extra_info('sockname')
5355
if len(self._local_address) == 4:
5456
self._local_address = self._local_address[:2]
5557
return self._local_address
@@ -60,7 +62,7 @@ def peer_address(self):
6062
Peer endpoint address as a tuple
6163
"""
6264
if not self._peer_address:
63-
self._peer_address = self.proto.transport.get_extra_info('peername')
65+
self._peer_address = self.proto.reader.transport.get_extra_info('peername')
6466
if len(self._peer_address) == 4:
6567
self._peer_address = self._peer_address[:2]
6668
return self._peer_address
@@ -78,13 +80,21 @@ def close_connection(self):
7880
"""
7981
Close the underlying transport
8082
"""
81-
self.proto.transport.close()
83+
@asyncio.coroutine
84+
def closeit():
85+
yield from self.proto.writer.drain()
86+
self.proto.writer.close()
87+
asyncio.async(closeit())
8288

8389
def _write(self, data):
8490
"""
8591
Write to the underlying transport
8692
"""
87-
self.proto.transport.write(data)
93+
@asyncio.coroutine
94+
def sendit(data):
95+
self.proto.writer.write(data)
96+
yield from self.proto.writer.drain()
97+
asyncio.async(sendit(data))
8898

8999
@asyncio.coroutine
90100
def run(self):
@@ -95,15 +105,18 @@ def run(self):
95105
has started.
96106
"""
97107
self.started = True
98-
self.opened()
99108
try:
109+
self.opened()
110+
reader = self.proto.reader
100111
while True:
101-
bytes = yield from self.proto.stream.read(self.reading_buffer_size)
102-
if not self.process(bytes):
112+
data = yield from reader.read(self.reading_buffer_size)
113+
if not self.process(data):
103114
return False
104115
finally:
105116
self.terminate()
106117

118+
return True
119+
107120
class EchoWebSocket(WebSocket):
108121
def received_message(self, message):
109122
"""

ws4py/server/tulipserver.py

Lines changed: 60 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,14 @@
1717

1818
__all__ = ['WebSocketProtocol']
1919

20-
class WebSocketProtocol(asyncio.Protocol):
20+
class WebSocketProtocol(asyncio.StreamReaderProtocol):
2121
def __init__(self, handler_cls):
22-
asyncio.Protocol.__init__(self)
22+
asyncio.StreamReaderProtocol.__init__(self, asyncio.StreamReader(),
23+
self._pseudo_connected)
2324
self.ws = handler_cls(self)
25+
26+
def _pseudo_connected(self, reader, writer):
27+
pass
2428

2529
def connection_made(self, transport):
2630
"""
@@ -31,10 +35,34 @@ def connection_made(self, transport):
3135
and the transport is associated before the
3236
initial HTTP handshake is undertaken.
3337
"""
34-
self.transport = transport
35-
self.stream = asyncio.StreamReader()
36-
self.stream.set_transport(transport)
37-
asyncio.async(self.handle_initial_handshake())
38+
#self.transport = transport
39+
#self.stream = asyncio.StreamReader()
40+
#self.stream.set_transport(transport)
41+
asyncio.StreamReaderProtocol.connection_made(self, transport)
42+
# Let make it concurrent for others to tag along
43+
f = asyncio.async(self.handle_initial_handshake())
44+
f.add_done_callback(self.terminated)
45+
46+
@property
47+
def writer(self):
48+
return self._stream_writer
49+
50+
@property
51+
def reader(self):
52+
return self._stream_reader
53+
54+
def terminated(self, f):
55+
if f.done() and not f.cancelled():
56+
ex = f.exception()
57+
if ex:
58+
print(ex)
59+
response = [b'HTTP/1.0 400 Bad Request']
60+
response.append(b'Content-Length: 0')
61+
response.append(b'Connection: close')
62+
response.append(b'')
63+
response.append(b'')
64+
self.writer.write(CRLF.join(response))
65+
self.ws.close_connection()
3866

3967
def close(self):
4068
"""
@@ -57,13 +85,11 @@ def connection_lost(self, exc):
5785
be aware of it by calling its `closed`
5886
method.
5987
"""
60-
self.ws.close_connection()
61-
if self.ws.started:
62-
self.ws.closed(1002, "Peer connection was lost")
88+
if exc is not None:
89+
self.ws.close_connection()
90+
if self.ws.started:
91+
self.ws.closed(1002, "Peer connection was lost")
6392

64-
def data_received(self, data):
65-
self.stream.feed_data(data)
66-
6793
@asyncio.coroutine
6894
def handle_initial_handshake(self):
6995
"""
@@ -81,6 +107,8 @@ def handle_initial_handshake(self):
81107
raise HandshakeError('HTTP method must be a GET')
82108

83109
headers = yield from self.read_headers()
110+
if req_protocol == b'HTTP/1.1' and 'Host' not in headers:
111+
raise ValueError("Missing host header")
84112

85113
for key, expected_value in [('Upgrade', 'websocket'),
86114
('Connection', 'upgrade')]:
@@ -113,30 +141,37 @@ def handle_initial_handshake(self):
113141
raise HandshakeError("WebSocket key's length is invalid")
114142

115143
protocols = []
144+
ws_protocols = []
116145
subprotocols = headers.get('Sec-WebSocket-Protocol')
117146
if subprotocols:
118-
ws_protocols = []
119147
for s in subprotocols.split(','):
120148
s = s.strip()
121149
if s in protocols:
122150
ws_protocols.append(s)
123151

124152
exts = []
153+
ws_extensions = []
125154
extensions = headers.get('Sec-WebSocket-Extensions')
126155
if extensions:
127156
for ext in extensions.split(','):
128157
ext = ext.strip()
129158
if ext in exts:
130159
ws_extensions.append(ext)
131160

132-
self.transport.write(('%s 101 Switching Protocols\r\n' % req_protocol).encode('utf-8'))
133-
self.transport.write(b'Upgrade: websocket\r\n')
134-
self.transport.write(b'Content-Length: 0\r\n')
135-
self.transport.write(b'Connection: Upgrade\r\n')
136-
self.transport.write(b'Sec-WebSocket-Version:' + bytes(str(version), 'utf-8') + CRLF)
137-
self.transport.write(b'Sec-WebSocket-Accept:' + base64.b64encode(sha1(key.encode('utf-8') + WS_KEY).digest()) + CRLF)
138-
self.transport.write(CRLF)
139-
161+
response = [req_protocol + b' 101 Switching Protocols']
162+
response.append(b'Upgrade: websocket')
163+
response.append(b'Content-Type: text/plain')
164+
response.append(b'Content-Length: 0')
165+
response.append(b'Connection: Upgrade')
166+
response.append(b'Sec-WebSocket-Version:' + bytes(str(version), 'utf-8'))
167+
response.append(b'Sec-WebSocket-Accept:' + base64.b64encode(sha1(key.encode('utf-8') + WS_KEY).digest()))
168+
if ws_protocols:
169+
response.append(b'Sec-WebSocket-Protocol:' + b', '.join(ws_protocols))
170+
if ws_extensions:
171+
response.append(b'Sec-WebSocket-Extensions:' + b','.join(ws_extensions))
172+
response.append(b'')
173+
response.append(b'')
174+
self.writer.write(CRLF.join(response))
140175
yield from self.handle_websocket()
141176

142177
@asyncio.coroutine
@@ -167,19 +202,19 @@ def next_line(self):
167202
Reads data until \r\n is met and then return all read
168203
bytes.
169204
"""
170-
line = yield from self.stream.readline()
205+
line = yield from self.reader.readline()
171206
if not line.endswith(CRLF):
172207
raise ValueError("Missing mandatory trailing CRLF")
173208
return line
174209

175210
if __name__ == '__main__':
176-
from ws4py.websocket import AsyncEchoWebSocket
211+
from ws4py.async_websocket import EchoWebSocket
177212

178213
loop = asyncio.get_event_loop()
179214

180215
def start_server():
181-
proto_factory = lambda: WebSocketProtocol(AsyncEchoWebSocket)
182-
return loop.create_server(proto_factory, '', 7002)
216+
proto_factory = lambda: WebSocketProtocol(EchoWebSocket)
217+
return loop.create_server(proto_factory, '', 9007)
183218

184219
s = loop.run_until_complete(start_server())
185220
print('serving on', s.sockets[0].getsockname())

0 commit comments

Comments
 (0)