Flask是一个基于Python开发并且依赖jinja2模板和Werkzeug WSGI服务的一个微型框架,对于Werkzeug本质是Socket服务端,其用于接收http请求并对请求进行预处理,然后触发Flask框架,开发人员基于Flask框架提供的功能对请求进行相应的处理,并返回给用户,如果要返回给用户复杂的内容时,需要借助jinja2模板来实现对模板的处理,即:将模板和数据进行渲染,将渲染后的字符串返回给用户浏览器。
“微”(micro) 并不表示你需要把整个 Web 应用塞进单个 Python 文件(虽然确实可以 ),也不意味着 Flask 在功能上有所欠缺。微框架中的“微”意味着 Flask 旨在保持核心简单而易于扩展。Flask 不会替你做出太多决策——比如使用何种数据库。而那些 Flask 所选择的——比如使用何种模板引擎——则很容易替换。除此之外的一切都由可由你掌握。如此,Flask 可以与您珠联璧合。
默认情况下,Flask 不包含数据库抽象层、表单验证,或是其它任何已有多种库可以胜任的功能。然而,Flask 支持用扩展来给应用添加这些功能,如同是 Flask 本身实现的一样。众多的扩展提供了数据库集成、表单验证、上传处理、各种各样的开放认证技术等功能。Flask 也许是“微小”的,但它已准备好在需求繁杂的生产环境中投入使用。
初始Flask框架,将会从下面11个方向进行介绍。
1. 介绍Flask、Django、Tornado框架
2. Flask快速入门
3. 配置文件
4. 路由系统
5. 模板语言
6. 请求&响应相关
7. session & cookie
8. 闪现
9. 蓝图
10. 请求扩展(django 中间件)
11. 中间件
一. 介绍Flask、Django、Tornado框架(Python的三个常用框架)
Django:重武器,大而全。内部包含了非常多组件:ORM、Form、ModelForm、缓存、Session、中间件、信号等...大型项目用Django的比较多
Flask:短小精悍,可扩展。内部没有太多组件,但是第三方组件非常丰富。
路由比较特殊:基于装饰器来实现,但是究其本质还是通过add_url_rule来实现。
小项目一般用Flask的比较多,同时因为Flask有非常多的第三方组件,也可以很方便的实现Django具备的功能
Tornado:异步非阻塞框架(node.js)
二. Flask快速入门
1. 安装Flask
为了不与现行项目想冲突,通常情况下会引入一个虚拟环境,将Flask安装在虚拟环境下,虚拟环境用virtualenv(关于virtualenv后面再专门写一篇帖子介绍它)
为了方便快速创建,所有过程都在Pycharm里完成
(1)使用Virtualenv创建虚拟环境
(2)将Flask安装在该虚拟环境里
2.WSGI
对于真实开发中的python web程序来说,一般会分为两部分:服务器程序和应用程序。
服务器程序负责对socket服务器进行封装,并在请求到来时,对请求的各种数据进行整理。
应用程序则负责具体的逻辑处理。
为了方便应用程序的开发,就出现了众多的Web框架,例如:Django、Flask、web.py 等。不同的框架有不同的开发方式,但是无论如何,开发出的应用程序都要和服务器程序配合,才能为用户提供服务。这样,服务器程序就需要为不同的框架提供不同的支持。这样混乱的局面无论对于服务器还是框架,都是不好的。对服务器来说,需要支持各种不同框架,对框架来说,只有支持它的服务器才能被开发出的应用使用。这时候,标准化就变得尤为重要。可以设立一个标准,只要服务器程序支持这个标准,框架也支持这个标准,那么他们就可以配合使用。一旦标准确定,双方各自实现。这样,服务器可以支持更多支持标准的框架,框架也可以使用更多支持标准的服务器。
WSGI(Web Server Gateway Interface)是一种规范,它定义了使用python编写的web app与web server之间接口格式,实现web app与web server间的解耦。
python标准库提供的独立WSGI服务器称为wsgiref。
# werkzeug示例: from werkzeug.wrappers import Request, Response @Request.application def hello(request): return Response('Hello World!') if __name__ == '__main__': from werkzeug.serving import run_simple run_simple('localhost', 4000, hello) # wsgiref示例: from wsgiref.simple_server import make_server def runserver(environ, start_response): start_response('200 OK', [('Content-Type', 'text/html')]) return [bytes('<h1>Hello, web!</h1>', encoding='utf-8'), ] # 本质的本质: import socket def handle_request(client): buf = client.recv(1024) client.send("HTTP/1.1 200 OK\r\n\r\n") client.send("Hello, Seven") def main(): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.bind(('localhost',8000)) sock.listen(5) while True: connection, address = sock.accept() handle_request(connection) connection.close() if __name__ == '__main__': main()
3.Flask
第一个简单的demo
# 1. 导入Flask from flask import Flask # 2. 实例化Flask对象 app = Flask(__name__) app.debug = True # 每次修改完成后,这个语句就会自动重启服务,省去开发者手动重启 ''' 看到这种加()的装饰器,就要知道它里面其实执行了2个步骤: 1. 先执行app.route('/'),把返回值赋给 v, 即v = app.route('/') 2. 在执行v(), 并把hello_world作为参数传给v,即v(hello_world) ''' # 3. 作用:将"/"和函数index的对应关系添加到路由中 @app.route('/') def index(): return 'Hello World!' if __name__ == '__main__': # 4. 监听用户请求 # 如果有用户请求过来,则执行app的__call__方法 # 1. run里不写内容,默认用用的就是本机IP,端口是5000 ;http://127.0.0.1:5000/ app.run() # 2.run里写了IP和端口,就使用传的IP和端口运行服务 app.run( host = 'localhost', # localhost =127.0.0.1,代表本机; 如果此处是0.0.0.0,代表任意ip的请求都是运行的 port = 9999 )
以用户登录作为一个简单的demo,并一步步填充知识点:
需求一:实现一个简单登录页面,如果用户名密码校验成功,跳转到百度首页;如果校验失败,返回到登录页面,并给出错误提示
用户登录页面
from flask import Flask,render_template,request,redirect app = Flask(__name__)
# 将构造函数的 name 参数传给 Flask 程序,Flask 用这个参数决定程序的根目录,以便稍后能够找到相对于程 序根目录的资源文件位置
客户端(例如 Web 浏览器)把请求发送给 Web 服务器,Web 服务器再把请求发送给 Flask程序实例。
程序实例需要知道对每个 URL 请求运行哪些代码,所以保存了一个 URL 到 Python 函数的映射关系。
处理 URL 和函数之间关系的程序称为路由
在 Flask 程序中定义路由的最简便方式,是使用程序实例提供的 app.route 修饰器,把修饰的函数注册为路由。 # 1. app.route()中:第一个参数指明了登录页面的路径,methods参数指明了可以接受的请求方式,不写的时候默认接受GET请求 @app.route("/login", methods=['GET', 'POST']) def login(): ''' 2. 登录函数里需要返回一个登录页面,需要用到render方法,在flask里导入render_template模块 ender_template("login.html")指明了模板路径 模板路径不能随便指定,Python里规定了必须写在templates目录下,所以需要先创建该目录 然后再templates目录下创建login.html页面''' ''' 3. 请求相关的数据过来后,全部放在request里,此时需要先导入request 同时,需要判断过来的请求是什么方式,并执行相关的操作请求逻辑 ''' if request.method =="GET": return render_template("login.html") else: # url中的请求数据全部放在query_string # request.query_string # 请求体相关的数据都放在form中,而且一定要get获取前端传的用户名和密码后才能传给Server,这里曾经犯了一个严重错误,没有get,数据无法传递给厚度,导致if逻辑没有处理 user = request.form.get("username") pwd = request.form.get("password") ''' 4.然后就需要去数据库里查找比对是否存在这个用户,此处先略去数据库的操作 ''' if user == "alex" and pwd =="123456": ''' 5.如果正确就会跳转到登录成功后的某个页面,需要用到redirect方法,没有就去导入''' return redirect("www.baidu.com") '''6. 如果用户名不对,还是跳回登录页面,同时提示用户名或者密码错误; 错误提示信息需要在页面展示,那login.html就需要有个变量名接受这个错误信息 ''' return render_template("login.html",error="输入的用户名或密码错误") return render_template("login.html", **{"error":"用户名和密码错误"}) # **context:可以接受字典方式的参数,但是书写要注意: # 1. 可以直接以赋值的方式写,如error="输入的用户名或密码错误" # 2. 也可以以字典的方式写,但是要注意,如果以字典方式,需要在{}前加两个**。 如上 if __name__ =="__main__": app.run()
login.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>登录页面</title> </head> <body> <h1>用户登录</h1> <form method = "post"> # 知识点:表单数据,一定要加在<form ></form>体内 <input type="text" name="username"> <input type="text" name="password"> <input type="submit" value="登 录"> # 烦的错误:登录按钮type="submit", 但是按钮名没有写成value,导致按钮上没有显示登录两个字 <br> {{error}} # 知识点:接受后端传送的值,需要通过花括号{{变量名}}的方式接受 </form> </body> </html>
需求二: 做一个首页,其内容展示出一个用户列表
首页-用户列表页
from flask import Flask,render_template,request,redirect app = Flask(__name__) app.debug = True # 1. 没有数据库,先用一个字典作为一个用户列表 USERS = { 1:{"name":"老大", "age":19,"gender":"男", "resume":"言语措辞间都能体会到今日头条的谨小慎微,生怕再出现任何问题……."}, 2:{"name":"老二", "age":18,"gender":"女", "resume":"当打开这款APP之后,就会发现这跟已经“死”去的内涵段子简直是一模一样。更厉害的地方是,它还支持用用户迁移内涵段子上的内容信息。"}, 3:{"name":"老三", "age":17,"gender":"男", "resume":"如果狒狒会说人话,他肯定是在说:喂…你怎么只给我吃了一口就跑了呀,我还没吃饱啊…喂喂喂…给我回来呀!哈哈哈"}, } # 2. 定义一个首页-用户列表页 @app.route("/index", methods=['GET']) # 首页一般都是先get数据的,所以methods用get方法 # 将USERS复制给变量user_dict,前端拿到该变量将会用来循环获取数据 def index(): return render_template("index.html", user_dict=USERS) @app.route("/login", methods=['GET', 'POST']) def login(): if request.method =="GET": return render_template("login.html") else: user = request.form.get("username") pwd = request.form.get("password") if user == "alex" and pwd =="123456": return redirect("https://www.baidu.com") return render_template("login.html", **{"error":"用户名和密码错误"}) if __name__ =="__main__": app.run()
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>首页</title> </head> <body> <h1>用户页面</h1> <!--用户列表是一个表格, 用<table> </table> --> <table> <!-- 后端传给前端的是一个用户列表,前端需要接受,并提取出需要的数据展示,这就需要用到循环了 知识点: 1. 在html里循环,需要以{% for循环条件 %}开始,以{% endfor %} 结尾 2. 循环结果展示也要以{{ }} 接受变量 3. v也是一个字典,在Flask里,字典值的获取有三种方式: 1) v.name 以.的方式直接获取 2) v['name'] 以索引的方式获取 3) v.get('name') 以get的方式获取 --> {% for k,v in user_dict.items() %} <tr> <td>{{ k }}</td> <td>{{ v.name }}</td> <td>{{ v['name'] }}</td> <td>{{ v.get('name') }}</td> <td>{{ v.age }}</td> <td>{{ v.get('gender') }}</td> <!-- 详细信息页,下面要用到--> <!-- a标签是超链接的标志 --> <!-- 需要给详细信息detail页传一个id,后端根据该id来决定显示什么数据给前端,ID也是花括号方式接受 --> <!-- 同时,后端需要一个地址来接受前端传的值,定义个路由"/detail/id"地址来接受 --> <td><a href="detail/{{ key }}">查看详细信息</a></td> </tr> {% endfor %} </table> </body> </html>
需求三: 首页内容展示出一个用户列表,点击详细信息可以查看用户的具体详细信息
详细信息页
from flask import Flask,render_template,request,redirect app = Flask(__name__) app.debug = True USERS = { 1:{"name":"老大", "age":19,"gender":"男", "resume":"言语措辞间都能体会到今日头条的谨小慎微,生怕再出现任何问题……."}, 2:{"name":"老二", "age":18,"gender":"女", "resume":"当打开这款APP之后,就会发现这跟已经“死”去的内涵段子简直是一模一样。更厉害的地方是,它还支持用用户迁移内涵段子上的内容信息。"}, 3:{"name":"老三", "age":17,"gender":"男", "resume":"如果狒狒会说人话,他肯定是在说:喂…你怎么只给我吃了一口就跑了呀,我还没吃饱啊…喂喂喂…给我回来呀!哈哈哈"}, } # 定义用户信息详情页,需要根据前端传的id决定具体展示哪个用户的详细信息,而且该id还需要是动态的 # Flask里动态接受,有个规则,需要用<类型:nid>的方式 # int:表示前端传的要是一个数值类型 # nid: 表示传的值 # 同时,前端传值了,就需要在detail函数里接收这个值 @app.route("/detail/<int:nid>", methods=['GET']) def detail(nid): # 接收前端传的数值类型的值 detail_info = USERS.get(nid) # USERS用户列表里根据nid获取值 return render_template("detail.html",info = detail_info) # info = detial_info 接受上面USERS里获取到的值,并给前端显示 @app.route("/index", methods=['GET']) def index(): return render_template("index.html", user_dict=USERS) @app.route("/login", methods=['GET', 'POST']) def login(): if request.method =="GET": return render_template("login.html") else: user = request.form.get("username") pwd = request.form.get("password") if user == "alex" and pwd =="123456": return redirect("https://www.baidu.com") return render_template("login.html", **{"error":"用户名和密码错误"}) if __name__ =="__main__": app.run()
detail.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>详细信息 {{info.name}}</h1> <div> {{info.text}} </div> </body> </html>
需求四:实现登录成功后,可以正常访问上面的页面,所以需要引入session。(Flask里有session,可直接引入),如果不用session就要自己写cookie了。
from flask import Flask,render_template,request,redirect,session,url_for app = Flask(__name__) app.debug = True USERS = { 1:{"name":"老大", "age":19,"gender":"男", "resume":"言语措辞间都能体会到今日头条的谨小慎微,生怕再出现任何问题……."}, 2:{"name":"老二", "age":18,"gender":"女", "resume":"当打开这款APP之后,就会发现这跟已经“死”去的内涵段子简直是一模一样。更厉害的地方是,它还支持用用户迁移内涵段子上的内容信息。"}, 3:{"name":"老三", "age":17,"gender":"男", "resume":"如果狒狒会说人话,他肯定是在说:喂…你怎么只给我吃了一口就跑了呀,我还没吃饱啊…喂喂喂…给我回来呀!哈哈哈"}, } @app.route("/detail/<int:nid>", methods=['GET']) def detail(nid): # 校验session,获取值 user = session.get('user_info') # 如果得到的session没值,不是该登录用户,那就重定向到登录页面 if not user: ''' 知识点: 一. 利用别名进行反向生成 1. redirect时跳转到某个url,如果这个url特别长,很容易写出错,怎么搞? 可以用别名的方式代替,根据别名可以反向生成 别名从哪里来?你要redirect到哪个页面,就在那个路由上起别名,用endpoint 2. 要根据别名进行反向生成,还需导入一个 url_for的模块,url_for的作用就是进行反向生成 3. end_for也是可以接收参数的,参数是**values,比如dtail后面会接受id就可以写成 url = url_for('l1',nid = nid)#是这么写吧,暂定? 二. 写一个装饰器,代替每个函数里session校验,进行用户认证 ''' url = url_for('l1') # 根据别名l1反向生成/login的地址,并复制给url return redirect(url) # 重定向跳转到url就可以 detail_info = USERS.get(nid) # 如果有值,就跳到详情页 return render_template("detail.html",info = detail_info) # info = detial_info 接受上面USERS里获取到的值,并给前端显示 @app.route("/index", methods=['GET']) def index(): # 没有通别名的方式进行反向生成的方式跳转 user = session.get("user_info") if not user: return redirect("/login") return render_template("index.html", user_dict=USERS) # ➕假设登录页url很长,给起个别名 endpoint = l1 @app.route("/login", methods=['GET', 'POST'], endpoint='l1') def login(): if request.method =="GET": return render_template("login.html") else: user = request.form.get("username") pwd = request.form.get("password") # 用户登录成功之前,写上session,让其 = 用户名,相当于给session里的user_info键赋值了 # 当用户登录成功后,session里其实就有值了,访问其他页面时,每一个都需要进行判断session # 所以,一定会想到用装饰器的方式解决 if user == "alex" and pwd =="123456": # session赋值的动作一定是咋用户登录信息校验通过后才获取已登录的用户名赋值给session下的'user_info的 # 犯的错误:把session['user_info'] = user 写到了if判断的外面,导致报错 session['user_info'] = user return redirect("https://www.baidu.com") return render_template("login.html", **{"error":"用户名和密码错误"}) if __name__ =="__main__": app.run()
装饰器:关于上面的知识点二,完成装饰器,需要注意的三个点
1. 装饰器一般是两层函数嵌套
2. 一个函数可以用多个装饰器,当用多个装饰器的时候,它的执行顺序是怎样的?
3. 如果url的别名重复了,是不允许的。也就是对于反向查找的名称不允许重复。flask里通过endpoint解决的
三.Flask的配置文件
先看个简单的配置的示例
# 配置文件 from flask import Flask app = Flask(__name__) # 两个简单的配置 app.debug = True app.secret_key = "34ufad342333mfkf" @app.route("/") def index(): return "Hello World" if __name__ == "__main__": app.run()
问题:Falsk里的配置文件有很多的时候,都需要写到Python代码里吗?答案是否定的
1. Flask都有哪些配置文件?
flask中的配置文件是一个flask.config.Config对象(继承字典),默认配置为: { 'DEBUG': get_debug_flag(default=False), 是否开启Debug模式 'TESTING': False, 是否开启测试模式 'PROPAGATE_EXCEPTIONS': None, 'PRESERVE_CONTEXT_ON_EXCEPTION': None, 'SECRET_KEY': None, 'PERMANENT_SESSION_LIFETIME': timedelta(days=31), 'USE_X_SENDFILE': False, 'LOGGER_NAME': None, 'LOGGER_HANDLER_POLICY': 'always', 'SERVER_NAME': None, 'APPLICATION_ROOT': None, 'SESSION_COOKIE_NAME': 'session', 'SESSION_COOKIE_DOMAIN': None, 'SESSION_COOKIE_PATH': None, 'SESSION_COOKIE_HTTPONLY': True, 'SESSION_COOKIE_SECURE': False, 'SESSION_REFRESH_EACH_REQUEST': True, 'MAX_CONTENT_LENGTH': None, 'SEND_FILE_MAX_AGE_DEFAULT': timedelta(hours=12), 'TRAP_BAD_REQUEST_ERRORS': False, 'TRAP_HTTP_EXCEPTIONS': False, 'EXPLAIN_TEMPLATE_LOADING': False, 'PREFERRED_URL_SCHEME': 'http', 'JSON_AS_ASCII': True, 'JSON_SORT_KEYS': True, 'JSONIFY_PRETTYPRINT_REGULAR': True, 'JSONIFY_MIMETYPE': 'application/json', 'TEMPLATES_AUTO_RELOAD': None, }
2. Flask都有哪些方式设置配置文件?有三种方式
(1)方法1
# 配置文件 from flask import Flask app = Flask(__name__) # 两个简单的配置文件 app.debug = True app.secret_key = "34ufad342333mfkf" # 方法1 app.config['debug'] = True app.config['secret_key'] = "2343urioeurq" # PS: 由于Config对象本质上是字典,所以还可以使用app.config.update(...) @app.route("/") def index(): return "Hello World" if __name__ == "__main__": app.run()
(2) 方法2: 以py文件的方式导入
配置文件需要写在同级目录下
app.config.from_pyfile("python文件名称")
app.config.from_pyfile("python文件名称") 如: settings.py DEBUG = True app.config.from_pyfile("settings.py")
app.config.from_envvar("环境变量名称") 环境变量的值为python文件名称名称,内部调用from_pyfile方法 app.config.from_json("json文件名称") JSON文件名称,必须是json格式,因为内部会执行json.loads app.config.from_mapping({'DEBUG':True}) 字典格式 PS: 从sys.path中已经存在路径开始写 PS: settings配置文件可能会存在于两个路径下面:
1. settings.py文件默认路径要放在程序root_path目录(即根目录下),
2. 如果instance_relative_config = True 时,settings.py 文件就要放在instance_path目录下
(3)方法3:以文件的方式进行配置,然后导入配置文件
1. 先在同级目录下,新建 settings.py文件,配置项需要大写
DEBUG = True # 配置项需要大写
2.在需要使用到配置文件的地方导入
from flask import Flask app = Flask(__name__) # 以from_pyfile方式导入配置文件 app.config.from_pyfile("settings.py") @app.route("/") def index(): return "Hello World!" if __name__ =="__main__": app.run()
(4)方法4:最常用的方式,以from_objec方式 ----推荐使用的方式
# 以from_object方式导入配置文件
app.config.from_object("python类或者类的路径")
# 表示FalskLearn目录下面的settings文件下面有个DevelopmentConfig类
app.config.from_object("pro_flask.settings.DevelopmentConfig")
Python类写在settings.py配置文件里
# 生产环境默认配置 class Config(): DEBUG = False TESTING = False DATABASE_URL = "sqlite://:memory:" # 测试或开发环境继承Config类,重写相关配置项 class ProductionConfig(Config): DATABASE_URL = "mysql://user@localhost/foo" class DevelopmentConfig(Config): DEBUG = True class TestingConfig(Config): TESTING = True
导入配置文件示例:
from flask import Flask app = Flask(__name__) # 以from_object方式导入配置文件 # app.config.from_object("python类或者类的路径") # 因为 settings.py文件与当前文件同级,所以settings前没再写它的目录 app.config.from_object("settings.DevelopmentConfig") @app.route("/") def index(): return "Hello World!" if __name__ =="__main__": app.run()
四.路由系统
- @app.route('/user/<username>') 表示字符串
- @app.route('/post/<int:post_id>') 表示整数
- @app.route('/post/<float:post_id>') 表示小数
- @app.route('/post/<path:path>') 表示可以传路径
- @app.route('/login', methods=['GET', 'POST'])
常用路由系统有以上五种,所有的路由系统都是基于一下对应关系来处理:
DEFAULT_CONVERTERS = { 'default': UnicodeConverter, 'string': UnicodeConverter, 'any': AnyConverter, 'path': PathConverter, 'int': IntegerConverter, 'float': FloatConverter, 'uuid': UUIDConverter, }
(1)路由系统的本质
from flask import Flask app = Flask(__name__) app.config.from_object("settings.DevelopmentConfig") ''' 看到路由系统,分析路由系统干了什么? 第一步:先执行:decorator = app.route("/", methods= ['GET','POST'], endpoint='n1') 根据源码,第一步执行了下面的函数,返回decorator,这就是一个闭包,闭包给谁用,谁以后执行这个函数就给谁用 def route(self, rule, **options): # 具体值与参数的对应关系: # app对象 # rule = "/" # options = {methods= ['GET','POST'], endpoint='n1'} def decorator(f): endpoint = options.pop('endpoint', None) self.add_url_rule(rule, endpoint, f, **options) return f return decorator 第二步: 第一步返回了decorator,所以相当于是: @decorator,等价于decorator(index),触发上面route函数下面的decorator函数的运行 def decorator(f): endpoint = options.pop('endpoint', None) self.add_url_rule(rule, endpoint, f, **options) # self就是app对象。加到了路由的对应表里去了,那里有url对应的函数 return f 最后总结一下,添加路由关系的本质,其实就是最终执行 self.add_url_rule(rule, endpoint, f, **options),生成路由的对应关系 ''' # 对于endpoint还要注意: # 如果endpoint没有写,根据源码可知,默认的就是函数名,即该路由对应函数,如index(endpoint = index) @app.route("/", methods= ['GET','POST'], endpoint='n1') def index(): return "Hello World!" def login(): return "登录" # 所以根据源码的原理,也可以按照下面的方式添加路由对应关系,就与Django类似了。但是在Flask里,还是要按照装饰器的方式去添加路由对应关系 app.add_url_rule('/login', 'n2', login, methods= ['GET','POST']) if __name__ == "__main__": app.run()
(2)路由系统值CBV
上面的路由是一种FBV的写法,在Flask里,还支持CBV的写法
from flask import Flask,views app = Flask(__name__) app.config.from_object("settings.DevelopmentConfig") def auth(func): def inner(*args, **kwargs): result = func(*args, **kwargs) return result return inner # 1. 继承自views.MethodView 采用CBV写法时,为了简单,都是采用继承MethodView的方式写的 class IndexView(views.MethodView): methods = ['GET'] decorators = [auth,] def get(self): return "Index.GET" def post(self): return "Index.POST" # view_func = 类.as_view # name="index" 指的就是endpoint # name = endpoint app.add_url_rule('/index', view_func=IndexView.as_view(name="index")) if __name__ == "__main__": app.run()
或
from flask import Flask,views app = Flask(__name__) app.config.from_object("settings.DevelopmentConfig") def auth(func): def inner(*args, **kwargs): print('before') result = func(*args, **kwargs) print('after') return result return inner # 也可以再往上继承自View class IndexView(views.View): methods = ['GET'] decorators = [auth, ] # 如果继承自View,就需要dispatch_request def dispatch_request(self): print('Index') return 'Index!' app.add_url_rule('/index', view_func=IndexView.as_view(name='index')) # name=endpoint if __name__ == "__main__": app.run()
(3)@app.route和app.add_url_rule参数:
A. 最常用参数
rule, URL规则 view_func, 视图函数名称 defaults=None, 默认值,当URL中无参数,函数需要参数时,使用defaults={'k':'v'}为函数提供参数 endpoint=None, 名称,用于反向生成URL,即: url_for('名称') methods=None, 允许的请求方式,如:["GET","POST"]
from flask import Flask app = Flask(__name__) # 以from_object方式导入配置文件 # app.config.from_object("python类或者类的路径") app.config.from_object("settings.DevelopmentConfig") @app.route("/",methods=['GET','POST'],) # 1. 函数index需要参数,但是url里无参数 def index(nid): return "Hello World!" if __name__ =="__main__": app.run()
from flask import Flask app = Flask(__name__) # 2. 函数index需要参数,但是url里无参数,即可以用defaults={"k":"v"}为函数提供参数 @app.route("/",methods=['GET','POST'],defaults={"nid":888}) # 1. 函数index需要参数,但是url里无参数 def index(nid): print(nid) # 会在工作它打印nid 888 return "Hello World!" if __name__ =="__main__": app.run()
B. strict_slashes = None,表示对URL最后的 / 符号是否有严格要求
from flask import Flask app = Flask(__name__) # strict_slashes=False表示访问http://127.0.0.1:5000/index 或者 http://127.0.0.1:5000/index/ 都可以 @app.route("/index",methods=['GET','POST'],strict_slashes=False) def index(): return "Hello World!" # strict_slashes=True 表示访问http://127.0.0.1:5000/login时,login后面有没有/, # 必须与@app.route("/login")的"/login"一致才可以 @app.route("/login",methods=['GET','POST'],strict_slashes=True) def login(): return "Hello World!" if __name__ =="__main__": app.run()
C.redirect_to = None : 重定向到指定地址
from flask import Flask app = Flask(__name__) app.Debug = True # 很多情况下,公司网站改版,都会有一段时间新老网站都会共存,很多用户都会点收藏的老的网站地址,需要能跳转到新的网站 # 可以用redirect_to进行重定向到新网站 @app.route("/index",methods=['GET','POST'],endpoint="n1", redirect_to="/index2") def index(): return "老网站"
# 或者 def index(): return "老网站" @app.route("/index",methods=['GET','POST'],endpoint="n1", redirect_to="index2") # 新网站 @app.route("/index2",methods=['GET','POST'],endpoint='n2') def index2(): return "新网站" if __name__ =="__main__": app.run()
D.subdomain = None: 子域名访问
通常情况下,公司都会有个官网,比如www.baidu.com, 但是也会存在一些子域名,比如:api.baidu.com; admin.baidu.com
需要在hosts里配置上对应关系
wins: C:\Windows\System32\drivers\etc\hosts
mac: /etc/hosts
from flask import Flask app = Flask(__name__) # 子域名方式,SERVER_NAME 是必须要配置的 # body.com是域名,但是目前这个程序运行ip是127.0.0.1 dns域名解析 # 所以要在hosts里把域名和ip进行配置对应关系 app.config['SERVER_NAME']='body.com:5000' @app.route('/',subdomain='admin') def static_index(): return 'static.body.com' # 子域名如果是写死的,访问地址如下: # admin.body.com:5000
# 示例及运行结果
# subdomain='<domain>',<>表示类似于一个字符串形式的正则表达式,传什么子域名就显示什么子域名 @app.route('/dyaciton',subdomain='<domain>') def domain_indext(domain): return domain+".body.com" # 子域名如果是以变量方式传入,访问地址如下: # buy.body.com:5000/dyaciton # admin.body.com:500/dyaction
# 示例及运行结果
if __name__=="__main__": app.run()
(4) Flask之扩展支持正则的路由
from flask import Flask, url_for from werkzeug.routing import BaseConverter app = Flask(import_name=__name__) # 1. 写转换器类,类名任意写,必须继承自BaseConverter class RegexConverter(BaseConverter): """ 构造方法 自定义URL匹配正则表达式 regex是传入的参数 """ def __init__(self, map, regex): super(RegexConverter, self).__init__(map) # 传入的参数只需要赋上值就好了,其他就不用管了 self.regex = regex # 类里需要实现两个方法:to_python 和 to_url def to_python(self, value): """ to_python的作用:路由匹配时,在正则匹配成功后,在传给视图函数之前,执行它,对匹配的数据进行一次校验 :param value: :return: """ return int(value) def to_url(self, value): """ to_url的作用:使用url_for反向生成URL时,传递的参数经过该方法处理,返回的值用于生成URL中的参数 :param value: :return: """ val = super(RegexConverter, self).to_url(value) return val # 2. 将转换器类RegexConverter 添加到flask的转换器列表,即要添加到 DEFAULT_CONVERTERS
app.url_map.converters['regex'] = RegexConverter
# regex代指的就是RegexConverter类,然后加(),
# 就表示会把括号里的正则表达式\d+当作参数传递给类RegexConverter的第二个参数regex
@app.route('/index/<regex("\d+"):nid>')
def index(nid): print(url_for('index', nid='888')) # url_for时,先执行to_url
return 'Index' if __name__ == '__main__': app.run()
五.Flask之模板语言
1. 模板的使用
Flask使用的时Jinja2模板,所以其语法和Django无差别
2.自定义模板方法
Flask中自定义模板方法的方式和Bottle相似,创建一个函数并通过参数的形式传入render_template.
前面的示例也涉及到了模板,下面再看个简单的示例,传入函数到模板里
from flask import Flask,render_template app = Flask(__name__) app.debug = True def func1(arg): return "hello" + arg @app.route("/index") def index(): return render_template("s5index.html",f=func1) # 传入函数 if __name__=="__main__": app.run()
S5index.html页面
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <!-- 传入函数 --> <h1>{{f('小马达')}}</h1> </body> </html>
from flask import Flask,render_template app = Flask(__name__) app.debug = True def func1(arg): return "<input type='text' value=%s />"%(arg,) @app.route("/index") def index(): return render_template("s5index.html",f=func1) # 传入函数 if __name__=="__main__": app.run()
s5index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <!-- 传入函数 --> {{f('小马达')}} </body> </html>
运行:http://127.0.0.1:5000/index
运行结果:
能输出这样,就是很好的,因为它是做了防止xss攻击了
要想让他免除这种设置,Django里可以再前端模板里通过管道符safe |safe, 后台也可以marksafe,同理,Flask也可以
(1)Flask前面模板里通过 |safe 做
s5index.html模板前端
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <!-- |safe 防xss攻击 --> {{f('小马达')|safe}} </body> </html>
运行结果:
(2)Flask后端通过 Markup做
后端通过Markup做
from flask import Flask,render_template,Markup app = Flask(__name__) app.debug = True def func1(arg): return Markup("<input type='text' value=%s />"%(arg,)) @app.route("/index") def index(): return render_template("s5index.html",f=func1) # 传入函数 if __name__=="__main__": app.run()
s5index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <!-- 传入函数 --> {{f('小马达')}} </body> </html>
运行结果:
(3)宏定义, 在html里定义, 通过关键字 macro关键字定义
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <!-- 传入函数 --> {{f('小马达')}} <!-- 宏定义 作用:就是定义一块html,相当于就是一个函数 xx是函数名 --> {% macro xx(name,type='text',value='') %} <input type="{{ type }}" name="{{ name }}1" value="{{value}}"> <input type="{{ type }}" name="{{ name }}2" value="{{value}}"> <input type="{{ type }}" name="{{ name }}3" value="{{value}}"> <input type="{{ type }}" name="{{ name }}4" value="{{value}}"> {% endmacro %} <!--执行,n是name --> {{ xx('n') }} </body> </html>
运行结果:
六. Flask之请求和响应
from flask import Flask,request,render_template,redirect,jsonify,make_response app = Flask(__name__) @app.route('/login.html', methods=['GET', "POST"]) def login(): return "内容" # 请求相关信息 # request.method # request.args # request.form # request.values # request.cookies # request.headers # request.path # request.full_path # request.script_root # request.url # request.base_url # request.url_root # request.host_url # request.host # request.files # obj = request.files['the_file_name'] # obj.save('/var/www/uploads/' + secure_filename(f.filename)) # 响应相关信息 # return "字符串" # return render_template('html模板路径',**{}) # return redirect('/index.html') # return jsonify({'k':'v'}) # 如果想设置响应头和回显cookie,就需要用到make_response # response = make_response(render_template('index.html')) # response = make_response("字符串") # response是flask.wrappers.Response类型 # response.delete_cookie('key') # response.set_cookie('key', 'value') # response.headers['X-Something'] = 'A value' # return response if __name__ == '__main__': app.run() ''' 示例 def index(): response = make_response("字符串") response.set_cookie response.delete_cookie('key') response.headers['X-Something'] = 'A value' return response '''
七.Falsk之session&cookie
除请求对象之外,还有一个 session 对象。它允许你在不同请求间存储特定用户的信息。它是在 Cookies 的基础上实现的,并且对 Cookies 进行密钥签名要使用会话,你需要设置一个密钥。
-
设置:session['username'] = 'xxx'
- 删除:session.pop('username', None)
from flask import Flask,session app=Flask(__name__) app.debug = True # session 在使用之前,必须要有一个secret_key,用来做签名和加密 # 记住: # 最终到浏览器的时候会有一个随机字符串,还会有k1:v1,k2:v2序列化之后,再用secret_key加密签名之后, # 返回到浏览器上的 app.secret_key="dfjade890sddss" app.session_interface app.route("/") def index(): # session['k1'] = 'v1'写法很像字典,因为它就是一个继承了字典类的对象 # flask内置的使用加密cookie(签名cookie)来保存数据 session['k1'] = 'v1' session['k2'] = 'v2' # 删除session session.pop('k1') return 'xxx' if __name__=="__main__": app.run()
pip3 install Flask-Session run.py from flask import Flask from flask import session from pro_flask.utils.session import MySessionInterface app = Flask(__name__) app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT' app.session_interface = MySessionInterface() @app.route('/login.html', methods=['GET', "POST"]) def login(): print(session) session['user1'] = 'alex' session['user2'] = 'alex' del session['user2'] return "内容" if __name__ == '__main__': app.run() session.py #!/usr/bin/env python # -*- coding:utf-8 -*- import uuid import json from flask.sessions import SessionInterface from flask.sessions import SessionMixin from itsdangerous import Signer, BadSignature, want_bytes class MySession(dict, SessionMixin): def __init__(self, initial=None, sid=None): self.sid = sid self.initial = initial super(MySession, self).__init__(initial or ()) def __setitem__(self, key, value): super(MySession, self).__setitem__(key, value) def __getitem__(self, item): return super(MySession, self).__getitem__(item) def __delitem__(self, key): super(MySession, self).__delitem__(key) class MySessionInterface(SessionInterface): session_class = MySession container = {} def __init__(self): import redis self.redis = redis.Redis() def _generate_sid(self): return str(uuid.uuid4()) def _get_signer(self, app): if not app.secret_key: return None return Signer(app.secret_key, salt='flask-session', key_derivation='hmac') def open_session(self, app, request): """ 程序刚启动时执行,需要返回一个session对象 """ sid = request.cookies.get(app.session_cookie_name) if not sid: sid = self._generate_sid() return self.session_class(sid=sid) signer = self._get_signer(app) try: sid_as_bytes = signer.unsign(sid) sid = sid_as_bytes.decode() except BadSignature: sid = self._generate_sid() return self.session_class(sid=sid) # session保存在redis中 # val = self.redis.get(sid) # session保存在内存中 val = self.container.get(sid) if val is not None: try: data = json.loads(val) return self.session_class(data, sid=sid) except: return self.session_class(sid=sid) return self.session_class(sid=sid) def save_session(self, app, session, response): """ 程序结束前执行,可以保存session中所有的值 如: 保存到resit 写入到用户cookie """ domain = self.get_cookie_domain(app) path = self.get_cookie_path(app) httponly = self.get_cookie_httponly(app) secure = self.get_cookie_secure(app) expires = self.get_expiration_time(app, session) val = json.dumps(dict(session)) # session保存在redis中 # self.redis.setex(name=session.sid, value=val, time=app.permanent_session_lifetime) # session保存在内存中 self.container.setdefault(session.sid, val) session_id = self._get_signer(app).sign(want_bytes(session.sid)) response.set_cookie(app.session_cookie_name, session_id, expires=expires, httponly=httponly, domain=domain, path=path, secure=secure)
真正用的时候,更多常用的是第三方库 Flask_session
#!/usr/bin/env python # -*- coding:utf-8 -*- """ pip3 install redis pip3 install flask-session """ from flask import Flask, session, redirect from flask.ext.session import Session app = Flask(__name__) app.debug = True app.secret_key = 'asdfasdfasd' app.config['SESSION_TYPE'] = 'redis' from redis import Redis app.config['SESSION_REDIS'] = Redis(host='192.168.0.94',port='6379') Session(app) @app.route('/login') def login(): session['username'] = 'alex' return redirect('/index') @app.route('/index') def index(): name = session['username'] return name if __name__ == '__main__': app.run() 第三方session