Building a Simple Web Application with Python
Tags: webdev, beginners, tutorial, python
Published: 2024-02-10 01:12:00

Python is widely used for web development because of its simplicity, versatility, readability, and strong community support. In this article, you will learn the following:

1. Creating a basic web server using the `http.server` Python module.
2. How to handle HTTP requests and responses.
3. How to serve static content.

Before you start, you should have a basic knowledge of Python language and web development.

Python has a module called [`http.server`](https://docs.python.org/3/library/http.server.html) that lets you create a server with minimal effort. You can initialize an HTTP server instance by specifying the server address and port. Additionally, you can define a custom request handler to process incoming requests. 

Let's begin by writing the code.

To start, open your preferred code editor and create a new folder. Within this folder, create two files: one for the server (`simple-web-server.py`) and another for static content (`index.html`). Now, you can paste the provided code into the appropriate files.

`# simple-web-server.py`

```


from http.server import BaseHTTPRequestHandler, HTTPServer

# Define the HTTP request handler class
class MyRequestHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.send_header('Content-type', 'text/html')
        self.end_headers()

        # Read the HTML content from the file
        with open('index.html', 'rb') as file:
            html_content = file.read()

        # Set the response body with the HTML content
        self.wfile.write(html_content)

    def do_POST(self):
        content_length_str = self.headers.get('Content-Length')
        if content_length_str is None:
            self.send_response(400)  # Bad request
            self.end_headers()
            self.wfile.write(b'Content-Length header is missing')
            return
        
        content_length = int(content_length_str)
        post_data = self.rfile.read(content_length)
        print("Received POST data:", post_data.decode('utf-8'))
        
        # Send a success response
        self.send_response(200)
        self.send_header('Content-type', 'text/plain')
        self.end_headers()
        self.wfile.write(b'POST request received successfully')

        return

    def do_PUT(self):
        content_length_str = self.headers.get('Content-Length')
        if content_length_str is None:
            content_length_str = '0'
        
        content_length = int(content_length_str)
        put_data = self.rfile.read(content_length)
        print("Received PUT data:", put_data.decode('utf-8'))

        # Send a success response
        self.send_response(200)
        self.send_header('Content-type', 'text/plain')
        self.end_headers()
        self.wfile.write(b'PUT request received successfully')

# Define the main function to start the server
def main():
    # Set the server address and port
    server_address = ('', 8080)

    # Create an instance of the HTTP server
    httpd = HTTPServer(server_address, MyRequestHandler)

    # Start the server
    print('Starting server...')
    httpd.serve_forever()

if __name__ == '__main__':
    main()
```

`# index.html`

```
<!DOCTYPE html>
<html>
<head>
    <title>Simple Web Server</title>
</head>
<body>
    <h1>Welcome to the Simple Web Server</h1>
    <p>This basic HTML page is served by a simple web server.</p>
</body>
</html>

```
Let's understand how the code functions:

`from http.server import BaseHTTPRequestHandler, HTTPServer`

The code starts by importing two modules: `BaseHTTPRequestHandler` and `HTTPServer` from the `http.server`. 

The purpose of importing `BaseHTTPRequestHandler` is to handle the incoming HTTP requests on the server. On the other hand, `HTTPServer` is used to create an instance of the server.

`class MyRequestHandler(BaseHTTPRequestHandler):`

Next, we have a class called `MyRequestHandler` that is created by inheriting from `BaseHTTPRequestHandler`. This custom request handler class allows us to handle different request methods like GET, POST, and so on.

```
 def do_GET(self):
        self.send_response(200)
        self.send_header('Content-type', 'text/html')
        self.end_headers()

        # Read the HTML content from the file
        with open('index.html', 'rb') as file:
            html_content = file.read()

        # Set the response body with the HTML content
        self.wfile.write(html_content)
```

Next, In the `MyRequestHandler` class, we define the function `do_GET` to handle `GET` requests. Firstly, we send an HTTP response with the status code `200 (OK)`. Then, we set the `content-type` header to `text/html` and end the response headers. Next, we open the file `index.html` in binary mode and read its content into the variable `html_content`. Finally, we send the `html_content` as the response body to the client.

To see the expected output, you can run the code and visit `http://127.0.0.1:8080/` in your web browser or use a tool like `curl`.


`# browser`


![serving static file](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9vjovsi6vvvdn5t12717.png)

`# using curl`




![serving static file](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i9fqbu2idc5v7vfidcqy.png)

_The above demonstrates the `get` request process_


```
   def do_POST(self):
        content_length_str = self.headers.get('Content-Length')
        if content_length_str is None:
            self.send_response(400)  # Bad request
            self.end_headers()
            self.wfile.write(b'Content-Length header is missing')
            return
        
        content_length = int(content_length_str)
        post_data = self.rfile.read(content_length)
        print("Received POST data:", post_data.decode('utf-8'))
        
        # Send a success response
        self.send_response(200)
        self.send_header('Content-type', 'text/plain')
        self.end_headers()
        self.wfile.write(b'POST request received successfully')
```
Here we define the `do_POST` method within the `MyRequestHandler` class to handle POST requests. Then, we retrieve the value of the `Content-Length` header from the HTTP request and store it in a variable called `content_length_str`. If the `Content-Length` header is missing, we send a `400 Bad Request` response with a message stating that the header is missing. After that, the code reads the `POST` data from the request body, using the content length specified in the header. It then decodes the received data from bytes to a UTF-8 encoded string and prints it to the console. Finally, we send a 200 OK response with a `text/plain` content type, along with a message indicating that the POST request was received successfully.
To test the `POST` request, we can use the command `curl -X POST -H "Content-Length: 0" http://localhost:8080`.

![POST request](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9uyomyl4x9z1hpotkzqz.png)

```
def do_PUT(self):
        content_length_str = self.headers.get('Content-Length')
        if content_length_str is None:
            content_length_str = '0'
        
        content_length = int(content_length_str)
        put_data = self.rfile.read(content_length)
        print("Received PUT data:", put_data.decode('utf-8'))

        # Send a success response
        self.send_response(200)
        self.send_header('Content-type', 'text/plain')
        self.end_headers()
        self.wfile.write(b'PUT request received successfully')

```
For `do_PUT` is similar to `do_POST`, but it handles PUT requests. To test for the `PUT` request we will use `curl -X PUT  http://localhost:8080`

![PUT request](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8e7b7xzxsdqtphcla98d.png)

```
# Define the main function to start the server
def main():
    # Set the server address and port
    server_address = ('', 8080)

    # Create an instance of the HTTP server
    httpd = HTTPServer(server_address, MyRequestHandler)

    # Start the server
    print('Starting server...')
    httpd.serve_forever()

if __name__ == '__main__':
    main()
```
The last part of the code defines the function that starts the server.

Conclusion:

Creating a basic web application with Python is an exciting adventure that highlights the language's strengths in web development. Throughout this process, we build a straightforward web server, manage HTTP requests and responses, and deliver static content. These tasks establish the groundwork for developing more advanced web applications.

References:

- [Python Documentation:](https://docs.python.org/3/library/http.server.html)

- [Mozilla Developer Network (MDN) - HTTP Overview:] (https://developer.mozilla.org/en-US/docs/Web/HTTP/Overview)