Over 10 years we help companies reach their financial and branding goals. Engitech is a values-driven technology agency dedicated.

Gallery

Contacts

411 University St, Seattle, USA

engitech@oceanthemes.net

+1 -800-456-478-23

Programming

Writing a Custom HTTP Server from Scratch in Python

Creating an HTTP server from scratch in Python is a great way to understand how web servers handle client requests and responses. Instead of using Python’s built-in http.server module, we will build a basic HTTP server using sockets that can process requests, serve static files, and respond to different HTTP methods.

Step 1: Setting Up the Server Socket

The first step is to create a socket that listens for incoming connections. The socket will bind to a specific IP address and port, allowing clients (such as web browsers) to connect and send requests.

Implementation:

import socket

# Server Configuration
HOST = '127.0.0.1'  # Localhost
PORT = 8080         # Port to listen on

# Create a socket object
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# Bind the socket to the address and port
server_socket.bind((HOST, PORT))

# Listen for incoming connections
server_socket.listen(5)
print(f"Server is running on http://{HOST}:{PORT}")

# Accept and handle client connections
while True:
    client_socket, client_address = server_socket.accept()
    print(f"Connection from {client_address}")
    
    request_data = client_socket.recv(1024).decode()
    print("Request Data:\n", request_data)

    # Close the client connection
    client_socket.close()

Explanation:

  • A TCP socket is created using socket.AF_INET (IPv4) and socket.SOCK_STREAM (TCP).
  • The socket binds to 127.0.0.1:8080 and listens for connections.
  • When a client connects, it receives the HTTP request and prints it.

Run the script and open http://127.0.0.1:8080 in a browser. You will see the raw HTTP request printed in the terminal.

Step 2: Handling HTTP Requests

Now, we need to parse the HTTP request to extract the requested resource (e.g., /index.html) and respond accordingly.

Implementation:

import socket

# Server Configuration
HOST = '127.0.0.1'
PORT = 8080

server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind((HOST, PORT))
server_socket.listen(5)

print(f"Server is running on http://{HOST}:{PORT}")

while True:
    client_socket, client_address = server_socket.accept()
    
    request_data = client_socket.recv(1024).decode()
    request_lines = request_data.split("\n")
    
    if request_lines:
        request_line = request_lines[0]  # Example: "GET /index.html HTTP/1.1"
        print("Request:", request_line)
        
        # Extract the requested file
        request_path = request_line.split(" ")[1]  
        
        # Default file if no specific file is requested
        if request_path == "/":
            request_path = "/index.html"
        
        try:
            with open("www" + request_path, "r") as file:
                response_body = file.read()
                response_header = "HTTP/1.1 200 OK\nContent-Type: text/html\n\n"
        except FileNotFoundError:
            response_body = "<h1>404 Not Found</h1>"
            response_header = "HTTP/1.1 404 Not Found\nContent-Type: text/html\n\n"
        
        response = response_header + response_body
        client_socket.sendall(response.encode())

    client_socket.close()

Explanation:

  • The server extracts the requested file path from the HTTP request.
  • If the requested path is /, it defaults to /index.html.
  • The server attempts to read the requested file from a www directory.
  • If the file exists, it sends an HTTP 200 OK response with the file content.
  • If the file is missing, it sends an HTTP 404 Not Found response.

Testing the Server:

  1. Create a folder named www in the same directory as the script.
  2. Inside www, create a file named index.html with some sample content:
<!DOCTYPE html>
<html>
<head><title>My Custom HTTP Server</title></head>
<body>
    <h1>Welcome to My Custom HTTP Server!</h1>
</body>
</html>
  1. Start the server and open http://127.0.0.1:8080 in a browser.
  2. Try visiting http://127.0.0.1:8080/hello.html (which doesn’t exist) to trigger a 404 error.

Step 3: Handling Different HTTP Methods

A full-featured web server should handle different HTTP methods like GET and POST.

Enhancing the Server to Handle GET and POST Requests:

import socket

HOST = '127.0.0.1'
PORT = 8080

server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind((HOST, PORT))
server_socket.listen(5)

print(f"Server running at http://{HOST}:{PORT}")

while True:
    client_socket, client_address = server_socket.accept()
    
    request_data = client_socket.recv(1024).decode()
    request_lines = request_data.split("\n")
    
    if request_lines:
        request_line = request_lines[0]
        method, path, _ = request_line.split(" ")
        
        if method == "GET":
            if path == "/":
                path = "/index.html"
            try:
                with open("www" + path, "r") as file:
                    response_body = file.read()
                    response_header = "HTTP/1.1 200 OK\nContent-Type: text/html\n\n"
            except FileNotFoundError:
                response_body = "<h1>404 Not Found</h1>"
                response_header = "HTTP/1.1 404 Not Found\nContent-Type: text/html\n\n"
        
        elif method == "POST":
            response_body = "<h1>POST Request Received</h1>"
            response_header = "HTTP/1.1 200 OK\nContent-Type: text/html\n\n"
        
        else:
            response_body = "<h1>405 Method Not Allowed</h1>"
            response_header = "HTTP/1.1 405 Method Not Allowed\nContent-Type: text/html\n\n"

        response = response_header + response_body
        client_socket.sendall(response.encode())

    client_socket.close()

Explanation:

  • The server checks the HTTP method.
  • If it’s GET, it serves static files.
  • If it’s POST, it sends a simple response acknowledging the request.
  • If the method is unsupported, it returns a 405 Method Not Allowed response.

Testing the POST Request:

Use curl or a tool like Postman to send a POST request:

curl -X POST http://127.0.0.1:8080

This will return POST Request Received.

Step 4: Improving the Server with Multi-threading

A single-threaded server processes one request at a time, making it slow under heavy traffic. Using multi-threading, the server can handle multiple clients simultaneously.

Adding Multi-threading:

import socket
import threading

HOST = '127.0.0.1'
PORT = 8080

server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind((HOST, PORT))
server_socket.listen(5)

print(f"Server running at http://{HOST}:{PORT}")

def handle_client(client_socket):
    request_data = client_socket.recv(1024).decode()
    request_lines = request_data.split("\n")
    
    if request_lines:
        request_line = request_lines[0]
        method, path, _ = request_line.split(" ")

        response_body = f"<h1>Request Received: {method} {path}</h1>"
        response_header = "HTTP/1.1 200 OK\nContent-Type: text/html\n\n"
        
        response = response_header + response_body
        client_socket.sendall(response.encode())

    client_socket.close()

while True:
    client_socket, client_address = server_socket.accept()
    thread = threading.Thread(target=handle_client, args=(client_socket,))
    thread.start()

This allows the server to process multiple requests in parallel, improving performance.

Conclusion

We built a simple HTTP server from scratch using sockets in Python. It can:

  • Handle GET and POST requests.
  • Serve static HTML files.
  • Process multiple clients simultaneously with threading.

Leave a comment

Your email address will not be published. Required fields are marked *