Skip to content

Python CGI 编程

使用 Python 进行传统 Web 脚本编写(理解 CGI 概念)

Section titled “使用 Python 进行传统 Web 脚本编写(理解 CGI 概念)”

注意:本教程使用 Python 讲解了通用网关接口(Common Gateway Interface,CGI)背后的概念。虽然 CGI 在历史上非常重要,但它不再是现代 Python Web 开发的标准。如今推荐使用 Flask、Django 和 FastAPI 等框架,它们采用 WSGI 或 ASGI 接口。了解 CGI 概念可以提供有用的历史背景,并深入了解 Web 服务器如何与后端脚本交互。

通用网关接口(CGI)是一种标准协议,定义了 Web 服务器如何执行外部脚本(如 Python 程序)来动态生成 Web 内容。它规定了服务器与脚本之间如何交换信息。

  • CGI 提供了一种标准方式,让 Web 服务器(如 Apache 或 Nginx)响应传入的 Web 请求时运行外部程序(CGI 脚本)。
  • 服务器不再仅仅提供静态文件,它可以执行脚本,并将脚本的输出发送回用户的浏览器。

理解基本的交互有助于掌握 CGI 所扮演的角色:

  • 用户的浏览器向 Web 服务器请求一个特定的 URL。
  • Web 服务器识别该 URL 是指向静态文件还是通过 CGI 等接口配置运行的脚本。
  • 如果是脚本,服务器会执行它,通常通过环境变量和标准输入传递请求详情(如表单数据或查询参数)。
  • 脚本处理请求,执行操作(例如,数据库查询),并生成 HTTP 响应(包括 Content-Type 等请求头和 HTML 主体)。
  • 脚本将此响应打印到标准输出。
  • Web 服务器捕获脚本的输出并将其发送回浏览器。
  • 浏览器渲染接收到的内容。

在 CGI 模型中,每个请求通常都会为脚本启动一个新进程,这效率低下。现代方法(WSGI/ASGI)使用持久化的应用服务器以获得更好的性能。

设想以下流程:

  1. 浏览器发送请求(例如 /scripts/my_script.py)->
  2. Web 服务器接收请求 ->
  3. Web 服务器识别需要通过 CGI 运行 my_script.py ->
  4. Web 服务器启动 my_script.py,传递请求数据 ->
  5. my_script.py 运行,生成 HTML 输出 ->
  6. my_script.py 将 HTML 打印到标准输出 ->
  7. Web 服务器捕获输出 ->
  8. Web 服务器将输出发送回浏览器 ->
  9. 浏览器显示 HTML。

传统上,Web 服务器需要特定的配置来启用 CGI 执行,通常会指定可执行脚本的目录(如 cgi-bin)。权限也需要正确设置。

现代 Python Web 框架通常通过 WSGI 或 ASGI 服务器(如 Gunicorn、uWSGI、Hypercorn、Uvicorn)运行,这些服务器配置方式不同,并更有效地管理应用程序生命周期,通常位于 Nginx 等反向代理之后。

一个简单的脚本示例(演示输出)

Section titled “一个简单的脚本示例(演示输出)”

此示例展示了 CGI 脚本需要产生的类型的输出。注意必需的 Content-Type 请求头,后跟一个空行,然后是 HTML 主体。现代框架处理响应生成更为优雅。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# 打印必需的 HTTP 请求头
# 注意请求头与主体之间的空行
print("Content-Type: text/html\n")
# 打印 HTML 内容
print("<!DOCTYPE html>")
print("<html>")
print("<head>")
print("<title>Hello from Python Script</title>")
print("</head>")
print("<body>")
print("<h2>Hello World! This is script output.</h2>")
print("</body>")
print("</html>")

如果由配置为支持 CGI 的 Web 服务器执行,此脚本会将一个基本的 HTML 页面发送回浏览器。

Content-Type: text/html 行是一个关键的 HTTP 请求头。它告诉浏览器期望的内容类型。还有许多其他请求头用于控制缓存、重定向、Cookie 等。CGI 脚本在实际内容之前必须手动打印这些请求头。

常见 HTTP 请求头(概念):

