Skip to content

Django

This article starts from the underlying principles of web frameworks, covering HTTP protocol basics, the socket communication model, and how to evolve step by step from a hand-rolled framework to a WSGI-based Django framework. It helps you understand Django’s design philosophy and applicable scenarios.

The Essence of a Web Framework

We can think of it this way: every web application is essentially a socket server,
and the user's browser is a socket client. This means we can implement a web
framework ourselves.

Hand-rolled framework:
    1. Socket code must be written by us
    2. HTTP-formatted data must be handled manually (we can only extract the URL the user typed)

Based on the wsgiref module:
    1. Encapsulates socket code for you
    2. Parses HTTP-formatted data for you (provides a large dict)

HTTP Protocol

HTTP (Hyper Text Transfer Protocol)
    The protocol used to transfer hypertext from a WWW server to a local browser.
    HTTP is an application-layer protocol consisting of requests and responses,
    following a standard client-server model.

Features:
    1. Supports the client/server model
    2. Simple and fast
        When a client requests a service from the server it only needs to send
        the request method and path. Common request methods are GET, HEAD, and POST.
        Because HTTP is simple, HTTP server programs are small and communication is fast.
    3. Flexible
        HTTP allows transmission of any type of data object.
    4. Connectionless
        Each connection handles only one request. After the server processes the client
        request and sends the response, the connection is closed. This saves transmission time.
    5. Stateless
        HTTP is a stateless protocol — it has no memory of previous transactions.
        The downside is that if later processing needs earlier information, it must be
        retransmitted, increasing data volume per connection. The upside is that responses
        are faster when prior context is not needed. Cookie and Session technology was
        introduced to work around HTTP's statelessness.

Common request methods:
    GET     — Request the specified page and return the entity body
    POST    — Submit data to the specified resource; data is included in the request body
    HEAD    — Like GET, but returns only headers, no body
    PUT     — Replace the content of the specified document with data sent from the client
    DELETE  — Request the server to delete the specified page
    CONNECT — Reserved in HTTP/1.1 for proxy servers that can switch to tunnel mode
    OPTIONS — Allow the client to query server capabilities
    TRACE   — Echo the received request; mainly used for testing or diagnostics

HTTP working principle:
    HTTP defines how a web client requests web pages from a web server and how the
    server delivers them. HTTP uses a request/response model. The client sends a
    request message containing the method, URL, protocol version, request headers,
    and request data. The server replies with a status line that includes the protocol
    version, a success or error code, server information, response headers, and
    response data.

HTTP request/response steps:
    1. Client connects to the web server
    2. Client sends an HTTP request
    3. Server accepts the request and returns an HTTP response
    4. TCP connection is released
    5. Client browser parses the HTML content

Status codes:
    200 OK              — Returned when the operation returns data in the response body.
    204 No Content      — Returned when the operation succeeds but returns no data.
    304 Not Modified    — Returned when testing whether an entity has been modified since last retrieval.
    400 Bad Request     — Returned when parameters are invalid.
    403 Forbidden       — Client error.
    401 Unauthorized    — Client error.
    404 Not Found       — Returned when the resource does not exist.
    405 Method Not Allowed — Incorrect method/resource combination (e.g. DELETE on a collection).
    412 Precondition Failed — Client error.
    413 Payload Too Large   — Returned when the request body is too large.
    501 Not Implemented     — Returned when the requested operation is not implemented.
    503 Service Unavailable — Returned when the Web API service is unavailable.

HTTP GET Request Format

HTTP GET request diagram

HTTP Response Format

HTTP response format diagram

Custom Web Framework — Version 1

    import socket

    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.bind(('127.0.0.1', 8000))
    sock.listen()

    while True:
        conn, addr = sock.accept()
        data = conn.recv(8096)
        # Add the response status line to the reply
        conn.send(b"HTTP/1.1 200 OK\r\n\r\n")
        conn.send(b"OK")
        conn.close()

Custom Web Framework — Different Paths Return Different Content

"""
Return different content based on different URL paths
"""

import socket
sk = socket.socket()
sk.bind(("127.0.0.1", 8080))  # Bind IP and port
sk.listen()  # Start listening


