Fork me on GitHub

视图及路由

目录

[TOC]

HTTP 通信过程与框架

web 开发各个节点关系图
web 开发各个节点关系.png

web 应用程序请求过程

虚拟环境安装

详细笔记:虚拟环境笔记

第一个 Flask 实例

app 的创建
app = Flask(__name__)
__name__该xx.py文件就是项目目录,到时候回去这个目录下去找静态文件目录,模板文件目录

视图函数和 url 绑定
装饰器的形式:@app.route('请求的 URL')

如何启动项目
app.run(host=xxx, port=8888)
from flask import Flask

"""
app:项目的应用对象

__name__ 取值:
1. python demo1_helloworld.py 方式运行 ————> __name__ == __main__
2. import demo1_helloworld 模块导入方式运行 ————> __name__ == demo1_helloworld

__name__ 作用:
demo1_helloworld.py 文件所在的目录就是 flask 项目目录,
进而会在这个项目的目录文件夹下寻找 static 静态文件目录和模板文件目录
"""
app = Flask(
__name__,
static_path="/static", # 访问静态文件的 url 的前缀(过期)
static_url_path="/static", # 访问静态文件的 url 的前缀
static_folder="static", # 保存静态文件的目录文件夹路径
template_folder="templates" # 保存模板文件的目录文件夹路径
)

"""
视图函数和 url 绑定
"""
# 装饰器的作用是将路由映射到视图函数
# 127.0.0.1:5000/ --> index 视图函数
@app.route('/')
def index():
return 'index'

# 127.0.0.1:5000/demo1 ————> demo1 视图函数
@app.route('/demo1')
def demo1():
return 'demo1'


# 127.0.0.1:5000/user/jovelin ————> demo2 视图函数
# <变量> 转换器的形式提取 URL 路径后面的参数
@app.route('/user/<user_id>')
def demo2(user_id):
return 'demo2 user_id = %s' % user_id


# 127.0.0.1:5000/user/1001 ————> demo3 视图函数
# <int:变量> 转换器的形式提取 URL 路径后面整形类的参数 int ——> IntegerConverter
@app.route('/user/<int:user_id>')
def demo3(user_id):
return 'demo3 user_id = %d' % user_id


# 通过设置 methods 列表属性的方式修改的视图函数的请求方式,OPTIONS(自带)、HEAD(自带)
@app.route('/demo4', methods=['GET', 'POST'])
def demo4():
return 'demo4'


"""
启动项目
"""
if __name__ == '__main__':

# url_map:将装饰器路由和视图的对应关系保存起来
# Map([<Rule '/' (OPTIONS, HEAD, GET) -> index>, <Rule '/static/<filename>' (OPTIONS, HEAD, GET) -> static>])
print(app.url_map)

# 端口占用问题:
# 1. lsof -i:5000(端口)
# 2. kill pid(杀掉对应pid)
app.run('0.0.0.0', 5000)

给 Flask 项目添加配置

# 第一种方式:从对象类中加载 flask 配置信息(推荐)
class Config(object):
# flask 项目配置类,通过属性的方式罗列配置信息即可
DEBUG = True

app.config.from_object(Config)

# 第二种方式:可以从 py 文件中加载 flask 配置
# app.config.from_pyfile("config.ini")

# 第三种方式:可以从环境变量中加载 flask 配置
# path:RUN -> Edit Config... -> Environment variables -> ... -> +
# app.config.from_envvar("flask_config")

# 常见的配置属性,也会在 app 中封装一份(以便我们使用)
app.debug = True
# app.secret_key
app.config["DEBUG"] = True


# 获取 flask 配置的属性值
print(app.config["DEBUG"])
# app.config.get("DEBUG")

json 数据格式

jsonifyflask 模块,推荐使用)

jsonify(dict)
能够将 dict 转换成 json 字符串,还能指明返回数据类型:contentType: application/json
并将返回的内容组织成 response 对象,注意:在视图函数中使用

jsonpython 自带模块)

json.dumps(dict)
字典转成 json 字符串

json.loads(json_str)
json 字符串转成字典
from flask import Flask, jsonify
import json

app = Flask(__name__)


@app.route('/')
def index():
return 'index'


@app.route('/demo1')
def demo1():
# 1. 定义一个字典
dict1 = {
"name": "james",
"age": 34,
"info": {
"team": "laker"
}
}

