# Basic web server implementation using inbuilt python tools

In my previous article, I talked about implementing the HTTP protocol over a standard TCP socket. Today, I will be using few libraries available in python that implements HTTP and try to build something like Flask web framework.

## What is Flask Web Framework

Flask is a lightweight and versatile web framework for Python, designed to make building web applications simple and easy. It provides the tools and libraries needed to develop web applications quickly and efficiently, with minimal boilerplate code. Flask follows the WSGI (Web Server Gateway Interface) standard, making it compatible with a wide range of web servers. Its simplicity, flexibility, and extensive documentation have made it a popular choice among developers for creating web applications, APIs, and more.

Here’s an example of Flask code that we try to create using inbuilt libraries in Python.

```python
from flask import Flask, render_template
import os

PORT = os.getenv('PORT')

app = Flask(__name__)

@app.route('/', methods=["GET"])
def home():
    return render_template('index.html', context={
	    "PORT": PORT
    })

if __name__ == '__main__':
    app.run(port=PORT)
```

The `index.html` looks like this.

```html
<!-- File - index.py -->

<h1>This is an HTTP server running of Port: {PORT}</h1>
```

## HTTP Server class

Python provides basic functionalities to implement HTTP protocol under `http.server` module. We will using this as our base for our version of Flask.

```python
# File - myflask.py

import http.server as http_server

class MyFlask():
    def __init__(self, http_handler=http_server.SimpleHTTPRequestHandler) -> None:
        self.http_handler = http_handler

    def run(self, host="localhost", port=3000):
        httpd = http_server.HTTPServer((host, port), self.http_handler)
        print(f"Running server at {host}:{port}")
        httpd.serve_forever()
```

Now let’s create a server file that import as uses `MyFlask` class.

```python
# File - main.py

from myflask import MyFlask

app = MyFlask()

if __name__ == "__main__":
    app.run()
```

If you run main.py at this point, and open http://localhost:3000 you can see `index.html` getting rendered.

```bash
python3 main.py
```

