首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Flask 入门系列教程(二)

Flask 入门系列教程(二)

作者头像
周萝卜
发布2020-09-18 15:35:09
1.1K0
发布2020-09-18 15:35:09
举报
文章被收录于专栏:萝卜大杂烩萝卜大杂烩

本节,我们先从一道经典的面试题目开始:当你在浏览器中输入一个 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 代码等等操作。

我们可以看下下面的图片

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

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/,可以得到

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

在这里,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 页面

可以看到,插入的 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 上下文的使用,请求钩子,重定向等知识点。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-09-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 萝卜大杂烩 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 请求响应循环
  • Flask 上下文
  • HTTP 请求
  • 两种上下文
    • session
      • g 和 current_app
        • 请求钩子
          • 重定向回上一个页面
          • 功能实现
          • 安全处理
            • 总结
            相关产品与服务
            测试服务
            测试服务 WeTest 包括标准兼容测试、专家兼容测试、手游安全测试、远程调试等多款产品,服务于海量腾讯精品游戏,涵盖兼容测试、压力测试、性能测试、安全测试、远程调试等多个方向,立体化安全防护体系,保卫您的信息安全。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档