[TOC]
一, 初识 Flask
1.1 什么是 flask?
Flask 本是作者 Armin Ronacher 在 2010 年 4 月 1 日的一个愚人节玩笑 , 不过后来大受欢迎, 进而成为一个正式的 python 编写的 web 框架
Flask 是一个 Python 编写的 Web 微框架, 让我们可以使用 Python 语言快速实现一个网站或 Web 服务, 在介绍 Flask 之前首先来聊下它和 Django 的联系以及区别, django 个大而全的 Web 框架, 它内置许多模块, flask 是一个小而精的轻量级框架, Django 功能大而全, Flask 只包含基本的配置, Django 的一站式解决的思路, 能让开发者不用在开发之前就在选择应用的基础设施上花费大量时间. Django 有模板, 表单, 路由, 基本的数据库管理等等内建功能. 与之相反, Flask 只是一个内核, 默认依赖于 2 个外部库: Jinja2 模板引擎和 WSGI 工具集 --Werkzeug , flask 的使用特点是基本所有的工具使用都依赖于导入的形式去扩展, flask 只保留了 Web 开发的核心功能.
WSGI(Web 服务器网关接口)是 python 中用来规定 Web 服务器如何与 python Web 服务器如何与 Python Web 程序进行沟通的标准, 本质上就是一个 socket 服务端. 而 Werkzeug 模块 就是 WSGI 一个具体的实现
关键词: 一个 Python 编写微 Web 框架 一个核心两个库( Jinja2 模板引擎 和 WSGI 工具集)
1.2 为什么要有 flask?
flask 性能上基本满足一般 Web 开发的需求, 并且灵活性以及可扩展性上要优于其他 Web 框架, 对各种数据库的契合度都非常高
关键词: 1. 性能基本满足需求 2 . 灵活性可拓展性强 3. 对各种数据库的契合度都比较高.
4. 在真实的生产环境下, 小项目开发快, 大项目设计灵活
二, Flask 快速启动
- '''
- pip install flask
- '''
- # 1 导入 flask, 我们要用 flask, 就必须导入 Flask
- from flask import Flask
- # 2 生成一个 Flask 对象,__name__表示当前文件的名字
- App = Flask(__name__)
- # 3 添加路由, flask 用的是装饰器的模式
- # 注册路由, 并写响应函数 index
- @App.route("/")
- def index():
- return "Hello flask"
- if __name__ == '__main__':
- #4 启动 flask
- #run 里面是执行了 run_simple(host,port,self=App, 也就是 flask 对象)
- App.run()
三, Flask 四剑客
- '''
- 响应字符串
- 响应 html 页面
- 跳转页面
- 返回 json 字符串
- '''
- from flask import Flask, render_template, redirect, jsonify
- App = Flask(__name__)
- @App.route("/index")
- def index():
- # 1. 返回字符串
- # return "hello word 啊啊啊"
- # 2. 返回 HTML 页面
- # 返回 HTML, 从 flask 里面导入 render_template
- # return render_template("index.html")
- # 3. 跳转路由
- # return redirect('/login')
- # 4. 返回数据转 JSON 返回, 从 flask 中导入 jsonify
- # data = {"name": "jeff", "age": "18"}
- # return jsonify(data)
- pass
- @App.route("/login")
- def login():
- return "我是 login 页面"
- if __name__ == '__main__':
- App.run()
三, flask 的配置文件
- '''
- 四种配置 flask 方法配置
- 1. 直接给 app 对象赋值属性
- 2. 以字典的形式, 给 flask 配置文件做配置
- 3. 以文件的形式给 flask 做配置(django 就是这种)
- 4. 以类的形式, 给 flask 做配置(推荐使用)
- '''
- from flask import Flask
- App = Flask(__name__)
- # 方式 1(不推荐), 因为只能配置两项. debug 和 secret_key
- # App.debug = True # 默认 false, 自动重启
- # 方式 2 字典的形式
- # App.config["DEBUG"] = True
- # 方式 3 以文件的形式, 在 from_pyfile 里传递路径
- # App.config.from_pyfile("settings.py")
- # 方式 4 以类的形式, 一个文件多个套配置, 减少测试与更改(推荐使用)
- # App.config.from_object("setobj.settings")
- @App.route("/")
- def index():
- return "json 是炮王"
- if __name__ == '__main__':
- App.run()
方式三的配置文件:
类似 Django 一样, 一个专门的配置文件
DEBUG = True
方式四的配置类:
优点: 一个文件, 多套配置. 不同的类不同的配置, 减少了测试与上线的更改的配置项
- class settings():
- DEBUG = True
可以配置的属性
flask 中的配置文件是一个 flask.config.Config 对象(继承字典), 默认配置为:
'''
default_config = ImmutableDict(
{
"ENV": None,
"DEBUG": None,
"TESTING": False,
"PROPAGATE_EXCEPTIONS": None,
"PRESERVE_CONTEXT_ON_EXCEPTION": None,
"SECRET_KEY": None,
"PERMANENT_SESSION_LIFETIME": timedelta(days=31),
"USE_X_SENDFILE": False,
"SERVER_NAME": None,
"APPLICATION_ROOT": "/",
"SESSION_COOKIE_NAME": "session",
"SESSION_COOKIE_DOMAIN": None,
"SESSION_COOKIE_PATH": None,
"SESSION_COOKIE_HTTPONLY": True,
"SESSION_COOKIE_SECURE": False,
"SESSION_COOKIE_SAMESITE": None,
"SESSION_REFRESH_EACH_REQUEST": True,
"MAX_CONTENT_LENGTH": None,
"SEND_FILE_MAX_AGE_DEFAULT": timedelta(hours=12),
"TRAP_BAD_REQUEST_ERRORS": None,
"TRAP_HTTP_EXCEPTIONS": False,
"EXPLAIN_TEMPLATE_LOADING": False,
"PREFERRED_URL_SCHEME": "http",
"JSON_AS_ASCII": True,
"JSON_SORT_KEYS": True,
"JSONIFY_PRETTYPRINT_REGULAR": False,
"JSONIFY_MIMETYPE": "application/json",
"TEMPLATES_AUTO_RELOAD": None,
"MAX_COOKIE_SIZE": 4093,
}
)
'''
四, flask 路由
4.1 源码分析
- # 源码分析:
- '''
- self.add_url_rule(rule, endpoint, f, **options)
- def add_url_rule(
- self, # app 对象
- rule, # url 路由
- endpoint=None, # 路由别名
- view_func=None, # 响应的函数名
- provide_automatic_options=None,
- **options
- ):
- methods :['POST','GET'] -》控制请求方法, 不传默认只能 GET 方法
- '''
- # @App.route 的本质就在执行 add_url_rule
- # rule 是路由, endpoint 是路由别名, view_func 是响应函数
- # 如果 endpoint 不传就是响应的函数名
4.2 使用 1: 起别名
- from flask import Flask , url_for, redirect
- App = Flask(__name__)
- def index():
- print()
- return "我是 index"
- # @App.route('/detail/<int:nid>',methods=['GET','POST'],endpoint='detail') # 典型写法
- @App.route('/login')
- def login():
- print(url_for("index1")) # 走的别名, 如果别名没有写, 默认函数名
- return redirect(url_for("index1")) # 走的别名, 如果别名没有写, 默认函数名
- # url_for:
- # 用 endpoint 获取路由要用 url_for 在 flask 中导入, 也就是反向解析
- # 路由
- App.add_url_rule('/', endpoint='index1', view_func=index)
- # endpoint 别名
- # view_func 响应的函数名
- if __name__ == '__main__':
- App.run()
4.3 使用 2: 有名分组
- from flask import Flask , url_for, redirect
- App = Flask(__name__)
- # 必须接收分组名字一样的 nid
- def index(nid):
- print(nid)
- return "我是 index"
- # 路由: 有名分组
- App.add_url_rule('/index/<string:nid>', endpoint='index1', view_func=index, methods=['POST','GET'])
- App.add_url_rule('/index/<int:nid>', endpoint='index', view_func=index, methods=['POST','GET'])
- # 浏览器: http://127.0.0.1:5000/index/asdfas
- # string,int 规定接收的类型
- if __name__ == '__main__':
- App.run()
4.4 路由小总结
总结:
1 @App.route("/login") 的本质 App.add_url_rule("/login",view_func=login), 所以我们就可以用这两个方式来添加路由
2 路由的参数,
2.1 endpoint, 做是反向解析, 如果上面添加路由的时候, 没有传递 endpoint 就是使用响应函数的函数名, 反向解析用 url_for(), 做解析, 这个 url_for 必须在 flask 里面导入
2.2 methods=["POST","GET"], 该参数控制路由允许哪些请求方法访问, 如果不传, 默认只能 GET 方法
2.3 路由以及路由路由转化器."/index/<int:nid>",<参数的类型: 用哪个变量来接收>, 响应函数中的形参的名字必须转化器中一致.
4.5 默认转化器
- DEFAULT_CONVERTERS = {
- 'default': UnicodeConverter,
- 'string': UnicodeConverter,
- 'any': AnyConverter,
- 'path': PathConverter,
- 'int': IntegerConverter,
- 'float': FloatConverter,
- 'uuid': UUIDConverter,
- }
4.6 自定义转换器
- #1 写类, 继承 BaseConverter
- #2 注册: App.url_map.converters['regex'] = RegexConverter
- # 3 使用:@App.route('/index/<regex("\d+"):nid>') 正则表达式会当作第二个参数传递到类中
- from flask import Flask, views, url_for
- from werkzeug.routing import BaseConverter
- App = Flask(import_name=__name__)
- # 1. 写类
- class RegexConverter(BaseConverter):
- """
- 自定义 URL 匹配正则表达式
- """
- def __init__(self, map, regex):
- super(RegexConverter, self).__init__(map)
- self.regex = regex
- def to_python(self, value):
- """
- 路由匹配时, 匹配成功后传递给视图函数中参数的值
- """
- return int(value)
- def to_url(self, value):
- """
- 使用 url_for 反向生成 URL 时, 传递的参数经过该方法处理, 返回的值用于生成 URL 中的参数
- """
- val = super(RegexConverter, self).to_url(value)
- return val
- # 2. 注册: 添加到 flask 中
- App.url_map.converters['regex'] = RegexConverter
- # 正则匹配处理结果, 要交给 to_python,to_python 函数可以对匹配处理结果做处理
- # 3. 使用:
- @App.route('/index/<regex("\d+"):nid>')
- def index(nid):
- print(url_for('index', nid='888'))
- return 'Index'
- if __name__ == '__main__':
- App.run()
4.7 自定义转换器总结
导入 from werkzeug.routing import BaseConverter
我写个继承 BaseConverter. 实现 3 个方法, def __init__ , def to_python , def to_url
将上面的类注册到 App.url_map.converters['regex'] = RegexConverter 中
然后就可以在路由转化器中使用 3 中的 regex("传正则")
当路由被访问以后. regex("传正则")会匹配结果, 把结果传递 to_python, 我们可以进行再次处理, to_python 处理好的结果, 会传递给响应函数的形参
当用 url 做反向解析的时候, 传递给路由转化器的参数, 会经过 to_url, 进行处理. 处理以后, 在拼接到路由.
五, flask 模板渲染
py 文件:
- from flask import Flask,render_template,Markup
- App = Flask(__name__)
- App.debug = True
- USERS = {
- 1:{'name':'张三','age':18,'gender':'男','text':"道路千万条"},
- 2:{'name':'李四','age':28,'gender':'男','text':"安全第一条"},
- 3:{'name':'王五','age':18,'gender':'女','text':"行车不规范"},
- }
- def func1(arg,tank):
- return Markup(f"<h1 > 饼哥正帅,{arg} is sb {tank} is same as {arg}</h1>")
- @App.route("/")
- def index():
- # data = {
- # "user" :USERS,
- # "name": "jason"
- # }
- return render_template("index.html",user = USERS,name="jason",ht1 = func1,ht="<h1 > 饼哥正帅</h1>")
- #return render_template("index.html",**data)
- if __name__ == '__main__':
- App.run()
1. 变量的循环
- <!DOCTYPE HTML>
- <HTML lang="en">
- <head>
- <meta charset="UTF-8">
- <title>
- Title
- </title>
- </head>
- <body>
- <h1>
- 我是 HTML
- </h1>
- <table>
- {% for k,v in user.items() %}
- <tr>
- <td>
- {{ k }}
- </td>
- <td>
- {{ v.name }}
- </td>
- <td>
- {{ v['name'] }}
- </td>
- <td>
- {{ v.get('name') }}
- </td>
- <td>
- {{url_for("index")}}
- </td>
- </tr>
- {% endfor %}
- </body>
- </HTML>
2. 逻辑判断
<!DOCTYPE HTML> <HTML lang="en"> <head> <meta charset="UTF-8"> <title> Title </title> </head> <body> <h1> 用户列表 </h1> <table> {% if name %} <h1> Hello {{ name }}! </h1> {% else %} <h1> Hello World! </h1> {% endif %} </table> </body> </HTML>
3. 执行函数, 传参数
比 django 中多可以加括号, 执行函数, 传参数
from flask import Flask,render_template,Markup App = Flask(__name__) App.debug = True def func1(arg,tank): return Markup(f"<h1 > 饼哥正帅,{arg} is sb {tank} is same as {arg}</h1>") @App.route("/") def index(): # data = { # "user" :USERS, # "name": "jason" # } return render_template("index.html",ht1 = func1,ht="<h1 > 饼哥正帅</h1>") if __name__ == '__main__': App.run()
HTML 文件
<!DOCTYPE HTML> <HTML lang="en"> <head> <meta charset="UTF-8"> <title> Title </title> </head> <body> {{ ht|safe}} {{ht1("jaosn","tank")}} // 传参数 </body> </HTML>
六, flask 的请求与响应
from flask import Flask, request, make_response App = Flask(__name__) App.debug = True @App.route('/', methods=['POST', 'GET']) def index(): # print('请求方法',request.method) # 请求方法 # print('get 请求的参数',request.args) # get 请求的参数 # print('post 请求的参数',request.form) # post 请求的参数 # print('请求的 cookies',request.cookies) # 请求的 cookies # print('post 与 get 的所有参数',request.values) # post 与 get 的所有参数 # 响应头, 添加 make_response response = make_response('ok') #response = make_response(render_template("login.html")) # 设置 cookie response.set_cookie("key","val") return 'OK' if __name__ == '__main__': App.run()
请求相关的信息
请求相关的信息
print("请求方法",request.method)# 请求方法 print("get 请求的参数",request.args)# get 请求的参数 print("post 请求的参数",request.form) #post 请求的参数 print("post, 与 get 的所有参数",request.values)#post, 与 get 的所有参数 print("请求的 cookies",request.cookies)# 请求的 cookies
请求相关信息
request.method 提交的方法
request.args get 请求提及的数据
request.form post 请求提交的数据
request.values post 和 get 提交的数据总和
request.cookies 客户端所带的 cookie
request.headers 请求头
request.path 不带域名, 请求路径
request.full_path 不带域名, 带参数的请求路径
request.script_root
request.url 带域名带参数的请求路径
request.base_url 带域名请求路径
request.url_root 域名
request.host_url 域名
request.host 127.0.0.1:500
七, 设置 cookies
from flask import Flask, make_response App = Flask(__name__) App.debug = True @App.route('/', methods=['POST', 'GET']) def index(): # 响应头, 添加 make_response response = make_response('ok') # 设置 cookies response.set_cookie('key', 'val') # 删除 cookies response.delete_cookie("key") # 设置响应头 response.headers["x-somexx"] = "A SB" return response if __name__ == '__main__': App.run()
设置 cookie 的参数
key, 键
value='', 值
max_age=None, 超时时间 cookie 需要延续的时间 (以秒为单位) 如果参数是 \ None`` , 这个 cookie 会延续到浏览器关闭为止
expires=None, 超时时间(IE requires expires, so set it if hasn't been already.)
path='/', Cookie 生效的路径,/ 表示根路径, 特殊的: 根路径的 cookie 可以被任何 url 的页面访问, 浏览器只会把 cookie 回传给带有该路径的页面, 这样可以避免将 cookie 传给站点中的其他的应用.
domain=None, Cookie 生效的域名 你可用这个参数来构造一个跨站 cookie. 如, domain=".example.com" 所构造的 cookie 对下面这些站点都是可读的: www.example.com , www2.example.com 和 an.other.sub.domain.example.com . 如果该参数设置为 None ,cookie 只能由设置它的站点读取
secure=False, 浏览器将通过 HTTPS 来回传 cookie
httponly=False 只能 http 协议传输, 无法被 JavaScript 获取(不是绝对, 底层抓包可以获取到也可以被覆盖)
八, flask 的 session
cookie: 存放在客户端的键值对 session: 存放在客户端的键值对 token: 存放在客户端, 通过算法来校验
8.1 设置 session(使用版)
在使用 session 之前必须现在设置一下密钥
App.secret_key="asdas" # 值随便 # App.config['SECRET_KEY'] = os.urandom(24) # 配置 session 使用的秘钥
设置: session['username'] = 'xxx'
# 在 django 中发什么三件事, 1, 生成一个随机的字符串 2 往数据库存 3 写入 cookie 返回浏览器 # 在 flask 中他没有数据库, 但 session 是怎样实现的? # 生成一个密钥写入这个 cookie, 然后下次请求的时候, 通过这个 cookie 解密, 然后赋值给 session # 我们通过 App.session_interface 来查看
删除: session.pop('username', None)
Flask 提供了 session 对象用来将 cookie 加密储存, session 通过秘钥对数据进行签名以加密数据.
from flask import Flask, session import os App = Flask(__name__) App.config['SECRET_KEY'] = os.urandom(24) # 配置 session 使用的秘钥 @App.route('/') def set_session_info(): session['username'] = 'mark' # 使用用户信息配置 sesion 信息作为 cookie, 并添加到响应体中 return '设置 session 信息'
session 对象像可以字典一样操作, 内部是把字典的信息进行加密操作然后添加到相应体中作为 cookie, 响应的时候会自动返回给浏览器.
session['username'] = 'mark' session['userphone'] = '123456' # 可以指定多条 session 信息, 统一放到响应的 cookie 中返回给浏览器
8.2 设置 session(分析版)
from flask import Flask,session App = Flask(__name__) # 要用 session, 必须 App 配置一个密钥 App.secret_key = "asdasdihasdiuh" # 设置 session 的名字, 默认配置文件中为: session App.config['SESSION_COOKIE_NAME']="python13session" # App.session_interface #App.session_interface 实现了两个方法, 一个叫 save_session, 一个 open_session, # save_session 存 session 执行, 加密设置 cookies # open_session 取 session 执行, 解密大字典, 拿到 session @App.route("/",) def index(): #如何设置 sessoion # 1 导入 session # 2 给 sessoion 设置值 session['name'] = "egon" return "ok" @App.route("/login") def login(): print(session["name"]) return "login" if __name__ == '__main__': App.run()
8.3 设置 session 有效期
后端 Flask 跟浏览器交互默认情况下, session cookie 会在用户关闭浏览器时清除. 通过将 session.permanent 属性设为 True 可以将 session 的有效期延长为 31 天, 也可以通过操作 App 的配置 PERMANENT_SESSION_LIFETIME 来设置 session 过期时间.
案例 3.3.2.1: 开启指定 session 过期时间模式
from flask import Flask, session import os App = Flask(__name__) App.config['SECRET_KEY'] = os.urandom(24) @App.route('/') def set_session_info(): session['username'] = 'mark' session['userphone'] = '123456' session.permanent = True # 开启设置有效期, 默认为 31 天后过期 return 'Hello World!' ...
设置自定义过期时间
# 通过设置 PERMANENT_SESSION_LIFETIME 指定具体的过期时间 from datetime import timedelta App.config['PERMANENT_SESSION_LIFETIME'] = timedelta(hours=1) # 设置为 1 小时候过期
8.4 获取 sessoin
在 Flask 中获取设置的 session 信息通过 session 对象获取, session 对象是继承了字典类, 所以获取的时候是字典的取值方式. 其内部会把浏览器传过来的 session 信息解密.
@App.route('/get_session/') def get_session(): username = session.get('username') userphone = session.get('userphone') if username or userphone: return "{},{}".format(username, userphone) return "session 为空"
8.5 删除 session
session 对象调用 pop()可以根据具体的 session 的 key 清除掉指定的 session 信息.
session 对象调用 clear()可以清除此次请求的浏览器关于本域名的所有 session 信息
@App.route('/del_session/') def del_session(): session.pop('username') # session.clear() return '删除成功'
8.6 源码分析
session 源码的执行流程
-save_seesion
- 响应的时候, 把 session 中的值加密序列化放大到了 cookie 中, 返回到浏览器中
-open_session
- 请求来了, 从 cookie 中取出值, 反解, 生成 session 对象, 以后再视图函数中直接用 sessoin 就可以了.
源码分析
# App.session_interface 点进去 class SecureCookieSessionInterface(SessionInterface): salt = "cookie-session" digest_method = staticmethod(hashlib.sha1) key_derivation = "hmac" serializer = session_json_serializer session_class = SecureCookieSession def get_signing_serializer(self, App): if not App.secret_key: return None signer_kwargs = dict( key_derivation=self.key_derivation, digest_method=self.digest_method ) return URLSafeTimedSerializer( App.secret_key, salt=self.salt, serializer=self.serializer, signer_kwargs=signer_kwargs, ) # 取 session 的时候执行的 def open_session(self, App, request): s = self.get_signing_serializer(App) if s is None: return None ##cookie 键是 SESSION_COOKIE_NAME"=session val = request.cookies.get(App.session_cookie_name) print("open_session.session_cookie_name,", App.session_cookie_name, ) if not val: return self.session_class() max_age = total_seconds(App.permanent_session_lifetime) try: data = s.loads(val, max_age=max_age) print("self.session_class(data)", self.session_class(data) ) return self.session_class(data) except BadSignature: return self.session_class() #存 session 的时候执行的 def save_session(self, App, session, response): domain = self.get_cookie_domain(App) path = self.get_cookie_path(App) # If the session is modified to be empty, remove the cookie. # If the session is empty, return without setting the cookie. if not session: if session.modified: response.delete_cookie( App.session_cookie_name, domain=domain, path=path ) return # Add a "Vary: Cookie" header if the session was accessed at all. if session.accessed: response.vary.add("Cookie") if not self.should_set_cookie(App, session): return httponly = self.get_cookie_httponly(App) secure = self.get_cookie_secure(App) samesite = self.get_cookie_samesite(App) expires = self.get_expiration_time(App, session) # 把 session 做了一个加密, 把整个 session 的 key--》val, 全部加密, 的到一个 value 值, #session 是一个大字典, val = self.get_signing_serializer(App).dumps(dict(session)) # 他把 session 加密后得到的 val 存到 cookie 里面了 #cookie 键是 SESSION_COOKIE_NAME"=session print("源码中的 session",dict(session)) print("app.session_cookie_name,",App.session_cookie_name,) response.set_cookie( App.session_cookie_name, val, expires=expires, httponly=httponly, domain=domain, path=path, secure=secure, samesite=samesite, )
九, 闪现
9.1 什么是闪现?
- 设置: flash('aaa')
- 取值: get_flashed_message()
-
- 假设在 a 页面操作出错, 跳转到 b 页面, 在 b 页面显示 a 页面的错误信息
from flask import Flask,flash,get_flashed_messages App = Flask(__name__) #App.session_interface App.secret_key ="sdasd" # 什么闪现: 就像 session 一样, 也是一个页面设置, 另一个页面使用, 我不管你在哪个页面调用的 # 只要调用一次, 就清空了, # 闪现的作用, 一般用信息处理. 假设用户, a 页面做操作, 产生了信息. 我希望在 b 页面内获取. # 但是我不知道用户在什么时候, 访问 b 页面, 但是只要用户一旦访问页面就把信息显示出来. # 同一页面, 同次请求是可以拿多次的 @App.route("/") def index(): #产生信息, message 设置消息的, category 给消息分类, 如果不传默认用 "message" flash("你错过了我") flash(message="你再次错过我",category="渣男") return "index" @App.route("/login") def login(): #(with_categories=True, 消息是否要带上分类信息, category_filter=["渣男"]对消息进行过滤, 取指定的分类消息 print(get_flashed_messages(with_categories=True,category_filter=["渣男"])) print(get_flashed_messages()) return "login" @App.route("/test") def test(): print(get_flashed_messages()) return "test" if __name__ == '__main__': App.run()
来源: https://www.cnblogs.com/guyouyin123/p/12521221.html