您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

107 行
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()