构建 HTTP 服务器与客户端是理解网络通信底层逻辑的核心实践,通过从零实现一个基础的 HTTP 服务,我们可以深入剖析请求行、请求头、请求体以及响应状态码、响应头和响应体的结构,以下将分别展示使用 Python 标准库 http.server 构建服务端,以及使用 urllib 或 requests 库构建客户端的完整实例。

HTTP 服务器端实例
在 Python 中,最轻量级的 HTTP 服务器可以通过继承 http.server.BaseHTTPRequestHandler 类来实现,这种方式允许我们精确控制如何处理不同的 HTTP 方法(如 GET、POST)。
核心代码实现
import http.server
import socketserver
import json
from urllib.parse import urlparse, parse_qs
class MyHTTPRequestHandler(http.server.BaseHTTPRequestHandler):
"""
自定义 HTTP 请求处理器
"""
def do_GET(self):
"""处理 GET 请求"""
# 解析 URL 路径和查询参数
parsed_path = urlparse(self.path)
query_params = parse_qs(parsed_path.query)
# 设置响应状态码为 200 OK
self.send_response(200)
# 设置响应头,告知客户端内容类型为 JSON
self.send_header('Content-type', 'application/json')
self.end_headers()
# 构建响应数据
response_data = {
"status": "success",
"message": "GET request received",
"path": parsed_path.path,
"params": query_params
}
# 将数据转换为 JSON 字符串并发送
self.wfile.write(json.dumps(response_data).encode('utf-8'))
def do_POST(self):
"""处理 POST 请求"""
# 获取 Content-Length 头部,确定请求体长度
content_length = int(self.headers['Content-Length'])
# 读取请求体数据
post_data = self.rfile.read(content_length)
# 解析 JSON 数据
try:
data = json.loads(post_data.decode('utf-8'))
except json.JSONDecodeError:
self.send_error(400, "Invalid JSON")
return
# 设置响应状态码
self.send_response(201) # Created
self.send_header('Content-type', 'application/json')
self.end_headers()
# 构建响应数据
response_data = {
"status": "success",
"message": "POST request received",
"received_data": data
}
self.wfile.write(json.dumps(response_data).encode('utf-8'))
def log_message(self, format, args):
"""
重写日志方法,自定义日志输出格式
"""
print(f"[{self.log_date_time_string()}] {args[0]}")
def run_server(port=8080):
"""启动 HTTP 服务器"""
with socketserver.TCPServer(("", port), MyHTTPRequestHandler) as httpd:
print(f"Server started on port {port}")
try:
httpd.serve_forever()
except KeyboardInterrupt:
print("nShutting down server...")
httpd.server_close()
if __name__ == "__main__":
run_server()
服务器端关键组件解析
| 组件 | 说明 |
|---|---|
BaseHTTPRequestHandler |
基类,提供了处理 HTTP 请求的基本框架,包括解析请求行、读取头部等。 |
do_GET / do_POST |
特定方法处理函数,当收到对应 HTTP 方法时自动调用,若未定义,服务器将返回 501 Not Implemented。 |
send_response |
发送状态行(如 HTTP/1.1 200 OK)。 |
send_header |
添加响应头字段,如 Content-Type、Content-Length 等。 |
end_headers |
发送空行,标志着响应头的结束,接下来是响应体。 |
wfile.write |
向客户端写入响应体数据,必须是字节流。 |
HTTP 客户端实例
客户端负责发起请求并接收服务端的响应,Python 提供了多种库,这里展示使用标准库 urllib 和第三方库 requests 两种方式。
使用 urllib (标准库)
import urllib.request
import urllib.parse
import json
def client_example_urllib():
print("--Using urllib ---")
# 1. 发起 GET 请求
url = "http://localhost:8080/api/data?name=Alice&age=25"
try:
request = urllib.request.Request(url)
with urllib.request.urlopen(request) as response:
status_code = response.getcode()
headers = response.getheaders()
body = response.read().decode('utf-8')
print(f"Status Code: {status_code}")
print(f"Response Body: {body}")
except urllib.error.HTTPError as e:
print(f"HTTP Error: {e.code} {e.reason}")
except Exception as e:
print(f"Error: {e}")
# 2. 发起 POST 请求
post_url = "http://localhost:8080/api/submit"
data = {"username": "Bob", "role": "admin"}
json_data = json.dumps(data).encode('utf-8')
try:
request = urllib.request.Request(
post_url,
data=json_data,
method='POST'
)
request.add_header('Content-Type', 'application/json')
with urllib.request.urlopen(request) as response:
body = response.read().decode('utf-8')
print(f"POST Response Body: {body}")
except Exception as e:
print(f"POST Error: {e}")
if __name__ == "__main__":
client_example_urllib()
使用 requests (推荐第三方库)
requests 库极大地简化了 HTTP 请求的发送过程,代码更加简洁易读。

