理解 flask.request中form、data、json属性的区别

By 刘志军 , 2022-08-23, 分类: flask

flask的request对象中关于请求参数的获取有几个不同的属性,例如 args、form、data、json。估计大部分人一开始也分不清什么情况下哪个属性有值,哪个属性没值,这篇文章全面整理了这几个属性之间的区别和使用场景。

flask.request对象其实是对HTTP请求的一种封装,我们知道HTTP 请求由请求行、请求头、请求体三部分组成

image-20220806100926271

而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

image-20220806104549571

@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

image-20220806105212078

@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

image-20220806125019373

@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 &#39;application/json&#39;.</p>

这时候我们可以通过get_json方法并指定参数force=True,强制要求做json编码转换,它与 json属性返回的类型是一样的,都是一个字典对象。

image-20220806125455114

@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资源

python之禅