Originally posted by **cirospaciari** December 1, 2022
Just testing the websockets ASGI protocol behavior, in uvicorn and other the following code i used
```python
async def app(scope, receive, send):
# handle non websocket
if scope['type'] == 'http':
await send({
'type': 'http.response.start',
'status': 200,
'headers': [
[b'content-type', b'text/plain'],
],
})
await send({
'type': 'http.response.body',
'body': b'Connect via ws protocol!',
})
if scope['type'] != 'websocket':
return
protocols = scope.get('subprotocols', [])
scope = await receive()
# get connection
assert scope['type'] == 'websocket.connect'
# accept connection
await send({
'type': 'websocket.accept',
'subprotocol': protocols[0] if len(protocols) > 0 else None
})
# get data
while True:
scope = await receive()
type = scope['type']
# disconnected!
if type == 'websocket.disconnect':
print("disconnected!", scope)
break
# echo!
await send({
'type': 'websocket.send',
'bytes': scope.get('bytes', None),
'text': scope.get('text', '')
})
```
The order:
scope['type'] = websocket:
call await receive() to get the first websocket.connect
send websocket.accept to accept the connection
call await receive() to get websocket.receive or websocket.disconnect
send websocket.close if i want to end the connection
In granian ASGI:
```python
async def app(scope, receive, send):
# handle non websocket
if scope['type'] == 'http':
await send({
'type': 'http.response.start',
'status': 200,
'headers': [
[b'content-type', b'text/plain'],
],
})
await send({
'type': 'http.response.body',
'body': b'Connect via ws protocol!',
})
if scope['type'] != 'websocket':
return
protocols = scope.get('subprotocols', [])
# accept connection
await send({
'type': 'websocket.accept',
'subprotocol': protocols[0] if len(protocols) > 0 else None
})
scope = await receive()
assert scope['type'] == 'websocket.connect'
# get data
while True:
scope = await receive()
type = scope['type']
# disconnected!
if type == 'websocket.disconnect':
print("disconnected!", scope)
break
# echo!
await send({
'type': 'websocket.send',
'bytes': scope.get('bytes', None),
'text': scope.get('text', '')
})
```
The order:
scope['type'] = websocket:
send websocket.accept to accept the connection
call await receive() to get the first websocket.connect
call await receive() to get websocket.receive or websocket.disconnect
send websocket.close if i want to end the connection
Falcon source code for ASGI for reference:
```python
async def _handle_websocket(self, ver, scope, receive, send):
first_event = await receive()
if first_event['type'] != EventType.WS_CONNECT:
# NOTE(kgriffs): The handshake was abandoned or this is a message
# we don't support, so bail out. This also fulfills the ASGI
# spec requirement to only process the request after
# receiving and verifying the first event.
await send({'type': EventType.WS_CLOSE, 'code': WSCloseCode.SERVER_ERROR})
return
```
https://github.com/falconry/falcon/blob/master/falcon/asgi/app.py Line 970
ASGI spec about websocket.connect
>"This message must be responded to with either an Accept message or a Close message before the socket will pass websocket.receive messages. The protocol server must send this message during the handshake phase of the WebSocket and not complete the handshake until it gets a reply, returning HTTP status code 403 if the connection is denied."
https://asgi.readthedocs.io/en/latest/specs/www.html#connect-receive-event
Another thing is if i call in granian
```python
await send({
'type': 'websocket.send',
'bytes': None,
'text': 'something'
})
```
It will send an empty binary message instead of using the 'text', in uvicorn the 'text' is sent if bytes is None
In ASGI docs:
>"Exactly one of bytes or text must be non-None. One or both keys may be present, however."
https://asgi.readthedocs.io/en/latest/specs/www.html#send-send-event
Discussed in https://github.com/emmett-framework/granian/discussions/17