import requests
def client_example_requests():
print("n--Using requests ---")
base_url = "http://localhost:8080"
# 1. 发起 GET 请求
params = {"name": "Charlie", "age": 30}
try:
response = requests.get(f"{base_url}/api/data", params=params)
response.raise_for_status() # 如果状态码不是 2xx,抛出异常
print(f"GET Status: {response.status_code}")
print(f"GET JSON: {response.json()}")
except requests.exceptions.RequestException as e:
print(f"GET Error: {e}")
# 2. 发起 POST 请求
post_data = {"username": "Dave", "role": "user"}
try:
response = requests.post(f"{base_url}/api/submit", json=post_data)
response.raise_for_status()
print(f"POST Status: {response.status_code}")
print(f"POST JSON: {response.json()}")
except requests.exceptions.RequestException as e:
print(f"POST Error: {e}")
if __name__ == "__main__":
client_example_requests()
客户端与服务器交互流程图解
sequenceDiagram
participant Client as 客户端
participant Server as 服务器端
Client->>Server: GET /api/data?name=Test HTTP/1.1
Note over Server: 解析请求行和头部
Server->>Server: 执行 do_GET 逻辑
Server->>Client: HTTP/1.1 200 OK
Server->>Client: Content-Type: application/json
Server->>Client: {"status": "success", ...}
Client->>Server: POST /api/submit HTTP/1.1
Note over Client: 发送 JSON 数据体
Note over Server: 解析请求行和头部
Server->>Server: 执行 do_POST 逻辑
Server->>Client: HTTP/1.1 201 Created
Server->>Client: Content-Type: application/json
Server->>Client: {"status": "success", ...}
常见问题与解答 (Q&A)
问题 1:在自定义 HTTP 服务器时,为什么必须调用 end_headers() 方法?如果不调用会发生什么?
解答:
在 HTTP 协议中,响应由“状态行”、“响应头”和“响应体”三部分组成,其中响应头和响应体之间必须用一个空行(CRLF)分隔。
send_header()只是将头部字段添加到内部的缓冲区中,并没有实际发送数据。end_headers()的作用是:- 将缓冲区中累积的所有响应头发送给客户端。
- 发送一个空行(
rn),明确告知客户端响应头部分已经结束。 - 此后写入
wfile的数据将被视为响应体。
如果不调用 end_headers() 直接写入响应体,客户端将无法区分哪些是头部信息,哪些是主体内容,导致解析错误,通常表现为连接挂起或客户端报错 Bad HTTP response。
问题 2:在处理 POST 请求时,如何正确获取并解析客户端发送的 JSON 数据?需要注意哪些潜在问题?
解答:
获取 POST 请求体数据的关键在于遵循以下步骤:

- 读取长度:首先从请求头中获取
Content-Length字段,这是客户端发送的数据字节数。content_length = int(self.headers['Content-Length'])
- 读取数据:使用
self.rfile.read(content_length)读取确切字节数的数据。post_data = self.rfile.read(content_length)
- 解码与解析:将字节流解码为字符串,再使用
json.loads()解析为 Python 字典。data = json.loads(post_data.decode('utf-8'))
潜在问题与注意事项:
- 缺失 Content-Length:如果客户端未发送
Content-Length头(例如在某些旧的 HTTP/1.0 请求或分块传输编码中),直接读取会导致阻塞或错误,应检查头部是否存在。 - 编码问题:必须确保解码编码(通常是
utf-8)与客户端发送的一致,否则会出现UnicodeDecodeError。 - JSON 格式错误:客户端可能发送了非 JSON 格式的数据(如纯文本或损坏的 JSON),必须使用
try-except捕获json.JSONDecodeError,并返回适当的错误状态码(如 400 Bad Request),而不是让服务器崩溃。 - 大文件处理:对于非常大的 POST 数据,一次性读取到内存可能导致内存溢出,在生产环境中,应考虑流式处理(streaming)或限制最大请求大小。
原创文章,发布者:酷盾叔,转转请注明出处:https://www.kd.cn/ask/498421.html