Skip to content

Commit ffd54e4

Browse files
authored
Merge pull request #191 from rsocket/asyncwebsockets
Asyncwebsockets
2 parents ea2846f + 5262d37 commit ffd54e4

File tree

7 files changed

+79
-20
lines changed

7 files changed

+79
-20
lines changed

CHANGELOG.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ Changelog
33

44
v0.4.15
55
=======
6-
- Websockets (https://github.com/python-websockets/websockets) server support
6+
- Websockets server support (https://github.com/python-websockets/websockets)
7+
- AsyncWebsockets client support (https://github.com/Fuyukai/asyncwebsockets)
78

89
v0.4.14
910
=======

README.md

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,19 @@ pip install rsocket
1212

1313
You may also install using some **extras**:
1414

15-
| Extra | Functionality | Documentation |
16-
|-------------|--------------------------------------------------------------------------------------------|---------------------------------------------------------------------|
17-
| rx | ReactiveX ([v3](https://pypi.org/project/Rx/)) integration | [Tutorial](https://rsocket.io/guides/rsocket-py/tutorial/reactivex) |
18-
| reactivex | [ReactiveX](https://reactivex.io/) ([v4](https://pypi.org/project/reactivex/)) integration | [Tutorial](https://rsocket.io/guides/rsocket-py/tutorial/reactivex) |
19-
| aiohttp | [aiohttp](https://docs.aiohttp.org/en/stable/) Websocket transport (server/client) | [Tutorial](https://rsocket.io/guides/rsocket-py/tutorial/websocket) |
20-
| quart | [Quart](https://pgjones.gitlab.io/quart/) Websocket transport (server only) | |
21-
| quic | [QUIC](https://github.com/aiortc/aioquic) transport | |
22-
| websockets | [Websockets](https://github.com/python-websockets/websockets) transport (server only) | |
23-
| cli | Command line | [Tutorial](https://rsocket.io/guides/rsocket-py/cli) |
24-
| optimized | Frame parse/serialize optimizations | |
25-
| cloudevents | [CloudEvents](https://cloudevents.io/) integration | |
26-
| graphql | [GraphQL](https://graphql.org/) integration | [Tutorial](https://rsocket.io/guides/rsocket-py/graphql) |
15+
| Extra | Functionality | Documentation |
16+
|-----------------|--------------------------------------------------------------------------------------------|---------------------------------------------------------------------|
17+
| rx | ReactiveX ([v3](https://pypi.org/project/Rx/)) integration | [Tutorial](https://rsocket.io/guides/rsocket-py/tutorial/reactivex) |
18+
| reactivex | [ReactiveX](https://reactivex.io/) ([v4](https://pypi.org/project/reactivex/)) integration | [Tutorial](https://rsocket.io/guides/rsocket-py/tutorial/reactivex) |
19+
| aiohttp | [aiohttp](https://docs.aiohttp.org/en/stable/) Websocket transport (server/client) | [Tutorial](https://rsocket.io/guides/rsocket-py/tutorial/websocket) |
20+
| quart | [Quart](https://pgjones.gitlab.io/quart/) Websocket transport (server only) | |
21+
| quic | [QUIC](https://github.com/aiortc/aioquic) transport | |
22+
| websockets | [Websockets](https://github.com/python-websockets/websockets) transport (server only) | |
23+
| asyncwebsockets | [Websockets](https://github.com/Fuyukai/asyncwebsockets) transport (client only) | |
24+
| cli | Command line | [Tutorial](https://rsocket.io/guides/rsocket-py/cli) |
25+
| optimized | Frame parse/serialize optimizations | |
26+
| cloudevents | [CloudEvents](https://cloudevents.io/) integration | |
27+
| graphql | [GraphQL](https://graphql.org/) integration | [Tutorial](https://rsocket.io/guides/rsocket-py/graphql) |
2728

2829
For example:
2930

requirements.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,5 @@ pydantic==1.10.13
2323
Werkzeug==3.0.0
2424
graphql-core==3.2.3
2525
gql==3.4.1
26-
websockets==11.0.3
26+
websockets==11.0.3
27+
asyncwebsockets==0.9.4
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import asyncio
2+
from contextlib import asynccontextmanager
3+
4+
from rsocket.exceptions import RSocketTransportError
5+
from rsocket.frame import Frame
6+
from rsocket.helpers import wrap_transport_exception, single_transport_provider
7+
from rsocket.logger import logger
8+
from rsocket.rsocket_client import RSocketClient
9+
from rsocket.transports.abstract_messaging import AbstractMessagingTransport
10+
11+
12+
@asynccontextmanager
13+
async def websocket_client(url: str,
14+
**kwargs) -> RSocketClient:
15+
"""
16+
Helper method to instantiate an RSocket client using a websocket url over asyncwebsockets client.
17+
"""
18+
from asyncwebsockets import open_websocket
19+
async with open_websocket(url) as websocket:
20+
async with RSocketClient(single_transport_provider(TransportAsyncWebsocketsClient(websocket)),
21+
**kwargs) as client:
22+
yield client
23+
24+
25+
class TransportAsyncWebsocketsClient(AbstractMessagingTransport):
26+
"""
27+
RSocket transport over client side asyncwebsockets.
28+
"""
29+
30+
def __init__(self, websocket):
31+
super().__init__()
32+
self._ws = websocket
33+
self._message_handler = None
34+
35+
async def connect(self):
36+
self._message_handler = asyncio.create_task(self.handle_incoming_ws_messages())
37+
38+
async def handle_incoming_ws_messages(self):
39+
from wsproto.events import BytesMessage
40+
try:
41+
async for message in self._ws:
42+
if isinstance(message, BytesMessage):
43+
async for frame in self._frame_parser.receive_data(message.data, 0):
44+
self._incoming_frame_queue.put_nowait(frame)
45+
except asyncio.CancelledError:
46+
logger().debug('Asyncio task canceled: incoming_data_listener')
47+
except Exception:
48+
self._incoming_frame_queue.put_nowait(RSocketTransportError())
49+
50+
async def send_frame(self, frame: Frame):
51+
with wrap_transport_exception():
52+
await self._ws.send(frame.serialize())
53+
54+
async def close(self):
55+
self._message_handler.cancel()
56+
await self._message_handler

setup.cfg

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@ cloudevents =
6161
graphql =
6262
graphql-core>=3.2.0
6363
gql>=3.4.0
64-
websockets =
65-
websockets>=11.0.0
64+
websockets = websockets>=11.0.0
65+
asyncwebsockets = asyncwebsockets>=0.9.4
6666

6767
[options.entry_points]
6868
cli.console_scripts =

tests/conftest.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,13 @@ def setup_logging(level=logging.DEBUG, use_file: bool = False):
4040
setup_logging(logging.WARN)
4141

4242
tested_transports = [
43-
'tcp'
43+
'tcp',
44+
'quart'
4445
]
4546

4647
if sys.version_info[:3] < (3, 11, 5):
4748
tested_transports += [
4849
'aiohttp',
49-
'quart',
5050
'quic',
5151
'http3',
5252
# 'websockets'

tests/tools/fixtures_quart.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
async def pipe_factory_quart_websocket(unused_tcp_port, client_arguments=None, server_arguments=None):
1212
from quart import Quart
1313
from rsocket.transports.quart_websocket import websocket_handler
14-
from rsocket.transports.aiohttp_websocket import websocket_client
14+
from rsocket.transports.asyncwebsockets_transport import websocket_client
1515

1616
app = Quart(__name__)
1717
server: Optional[RSocketBase] = None
@@ -32,7 +32,7 @@ async def ws():
3232
server_task = asyncio.create_task(app.run_task(port=unused_tcp_port))
3333
await asyncio.sleep(0)
3434

35-
async with websocket_client('http://localhost:{}'.format(unused_tcp_port),
35+
async with websocket_client('ws://localhost:{}'.format(unused_tcp_port),
3636
**client_arguments) as client:
3737
await wait_for_server.wait()
3838
yield server, client

0 commit comments

Comments
 (0)