赵走x博客
网站访问量:151932
首页
书籍
软件
工具
古诗词
搜索
登录
Flask 实战:41、电子邮件进阶实践
Flask 实战:40、使用事务邮件服务SendGird
Flask 实战:39、使用Flask-Mail发送电子邮件
Flask 实战:38、数据库进阶实践
Flask 实战:37、更新数据库表
Flask 实战:36、定义关系
Flask 实战:35、在视图函数里操作数据库
Flask 实战:34、数据库操作:CRUD
Flask 实战:33、使用Flask-SQLAlchemy管理数据库
Flask 实战:32、ORM魔法
Flask 实战:31、数据库的分类
Flask 实战:30、单个页面多个表单
Flask 实战:29、单个表单多个提交按钮
Flask 实战:28、使用Flask-CKEditor集成富文本编辑器
Flask 实战:27、多文件上传
Flask 实战:26、文件上传
Flask 实战:25、自定义验证器
Flask 实战:24、使用宏渲染表单
Flask 实战:23、设置错误消息语言
Flask 实战:22、处理表单数据
Flask 实战:21、使用Flask-WTF处理表单
Flask 实战:20、HTML表单
Flask 实战:19、模板进阶实践
Flask 实战:18、模板结构组织
Flask 实战:17、模板辅助工具
Flask 实战:16、模板基本用法
Flask 实战:15、HTTP进阶实践
Flask 实战:14、Flask上下文
Flask 实战:13、HTTP响应
Flask 实战:12、HTTP请求
Flask 实战:11、请求响应循环
Flask 实战:10、Flask与MVC架构
Flask 实战:9、模板与静态文件
Flask 实战:8、Flask命令
Flask 实战:7、URL与端点
Flask 实战:6、项目配置
Flask 实战:5、Flask扩展
Flask 实战:4、Python Shell
Flask 实战:3、启动开发服务器
Flask 实战:2、Hello,Flask!
Flask 实战:1、初识Flask
Flask 实战:12、HTTP请求
资源编号:75840
Python Web
Flask Web开发实战:入门、进阶与原理解析
热度:103
URL是一个请求的起源。不论服务器是运行在美国洛杉矶,还是运行在我们自己的电脑上,当我们输入指向服务器所在地址的URL,都会向服务器发送一个HTTP请求。
URL是一个请求的起源。不论服务器是运行在美国洛杉矶,还是运行在我们自己的电脑上,当我们输入指向服务器所在地址的URL,都会向服务器发送一个HTTP请求。 一个标准的URL由很多部分组成,以下面这个URL为例: ``` http://helloflask.com/hello?name=Grey ``` 这个URL的各个组成部分如表2-1所示。  表2-1 URL组成部分 这个URL后面的?name=Grey部分是查询字符串(query string)。URL中的查询字符串用来向指定的资源传递参数。查询字符串从问号?开始,以键值对的形式写出,多个键值对之间使用&分隔。 # 1、请求报文 当我们在浏览器中访问这个URL时,随之产生的是一个发向[http://helloflask.com](http://helloflask.com) 所在服务器的请求。请求的实质是发送到服务器上的一些数据,这种浏览器与服务器之间交互的数据被称为报文(message),请求时浏览器发送的数据被称为请求报文(request message),而服务器返回的数据被称为响应报文(response message)。 请求报文由请求的方法、URL、协议版本、首部字段(header)以及内容实体组成。前面的请求产生的请求报文示意如表2-2所示。  表2-2 请求报文示意表 如果你想看真实的HTTP报文,可以在浏览器中向任意一个有效的URL发起请求,然后在浏览器的开发者工具(F12)里的Network标签中看到URL对应资源加载的所有请求列表,单击任一个请求条目即可看到报文信息,图2-3是使用Chrome访问本地示例程序的示例。  图2-3 在Chrome浏览器中查看请求和响应报文 报文由报文首部和报文主体组成,两者由空行分隔,请求报文的主体一般为空。如果URL中包含查询字符串,或是提交了表单,那么报文主体将会是查询字符串和表单数据。 HTTP通过方法来区分不同的请求类型。比如,当你直接访问一个页面时,请求的方法是GET;当你在某个页面填写了表单并提交时,请求方法则通常为POST。表2-3是常见的几种HTTP方法类型。  表2-3 常见的HTTP方法 报文首部包含了请求的各种信息和设置,比如客户端的类型、是否设置缓存、语言偏好等。 > 附注: HTTP中可用的首部字段列表可以在[https://www.iana.org/assignments/message-headers/message-headers.xhtml](https://www.iana.org/assignments/message-headers/message-headers.xhtml) 看到。请求方法的详细列表和说明可以在RFC 7231([https://tools.ietf.org/html/rfc7231](https://tools.ietf.org/html/rfc7231) )中看到。 如果运行了示例程序,那么当你在浏览器中访问[http://127.0.0.1:5000/hello](http://127.0.0.1:5000/hello) 时,开发服务器会在命令行中输出一条记录日志,其中包含请求的主要信息: ``` 127.0.0.1 - - [02/Aug/2017 09:51:37] "GET /hello HTTP/1.1" 200 – ``` # 2、Request对象 现在该让Flask的请求对象request出场了,这个请求对象封装了从客户端发来的请求报文,我们能从它获取请求报文中的所有数据。 > 注意: 请求解析和响应封装实际上大部分是由Werkzeug完成的,Flask子类化Werkzeug的请求(Request)和响应(Response)对象并添加了和程序相关的特定功能。在这里为了方便理解,我们先略过不谈。在第16章,我们会详细了解Flask的工作原理。 和上一节一样,我们先从URL说起。假设请求的URL是[http://helloflask.com/hello?name=Grey](http://helloflask.com/hello?name=Grey) ,当Flask接收到请求后,请求对象会提供多个属性来获取URL的各个部分,常用的属性如表2-4所示。  表2-4 使用request的属性获取请求URL 除了URL,请求报文中的其他信息都可以通过request对象提供的属性和方法获取,其中常 用的部分如表2-5 所示。  表2-5 request对象常用的属性和方法 > 提示 Werkzeug的MutliDict类是字典的子类,它主要实现了同一个键对应多个值的情况。比如一个文件上传字段可能会接收多个文件。这时就可以通过getlist()方法来获取文件对象列表。而ImmutableMultiDict类继承了MutliDict类,但其值不可更改。更多内容可访问Werkzeug相关数据结构章节[http://werkzeug.pocoo.org/docs/latest/datastructures/](http://werkzeug.pocoo.org/docs/latest/datastructures/) 。 在我们的示例程序中实现了同样的功能。当你访问 [http://localhost:5000/hello?name=Grey](http://localhost:5000/hello?name=Grey) 时,页面加载后会显示“Hello,Grey!”。这说明处理这个URL的视图函数从查询字符串中获取了查询参数name的值,如代码清单2-1所示。 代码清单2-1 获取请求URL中的查询字符串 ``` from flask import Flask, request app = Flask(__name__) @app.route('/hello') def hello(): name = request.args.get('name', 'Flask') # 获取查询参数name的值 return '
Hello, %s!
' % name # 插入到返回值中 ``` > 注意 上面的示例代码包含安全漏洞,在现实中我们要避免直接将用户传入的数据直接作为响应返回,在本章的末尾我们将介绍这个安全漏洞的具体细节和防范措施。 需要注意的是,和普通的字典类型不同,当我们从request对象的类型为MutliDict或ImmutableMultiDict的属性(比如files、form、args)中直接使用键作为索引获取数据时(比如request.args['name']),如果没有对应的键,那么会返回HTTP 400错误响应(Bad Request,表示请求无效),而不是抛出KeyError异常,如图2-4所示。为了避免这个错误,我们应该使用get()方法获取数据,如果没有对应的值则返回None;get()方法的第二个参数可以设置默认值,比如requset.args.get('name','Human')。  图2-4 400错误响应 如果开启了调试模式,那么会抛出BadRequestKeyError异常并显示对应的错误堆栈信息,而不是常规的400响应。 # 3、在Flask中处理请求 URL是指向网络上资源的地址。在Flask中,我们需要让请求的URL匹配对应的视图函数,视图函数返回值就是URL对应的资源。 ### 1.路由匹配 为了便于将请求分发到对应的视图函数,程序实例中存储了一个路由表(app.url_map),其中定义了URL规则和视图函数的映射关系。当请求发来后,Flask会根据请求报文中的URL(path部分)来尝试与这个表中的所有URL规则进行匹配,调用匹配成功的视图函数。如果没有找到匹配的URL规则,说明程序中没有处理这个URL的视图函数,Flask会自动返回404错误响应(Not Found,表示资源未找到)。你可以尝试在浏览器中访问[http://localhost:5000/nothing](http://localhost:5000/nothing) ,因为我们的程序中没有视图函数负责处理这个URL,所以你会得到404响应,如图2-5所示。  图2-5 404错误响应 如果你经常上网,那么肯定会对这个错误代码相当熟悉,它表示请求的资源没有找到。和前面提及的400错误响应一样,这类错误代码被称为HTTP状态码,用来表示响应的状态,具体会在下面详细讨论。 当请求的URL与某个视图函数的URL规则匹配成功时,对应的视图函数就会被调用。使用flask routes命令可以查看程序中定义的所有路由,这个列表由app.url_map解析得到: ``` $ flask routes Endpoint Methods Rule -------- ------- ----------------------- hello GET /hello static GET /static/
``` 在输出的文本中,我们可以看到每个路由对应的端点(Endpoint)、HTTP方法(Methods)和URL规则(Rule),其中static端点是Flask添加的特殊路由,用来访问静态文件,具体我们会在第3章学习。 ### 2.设置监听的HTTP方法 在上一节通过flask routes命令打印出的路由列表可以看到,每一个路由除了包含URL规则外,还设置了监听的HTTP方法。GET是最常用的HTTP方法,所以视图函数默认监听的方法类型就是GET,HEAD、OPTIONS方法的请求由Flask处理,而像DELETE、PUT等方法一般不会在程序中实现,在后面我们构建Web API时才会用到这些方法。 我们可以在app.route()装饰器中使用methods参数传入一个包含监听的HTTP方法的可迭代对象。比如,下面的视图函数同时监听GET请求和POST请求: ``` @app.route('/hello', methods=['GET', 'POST']) def hello(): return '
Hello, Flask!
' ``` 当某个请求的方法不符合要求时,请求将无法被正常处理。比如,在提交表单时通常使用POST方法,而如果提交的目标URL对应的视图函数只允许GET方法,这时Flask会自动返回一个405错误响应(Method Not Allowed,表示请求方法不允许),如图2-6所示。  图2-6 405错误响应 通过定义方法列表,我们可以为同一个URL规则定义多个视图函数,分别处理不同HTTP方法的请求,这在本书第二部分构建Web API时会用到这个特性。 ### 3.URL处理 URL中的变量部分默认类型为字符串,但Flask提供了一些转换器可以在URL规则里使用,如表2-6所示。  表2-6 Flask内置的URL变量转换器 转换器通过特定的规则指定,即“<转换器:变量名>”。
把year的值转换为整数,因此我们可以在视图函数中直接对year变量进行数学计算: ``` @app.route('/goback/
') def go_back(year): return '
Welcome to %d!
' % (2018 - year) ``` 默认的行为不仅仅是转换变量类型,还包括URL匹配。在这个例子中,如果不使用转换器,默认year变量会被转换成字符串,为了能够在Python中计算天数,我们需要使用int()函数将year变量转换成整型。但是如果用户输入的是英文字母,就会出现转换错误,抛出ValueError异常,我们还需要手动验证;使用了转换器后,如果URL中传入的变量不是数字,那么会直接返回404错误响应。比如,你可以尝试访问[http://localhost:5000/goback/tang](http://localhost:5000/goback/tang) 。 在用法上唯一特别的是any转换器,你需要在转换器后添加括号来给出可选值,即“
”,比如: ``` @app.route('/colors/
') def three_colors(color): return '
Love is patient and kind. Love is not jealous or boastful or proud or rude.
' ``` 当你在浏览器中访问[http://localhost:5000/colors/](http://localhost:5000/colors/
' % str(colors)[1:-1]) ... ``` # 4、请求钩子 有时我们需要对请求进行预处理(preprocessing)和后处理(postprocessing),这时可以使用Flask提供的一些请求钩子(Hook),它们可以用来注册在请求处理的不同阶段执行的处理函数(或称为回调函数,即Callback)。这些请求钩子使用装饰器实现,通过程序实例app调用,用法很简单:以before_request钩子(请求之前)为例,当你对一个函数附加了app.before_request装饰器后,就会将这个函数注册为before_request处理函数,每次执行请求前都会触发所有before_request处理函数。Flask默认实现的五种请求钩子如表2-7所示。  表2-7 请求钩子 这些钩子使用起来和app.route()装饰器基本相同,每个钩子可以注册任意多个处理函数,函数名并不是必须和钩子名称相同,下面是一个基本示例: ``` @app.before_request def do_something(): pass # 这里的代码会在每个请求处理前执行 ``` 假如我们创建了三个视图函数A、B、C,其中视图C使用了after_this_request钩子,那么当请求A进入后,整个请求处理周期的请求处理函数调用流程如图2-7所示。 下面是请求钩子的一些常见应用场景: * before_first_request:在玩具程序中,运行程序前我们需要进行一些程序的初始化操作,比如创建数据库表,添加管理员用户。这些工作可以放到使用before_first_request装饰器注册的函数中。 * before_request:比如网站上要记录用户最后在线的时间,可以通过用户最后发送的请求时间来实现。为了避免在每个视图函数都添加更新在线时间的代码,我们可以仅在使用before_request钩子注册的函数中调用这段代码。 * after_request:我们经常在视图函数中进行数据库操作,比如更新、插入等,之后需要将更改提交到数据库中。提交更改的代码就可以放到after_request钩子注册的函数中。  图2-7 请求处理函数调用示意图 另一种常见的应用是建立数据库连接,通常会有多个视图函数需要建立和关闭数据库连接,这些操作基本相同。一个理想的解决方法是在请求之前(before_request)建立连接,在请求之后(teardown_request)关闭连接。通过在使用相应的请求钩子注册的函数中添加代码就可以实现。这很像单元测试中的setUp()方法和tearDown()方法。 > 注意 after_request钩子和after_this_request钩子必须接收一个响应类对象作为参数,并且返回同一个或更新后的响应对象。