Python build Web server: 3.0

Posted by darktimesrpg on Thu, 20 Jan 2022 02:55:57 +0100

Python build Web server: 3.0

1, Introduction

In the previous chapter, we have successfully enabled the Web server to run continuously and support multi-user connections at the same time. But so far, our Web server can only return "Hello World" for all routes. As a qualified Web server, we need to be able to support the function of resolving routes.

In this section, we will implement the function of parsing routes and returning text resources.

2, Analytic routing

Randomly use the browser to capture the header of an HTTP message for observation:

GET / HTTP/1.1
Host: www.baidu.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:89.0) Gecko/20100101 Firefox/89.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

Find the browser, and the location of the requested resource path is the first line of the message. Then we only need to extract this path to know what resources the client needs:

def parse_path(data):
    """
    from HTTP Extract resource path from message
    :param data:
    :return:
    """
    # Convert binary message to string
    tmp = data.decode()
    # Get first row of request data
    tmp = tmp.split("\r\n")
    # Get request path
    path = tmp[0].split()
    # Return resource request path
    return path[1]

3, Extract resources

First, we create a resource folder under the same level directory where the server program is located, and create an index HTML file and enter the following code:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Hello</title>
</head>
<body>
<h1>Hello World</h1>
</body>
</html>

This index The HTML file is the resource we are ready to request.

Now we extract resources through the path found in step 2:

def get_resource(path):
    """
    Extract the content of the resource
    :param path: Resource path
    :return: Resource content
    """
    with open(path, "r", encoding="utf-8") as f:
        data = f.read()
	
	return data

4, 404 page

Note that there is a problem: the resources requested by the client do not always exist!

When the client requests a nonexistent resource, we should return 404 status code and 404 page to prompt the user:

Create a new 404 in the resource folder HTML file, write the following code:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>404</title>
</head>
<body>
<h1>Resource Not Found</h1>
</body>
</html>

Modify our Python code:

def get_resource(path):
    """
    Extract the content of the resource
    :param path: Resource path
    :return: Status code, resource content
    """
    RESOURCE = "resource"
    
    data_404 = RESOURCE + "/404.html"
    path = RESOURCE + path
        
    # Judge whether the resource exists
    if os.path.exists(path):
        # If it exists, extract the resource and set the status code to 200
        with open(path, "r", encoding="utf-8") as f:
            data = f.read()
            status = 200
    else:
        # If it does not exist, return to the 404 page and set the status code to 404
        with open(data_404, "r", encoding="utf-8") as f:
            data = f.read()
            status = 404

    return status, data

5, Packet encapsulation

After the resource content is extracted, we need to package it into HTTP message in the next step:

def package_message(status, text):
    """
    HTTP Packet encapsulation
    :param status: Status code
    :param text: content
    :return: HTTP message
    """
    message = f'HTTP/1.1 {status} OK\r\nContent-Type: text/html\r\n\r\n{text}'
    return message.encode("utf-8")

Next, just send the HTTP message to the server as before.

6, Complete code

import socket
import threading
import os


def process_connection(client):
    """
    Handling client connections
    :param client: client
    :return:
    """
    # Get client request
    data = b''
    while True:
        chunk = client.recv(1024)
        data += chunk
        if len(chunk) < 1024:
            break

    path = parse_path(data)
    status, text = get_resource(path)

    # Return response to client
    message = package_message(text, status)
    client.sendall(message)
    print("*" * 100)
    client.close()


def package_message(status, text):
    """
    HTTP Packet encapsulation
    :param status: Status code
    :param text: content
    :return: HTTP message
    """
    message = f'HTTP/1.1 {status} OK\r\nContent-Type: text/html\r\n\r\n{text}'
    return message.encode("utf-8")


def parse_path(data):
    """
    from HTTP Extract resource path from message
    :param data: Message data
    :return: Resource path
    """
    # Convert binary message to string
    tmp = data.decode()
    # Get first row of request data
    tmp = tmp.split("\r\n")
    # Get request path
    path = tmp[0].split()
    # Return resource request path
    return path[1]


def get_resource(path):
    """
    Extract the content of the resource
    :param path: Resource path
    :return: Status code, resource content
    """
    RESOURCE = "resource"

    data_404 = RESOURCE + "/404.html"
    path = RESOURCE + path

    # Judge whether the resource exists
    if os.path.exists(path):
        # If it exists, extract the resource and set the status code to 200
        with open(path, "r", encoding="utf-8") as f:
            data = f.read()
            status = 200
    else:
        # If it does not exist, return to the 404 page and set the status code to 404
        with open(data_404, "r", encoding="utf-8") as f:
            data = f.read()
            status = 404

    return status, data


def main():
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # Port multiplexing
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sock.bind(('127.0.0.1', 8000))
    sock.listen(5)
    print("The server is listening...")

    while True:
        client, addr = sock.accept()
        print("Client information:", addr)

        # Create a new thread for users to handle client connections
        threading.Thread(target=process_connection, args=(client,)).start()


if __name__ == '__main__':
    main()

Topics: Python