Flask 入门系列教程(二)

简介: 本节,我们先从一道经典的面试题目开始:当你在浏览器中输入一个 URL 并按下 Enter 后,都发生了什么?其实这个问题还是蛮大的,网上也有很多解读,今天我们就从 HTTP 来入手,看看这背后究竟发生了什么。

请求响应循环


其实大家对于 HTTP 协议应该是再熟悉不过了,它是超文本传输协议,定义了服务器和客户端之间信息交流的格式和传递方式。


那么对于上面的问题,我们其实也可以大致的说出一个简易流程:

  1. 按下 Enter 之后,浏览器会向 URL 地址发送一个 HTTP 请求
  2. 在浏览器的背后,有一个后台程序,用于接收相关请求,并返回处理的结果
  3. 浏览器接收结果,并渲染给终端用户查看

事实上,每一个 Web 应用都包含这种处理模式,即“请求-响应循环(Request-Response Cycle)”:客户端(浏览器等)发出请求,服务端处理请求并响应。

我们再把上面的流程扩展到 Flask 服务器上,就是由浏览器生成的 HTTP 请求发送至 Web 服务器。Web 服务器接收到请求后,经由 WSGI 协议把数据转换成 Flask 程序能够识别的数据后,传递给 Flask 程序。然后 Flask 程序再根据视图函数等处理相关请求,最后再返回响应给 Web 服务器。最终交由浏览器来渲染结果,比如加载 CSS,执行 JavaScript 代码等等操作。

我们可以看下下面的图片

image.png

这里有两个概念我们要先明确下

Web 服务器:Web 服务器是一类特殊的服务器,其作用是主要是接收 HTTP 请求并返回响应。我们常用的 Web 服务器有 Nginx,tomcat 等,相信大家都非常熟悉或多少听说些。

WSGI:它确切来说应该是一种协议,或者接口规范。定义了 web 服务器和 web 应用(Flask 等)之间的接口规范。只有 Web 服务器和 Web 应用都遵守了 WSGI 协议,那么他们才能正常通信。

比如说在上一节我们使用 app.run() 启动测试服务器时,就是使用了 Flask 自带的 Web 服务器,当然这种服务器只能用来开发测试时使用,在生成环境,我们需要部署到 Nginx 等 Web 服务器上。

在了解了 Web 程序的整体运行流程之后,我们再来深入的探究下 Flask 的工作原理。


Flask上下文


HTTP 请求

当 Flask 接收到客户端的请求后(后面的章节中我们都会直接省略 Web 服务器和 WSGI 的转换步骤),就会产生一些视图函数可以访问的对象,通过这些对象来处理请求,这就是请求对象--request。

request 对象包含了 HTTP 请求中的 URL 信息和相关的报文信息

URL 信息

例如请求 URL 为:http://www.luobodazahui.top/hello?name=zhouluobo

属性
path '/hello'
full_path '/hello?name=zhouluobo'
host 'www.luobodazahui.top'
host_url 'http://www.luobodazahui.top'
base_url 'http://www.luobodazahui.top/hello'
url 'http://www.luobodazahui.top/hello?name=zhouluobo'


报文信息

属性或方法 说明
args 查询字符串信息
cookies cookies 信息字典
data 字符串形式的请求数据
form 表单数据
get_json() 获取 json 类型的请求数据
method 请求的 HTTP 方法


下面我们通过一个简单的例子来具体查看下

@app.route('/test/')
def test_view():
    query = 'Flask'
    if request.args:
        query = request.args.get('name', 'Flask')
    host = request.host
    path = request.full_path
    cookie = request.cookies
    method = request.method
    return """
    <h1>
    <p>query string: %s</p>
    <p>host: %s</p>
    <p>path: %s</p>
    <p>cookies: %s</p>
    <p>method: %s</p>
    </h1>
    """ % (query, host, path, cookie, method)

当我们在浏览器输入:http://127.0.0.1:5000/test/,可以得到

image.png

当我们在浏览器输入:http://127.0.0.1:5000/test/?name=luobo,可以得到

image.png

在这里,request 是一个全局的变量,我们可以在任何的视图函数中去使用它。当然,这仅仅局限在当前线程中,对于多线程服务器中,不同线程服务器的请求对象是不同的。


两种上下文

在 Flask 中,有两种上下文:程序上下文和请求上下文。主要包括下面四种

变量名 上下文类型 说明
request 请求上下文 请求对象,封装了 HTTP 请求中的内容
session 请求上下文
请求上下文用户会话,存储请求之间需要保留的值
g 程序上下文 处理请求时的临时存储对象,仅在当前请求有效
current_app 程序上下文 当前的程序实例

对于 request,我们已经了解了,下面再来看看 session。

session

session 最常用的就是确认用户状态了,比如检查用户是否登陆等。下面我们就简单实现一个基于浏览器的用户认证功能,来理解下 session 的强大功效。

普通的认证系统,用户在页面表单中输入用户名和密码后,后台程序进行确认,如果认证通过,则返回响应,并在浏览器的 Cookie 中设入标记,例如“loginID:User1”。但是因为浏览器 Cookie 是很容易被修改的,所以如果使用名称存储这些信息就会非常不安全,此时就需要 session 登场了。

在 Flask 中 session 通过密钥对数据进行签名从而加密数据,所以我们需要先设置一个密钥。

app.secret_key = 'Very Hard Secret'

当然,更加安全的做法是把该密钥写到部署服务器的环境变量中,对于这种写法,我们在后面部署程序时再详细讲解。

接下来我们做模拟用户认证的情况,写两个视图函数,分别模拟登陆和登出场景。

@app.route('/login/')
def login():
    session['loginID'] = 'admin'
    return redirect(url_for('welcome'))@app.route('/logout/')
def logout():
    if 'loginID' in session:
        session.pop('loginID')
    return redirect(url_for('welcome'))

再修改 welcome 视图函数,用于展示是否登陆

@app.route('/user/', defaults={'name': '陌生人'})
@app.route('/user/<name>')
def welcome(name):
    res = '<h1>Hello, %s!</h1>' % name
    if 'loginID' in session:
        res += 'Authenticated'
    else:
        res += 'UnAuthenticated'
    return res

这里我们使用了 redirect 函数,是一个重定向方法。只需要传入目标的 URL 地址,就可以在视图函数处理结束后跳转至目标的页面。

当我在浏览器输入:http://127.0.0.1:5000/login/的时候,就会在浏览器中插入一个加密的 cookie 并跳转至 welcome 页面

image.png

可以看到,插入的 cookie 是加密的,这样就加大了攻击者的攻击难度,从而在一定程度上保护了我们系统的安全。

g 和 current_app

其实你应该会有个疑惑,我们已经有了一个 app 程序实例了,为什么还需要定义一个 current_app 变量呢?在不同的视图函数中,request 对象都表示和视图函数对应的请求,也就是当前请求(current request)。而程序会有多个程序实例的情况,为了能获取对应的程序实例,而不是固定的某一个程序实例,我们就需要使用 current_app 变量。当然对于多个程序实例的情况,我们留待后面的章节详细介绍。

g 存储在程序上下文中,而程序上下文会随着每一个请求的进入而激活,随着每一个请求的处理完毕而销毁,所以每次请求都会重设这个值。比如说如果对于某个请求,我们几个视图函数都需要用到一个前端传递过来的变量,那么就可以把它保存到 g 变量当中

g.name = request.args.get('name')

这样,其他的视图函数就可以在同一个请求中直接使用 g.name 来访问,而不用每次都调用 request 了。

对于 current_app 和 g 的更多使用方式,在后面的学习中我们会慢慢接触的更多。


请求钩子


在处理请求之前或之后执行的代码,就称为请求钩子。比如在请求之前,我们需要初始化数据库,创建 admin 用户等等,就需要在请求之前调用请求钩子来做这件事情。

在 Flask 中提供了四种请求钩子,以装饰器的形式注册到函数,使得我们可以方便的应用该功能

钩子名称 作用
before_first_request 在处理第一个请求之前运行
before_request 在每次请求之前运行
after_request 如果没有未处理的异常抛出,则在每次请求之后运行
teardown_request 即使有未处理的异常抛出,也在每次请求之后运行

在请求钩子函数和视图函数之间共享数据一般使用上下文全局变量 g,比如上面的例子我们就可以写成

from flask import g
@app.before_request def get_name():    
    g.name = request.args.get('name')


重定向回上一个页面


功能实现

重定向回上一个页面,这应该是一个非常常见的应用场景,那么该如何通过 Flask 来实现呢。

首先我们修改下 login 视图函数,在请求参数中查找 next 参数,如果存在则重定向到 next 参数对应的地址,否则重定向到 hello 视图函数对应的地址

@app.route('/login/')
def login():
    session['loginID'] = 'admin'
    return redirect(request.args.get('next') or url_for('hello'))

这里所谓的 next 参数,其实只是一种约定俗成的命名方式

再修改 needpage1 视图函数,如果用户未登陆则展示登陆链接,并保存 next 参数

@app.route('/needlogin1/')
def needLogin1():
    if 'loginID' in session:
        return '<h1>Hello, needLogin1!</h1>'
    else:
        return """
            <h1>Login</h1><a href="%s">Go To Login</a>
                """ % url_for('login', next=request.url)

这样,当用户处于未登录状态时,就可以点击 Go To Login 链接进行登陆,登陆成功之后会自动跳转回当前页面了。


安全处理

现在我们虽然完成了功能,但是却还遗留了相关的安全问题。因为我们的 next 参数是以查询字符串的方式写在 URL 里的,所以如果有人拦截了我们的请求,就可以随便修改 next 的指向,此时我们就需要验证 next 变量是否属于我们的应用,否则很容易被指向外部链接,从而造成安全隐患。

我们先创建一个检查 URL 正确性的函数

from urllib.parse import urlparse
def check_next(target):
    ref_url = urlparse(request.host_url)
    test_url = urlparse(target)
    return ref_url.netloc == test_url.netloc

该函数接收目标地址为参数,并比较本应用的 host_url 和目标地址的 host_url 是否相同

改写 login 视图函数

@app.route('/login/')
def login():
    session['loginID'] = 'admin'
    target = request.args.get('next')
    if check_next(target):
        return redirect(target)
    return redirect(url_for('hello'))

只有当 check_next 函数返回 True 时才重定向到 next 变量对应的地址,否则重定向到 hello 对应的地址。

本节所以代码可以查看本教程的 GitHub 代码仓库的 2a tag 版本代码


总结


本章着重介绍了 Flask 中的 HTTP 相关知识,包括 Web 服务器的运行方式,Flask 上下文的使用,请求钩子,重定向等知识点。

image.png

相关文章
|
4月前
|
安全 网络安全 数据安全/隐私保护
Flask 入门系列教程(六)
Flask 入门系列教程(六)
|
4月前
|
存储 安全 前端开发
Flask 入门系列教程(二)
Flask 入门系列教程(二)
|
2月前
|
人工智能 前端开发 算法
Python 潮流周刊#18:Flask、Streamlit、Polars 的学习教程
Python 潮流周刊#18:Flask、Streamlit、Polars 的学习教程
33 4
|
4月前
|
SQL 关系型数据库 Shell
Flask 入门系列教程(五)
Flask 入门系列教程(五)
|
4月前
|
安全 数据安全/隐私保护 Python
Flask 入门系列教程(四)
Flask 入门系列教程(四)
|
4月前
|
存储 前端开发 JavaScript
Flask 入门系列教程(三)
Flask 入门系列教程(三)
|
4月前
|
Web App开发 前端开发 JavaScript
Flask 入门系列教程(一)
Flask 入门系列教程(一)
|
5月前
|
API 数据库 网络架构
Python Flask框架学习教程
概述: 本教程将介绍Python Flask框架的基础知识和使用方法。Flask是一个简单而灵活的Web应用框架,它具有轻量级、易用性和高度可扩展等特点。通过学习本教程,您将能够了解Flask的基本概念、安装方法、语法结构、模板使用以及实际案例,并通过练习题加深对框架的理解和应用。
|
Python Windows Linux
Flask 教程 第一章:Hello, World!
本文转载自:https://www.jianshu.com/p/fcbd137f308b 一趟愉快的学习之旅即将开始,跟随它你将学会用Python和Flask来创建Web应用。上面的视频包含了整个教程的内容预览(译者注:视频见原文)。
1998 0
|
17天前
|
安全 数据库 C++
Python Web框架比较:Django vs Flask vs Pyramid
【4月更文挑战第9天】本文对比了Python三大Web框架Django、Flask和Pyramid。Django功能全面,适合快速开发,但学习曲线较陡;Flask轻量灵活,易于入门,但默认配置简单,需自行添加功能;Pyramid兼顾灵活性和可扩展性,适合不同规模项目,但社区及资源相对较少。选择框架应考虑项目需求和开发者偏好。