在 Web 开发中,GET 和 POST 是两种最常用的 HTTP 请求方法
理解 HTTP 中的 GET 和 POST 方法是 Web 开发的核心基础。
下面从本质、工作原理到实际应用进行深度解析:
一、本质区别:设计目的
| GET | POST | |
|---|---|---|
| 本质 | 获取数据(只读操作) | 提交数据(写操作) |
| 哲学 | "请给我这个资源" | "请处理我提交的这些数据" |
| 类比 | 在图书馆查询书籍目录(不改变书籍) | 向图书馆提交新书入库申请(改变库存) |
二、技术特性深度对比
1. 数据传输位置
### GET 请求示例
GET /search?q=apple&page=1 HTTP/1.1
Host: example.com
✅ 数据在 URL 中:
?q=apple&page=1 直接暴露在地址栏
### POST 请求示例
POST /login HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
username=admin&password=123456
✅ 数据在请求体(Body)中:
敏感信息不会显示在地址栏
2. 数据长度限制
| GET | POST | |
|---|---|---|
| 限制 | ✅ 约 2048 字符 | ❌ 理论上无上限 |
| 原因 | URL 长度被浏览器限制 | 数据通过 Body 分段传输 |
| 场景 | 适合简短查询参数 | 适合文件上传、长文本提交 |
3. 安全性对比
graph LR
A[请求方式] --> B{安全?}
B -->|GET| C[❌ 不安全]
C --> D[参数在URL中暴露<br>历史记录可查<br>易被网络嗅探]
B -->|POST| E[⛔ 相对安全]
E --> F[数据在Body中<br>需HTTPS加密保护<br>但仍可能被恶意抓包]
重要认知:POST 不是绝对安全!敏感数据必须配合 HTTPS 加密
4. 幂等性(Idempotence)
# GET 是幂等的(多次调用结果相同)
def handle_get_request():
return database.read_data() # 不会改变系统状态
# POST 是非幂等的(每次可能产生新结果)
def handle_post_request():
database.create_new_order() # 每次调用创建新订单
| GET | POST | |
|---|---|---|
| 幂等 | ✅ 是 | ❌ 否 |
| 影响 | 可重复请求 | 需防重复提交 |
三、底层协议行为
1. 浏览器缓存机制
sequenceDiagram
Browser->>Server: GET /product/123
Server-->>Browser: 200 OK (带缓存头)
Browser->>Browser: 缓存响应结果
用户再次访问->>Browser: 直接读缓存
- GET:可被缓存(Cache-Control 控制)
- POST:不会被缓存(刷新时浏览器会警告重复提交)
2. TCP 数据包结构差异
GET 请求数据包:
[ TCP Header | IP Header | HTTP Header | URL参数 ]
POST 请求数据包:
[ TCP Header | IP Header | HTTP Header | 空行 | Body数据 ]
✅ POST 比 GET 多一次 TCP 数据包传输(首次发送 Header,二次发送 Body)
四、如何选择?决策流程图
graph TD
A[需要执行什么操作?] --> B{是否改变服务器状态?}
B -->|否| C[使用 GET]
B -->|是| D{是否含敏感数据/文件?}
D -->|否| E[考虑 PUT/PATCH]
D -->|是| F[使用 POST]
C --> G[示例:搜索、页面跳转]
F --> H[示例:登录、支付、上传]
五、Flask 中的实战示例
GET 请求处理
@app.route('/search', methods=['GET']) # 可省略 methods
def search():
# 从URL获取参数: /search?q=python
query = request.args.get('q', '') # args 解析查询字符串
results = db.search(query)
return render_template('results.html', results=results)
POST 请求处理
from flask import request
@app.route('/register', methods=['POST'])
def register():
# 从表单体获取数据
username = request.form['username'] # form 解析表单数据
password = request.form['password']
# 创建新用户(非幂等操作)
user = User.create(username, password)
return redirect(url_for('login'))
混合处理 GET/POST
@app.route('/contact', methods=['GET', 'POST'])
def contact():
if request.method == 'POST':
# 处理表单提交
name = request.form['name']
email = request.form['email']
send_email(name, email)
return "提交成功!"
else:
# 显示空表单(GET请求)
return render_template('contact_form.html')
六、必须遵守的安全规范
-
永远不要用 GET 传输敏感数据
# 危险!密码暴露在URL @app.route('/login?user=admin&pass=123456') # ❌ # 正确做法 @app.route('/login', methods=['POST']) # ✅ -
POST 也需要防御 CSRF 攻击
<!-- 在表单中添加CSRF令牌 --> <form method="POST"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"> <!-- 其他字段 --> </form> -
关键操作使用 POST + 幂等校验
# 防止重复提交订单 @app.route('/order', methods=['POST']) def create_order(): if 'order_token' not in session: abort(400) token = request.form.get('token') if token != session['order_token']: abort(403) # 禁止重复提交 # 处理订单... session.pop('order_token', None) # 销毁令牌
终极理解心法
把 HTTP 方法看作对资源的操作指令:
- GET =
SELECT * FROM ...(数据库查询) - POST =
INSERT INTO ...(数据库插入)
就像你不会用 SELECT 语句删除数据一样,永远不要用 GET 请求执行写入操作。遵循这个原则,你就掌握了 RESTful 设计的精髓。