From 3a5e55d77e773810a80bf69424f8ff78b4a51f6a Mon Sep 17 00:00:00 2001 From: Paul Sokolovsky Date: Fri, 3 Nov 2017 21:08:08 +0200 Subject: [PATCH 1/3] WIP: uasyncio.websocket: Websocket implementation for uasyncio. Q #1: Should this be in uasyncio package at all? Upstream doesn't have this. Pro: will be easier for people do discover (see e.g. https://github.com/micropython/micropython-lib/issues/148) Q #2: This provides implements 2 ways to create a WS connections: 1) using start_ws_server(); 2) using wrapping existing StreamReader and StreamWriter. History: initial prototype of course used 2). But the idea was "it should be like the official start_server()!!1". But then I though how to integrate it e.g. with Picoweb, and became clear that 2) is the most flixble way. So, 1) is intended to be removed. Q #3: Uses native websocket module for read path, but has own write path due to https://github.com/micropython/micropython/issues/3396 Q #4: Requires https://github.com/micropython/micropython-lib/pull/227 due to https://github.com/micropython/micropython/issues/3394 . --- uasyncio.websocket/uasyncio/websocket.py | 104 +++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 uasyncio.websocket/uasyncio/websocket.py diff --git a/uasyncio.websocket/uasyncio/websocket.py b/uasyncio.websocket/uasyncio/websocket.py new file mode 100644 index 000000000..2baed1baa --- /dev/null +++ b/uasyncio.websocket/uasyncio/websocket.py @@ -0,0 +1,104 @@ +import uasyncio as asyncio +import uhashlib, ubinascii +import websocket + + +def make_respkey(webkey): + d = uhashlib.sha1(webkey) + d.update(b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11") + respkey = d.digest() + respkey = ubinascii.b2a_base64(respkey) #[:-1] + return respkey + + +class WSWriter: + + def __init__(self, s): + self.s = s + + async def awrite(self, data): + assert len(data) < 126 + await self.s.awrite(b"\x81") + await self.s.awrite(bytes([len(data)])) + await self.s.awrite(data) + + +def WSReader(reader, writer): + + webkey = None + while 1: + l = yield from reader.readline() + print(l) + if not l: + raise ValueError() + if l == b"\r\n": + break + if l.startswith(b'Sec-WebSocket-Key'): + webkey = l.split(b":", 1)[1] + webkey = webkey.strip() + + if not webkey: + raise ValueError("Not a websocker request") + + respkey = make_respkey(webkey) + + await writer.awrite(b"""\ +HTTP/1.1 101 Switching Protocols\r +Upgrade: websocket\r +Connection: Upgrade\r +Sec-WebSocket-Accept: """) + await writer.awrite(respkey) + await writer.awrite("\r\n") +# await writer.awrite("\r\n\r\n") + + print("Finished webrepl handshake") + + ws = websocket.websocket(reader.ios) + rws = asyncio.StreamReader(reader.ios, ws) + + return rws + + +#deprecated +def start_ws_server(client_coro, host, port, backlog=10): + + def ws_wrap(reader, writer): + print(reader, writer) + + print("Req:", (yield from reader.readline())) + + webkey = None + while 1: + l = yield from reader.readline() + print(l) + if not l: + raise ValueError() + if l == b"\r\n": + break + if l.startswith(b'Sec-WebSocket-Key'): + webkey = l.split(b":", 1)[1] + webkey = webkey.strip() + + if not webkey: + raise ValueError("Not a websocker request") + + respkey = make_respkey(webkey) + + await writer.awrite(b"""\ +HTTP/1.1 101 Switching Protocols\r +Upgrade: websocket\r +Connection: Upgrade\r +Sec-WebSocket-Accept: """) + await writer.awrite(respkey) + await writer.awrite("\r\n") +# await writer.awrite("\r\n\r\n") + + print("Finished webrepl handshake") + + ws = websocket.websocket(reader.ios) + rws = asyncio.StreamReader(reader.ios, ws) + + return await client_coro(rws, WSWriter(writer)) + + + await asyncio.start_server(ws_wrap, host, port, backlog) From 754d30019e1416ed2122a19298faa465e3a7d11a Mon Sep 17 00:00:00 2001 From: Paul Sokolovsky Date: Fri, 3 Nov 2017 21:24:43 +0200 Subject: [PATCH 2/3] WIP: uasyncio.websocket: Add example using StreamReader/Writer wrapping. --- uasyncio.websocket/example_websock.py | 28 +++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 uasyncio.websocket/example_websock.py diff --git a/uasyncio.websocket/example_websock.py b/uasyncio.websocket/example_websock.py new file mode 100644 index 000000000..ad81eea66 --- /dev/null +++ b/uasyncio.websocket/example_websock.py @@ -0,0 +1,28 @@ +import uasyncio +from uasyncio_.websocket import WSReader, WSWriter + + +def echo(reader, writer): + # Consume GET line + yield from reader.readline() + + reader = yield from WSReader(reader, writer) + writer = WSWriter(writer) + + while 1: + l = yield from reader.read(256) + print(l) + if l == b"\r": + await writer.awrite(b"\r\n") + else: + await writer.awrite(l) + + +import logging +#logging.basicConfig(level=logging.INFO) +logging.basicConfig(level=logging.DEBUG) +loop = uasyncio.get_event_loop() +#loop.create_task(asyncio.start_server(serve, "127.0.0.1", 8081)) +loop.create_task(uasyncio.start_server(echo, "127.0.0.1", 8081)) +loop.run_forever() +loop.close() From 3c9a75919d01d8c1138c6611b7d57a8ef0c72545 Mon Sep 17 00:00:00 2001 From: Paul Sokolovsky Date: Fri, 3 Nov 2017 21:25:11 +0200 Subject: [PATCH 3/3] WIP: uasyncio.websocket: Add example use start_ws_server() (deprecated). --- uasyncio.websocket/example_websock_old.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 uasyncio.websocket/example_websock_old.py diff --git a/uasyncio.websocket/example_websock_old.py b/uasyncio.websocket/example_websock_old.py new file mode 100644 index 000000000..9c5ca3d67 --- /dev/null +++ b/uasyncio.websocket/example_websock_old.py @@ -0,0 +1,22 @@ +import uasyncio +import uasyncio_.websocket + + +def echo(reader, writer): + while 1: + l = yield from reader.read(256) + print(l) + if l == b"\r": + await writer.awrite(b"\r\n") + else: + await writer.awrite(l) + + +import logging +#logging.basicConfig(level=logging.INFO) +logging.basicConfig(level=logging.DEBUG) +loop = uasyncio.get_event_loop() +#loop.create_task(asyncio.start_server(serve, "127.0.0.1", 8081)) +loop.create_task(uasyncio_.websocket.start_ws_server(echo, "127.0.0.1", 8081)) +loop.run_forever() +loop.close()