请求头描述
Content-Type指定响应主体的 MIME 类型(例如,text/html、application/json、image/png)。 必需。
Location用于重定向。告诉浏览器请求不同的 URL。
Set-Cookie从服务器发送一个 Cookie,由浏览器存储。
Cache-Control提供缓存机制的指令(例如,no-cache)。

现代框架提供帮助函数或对象,无需手动打印即可设置响应请求头。

Web 服务器通过环境变量将请求信息传递给 CGI 脚本。虽然现在很少直接访问这些变量,但理解它们可以了解请求期间哪些数据是可用的。

变量名(概念)描述
REQUEST_METHOD使用的 HTTP 方法(例如,‘GET’、‘POST’)。
QUERY_STRINGURL 中 ’?’ 之后的部分,包含 GET 参数。
CONTENT_TYPE请求主体的 MIME 类型(对 POST 请求很重要,例如 application/x-www-form-urlencoded)。
CONTENT_LENGTH请求主体的字节长度(对 POST 请求很重要)。
SCRIPT_NAME正在执行的脚本的路径。
REMOTE_ADDR发出请求的客户端的 IP 地址。
HTTP_USER_AGENT浏览器/客户端标识字符串。
HTTP_COOKIE客户端发送的 Cookie。
SERVER_NAME服务器的主机名或 IP 地址。
SERVER_PORT服务器接收请求的端口。

现代框架通过专门的请求对象提供此信息(例如,在 Flask 或 Django 中通过 request.method、request.args、request.headers、request.cookies 访问)。

示例:访问环境信息(概念)

#!/usr/bin/env python3
import os
print("Content-Type: text/html\n")
print("<!DOCTYPE html><html><head><title>Env Info</title></head><body>")
print("<h1>Request Environment (Conceptual)</h1>")
print("<pre>")
# 在真实的 CGI 环境中,这些由服务器填充。
# 在现代框架中,通过请求对象访问。
for param, value in os.environ.items():
# 仅显示几个相关的变量作为示例
if param in ['REQUEST_METHOD', 'QUERY_STRING', 'REMOTE_ADDR', 'HTTP_USER_AGENT']:
print(f"**{param}:** {value}<br>")
print("</pre>")
print("</body></html>")

这是从浏览器向服务器发送数据的两种最常见的 HTTP 方法:

数据作为查询字符串附加到 URL 中(例如,/search?query=python&page=1)。

  • 在浏览器地址栏和服务器日志中可见。
  • 适用于非敏感数据,如搜索词或页码。
  • 长度有限(取决于浏览器/服务器,通常约为 2048 个字符)。
  • 幂等(重复请求理论上应产生相同效果)。
  • 在 CGI 中,数据位于 QUERY_STRING 环境变量中。
  • 在现代框架中,通过请求对象属性(如 request.args)访问。

数据在 HTTP 请求的主体中发送。

  • 在 URL 中不可见。
  • 适用于敏感数据(如密码)或大量数据(例如,文件上传、长表单)。
  • 没有实际的长度限制。
  • 不一定是幂等(重复操作可能会再次执行,例如,提交订单)。
  • 在 CGI 中,数据从标准输入读取,长度由 CONTENT_LENGTH 指定。
  • 在现代框架中,通过属性(如 request.form(用于表单数据)或 request.data/request.json(用于原始/JSON 主体))访问。

Web 表单允许用户输入数据。提交时,这些数据使用 GET 或 POST 方法发送。

HTML 表单示例:

<!DOCTYPE html>
<html>
<head><title>Form Example</title></head>
<body>
<form action="/process-form" method="post">
<label for="fname">First Name:</label>
<input type="text" id="fname" name="first_name"><br><br>
<label for="lname">Last Name:</label>
<input type="text" id="lname" name="last_name"><br><br>
<input type="submit" value="Submit">
</form>
</body>
</html>

Python 代码片段(演示数据访问逻辑 - 框架风格):