while 1:
    # Wait for a connection
    conn, add = sk.accept()
    data = conn.recv(8096)  # Receive message from client
    # Extract the path from data
    data = str(data, encoding="utf8")  # Convert received bytes to string
    # Split on \r\n
    data1 = data.split("\r\n")[0]
    url = data1.split()[1]  # url is the access path separated from the browser message
    conn.send(b'HTTP/1.1 200 OK\r\n\r\n')  # Must follow HTTP protocol, so the reply also needs a status line
    # Return different content for different paths
    if url == "/index/":
        response = b"index"
    elif url == "/home/":
        response = b"home"
    else:
        response = b"404 not found!"

    conn.send(response)
    conn.close()

Custom Web Framework — Different Paths Return Different Content (Function Version)

"""
Return different content based on different URL paths -- function version
"""

import socket
sk = socket.socket()
sk.bind(("127.0.0.1", 8080))  # Bind IP and port
sk.listen()  # Start listening


# Encapsulate the different response parts into functions
def index(url):
    s = "This is the {} page!".format(url)
    return bytes(s, encoding="utf8")


def home(url):
    s = "This is the {} page!".format(url)
    return bytes(s, encoding="utf8")


while 1:
    # Wait for a connection
    conn, add = sk.accept()
    data = conn.recv(8096)  # Receive message from client
    # Extract the path from data
    data = str(data, encoding="utf8")  # Convert received bytes to string
    # Split on \r\n
    data1 = data.split("\r\n")[0]
    url = data1.split()[1]  # url is the access path separated from the browser message
    conn.send(b'HTTP/1.1 200 OK\r\n\r\n')  # Must follow HTTP protocol, so the reply also needs a status line
    # Return different content for different paths; response is the specific response body
    if url == "/index/":
        response = index(url)
    elif url == "/home/":
        response = home(url)
    else:
        response = b"404 not found!"

    conn.send(response)
    conn.close()

Custom Web Framework — Different Paths Return Different Content (Advanced Function Version)

"""
Return different content based on different URL paths -- advanced function version
"""

import socket
sk = socket.socket()
sk.bind(("127.0.0.1", 8080))  # Bind IP and port
sk.listen()  # Start listening


# Encapsulate the different response parts into functions
def index(url):
    s = "This is the {} page!".format(url)
    return bytes(s, encoding="utf8")


def home(url):
    s = "This is the {} page!".format(url)
    return bytes(s, encoding="utf8")


# Define a mapping between URLs and the functions to execute
list1 = [
    ("/index/", index),
    ("/home/", home),
]

while 1:
    # Wait for a connection
    conn, add = sk.accept()
    data = conn.recv(8096)  # Receive message from client
    # Extract the path from data
    data = str(data, encoding="utf8")  # Convert received bytes to string
    # Split on \r\n
    data1 = data.split("\r\n")[0]
    url = data1.split()[1]  # url is the access path separated from the browser message
    conn.send(b'HTTP/1.1 200 OK\r\n\r\n')  # Must follow HTTP protocol, so the reply also needs a status line
    # Return different content for different paths
    func = None  # Variable to hold the function to be executed
    for i in list1:
        if i[0] == url:
            func = i[1]
            break
    if func:
        response = func(url)
    else:
        response = b"404 not found!"

    # Send the specific response message
    conn.send(response)
    conn.close()

Custom Web Framework — Returning a Web Page

"""
Return different content based on different URL paths -- advanced function version
Return standalone HTML pages
"""

import socket
sk = socket.socket()
sk.bind(("127.0.0.1", 8080))  # Bind IP and port
sk.listen()  # Start listening


# Encapsulate the different response parts into functions
def index(url):
    # Read the content of the index.html page
    with open("index.html", "r", encoding="utf8") as f:
        s = f.read()
    # Return as bytes
    return bytes(s, encoding="utf8")


def home(url):
    with open("home.html", "r", encoding="utf8") as f:
        s = f.read()
    return bytes(s, encoding="utf8")


# Define a mapping between URLs and the functions to execute
list1 = [
    ("/index/", index),
    ("/home/", home),
]

