You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

преди 5 години
преди 5 години
преди 5 години
преди 5 години
преди 5 години
преди 5 години
преди 5 години
преди 5 години
преди 5 години
преди 5 години
преди 5 години
преди 5 години
преди 5 години
преди 5 години
преди 5 години
преди 5 години
преди 5 години
преди 5 години
преди 5 години
преди 5 години
преди 5 години
преди 5 години
преди 5 години
преди 5 години
преди 5 години
преди 5 години
преди 5 години
преди 5 години
преди 5 години
преди 5 години
преди 4 години
преди 5 години
преди 5 години
преди 5 години
преди 5 години
преди 5 години
преди 5 години
преди 5 години
преди 5 години
преди 5 години
преди 5 години
преди 5 години
преди 5 години
преди 5 години
преди 5 години
преди 5 години
преди 5 години
преди 5 години
преди 5 години
преди 5 години
преди 5 години
преди 5 години
преди 5 години
преди 5 години
преди 5 години
преди 5 години
преди 5 години
преди 5 години
преди 4 години
преди 5 години
преди 5 години
преди 5 години
преди 5 години
преди 5 години
преди 5 години
преди 5 години
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. import asyncio
  2. import collections
  3. import datetime
  4. import http
  5. import json
  6. import uuid
  7. from functools import partial
  8. from pathlib import Path
  9. from urllib.parse import urlsplit, urlunsplit
  10. import websockets
  11. rooms = collections.defaultdict(dict)
  12. def ignore_querystring(path):
  13. return urlunsplit(urlsplit(path)[:3] + ('', ''))
  14. class PicoProtocol(websockets.WebSocketServerProtocol):
  15. def serve_file(self, path):
  16. home = Path(__file__).parent
  17. document = home.joinpath(path.lstrip('/'))
  18. if not document.is_file():
  19. document = home.joinpath('pico.html').resolve()
  20. if document.suffix == '.html':
  21. content_type = 'text/html; charset=utf-8'
  22. elif document.suffix == '.js':
  23. content_type = 'application/javascript; charset=utf-8'
  24. elif document.suffix == '.css':
  25. content_type = 'text/css; charset=utf-8'
  26. elif document.suffix == '.svg':
  27. content_type = 'image/svg+xml; charset=utf-8'
  28. else:
  29. content_type = 'text/plain; charset=utf-8'
  30. return (
  31. http.HTTPStatus.OK,
  32. [('Content-Type', content_type)],
  33. document.read_bytes(),
  34. )
  35. def redirect_to(self, path):
  36. return (
  37. http.HTTPStatus.FOUND,
  38. [('Location', path)],
  39. b'',
  40. )
  41. async def process_request(self, path, request_headers):
  42. if path == '/':
  43. random_path = f'{uuid.uuid4()}'.replace('-', '')[:8]
  44. return self.redirect_to(random_path)
  45. elif path.lower() != path:
  46. return self.redirect_to(path.lower()[1:])
  47. if request_headers.get('Upgrade') != 'websocket':
  48. return self.serve_file(path)
  49. return await super().process_request(path, request_headers)
  50. async def send_json_many(targets, **data):
  51. for websocket in list(targets):
  52. await send_json(websocket, **data)
  53. async def send_json(websocket, **data):
  54. try:
  55. await websocket.send(json.dumps(data))
  56. except websockets.exceptions.ConnectionClosed:
  57. pass
  58. async def recv_json(websocket):
  59. try:
  60. return json.loads(await websocket.recv())
  61. except websockets.exceptions.ConnectionClosed:
  62. return {'kind': 'logout'}
  63. except json.decoder.JSONDecodeError:
  64. return {}
  65. async def handle(ws, path, server_name):
  66. room = rooms[ignore_querystring(path)]
  67. usernames = room.keys()
  68. sockets = room.values()
  69. username = None
  70. while True:
  71. data = await recv_json(ws)
  72. ts = datetime.datetime.now().isoformat() + 'Z'
  73. emit = partial(send_json_many, kind=data['kind'], ts=ts)
  74. broadcast = partial(emit, targets=sockets)
  75. reply = partial(emit, targets=[ws])
  76. error = partial(reply, kind='error')
  77. if 'kind' not in data:
  78. await error(value='Message without kind is invalid')
  79. elif data['kind'] == 'login':
  80. username = data['value']
  81. if not username:
  82. await error(value='Username not allowed')
  83. break
  84. if username in usernames:
  85. await error(value='Username taken')
  86. break
  87. room[username] = ws
  88. online = list(usernames)
  89. await reply(kind='state', username=username)
  90. await broadcast(kind='state', online=online)
  91. elif username not in room:
  92. await error(value='Login required')
  93. break
  94. elif data['kind'] == 'logout':
  95. del room[username]
  96. online = list(usernames)
  97. await broadcast(kind='state', online=online)
  98. break
  99. else:
  100. value = data.get('value')
  101. if 'target' in data:
  102. targets = {v for k, v in room.items() if k == data['target']}
  103. await broadcast(source=username, value=value, targets=targets)
  104. else:
  105. await broadcast(source=username, value=value)
  106. async def start_server(host, port, server_name):
  107. bound_handle = partial(handle, server_name=server_name)
  108. bound_serve = partial(websockets.serve, create_protocol=PicoProtocol)
  109. return await bound_serve(bound_handle, host, port)
  110. if __name__ == '__main__':
  111. host, port = 'localhost', 9753
  112. loop = asyncio.get_event_loop()
  113. loop.run_until_complete(start_server(host, port, 'PicoChat'))
  114. print(f'Running on {host}:{port}')
  115. loop.run_forever()