《Flask Web开发》笔记——第二章 程序的基本结构
1. 一个简单的Flask程序
一个最简单的flask应用看起来如下,这个程序命名为hello.py:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'hello world!'
if __name__ == '__main__':
app.run(debug=True)
对于这个应用解释如下:
- 通过
from flask import Flask
导入Flask类 - .
app = Flask(__name__)
是创建Flask的实例,命名为app。对于Flask类中的第一个参数,先看一下官方的解释如下,其主要的作用是告诉Flask在哪里去寻找所需要的资源。对于单一的应用来说,设置为__name__
是一个最常见的选择,__name__
这个系统变量显示了当前模块执行过程中的名称,应用会根据这个参数去寻找需要的资源,比如模板存放的文件夹位置。也可以通过硬编码将包的名字赋予这个参数。
| .. admonition:: About the First Parameter
|
| The idea of the first parameter is to give Flask an idea of what
| belongs to your application. This name is used to find resources
| on the filesystem, can be used by extensions to improve debugging
| information and a lot more.
|
| So it's important what you provide there. If you are using a single
| module, "__name__" is always the correct value. If you however are
| using a package, it's usually recommended to hardcode the name of
| your package there.
- 程序实例提供的
@app.route
修饰器,把修饰的函数注册为路由,这是是Flask程序中定义路由的最简便方式。其本质还是调用Flask对象的add_url_rule
函数。 - 视图函数(view function):如下的
hello_world()
函数就是视图函数,它的主要作用是处理客户端的请求,并返回响应。
def hello_world():
return 'hello world'
- 如下的代码的主要作用是,如果这个代码直接运行时,则运行该app。如果作为模块导入时,则
__name__ != '__main__'
,app.run()
指令不会执行。
if __name__ == '__main__':
app.run(debug=True)
2. 当我们上网我们在干嘛
想一想我们希望在网上浏览新闻,或去购物网站买东西,又或是去某个网站填一份调查问卷。对于我们用户来讲,我们只是在浏览器中输入一串字符,然后就可以去浏览我们需要的页面,也可以在页面中输入一些信息,进行一些操作。这一切看起来是如此的简单,但我们想想,我们在进行这些操作的时候,浏览器在干什么?浏览器背后的网络在干什么?
WSGI Server 和 App交互图
这背后的逻辑,我们可以通过上面的图进行解释:
- 首先,我们在浏览器中会输入网址,有时候我们是通过点击链接的方式打开某个页面,但实际上同输入网址是同一个过程。这里浏览器我们称为客户端。输入网址表示我们希望浏览这个网址所指向的这个页面。
- 输入网址并提交后,浏览器会把我们提交的信息包装成HTTP请求报文,即Request。当浏览器向Web服务器发出请求时,它向服务器传递了一个数据块,也就是请求信息,HTTP请求信息由3部分组成:
| 请求行(request line)
| 请求头(request header)
| 请求正文
请求信息的组成如下表所示,注意请求头部和请求数据之间有一个空格,这表示请求头部的结束。这里我们不去深入解释具体的参数,只是说一下每一部分都有什么作用。
请求行:用来说明请求类型,要访问的资源以及所使用的HTTP版本
请求头部:用来说明服务器要使用的附加信息
请求数据:也叫主体,可以添加任意的其他数据
请求信息格式
- 由于请求信息中包括了URL的信息,因此服务器会收到浏览器发出的请求信息。然后服务器会将请求信息解包,并调用WSGI程序对信息进行逻辑处理。这个程序就是我们需要开发的Flask web程序。
-
WSGI程序会根据request返回给客户端两个内容:一是HTTP状态码和消息报头(报文头);二是HTTP正文。这称为HTTP响应(Response)。
HTTP响应
这里对这些响应的内容稍微作一个解释如下:
状态行:由HTTP协议版本号, 状态码, 状态消息 三部分组成
消息报头:用来说明客户端要使用的一些附加信息
响应正文:服务器返回给客户端的文本信息
注意,在消息报头和响应正文之间,必须用一个空格隔开,同HTTP请求信息类似,这表明消息报头的结束。 - 客户端接收到服务端的响应文件。注意上图中响应的正文部分,其实浏览器接收到的就是这样一些代码。
- 浏览器对代码进行解析,输出我们最终看到的页面。
这以上就是我们在使用浏览器上网的过程中,在我们浏览器背后所隐藏着的动作,明白这里面的逻辑关系,有助于我们对开发web应用的理解。
3. 再看Flask程序
对于上面我们看到的"一个简单的Flask程序",当我们在浏览器上输入网址后,服务器接收到这个信息,并交给Flask程序处理,Flask根据收到的URL分配路由,如这里如果是主机后面之间是'/'(如果没有直接写出'/',会当作默认加上该符号),则将该路由下的视图函数反馈的内容作为响应主体反馈。因此,在浏览器上会显示出返回的字符串'Hello World'。
因此,我们开发的Flask应用就有两个主要内容:一是定义路由;二是定义该路由下的反馈内容。如我们前面的这个最简单Flask程序中,就包括了如何定义路由,和如何反馈内容的视图函数。
当然,围绕着这两个内容,我们还需要做更多的工作:比如如何存放数据,如何设计反馈的信息,处理不同路由之间的逻辑,设置不同权限,等等。这些都将在后面的内容中进行学习。
4. 请求-响应循环
4.1 程序和请求上下文
刚刚看到这里的时候,我是一头雾水,对于这里的上下文,完全不能够理解。在网上查了半天,也还是云里雾里,好像知道了什么,又好像什么都不知道。后来跳过这里,接着往后面学习,慢慢对上下文有了自己的一些理解。特别是这里的一篇文章,大家可以参考看一下。(https://www.jianshu.com/p/2a2407f66438)
首先,我们需要对上下文这个名词进行理解。上下文的英文是Context,与其翻译为上下文,不如翻译为“语境”更贴切。对于“语境”怎么理解呢?我们设想一下如下的情形。
张三问你:“怎么样?”
你该如何回答?你可能会摸不着头脑:“什么怎么样?”
但假如你和张三刚刚完成一门考试,你可能会回答:“哦,还不错哦!”
或者你们在吃着火锅,你正尝了一口食物,你可能会回答:“太辣了!”
看到没有,同样的一句话,不同的时候,回答是不一样的。这跟说话当时的情形是紧密相关的,就是说话时候的“语境”。
那么我们这里说到Flask的上下文或者称为“语境”到底指什么具体的“语境”呢?还记得前面第二部分的WSGI Server 和 App交互图
中所标出的封装environ
吗?在客户端发起请求,服务器端接收到请求后,Flask将根据请求的信息创建出一个“语境”,这样,在这个语境中,所有的信息都来自与这次请求。在《Flask Web开发》这本书中,其原文:请求对象就是一个很好的例子,它封装了客户端发送的HTTP请求。
Flask有两种Context上下文:程序上下文和请求上下文。参考如下表。
4.2 请求调度
什么是请求调度呢?就是当客户端发来请求的时候,Flask需要给出一个响应。但是这个响应对应的是处理该请求的视图函数,那么问题来了:如何给请求找到对应的视图函数呢?请求调度就是给请求找到对应的视图函数。
Flask是在程序的URL映射中查找请求的URL的。我们可以通过flask的url_map
函数去看看生成的映射是什么样子。
(venv) $ python
>>>from hello import app
>>>app.url_map
Map([<Rule '/' (HEAD, OPTIONS, GET) -> index>,
<Rule '/static/<filename>' (HEAD, OPTIONS, GET) -> static>,
<Rule '/user/<name>' (HEAD, OPTIONS, GET) -> user>])
从这个映射中可以看出,对应不同的路由,在映射中保留了其对应的视图函数。比如这里的index
,static
和user
分别是不同的视图函数。而前面的Rule对应着不同的路由。
4.3 请求钩子
请求钩子的作用是:有时候在执行请求之前或执行请求之后,需要执行一个操作。比如在查看某一个页面之前,需要先确认用户是否登录,如果没有请求钩子,要实现这个目的就比较麻烦。因此请求钩子是Flask为了这些通用功能设计的一种快捷机制。
请求钩子通过修饰器实现。Flask支持以下4种钩子:
- before_first_request:注册一个函数,在处理第一个请求之前运行。
- before_request:注册一个函数,在每次请求之前运行。
- after_request:注册一个函数,如果没有未处理的异常抛出,在每次请求之后运行。
- teardown_request:注册一个函数,即使有未处理的异常抛出,也在每次请求之后运行
在请求钩子函数和视图函数之间共享数据一般使用上下文全局变量 g。例如, before_request 处理程序可以从数据库中加载已登录用户,并将其保存到 g.user 中。随后调用视图函数时,视图函数再使用 g.user 获取用户。
4.4 响应
如前面讲过,Flask调用视图函数后,将其返回值作为响应的内容。我们在浏览器看到的页面就是响应一部分内容。
但是HTTP响应还有状态码,状态码表明该相应处理的状态。比如200表示成功处理。400表示处理无效。