一、请求对象:request
后去前端传过来的请求数据是后端服务来管理的,Flask帮我们把请求数据相关的都从环境变量中获取出来储存在request代理对象上了,主要基于Request类实现。
我们只需要from flask import request 使用request对象获取请求数据即可
1、Get请求
- 获取get请求的参数:request.args
- 获取的数据类型:ImmutableMultiDict(不可变字典)
- 可以用 to_dict() 方法转换成普通的可变字典
2、post请求
获取表单数据,请求头是:application/x-www-form-urlencoded
- 获取表单数据:request.form
- 获取的数据类型:ImmutableMultiDict(不可变字典)
获取json数据,请求头是:applocation/json
- 获取json数据:request.json
- 数据类型:普通字典dict类型,内部帮我们用json.loads(data)转成dict类型。方便我们的后续操作
- 其实内部是调用的get_json()方法
3、其他参数
request.values
- 可以同时获取args参数和form表单参数
- 类型是CombinedMultiDict,内容是args和form
- CombinedMultiDict([ImmutableMultiDict([('age', '17')]), ImmutableMultiDict([('name', 'zhangsan'), ('greden', 'nv')])])
- 前端一个是args,后面是form表单参数
- 一样可根据字典的方法取获取对应的参数名的值
request.cookies
- 故名思意,获取请求头的cookie数据
- 类型是dict
request.headers
- 获取请求头
- 类型dict
request.data
- 包含传入的请求数据作为字符串,以防它与 Werkzeug无法处理mimetype。
- 比如json数据可以用这个获取到,但是获取出来的数据类型bytes,一般用的很少,我们request.json更方便
request.files:比较重要
- 获取post或put请求的文件
- 类型MultiDict
request.environ
- 获取WSGI隐含的环境配置
- 当我需要的数据request对象没有帮我封装的时候,我也可以通过environ获取,和我们最小原型获取env里的数据一样
request.method
- 获取请求方法
- 主要用于在同一个视图函数中实现get和post不同请求方法时的两种逻辑会经常用到
request.remote_addr
- 获取远程ip
- 主要用于我们要限制ip的场景下
reuqest.user_agent
- 获取请求头中的user-agent数据
- 可以做一些反扒和恶意攻击的处理
request.is_josn
- 获取mimetype是不是application开头,并且是json结尾
- 布尔类型
request.is_xhr
- 判断是否ajax请求发来的
- request.is_xhr要废弃了
- 我们也可以通过request.environ["HTTP_X_REQUESTED_WITH"] 来获取
- 但是不一定是这个就一定是ajax请求,因为从前端获取的数据都是不可信的
4、代理模式
request本质Request对象,Request类继承BaseRequest类,只能和路由请求绑定使用,单独定义的一个使用request是获取不到的
下图是request对像封装了哪些数据,我们也可以自己打断点查看,更多说明可以查看Request类中继承的BaseRequest类
- args:是从环境变量中QUERY_STRING获取的。
- 其他很多都是从环境变量中获取的
5、文件上传(简单的图床功能)
- 写一个上传文件的入口(HTML)
- 后端写一个接口get请求返回HTMl,post请求接收图片保存本地static目录下
- 前端用ajax发送请求
1、前端HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>文件上传</title>
<link rel="stylesheet" href="../static/css/reset.css" type="text/css">
<script type="text/javascript" src="../static/js/jquery-1.12.4.min.js"></script>
<script type="text/javascript" src="../static/js/upload.js"></script>
</head>
<body>
<div>登录成功,进入文件上传</div>
<div>
<!-- 上传文件必须带上enctype="multipart/form-data,ajax请求时可以不带 -->
<form id="uploadForm">
<input id="file" type="file" name="img"/>
<button id="upload" type="button">上传按钮</button>
</form>
</div>
</body>
</html>
2、upload.js 发送ajax请求
$(function () {
//点击上传按钮时触发
$("#upload").click(function () {
$.ajax({
url: '/up_img',
type: 'POST',
data: new FormData($('#uploadForm')[0]),
processData: false,
contentType: false
}).done(function (res) {
alert(res.msg)
}).fail(function () {
alert("请求失败")
});
})
});
3、后端服务
import os
import time
from flask import Flask, render_template, request, jsonify, url_for
from werkzeug.utils import secure_filename
# 在前面我们提到flask的配置项,其中一个就可以非常简单限制上传文件的大小
app.config["MAX_CONTENT_LENGTH"] = 2 * 1024 * 1024
@app.route("/up_img", methods=['GET', 'POST'])
def load_img():
# 如果get方法,返回上传文件的HTML页面
if request.method == "GET":
return render_template("upload.html")
# 否则获取上传的文件,img是前端请求参数名
img = request.files["img"]
# 获取后缀名判断类型,文件大小我们通过config配置了
img_name_end = img.filename.split(".")[-1]
if img_name_end in ("jpg", "png"):
# 保存文件,secure_filename主要作用是处理空格会被url转移的特殊字符
# 方法内定义了很多规则进行转换,比如会把空格转成下划线
save_name = "".join((str(time.time()), ".", img_name_end))
img.save(os.path.join(app.static_folder, "img", secure_filename(save_name)))
# 利用url_for生成url 供前端访问该图片
# 前端f12查看接口返回结果访问即可
return jsonify(
{"status": 200, "data": {
"lmg_url": url_for("index", _external=True) + "static/img/" + secure_filename(save_name)},
"msg": "上传成功"})
return jsonify({"status": 100, "msg": "不支持的文件格式"})
if __name__ == '__main__':
app.run(debug=True)
备注
- 上传文件的时候可能我们文件名中有空格之类的。可以要用secure_filename效验参数名,来动态处理参数名的空格 等转义问题
- 可以在app.config中限制文件大小 MAX_CONTENT_LENGTH 字段来设置
- 也可以size = len(upload_file.read())用这方法获取到 然后在自己写逻辑判断
- 不要忘记我们之前分享提到的,falsk项目的默认模板目录和静态资源目录,当然可以初始化app核心对象时,通过对应参数使用修改。具体请回顾Flask-1-快速开始-flask核心类初始化参数说明
- HTMl默认清空下需要保存在templates目录下
- js文件默认清空下需要保存在static
- 前端访问静态资源,访问图片默认url是http://locakhost:5000/static/.....
- 利用url_for()生成绝对url。
二、响应对象
如果不做任何限制,返回字符串游览器自动会把它变成HTML,因为响应对象默认定义的mimetype,也就响应头对应content-type是text/html。
1、主要的响应对象
- 文本:text/plain
- HTML:text/html
- XML:aplication/xml
- json:aplication/json
2、手动修改响应状态码和媒体类型
flask构造响应对象时,如何定义响应状态码,响应头,影响数据的???
其实在我们return的时时候,可以接收三个参数,第一个响应数据,第二个响应状态码,第三个响应头
@app.route("/", methods=["GET", "POST"])
def index():
return json.dumps({"username": "jiangmingbai"}), 201, {"content-type": "application/json"}
预览器的响应发生了变化
3、实现原理:make_response方法
其实return的响应信息也是基于make_response()方法实现的,我们也可以return一个make_response()对象,
make_response()里基于current_app.make_response(args)方法,这里面最后调用的Response()类,Response()类,继承的BaseResponse(),其中需要传响应对象的初始化参数
def __init__(
self,
response=None, # 响应数据
status=None, # 状态码
headers=None, # 头信息
mimetype=None, # 媒体类型
content_type=None,
direct_passthrough=False,
):
注意:
- 具体细节可以导入falsk下的make_reponse,查看源码,注释。都有案例
- 在make_response()传参时, status可以时int类型,内部做了判断如果时int会赋值给,status_code,但是如果用实例对象在赋值时必须时字符串。
from flask import Flask, make_response
app = Flask(__name__)
@app.route("/", methods=["GET", "POST"])
def index():
r = make_response(json.dumps({"username": "jiangmingbai"}), 201, {"content-type": "application/json"})
print(r) # <Response 28 bytes [201 CREATED]>实例对象
r.status = "202 OK" # 通过实例在赋值stutas时必须是字符串
return r
if __name__ == '__main__':
app.run(debug=True)
4、json响应头:jsonify
我在返回json数据到前端的时候,前面我们用的是自己使用json.dumps方法把数据转成json,并把响应头媒体类型定义成application/json
其实flask帮我们封装了一个方法,专门用于返回json数据响应对象。我只需要调用改方法即可
其实也是用Response类处理的,
帮我动态的自动获取config里面的源类型 默认是application/json
所以一样可以接收返回值,也就是Response实例对象来操作初始化参数和属性值
from flask import Flask, jsonify
app = Flask(__name__)
@app.route("/user", methods=["GET", "POST"])
def user():
r = jsonify({"age": 18})
r.status = "202 ok"
return r
if __name__ == '__main__':
app.run(debug=True)
5、重定向 redirect
之前讲在分享路由redirect_to参数的时候其实有提到过实现重定向的两种方式,一致在路由加redirect_to参数。一种是用在视图函数种return redirect() 并推荐结合url_for一起用,因为端点变化的几率更小
具体请跳转-->https://www.cnblogs.com/jiangmingbai
三、自定义错误响应
当我们服务器遇到问题比如500,404 等,flask会帮我们自动返回一个一个它封装好的页面,但是这个页面并不友好
还有就是当用户未授权访问服务时,肯定需要抛401的错误,此时flask帮我返回的页面,一样也不友好。
那如何自定义错误响应页面呢??
flask帮我们实现了自定义错误响应的装饰器:@app.errorhandler(code_except):响应码或者错误类型
注意: 不要用debug调试模式,返回帮我们把错误信息输出到前端,而不是自定义的错误页面
1、不可预见的错误响应:500,502,404等
有一些错误是我们不可预见的,我们也不知道哪里会发生错误,我们此时可以定义一个全局的错误响应来替换
案例:500 ,404 自己写好页面,放在项目templates目录下
- 利用@app.errorhandler(500)定义好,返回对应500错误页面的方法
- 程序在遇到500时,会自动帮我们返回自定义500的页面
from flask import Flask, render_template
app = Flask(__name__)
# 全局捕获的500错误的
@app.errorhandler(500)
def error_500(error_msg):
return render_template("error_500.html", error_msg=error_msg), 500
# 全局捕获的404错误的
@app.errorhandler(404)
def error_500(error_msg):
return render_template("404.html"), 404
# 遇到不可遇见的返回我们定义500错误页面
# 当发生500错误时,自动返回我们定义error_500.html
@app.route("/")
def index():
1 / 0
return render_template("login.html")
if __name__ == '__main__':
app.run()
2、可以遇见的错误:比如:401,403 等等
还有一种是我们可以遇见的,就是我们知道我们想要什么,如果不对,我们就主动抛处一个错误。而不是return。
而falsk就我们封装了一个abort方法,帮我统一主动raise,我们只需要传对应code码即可,但是都是flask帮我们定制了所有对应code
页面,对我们来说并不友好, 如何自定义呢?
我本一样可以@app.errorhandler(code_except),然后在封装方法返回我们自定义的页面即可
案例:比如 登录未授权,返回401页面
- 自定义错误类型的装饰器,实现返回对应状态码的错误页面
- 程序到预知到此种错误的时候,利用abort(401),会自动返回对应401的页面
from flask import Flask, render_template, abort, request
app = Flask(__name__)
# 自定义401错误响应页面,配合abort使用
@app.errorhandler(401)
def error_401(error_smg):
return render_template("error_401.html"), 401
# 可遇见,用abort处理
@app.route("/project")
def get_pro():
# 假如没有获取到项目id,就抛未授权,当然实际种时判断cookie
if not request.args.get("pro_id"):
abort(401)
return "获取成功"
if __name__ == '__main__':
app.run()
3、自定义错误类型
当然我们也可以自定义错误类型进行返回,我们自己主动raise一个错误类型,而不用abort处理
此时我们需要自定义错误类型,,只需要继承一下Exception,然后在用@app.errorhandler(自定义错误类型(except)),装饰我们的方法,返回自定义的错误类型对应的页面即可
flask很好为我们提供了abort,也很方便,具体根据实际情况想咋用都行,其实abort处理更加方便
案例:
- 定义错误类型
- @app.errorhandler(except),处理返回对应错误类型的自定义页面
- 遇到此种错误的时候,只用raise 这个错误类型,就会返回我们自定义的错误页面
from flask import Flask, render_template, abort, request
app = Flask(__name__)
# 自定义一个错误类型,用户错误
class UserError(Exception):
pass
# 自定义错误类型返回的页面
@app.errorhandler(UserError)
def error_401(error_smg):
return render_template("username_error.html"), 401
# 自定义错误类型,raise处理
@app.route("/user")
def get_user():
if not request.args.get("username"):
# 自己主动抛出我们定义的错误类型即可
raise UserError
return "后获取用户成功"
if __name__ == '__main__':
app.run()