Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

107 lines
3.3KB

  1. import asyncio
  2. import collections
  3. import datetime
  4. import http
  5. import json
  6. import functools
  7. from pathlib import Path
  8. import websockets
  9. rooms = collections.defaultdict(dict)
  10. async def send_json_many(targets, **data):
  11. for websocket in list(targets):
  12. await send_json(websocket, **data)
  13. async def send_json(websocket, **data):
  14. try:
  15. await websocket.send(json.dumps(data))
  16. except websockets.exceptions.ConnectionClosed:
  17. pass
  18. async def recv_json(websocket):
  19. try:
  20. return json.loads(await websocket.recv())
  21. except websockets.exceptions.ConnectionClosed:
  22. return {'action': 'logout'}
  23. except json.decoder.JSONDecodeError:
  24. return {}
  25. async def core(websocket, path, server_name):
  26. sockets = rooms[path]
  27. while True:
  28. data = await recv_json(websocket)
  29. ts = datetime.datetime.now().isoformat()
  30. reply = functools.partial(send_json, websocket=websocket, ts=ts)
  31. error = functools.partial(reply, kind='error')
  32. broadcast = functools.partial(send_json_many, targets=sockets, ts=ts)
  33. if 'action' not in data:
  34. await error(info='Message without action is invalid')
  35. elif websocket not in sockets and data['action'] not in {'login', 'logout'}:
  36. await error(info='Not logged in')
  37. elif data['action'] == 'login':
  38. username = data['username']
  39. if not username:
  40. await error(info='Invalid username')
  41. elif username in sockets.values():
  42. await error(info='Username taken')
  43. else:
  44. sockets[websocket] = username
  45. await reply(kind='update', username=username)
  46. await broadcast(kind='update', users=list(sockets.values()), info=f'{username} joined')
  47. elif data['action'] == 'post':
  48. text = data['text']
  49. if not text:
  50. continue
  51. await broadcast(kind='post', source=sockets[websocket], text=text)
  52. elif data['action'] == 'logout':
  53. if websocket in sockets:
  54. username = sockets.pop(websocket)
  55. await broadcast(kind='update', users=list(sockets.values()), info=f'{username} left')
  56. break
  57. async def serve_html(path, request_headers):
  58. if request_headers.get('Upgrade') != 'websocket':
  59. document = Path(__file__, '..', Path(path).name).resolve()
  60. if not document.is_file():
  61. document = Path(__file__, '..', 'pico.html').resolve()
  62. content_type = 'text/html; charset=utf-8'
  63. elif path.endswith('.js'):
  64. content_type = 'application/javascript; charset=utf-8'
  65. elif path.endswith('.css'):
  66. content_type = 'text/css; charset=utf-8'
  67. else:
  68. content_type = 'text/plain; charset=utf-8'
  69. return (
  70. http.HTTPStatus.OK,
  71. [('Content-Type', content_type)],
  72. document.read_bytes(),
  73. )
  74. async def start_server(host, port, server_name):
  75. bound_core = functools.partial(core, server_name=server_name)
  76. return await websockets.serve(bound_core, host, port, process_request=serve_html)
  77. if __name__ == '__main__':
  78. host, port = 'localhost', 9753
  79. loop = asyncio.get_event_loop()
  80. loop.run_until_complete(start_server(host, port, 'PicoChat'))
  81. print(f'Running on {host}:{port}')
  82. loop.run_forever()