webserver.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. #!/usr/bin/env python
  2. # WS server example that synchronizes state across clients
  3. import asyncio
  4. import functools
  5. import json
  6. import logging
  7. import os
  8. import random
  9. import websockets
  10. from http import HTTPStatus
  11. from option import OptionCode, Option
  12. from game import Game
  13. from player import ErrorCode, StateCode, Player
  14. HOST = os.environ.get("KEEZEN_HOST") or "localhost"
  15. PORT = os.environ.get("KEEZEN_PORT") or 6789
  16. logging.basicConfig()
  17. games = dict() # code -> game
  18. sockets = dict() # code -> [(player, websocket), ...]
  19. users = ["Anna", "Bob", "Cynthia", "Daan", "Esoirulthayro", "Frank", "Gerben", "Hanna", "Ida", "Joost", "Karin", "Loes", "Max", "Nina"]
  20. class DogEncoder(json.JSONEncoder):
  21. def default(self, obj):
  22. if hasattr(obj, '__dict__'):
  23. return obj.__dict__
  24. return json.JSONEncoder.default(self, obj)
  25. MIME_TYPES = {
  26. "html": "text/html",
  27. "js": "text/javascript",
  28. "css": "text/css"
  29. }
  30. async def process_request(sever_root, path, request_headers):
  31. """Serves a file when doing a GET request with a valid path."""
  32. if "Upgrade" in request_headers:
  33. return # Probably a WebSocket connection
  34. path = '/dog.html'
  35. response_headers = [
  36. ('Server', 'asyncio websocket server'),
  37. ('Connection', 'close'),
  38. ]
  39. # Derive full system path
  40. full_path = os.path.realpath(os.path.join(sever_root, path[1:]))
  41. # Validate the path
  42. if os.path.commonpath((sever_root, full_path)) != sever_root or \
  43. not os.path.exists(full_path) or not os.path.isfile(full_path):
  44. print("HTTP GET {} 404 NOT FOUND".format(path))
  45. return HTTPStatus.NOT_FOUND, [], b'404 NOT FOUND'
  46. # Guess file content type
  47. extension = full_path.split(".")[-1]
  48. mime_type = MIME_TYPES.get(extension, "application/octet-stream")
  49. response_headers.append(('Content-Type', mime_type))
  50. # Read the whole file into memory and send it out
  51. body = open(full_path, 'rb').read()
  52. response_headers.append(('Content-Length', str(len(body))))
  53. print("HTTP GET {} 200 OK".format(path))
  54. return HTTPStatus.OK, response_headers, body
  55. async def notify(player_sockets):
  56. if player_sockets:
  57. await asyncio.wait([websocket.send(json.dumps(player, cls=DogEncoder)) for (player, websocket) in player_sockets])
  58. async def handler(websocket, path):
  59. player = Player(name=users[0])
  60. player.options = [
  61. reply.state = StateCode.START
  62. reply.options = [
  63. Option(OptionCode.NEW_GAME, "Nieuw spel"),
  64. Option(OptionCode.JOIN_GAME, "Doe mee met een spel")]
  65. await notify([(reply, websocket)])
  66. async def handler(websocket, path):
  67. player = None
  68. game_code = 0
  69. game = None
  70. try:
  71. async for message in websocket:
  72. option = Option(**json.loads(message))
  73. if option.code == OptionCode.NEW_GAME:
  74. game_code = random.randint(1000, 9999);
  75. while game_code in games:
  76. game_code = random.randint(1000, 9999)
  77. game = Game()
  78. games[game_code] = game
  79. player = game.join_player(option.user_name)
  80. player.game_code = game_code
  81. sockets[game_code] = [(player, websocket)]
  82. await notify(sockets[game_code])
  83. elif option.code == OptionCode.JOIN_GAME:
  84. game_code = option.game_code
  85. game = games.get(game_code) if game_code is not None and type(game_code) is int and game_code > 0 else None
  86. if game is None:
  87. await notify_error(websocket, ErrorCode.UNKNOWN_CODE, f"onbekende code {game_code}", game_code=game_code)
  88. continue
  89. player = game.join_player(option.user_name, option.color)
  90. player.game_code = game_code
  91. sockets[game_code].append((player, websocket))
  92. await notify(sockets[game_code])
  93. elif game is None or player is None:
  94. await notify_error(websocket, ErrorCode.NO_GAME, f"Start eerst een nieuw spel of doe mee met een spel")
  95. elif option.code == OptionCode.CHANGE_NAME:
  96. player.name = option.user_name
  97. for p in game.players:
  98. p.set_others(game.players)
  99. await notify(sockets[game_code])
  100. else:
  101. game = games[game_code]
  102. player.set_error(None)
  103. if not player.check_option(option):
  104. await notify([(player, websocket)])
  105. continue
  106. game.play_option(player, option)
  107. await notify(sockets[game_code])
  108. finally:
  109. if player is not None:
  110. if game is not None:
  111. game.unjoin_player(player)
  112. if game_code > 0 and game_code in sockets:
  113. sockets[game_code].remove((player, websocket))
  114. if len(sockets[game_code]) > 0:
  115. await notify(sockets[game_code])
  116. else:
  117. del sockets[game_code]
  118. if game_code in games:
  119. del games[game_code]
  120. if __name__ == "__main__":
  121. random.seed()
  122. static_handler = functools.partial(process_request, os.path.join(os.getcwd(), 'ui'))
  123. start_server = websockets.serve(handler, HOST, PORT, process_request=static_handler)
  124. print(f"Running server at {HOST}:{PORT}")
  125. asyncio.get_event_loop().run_until_complete(start_server)
  126. asyncio.get_event_loop().run_forever()