当前位置: 首页 > news >正文

【Django】-2- 处理HTTP请求

一、request 请求 

 

先理解:Request 是啥?

用户访问你的网站时,会发一个 “请求包” 📦 ,里面装着:

  • 想访问啥路径?用啥方法(GET/POST 等)?
  • 带了啥头信息(比如 Cookies)?
  • 有没有传表单、文件?
  • 客户端 IP 是啥?
  • 用户登录状态是啥?

Django 把这些信息封装成 HttpRequest 对象(一般叫 request ),视图函数里拿到它,就能 “拆包” 提取信息啦~

Request 请求的分类角度 🌐

这张图是 “总纲”,告诉你可以从 三个维度 分析 request :

角度关注啥内容?类比(超通俗)
HTTP 协议角度请求行(方法、路径)、请求头、请求正文快递单上的 “地址、电话、包裹内容”
TCP 协议角度客户端 IP(用户的网络地址)快递单上的 “发件人 IP 地址”
网站功能角度用户身份、Session(会话数据)快递单上的 “收件人信息、专属标记”

 

HTTP 协议角度 👉 拆 “请求的核心内容”

代码里的 echo 视图,把 HTTP 请求的关键部分 提取出来,返回给用户看~

def echo(request: HttpRequest):# 用 f-string 拼接 HTML 内容,把 request 的信息嵌入进去html = f"""请求行  请求方法:{request.method}   # 比如 GET/POST/PUT 等,告诉服务器“想干啥”请求路径:{request.path}     # 比如 /hello123 ,告诉服务器“访问哪个页面”查询字符串:{request.GET}     # 比如 ?name=beifan ,是 URL 里带的参数请求头  请求头:{request.headers}    # 装着浏览器信息、Cookies 等,像“附加说明”请求正文  {request.body}               # POST 请求时,表单、JSON 数据会存在这里"""return HttpResponse(html)

超通俗解释

  • 你访问网站时,浏览器会发一个 “超级详细的快递” 给服务器~
  • request.method 是 “快递单上的操作类型”(比如 “我要查询”“我要寄件”)
  • request.path 是 “快递单上的地址”(比如 “北京市朝阳区 xxx 路”)
  • request.headers 是 “快递单上的备注”(比如 “ fragile 易碎”“需要冷藏”)
  • request.body 是 “包裹里的东西”(如果是 POST 请求,表单、文件会存在这里)

 

TCP 协议角度 👉 拿 “客户端 IP”

代码里的 echo 视图,专门提取 用户的 IP 地址 :

def echo(request: HttpRequest):# 提取客户端 IP ,这里处理了“代理转发”的情况(很多网站会用反向代理,比如 Nginx)ip = request.META.get("HTTP_X_FORWARDED_FOR", request.META['REMOTE_ADDR'])html = f"""客户端IP:{ip}  # 用户的网络地址,比如 192.168.1.100"""return HttpResponse(html)

超通俗解释

  • 每个设备上网都有 “网络身份证”(IP 地址),服务器需要知道 “谁在访问我”~
  • 有时候用户是通过 “代理服务器” 访问的(比如公司网络、CDN),HTTP_X_FORWARDED_FOR 就是 “真实身份证”,REMOTE_ADDR 是 “代理的身份证”~
  • 这段代码会优先拿 “真实身份证”,拿不到就用 “代理的身份证”~

 

网站功能角度 👉 拿 “用户身份 & Session”

代码里的 echo 视图,提取 和用户身份、会话相关的信息 :

def echo(request: HttpRequest):html = f"""当前用户:{request.user}        # 登录后显示用户名,没登录可能是匿名用户Session数据:{request.session.get("name", "beifan")}  # 从 Session 里取数据,没有就默认 "beifan""""return HttpResponse(html)

超通俗解释

  • request.user:告诉你 “当前是谁在访问”~ 登录后是用户名,没登录就是 “匿名用户”(像 “快递单上的收件人姓名”)
  • request.session:是服务器给用户的 “专属小本本”📒 ,用户每次访问都带着,能存登录状态、偏好设置等~
    • 比如你登录后,session 里存了 {"name": "beifan"} ,下次访问就能直接拿到!

 

核心逻辑 🌟

