Permalink
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
CSE3300-PA1/single-server.py
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
147 lines (112 sloc)
4.41 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import re | |
import socket | |
import sys | |
# Constants | |
ADDR = "localhost" | |
PORT = 50009 | |
MAX_CONNS = 1 | |
# Enable print logging for debugging | |
_LOGGING = False | |
def printLog(message: str) -> None: | |
"""Print the message if logging is enabled | |
:param message: The message to print | |
:type message: str | |
""" | |
if _LOGGING: | |
print(message) | |
def find_matching_words(query: str) -> list: | |
"""Find matching partial strings in words from the wordlist.txt file based on the query | |
:param query: The query partial string to search for | |
:type query: str | |
:return: List of matching words | |
:rtype: list | |
""" | |
# Loop through every character and add a "\" to escape special characters | |
query = "".join(f"\\{character}" if character in "[](){}.*+^$|\\" else character for character in query) | |
# Replace ? with . to match any character and add .* at the start and end to match any characters before and after the query | |
pattern = f".*{query.replace("?", ".")}.*" | |
with open("wordlist.txt", "r") as file: | |
# Use regex to match the pattern with a word | |
matching_words = [ | |
word.strip() for word in file if re.match(pattern, word.strip()) | |
] | |
return matching_words | |
def handle_client(connection: socket.socket, clientAddress: tuple) -> None: | |
printLog(f"Connected by {clientAddress}") | |
encodedData = b"" | |
while True: | |
# Receive data in chunks of 1024 bytes | |
encodedDataChunk = connection.recv(1024) | |
if not encodedDataChunk: | |
break | |
encodedData += encodedDataChunk | |
# Check if the data received is incomplete | |
if len(encodedDataChunk) < 1024 and encodedDataChunk[-3:] != b"END": | |
printLog("Data received is incomplete, exiting...") | |
return | |
# Data send is complete once the END tag is received | |
if encodedDataChunk[-3:] == b"END": | |
break | |
try: | |
data = encodedData.decode() | |
except UnicodeDecodeError: | |
printLog("Data is not in unicode format") | |
# Status code 400 indicates a unicode error | |
connection.send("400:Data is not in unicode format:END".encode()) | |
connection.close() | |
return | |
printLog(f"Data Received: {data}") | |
if not data.startswith("200"): | |
printLog(f"Unexpected query: {data}") | |
# Status code 401 indicates an unexpected query | |
connection.send("401:Unexpected query:END".encode()) | |
connection.close() | |
return | |
# Extract the query from in between the Status Code and END tags | |
query = data.split(":")[1] | |
if not query: | |
printLog("Empty query") | |
# Status code 402 indicates an empty query | |
connection.send("402:Empty query:END".encode()) | |
connection.close() | |
return | |
print(f"Client Query: {query}") | |
# Check if the query contains only letters and symbols no numbers | |
if any(character.isnumeric() for character in query): | |
printLog("Invalid query") | |
# Status code 403 indicates an invalid query | |
connection.send("403:Invalid query:END".encode()) | |
connection.close() | |
return | |
# Find the matching words based on the query | |
matchingWords = find_matching_words(query) | |
wordCount = len(matchingWords) | |
if not matchingWords: | |
printLog("No matching words found") | |
# Create the response with the matching words, status code 100 indicates a successful response | |
response = f"100:{wordCount}:{','.join(matchingWords)}:END" | |
connection.send(response.encode()) | |
printLog(f"Sent response: {response}") | |
connection.close() | |
return | |
def run_server() -> None: | |
"""Run the server to listen for incoming connections and respond to queries from clients""" | |
try: | |
serverSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | |
serverSocket.bind((ADDR, PORT)) | |
serverSocket.listen(MAX_CONNS) | |
except socket.error as e: | |
print(f"Socket Error: {e}") | |
return | |
print( | |
f"Server started at address {ADDR} on port {PORT} with {MAX_CONNS} max connection(s)" | |
) | |
while True: | |
printLog("Waiting for connection...") | |
connection, clientAddress = serverSocket.accept() | |
# Handle the client connection in a separate function for easier implementation of multi-threading | |
handle_client(connection, clientAddress) | |
if __name__ == "__main__": | |
if len(sys.argv) > 1 and sys.argv[1] == "-debug": | |
_LOGGING = True | |
run_server() |