Building a Simple HTTP Server using Python

Building an HTTP server in Python might seem intimidating at first, but it’s actually pretty easy with the help of some built-in modules like `http.server`. In this tutorial, we’ll show you how to create a simple web server that can handle GET and POST requests using Python.

To start: let’s get our environment set up. You’ll need to have Python installed on your machine, which is pretty much a given if you’re reading this tutorial. If not, well…you might want to consider getting out more often!

Next, open up your favorite text editor (or IDE) and create a new file called `server.py`. This will be the main script that we’ll use to run our server.

Now let’s get down to business. We’re going to start by importing the necessary modules:

# This script imports the necessary modules for our server to run.
import http.server # This module allows us to create a basic HTTP server.
from urllib.parse import urlparse, parse_qs # This module allows us to parse URLs and query strings.

# This is a comment, it is used to explain the purpose of the code segment below.
# In this case, we are creating a new file called `server.py` to serve as our main script.
# We will use this file to run our server.
# This is a good practice to keep our code organized and easy to maintain.

# This is a comment, it is used to explain the purpose of the code segment below.
# Here, we are importing the necessary modules for our server to run.
# The `http.server` module allows us to create a basic HTTP server.
# The `urllib.parse` module allows us to parse URLs and query strings.
# We use the `from` keyword to specify which specific functions or classes we want to import from a module.

The `http.server` module provides us with all of the building blocks we need for creating an HTTP server in Python. The `urlparse` and `parse_qs` modules are used to help us handle URLs and query strings respectively.

Next, let’s define our request handler class:

# Import necessary modules
import http.server # Importing the http.server module to create an HTTP server
from urllib.parse import urlparse, parse_qs # Importing the urlparse and parse_qs modules to handle URLs and query strings respectively

# Define request handler class
class MyRequestHandler(http.server.BaseHTTPRequestHandler):
    # Define function to handle GET requests
    def do_GET(self):
        try:
            # Parse the query string from the URL
            query = parse_qs(urlparse(self.path).query)
            # Check if 'name' key is present in the query string
            if 'name' in query['q']:
                # Get the value of 'name' key
                name = query['q'][0]
                # Create a message with the name
                message = f"Hello, {name}!"
            else:
                # If 'name' key is not present, display a default message
                message = "Welcome to my web server!"
        except Exception as e:
            # If there is an error, send a 400 error response
            self.send_error(400)
            return
        
        # Send a 200 success response
        self.send_response(200)
        # Set the content type header
        self.send_header('Content-type', 'text/plain')
        # End the headers
        self.end_headers()
        # Write the message to the response body
        self.wfile.write(bytes(message, "utf-8"))
    
    # Define function to handle POST requests
    def do_POST(self):
        # Get the content length from the request headers
        content_length = int(self.headers['Content-Length'])
        # Read the request body and decode it
        body = self.rfile.read(content_length).decode()
        
        try:
            # Split the body into name and message
            name, message = map(str.strip, body.split('&'))
            # Check if 'name' and 'message' keys are present
            if 'name' in name and 'message' in message:
                # Get the values of 'name' and 'message' keys
                name, message = name[len('name='):], message[len('message='):]
                # Send a 201 success response
                self.send_response(201)
                # Set the content type header
                self.send_header('Content-type', 'text/plain')
                # End the headers
                self.end_headers()
                # Write a thank you message to the response body
                self.wfile.write(bytes("Thank you for your feedback!", "utf-8"))
            else:
                # If 'name' or 'message' keys are missing, raise a ValueError
                raise ValueError
        except Exception as e:
            # If there is an error, send a 400 error response
            self.send_error(400)
            return

In this example, we’re handling GET and POST requests differently. For GET requests, we’re parsing the query string to extract a name (if provided). If no name is provided, we default to a generic welcome message. We then send back an HTTP response with the appropriate headers and body.

For POST requests, we’re reading in the request body and parsing it into two variables: `name` and `message`. We check that both of these are present before sending back an HTTP response with a thank you message.

Now let’s add some code to our main script to start up our server:

# Importing necessary modules
import http.server # Importing the http.server module to handle HTTP requests
import socketserver # Importing the socketserver module to create a TCP server
import urllib.parse # Importing the urllib.parse module to parse URL strings

# Defining a custom request handler class
class MyRequestHandler(http.server.BaseHTTPRequestHandler): # Creating a class that inherits from the BaseHTTPRequestHandler class in the http.server module
    def do_POST(self): # Defining a method to handle POST requests
        content_length = int(self.headers['Content-Length']) # Getting the length of the request body from the Content-Length header
        body = self.rfile.read(content_length) # Reading the request body
        parsed_body = urllib.parse.parse_qs(body) # Parsing the request body into a dictionary
        name = parsed_body[b'name'][0] # Getting the value of the 'name' key from the parsed body
        message = parsed_body[b'message'][0] # Getting the value of the 'message' key from the parsed body
        if name and message: # Checking if both name and message are present
            self.send_response(200) # Sending a 200 OK response
            self.send_header('Content-type', 'text/html') # Setting the content type header to 'text/html'
            self.end_headers() # Ending the headers
            self.wfile.write(b'<html><body><h1>Thank you for your message, ' + name + b'!</h1></body></html>') # Sending a thank you message with the name included
        else: # If either name or message is missing
            self.send_response(400) # Sending a 400 Bad Request response
            self.send_header('Content-type', 'text/html') # Setting the content type header to 'text/html'
            self.end_headers() # Ending the headers
            self.wfile.write(b'<html><body><h1>Missing name or message.</h1></body></html>') # Sending an error message

# Starting up the server
if __name__ == '__main__': # Checking if the script is being run directly
    Handler = MyRequestHandler # Assigning our custom request handler class to the Handler variable
    http.server.test(Handler, ('', 80)) # Starting a test server on port 80 with our custom request handler
    print("Server running at http://localhost:80/") # Printing a message to indicate that the server is running at http://localhost:80/

This code starts up our server on port 80 (the default HTTP port) and prints out a message to let us know that it’s running.

You now have your very own web server in Python, complete with GET and POST handling. It might not be the most sophisticated thing ever created, but hey…it works!

However, because http.server has certain limitations , you should remain cautious when using it to create a custom web framework. For example:
– The `BaseHTTPRequestHandler` class is designed for simple HTTP servers and doesn’t provide much flexibility or control over the server behavior. If you need more advanced features like session management, caching, or authentication, then you should consider using a full-fledged web framework like Flask or Django instead.
– The `test()` function is used to start up the server in test mode, which means that it will only run for one request and then exit immediately afterwards. If you need to keep your server running continuously, then you should use a different method (like starting the server with a separate process or daemon).

SICORPS