Flask如何接收数据
|字数总计:4.1k|阅读时长:21分钟|阅读量:
简介
本篇文章将介绍使用 Python Flask 库搭建的后端程序,如何与前端程序进行数据通信。一方面介绍如何使用 Python requests 库发送 post 请求,另一方面介绍如何使用 JavaScript fetch 方法发送 post 请求。此外,详细地展示在 Flask 中如何接受数据,数据格式的转换与解码等。
requests.post
requests.post | requests docs
使用 requests 库的 post 方法进行数据传输时有非常多的可选参数,它的函数声明如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| (function) def post( url: str | bytes, data: _Data | None = None, json: Any | None = None, *, params: _Params | None = ..., headers: _HeadersMapping | None = ..., cookies: CookieJar | _TextMapping | None = ..., files: _Files | None = ..., auth: _Auth | None = ..., timeout: _Timeout | None = ..., allow_redirects: bool = ..., proxies: _TextMapping | None = ..., hooks: _HooksInput | None = ..., stream: bool | None = ..., verify: _Verify | None = ..., cert: _Cert | None = ... ) -> Response
|
常用的与数据传输相关的参数有 data、json 和 files,它们都有各自的应用场景和使用方式。下面将分别介绍如何在 requests.post 方法中使用这些参数,以及如何在 flask.request 中接收对应的数据。
requests.post(url, data=data)
首先介绍 data 参数,data 参数主要针对表单类型数据,如下是一个示例的使用 data 参数的 requests.post 请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| """client.py""" import requests
url = "http://127.0.0.1:5000/receive" data = {"name": "data", "content": 123} response = requests.post(url=url, data=data, timeout=3)
print(f"response: {response},\n" f"response.status_code: {response.status_code},\n" f"type(response): {type(response)},\n" f"response.json(): {response.json()},\n" f"response.request.headers['Content-Type']: {response.request.headers['Content-Type']}")
|
使用 requests.post(url, data=data)
方法的 data
参数传输数据,对应请求头中 Content-Type
字段的内容为 application/x-www-form-urlencoded
,这表示数据为表单类型。
1 2 3 4 5 6 7
| Host: 127.0.0.1:5000 User-Agent: python-requests/2.32.3 Accept-Encoding: gzip, deflate Accept: */* Connection: keep-alive Content-Length: 21 Content-Type: application/x-www-form-urlencoded
|
flask.request.get_data()
在 Flask 服务端使用 request.get_data()
方法来获取 requests.post(url, data=data)
所发送的 data
数据,得到的数据将是原始字节流,不解析数据格式的 raw data,数据类型为 <class 'bytes'>
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| """server.py""" from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route("/receive", methods=["POST"]) def receive(): print(request.headers)
raw_data = request.get_data() print(f"raw_data: {raw_data},\ntype(raw_data): {type(raw_data)}") return jsonify({"success": True})
if __name__ == "__main__": app.run(host="0.0.0.0", port=5000, debug=True)
|
在 Flask 服务端使用 request.form
来获取 requests.post(url, data=data)
所发送的 data
数据,得到的将是处理后的表单数据,数据类型为 <class 'werkzeug.datastructures.structures.ImmutableMultiDict'>
, 类似 Python 的字典:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| """server.py""" from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route("/receive", methods=["POST"]) def receive(): print(request.headers)
form = request.form print(f"form: {form},\ntype(form): {type(form)}")
return jsonify({"success": True})
if __name__ == "__main__": app.run(host="0.0.0.0", port=5000, debug=True)
|
对于 ImmutableMultiDict 类型的对象,可以通过它的 get 方法来获取表单中的内容
1
| value = request.form.get("key")
|
如何处理 <class ‘bytes’> 类型数据
对于原始字节流形式的 <class 'bytes'>
类型数据,需要先进行解码才能够使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| """server.py""" from flask import Flask, request, jsonify from urllib.parse import parse_qs
app = Flask(__name__)
@app.route("/receive/", methods=["POST"]) def receive(): print(request.headers)
raw_data = request.get_data() print(f"raw_data: {raw_data},\ntype(raw_data): {type(raw_data)}")
'''decode bytes to string''' decoded_data = raw_data.decode('utf-8') print(f"decoded_data: {decoded_data},\ntype(decoded_data): {type(decoded_data)}")
'''parse to form data''' parsed_data = parse_qs(decoded_data) print(f"parsed_data: {parsed_data},\ntype(parsed_data): {type(parsed_data)}")
'''convert to dict''' parsed_data = {key: value[0] for key, value in parsed_data.items()} print(f"parsed_data: {parsed_data},\ntype(parsed_data): {type(parsed_data)}")
return jsonify({"success": True})
if __name__ == "__main__": app.run(host="0.0.0.0", port=5000, debug=True)
|
1 2 3 4 5 6 7 8 9
| """client.py""" import requests
url = "http://127.0.0.1:5000/receive" data = {"name": "data", "content": 123} response = requests.post(url=url, data=data, timeout=3)
print(f"response: {response}, type: {type(response)}, json: {response.json()}")
|
requests.post(url, json=json)
json 参数适合用于指定传输 dict 或者 json 类型的数据,如下是一个使用 json 参数的 requests.post 请求的示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| """client.py""" import requests
url = "http://127.0.0.1:5000/receive" json = {"name": "json", "content": 456} response = requests.post(url=url, json=json, timeout=3)
print(f"response: {response},\n" f"response.status_code: {response.status_code},\n" f"type(response): {type(response)},\n" f"response.json(): {response.json()},\n" f"response.request.headers['Content-Type']: {response.request.headers['Content-Type']}")
|
对应的请求头如下
1 2 3 4 5 6 7
| Host: 127.0.0.1:5000 User-Agent: python-requests/2.32.3 Accept-Encoding: gzip, deflate Accept: */* Connection: keep-alive Content-Length: 32 Content-Type: application/json
|
在 Flask 中使用 flask.request.get_json() 方法或者 flask.request.json 属性方法可以直接获得 json 类型结构的数据,对应于 python 中的 dict
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| """server.py""" from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route("/receive", methods=["POST"]) def receive(): print(request.headers) json = request.get_json() print(f"json: {json},\ntype(json): {type(json)}") return jsonify({"success": True})
if __name__ == "__main__": app.run(host="0.0.0.0", port=5000, debug=True)
|
requests.post(url, files=files)
requests.post 方法的 files 参数用于发送文件数据到服务端,通常是用于上传文件的场景。files 参数需要传入一个字典,其中每个关键字是字段名,每个值是包含 (文件名,文件对象,文件类型) 的三元组。files 参数的基本格式如下:
1
| files = {'field_name': (file_name, file_object, file_type)}
|
field_name
对应于 flask.request.files.keys()
中的关键字,以及 flask.request.files['field_name'].name
中的 name 字段;
file_name
对应于 flask.request.files['field_name'].filename
中的 filename 字段;
file_object
为文件的二进制流对象,例如通过 open('file.txt', 'rb')
打开的 <class '_io.TextIOWrapper'>
类型文件。
file_type
用于指定文件的类型,例如 text/plain
,image/jpg
等,其对应于 flask.request.files['field_name'].context_type
中的 content_type 字段
如下是一个使用 files 参数的 requests.post 请求的示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| """client.py""" import requests
file = open(file="./image.jpg", mode='rb') url = "http://127.0.0.1:5000/receive" files = {"file": ("image.jpg", file, "image/jpg")} response = requests.post(url=url, files=files, timeout=3) file.close()
print(f"response: {response},\n" f"response.status_code: {response.status_code},\n" f"type(response): {type(response)},\n" f"response.json(): {response.json()},\n" f"response.request.headers['Content-Type']: {response.request.headers['Content-Type']}")
|
在 Flask 中使用 flask.request.files
属性方法可以获得对应的 files 数据,使用 flask.request.files["field_name"]
可以获得对应文件的二进制流数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| """server.py""" from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route("/receive", methods=["POST"]) def receive(): print(request.headers) files = request.files file = request.files["file"]
print(f"files: {files},\n" f"type(files): {type(files)}") print(f"file: {file},\n" f"type(file): {type(file)},\n" f"file.name: {file.name},\n" f"file.filename: {file.filename},\n" f"file.content_type: {file.content_type}")
return jsonify({"success": True})
if __name__ == "__main__": app.run(host="0.0.0.0", port=5000, debug=True)
|
如何处理 FileStorage 类型的对象
使用 FileStoreage.read()
方法可以将 FileStorage 类型的对象转换成 bytes 类型的字节流数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| """server.py""" from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route("/receive", methods=["POST"]) def receive(): print(request.headers) files = request.files print(f"files: {files},\ntype(files): {type(files)}") file = files["file"] print(f"file: {file},\n" f"type(file): {type(file)}")
file_bytes = file.read() print(f"file_bytes: {file_bytes},\n" f"type(file_bytes): {type(file_bytes)}")
return jsonify({"success": True})
if __name__ == "__main__": app.run(host="0.0.0.0", port=5000, debug=True)
|
data 和 json 的区别
data 与 json 参数的区别:
- data 发送的是表单数据,通常编码为
application/x-www-form-urlencoded
- json 发送的是 JSON 数据,自动编码为
application/json
使用 data 参数传输 int 和 list 类型的数据时,服务端调用 request.form 得到的都是 str 类型数据
1 2 3 4 5 6 7 8 9 10 11
| """client.py""" ... response = requests.post(url=url, data={"num": 123, "array": [1, 2, 3]})
"""server.py""" ... form = request.form print(f"form: {form},\ntype: {type(form['num'])}, {type(form['array'])}")
...
|
使用 request.form.getlist(“key”) 方法可以得到 list 类型,但其中的元素依然是 str 类型
1 2 3 4 5 6 7 8 9
| ... form = request.form print(f"form: {form},\n" f"form.get: {form.get('num')}, type: {type(form.get('num'))},\n" f"form.getlist: {form.getlist('array')}, type: {type(form.getlist('array'))}") # form: ImmutableMultiDict([('num', '123'), ('array', '1'), ('array', '2'), ('array', '3')]), # form.get: 123, type: <class 'str'>, # form.getlist: ['1', '2', '3'], type: <class 'list'> ...
|
使用 json 参数传输 int 和 list 类型的数据时,服务端调用 request.json 得到的将是对应的类型数据
1 2 3 4 5 6 7 8 9 10 11
| """client.py""" ... response = requests.post(url=url, json={"num": 123, "array": [1, 2, 3]})
"""server.py""" ... json = request.json print(f"json: {json},\ntype: {type(json['num'])}, {type(json['array'])}")
...
|
此外不能同时使用 data 和 json 参数,如果同时指定 data 和 json 参数,requests.post 会忽略 data 参数,只处理 json。
手动指定 Content-Type
如果使用 data 并希望发送 JSON 数据,需要手动设置 Content-Type 并将数据序列化:
1 2 3 4 5
| import json url = "..." payload = {"key1": "value1", "key2": "value2"} headers = {'Content-Type': 'application/json'} response = requests.post(url, data=json.dumps(payload), headers=headers)
|
Python 如何处理 bytes 类型数据
针对文本类型数据
1 2 3
| file_bytes = file.read() decoded_bytes = file_bytes.decode('utf-8') print(decoded_bytes)
|
针对图片类型数据
针对图片类型数据,可以通过 numpy.frombuffer 将数据转换成 ndarray 格式,再通过 cv2.imdecode 对数据进行解码,转换成图片对应的 ndarray 类型数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| """server.py""" from flask import Flask, request, jsonify import numpy as np import cv2
app = Flask(__name__)
@app.route("/receive", methods=["POST"]) def receive(): files = request.files print(f"files: {files},\ntype(files): {type(files)}") file = files["file"] print(f"file: {file},\n" f"type(file): {type(file)}")
file_bytes = file.read() print(f"file_bytes: {file_bytes},\n" f"type(file_bytes): {type(file_bytes)}, len(file_bytes): {len(file_bytes)}")
image = np.frombuffer(file_bytes, np.uint8) print(f"image.shape: {image.shape}, type(image): {type(image)}, image.dtype: {image.dtype}") image = cv2.imdecode(image, cv2.IMREAD_COLOR) print(f"image.shape: {image.shape}, type(image): {type(image)}, image.dtype: {image.dtype}")
return jsonify({"success": True})
if __name__ == "__main__": app.run(host="0.0.0.0", port=5000, debug=True)
|
注意上面代码中 file_bytes 的长度为 8824,转换成 np.ndarray 格式后的 shape 也为 (8824,),解码后得到的图片形状却为 (193, 261, 3),两者的数据类型都是 np.uint8,所以两者的大小并不匹配,193×261×3 = 151118 ≠ 8824。这是因为 8824 对应的是图片压缩之后的数据,其比实际的图片大小要小,通过 cv2.imdecode 方法解码后还原得到未经压缩的像素矩阵,它的大小要比压缩后的数据流更大。
除了使用 cv2,还可以使用 python 的 io 库 + Pillow 对图片进行解码
1 2 3 4 5 6 7 8 9 10 11
| ... bytes = io.BytesIO(file_bytes) print(f"bytes: {bytes.getvalue()}, type(bytes): {type(bytes)}") image = Image.open(bytes) print(f"image.size: {image.size}, type(image): {type(image)}") image = np.array(image) print(f"image.shape: {image.shape}, type(image): {type(image)}, image.dtype: {image.dtype}") ...
|
JavaScript
使用 fetch 方法发送 post 请求
前端的 HTML 和 JavaScript 代码,通过下面的命令调用
1
| python -m http.server --bind 0.0.0.0 5001
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <script src="script.js"></script> <title>Transfer File</title> </head> <body> <input type="file" id="fileInput" accept="image/*" onchange="transferFile(event)"/> </body> </html>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| const url = "http://127.0.0.1:5000/receive"
function transferFile(event) { console.log(event.target) const imageFile = event.target.files[0]; console.log(imageFile)
const formData = new FormData(); formData.append('imgfile', imageFile);
fetch(url, {method: 'POST', body: formData}) .then(response => response.json()) .then(jsondata => console.log(jsondata)) .catch(error => console.error('Error:', error)); }
|
后端的 Python + Flask 代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| """server.py""" from flask import Flask, request, jsonify from flask_cors import CORS
app = Flask(__name__) CORS(app)
@app.route("/receive", methods=["POST"]) def receive(): print(request.headers) files = request.files print(f"files: {files},\n" f"type(files): {type(files)}") file = files["imgfile"] print(f"file: {file},\n" f"type(file): {type(file)},\n" f"file.name: {file.name},\n" f"file.filename: {file.filename},\n" f"file.content_type: {file.content_type}")
return jsonify({"success": True})
if __name__ == "__main__": app.run(host="0.0.0.0", port=5000, debug=True)
|