Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

5 anos atrás
5 anos atrás
5 anos atrás
5 anos atrás
5 anos atrás
5 anos atrás
5 anos atrás
5 anos atrás
5 anos atrás
5 anos atrás
5 anos atrás
5 anos atrás
5 anos atrás
5 anos atrás
5 anos atrás
5 anos atrás
5 anos atrás
5 anos atrás
5 anos atrás
5 anos atrás
5 anos atrás
5 anos atrás
5 anos atrás
5 anos atrás
5 anos atrás
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  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. from websockets.headers import (
  10. build_www_authenticate_basic,
  11. parse_authorization_basic,
  12. )
  13. rooms = collections.defaultdict(set)
  14. class PicoProtocol(websockets.WebSocketServerProtocol):
  15. def http_unauthorized(self, message, realm='pico'):
  16. return (
  17. http.HTTPStatus.UNAUTHORIZED,
  18. [("WWW-Authenticate", build_www_authenticate_basic(realm))],
  19. message.encode(),
  20. )
  21. def serve_file(self, path):
  22. document = Path(__file__, '..', Path(path).name).resolve()
  23. if not document.is_file():
  24. document = Path(__file__, '..', 'pico.html').resolve()
  25. content_type = 'text/html; charset=utf-8'
  26. elif path.endswith('.js'):
  27. content_type = 'application/javascript; charset=utf-8'
  28. elif path.endswith('.css'):
  29. content_type = 'text/css; charset=utf-8'
  30. else:
  31. content_type = 'text/plain; charset=utf-8'
  32. return (
  33. http.HTTPStatus.OK,
  34. [('Content-Type', content_type)],
  35. document.read_bytes(),
  36. )
  37. async def process_request(self, path, request_headers):
  38. if request_headers.get('Upgrade') != 'websocket':
  39. return self.serve_file(path)
  40. try:
  41. authorization = request_headers['Authorization']
  42. self.username, password = parse_authorization_basic(authorization)
  43. except Exception as error:
  44. return self.http_unauthorized('No username found')
  45. return await super().process_request(path, request_headers)
  46. def get_usernames(sockets):
  47. return sorted(ws.username for ws in sockets)
  48. async def send_json_many(targets, **data):
  49. for websocket in list(targets):
  50. await send_json(websocket, **data)
  51. async def send_json(websocket, **data):
  52. try:
  53. await websocket.send(json.dumps(data))
  54. except websockets.exceptions.ConnectionClosed:
  55. pass
  56. async def recv_json(websocket):
  57. try:
  58. return json.loads(await websocket.recv())
  59. except websockets.exceptions.ConnectionClosed:
  60. return {'action': 'logout'}
  61. except json.decoder.JSONDecodeError:
  62. return {}
  63. async def core(ws, path, server_name):
  64. sockets = rooms[path]
  65. while True:
  66. if ws in sockets:
  67. data = await recv_json(ws)
  68. else:
  69. data = {'action': 'login'}
  70. ts = datetime.datetime.now().isoformat()
  71. reply = functools.partial(send_json, websocket=ws, ts=ts)
  72. error = functools.partial(reply, kind='error')
  73. broadcast = functools.partial(send_json_many, targets=set(sockets), ts=ts)
  74. if 'action' not in data:
  75. await error(info='Message without action is invalid')
  76. elif data['action'] == 'login':
  77. if not ws.username:
  78. await error(info='Username not allowed')
  79. break
  80. if ws.username in get_usernames(sockets):
  81. await error(info='Username taken')
  82. break
  83. sockets.add(ws)
  84. online = get_usernames(sockets)
  85. await reply(kind='update', users=online, info=f'Welcome to {path}', username=ws.username)
  86. await broadcast(kind='update', users=online, info=f'{ws.username} joined')
  87. elif data['action'] == 'post':
  88. text = data['text']
  89. if not text:
  90. continue
  91. await broadcast(kind='post', source=ws.username, text=text)
  92. elif data['action'] == 'logout':
  93. sockets.discard(ws)
  94. await broadcast(kind='update', users=get_usernames(sockets), info=f'{ws.username} left')
  95. break
  96. async def start_server(host, port, server_name):
  97. bound_core = functools.partial(core, server_name=server_name)
  98. return await websockets.serve(bound_core, host, port, create_protocol=PicoProtocol)
  99. if __name__ == '__main__':
  100. host, port = 'localhost', 9753
  101. loop = asyncio.get_event_loop()
  102. loop.run_until_complete(start_server(host, port, 'PicoChat'))
  103. print(f'Running on {host}:{port}')
  104. loop.run_forever()