![Basic implementation](https://cdn.hashnode.com/res/hashnode/image/upload/v1741074626818/696d4368-1231-4430-841c-ca20fea7904c.png align="left")

Here `SimpleHTTPRequestHandler` simply rendered the current working directory and since `index.html` happens to be there, the server simply returned the contents of that file. If we rename index.html to something else then an `Index Of` page will be rendered.

![Index of screen](https://cdn.hashnode.com/res/hashnode/image/upload/v1741074627921/c784f800-1c8b-4e22-8d43-8d9446941ce5.png align="left")

## Building Routes

What we have built works, but that only provides a simple static website server with no added security. We can change this behavior by implementing our own https handler. we can do this simply by defining a Http Request Handler class and passing it through to the `MyFlask` constructor

```python
# File - main.py

from myflask import MyFlask

app = MyFlask(MyHttpRequestHandler)

if __name__ == "__main__":
    app.run()
```

Now let’s define this `MyHttpRequestHandler` class.

### Structure of HTTP Request Handler

To implement a custom HTTP request handler from scratch, you would typically subclass the `SimpleHTTPRequestHandler` class from the `http.server` module. This allows you to define custom behavior for handling different types of HTTP requests, such as GET, POST, PUT, DELETE, etc.

You can handle more HTTP methods by implementing `do_{method}` method inside `MyHttpRequestHandler` class. For example, `do_POST`, `do_DELETE`, `do_PATCH`, etc.

```python
# File - main.py

from myflask import MyFlask
from http.server import SimpleHTTPRequestHandler

class MyHttpRequestHandler(SimpleHTTPRequestHandler):
    def do_GET(self):
        self.send_error(418)

app = MyFlask(MyHttpRequestHandler)

if __name__ == "__main__":
    app.run()
```

![Teapot 418 HTTP Error](https://cdn.hashnode.com/res/hashnode/image/upload/v1741074629146/f11bd091-5f8b-4c5f-b371-a4a74242d2f2.png align="left")

The problem with this implementation is that the server will return HTTP error 418 no matter the page you try to visit.

We can easily implement routes by performing some pattern matching over the `self.path`.

```python
# File - main.py

from myflask import MyFlask
from http.server import SimpleHTTPRequestHandler

class MyHttpRequestHandler(SimpleHTTPRequestHandler):
    def do_GET(self):
        if self.path == "/":
            self.send_response(200) # Send HTTP code
            self.send_header("Content-Type", "text/html") # Send Headers
            self.end_headers() # Mark the end of Headers
            self.wfile.write("<p>Hello World</p>".encode()) # HTTP body

app = MyFlask(MyHttpRequestHandler)

if __name__ == "__main__":
    app.run()
```

You might find the order I wrote the command inside `do_GET` kind of similar to the HTTP Response format we discussed in our [Create HTTP server from Scratch](https://idiomaticprogrammers.com/post/create-http-server-from-scratch/) article.

```plaintext
HTTP/1.1 200 OK
Content-Type text/html

<p>Hello World</p>
```

First we sent the response code which is `200` in this case. Then we list all our headers, in this case just `Content-Type` and once we listed all the required headers we mark the end of headers using `self.end_headers()` and finally we write the body and encoded it as bytes.

If you want to support another page, we can simply add another pattern matching.

```python
# File - main.py

from myflask import MyFlask
from http.server import SimpleHTTPRequestHandler

class MyHttpRequestHandler(SimpleHTTPRequestHandler):
    def do_GET(self):
        if self.path == "/":
            self.send_response(200) # Send HTTP code
            self.send_header("Content-Type", "text/html") # Send Headers
            self.end_headers() # Mark the end of Headers
            self.wfile.write("<p>Hello World</p>".encode()) # HTTP body
        elif self.path == "/page1":
            self.send_response(200) # Send HTTP code
            self.send_header("Content-Type", "text/html") # Send Headers
            self.end_headers() # Mark the end of Headers
            self.wfile.write("<p>Rendering Page 1</p>".encode()) # HTTP body

app = MyFlask(MyHttpRequestHandler)

if __name__ == "__main__":
    app.run()
```

![Custom Routes](https://cdn.hashnode.com/res/hashnode/image/upload/v1741074630902/89e60301-664b-45aa-8d6b-add84f8ce0c7.jpeg align="left")

### Handling HTML template expressions

Now finally let’s do some context filling something like we do in Flask.

```python
# File - main.py

from myflask import MyFlask
from http.server import SimpleHTTPRequestHandler

import os

PORT = (os.getenv('PORT') or 3000) # port 3000 by default

class MyHttpRequestHandler(SimpleHTTPRequestHandler):
    def do_GET(self):
        if self.path == "/":
            self.send_response(200) # Send HTTP code
            self.send_header("Content-Type", "text/html") # Send Headers
            self.end_headers() # Mark the end of Headers
            self.wfile.write("<p>Hello World</p>".encode()) # HTTP body
        elif self.path == "/page1":
            self.send_response(200) # Send HTTP code
            self.send_header("Content-Type", "text/html") # Send Headers
            self.end_headers() # Mark the end of Headers
            self.wfile.write("<p>Rendering Page 1</p>".encode()) # HTTP body
        elif self.path == "/port":
            self.send_response(200) # Send HTTP code
            self.send_header("Content-Type", "text/html") # Send Headers
            self.end_headers() # Mark the end of Headers
            res_body = open('index.html',"r").read().format_map({
		            "PORT":PORT
            })
            self.wfile.write(res_body.encode()) # HTTP body

app = MyFlask(MyHttpRequestHandler)

if __name__ == "__main__":
    app.run(port=PORT)
```

![HTML Template](https://cdn.hashnode.com/res/hashnode/image/upload/v1741074631981/69d3d8e3-7025-4a4f-a4ca-faa9715e1596.png align="left")

### Managing Error codes

Final missing piece in our HTTP server puzzle is managing different error codes. In `SimpleHTTPRequestHandler` there is a method called `send_error()` this is used send an error code to the client. We have used this method in the beginning before we implemented the route.

```python
# File - main.py

from myflask import MyFlask
from http.server import SimpleHTTPRequestHandler

import os

PORT = (os.getenv('PORT') or 3000) # port 3000 by default

class MyHttpRequestHandler(SimpleHTTPRequestHandler):
    def do_GET(self):
        if self.path == "/":
            self.send_response(200) # Send HTTP code
            self.send_header("Content-Type", "text/html") # Send Headers
            self.end_headers() # Mark the end of Headers
            self.wfile.write("<p>Hello World</p>".encode()) # HTTP body
        elif self.path == "/page1":
            self.send_response(200) # Send HTTP code
            self.send_header("Content-Type", "text/html") # Send Headers
            self.end_headers() # Mark the end of Headers
            self.wfile.write("<p>Rendering Page 1</p>".encode()) # HTTP body
        elif self.path == "/port":
            self.send_response(200) # Send HTTP code
            self.send_header("Content-Type", "text/html") # Send Headers
            self.end_headers() # Mark the end of Headers
            res_body = open('index.html',"r").read().format_map({
		            "PORT":PORT
            })
            self.wfile.write(res_body.encode()) # HTTP body
        else:
            self.send_error(404)

app = MyFlask(MyHttpRequestHandler)

if __name__ == "__main__":
    app.run(port=PORT)
```

![Error 404 Page](https://cdn.hashnode.com/res/hashnode/image/upload/v1741074633149/bceb6768-fea9-48ea-b806-d2aa83367fcf.png align="left")

## Conclusion

In conclusion, while implementing a custom HTTP request handler can provide valuable insight into how web servers operate and how frameworks like Flask, Jinja, or Django function under the hood, for most practical purposes, using the `SimpleHTTPRequestHandler` provided by the `http.server` module is often the better choice.

The `SimpleHTTPRequestHandler` is ideal for testing, prototyping, and learning purposes due to its simplicity and ease of use. It allows for quick setup of a basic HTTP server that can serve static files and handle simple GET requests. Moreover, it provides a straightforward platform for experimenting with web development concepts without the added complexity of implementing a custom request handler.

However, it's important to note that while the `SimpleHTTPRequestHandler` is suitable for testing and learning, it is not the optimal solution for a production-ready server. Production environments typically require more advanced features such as security, scalability, performance optimization, and support for various HTTP methods beyond just GET requests. In such cases, using a mature and feature-rich web framework like Flask, Jinja, or Django, which are specifically designed for building robust web applications, would be the recommended approach.

## Reference

1. https://docs.python.org/3/library/http.server.html
    
2. https://github.com/python/cpython/blob/main/Doc/library/http.server.rst
    
3. https://http.dev/418
