diff --git a/chatserver/server.py b/chatserver/server.py index dd6bb99..ca5a6d5 100644 --- a/chatserver/server.py +++ b/chatserver/server.py @@ -3,15 +3,31 @@ import select import uuid import pickle import os +import threading +from flask import Flask, request, jsonify +from flask_socketio import SocketIO, emit, join_room, leave_room + +# Flask app for REST API and WebSocket +app = Flask(__name__) +socketio = SocketIO(app) # Initialize Flask-SocketIO CHATPORT = 8181 BUFFER_SIZE = 1024 MAX_CONNECT_QUEUE = 10 DATA_FILE = 'server_data.pkl' -ADMIN_USERNAME = "admin" -ADMIN_PASSWORD = "adminpass" UUID_FILE = 'uuids.pkl' +# Global variables for chat server +socket_list = [] +client_info = {} +chat_rooms = {} +client_states = {} +uuid_dict = {} +message_history = {} # New: Store message history for each chat room +server_socket = None +quitFlag = True + +# Utility functions def save_data(client_info, chat_rooms, client_states): serializable_client_info = {info["user_id"]: info for sock, info in client_info.items()} serializable_chat_rooms = {info["user_id"]: chat_rooms[sock] for sock, info in client_info.items()} @@ -52,60 +68,133 @@ def get_client_uuid(client_socket, uuid_dict): return user_id -def main(): - global quitFlag - # set the flag variable to true for infinite loop - quitFlag = True - # create a socket object +# REST API Endpoints +@app.route('/send_message', methods=['POST']) +def send_message(): + """Send a message to a specific chat room.""" + data = request.json + username = data.get('username') + chat_room = data.get('chat_room', 'default') + message = data.get('message') + + if not username or not message: + return jsonify({"error": "Username and message are required"}), 400 + + # Store the message in the chat room's history + if chat_room not in message_history: + message_history[chat_room] = [] + message_history[chat_room].append(f"{username}: {message}") + + # Broadcast the message to all clients in the same chat room + send_message = f"{username} >> {message}".encode('utf-8') + for client_sock in socket_list: + if client_sock != server_socket and chat_rooms.get(client_sock) == chat_room: + client_sock.send(send_message) + + return jsonify({"status": "Message sent and saved in history"}), 200 + +@app.route('/create_chatroom', methods=['POST']) +def create_chatroom(): + """Create a new chat room.""" + data = request.json + chat_room = data.get('chat_room') + + if not chat_room: + return jsonify({"error": "Chat room name is required"}), 400 + + # Add logic to create a chat room (if needed) + return jsonify({"status": f"Chat room '{chat_room}' created"}), 200 + +@app.route('/get_messages', methods=['GET']) +def get_messages(): + """Retrieve messages for a specific user (not implemented here).""" + # You can implement a message queue or buffer for each user to store messages + return jsonify({"error": "Not implemented"}), 501 + +@app.route('/get_history', methods=['GET']) +def get_history(): + """Retrieve message history for a specific chat room.""" + chat_room = request.args.get('chat_room', 'default') + if chat_room not in message_history: + return jsonify({"error": f"No history found for chat room '{chat_room}'"}), 404 + + return jsonify({"chat_room": chat_room, "messages": message_history[chat_room]}), 200 + +# WebSocket event handlers +@socketio.on('connect') +def handle_connect(): + print("WebSocket client connected") + +@socketio.on('disconnect') +def handle_disconnect(): + print("WebSocket client disconnected") + +@socketio.on('join') +def handle_join(data): + username = data.get('username') + chat_room = data.get('chat_room', 'default') + join_room(chat_room) + emit('message', {'message': f"{username} has joined the chat room '{chat_room}'."}, room=chat_room) + +@socketio.on('leave') +def handle_leave(data): + username = data.get('username') + chat_room = data.get('chat_room', 'default') + leave_room(chat_room) + emit('message', {'message': f"{username} has left the chat room '{chat_room}'."}, room=chat_room) + +@socketio.on('send_message') +def handle_send_message(data): + username = data.get('username') + chat_room = data.get('chat_room', 'default') + message = data.get('message') + + # Store the message in the chat room's history + if chat_room not in message_history: + message_history[chat_room] = [] + message_history[chat_room].append(f"{username}: {message}") + + # Broadcast the message to the chat room + emit('message', {'username': username, 'message': message}, room=chat_room) + +# Chat server logic +def chat_server(): + global quitFlag, server_socket, socket_list, client_info, chat_rooms, client_states, uuid_dict + + # Create and configure the server socket server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - # set the socket options, SO_REUSEADDR allows to reuse the same port - # SO_KEEPALIVE enables the socket to send keepalive messages server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) - # bind the socket to the address and port server_socket.bind(('', CHATPORT)) - # listen for incoming connections server_socket.listen(MAX_CONNECT_QUEUE) - # set the server socket to non-blocking server_socket.setblocking(False) - # create a list to store the sockets, server and client - socket_list = [server_socket] - # load persistent data - client_info, chat_rooms, client_states = load_data() - uuid_dict = load_uuids() - # let the user know the server is listening on port CHATPORT. - print(f"Chatserver listening on port: {CHATPORT}") + socket_list.append(server_socket) + print(f"Chat server listening on port {CHATPORT}") - # infinite loop for the program while quitFlag: - # use the select function to monitor the sockets read_sockets, _, _ = select.select(socket_list, [], []) - # loop through the sockets for sock in read_sockets: - # if the socket is the server socket, accept the connection if sock == server_socket: + # Accept new client connections client_socket, client_address = server_socket.accept() client_socket.setblocking(False) socket_list.append(client_socket) - print("\nAccepted connection from new client.") - client_socket.send(b"\nWelcome to the chat server!\n") - client_socket.send(b"\nUse '%!' to exit, or '%?' for help.\n") - client_socket.send(b"\nType in a username: \n") + print(f"Accepted connection from {client_address}") + client_socket.send(b"Welcome to the chat server!\n") + client_socket.send(b"Type in a username: \n") client_states[client_socket] = "WAITING_FOR_USERNAME" - # Get or generate a unique user ID for the client user_id = get_client_uuid(client_socket, uuid_dict) client_info[client_socket] = {"user_id": user_id} - # otherwise the socket is a client socket. else: - # try to receive the message from the client + # Handle client messages try: message = sock.recv(BUFFER_SIZE).decode('utf-8') if message: state = client_states.get(sock, "NORMAL") if state == "WAITING_FOR_USERNAME": username = message.strip() - if username == ADMIN_USERNAME: + if username == "admin": sock.send(b"\nEnter admin password: \n") client_states[sock] = "WAITING_FOR_ADMIN_PASSWORD" client_info[sock]["username"] = username @@ -116,7 +205,7 @@ def main(): client_states[sock] = "NORMAL" elif state == "WAITING_FOR_ADMIN_PASSWORD": password = message.strip() - if password == ADMIN_PASSWORD: + if password == "adminpass": sock.send(b"\nAdmin authenticated successfully.\n") client_info[sock]["is_admin"] = True chat_rooms[sock] = "default" @@ -188,6 +277,13 @@ def main(): username = client_info[sock]["username"] chat_room = chat_rooms[sock] send_message = f"{username} >> {message}".encode('utf-8') + + # Store the message in the chat room's history + if chat_room not in message_history: + message_history[chat_room] = [] + message_history[chat_room].append(f"{username}: {message}") + + # Broadcast the message to all clients in the same chat room for client_sock in socket_list: if client_sock != server_socket and chat_rooms.get(client_sock) == chat_room: client_sock.send(send_message) @@ -219,9 +315,14 @@ def main(): del client_states[sock] print("\nClient has closed connection.") - # Save data before closing the server + # Save data before shutting down save_data(client_info, chat_rooms, client_states) server_socket.close() if __name__ == "__main__": - main() \ No newline at end of file + # Start the chat server in a separate thread + server_thread = threading.Thread(target=chat_server, daemon=True) + server_thread.start() + + # Start the Flask REST API and WebSocket server + socketio.run(app, host='0.0.0.0', port=5000) \ No newline at end of file diff --git a/chatserver/templates/index.html b/chatserver/templates/index.html new file mode 100644 index 0000000..6bb20fc --- /dev/null +++ b/chatserver/templates/index.html @@ -0,0 +1,74 @@ + + + + + + Web Chat Client + + + + +
+
+ + +
+ + + + \ No newline at end of file diff --git a/chatserver/webclient.py b/chatserver/webclient.py new file mode 100644 index 0000000..2a1856c --- /dev/null +++ b/chatserver/webclient.py @@ -0,0 +1,10 @@ +from flask import Flask, render_template + +app = Flask(__name__) + +@app.route('/') +def index(): + return render_template('index.html') # Serve the HTML client + +if __name__ == "__main__": + app.run(host='0.0.0.0', port=8080) # Run the web client on port 8080 \ No newline at end of file