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()