In-Class Lab 3 (10 points)
Initial setup of your development environment using WSL and VSCode. This lab introduces you to basic Linux commands, Python execution, and packet capturing.
Upon the completion of all activities, have your instructor review your work and track your progress to receive full credit.
HTTP Server
Copy the following code into a file named http_server.py on your VM or WSL. Try the copy icon on the top-right of the code block. Fill in the blanks. Run it to start listening for HTTP requests on port 8080. You can stop the server with Ctrl+C.
Resources
- HTTP Message Format - Mozilla docs
- socket library - Python docs -> see Echo Server example
Starter Code
http_server.py
import socket
import time
class TCPServer:
def __init__(self, server_ip='127.0.0.1', server_port=8080):
print("Initializing TCP Server...")
self.server_ip = server_ip
self.server_port = server_port
self.listening = False
self.setup_socket()
self.resources = {
"/": "Welcome to the TCP Server!",
"/hello": "Hello, TCP Client!",
"/goodbye": "Goodbye from the TCP Server!",
"/joke": "Why do programmers prefer dark mode? Because light attracts bugs!"
}
def setup_socket(self) -> None:
try:
print(f"Creating TCP socket")
# TODO create a TCP listening socket
# TODO bind the socket
# TODO start listening
self.listening = True
# print(...)
except Exception as e:
print(f"Error setting up socket: {e}")
self.listening = False
print('NOT IMPLEMENTED')
def acceptConnection(self) -> None:
print('Accepting connections...')
# TODO accept a connection
# print(...)
# TODO store the request data somewhere
# TODO in a loop, receive data until the full request is obtained
# TODO use the connection to receive data
# TODO append the received data somewhere
# print(...)
# TODO once the request is received, parse it
# TODO build the response using build_http_response()
# print(...)
# TODO send the response using send_response()
# print(...)
# TODO close the connection
# print(...)
def parse_request(self, request: str) -> tuple[str | None, str | None]:
method = None
resource = None
# TODO parse the request string and extract the method and resource
return method, resource
def build_http_response(self, method: str, resource: str) -> str:
responseCode = 500
responseMsg = "Internal Server Error"
responseBody = "Sadness ensues. Something did not go as planned. All hope is lost."
# TODO parse the resource and create the corresponding message
# TODO 200 OK
# TODO 404 Not Found
# TODO 400 Bad Request
http_response = None
return http_response
def send_response(self, response) -> None:
print('NOT IMPLEMENTED')
# TODO send the response through the connection
def close_connection(self) -> None:
self.sock.close()
print("Listening socket closed.")
if __name__ == "__main__":
server = TCPServer()
server.listening = True
try:
while server.listening:
server.acceptConnection()
time.sleep(1)
except KeyboardInterrupt:
print("Shutting down server...")
server.listening = False
finally:
server.close_connection()Testing
- Use
curlto test your server:curl http://localhost:8080/hello- You should see the message “Hello, TCP Client!” in the terminal.
- Use
curl -vto see the full HTTP response. - Try other resources like
/,/goodbye, and/joke. - Try an invalid resource like
/invalidto see the 404 response.
Walkthrough
Create a listening socket and assign it to a class attribute.
# create a TCP listening socket
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# bind the socket
HOST = self.server_ip
PORT = self.server_port
self.sock.bind((HOST, PORT))
# start listening
self.sock.listen(1)
print(f"Listening on {self.server_ip}:{self.server_port}")Accept incoming connections in a loop and handle them.
print('Ready to accept a connection...')
# accept a connection
conn, addr = self.sock.accept()
print(f'Accepted connection from {addr}')
self.conn = conn
# store the request data somewhere
received = b''
data = b''
# in a loop, receive data until the full request is obtained
while not data.endswith(b'\r\n\r\n'):
# use the connection to receive data
data = conn.recv(1024)
# append the received data somewhere
received += data
print(f'Received data: {len(received)} bytes')
receivedStr = received.decode()
print(f'======= REQUEST =======\n{receivedStr}\n=======================')
# once the request is received, parse it
method, resource = self.parse_request(receivedStr)
print(f'Method: {method}, Resource: {resource}')
# build the response using build_http_response()
response = self.build_http_response(method, resource)
print(f'======= RESPONSE =======\n{response}\n=======================')
# send the response using send_response()
# print(...)
self.send_response(response)
# close the connection
self.conn.close()
self.conn = None
print(f'Connection closed: {addr}')
# print(...)Parse the HTTP request to extract the method and resource.
items = request.split()
method = items[0]
resource = items[1]Build the HTTP response based on the method and resource.
if method != "GET":
responseCode = 400
responseMsg = "BAD REQUEST"
responseBody = "Only GET method is supported."
elif not resource in self.resources:
responseCode = 404
responseMsg = "NOT FOUND"
else:
responseCode = 200
responseMsg = "OK"
responseBody = self.resources[resource]
responseBody += '\r\n'
header = f'Content-Length: {len(responseBody)}'
http_response = f'HTTP/1.1 {responseCode} {responseMsg}\r\n{header}\r\n\r\n{responseBody}'Send the HTTP response back to the client.
bytes = response.encode()
self.conn.sendall(bytes)
print(f'Sent: {len(bytes)} bytes')Finally, close the connection.
def close_connection(self) -> None:
if self.conn:
self.conn.close()
self.sock.close()
print("Listening socket closed.")