# 2. 将 python 字典对象转换成 json 字符串
my_json = json.dumps(dict1)

# 3. 将 json 字符串转化成 python 字典对象
my_dict = json.loads(my_json)

# 4. jsonify 的作用
# 4.1 可以将字典转成 json 字符串
# 4.2 指定返回字符串的数据类型:contentType: application/json
# 4.3 并将返回的内容组织成 response 对象
json_str = jsonify(dict1)
return json_str


if __name__ == '__main__':
app.run('0.0.0.0', 5000, debug=True)

重定向、自定义状态码

from flask import Flask, redirect, url_for

app = Flask(__name__)


@app.route('/')
def index():
return 'index'


@app.route('/demo1')
def demo1():
# 重定向到指定网站
return redirect('http://jovelin.cn')


@app.route('/demo2')
def demo2():
# 重定向到自己的路径
# url_for(函数名称) 返回:根据函数名称将该视图函数对应的 URL 返回
return redirect(url_for('index'))


@app.route('/demo3')
def demo3():
# 自定义状态码
# return 'demo3', 666
# 自定义状态码并声明状态码类型
return 'demo3', '666 six six six'


if __name__ == '__main__':
app.run('0.0.0.0', 5000, debug=True)

正则匹配路由(转换器)

1. 普通转换器<参数类型:参数>  

2. 自定义转换器(能够理解)
1.创建自定义类
class RegxConverter(BaseConverter):
"""自定义正则匹配类"""
# regex = "[0-9]{6}"

def __init__(self, url_map, re):
super(RegxConverter, self).__init__(url_map)
# 接受外界传入的正则表达式,进行赋值
self.regex = re

2.将自定义的正则类关联到 url_map
app.url_map.converters["re"] = RegxConverter

3.使用
'<re("[0-9]{6}"):user_id>'

to_python(了解)
re正则匹配成功后的值会传入to_python,可以拦截处理,最终返回

to_url(了解)
调用url_for函数,会将参数传入到to_url最终构建一个完整的url返回

to_python() 图解

to_python.gif

to_url() 图解

tu_url.gif

from flask import Flask, url_for
from werkzeug.routing import BaseConverter

app = Flask(__name__)


# 1. 自定义正则配置类
class RegexConverter(BaseConverter):
# 将正则规则赋值给 regex,BaseConverter 就会自动帮你去完成正则配置
# regex = '[0-9]{6}'
# BaseConverter 这个类帮我们去传入了一个属性 map :路由映射
# 我们只需要负责传入 re 正则字符串即可
def __init__(self, map, re):
super(RegexConverter, self).__init__(map)
self.regex = re

def to_python(self, value):
print('拦截 to_python value = %s' % value)
# 可以拦截自己处理,但是一般不这么做
return value

def to_url(self, value):
print('拦截 to_url value = %s' % value)
return value


# 2. 注册
# { re : RegexConverter }
app.url_map.converters['re'] = RegexConverter


# 127.0.0.1:5000/user/123456 --> [0-9]{6}
# @app.route('/user/<re:user_id>')
# 127.0.0.1:5000/user/1234 --> [0-9]{4}
# 3. 使用 <re(正则字符串):变量名>
@app.route('/user/<re("[0-9]{4}"):user_id>')
def user(user_id):
return 'user user_id = %s' % user_id


# to_url 拦截
@app.route('/redirect_user')
def redirect_user():
return '<a href="%s">redirect_user</a>' % url_for('user', user_id='1001')


@app.route('/')
def index():
return 'index'


if __name__ == '__main__':
app.run('0.0.0.0', 5000, debug=True)

异常捕获

from flask import Flask, abort, redirect

app = Flask(__name__)


"""HTTP 异常主动抛出"""
@app.route('/')
def index():
# 主动产生异常的函数
# 参数:必须是 HTTP 的错误状态码
abort(404)
return 'index'


@app.route('/zero')
def zero():
a = 1 / 0
redirect('zero')


"""捕获错误"""
@app.errorhandler(404)
def error_handler(e):
print(e)
# 当捕获到 404 异常后,重定向到 404 错误页面
return redirect('http://jovelin.cn/404.html')


# 捕获指定异常
@app.errorhandler(ZeroDivisionError)
def error_handler1(e):
print(e)
return "不能除0"


if __name__ == '__main__':
app.run('0.0.0.0', 5000, debug=True)

请求钩子

