Writing a Custom HTTP Server in Python

Loading

An HTTP server listens for client requests (usually from web browsers or API clients) and responds with appropriate data. While Python provides built-in web frameworks like Flask and Django, you can also create a custom HTTP server using Python’s socket module.

Why Build a Custom HTTP Server?
✔ Learn how HTTP servers work internally
✔ Handle specific requirements that existing frameworks don’t support
✔ Lightweight alternative to full-fledged frameworks


1. Understanding HTTP Requests and Responses

Structure of an HTTP Request

A basic HTTP request from a client (e.g., a web browser) consists of:

GET / HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0
Accept: text/html

Structure of an HTTP Response

A basic HTTP response consists of:

HTTP/1.1 200 OK
Content-Type: text/html

<html><body><h1>Hello, World!</h1></body></html>

The response must follow HTTP standards, including status codes and headers.


2. Creating a Basic HTTP Server with socket

A simple HTTP server that listens for incoming requests and responds with a basic HTML page.

import socket

HOST = 'localhost'
PORT = 8080

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

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

while True:
client_socket, addr = server_socket.accept()
print(f"Connected by {addr}")

request = client_socket.recv(1024).decode()
print(f"Request:\n{request}")

# Basic HTTP response
response = """\
HTTP/1.1 200 OK
Content-Type: text/html

<html><body><h1>Hello, World!</h1></body></html>
"""
client_socket.send(response.encode())
client_socket.close()

Listens on port 8080
Handles HTTP GET requests
Sends a simple HTML response


3. Handling Different HTTP Methods

Let’s enhance the server to handle GET and POST requests.

import socket

def handle_request(request):
headers = request.split("\r\n")
method, path, _ = headers[0].split()

if method == "GET":
response_body = "<h1>This is a GET response</h1>"
elif method == "POST":
response_body = "<h1>This is a POST response</h1>"
else:
response_body = "<h1>Method Not Allowed</h1>"

response = f"""\
HTTP/1.1 200 OK
Content-Type: text/html

{response_body}
"""
return response.encode()

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

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

while True:
client_socket, addr = server_socket.accept()
request = client_socket.recv(1024).decode()

response = handle_request(request)
client_socket.send(response)
client_socket.close()

Processes GET and POST methods
Returns different responses based on the request type


4. Multi-Threaded HTTP Server

To handle multiple client requests simultaneously, let’s use multi-threading.

import socket
import threading

def handle_client(client_socket):
request = client_socket.recv(1024).decode()
print(f"Request:\n{request}")

response = """\
HTTP/1.1 200 OK
Content-Type: text/html

<html><body><h1>Multi-Threaded Server</h1></body></html>
"""
client_socket.send(response.encode())
client_socket.close()

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

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

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

Handles multiple requests concurrently
Each client request is processed in a separate thread


5. Adding Routing for Custom Endpoints

A simple router to handle different URL paths.

import socket

def handle_request(request):
headers = request.split("\r\n")
method, path, _ = headers[0].split()

if path == "/":
response_body = "<h1>Welcome to the Homepage</h1>"
elif path == "/about":
response_body = "<h1>About Page</h1>"
else:
response_body = "<h1>404 - Page Not Found</h1>"

response = f"""\
HTTP/1.1 200 OK
Content-Type: text/html

{response_body}
"""
return response.encode()

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

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

while True:
client_socket, addr = server_socket.accept()
request = client_socket.recv(1024).decode()

response = handle_request(request)
client_socket.send(response)
client_socket.close()

Routes requests based on URL path
✔ Returns 404 for invalid pages


6. Using http.server Module

Python has a built-in HTTP server module that simplifies development.

from http.server import SimpleHTTPRequestHandler, HTTPServer

class MyHandler(SimpleHTTPRequestHandler):
def do_GET(self):
if self.path == "/":
self.send_response(200)
self.send_header("Content-type", "text/html")
self.end_headers()
self.wfile.write(b"<h1>Custom HTTP Server</h1>")
elif self.path == "/about":
self.send_response(200)
self.send_header("Content-type", "text/html")
self.end_headers()
self.wfile.write(b"<h1>About Page</h1>")
else:
self.send_response(404)
self.end_headers()
self.wfile.write(b"<h1>404 - Not Found</h1>")

server = HTTPServer(("localhost", 8080), MyHandler)
print("Server running on port 8080...")
server.serve_forever()

✔ Uses SimpleHTTPRequestHandler to handle requests.
✔ Implements custom GET request handling.


7. Summary

FeatureBasic Socket ServerMulti-Threaded Serverhttp.server Module
ComplexityLowModerateVery Low
Multi-ThreadingNoYesNo
Routing SupportManualManualBuilt-in
Best ForLearning, BasicsHandling multiple clientsQuick HTTP servers

Leave a Reply

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