11

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

 2 years ago
source link: https://foofish.net/flask_requset_form_data_json.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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

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



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

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

image-20220806100926271
  • 请求行指定了请求方法,请求路径以及HTTP版本号

  • 请求头是浏览器向服务器发送请求的补充说明,比如content-type 告诉服务器这次请求发送的数据类型是什么

  • 请求体是浏览器向服务器提交的数据,请求头与请求体之间用空行隔开。一般在POST或者PUT方法中带有请求体数据

而flask中request对象中的form、data、json这三个属性其实是flask根据不同的content-type类型将HTTP请求体进行转换而来的数据,这几个属性的类型一般都是字典或者是字典的子类。

先简单介绍下args

args属性是请求路径中的查询参数,例如:/hello?name=zs, args 解析出来的数据是一个类似字典的对象,它的值是:

args = {"name": 'zx'}

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 这个属性就有值了。

发送的请求体中,当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类型的数据

如果我将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 'application/json'.</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之禅

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK