0.Flask入门

1. WSGI

1.1 简介
了解了HTTP协议和HTML文档, 我们其实就明白了一个Web应用的本质就是:
浏览器发送一个HTTP请求;
服务器收到请求, 生成一个HTML文档;
服务器把HTML文档作为HTTP响应的Body发送给浏览器;
浏览器收到HTTP响应, 从HTTP Body取出HTML文档并显示.
所以, 最简单的Web应用就是先把HTML用文件保存好, 
用一个现成的HTTP服务器软件, 接收用户请求, 从文件中读取HTML, 返回.

Apache、Nginx、Lighttpd等这些常见的静态服务器就是干这件事情的.
如果要动态生成HTML, 就需要把上述步骤自己来实现.
不过, 接受HTTP请求、解析HTTP请求、发送HTTP响应都是苦力活, 
如果我们自己来写这些底层代码, 还没开始写动态HTML呢, 就得花个把月去读HTTP规范.

正确的做法是底层代码由专门的服务器软件实现, 我们用Python专注于生成HTML文档.
因为我们不希望接触到TCP连接、HTTP原始请求和响应格式, 
所以, 需要一个统一的接口来实现这样的服务器软件, 让我们专心用Python编写Web业务.
这个接口就是WSGI, wsgi也是基于socket server编写.
WSGI(Web Server Gateway Interface,web服务器网关接口)
主要规定了服务器端和应用程序之间的接口,
即规定了请求的URL到后台处理函数之间的映射该如何实现.
1.2 WSGI基本原理

img

* 1. 浏览器到WSGI Server:浏览器发送的请求会先到WSGI Server。

* 2. environ:WSGI Server会将HTTP请求中的参数等信息封装到environ(一个字典)中。

* 3. WSGI Server到WSGI App:App就是我们自己编写的后台程序,每个URL会映射到对应的入口处理函数(或其他可调用对象),WSGI Server调用后台App时,会将environ和WSGI Server中自己的一个start_response函数注入到后台App中。
* 4. 逻辑处理:后台函数(或其他可调用对象)需要接收environ和start_response,进行逻辑处理后返回一个可迭代对象,可迭代对象中的元素为HTTP正文。

* 5. WSGI App到WSGI Server:后台函数处理完后,会先调用start_response函数将HTTP状态码、报文头等信息(响应头)返回给WSGI Server,然后再将函数的返回值作为HTTP正文(响应body)返回给WSGI Server。

* 6. WSGI Server到浏览器:WSGI Server将从App中得到的所有信息封装为一个response返回给浏览器
1.3 wsgiref介绍
wsgiref是Python基于WSGI协议开发的服务模块.(Django使用)
wsgiref是Python自带的内置库,它用来开发者对wsgi进行测试用的,wsgiref性能较低在生产环会替换其他的库.
官方文档: https://docs.python.org/zh-tw/3.10/library/wsgiref.html
1.4 实例
* 1. 实例程序
# 从简单服务中导入 制作服务和实例应用
from wsgiref.simple_server import make_server, demo_app

"""
默认情况下会将所有url都传入demo_app进行处理,具体可参考demo_app源码
app参数可以是任何可调用对象,但是内部处理需要参考demo_app源码,
即environ处理、start_response调用、返回值类型
"""

ws = make_server('127.0.0.1', 9999, demo_app)
# 启动服务
ws.serve_forever()
* 2. 运行实例程序, 在浏览器中输入: http://127.0.0.1:9999/ 访问服务器中的应用, 会返回结果.
请求地址
PATH_INFO = '/'
请求方式
REQUEST_METHOD = 'GET'
...

image-20220914174713710

* 3. 实例应用源码分析
      print函数修改了file参数的值, 标准化输出到终端改为输出到内存中进行缓存,
      stdout.getvalue()会取出所有内存中缓存的值.
def demo_app(environ, start_response):
    # StringIO 模块 可以在内存中读写字符串类型数据
    from io import StringIO
    # 生成对象, stdout标准化输出
    stdout = StringIO()
    # 将Hello world!输入代内存中
    print("Hello world!", file=stdout)
    # 将空行输入到内存中
    print(file=stdout)
    # environ是一个字典,包含了所有请求信息, sorted函数按key值对字典排序
    h = sorted(environ.items())
    # 遍历字典的数据
    for k, v in h:
    	# repr() 函数将对象转化为供解释器读取的形式.
        print(k, '=', repr(v), file=stdout)
    # return之前需要调用start_response设置响应头信息, 
    # 浏览器获取元素内容依据start_response中指定的Content-Type来解析
    start_response("200 OK", [('Content-Type', 'text/plain; charset=utf-8')])
    # 一个可迭代对象,元素为byte类型,
    return [stdout.getvalue().encode("utf-8")]  

1.5 自定义应用
WSGI接口定义非常简单,它只要求Web开发者实现一个函数,就可以响应HTTP请求。
* 1. 在项目新建login.html页面
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>login页面</title>
</head>
<body>
    <h1>login页面</h1>
</body>

* 2. 在项目新建index.html页面
</html>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>index页面</title>
</head>
<body>
<h1>index页面</h1>
</body>
</html>

* 3. 自定义app应用
from wsgiref.simple_server import make_server, demo_app


# 自定义应用(定义一个函数)
def myapp(environ, start_response):
    print(environ.get('PATH_INFO'))

    # 获取路径进行判断, 返回不通的结果
    if environ.get('PATH_INFO') == '/index':
        with open('index.html', mode='rb') as wf:
            data = wf.read()
	
    elif environ.get('PATH_INFO') == '/login':
        with open('login.html') == '/login' as wf:
            data = wf.read()
    else:
        data = b'<h1>Hello World!</h1>'

    # 设置响应头data
    start_response('200 OK', [('Content-Type', 'text/html')])
    # 少了start_response浏览器提示 A server error occurred.  Please contact the administrato

    # 返回响应体, 响应体是一个可迭代的数据, 直接使用[]括起来即127可
    return [data]


# 设置访问ip, 端口, 处理请求的应用
ws = make_server('127.0.0.1', 8081, myapp)
# 启动服务
ws.serve_forever()

* 4. 运行程序在浏览器中测试, 分别输入以下地址查看结果:
     127.0.0.1:8081
     127.0.0.1:8081/login
     127.0.0.1:8081/index

image-20220914184602060

2. werkzeug

2.1 介绍
werkzeug是一个WSGI工具包, 封装了很多WEB框架的东西, 例如 Request, Response...
werkzeug可以作为一个WEB框架的底层库, 可以当成是一个微型的WSGI框架.
2.2 werkzeug的使用
* 1. 安装werkzeug模块
     命令: pip install werkzeug
PS C:\Users\13600\Desktop\Python Project\psutils> pip install werkzeug
Collecting werkzeug
  Downloading Werkzeug-2.2.2-py3-none-any.whl (232 kB)
     |████████████████████████████████| 232 kB 409 kB/s
Collecting MarkupSafe>=2.1.1
...
* 2. 简单的WEB服务
# 导入请求模块 与 响应模块
from werkzeug.wrappers import Request, Response

# 装饰器作用于应用上
@Request.application
def hello(request):
    # 响应信息
    return Response('Hello World!')


if __name__ == '__main__':
    # 导入核心模块run_simple, 后续会详情讲解
    from werkzeug.serving import run_simple
	# ip:端口:应用名, hello函数会接收到request函数
    run_simple('localhost', 4000, hello)

* 3. 程序测试

2022-09-12_00886

3. Flask

3.1 介绍
Flask是基于jinja2模板和Werkzeug WSGI服务的一个微型架构.
Werkzeug的本质是Socket服务端, 接收http请求并对请求进行预处理, 然后触发Flask框架, 
开发人员基于Flask框架提供的功能对请求进行相对处理, 返回给用户浏览器,
需要返回模板页面时, 可借助jinja2模板对模板和数据进行渲染, 
将渲染后的字符串返回给用户浏览器.
Flask的宗旨是在保持核心简单而易于拓展.
默认情况下, Flask不包含数据库抽象层, 表单验证, 等功能,
然而, Flask支持用拓展来给应用添加这些功能
3.2 Flask入门
* 1. 安装Flask模块
  命令: pip install flask
PS C:\Users\13600\Desktop\Python Project\werkzeug> pip install flask
Collecting flask issue with the package mentioned above, not pip.
  Downloading Flask-2.2.2-py3-none-any.whl (101 kB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 101.5/101.5 kB 449.4 kB/s eta 0:00:00
* 2. 实例程序
from flask import Flask

# 为对象起一个名字, 默认使用__main__即可, 实例化得到一个对象,
app = Flask(__name__)


# 为应用设置路由
@app.route('/')
def index():
    # 返回信息
    return 'Hello World!'


# 启动服务
if __name__ == '__main__':
    app.run()

* 3. 运行程序, 终端提示:
"C:\Speciality Program Files\Python3.8.5\python.exe" "C:/Users/13600/Desktop/Python Project/werkzeug/test1.py"
 * Serving Flask app 'test1'
 * Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on http://127.0.0.1:5000
Press CTRL+C to quit
* 4. 在浏览器中测试, Flask端口默认是5000, 输入地址: 127.0.0.1:5000

image-20220914191938620

3.3 生成app对象代码解析
# 只需要提供模块名称的参数, 其他的参数都有默认值 __name__ 得到 '__main__'
app = Flask(__name__)
    # Flask初始化方法
    def __init__(
        # Flask
        self,
        # 模块名字
        import_name: str,
        # 静态url地址
        static_url_path: t.Optional[str] = None,
        # static folder
        static_folder: t.Optional[t.Union[str, os.PathLike]] = "static",
        # 静态主机
        static_host: t.Optional[str] = None,
        # 主机匹配
        host_matching: bool = False,
        # 子域匹配
        subdomain_matching: bool = False,
        # 模板文件夹
        template_folder: t.Optional[str] = "templates",
        instance_path: t.Optional[str] = None,
        instance_relative_config: bool = False,
        root_path: t.Optional[str] = None,
    ):
        super().__init__(
            import_name=import_name,
            static_folder=static_folder,
            static_url_path=static_url_path,
            template_folder=template_folder,
            root_path=root_path,
        )
       ...
3.3 路由代码解析
# 为应用设置路由, app.route('/')先执行, 将app对象, 与/作为参数传递给route
@app.route('/')
def index():
    # 返回信息
    return 'Hello World!'
# route源码 
@setupmethod  # route(app, '/')
    def route(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]:
        """
		注释文档
        """
	   # decorator(index)
        def decorator(f: T_route) -> T_route:
            # 从options字典参数 弹出endpoint的值, 如果没有就设置为None
            endpoint = options.pop("endpoint", None)
            # self是app对象, 执行app.add_url_rule('/', endpoint, index, **options)
            self.add_url_rule(rule, endpoint, f, **options)
            # 返回函数 index
            return f

        return decorator
3.4 启动代码解析
# 启动服务
if __name__ == '__main__':
	# 执行run()方法, 不提供参数都有默认值
    app.run()
    # app(request)
run()会先执行类的__call__方法, 在执行run方法.
    # wsgi_app方法, 这里是Flask的源码
    def wsgi_app(self, environ: dict, start_response: t.Callable) -> t.Any:
        ctx = self.request_context(environ)
        error: t.Optional[BaseException] = None
        try:
            try:
                ctx.push()
                response = self.full_dispatch_request()
            except Exception as e:
                error = e
                response = self.handle_exception(e)
            except:  # noqa: B001
                error = sys.exc_info()[1]
                raise
            # 将数据返回
            return response(environ, start_response)
        finally:
            if "werkzeug.debug.preserve_context" in environ:
                environ["werkzeug.debug.preserve_context"](_cv_app.get())
                environ["werkzeug.debug.preserve_context"](_cv_request.get())

            if error is not None and self.should_ignore_error(error):
                error = None

            ctx.pop(error)
          
	# 接收environ 与 start_response
    def __call__(self, environ: dict, start_response: t.Callable) -> t.Any:
		
        # 返回对象的wsgi_app方法将 environ 与 start_response 传递进去
        return self.wsgi_app(environ, start_response)
    # run方法中调用werkzeug框架
    def run(
        self,
        host: t.Optional[str] = None,
        port: t.Optional[int] = None,
        debug: t.Optional[bool] = None,
        load_dotenv: bool = True,
        **options: t.Any,
    ) -> None:
   		...
        
    	# ip地址
    	if not host:
        	if sn_host:
            	host = sn_host
        else:
            host = "127.0.0.1"
		
        # 端口号
        if port or port == 0:
            port = int(port)
        elif sn_port:
            port = int(sn_port)
        else:
            port = 5000
    	# run中调werkzeug框架
        from werkzeug.serving import run_simple

        try:
            # 使用werkzeug框架 (ip:端口:应用名, self现在是app, app对象会接收到request)
            run_simple(t.cast(str, host), port, self, **options)
            
     	...

4. Flask路由

4.1 获取路由地址
flask的路由在全局对象request中, request变量会随意访问的应用而更改.
Flask与Django不一样,  Flask默认不会重定向, 注意末尾'/'. 

request对象方法:
.path 获取地址, /, /login
.url  获取http请求协议+ip地址+端口, http://127.0.0.1:5000/
from flask import Flask, request

app = Flask(__name__)


@app.route('/')
def index():
    # 获取路由地址
    print(request.path)
    # 返回信息
    return '索引页面'


@app.route('/login')
def login():
    # 获取路由地址
    print(request.path)
    # 返回信息
    return '登入页面!'


# 启动服务
if __name__ == '__main__':
    app.run()

在浏览器中测试:
http://127.0.0.1:5000
http://127.0.0.1:5000/login

2022-09-14_00890

Flask默认不会重定向, 注意末尾'/'. 
在浏览器中测试:
http://127.0.0.1:5000/login
http://127.0.0.1:5000/login/

2022-09-15_00893

4.2 转换器
1. 介绍
转化器的作用是获取url中地址信息, 并转为指定类型.
需要匹配的路径信息使用<>中括号括起来的, 并在<>括号中定义一个接收到值的变量.
<类型:变量>中括号中可以指定数据转换的类型.
flask自带六种类型的装换器(所以类都继承BaseConverter类):

string: 接受任何不包含‘/’的文本, 默认装换器.
any     
path:   接收包含‘/’的文本
int:    接收正整数
float:  接收正浮点数
uuid:   接受uuid随机字符串
from flask import Flask, request

app = Flask(__name__)

converters = app.url_map.converters
for k, v in converters.items():
    print(k, v)
    """
    default <class 'werkzeug.routing.converters.UnicodeConverter'>
    string <class 'werkzeug.routing.converters.UnicodeConverter'>
    any <class 'werkzeug.routing.converters.AnyConverter'>
    path <class 'werkzeug.routing.converters.PathConverter'>
    int <class 'werkzeug.routing.converters.IntegerConverter'>
    float <class 'werkzeug.routing.converters.FloatConverter'>
    uuid <class 'werkzeug.routing.converters.UUIDConverter'>
    """
2. 使用装换器
数据的转换类型默认为字符串类型, 字符串.
from flask import Flask

app = Flask(__name__)


# 在路由装饰器中设置路由, 并设置需要获取的地址
@app.route('/index/<id_num>')
def index(id_num):
    # 1 <class 'str'>
    print(id_num, type(id_num))
    return 'index'


if __name__ == '__main__':
    app.run()

from flask import Flask

app = Flask(__name__)


# 在路由装饰器中设置路由, 并设置需要获取的地址
@app.route('/index/<int:id_num>')
def index(id_num):
    # 1 <class 'int'>
    print(id_num, type(id_num))
    return 'index'


if __name__ == '__main__':
    app.run()

import uuid
# 生成uuid d9b328c6-ed5f-4a07-b801-3a3031dd29ad 
print(uuid.uuid4())
一些案例:
@app.route('/index/<path:id_num>')    x1/x2 <class 'str'>
只能匹配带小数点的数
@app.route('/index/<float:id_num>')   1.1 <class 'float'>
# 必须是uuid字符串
@app.route('/index/<uuid:id_num>')    d9b328c6-ed5f-4a07-b801-3a3031dd29ad <class 'uuid.UUID'>

3. 自定义转换器
还可能遇到路由中验证手机号/邮箱等业务,这种情况下需要使用正则来自定义转换器.
* 1. 导入自定转换器集成自werkzeug.routing中的BaseConverter类
* 2. 编写自定义转换器类, 在类设置匹配规则.
* 3. 将自定义转换器添加到app.url_map.converters中, app.url_map.converters是一个字典
所有的转换器都是继承BaseConverter类.
# BaseConverter类
class BaseConverter:
    """Base class for all converters."""
	
    # 默认参数
    # 正则
    regex = "[^/]+"
    weight = 100
    part_isolating = True
	
    # 初始化
    def __init__(self, map: "Map", *args: t.Any, **kwargs: t.Any) -> None:
        self.map = map
	
    #  返回到python中的值
    def to_python(self, value: str) -> t.Any:
        return value

    def to_url(self, value: t.Any) -> str:
        if isinstance(value, (bytes, bytearray)):
            return _fast_url_quote(value)
        return _fast_url_quote(str(value).encode(self.map.charset))
flask模块自定义转换器
# 导入flask模块
from flask import Flask
# 导入基础装换器模块
from werkzeug.routing import BaseConverter

# 实例化得到app对象
app = Flask(__name__)


# 定义手机号装换器
class PhoneConverter(BaseConverter):
    # 定义字符串, 字符串中是正则表达式
    regex = '^1[3-9]\d{9}$'

    def __init__(self, url_map):
        # 调用父类方法
        super(PhoneConverter, self).__init__(url_map)
        
    # 返回到python中的数据, 接收的字符串类型
    def to_python(self, value: str):
        # 将字符串类型转为整型
        value = int(value)
        # 将值返回
        return value


# 将自定义转换器添加到app.url_map.converters中, 添加类名即可{'phone' : PhoneConverter}
app.url_map.converters['phone'] = PhoneConverter
# 查看是否添加成功
print(app.url_map.converters['phone'])


# 路由总使用自定义转换器
@app.route('/index/<phone:id_num>')
def index(id_num):
    print(id_num, type(id_num))
    return 'index'


if __name__ == '__main__':
    app.run()

在浏览器中输入测试路由: 
http://127.0.0.1:5000/index/13123456789 查看结果访问成功
http://127.0.0.1:5000/index/1312345678  Not Found 没有查到
另一种使用方法, 在路由中使用转换器时写正则表达式.
from flask import Flask, url_for
from werkzeug.routing import BaseConverter

app = Flask(import_name=__name__)


# 自定义装换器
class RegexConverter(BaseConverter):
    """
    自定义URL匹配正则表达式
    """

    # map接收路径, regex接收正则
    def __init__(self, map, regex):
        super(RegexConverter, self).__init__(map)
        self.regex = regex

    def to_python(self, value):
        # 路由匹配时,匹配成功后传递给视图函数中参数的值,
        print(value)  # 123
        return value

    def to_url(self, value):
        # 使用url_for反向生成URL时,传递的参数经过该方法处理,返回的值用于生成URL中的参数
        # 将上面匹配到的123, 交给to_url处理拼接路由
        val = super(RegexConverter, self).to_url(value)
        print(val)
        return val


# 添加到flask中
app.url_map.converters['regex'] = RegexConverter


# 使用自定义的转换器, ()中写正则表达式
@app.route('/index/<regex("\d+"):nid>')
def index(nid):
    # 需要提供自己解析的nid
    print(url_for('index', nid=nid))  # /index/123
    return 'Index'


if __name__ == '__main__':
    app.run()

在浏览器中输入测试路由: 
http://127.0.0.1:5000/index/123 查看终端显示信息
4.3 请求方法
可以在路由装饰器中使用method参数指定访问应用的请求方式.
@app.route('路由', methods=['请求方法, 支持大小写', ...])
# 导入flask模块
from flask import Flask

# 实例化得到app对象
app = Flask(__name__)


# 路由总使用自定义转换器, 可以通过get, post请求访问,
@app.route('/index', methods=['get', 'post'])
def index():
    return 'index'


if __name__ == '__main__':
    app.run()
在postman中测试查看结果, 可以通过get, post请求访问: http://127.0.0.1:5000/index

image-20220915022641550

4.4 别名
在路由装饰器中通过endpoint参数为路径设置别名,
可以在任何应用中中可以通过url_for函数反向解析获取.

导入url_for函数: from flask import  url_for
# 导入flask模块
from flask import Flask, url_for, redirect

# 生成app对象
app = Flask(__name__)


# endpoint为路由起别名
@app.route('/index', endpoint='i1')
def index():
    # 使用url_for函数通过过别名反向解析获取到路径, 可以获取到任何应用的路由的别买
    index_url = url_for('i1')
    test1_url = url_for('t1')

    print(index_url, test1_url)
    return redirect(test1_url)


# endpoint为路由起别名
@app.route('/test1', endpoint='t1')
def test1():
    return 'test1'


if __name__ == '__main__':
    app.run()

4.5 路由源码
    # 路由源码
    @setupmethod
    def route(self, rule: str, **options: t.Any) -> t.Callable[[T_route], T_route]:


        def decorator(f: T_route) -> T_route:
            endpoint = options.pop("endpoint", None)
            self.add_url_rule(rule, endpoint, f, **options)
            return f

        return decorator
参数self, 是app
参数rule, 为路由
参数options, 接受其他的关键字参数, 如methods.

endpoint 别名设置被单独弹出
路由最终是通过app.add_url_rule(路由, 别名, 被装饰的函数, **options) 实现
    # app的add_url_rule方法, 去app源码中搜索add_url_rule, 点击self.add_url_rule跳转的不对
    @setupmethod
    def add_url_rule(
        # app
        self,
        # 路由
        rule: str,
        # 别名
        endpoint: t.Optional[str] = None,
        # 被路由装饰的函数
        view_func: t.Optional[ft.RouteCallable] = None,
        # 自动选项
        provide_automatic_options: t.Optional[bool] = None,
        # 其他的参数
        **options: t.Any,
    ) -> None:
        # 单别名没有设置的时候
        if endpoint is None:
            # 执行_endpoint_from_view_func并将函数名作为参数传递, 返回一个别名
            endpoint = _endpoint_from_view_func(view_func)  # type: ignore
        # 将别名添加到options中
        options["endpoint"] = endpoint
        # 获取请求方法
        methods = options.pop("methods", None)

		# 没有设置
        if methods is None:
            # 获取view_func被装饰的methods属性, 如果没有methods的值给GET
            methods = getattr(view_func, "methods", None) or ("GET",)
        # 请求方法必须为字符串类型, 某则报错
        if isinstance(methods, str):
            raise TypeError(
                "Allowed methods must be a list of strings, for"
                ' example: @app.route(..., methods=["POST"])'
            )
        # 讲请求方法字符串都转为大写
        methods = {item.upper() for item in methods}
        ...
# 别名来着与视图函数
def _endpoint_from_view_func(view_func: t.Callable) -> str:
	# 路由装饰器装饰的函数不能为Nont
    assert view_func is not None, "expected view func if endpoint is not provided."
    # 放回函数名
    return view_func.__name__
通过源码得知, 在没有给路由起别名的时候, 会默认使用函数名作为别名.
那么意味着在其他应用中可以直接通过默认的别名反向解析得到路由.
# 导入flask模块
from flask import Flask, url_for, redirect

# 生成app对象
app = Flask(__name__)


# endpoint为路由起别名
@app.route('/index', endpoint='i1')
def index():
    # 使用url_for函数通过过别名反向解析获取到路径, 可以获取到任何应用的路由的别买
    index_url = url_for('i1')
    # 默认为使用函数名做为别名
    test1_url = url_for('test1')

    print(index_url, test1_url)
    return redirect(test1_url)


# 不设置别名
@app.route('/test1')
def test1():
    return 'test1'


if __name__ == '__main__':
    app.run()

4.6 注册路由
由源码得出, 路由设置是通过app.add_url_rule方法显示的.
# 导入flask模块
from flask import Flask

# 生成app对象
app = Flask(__name__)


# 定义应用
def test1():
    return 'test1'


# 设置路由
app.add_url_rule('/test1', view_func=test1)

if __name__ == '__main__':
    app.run()

4.7 重名问题
不设置别名默认使用被装饰器装饰的函数名作为别名.
如果出现别名一样Flask运行启动就会报错, 提示报错 别名已经存在... 
4.8 参数传递
@app.route和app.add_url_rule参数
defaults = None, 默认值,
当URL中无参数,函数需要参数时,使用defaults = {'k': 'v'}, 将v赋值给k(变量).
函数中接收defaults中的k最为参数.
from flask import Flask

app = Flask(__name__)


# 设置参数, 函数需要接受参数
@app.route('/', defaults={'name': 'kid'})
def index(name):
    # kid
    print(name)  
    return 'hello'


if __name__ == '__main__':
    app.run()

4.9 严格匹配路径
@app.route和app.add_url_rule参数
strict_slashes = None,  默认值,
对URL最后的 / 符号是否严格要求, 默认严格,False,就是不严格
from flask import Flask

app = Flask(__name__)


# 关闭严格模式
@app.route('/index/', strict_slashes=False)
def index():
    return 'hello'


if __name__ == '__main__':
    app.run()

浏览器中测试:
http://127.0.0.1:5000/index
http://127.0.0.1:5000/index/
两个地址都可以访问, 否则第二个地址访问会报错.
4.10 重定向
@app.route和app.add_url_rule参数
redirect_to = None, 默认值,
重定向到指定地址.
* 测试的时候一定要关闭浏览器的缓存功能
from flask import Flask

app = Flask(__name__)

# 直接重定向到指定地址
@app.route('/', redirect_to='https:www.baidu.com')
def index():
    pass


if __name__ == '__main__':
    app.run()

5. Flask三板斧

return '字符串'  字符串格式html
return render_template('html文件')
return redirect('地址')
5.1 返回字符串信息
# 导入flask模块
from flask import Flask

# 生成app对象
app = Flask(__name__)


@app.route('/')
def index():
    # 返回字符串信息
    return 'index'

在浏览器中测试: http://127.0.0.1:5000
页面展示: index
5.2 返回字符串格式html
# 导入flask模块
from flask import Flask

# 生成app对象
app = Flask(__name__)


@app.route('/')
def index():
    # 返回html字符串信息
    html_str = '<p style="color:red">你好</p>'
    return html_str


if __name__ == '__main__':
    app.run()
在浏览器中测试: http://127.0.0.1:5000
页面展示: 你好(红色的, css渲染)
5.3 返回html模板
在生成app对象的时候有一个参数template_folder, 模板文件夹设置,
这个参数默为: template_folder: t.Optional[str] = "templates",
项目下的templates目录作为html的存放目录, 
可以通过render_template函数将templates下的html文件返回给浏览器.
# render_template源码
def render_template(
    template_name_or_list: t.Union[str, Template, t.List[t.Union[str, Template]]],
    # 之接收而外的关键字参数, Django可以使用, 可以只写locals(), Flask
    **context: t.Any
    ...
在返回页面的时候, 可以将后端的数据关键字参数形式, 返回给页html页面. (详情见第6.3小节)
* 1. 在项目下新建templates目录
* 2. 在templates目录下新建index.html模板
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>index页面</title>
</head>
<body>
<h1>index页面</h1>
</body>
</html>
* 3. 导入render_template函数, 通过render_template函数函数将页面返回.
# 导入flask模块
from flask import Flask, render_template

# 生成app对象
app = Flask(__name__)


# 设置路由
@app.route('/index')
def index():
    return render_template('index.html')


if __name__ == '__main__':
    app.run()

* 4. 在浏览器中测试: http://127.0.0.1:5000/index
	 页面展示: index.htm页面信息
5.4 重定向
可以通过redirect函数重定向, 可以重定向到自己的网页, 也可以重定向到网络上的地址.
定向到网络上的地址  redirect('https://www.baidu.com')  需要带协议部分(http, https)
重定向到自己的网页  redirect('/home')
导入redirect: from flask import redirect
# 导入flask模块
from flask import Flask, redirect

# 生成app对象
app = Flask(__name__)


# 设置路由, 使用装换器获取路径信息
@app.route('/index/<num_str>')
def index(num_str):
    # 输入 127.0.0.1:5000/index/1 跳转到百度页面
    if num_str == '1':
        return redirect('https://www.baidu.com')
     # 输入 127.0.0.1:5000/index/2 跳转home页面
    elif num_str == '2':
        return redirect('/home')


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


if __name__ == '__main__':
    app.run()

在浏览器中测试: 
输入 127.0.0.1:5000/index/1 跳转到百度页面
输入 127.0.0.1:5000/index/2 跳转home页面

6. 前后端交互案例

6.1 准备环境
* 1. 新建Python项目前后端交互案例
* 2. 在项目下新建templates目录, 用于存放html模板
6.1 用户登入
Flask中session的使用, session是一个全局的函数, (操作和字典相似).
存储用户信息:
session[key] = value
获取用户信息
session.get(key)
步骤:
1. 从request.method获取请求方式, 得到请求方式字符串, 大写!
2. 判断请求方法是get还是post.
3. 如果是get, 返回登入页面, 在templates目录下创建login.html登入页面.
4. 如果是post, 从表单中获取数据, 做登入校验, 校验成功, 保存登入信息到session, 返回index主页.
5. 校验失败返回登入页面, 

session保存用户信息需要设置密码否则会报错.
RuntimeError: The session is unavailable because no secret key was set.  Set the secret_key on the application to something unique and secret.
* 1. 在项目下创建main.py
from flask import Flask, request, render_template, redirect, session

app = Flask(__name__)

# 设置密码, session保存信息需要使用
app.secret_key = 'qwer'


# 登入页面
@app.route('/login', methods=['get', 'post'])
def login():
    # 判断请求方式为get返回登入页面
    print(request.method)
    if request.method == 'GET':
        return render_template('login.html')

    if request.method == 'POST':
        # 获取表单数据
        user = request.form.get('user')
        pwd = request.form.get('pwd')
        if user == 'kid' and pwd == '123':
            # 登入成功, 更新状态, 保存到session中, 需要设置密钥否则会报错
            session['user_info'] = user
            # 登入成功跳转到index主页
            return redirect('/index')

        else:
            return render_template('login.html', error='账户或密码错误!')


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


if __name__ == '__main__':
    app.run()
* 2. 在templates目录下创建login.html模板页面
<!-- login.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登入页面</title>
</head>
<body>
<div>
    <h1>用户登入</h1>
    <!-- 设置表单提交方式 -->
    <form method="post">
        <p>账户: <input type="text" name="user"></p>
        <p>密码: <input type="password" name="pwd"> <span style="color:red;">{{error}}</span></p>
        <p>
            <button>登入</button>
        </p>
    </form>

</div>
</body>
</html>
6.3 index主页
1. 判断用户是否登入, 如果没有登入, 重定向到login登入页面.要求用户登入.
2. 有用户登入, 返回index主页面, 并将数据数据提供给index主页面渲染.
   使用render_template不进可以返回html页面, 还可以将后端数据提交给前端.
   前端使用模板语法获取变量使用, 与django不同的是, Flask中支持python的方法使用, 可以使用().
* 1. 完善index应用
# mian.py 的index应用
# 数据
all_user = {
    1: {'name': 'kid', 'age': 18, 'gender': '男', 'text': '德玛西亚'},
    2: {'name': 'qq', 'age': 19, 'gender': '男', 'text': '诺克萨斯'},
    3: {'name': 'aa', 'age': 20, 'gender': '女', 'text': '艾欧尼亚'}
}


# index主页
@app.route('/index')
def index():
    # 判断用户状态(是否登入校验)
    user = session.get('user_info', None)

    # 没有用户登入, 跳转到登入页面
    if not user:
        return redirect('/login')


    # 有用户登入, 展示详情页面, 将数据传递给模板渲染
    return render_template('index.html', user_dict=all_user)
* 2. 在templates目录下创建index.html模板页面
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>index主页</title>
</head>
<body>
<div>
    <!--设置表格边框为1px-->
    <table border="1">
        <thead>
        <tr>
            <th>序号</th>
            <th>姓名</th>
            <th>年龄</th>
            <th>性别</th>
            <th>语录</th>
            <th>详情</th>
        </tr>
        </thead>
        <tbody>
        <!--使用模板语法, 可以使用python的语法, 字典支持.键取值-->
        {% for k,v in user_dict.items() %}

        <tr>
            <th>{{k}}</th>
            <th>{{v.name}}</th>
            <th>{{v['age']}}</th>
            <th>{{v.get('gender')}}</th>
            <th>{{v.text}}</th>
            <!--点击向/detail/序号 发给请求-->
            <th><a href="/detail/{{k}}">查看详情</a></th>
        </tr>
        {% endfor %}
        </tbody>
    </table>
</div>
</body>
</html>

2022-09-17_00897

6.4 详情页面
* 1. 主页面中点击查看详情, 跳转至个detail人详情页面, 携带用户id号
* 2. 判断用户状态, 如果没有登入, 重定向到登入页面
* 3. 如果有用户登入, 返回个人详情页面
* 1. 在Flask框架中添加detail详情页面
# 详情页面, 获取用户pid
@app.route('/detail/<int:pid>')
def detail(pid):
    # 用户状态校验
    user = session.get('user_info', None)

    # 没有用户登入重定向到登入页面
    if not user:
        return redirect('/login')

    # 有用户登入, 通过pid获取个人详情页面
    user_detail = all_user.get(pid, None)
    # 判断该用户数据是否存在
    if not user_detail:
        return '用户信息不存在'

    # 成功获取到用户数据, 返回detail用户详情页面, 并将数据返回给模板渲染
    return render_template('detail.html', user_detail=user_detail)
* 2. 在templates目录下创建detail.html模板页面
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>详情页面</title>
</head>
<body>
<!--接收到一个变量是字典-->
<p>姓名: {{user_detail.name}}</p>
<p>年龄: {{user_detail.age}}</p>
<p>性别: {{user_detail.gender}}</p>
<p>语录: {{user_detail.text}}</p>
</body>
</html>
6.5 完整flask代码
from flask import Flask, request, render_template, redirect, session

app = Flask(__name__)

# 设置密码, session保存信息需要使用
app.secret_key = 'qwer'

# 数据
all_user = {
    1: {'name': 'kid', 'age': 18, 'gender': '男', 'text': '德玛西亚'},
    2: {'name': 'qq', 'age': 19, 'gender': '男', 'text': '诺克萨斯'},
    3: {'name': 'aa', 'age': 20, 'gender': '女', 'text': '艾欧尼亚'}
}


# 登入页面
@app.route('/login', methods=['get', 'post'])
def login():
    # 判断请求方式为get返回登入页面
    print(request.method)
    if request.method == 'GET':
        return render_template('login.html')

    if request.method == 'POST':
        # 获取表单数据
        user = request.form.get('user')
        pwd = request.form.get('pwd')
        if user == 'kid' and pwd == '123':
            # 登入成功, 更新状态, 保存到session中, 需要设置密钥否则会报错
            session['user_info'] = user
            print('更新用户状态')
            # 登入成功跳转到index主页面
            return redirect('/index')

        else:
            return render_template('login.html', error='账户或密码错误!')


# index主页
@app.route('/index')
def index():
    # 判断用户状态(是否登入校验)
    user = session.get('user_info', None)

    # 没有用户登入, 跳转到登入页面
    if not user:
        return redirect('/login')

    # 有用户登入, 展示详情页面, 将数据传递给模板渲染
    return render_template('index.html', user_dict=all_user)


# 详情页面, 获取用户pid
@app.route('/detail/<int:pid>')
def detail(pid):
    # 用户状态校验
    user = session.get('user_info', None)

    # 没有用户登入重定向到登入页面
    if not user:
        return redirect('/login')

    # 有用户登入, 通过pid获取个人详情页面
    user_detail = all_user.get(pid, None)
    # 判断该用户数据是否存在
    if not user_detail:
        return '用户信息不存在'

    # 成功获取到用户数据, 返回detail用户详情页面, 并将数据返回给模板渲染
    return render_template('detail.html', user_detail=user_detail)


if __name__ == '__main__':
    app.run()

7.多个装饰的使用

自己给应用添加装饰器, 那么需要注意装饰器执行的顺序.
下例程序为index主页添加登入.
from flask import Flask, request, render_template, redirect, session, url_for

app = Flask(__name__)

# 设置密码, session保存信息需要使用
app.secret_key = 'qwer'

# 数据
all_user = {
    1: {'name': 'kid', 'age': 18, 'gender': '男', 'text': '德玛西亚'},
}


# 登入装饰器
def verification(index):
    # 获取用户登入状态
    def func(*args, **kwargs):
        user = session.get('user_info', None)

        # 没有用户登入, 返回用户登入页面
        if not user:
            return redirect('/login')
        else:
            res = index(*args, **kwargs)
            return res

    return func


# 登入页面
@app.route('/login', methods=['get', 'post'])
def login():
    # 判断请求方式为get返回登入页面
    print(request.method)
    if request.method == 'GET':
        return render_template('login.html')

    if request.method == 'POST':
        # 获取表单数据
        user = request.form.get('user')
        pwd = request.form.get('pwd')
        if user == 'kid' and pwd == '123':
            # 登入成功, 更新状态, 保存到session中, 需要设置密钥否则会报错
            session['user_info'] = user
            print('更新用户状态')
            # 登入成功跳转到index主页面
            return redirect('/index')

        else:
            return render_template('login.html', error='账户或密码错误!')


# index主页
@app.route('/index')
@verification
def index():
    # 反向解析得到index
    print(url_for('func'))
    # 展示详情页面, 将数据传递给模板渲染
    return render_template('index.html', user_dict=all_user)


if __name__ == '__main__':
    app.run()

# 在源码(4.5小节提过)中添加print(view_func.__name__)查看被装饰当前的函数名
def _endpoint_from_view_func(view_func: t.Callable) -> str:

    assert view_func is not None, "expected view func if endpoint is not provided."
    # 查看被装饰当前的函数名
    print(view_func.__name__)
    return view_func.__name__
@路由装饰器
@登入装饰器
def 运行的应用index
	...
装饰器至下往上加载
1. 运行运行的应用名, 被作为参数传递给登入装饰器, 返回func函数名, 装饰的是index.
2. func函数名被路由装饰器最为参数进行传递, 装饰的是func函数
运行至上往下运行.
1. 请求来了先执行func函数的登入校验
2. 校验通过执行index函数

路由装饰装饰了下一个装饰器放回的函数, 这个是否默认的别名就是路由装饰器装饰的函数名.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值