在客户端和服务器交互的过程中,有些准备工作或扫尾工作需要处理,比如:

在请求开始时,建立数据库连接;
在请求开始时,根据需求进行权限校验;
在请求结束时,指定数据的交互格式;

为了让每个视图函数避免编写重复功能的代码,Flask 提供了通用设施的功能,即请求钩子。

请求钩子是通过装饰器的形式实现,Flask 支持如下四种请求钩子:
from flask import Flask, request

app = Flask(__name__)


"""
# 在第一次请求之前调用,可以在此方法内部做一些初始化操作
# 应用场景:数据库的第一次连接
"""
@app.before_first_request
def before_first_request():
print('before_first_request')


"""
# 在每一次请求之前调用,这时候已经有请求了,可能在这个方法里面做请求的校验
# 如果请求的校验不成功,可以直接在此方法中进行响应,直接 return 之后那么就不会执行视图函数
# 应用场景:封 ip 处理、过滤处理
"""
@app.before_request
def before_request():
print('before_request')
# 黑名单的 ip
ips = []
if request.remote_addr in ips:
# 假装封 ip 处理
pass


"""
# 在执行完视图函数之后会调用,并且会把视图函数所生成的响应传入,可以在此方法中对响应做最后一步统一的处理
# 应用场景:拿到响应对象,进行统一处理
"""
@app.after_request
def after_request(response):
print("after_request")
response.headers["Content-Type"] = "application/json"
# 设置 cookie
# response.set_cookie()
return response

"""
# 请每一次请求之后都会调用,会接受一个参数,参数是服务器出现的错误信息
"""
@app.teardown_request
def teardown_request(e):
print("teardown_request")


@app.route('/')
def index():
return 'index'


if __name__ == '__main__':
app.run('0.0.0.0', 5000, debug=True)

装饰器路由具体实现

Flask路由实现代码结构.gif

1. Flask 有两大核心:Werkzeug 和 Jinja2

- Werkzeug 实现路由、调试和 Web 服务器网关接口
- Jinja2 实现了模板。

2. Werkzeug 是一个遵循 WSGI 协议的 python 函数库

- 其内部实现了很多 Web 框架底层的东西,比如 request 和 response 对象;
- 与 WSGI 规范的兼容;支持 Unicode;
- 支持基本的会话管理和签名 Cookie;
- 集成 URL 请求路由等。

3. 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 记录请求上传的文件 *
from flask import Flask, request

# get 请求:通过携带参数的方式去获取服务器的资源(参数作用:告知服务器我要获取什么类型的数据)
# get 请求的参数是放到问号后面,使用 & 符号进行拼接

# post 请求:将客户端的数据发送给服务器,在服务器新建数据(保存到服务器)
# post 请求的参数是放到请求体里面

app = Flask(__name__)


# 127.0.0.1:5000/get?name=curry&age=18
# get 问号后面的参数:request.args 属性
@app.route('/get')
def get():
"""使用 request.args 属性提取 get 请求携带的参数"""
# request.method 获取请求方式,返回的值是大写格式
if request.method == "GET":
# 格式:request.args.get("key", "")
name = request.args.get("name", "")
age = request.args.get("age", "")
return "%s ---- %s" % (name, age)
else:
return "请求方式不是 get 405"


# 127.0.0.1:5000/post
# post 请求体数据:request.form 属性
@app.route('/post', methods=['POST'])
def post():
"""通过 request.form 属性提取 post 请求体(表单)里面的数据"""
if request.method == 'POST':
# 格式:request.form.get(key, "")
name = request.form.get("name", "")
age = request.form.get("age", "")
return "%s ---- %s" % (name, age)
else:
return "请求方式不是 post 405"


# 127.0.0.1:5000/upload
# 文件数据:request.files 属性提取
@app.route('/upload', methods=["post"])
def upload():
"""使用 request.files 属性 接受上传的图片"""
if request.method == 'POST':
# 1.接受图片数据
pic = request.files.get("pic")
# 2.保存图片数据 (保存到本地 并修改其名字为: 1.png)
pic.save("./1.png")
return 'upload success'
else:
return "请求方式不是post 405"


if __name__ == '__main__':
app.run(debug=True)
因为 http 是一种无状态协议,浏览器请求服务器是无状态的。

无状态:指一次用户请求时,浏览器、服务器无法知道之前这个用户做过什么,每次请求都是一次新的请求。

