flask的request对象中关于请求参数的获取有几个不同的属性,例如 args、form、data、json。估计大部分人一开始也分不清什么情况下哪个属性有值,哪个属性没值,这篇文章全面整理了这几个属性之间的区别和使用场景。
flask.request对象其实是对HTTP请求的一种封装,我们知道HTTP 请求由请求行、请求头、请求体三部分组成
-
请求行指定了请求方法,请求路径以及HTTP版本号
-
请求头是浏览器向服务器发送请求的补充说明,比如content-type 告诉服务器这次请求发送的数据类型是什么
-
请求体是浏览器向服务器提交的数据,请求头与请求体之间用空行隔开。一般在POST或者PUT方法中带有请求体数据
而flask中request对象中的form、data、json这三个属性其实是flask根据不同的content-type类型将HTTP请求体进行转换而来的数据,这几个属性的类型一般都是字典或者是字典的子类。
先简单介绍下args
args
args属性是请求路径中的查询参数,例如:/hello?name=zs
, args 解析出来的数据是一个类似字典的对象,它的值是:
args = {"name": 'zx'}
form
form 顾名思义是表单数据,当请求头content-type 是 application/x-www-form-urlencoded 或者是 multipart/form-data 时,请求体的数据才会被解析为form属性。
application/x-www-form-urlencoded 是浏览器的form表单默认使用的content-type。例如
<form action="http://localhost:8000/demo" method="post">
<input type="text" name="username">
<input type="password" name="password">
<button>登录</button>
</form>
发送HTTP请求类似这样:
POST http://localhost:8000/demo HTTP/1.1
Content-Type: application/x-www-form-urlencoded;charset=utf-8
username=zs&password=123456
服务器接收到数据
@app.route("/hello", methods=["GET", "POST"])
def hello():
print("content_type:", request.headers.get("content_type"))
print('form:', request.form)
print('data:', request.data)
return "hello"
打印:
content_type: application/x-www-form-urlencoded
form: ImmutableMultiDict([('username', 'zs'), ('password', '123456')])
data: b''
form的值一个不可变的字典对象,里面的值就是我们提交的表单字段,注意这时data是一个空字符串(byte)
files
当浏览器上传文件时,form表单需要指定 enctype为 multipart/form-data
<form action="http://localhost:8000/demo" method="post" enctype="multipart/form-data">
<input type="text" name="myTextField">
<input type="checkbox" name="myCheckBox">Check</input>
<input type="file" name="myFile">
<button>Send the file</button>
</form>
发送的HTTP请求是这样
POST /demo HTTP/1.1
Host: localhost:8000
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:50.0) Gecko/20100101 Firefox/50.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Content-Type: multipart/form-data; boundary=---------------------------8721656041911415653955004498
Content-Length: 465
-----------------------------8721656041911415653955004498
Content-Disposition: form-data; name="myTextField"
Test
-----------------------------8721656041911415653955004498
Content-Disposition: form-data; name="myCheckBox"
on
-----------------------------8721656041911415653955004498
Content-Disposition: form-data; name="myFile"; filename="test.txt"
Content-Type: text/plain
Simple file.
-----------------------------8721656041911415653955004498--
请求体用 boundary 分割不同的字段,每个字段以boundary 开始,接着是内容的描述信息,然后是回车换行,最后是内容部分。比如 下面是myTextField 这个字段完整的信息。
-----------------------------8721656041911415653955004498
Content-Disposition: form-data; name="myTextField"
Test
注意,如果表单提交的字段是一个文件,那么该这段里除了content-disposition外,里面还会有一个content-type的字段,来说明该文件的类型。
我们可以用postman模拟一个请求, 指定content-type为 multipart/form-data, 指定test字段的类型为file, 上传的文件名test.md
@app.route("/hello", methods=["GET", "POST"])
def hello():
print(request.headers.get("content_type"))
print("files:", request.files)
return ""
打印
content_type: multipart/form-data; boundary=--------------------------825074346815931435776253
files: ImmutableMultiDict([('test', <FileStorage: 'test.md' ('text/markdown')>)])
意味着当请求头content-type是multipart/form-data,而且请求体中的字段中还有content-type属性时(说明是文件上传),flask会把它当做文件来处理,所以这时候 files 这个属性就有值了。
data
发送的请求体中,当content-type不是multipart/form-data、application/x-www-form-urlencoded 这两种类型时,data才会有值,例如我现在用postman指定的content-type是text/plain
@app.route("/hello", methods=["GET", "POST"])
def hello():
print("content_type:", request.headers.get("content_type"))
print("data:", request.data)
print("form:", request.form)
print("files:", request.files)
return ""
打印结果
content_type: text/plain
data: b'{"name":"zs"}'
form: ImmutableMultiDict([])
files: ImmutableMultiDict([])
form和files都是空,data是一个byte类型的数据
json
如果我将content-type指定为application/json, flask就会将接收到的请求体数据做一次json编码转换,将字符串转换为字典对象,赋值给属性json
@app.route("/hello", methods=["GET", "POST"])
def hello():
print("content_type:", request.headers.get("content_type"))
print("data:", request.data)
print("form:", request.form)
print("json:", request.json)
return ""
打印
content_type: application/json
data: b'{"name":"zs"}'
form: ImmutableMultiDict([])
json: {'name': 'zs'}
files: ImmutableMultiDict([])
get_json()
如果浏览器传过来的是json格式的字符串数据,但是请求头中又没有指定content-type :application/json,如果你直接调用request.json 会直接报错,返回401错误
<!doctype html>
<html lang=en>
<title>400 Bad Request</title>
<h1>Bad Request</h1>
<p>Did not attempt to load JSON data because the request Content-Type was not 'application/json'.</p>
这时候我们可以通过get_json方法并指定参数force=True,强制要求做json编码转换,它与 json属性返回的类型是一样的,都是一个字典对象。
@app.route("/hello", methods=["GET", "POST"])
def hello():
print("content_type:", request.headers.get("content_type"))
print("get_json:", request.get_json(force=True))
return "hello"
打印
content_type: text/plain
get_json: {'name': 'zs'}
values
values 是 args 和 form 两个字段的组合
@app.route("/hello", methods=["GET", "POST"])
def hello():
print("content_type:", request.headers.get("content_type"))
print("args:", request.args)
print("form:", request.form)
print("values:", request.values)
return "hello"
打印
content_type: application/x-www-form-urlencoded
args: ImmutableMultiDict([('gender', '1')])
form: ImmutableMultiDict([('name', 'zs')])
values: CombinedMultiDict([ImmutableMultiDict([('gender', '1')]), ImmutableMultiDict([('name', 'zs')])])
总结
这么多属性什么时候有值什么时候没值,其实完全取决于我们请求头content-type是什么,如果是以表单形式multipart/form-data、application/x-www-form-urlencoded 提交的数据,form或者files属性有值,如果是以application/json提交的数据,data、json就有值。而 args 是通过解析url中的查询参数得来的。
https://flask.palletsprojects.com/en/2.2.x/api/#incoming-request-data
https://github.com/pallets/werkzeug/blob/main/src/werkzeug/wrappers/request.py
https://stackoverflow.com/questions/10434599/get-the-data-received-in-a-flask-request
https://www.jianshu.com/p/24c5433ce31b
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Basics_of_HTTP/MIME_types
https://imququ.com/post/four-ways-to-post-data-in-http.html
关注公众号「Python之禅」,回复「1024」免费获取Python资源