所有代码都是围绕 “如何从 request 里提取信息” 展开:

  1. 从 HTTP 协议角度:拆请求行、头、正文 → 看 “请求的原始内容”
  2. 从 TCP 协议角度:拆客户端 IP → 看 “谁在访问”
  3. 从 网站功能角度:拆用户身份、Session → 看 “用户是谁,有啥专属数据”

最终目的:让你明白 request 里 “藏着这么多有用的信息”,以后写视图时,想拿啥就从对应的地方取~

 

二、认识几个格式转换的函数 

json.dumps是 Python 标准库json中的一个函数 ,它的主要作用是将 Python 对象(如字典、列表等)转换为 JSON 格式的字符串

import json# 示例1:转换字典
data_dict = {"name": "Alice", "age": 25}
json_str = json.dumps(data_dict)
print(json_str)
# 输出: {"name": "Alice", "age": 25}# 示例2:转换列表
data_list = [1, 2, 3, "four"]
json_str = json.dumps(data_list)
print(json_str)
# 输出: [1, 2, 3, "four"]

  • ensure_ascii:默认为True,在这种情况下,非 ASCII 字符会被转义成 Unicode 编码形式(如\uXXXX)。如果设置为False,则会以原始的非 ASCII 字符形式输出。
data = {"名字": "张三"}
print(json.dumps(data))
# 输出: {"\u540d\u5b57": "\u5f20\u4e09"}print(json.dumps(data, ensure_ascii=False))
# 输出: {"名字": "张三"}

  • indent:用于设置缩进,使输出的 JSON 字符串更具可读性,常用于调试或格式化输出。它可以是一个整数,表示缩进的空格数,也可以是字符串(如\t表示制表符)。
data = {"name": "Bob", "hobbies": ["reading", "swimming"]}
print(json.dumps(data, indent=4))
# 输出: 
# {
#     "name": "Bob",
#     "hobbies": [
#         "reading",
#         "swimming"
#     ]
# }

  • sort_keys:默认为False,如果设置为True,在转换字典时,会按照键的字母顺序对键值对进行排序后输出。
data = {"c": 3, "a": 1, "b": 2}
print(json.dumps(data))
# 输出: {"c": 3, "a": 1, "b": 2}print(json.dumps(data, sort_keys=True))
# 输出: {"a": 1, "b": 2, "c": 3}

 

json.loads 是 Python 的 json 模块里的一个函数,作用是 把 JSON 格式的字符串,转换成 Python 能直接用的数据类型(比如字典、列表)

 

data = json.loads(request.body)

  • request.body:Django 里 request 对象的 body 属性,存的是 客户端发过来的原始数据(二进制字符串),比如客户端 POST 一个 JSON 数据 {"c": 3}request.body 拿到的是 b'{"c": 3}'(二进制格式的 JSON 字符串 )。
  • json.loads(...):把这个二进制字符串(转成普通字符串后),解析成 Python 的字典 {"c": 3},这样 data 就可以像普通字典一样用(比如 data['c'] 取到 3 )。

 

json.loads 的核心作用:“JSON → Python”

import json# 这是一个 JSON 格式的字符串(字符串里的内容符合 JSON 语法)
json_str = '{"c": 3}'# 用 json.loads 解析,转成 Python 字典
python_dict = json.loads(json_str)print(python_dict)  # 输出: {'c': 3}
print(type(python_dict))  # 输出: <class 'dict'>

  • 输入:JSON 格式的字符串(必须用双引号,符合 JSON 规范 )。
  • 输出:Python 的字典(或列表、数字等,取决于 JSON 内容 )。

 

三、为什么需要 json.loads

因为客户端和服务器通信时,只能传 “字符串”(HTTP 协议的限制 )。

如果客户端要传复杂数据(比如字典、列表),必须先转成 JSON 字符串(json.dumps 干的事 ),服务器收到后,再用 json.loads 转成 Python 能处理的数据类型。

就像你给外国朋友写信,得用 “国际通用语言(JSON 字符串)”,朋友收到后,用 json.loads 翻译成自己能懂的语言(Python 字典 )。

 

和 json.dumps 的关系(“反向操作”)

  • json.dumps:把 Python 数据(字典、列表等)转成 JSON 字符串(Python → JSON )。
  • json.loads:把 JSON 字符串转成 Python 数据(JSON → Python )。

