1、Flask简介
Web应用程序的本质,Web(World Wide Web)诞生最初的目的,是为了利用互联网交流工作文档。
Web框架
Web框架就是协助开发者快速开发Web应用程序的一套功能代码。开发者只需要按照框架约定要求,在指定位置写上自己的业务逻辑代码。(就是要么重新造轮子,要么用轮子造轮子)
为什么要用Web框架?
Web网站发展至今,特别是服务端,涉及到的知识、内容非常广泛。这对程序员的要求也会越来越高。如果采用成熟,稳健的框架,那么一些基础的工作,比如,安全性,数据流控制等都可以用框架来处理,那么程序开发人员可以把精力放在具体任务的逻辑上面。使用框架的优点(1、稳定性和可拓展性强2、可以降低开发难度,提高开发效率)。
python中常用的Web框架有flask,django,tornado
Flask
Flask诞生于2010年,是Armin ronacher(人名)用 Python 语言基于 Werkzeug 工具箱编写的轻量级Web开发框架。
Flask 本身相当于一个内核,其他几乎所有的功能都要用到扩展(邮件扩展Flask-Mail,用户认证Flask-Login,数据库Flask-SQLAlchemy),都需要用第三方的扩展来实现。比如可以用 Flask 扩展加入ORM、窗体验证工具,文件上传、身份验证等。Flask 没有默认使用的数据库,你可以选择 MySQL,也可以用 NoSQL。
其 WSGI 工具箱采用 Werkzeug(路由模块),模板引擎则使用 Jinja2。这两个也是 Flask 框架的核心。
Flask常用扩展包
- Flask-SQLalchemy:操作数据库;
- Flask-script:插入脚本;
- Flask-migrate:管理迁移数据库;
- Flask-Session:Session存储方式指定;
- Flask-WTF:表单;
- Flask-Mail:邮件;
- Flask-Bable:提供国际化和本地化支持,翻译;
- Flask-Login:认证用户状态;
- Flask-OpenID:认证;
- Flask-RESTful:开发REST API的工具;
- Flask-Bootstrap:集成前端Twitter Bootstrap框架;
- Flask-Moment:本地化日期和时间;
- Flask-Admin:简单而可扩展的管理接口的框架
安装Flask
pip3 install flask
新建python项目
导入Flask类:
from flask import Flask
app = Flask(__name__)
Flask函数接收一个参数__name__,他会指向程序所在的包。name 是Python中的特殊变量,如果文件作为主程序执行,那么__name__变量的值就是__main__,如果是被其他模块引入,那么__name__的值就是模块名称。
装饰器的作用就是将路由映射到视图函数index
@app.route('/') def index(): return 'Hello World'
Flask应用程序实例的run方法,启动WEB服务器
if __name__ == '__main__': app.run()
程序运行成功,去浏览器会看到Hello,World.
相关配置参数
刚才实现了一个简单的Flask应用程序,只写了几行代码,接下来对Flask程序的创建,运行配置做进一步的了解,具体有(Flask程序初始化参数,Flask程序相关配置加载方式,app.run()参数)
初始化参数
Flask程序实例在创建的时候,需要默认传入当前Flask程序所指定的包(模块)。
app = Flask(import_name,static_url_path=None,static_folder="static",template_folder="templates",instance_path=None,instance_relative_config=False, )
import_name Flask程序所在的包(模块),传 __name__ 就可以 其可以决定 Flask 在访问静态文件时查找的路径 static_path 静态文件访问路径(不推荐使用,使用 static_url_path 代替) static_url_path 静态文件访问路径,可以不传,默认为:/ + static_folder static_folder 静态文件存储的文件夹,可以不传,默认为 static template_folder 模板文件存储的文件夹,可以不传,默认为 templates
程序加载配置
在 Flask 程序运行的时候,可以给 Flask 设置相关配置,比如:配置 Debug 模式,配置数据库连接地址等等,设置 Flask 配置有以下三种方式:
从配置对象中加载(常用)
app.config.from_object()
从配置文件中加载
app.config.from_pyfile()
从环境变量中加载(了解)
app.config.from_envvar()
以下演练以设置应用程序的 DEBUG(调试模式) 为例,设置应用为调式模式这后,可以实现以下功能:
1、程序代码修改后可以自动重启服务器
2、在服务器出现相关错误的时候可以直接将错误信息进行抛出到控制台打印
使用方式
1、配置对象中加载,创建配置的类,代码如下:(DEBUG = True,修改代码后会自动重启服务器)
from flask import Flask #配置对象,里面定义需要给APP添加一系列配置 class Config(object): DEBUG = True #创建Flask类的对象,指向程序所在报的名称 app = Flask(__name__) #从配置对象中加载配置 app.config.from_object(Config)
2、配置文件
创建配置文件config.ini,在配置文件中添加配置
from flask import Flask #创建Flask类的对象,指向程序所在报的名称 app = Flask(__name__) #从配置对象中加载配置 参数文件名 app.config.from_pyfile("config.ini") @app.route('/') def index(): return "Hello World!" if __name__ == '__main__': app.run()
3、环境变量(了解)
1、编辑运行配置
使用代码加载配置:
# 创建 Flask 类的对象,指向程序所在的包的名称 app = Flask(__name__) # 加载指定环境变量名称所对应的相关配置 app.config.from_envvar('FLASKCONFIG')
读取配置:
app.config.get()
或者
在视图函数中使用 current_app.config.get()
注:Flask 应用程序将一些常用的配置设置成了应用程序对象的属性,也可以通过属性直接设置/获取某些配置:app.debug = True
app.run的参数
可以指定运行的主机IP地址,端口,是否开启调试模式。
app.run(host="127.0.0.1", port=5000, debug = True)
路由基本定义
1、明确路由定义的参数,请求方式指定 2、PostMan的使用
指定路由地址
# 指定访问路径为 demo1 @app.route('/demo1') def demo1(): return 'demo1'
给路由传参示例
有时我们需要将同一类URL映射到同一个视图函数处理,比如:使用同一个视图函数来显示不同用户的个人信息。
#路由传递参数 @app.route("/user/<user_id>") def user_info(user_id): return "hello %s" % user_id
路由传递的参数默认当做string处理,也可以指定参数的类型。
#路由传递参数 指定参数类型 @app.route("/user/<int:user_id>") def user_info(user_id): return "hello %s" % user_id
这里指定的是int,尖括号中的内容是动态的,在此时可以理解为接收int类型的值,实际上int代表使用IntegerConverter去处理url传入的参数。
指定请求方式
在 Flask 中,定义一个路由,默认的请求方式为:
- GET
- OPTIONS(自带)
- HEAD(自带)
如果想添加请求方式,那么可以如下指定:
@app.route("/demo2",methods=["GET","POST"]) def demo2(): #直接从请求中取到请求方式并返回 return request.method
使用PostMan对请求进行测试
PostMan 是一款功能强大的网页调试与发送网页 HTTP 请求的 Chrome 插件,可以直接去对我们写出来的路由和视图函数进行调试,作为后端程序员是必须要知道的一个工具。
- 安装方式1:去 Chrome 商店直接搜索 PostMan 扩展程序进行安装
- 安装方式2:https://www.getpostman.com/ 官网下载桌面版
-
安装方式3:将已下载好的 PostMan 插件文件夹拖入到浏览器
- 打开 Chrome 的扩展程序页面,打开
开发者模式
选项 - 将插件文件夹拖入到浏览器(或者点击加载已解压的扩展程序选择文件夹)
- 在 Mac 下生成桌面图标,可以点击启动
- 在 ubuntu 旧版的 Chrome 浏览器中会显示以下效果,可以直接点击启动
- 打开 Chrome 的扩展程序页面,打开
-
使用 PostMan,打开之后,会弹出注册页面,选择下方的
Skip this,go straight to the app
进行程序
视图常用逻辑
返回JSON
在使用Flask写一个接口时候需要给客户端返回JSON数据,在Flask中可以直接使用jsonify生成一个JSON的响应。
#返回一个JSON数据 @app.route("/demo3") def demo3(): json_dict = { "username" = "lishuntao" "password" = "123456" } #返回一个json数据 return jsonify(json_dict)
不推荐使用json.dumps序列化转化成JSON字符串直接返回,因为返回的数据要符合HTTP协议规范,如果是JSON需要指定content-type:application/json
重定向
# 重定向到百度 @app.route('/demo4') def demo4(): return redirect('http://www.baidu.com')
重定向到自己写的视图函数:
1、可以直接写自己url路径
2、也可以使用url_for生成指定视图函数所对应的的url
@app.route('/demo1') def demo1(): return 'demo1' # 重定向 @app.route('/demo5') def demo5(): #重定向使用url_for生成指定视图函数所对应的url return redirect(url_for('demo1'))
重定向到带有参数的视图函数:
在url_for函数中传入参数:
#路由传递参数 指定参数类型 @app.route("/user/<int:user_id>") def user_info(user_id): return "hello %s" % user_id # 重定向 @app.route('/demo6') def demo6(): #重定向到带有参数的视图函数, # 使用 url_for 生成指定视图函数所对应的 url return redirect(url_for('user_info', user_id=100))
自定义状态码
在Flask中,可以很方便的返回自定义状态码,用来实现不符合HTTP协议的状态码,例如:status code:888
@app.route('/demo7') def demo7(): return '状态码为 888', 888
正则匹配路由
在web开发中,可能会出现限制用户访问规则的场景,那么这个时候需要用到正则匹配,根据自己的规则去限定请求参数在进行访问。
步骤:
1、导入转换器基类:在Flask中,所有的路由的匹配规则都是使用转换器对象进行记录。
2、自定义转换器:自定义类继承于转换器基类
3、添加转换器到默认的转换器字典中
4、使用自定义转换器实现自定义匹配规则
导入转换器基类
from werkzeug.routing import BaseConverter
from flask import Flask,request,jsonify,redirect,url_for #1、导入转换器基类 from werkzeug.routing import BaseConverter #2、自定义转换器 自定义正则转换器 自定义类继承于转换器基类 class RegexConverter(BaseConverter): def __init__(self,url_map,*args): #初始化基类 super(RegexConverter,self).__init__(url_map) #将接受的第一个参数当做匹配规则进行保存 self.regex=args[0] #3、创建Flask类的对象,指向程序所在报的名称 app = Flask(__name__) #4、添加转换器到默认的转换器字典中,并指定转换器使用时名字为: re app.url_map.converters["re"] = RegexConverter #5、使用转换器去实现自定义匹配规则 当前此处定义的规则是:3位数字 @app.route('/user/<re("[0-9]{3}"):user_id>') def user_info(user_id): return "user_id 为 %s" % user_id
自定义转换器其他函数实现
继承于自定义转换器之后,还可以实现to_python和to_url这两个函数去匹配参数做进一步处理。
to_python:
该函数参数中的 value 值代表匹配到的值,可输出进行查看
匹配完成之后,对匹配到的参数作最后一步处理再返回,比如:转成 int 类型的值再返回:
#自定义转换器
class RegexConverter(BaseConverter): def __init__(self, url_map, *args): super(RegexConverter, self).__init__(url_map) # 将接受的第1个参数当作匹配规则进行保存 self.regex = args[0] #输出之前,将value就是上面匹配到的值进一步处理成int类型在返回 def to_python(self, value): return int(value)
运行测试,在视图函数中可以查看参数的类型,由之前默认的str已变成int类型的值。
系统自带的转换器
DEFAULT_CONVERTERS = { 'default': UnicodeConverter, 'string': UnicodeConverter, 'any': AnyConverter, 'path': PathConverter, 'int': IntegerConverter, 'float': FloatConverter, 'uuid': UUIDConverter, }
备注:系统自带的转换器具体使用方式在每种转换器的注释代码中有写,请留意每种转换器初始化的参数。
异常捕获
HTTP异常主动抛出:
abort方法:
抛出一个给定状态代码的HTTPException或者指定响应,例如想要用一个页面未找到异常来终止请求,你可以调用abort(404)
参数:
code-HTTP的错误状态码
# abort(404) abort(500)
捕获错误
- errorhandler 装饰器
- 注册一个错误处理程序,当程序抛出指定错误状态码的时候,就会调用该装饰器所装饰的方法
- 参数:
- code_or_exception – HTTP的错误状态码或指定异常
- 例如统一处理状态码为500的错误给用户友好的提示:
@app.errorhandler(500) def internal_server_error(e): return '服务器搬家了'
捕获指定的异常
@app.errorhandler(ZeroDivisionError) def zero_division_error(e): return '除数不能为0'
请求钩子(避免写重复代码)
在客户端和服务器交互的过程中,有些准备工作或扫尾工作需要处理,比如:
- 在请求开始时,建立数据库连接;
- 在请求开始时,根据需求进行权限校验;
- 在请求结束时,指定数据的交互格式;
为了让每个视图函数避免编写重复功能的代码,Flask提供了通用设施的功能,即请求钩子。
请求钩子是通过装饰器的形式实现,Flask支持如下四种请求钩子:
before_first_request
在处理第一个请求前执行
before_request
在每次请求前执行
如果在某修饰的函数中返回了一个响应,视图函数将不再被调用
after_request
如果没有抛出错误,在每次请求后执行
接受一个参数:视图函数作出的响应
在此函数中可以对响应值在返回之前做最后一步修改处理
需要将参数中的响应在此参数中进行返回
teardown_request:
在每次请求后执行
接受一个参数:错误信息,如果有相关错误抛出
代码:
from flask import Flask,abort #接收一个参数__name__,指向程序所在的包 app = Flask(__name__) #装饰器的作用就是将路由映射到函数 #在第一次请求前调用,可以在此方法下做一些初始化操作 @app.before_first_request def before_first_request(): print("打印的是:before_first_request") """在每一次请求前调用,这个时候已经有请求了, 可能在这个方法里面做请求的校验 如果校验不成功,可以直接在此方法中进行响应, 直接return之后那么就不会执行视图函数。 """ @app.before_request def before_request(): print("这里打印的是before_request") """ 在执行完视图函数之后会调用,并且会把视图函数所生成的 响应传入,可以再此方法中对响应做最后一步统一的处理 """ @app.after_request def after_request(response): print("这是after_request") response.headers["Content-Type"] = "application/json" return response """ 每一次请求之后都会调用,会接受一个参数, 参数就是服务器出现的错误信息 """ @app.teardown_request def teardown_request(e): print("teardown_request") @app.route("/") def index(): return "index" if __name__ == '__main__': app.run(debug=True,port=8008)
第一次打印结果:
打印的是:before_first_request
这里打印的是before_request
这是after_request
teardown_request
第二次打印结果:
这里打印的是before_request
这是after_request
teardown_request
装饰器路由具体实现梳理
Flask有两大核心:Werkzeug和Jinja2
1、Werkzeug实现路由、调试和Web服务器网关接口
2、Jinja实现了模板
Werkzeug是一个遵循WSGI协议的python函数库,其内部实现了很多Web框架底层的东西。
1、比如request和response对象。
2、与WSGI规范的兼容,支持Unicode
3、支持基本的会话管理和签名Cookie
4、集成URL请求路由等
Werkzeug库的routing模块负责实现URL解析。不同的URL对应不同的视图函数,routing模块会对请求信息的URL进行解析,匹配到URL对应的视图函数,执行该函数以此生成一个响应信息。
routing模块内部有:
Rule类
用来构造不同的URL模式的对象,路由URL规则
Map类
存储所有的URL规则和一些配置参数
BaseConverter的子类
负责定义匹配规则
MapAdapter类
负责协调Rule做具体的匹配的工作
request
request 就是flask中代表当前请求的 request 对象,其中一个请求上下文变量(理解成全局变量,在视图函数中直接使用可以取到当前本次请求)
常用的属性:
属性 | 说明 | 类型 |
---|---|---|
data | 记录请求的数据,并转换为字符串 | * |
form | 记录请求中的表单数据 | MultiDict |
args | 记录请求中的查询参数 | MultiDict |
cookies | 记录请求中的cookie信息 | Dict |
headers | 记录请求中的报文头 | EnvironHeaders |
method | 记录请求使用的HTTP方法 | GET/POST |
url | 记录请求的URL地址 | string |
files | 记录请求上传的文件 | * |
状态保持
- 因为 http 是一种无状态协议,浏览器请求服务器是无状态的。
- 无状态:指一次用户请求时,浏览器、服务器无法知道之前这个用户做过什么,每次请求都是一次新的请求。
- 无状态原因:浏览器与服务器是使用 socket 套接字进行通信的,服务器将请求结果返回给浏览器之后,会关闭当前的 socket 连接,而且服务器也会在处理页面完毕之后销毁页面对象。
- 有时需要保持下来用户浏览的状态,比如用户是否登录过,浏览过哪些商品等
- 实现状态保持主要有两种方式:
- 在客户端存储信息使用
Cookie
- 在服务器端存储信息使用
Session
- 在客户端存储信息使用
无状态协议:
- 协议对于事务处理没有记忆能力
- 对同一个 url 请求没有上下文关系
- 每次的请求都是独立的,它的执行情况和结果与前面的请求和之后的请求是无直接关系的,它不会受前面的请求应答情况直接影响,也不会直接影响后面的请求应答情况
- 服务器中没有保存客户端的状态,客户端必须每次带上自己的状态去请求服务器
- 人生若只如初见
Cookie
- Cookie:指某些网站为了辨别用户身份、进行会话跟踪而储存在用户本地的数据(通常经过加密)。
- 复数形式Cookies。
- Cookie最早是网景公司的前雇员Lou Montulli在1993年3月的发明。
- Cookie是由服务器端生成,发送给客户端浏览器,浏览器会将Cookie的key/value保存,下次请求同一网站时就发送该Cookie给服务器(前提是浏览器设置为启用cookie)。
- Cookie的key/value可以由服务器端自己定义。
- 应用:
- 最典型的应用是判定注册用户是否已经登录网站,用户可能会得到提示,是否在下一次进入此网站时保留用户信息以便简化登录手续,这些都是Cookie的功用。
- 网站的广告推送,经常遇到访问某个网站时,会弹出小窗口,展示我们曾经在购物网站上看过的商品信息。
- 购物车,用户可能会在一段时间内在同一家网站的不同页面中选择不同的商品,这些信息都会写入Cookie,以便在最后付款时提取信息。
-
提示:
- Cookie是存储在浏览器中的一段纯文本信息,建议不要存储敏感信息如密码,因为电脑上的浏览器可能被其它人使用
- Cookie基于域名安全,不同域名的Cookie是不能互相访问的
- 如访问itcast.cn时向浏览器中写了Cookie信息,使用同一浏览器访问baidu.com时,无法访问到itcast.cn写的Cookie信息
- 浏览器的同源策略
-
当浏览器请求某网站时,会将本网站下所有Cookie信息提交给服务器,所以在request中可以读取Cookie信息
设置cookie
from flask import Flask,make_response app = Flask(__name__) @app.route("/cookie") def cookie(): resp = make_response("<h1>这是在建立cookie</h1>") resp.set_cookie("username","itcast") return resp if __name__ == '__main__': app.run(debug=True,port=8009)
设置过期时间:
@app.route('/cookie') def set_cookie(): response = make_response('hello world') #设置过期时间 response.set_cookie('username', 'lalalla', max_age=3600) return response
获取cookie
from flask import Flask,request #获取cookie @app.route('/request') def resp_cookie(): resp = request.cookies.get('username') return resp
Session
- 对于敏感、重要的信息,建议要存储在服务器端,不能存储在浏览器中,如用户名、余额、等级、验证码等信息
- 在服务器端进行状态保持的方案就是
Session
- Session依赖于Cookie
Session数据的获取
session:请求上下文对象,用于处理http请求中的一些数据内容
@app.route('/index1') def index1(): session['username'] = 'itcast' return redirect(url_for('index')) @app.route('/') def index(): return session.get('username')
记得设置secret_key: app.secret_key = 'itheima' secret_key的作用:
SECRET_KEY 配置变量是通用密钥, 可在 Flask 和多个第三方扩展中使用. 如其名所示, 加密的强度取决于变量值的机密度. 不同的程序要使用不同的密钥, 而且要保证其他人不知道你所用的字符串.
SECRET_KEY 的作用(https://segmentfault.com/q/1010000007295395)主要是提供一个值做各种 HASH, 没有实际研究过源码, 不同框架和第三方库的功能也不尽相同, 不能给出准确的答案, 但是主要的作用应该是在其加密过程中作为算法的一个参数(salt 或其他). 所以这个值的复杂度也就影响到了数据传输和存储时的复杂度.
另外, 考虑到安全性, 这个密钥是不建议存储在你的程序中的. 最好的方法是存储在你的系统环境变量中, 通过 os.getenv(key, default=None)
获得.
上下文
上下文:相当于一个容器,保存了 Flask 程序运行过程中的一些信息。
Flask中有两种上下文,请求上下文和应用上下文
请求上下文(request context)
思考:在视图函数中,如何取到当前请求的相关数据?比如:请求地址,请求方式,cookie等等
在 flask 中,可以直接在视图函数中使用 request 这个对象进行获取相关数据,而 request就是请求上下文的对象,保存了当前本次请求的相关数据,请求上下文对象有:request、session
- request
- 封装了HTTP请求的内容,针对的是http请求。举例:user = request.args.get('user'),获取的是get请求的参数。
- session
- 用来记录请求会话中的信息,针对的是用户信息。举例:session['name'] = user.id,可以记录用户信息。还可以通过session.get('name')获取用户信息。
应用上下文(application context)
它的字面意思是 应用上下文,但它不是一直存在的,它只是request context 中的一个对 app 的代理(人),所谓local proxy。它的作用主要是帮助 request 获取当前的应用,它是伴 request 而生,随 request 而灭的。
应用上下文对象有:current_app,g
current_app
应用程序上下文,用于存储应用程序中的变量,可以通过current_app.name打印当前app的名称,也可以在current_app中存储一些变量,例如:
- 应用的启动脚本是哪个文件,启动时指定了哪些参数
- 加载了哪些配置文件,导入了哪些配置
- 连了哪个数据库
- 有哪些public的工具类、常量
- 应用跑再哪个机器上,IP多少,内存多大
current_app.name current_app.test_value='value'
g变量
g 作为 flask 程序全局的一个临时变量,充当者中间媒介的作用,我们可以通过它传递一些数据,g 保存的是当前请求的全局变量,不同的请求会有不同的全局变量,通过不同的thread id区别
g.name='abc'
注意:不同的请求,会有不同的全局变量
两者区别:
- 请求上下文:保存了客户端和服务器交互的数据
- 应用上下文:flask 应用程序运行过程中,保存的一些配置信息,比如程序名、数据库连接、应用信息等
application 就是指app = Flask(__name__)
对象
request 就是对应每次http 请求创建的Request
对象
flask通过_RequestContext
将app
与Request
关联起来
总结
-
app = Flask(__name__)
创建了application, 这个application对应的上下文,就是application context -
Flask
每响应一个http请求,就会创建一个Request
对象,这个request对象对应的上下文,就是request context
Flask-Script扩展
通过使用Flask-Script扩展,我们可以在Flask服务器启动的时候,通过命令行的方式传入参数。而不仅仅通过app.run()方法中传参,比如我们可以通过:
python hello.py runserver -host ip地址
以上代码告诉服务器在哪个网络接口监听来自客户端的连接。默认情况下,服务器只监听来自服务器所在的计算机发起的连接,即localhost连接。
我们可以通过python hello.py runserver --help来查看参数。
代码实现:
1、安装Flask-Script扩展
pip install flask-script
2、集成Flask-Script
from flask import Flask from flask_script import Manager app = Flask(__name__) # 把 Manager 类和应用程序实例进行关联 manager = Manager(app) @app.route('/') def index(): return 'OK here' if __name__ == "__main__": manager.run()
备注:Flask-Script 还可以为当前应用程序添加脚本命令,后续项目中会使用到