123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191 |
- ## ---- imports ----
- ## importing libraries required for the WS server;
- ## the only dependency you have to install manually
- ## is 'websocket' module (you can probably do that
- ## by running 'pip install websockets' in terminal)
- ## Note: if you are using hyperbola like me,
- ## you won't find python-pip in repositories
- ## as it is not considered free software.
- ## But you can use pacman to install python packages
- ## instead: for this project you can simply run
- ## $ doas pacman -Sy python-websockets
- ## to install the websockets package
- import websockets
- import asyncio
- import codecs
- import time
- import json
- import sys
- ## ---- reading settings from every possible place ----
- ## trying to read the config file (otherwise setting
- ## everything to default)
- try:
- ## trying to read config file to get settings
- settings = codecs.open("config.json", "r", encoding = "UTF-8").read()
- except:
- ## using defaults if there is no file or it is
- ## unaccessible for whatever reason
- settings = {
- "port": 6392,
- "visible_message_history_length": 100,
- }
- ## also reading arguments given from the cli (argv)
- ## and overwriting settings from the file (or defauts)
- possible_arguments_list_shorts = {
- "p": "port",
- "h": "visible_message_history_length",
- }
- possible_arguments_list_longs = {
- "port": "port",
- "history-length": "visible_message_history_length",
- }
- current_arg = ""
- for arg in sys.argv[1:]:
- if (arg == "--help"):
- print( codecs.open("src/help.txt", "r", encoding = "UTF-8").read() )
- sys.exit(0)
- elif (arg == "--version"):
- print("Version: 0.1 (developer_preview)")
- sys.exit(0)
-
- elif arg[:2] == "--":
- if arg[2:] in possible_arguments_list_longs:
- current_arg = possible_arguments_list_longs[arg[2:]]
- else:
- print("[ERROR] Unrecognized argument: {}\nFor usage info and possible options see '{} --help'".format(arg, sys.argv[0]))
- sys.exit(1)
- elif arg[:1] == "-":
- if arg[1:] in possible_arguments_list_shorts:
- current_arg = possible_arguments_list_shorts[arg[1:]]
- else:
- print("[ERROR] Unrecognized argument: {}\nFor usage info and possible options see '{} --help'".format(arg, sys.argv[0]))
- sys.exit(1)
- else:
- if current_arg != "":
- settings[current_arg] = arg
- current_arg = ""
- else:
- print("[ERROR] Positional argument '{}' detected, but none expected\nFor usage info and possible options see '{} --help'".format(arg, sys.argv[0]))
- sys.exit(1)
- ## certain settings values have to be converted
- ## into proper data types; doing it there
- settings["visible_message_history_length"] = int(settings["visible_message_history_length"])
- ## ---- defining useful server functions ----
- ## place for future code
- ## ---- actual server code ----
- ## sent messages array
- messages = []
- ## defining client handler for websockets
- async def client_handler(websocket, requested_path):
- ## client tells us if it is
- ## a listener or a sender
- responce = await websocket.recv()
- client_role = json.loads(responce)
- ## listener code
- if client_role == "listener_basic":
- ## remembering last message ID to send only new messages
- last_message_id = len(messages) - settings["visible_message_history_length"]
- ## anti infinite loop patch
- if last_message_id < 0:
- last_message_id = 0
- ## using "try" for safety
- try:
- ## always try to send new messages
- while True:
- new_messages_amount = ((len(messages)) - last_message_id)
-
- if new_messages_amount > 0:
- last_message_id += new_messages_amount
- print("new_messages_amount = {}".format(new_messages_amount))
- if new_messages_amount == 1:
- print("pass 1")
- msg = messages[-1]
- await websocket.send( json.dumps(msg) )
- else:
- print("pass 2")
- msg = messages[-new_messages_amount:]
- for i in msg:
- print("pass 3")
- await websocket.send( json.dumps(i) )
- await asyncio.sleep(0.2)
- else:
- await asyncio.sleep(1)
- ## closing connection if anything goes wrong
- except Exception as e:
- print("[client_handler] Warning: got exception \"{}\", closing connection...".format(e))
- return 1
- ## sender code
- elif client_role == "sender_basic":
- try:
- ## always listening for messages
- while True:
- new_message = await websocket.recv()
- ## "sanity check"
- try:
- msg = json.loads(new_message)
- ## checking if all required objects are present
- if not ( ("data" in msg) and ("nickname" in msg["data"]) and ("content" in msg["data"]) ):
- print("[client_handler:sender_basic:sanity_check] Warning: message is not formatted properly, closing connection...")
- return 1
- except Exception as e:
- print("[client_handler:sender_basic:sanity_check] Warning: unexpected exception \"{}\", closing connection...".format(e))
- return 1
- ## add server timestamp to the message
- msg["timestamp_server"] = time.time()
- ## add message to global list
- messages.append(msg)
- ## closing connection on exceptions
- except Exception as e:
- print("[client_handler:sender_basic] Warning: unexpected exception \"{}\", closing connection...".format(e))
-
- ## getting rid of unknown client types
- else:
- print("[client_handler] Warning: client tried to connect as \"{}\", closing connection...".format(client_role))
- return 0
- ## starting websocket server
- websocket_server = websockets.serve(client_handler, "", settings["port"])
- asyncio.get_event_loop().run_until_complete(websocket_server)
- asyncio.get_event_loop().run_forever()
|