这俩是 “一对”,经常一起用在前后端数据交互场景:

  1. 前端用 JSON.stringify(类似 json.dumps )把数据转成 JSON 字符串,发给后端。
  2. 后端用 json.loads 把 JSON 字符串转成 Python 数据,处理完后,再用 json.dumps 转回 JSON 字符串,返回给前端。

 

三、认识Querydict类型 

QueryDict 本质是 Python 对象,但它又和普通 dict 不太一样,这得从 Django 的设计逻辑说起

 

⭐ 先明确:所有能在 Python 里操作的东西,都是 “Python 对象”

Python 里几乎一切皆对象

  • 普通字典 {"a": 1} 是对象(dict 类型)
  • 字符串 'abc' 是对象(str 类型)
  • 甚至数字 123 也是对象(int 类型)

所以 QueryDict 作为 Django 定义的自定义类,自然也是 Python 对象,只不过它是 Django 为了处理 HTTP 请求参数,专门设计的 “特殊字典”~

 

⭐ QueryDict 是 “特殊定制的 Python 对象”

Django 里的 QueryDict 继承自 Python 的 dict,但增强了处理 HTTP 请求参数的能力,核心是为了应对两个场景:

1. 支持 “一个键对应多个值”(HTTP 查询参数的特性)

HTTP 的查询参数(如 ?a=1&a=2)允许同一个键(a)出现多次,普通 dict 会直接覆盖值(只能存 {'a': 2} ),但 QueryDict 可以存成列表({'a': ['1', '2']} )。

比如你的请求 URL 是 ?a=1&a=2,Django 会把参数解析成:

request.GET  # QueryDict: {'a': ['1', '2']}

 

这样就能正确处理 “同一个键传多个值” 的场景(比如复选框多选)。

 

2. 提供更贴合 HTTP 请求的方法

QueryDict 还加了很多实用方法,比如:

  • getlist('a'):直接获取键 a 对应的所有值(返回 ['1', '2'] )
  • urlencode():把 QueryDict 转成 URL 编码的字符串(a=1&a=2 )

这些方法让处理 HTTP 请求参数更方便,是普通 dict 做不到的~

 

四、为什么字典是单引号 json是双引号 

这个问题涉及 Python 语法和 JSON 规范的差异,本质是两种 “数据格式” 的设计约定,咱们用 “方言 vs 国际语” 的思路理解~ 🗣️

Python 字典用单引号:语法灵活性

Python 里的字典,键值对的字符串可以用 单引号 或 双引号,甚至混用,这是 Python 语法的灵活性:

# 合法的 Python 字典
my_dict = {'name': "beifan",  # 单引号、双引号混用"age": 18
}

Python 这么设计,是为了让开发者写代码时更自由(比如字符串里本身有双引号,就可以用单引号包起来,避免转义 )。

 

JSON 用双引号:国际标准规范

JSON 是 “跨语言的数据交换格式”,它的语法有严格规范,其中一条就是:字符串必须用双引号包裹

这是因为 JSON 的设计目标是 “让所有语言都能统一解析”,而不同语言对字符串引号的支持不同:

  • 比如 JavaScript 里,对象的键必须用双引号({"name": "beifan"} 是合法的,{'name': 'beifan'} 会报错 )。
  • 为了兼容所有语言,JSON 强制规定用双引号,这样不管是 Python、JavaScript、Java 还是其他语言,都能一致解析。

 

json.dumps 的作用:自动转换引号

当你用 json.dumps 把 Python 字典转成 JSON 字符串时,Python 会自动做两件事:

  1. 把字典里的单引号,替换成双引号(符合 JSON 规范)。
  2. 处理其他 Python 特有的语法(比如 None 转成 nullTrue 转成 true )。

my_dict = {'name': 'beifan'}
json_str = json.dumps(my_dict)
# 输出: '{"name": "beifan"}'(双引号)

 

总结:两种格式的设计目标不同

  • Python 字典:是 Python 语言内部使用的数据结构,语法灵活,方便开发者写代码。
  • JSON:是跨语言的 “数据交换协议”,语法严格,确保所有语言都能统一解析。

所以 Python 字典用单引号(或双引号)都行,但转成 JSON 后必须用双引号 —— 这是为了让其他语言能看懂,实现 “跨语言交流”~

