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
argsargs属性是请求路径中的查询参数,例如:/hello?name=zs, args 解析出来的数据是一个类似字典的对象,它的值是:
args = {"name": 'zx'}formform 顾名思义是表单数据,当请求头content-type 是 application/x-www-form-urlencoded 或者是 multipart/form-data 时,请求体的数据才会被解析为form属性。
application/x-www-form-urlencoded 是浏览器的form表单默认使用的content-type。例如
登录发送HTTP请求类似这样:
POST http://localhost:8000/demo HTTP/1.1Content-Type: application/x-www-form-urlencoded;charset=utf-8username=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-urlencodedform: ImmutableMultiDict([('username', 'zs'), ('password', '123456')])data: b''form的值一个不可变的字典对象,里面的值就是我们提交的表单字段,注意这时data是一个空字符串(byte)
files当浏览器上传文件时,form表单需要指定 enctype为 multipart/form-data
CheckSend the file发送的HTTP请求是这样
POST /demo HTTP/1.1Host: localhost:8000User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:50.0) Gecko/20100101 Firefox/50.0Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8Accept-Language: en-US,en;q=0.5Accept-Encoding: gzip, deflateConnection: keep-aliveUpgrade-Insecure-Requests: 1Content-Type: multipart/form-data; boundary=---------------------------8721656041911415653955004498Content-Length: 465-----------------------------8721656041911415653955004498Content-Disposition: form-data; name="myTextField"Test-----------------------------8721656041911415653955004498Content-Disposition: form-data; name="myCheckBox"on-----------------------------8721656041911415653955004498Content-Disposition: form-data; name="myFile"; filename="test.txt"Content-Type: text/plainSimple file.-----------------------------8721656041911415653955004498--请求体用 boundary 分割不同的字段,每个字段以boundary 开始,接着是内容的描述信息,然后是回车换行,最后是内容部分。比如 下面是myTextField 这个字段完整的信息。
-----------------------------8721656041911415653955004498Content-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=--------------------------825074346815931435776253files: ImmutableMultiDict([('test', )])意味着当请求头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/plaindata: 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/jsondata: b'{"name":"zs"}'form: ImmutableMultiDict([])json: {'name': 'zs'}files: ImmutableMultiDict([])get_json()如果浏览器传过来的是json格式的字符串数据,但是请求头中又没有指定content-type :application/json,如果你直接调用request.json 会直接报错,返回401错误
400 Bad RequestBad RequestDid not attempt to load JSON data because the request Content-Type was not 'application/json'.这时候我们可以通过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/plainget_json: {'name': 'zs'}valuesvalues 是 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-urlencodedargs: 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资源