# Assume 'request' is an object provided by a web framework
# Example for POST data access (e.g., Flask/Django style)
# first_name = request.form.get('first_name', 'Guest')
# last_name = request.form.get('last_name', '')
# Example for GET data access (e.g., Flask/Django style)
# query = request.args.get('query', '')
def process_form_data(form_data):
"""用于处理解析后的表单数据的概念函数。"""
first_name = form_data.get('first_name', 'Guest')
last_name = form_data.get('last_name', '')
# 生成响应(框架处理请求头细节)
response_body = f"<!DOCTYPE html><html><head><title>Form Received</title></head>"
response_body += f"<body><h2>Hello {first_name} {last_name}</h2></body></html>"
return response_body
# 在真实的应用程序中,框架会路由请求并
# 将解析后的表单数据(例如,request.form)提供给处理函数。
# 模拟调用示例:
# received_data = {'first_name': 'Zara', 'last_name': 'Ali'}
# html_output = process_form_data(received_data)
# print(html_output) # 框架会将此作为响应发送

复选框(Checkboxes)、单选按钮(radio buttons)、文本区域(text areas)和下拉列表(dropdowns)的处理方式类似:它们的 name 属性成为键,它们的 value 属性(或文本区域的内容)成为值。复选框如果选中,可能会为同一个名称发送多个值;如果未选中,则可能完全不发送该键。框架会将这些解析为适当的数据结构(通常是类似字典的对象)。

Cookie 是浏览器存储的小块数据,会在后续请求中发送回服务器。它们用于在无状态的 HTTP 请求之间维护状态(如用户会话)。

  • 设置 Cookie: 通过 Set-Cookie HTTP 响应头完成。包含 Cookie 名称/值以及可选属性,如 Expires、Max-Age、Domain、Path、Secure、HttpOnly、SameSite。
  • 检索 Cookie: 浏览器将存储的 Cookie 在 Cookie HTTP 请求头中发送回服务器。
  • 现代实践: 框架提供了方便设置和获取 Cookie 的方法(例如,response.set_cookie(...) 和 request.cookies.get(...))。

HTML 表单中使用 enctype="multipart/form-data" 和 <input type="file"> 允许用户上传文件。

服务器接收文件数据作为请求主体的一部分。在 CGI 时代,解析 multipart 数据很复杂。现代框架通过请求对象(例如,Flask/Django 中的 request.files)提供对上传文件的便捷访问,处理了复杂的解析过程。

示例:处理上传文件(框架风格逻辑)

# Assume 'request' is a framework request object
# Assume 'secure_filename' is a utility to sanitize filenames
# Assume UPLOAD_FOLDER is a configured path
# file_storage = request.files.get('filename')
def save_uploaded_file(file_storage):
"""用于处理上传的文件数据的概念函数。"""
message = "No file was uploaded."
if file_storage and file_storage.filename:
try:
# 清理来自用户输入的文件名至关重要
# filename = secure_filename(file_storage.filename)
# save_path = os.path.join(UPLOAD_FOLDER, filename)
# file_storage.save(save_path)
filename = file_storage.filename # 在实际代码中,请清理此内容!
message = f'The file "{filename}" was uploaded successfully (simulated).'
# 在真实的应用程序中,您会使用 file_storage.save()
except Exception as e:
message = f"An error occurred: {e}"
response_body = f"<!DOCTYPE html><html><body><p>{message}</p></body></html>"
return response_body
# 模拟调用:
# class MockFileStorage:
# filename = 'example.txt'
# def save(self, path): print(f'模拟保存到 {path}')
#
# uploaded_file = MockFileStorage()
# html_output = save_uploaded_file(uploaded_file)
# print(html_output)

安全注意:务必验证和清理用户输入,特别是文件名和文件内容,以防止目录遍历等安全漏洞。

要提示浏览器下载文件而不是显示它,服务器需要发送特定的 HTTP 请求头:

  • Content-Type: application/octet-stream(或如果已知,则使用更具体的 MIME 类型)告诉浏览器这是二进制数据。
  • Content-Disposition: attachment; filename="your_file_name.ext" 建议了下载对话框中的文件名。

现代框架通常提供帮助函数(例如,Flask 中的 send_file)来生成这些请求头并正确地流式传输文件内容。

虽然直接使用 Python 进行 CGI 编程已经过时,但处理请求、处理数据、管理状态(Cookie)和生成响应等底层概念对于 Web 开发仍然至关重要。现代 Python Web 框架建立在这些概念之上,提供了更高效、更安全、对开发者更友好的抽象。

进一步学习:

  • 探索流行的 Python Web 框架:Flask、Django、FastAPI。
  • 了解 WSGI(Web Server Gateway Interface)和 ASGI(Asynchronous Server Gateway Interface)。