无状态原因:浏览器与服务器是使用 socket 套接字进行通信的,服务器将请求结果返回给浏览器之后,会关闭当前的 socket 连接,而且服务器也会在处理页面完毕之后销毁页面对象。

有时需要保持下来用户浏览的状态,比如用户是否登录过,浏览过哪些商品等

实现状态保持主要有两种方式:

在客户端存储信息使用Cookie

在服务器端存储信息使用Session
1. cookie

1. cookie 是由服务器端生成,发送给客户端浏览器,浏览器会将 cookie 的 key/value 保存,
下次请求同一网站时就发送该 cookie 给服务器(前提是浏览器设置为启用 cookie)。

2. cookie 基于域名安全,不同域名的 cookie 是不能互相访问的
- 如访问 jovelin.cn 时向浏览器中写了 cookie 信息,使用同一浏览器访问 baidu.com 时,无法访问到 jovelin.cn 写的 cookie 信息
- 浏览器的同源策略

3. 设置 cookie

from flask imoprt Flask,make_response
@app.route('/cookie')
def set_cookie():
resp = make_response('this is to set cookie')
resp.set_cookie('userid', '1001')
# 设置过期时间
response.set_cookie('username', 'jovelin', max_age=3600)
return resp

4. 获取cookie

from flask import Flask,request
# 获取cookie
@app.route('/request')
def resp_cookie():
resp = request.cookies.get('username')
return resp

2. session

session: 请求上下文对象,用于处理 http 请求中的一些数据内容

1. 对于敏感、重要的信息,建议要存储在服务器端,不能存储在浏览器中,如用户名、余额、等级、验证码等信息

2. 在服务器端进行状态保持的方案就是 session

3. session 依赖于 cookie

4. session 数据的获取

from flask import Flask, session

# SECRET_KEY 加密字符串:flask 拿到这个混淆字符串和 session 里面的数据一起加密
# app.secret_key = "SECRET_KEY 随机字符串"
app.config["SECRET_KEY"] = "SECRET_KEY"

@app.route('/login')
def login():
# 存储数据到 session
session['username'] = 'jovelin'
return redirect(url_for('index'))

@app.route('/')
def index():
# 提取 session 里面的键值对数据
username = session.get('username', '')
return username

@app.route('/login_out')
def login_out():
# 删除 session 里面的键值对数据
session.pop("username")
return "loginout success"

上下文

上下文:相当于一个容器,保存了 Flask 程序运行过程中的一些信息。

Flask 中有两种上下文,请求上下文和应用上下文。

1. 请求上下文(request context)

1. request
封装了 HTTP 请求的内容,针对的是 http 请求。

2. session
用来记录请求会话中的信息,针对的是用户信息。

2. 应用上下文(application context)

1. current_app(app 的别名)
应用程序上下文,用于存储应用程序中的变量

2. g(本次请求的 临时全局变量)
注意:不同的请求,会有不同的全局变量

3. 两者区别:
请求上下文:保存了客户端和服务器交互的数据
应用上下文:flask 应用程序运行过程中,保存的一些配置信息,比如程序名、数据库连接、应用信息等

Flask-Script 扩展

通过使用 Flask-Script 扩展,我们可以在 Flask 服务器启动的时候,通过命令行的方式传入参数。
而不仅仅通过 app.run() 方法中传参。

1. 安装 Flask-Script 扩展:pip install flask-script

2. 查看参数:python3 manager.py runserver --help

3. 运行命令:python3 manager.py runserver -h 地址 -p 端口

4. 代码实现

from flask import Flask
# 1.导入管理类
from flask_script import Manager

app = Flask(__name__)

# 2.创建管理类
manager = Manager(app)


@app.route('/')
def hello_world():
return 'Hello World!'


if __name__ == '__main__':
# app.run(debug=True)
# 3.使用管理类运行 flask 项目:manager.run()
manager.run()

5. 命令行运行

# 查看脚本运行需要携带那些参数
python3 manager.py runserver --help
# python3 项目名称 runserver -h ip地址 -p 端口号 -d 开启 debug 模式
python3 manager.py runserver -h 127.0.0.1 -p 8888 -d
-------------本文结束感谢您的阅读-------------

本文标题:视图及路由

文章作者:曹永林

发布时间:2018年08月15日 - 09:08

最后更新:2018年08月27日 - 23:08

原始链接:http://jovelin.cn/2018/08/15/视图及路由/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。