add basic gateway logic

This commit is contained in:
Luna Mendes 2018-06-19 04:06:01 -03:00
parent c78e4c5f76
commit 32b9698ea7
7 changed files with 150 additions and 5 deletions

View File

@ -9,6 +9,7 @@ itsdangerous = "==0.24"
asyncpg = "==0.16.0" asyncpg = "==0.16.0"
websockets = "==5.0.1" websockets = "==5.0.1"
Quart = "==0.6.0" Quart = "==0.6.0"
Earl-ETF = "==2.1.2"
[dev-packages] [dev-packages]

10
Pipfile.lock generated
View File

@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "e37a82fc53dadfc4b8ebbded3bb043d686fa5c2cde07b11589430586e236386b" "sha256": "f4558d5a01c7a8954d1b4f60042c3599189df0e720c235c89bb41facb9d704ab"
}, },
"host-environment-markers": { "host-environment-markers": {
"implementation_name": "cpython", "implementation_name": "cpython",
@ -9,9 +9,9 @@
"os_name": "posix", "os_name": "posix",
"platform_machine": "x86_64", "platform_machine": "x86_64",
"platform_python_implementation": "CPython", "platform_python_implementation": "CPython",
"platform_release": "4.16.13-2-ARCH", "platform_release": "4.17.2-1-ARCH",
"platform_system": "Linux", "platform_system": "Linux",
"platform_version": "#1 SMP PREEMPT Fri Jun 1 18:46:11 UTC 2018", "platform_version": "#1 SMP PREEMPT Sat Jun 16 11:08:59 UTC 2018",
"python_full_version": "3.6.5", "python_full_version": "3.6.5",
"python_version": "3.6", "python_version": "3.6",
"sys_platform": "linux" "sys_platform": "linux"
@ -53,6 +53,10 @@
"hashes": [], "hashes": [],
"version": "==6.7" "version": "==6.7"
}, },
"earl-etf": {
"hashes": [],
"version": "==2.1.2"
},
"h11": { "h11": {
"hashes": [], "hashes": [],
"version": "==0.7.0" "version": "==0.7.0"

View File

@ -0,0 +1,17 @@
from ..errors import WebsocketClose
class UnknownOPCode(WebsocketClose):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# hacky solution to
# decrease code repetition
self.args = [4001, self.args[0]]
class DecodeError(WebsocketClose):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.args = [4002, self.args[0]]

View File

@ -1,4 +1,5 @@
import urllib.parse import urllib.parse
from .websocket import GatewayWebsocket
async def websocket_handler(ws, url): async def websocket_handler(ws, url):
@ -26,5 +27,6 @@ async def websocket_handler(ws, url):
if gw_compress and gw_compress not in ('zlib-stream',): if gw_compress and gw_compress not in ('zlib-stream',):
return await ws.close(1000, 'Invalid gateway compress') return await ws.close(1000, 'Invalid gateway compress')
await ws.close(code=1000, reason='ass') gws = GatewayWebsocket(ws, v=gw_version,
return encoding=gw_encoding, compress=gw_compress)
await gws.run()

View File

@ -0,0 +1,14 @@
class OP:
"""Gateway OP codes."""
DISPATCH = 0
HEARTBEAT = 1
IDENTIFY = 2
STATUS_UPDATE = 3
VOICE_UPDATE = 4
VOICE_PING = 5
RESUME = 6
RECONNECT = 7
REQ_GUILD_MEMBERS = 8
INVALID_SESSION = 9
HELLO = 10
HEARTBEAT_ACK = 11

View File

@ -0,0 +1,7 @@
class GatewayState:
"""Main websocket state.
Used to store all information tied to the websocket's session.
"""
def __init__(self, **kwargs):
pass

View File

@ -0,0 +1,100 @@
import json
import earl
from ..errors import WebsocketClose
from .errors import DecodeError, UnknownOPCode
from .opcodes import OP
def encode_json(payload) -> str:
return json.dumps(payload)
def decode_json(data: str):
return json.loads(data)
def encode_etf(payload) -> str:
return earl.pack(payload)
def decode_etf(data):
return earl.unpack(data)
class GatewayWebsocket:
"""Main gateway websocket logic."""
def __init__(self, ws, **kwargs):
self.ws = ws
self.version = kwargs.get('v', 6)
self.encoding = kwargs.get('encoding', 'json')
self.compress = kwargs.get('compress', None)
self.set_encoders()
def set_encoders(self):
encoding = self.encoding
encodings = {
'json': (encode_json, decode_json),
'etf': (encode_etf, decode_etf),
}
self.encoder, self.decoder = encodings[encoding]
async def send(self, payload):
encoded = self.encoder(payload)
# TODO: compression
await self.ws.send(encoded)
async def send_hello(self):
"""Send the OP 10 Hello"""
await self.send({
'op': OP.HELLO,
'd': {
'heartbeat_interval': 45000,
'_trace': [
'despacito'
],
}
})
async def handle_0(self, payload):
pass
async def process_message(self, payload):
"""Process a single message coming in from the client."""
try:
op_code = payload['op']
except KeyError:
raise UnknownOPCode('No OP code')
try:
handler = getattr(self, f'handle_{op_code}')
except AttributeError:
raise UnknownOPCode('Bad OP code')
await handler(payload)
async def listen_messages(self):
"""Listen for messages coming in from the websocket."""
while True:
message = await self.ws.recv()
if len(message) > 4096:
raise DecodeError('Payload length exceeded')
payload = self.decoder(message)
await self.process_message(payload)
async def run(self):
"""Wrap listen_messages inside
a try/except block for WebsocketClose handling."""
try:
await self.send_hello()
await self.listen_messages()
except WebsocketClose as err:
await self.ws.close(code=err.code,
reason=err.reason)