理解 flask.request中form、data、json屬性的區別

語言: CN / TW / HK

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資源