while 1:
    # Wait for a connection
    conn, add = sk.accept()
    data = conn.recv(8096)  # Receive message from client
    # Extract the path from data
    data = str(data, encoding="utf8")  # Convert received bytes to string
    # Split on \r\n
    data1 = data.split("\r\n")[0]
    url = data1.split()[1]  # url is the access path separated from the browser message
    conn.send(b'HTTP/1.1 200 OK\r\n\r\n')  # Must follow HTTP protocol, so the reply also needs a status line
    # Return different content for different paths
    func = None  # Variable to hold the function to be executed
    for i in list1:
        if i[0] == url:
            func = i[1]
            break
    if func:
        response = func(url)
    else:
        response = b"404 not found!"

    # Send the specific response message
    conn.send(response)
    conn.close()

Custom Web Framework — Dynamic Web Pages

"""
Return different content based on different URL paths -- advanced function version
Return HTML pages
Make the web page dynamic
"""

import socket
import time

sk = socket.socket()
sk.bind(("127.0.0.1", 8080))  # Bind IP and port
sk.listen()  # Start listening


# Encapsulate the different response parts into functions
def index(url):
    with open("index.html", "r", encoding="utf8") as f:
        s = f.read()
        now = str(time.time())
        s = s.replace("@@oo@@", now)  # Define a special placeholder in the page and replace it with dynamic data
    return bytes(s, encoding="utf8")


def home(url):
    with open("home.html", "r", encoding="utf8") as f:
        s = f.read()
    return bytes(s, encoding="utf8")


# Define a mapping between URLs and the functions to execute
list1 = [
    ("/index/", index),
    ("/home/", home),
]

while 1:
    # Wait for a connection
    conn, add = sk.accept()
    data = conn.recv(8096)  # Receive message from client
    # Extract the path from data
    data = str(data, encoding="utf8")  # Convert received bytes to string
    # Split on \r\n
    data1 = data.split("\r\n")[0]
    url = data1.split()[1]  # url is the access path separated from the browser message
    conn.send(b'HTTP/1.1 200 OK\r\n\r\n')  # Must follow HTTP protocol, so the reply also needs a status line
    # Return different content for different paths
    func = None  # Variable to hold the function to be executed
    for i in list1:
        if i[0] == url:
            func = i[1]
            break
    if func:
        response = func(url)
    else:
        response = b"404 not found!"

    # Send the specific response message
    conn.send(response)
    conn.close()

WSGI Module — Defining the Server

In a real Python web application, the system is generally split into two parts:
the server program and the application program.

The server program is responsible for wrapping the socket server and organizing all
the request data when a request arrives.

The application program handles the specific business logic. To make application
development easier, many web frameworks have emerged, such as Django, Flask, web.py,
etc. Different frameworks have different development styles, but all applications
ultimately need to work with a server program to serve users.

This means the server program needs to provide different support for different
frameworks — a chaotic situation that is bad for both sides. The server must support
all sorts of frameworks; a framework can only be used with servers that support it.

Standardization becomes critically important here. We can define a standard: if both
the server and the framework support this standard, they can work together. Once the
standard is set, each side implements it independently. This lets servers support
more frameworks, and frameworks run on more servers.

WSGI (Web Server Gateway Interface) is exactly such a specification. It defines the
interface format between Python web applications and web server programs, decoupling
the two sides.

Common WSGI servers include uwsgi and Gunicorn. The Python standard library provides
a standalone WSGI server called wsgiref, which Django's development server uses.

Code

"""
Return different content based on different URL paths -- advanced function version
Return HTML pages
Make the web page dynamic
wsgiref module version
"""

import time
from wsgiref.simple_server import make_server


# Encapsulate the different response parts into functions
def index(url):
    with open("index.html", "r", encoding="utf8") as f:
        s = f.read()
        now = str(time.time())
        s = s.replace("@@oo@@", now)
    return bytes(s, encoding="utf8")


def home(url):
    with open("home.html", "r", encoding="utf8") as f:
        s = f.read()
    return bytes(s, encoding="utf8")


# Define a mapping between URLs and the functions to execute
list1 = [
    ("/index/", index),
    ("/home/", home),
]


def run_server(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/html;charset=utf8'), ])  # Set HTTP response status code and headers
    url = environ['PATH_INFO']  # Get the URL entered by the user
    func = None
    for i in list1:
        if i[0] == url:
            func = i[1]
            break
    if func:
        response = func(url)
    else:
        response = b"404 not found!"
    return [response, ]


if __name__ == '__main__':
    httpd = make_server('127.0.0.1', 8090, run_server)
    print("Waiting for you on port 8090...")
    httpd.serve_forever()
Last updated on