Building a Simple HTTP Server in Python: A Step-by-Step Guide

Introduction to HTTP and HTTP Servers

The HyperText Transfer Protocol (HTTP) is the foundation of the World Wide Web. It enables communication between clients (e.g., web browsers) and servers to transfer data such as HTML pages, images, and other web resources. HTTP operates as a request-response protocol:

  1. The client sends an HTTP request specifying the resource or action required.
  2. The server processes this request and sends back an HTTP response, which includes the requested data or an error message.

HTTP defines methods like GET, POST, PUT, and DELETE, which dictate the purpose of a request:

  • GET: Retrieve data from the server.
  • POST: Send data to the server to create or update a resource.
  • PUT: Update or create a resource.
  • DELETE: Remove a resource.

What Are HTTP Servers?

An HTTP server is a software application that listens for incoming HTTP requests on a specific network address (IP address and port) and processes them according to the HTTP protocol. Examples of popular HTTP servers include Apache, Nginx, and Python’s built-in http.server.

However, in some cases, you might need a simple custom HTTP server—for example, during testing, prototyping, or building a lightweight application. That’s where building your own server comes in handy.

Features of Our HTTP Server

In this tutorial, we will build a custom HTTP server using Python. Our server will support the following features:

  • GET /echo/{string}: Returns the given string in the response body.
  • GET /user-agent: Reads and returns the User-Agent header from the request.
  • GET /files/{filename}: Serves files from a specified directory.
  • POST /files/{filename}: Creates a file with the provided content.

By building this server, you will gain a deeper understanding of how HTTP works at a low level and how servers handle requests and responses.

In this blog, we’ll explore how to create a simple HTTP server using Python’s socket module. This server will handle basic requests like GET and POST and demonstrate fundamental concepts of HTTP request handling.

Prerequisites

Before we dive into the code, ensure you have Python 3.8 or later installed. Familiarity with Python and basic networking concepts will be helpful.

Building a Simple HTTP Server in Python: A Step-by-Step Guide

In this tutorial, we’ll explore how to create a simple HTTP server using Python’s socket module. This server will handle basic requests like GET and POST and demonstrate fundamental concepts of HTTP request handling.


Prerequisites

Before we dive into the code, ensure you have Python 3.8 or later installed. Familiarity with Python and basic networking concepts will be helpful.


Features of Our HTTP Server

  1. GET /echo/{string}: Returns the given string in the response body.
  2. GET /user-agent: Reads and returns the User-Agent from the request headers.
  3. GET /files/{filename}: Serves files from a specified directory.
  4. POST /files/{filename}: Creates a file with the specified content.

Understanding the Code

1. Server Initialization

We define a MyServer class to encapsulate the server’s behavior. It initializes a socket and binds it to a host and port.

class MyServer:

    def __init__(self, host="localhost", port=4221, directory=None):

        self.HOST = host

        self.PORT = port

        self.directory = directory

        self.server_socket = socket.create_server((self.HOST, self.PORT), reuse_port=True)


The directory parameter is where files will be served from or stored.

2. Starting the Server

The start method listens for incoming connections and spawns a new thread to handle each request.

def start(self):

    print(f"Running server on {self.HOST}:{self.PORT}")

    while True:

        client_socket, client_address = self.server_socket.accept()

        thread = threading.Thread(target=self.handle_request, args=(client_socket, client_address))

        thread.start()

Threads allow the server to handle multiple requests concurrently.


3. Handling Requests

The handle_request method processes the incoming HTTP requests, parsing the method, URL, headers, and body.

def handle_request(self, client_socket, client_address): request_line = client_socket.recv(1024).decode() request_content = request_line.split('\r\n') headers = {} for i, content in enumerate(request_content): if i == 0: request_type, url, http_version = content.split(" ") elif i == len(request_content) - 1: body = content elif content and ':' in content: key, val = content.split(": ") headers[key] = val




4. Implementing Endpoints

Here’s how each endpoint is implemented:

/echo/{string}

This endpoint echoes the string back to the client.

def do_echo(self, url): string = url.replace('/echo/', "").split('/')[0] length = len(string) return f"HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: {length}\r\n\r\n{string}"


/user-agent

This endpoint returns the User-Agent from the request headers.

def get_user_agent(self, user_agent): length = len(user_agent) return f"HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: {length}\r\n\r\n{user_agent}"

/files/{filename} (GET)

This endpoint serves a file from the specified directory.

def get_files(self, url): filepath = path.join(self.directory, url.split('/')[2]) if path.exists(filepath): with open(filepath) as file: content = file.read() return f"HTTP/1.1 200 OK\r\nContent-Type: application/octet-stream\r\nContent-Length: {len(content)}\r\n\r\n{content}" else: return "HTTP/1.1 404 Not Found\r\n\r\n"


/files/{filename} (POST)

This endpoint creates a file with the specified content.

def create_files(self, url, content): with open(path.join(self.directory, url.split('/')[2]), 'w') as write_file: write_file.write(content) return "HTTP/1.1 201 Created\r\n\r\n"


5. Running the Server

The main block sets up the server and starts it.

if __name__ == "__main__": parser = argparse.ArgumentParser(description='This program runs a small server with limited functionalities') parser.add_argument('-d', '--directory') args = parser.parse_args() server = MyServer(directory=args.directory) server.start()


You can run the server with:
`python server.py --directory /path/to/serve`



Testing the Server

Using curl

  • Test /echo:

curl -i http://localhost:4221/echo/hello

Response:

HTTP/1.1 200 OK Content-Type: text/plain Content-Length: 5 hello

  • Test /user-agent:
curl -i -A "MyCustomAgent" http://localhost:4221/user-agent
Response:
HTTP/1.1 200 OK Content-Type: text/plain Content-Length: 13 MyCustomAgent

  • Test /files (GET and POST):

curl -i -X POST --data "Hello, World!" http://localhost:4221/files/hello.txt curl -i http://localhost:4221/files/hello.txt


Conclusion

This simple HTTP server demonstrates how to handle requests, parse headers,

and serve or manipulate files. You can extend this project to support

additional HTTP methods, enhance security, or implement more complex features.




GitHub Repository

If you’d like to access the full code or contribute to the project, you can find the repository on GitHub:

https://github.com/zudheer/simple-http-server/

Comments

Popular posts from this blog

From Mistakes to Mastery: How SSH Config Rescued Me from Command Catastrophes in Production