简单说:单引号是 Python 的 “方言”,双引号是 JSON 的 “国际语”json.dumps 就是 “翻译官”,把 Python 方言翻译成国际通用的 JSON 语~ 😊

 

五、测试用例实操 

项目结构

Django 服务端(views.py

写了 3 个视图函数(echo/submit/result),负责接收请求、处理数据、返回响应

测试客户端(test_api.py

用 requests 库模拟客户端,给 Django 服务发 POST 请求(带 JSON 数据)

验证服务端返回的响应是否符合预期

 

🐇 echo 视图函数

def echo(request: HttpRequest):# 将 request.GET(QueryDict 类型)转成 JSON 字符串返回html = f"{json.dumps(request.GET)}"  return HttpResponse(html)

(request:HttpRequest)类型注释:别人看到 request: HttpRequest,不用猜就知道:这个参数是 Django 封装的 “请求对象”,里面有 methodbody 这些属性

  • request.GET:Django 中专门用来接收 URL 查询参数(如 ?a=1)的对象,类型是 QueryDict
  • 作用:访问该视图时,会把 URL 里的查询参数转成 JSON 字符串返回给客户端。(JSON 是前后端都能 “看懂” 的 “通用语言”,把数据转成 JSON 再返回,能让客户端(浏览器、App 等)更方便地处理数据~)

 

在这个 echo 视图函数中,HttpResponse(html) 会将经过处理后的 html(这里是包含 URL 查询参数的 JSON 字符串 )作为响应体,加上默认的响应头(如 Content-Type: text/html; charset=utf-8 )和状态码 200,一起返回给客户端。

客户端(通常是浏览器)接收到这个 HTTP 响应后,会根据响应头中的 Content-Type 来决定如何处理响应体内容。如果是 text/html,就会渲染展示 HTML 页面;如果是 application/json,则可能会将其解析为 JavaScript 对象进行后续操作。

HttpResponse(html) 是 Django 视图函数将处理结果返回给客户端的关键步骤,它按照 HTTP 协议的规范,将数据包装成包含状态行、响应头和响应体的完整 HTTP 响应,从而实现服务器与客户端之间的数据交互和信息传递。

 

🐇 submit 视图函数

def submit(request):print(request.body)  # 打印请求体(一般 POST 请求会用到,这里可能无实际数据)# 读取本地 submit.html 文件内容并返回html = open('submit.html', encoding="utf-8").read()  return HttpResponse(html)

  • 作用:访问该视图时,返回 submit.html 的内容,常用来展示前端页面。

🐇 test_api.py 测试逻辑

import json
import requests  # 发送 GET 请求(带查询参数 ?a=1)
resp = requests.get("http://127.0.0.1:8000/beifan/echo?a=1")  
print(resp.status_code)  # 打印响应状态码(如 200 表示成功)
print(resp.text)         # 打印响应内容(echo 视图返回的 JSON 字符串)# 断言:期望响应内容是 {"a": 1} 的 JSON 字符串
assert resp.text == json.dumps({"a": "1"})  

 

为什么表单里面没有数据类型({"a":"1"}而不是{"a":1}) 都按照字符串进行处理 ?

表单是 “传递文本的载体”,类型由后端决定

表单的作用就像 “快递单”:前端只负责把用户输入的内容以 “字符串” 形式传给服务器,不关心内容的实际类型。而服务器(后端)拿到字符串后,再根据业务场景转成需要的类型(数字、布尔值、日期等)。

这种设计看似 “粗糙”,实则是 Web 开发中 “兼容性” 和 “灵活性” 的平衡 —— 既保证了所有系统都能互通,又让开发者能按需处理数据~

想解决 QueryDict 转字典后,值带列表中括号 [ ] 的问题!

比如想把 {"b": ["2"]} 变成 {"b": "2"},这确实是 Django 处理表单数据时的常见需求,咱们一步步讲清楚怎么实现~

 

先明白为什么会有中括号 [ ]

request.POST 是 QueryDict 类型,它的设计是 “一个键可以对应多个值”(比如表单里多个同名的复选框),所以即使只有一个值,也会存成列表形式(["2"])。

  • 表单提交 b=2request.POST 实际是 QueryDict({'b': ['2']})
  • 直接转字典会变成 {'b': ['2']},JSON 序列化后就是 {"b": ["2"]}

 

  • 前端表单里的字段(比如输入框、单选框)只会传一个值(比如 b=2 不可能同时传 b=2&b=3)。
  • 你明确知道 “每个键只有一个值”,列表里的其他元素不存在(或者不需要)。

这时候,取列表的第一个元素 v[0] 是安全的,比如:

# 原代码:v 是列表 ['2'],存成 data[k] = v → 结果 {'b': ['2']}
# 改后代码:取 v[0] → 结果 {'b': '2'}
for k, v in request.POST.items():data[k] = v[0]  # 只取列表第一个元素,自然去掉了中括号

 

六、基于 Django Session 的 “访问计数” 功能 

整体流程:“客户端请求 → 服务端计数 → 测试验证”

  1. Django 服务端echo 视图通过 request.session 记录用户的 “访问次数”,每次访问计数 +1,再返回当前计数。
  2. 测试客户端:用 requests.Session() 模拟同一个客户端,多次发送 POST 请求到 echo 视图,验证每次返回的计数是否符合预期(第一次 1、第二次 2、第三次 3…)。

 

Django 服务端:echo 视图(核心计数逻辑)

from django.http import HttpRequest, HttpResponse
import jsondef echo(request: HttpRequest):# 1. 从 Session 中获取 'num',默认值为 0(第一次访问时,Session 中无 'num')num = request.session.get("num", 0)  # 2. 计数 +1(每次访问,计数自增)num = num + 1  # 3. 把新的计数存回 Session(下次访问时,能拿到更新后的值)request.session['num'] = num  # 4. 转成 JSON 字符串,返回给客户端html = f"{json.dumps({'num': num})}"  return HttpResponse(html)

num 在这里是一个 “计数器变量”,专门用来记录 “同一个用户访问当前视图的次数”

 

⭐ request.session:用户的 “专属储物柜”

request.session 是 Django 为每个访问网站的用户(客户端)准备的 “专属储物柜”,用来存这个用户的临时数据(比如登录状态、访问次数等)。

  • 每个用户的 session 是独立的(就像每个储物柜有不同的钥匙),A 用户的 session 里的数据,B 用户看不到。
  • 这个 “储物柜” 里的数据以 键值对 形式存储(类似字典),比如可以存 {"num": 3, "username": "张三"}

 

⭐ get("num", 0):安全地取数据

这是字典(或类字典对象)的常用方法,作用是:

  • 尝试从 session 里找 键为 num 的值(比如之前存过 num=2,就会取到 2)。
  • 如果找不到这个键(比如用户第一次访问,session 里还没有 num),就返回 默认值 0

 

⭐ 访问计数

假设这是用户第一次访问网站:

  • request.session 里还没有 num 这个键,所以 request.session.get("num", 0) 会返回 0 → num = 0

用户第二次访问时:

  • 因为第一次访问后,代码里已经把 num 存回了 session(比如 request.session['num'] = 1),所以这次会取到 1 → num = 1

以此类推,每次访问都会拿到上一次存的计数,实现 “累加” 效果。

测试客户端:requests.Session() 模拟用户请求

测试代码用 requests 库模拟客户端,重点是用 requests.Session() 保持 Session(Cookie),让服务端认为是 “同一个用户的多次请求”。

import json
import requests# 1. 创建 Session 对象(自动管理 Cookie,模拟同一个客户端)
session = requests.Session()  # 2. 第一次请求:模拟客户端访问 echo 视图
resp = session.post("http://127.0.0.1:8000/beifan/echo?a=1",cookies={"d": "4"}  # 可选:手动设置 Cookie(实际 Django 会自动处理 Session Cookie)
)
print(resp.status_code)  # 打印状态码(预期 200)
print(resp.text)         # 打印响应内容(预期 {"num": 1})
# 验证:第一次访问,计数应为 1
assert resp.text == json.dumps({"num": 1})  # 3. 第二次请求:同一个 Session,再次访问 echo 视图
resp = session.post("http://127.0.0.1:8000/beifan/echo?a=1",cookies={"d": "4"}
)
print(resp.status_code)  
print(resp.text)         # 预期 {"num": 2}
# 验证:第二次访问,计数应为 2
assert resp.text == json.dumps({"num": 2})  # 4. 第三次请求:继续验证计数
resp = session.post("http://127.0.0.1:8000/beifan/echo?a=1",cookies={"d": "4"}
)
print(resp.status_code)  
print(resp.text)         # 预期 {"num": 3}
# 验证:第三次访问,计数应为 3
assert resp.text == json.dumps({"num": 3})  

 

客户端代码里的 session.post("http://127.0.0.1:8000/beifan/echo?a=1"),通过 URL 路径 /beifan/echo,借助 Django 的路由系统,精准 “命中” 了 echo 视图。

 

关键逻辑:requests.Session() 保持 Session
  • 为什么用 requests.Session()
    requests.Session() 会自动保存服务器返回的 Cookie,并在后续请求中自动带上这些 Cookie。这样,服务端通过 Cookie 识别到 “同一个用户”,request.session 就能正确关联到之前的会话,计数才会持续 +1。

  • 如果不用 Session()
    每次用 requests.post() 发请求,都是 “新的客户端”,服务端会认为是不同用户,num 每次都会从 0 开始(第一次请求 num=1,第二次请求又会从 0 开始,导致计数错误)。

 

完整执行流程(以第一次、第二次请求为例)

第一次请求(客户端→服务端→客户端)
  1. 测试代码执行 session.post(...),发送第一次请求。
  2. 服务端 echo 视图:
    • request.session.get("num", 0) → 取到 0(第一次访问,Session 中无 num)。
    • num = 0 + 1 = 1 → 存回 session["num"] = 1
    • 返回 {"num": 1}
  3. 测试客户端收到响应,assert 验证 resp.text == '{"num": 1}' → 通过。
第二次请求(同一个客户端再次访问)
  1. 测试代码执行 session.post(...)Session 对象自动带上第一次请求的 Cookie
  2. 服务端 echo 视图:
    • request.session.get("num", 0) → 取到 1(Session 中已存 num=1)。
    • num = 1 + 1 = 2 → 存回 session["num"] = 2
    • 返回 {"num": 2}
  3. 测试客户端收到响应,assert 验证 resp.text == '{"num": 2}' → 通过。

 

代码的核心目的

  • 服务端:通过 Session 实现 “用户访问计数”,每次访问计数 +1,体现 “状态保持”(知道是同一个用户的多次请求)。
  • 测试端:用 requests.Session() 模拟同一个客户端,验证每次访问的计数是否正确(第一次 1、第二次 2、第三次 3…),体现 “接口自动化测试”

http://www.dtcms.com/a/310386.html

相关文章:

  • HTTP客户端实现:深入理解Go的net/http包
  • Vue3 + Vite 项目中 API 代理配置问题分析与解决
  • 如何处理Y2K38问题
  • 驾驶场景安全带识别误检率↓76%:陌讯动态特征聚合算法实战解析
  • 【深度学习①】 | Numpy数组篇
  • 【从0开始学习Java | 第12篇】内部类
  • C语言:冒泡排序
  • VUE:学习路径
  • 机器学习:开启智能时代的钥匙
  • 前端学习日记(十七)
  • Unity3D制作UI动画效果
  • treeshaking,webpack,vite
  • 技术为核,口碑为盾:普瑞眼科成都市场“卷王”地位的形成逻辑
  • Canny边缘检测算法-个人记录
  • 计数组合学7.10(舒尔函数的组合定义)
  • 图片搜索1688的商品技术实现:API接口item_search_img
  • 嵌入式——C语言:俄罗斯方块
  • C#常见的转义字符
  • 国产开源大模型崛起:使用Kimi K2/Qwen2/GLM-4.5搭建编程助手
  • 浏览器渲染过程
  • VSCode Python 与 C++ 联合调试配置指南
  • web前端第一次作业
  • TwinCAT3编程入门2
  • 如何快速给PDF加书签--保姆级教程
  • TCP协议的特点和首部格式
  • 电力系统与变压器实验知识全总结 | 有功无功、同步发电机、短路空载实验、电压调整率、效率条件全讲透!
  • curl命令使用
  • 蒙特卡罗方法(Monte Carlo Method)_学习笔记
  • 【面板数据】全国31省名义、实际GDP及GDP平减指数数据(2000-2024年)
  • VR拍摄的流程与商业应用,实用的VR拍摄技巧