http
处理所有客户端—服务器HTTP请求的具体细节\n",
117 | " - client
处理客户端部分\n",
118 | " - server
提供了实现HTTP服务器的各种类\n",
119 | " - cookies
支持在服务器端处理HTTP cookie\n",
120 | " - cookiejar
支持在客户端存储和管理HTTP cookie\n",
121 | "\n",
122 | "- urllib
基于http
的高层库,用于编写与HTTP服务器等交互的客户端\n",
123 | " - request
处理客户端请求\n",
124 | " - response
处理服务器端响应\n",
125 | " - parse
用于操作URL字符串\n"
126 | ]
127 | },
128 | {
129 | "cell_type": "code",
130 | "execution_count": null,
131 | "metadata": {
132 | "autoscroll": false,
133 | "collapsed": false,
134 | "ein.tags": "worksheet-0",
135 | "slideshow": {
136 | "slide_type": "-"
137 | }
138 | },
139 | "outputs": [],
140 | "source": [
141 | "import urllib.request as ur\n",
142 | "url = 'http://httpbin.org/'\n",
143 | "conn = ur.urlopen(url)\n",
144 | "print(conn)\n",
145 | "print('=' * 50)\n",
146 | "data = conn.read()\n",
147 | "print(data[:16])\n",
148 | "print(conn.status)\n",
149 | "\n",
150 | "print(conn.getheader('Content-Type'))\n",
151 | "for key, value in conn.getheaders():\n",
152 | " print(key, value, sep=': ')"
153 | ]
154 | },
155 | {
156 | "cell_type": "markdown",
157 | "metadata": {
158 | "ein.tags": "worksheet-0",
159 | "slideshow": {
160 | "slide_type": "-"
161 | }
162 | },
163 | "source": [
164 | " http
命令,允许通过自然的语法发送任意 HTTP 请求数据, 展示色彩化的输出。\n",
204 | "\n",
205 | "HTTPie 可用于与 HTTP 服务器做测试、调试和常规交互。\n",
206 | "\n",
207 | "HTTPie 用 Python 编写,用到了 Requests 和 Pygments 这些出色的库。\n",
208 | "\n",
209 | "\n",
210 | "\n",
211 | "\n",
212 | "### httpbin\n",
213 | "\n",
214 | "deactivate
取消激活虚拟环境:\n",
67 | "\n",
68 | " (flaskr_env3) $ deactivate\n",
69 | "\n",
70 | "2. 使用 **pip** 安装Python包\n",
71 | "\n",
72 | " 执行下述命令在虚拟环境中安装 Flask:\n",
73 | "\n",
74 | " (flaskr_env3) $ pip install flask\n",
75 | "\n",
76 | " 其会自动安装 Flask 及其依赖。安装完之后,可以验证 Flask 是否安装成功,启动 Python 解释器,尝试导入 Flask:\n",
77 | "\n",
78 | " (flaskr_env3) $ python\n",
79 | " >>> import flask\n",
80 | " >>>\n",
81 | "\n",
82 | " 如果没有错误提示,则表示 Flask 安装成功。\n",
83 | "\n",
84 | "\n",
85 | "## 一个完整的程序\n",
86 | "\n",
87 | "```python\n",
88 | "# hello.py\n",
89 | "from flask import Flask\n",
90 | "app = Flask(__name__)\n",
91 | "\n",
92 | "\n",
93 | "@app.route('/')\n",
94 | "def index():\n",
95 | " return 'http://127.0.0.1:5000/
。\n",
113 | "\n",
114 | "**执行git checkout 1a
签出程序的这个版本。**\n",
115 | "\n",
116 | "\n",
117 | "## 调试模式\n",
118 | "\n",
119 | "上面启动命令中通过export FLASK_DEBUG=1
启用调试模式,服务器会在代码修改后自动重新载入。\n",
120 | "\n",
121 | "如果FLASK_DEBUG
设置为0
则将关闭调试模式。\n",
122 | "\n",
123 | "## 动态路由\n",
124 | "```python\n",
125 | "# hello.py\n",
126 | "from flask import Flask\n",
127 | "app = Flask(__name__)\n",
128 | "\n",
129 | "\n",
130 | "@app.route('/')\n",
131 | "def index():\n",
132 | " return 'http://localhost:5000/user/david
,程序会显示一个使用name
动态参数生成的欢迎消息。可尝试使用不同的名字,可看到视图函数总是使用指定的名字生成响应。\n",
141 | "\n",
142 | "**执行git checkout 1b
签出程序的这个版本。**\n",
143 | "\n",
144 | "\n",
145 | "### 动态URL规则\n",
146 | "\n",
147 | "URL规则可以添加变量部分,即将符合同种规则的URL抽象称一个URL模式,如/item/1/
、/item/2/
、/item/3/
… 假如不抽象,就得这样写:\n",
148 | "\n",
149 | "```python\n",
150 | "@app.route('/item/1/')\n",
151 | "@app.route('/item/2/')\n",
152 | "@app.route('/item/3/')\n",
153 | "def item(id):\n",
154 | " return 'Item: {}'.format(id)\n",
155 | "```\n",
156 | "\n",
157 | "正确的用法是:\n",
158 | "\n",
159 | "```python\n",
160 | "@app.route('/item//item/
前缀的URL都会被映射到这个路由上,在 内部把id
作为参数而获得。\n",
166 | "\n",
167 | "它使用了特殊的字段标记 **int
,但是接受浮点数\n",
172 | "- **`path`:** 和默认的相似,但也接受斜杠\n",
173 | "- **`uuid`:** 只接受uuid字符串\n",
174 | "- **`any`:** 可以指定多种路径,但是需要传入参数\n",
175 | "\n",
176 | " `@app.route('//a/
和 访问/b/
都符合这个规则,/a/
对应的page_name
就是a
。\n",
179 | "\n",
180 | "如果不希望定制子路径,还可以通过 **传递参数** 的方式。比如/people/?name=a
,/people/?name=b
,这样即可通过name = request.args.get('name')
获得传入的name
值。\n",
181 | "\n",
182 | "如果使用 **POST** 方法,表单参数需要通过request.form.get('name')
获得。\n",
183 | "\n",
184 | "\n",
185 | "### 唯一URL\n",
186 | "\n",
187 | "Flask的URL规则基于Werkzeug的路由模块,这个模块背后的思想是希望保证优雅且唯一的URL。\n",
188 | "\n",
189 | "举个例子:\n",
190 | "\n",
191 | "```python\n",
192 | "@app.route('/projects/')\n",
193 | "def projects():\n",
194 | " return 'The project page'\n",
195 | "```\n",
196 | "\n",
197 | "**访问一个结尾不带斜线的URL会被重定向到带斜线的规范的URL上去** 。\n",
198 | "\n",
199 | "再看一个例子:\n",
200 | "\n",
201 | "```python\n",
202 | "@app.route('/about')\n",
203 | "def about():\n",
204 | " return 'The about page'\n",
205 | "```\n",
206 | "\n",
207 | "URL结尾不带斜线,很像文件的路径,当访问带斜线的URL(/about/
)会产生一个404 “Not Found” 错误。\n",
208 | "\n",
209 | "\n",
210 | "## 请求—响应循环\n",
211 | "\n",
212 | "接下来介绍 Flask 的工作方式,了解这个框架的一些设计理念。\n",
213 | "\n",
214 | "\n",
215 | "### 程序和请求上下文\n",
216 | "\n",
217 | "Flask 从客户端收到请求时,要让视图函数能访问一些对象来处理请求。 **请求对象** 封装了客户端发送的 HTTP 请求。\n",
218 | "\n",
219 | "要想让视图函数能够访问请求对象,一个方式是 **将其作为参数传入视图函数** , 不过这会导致程序中的每个视图函数都增加一个参数。 如果视图函数在处理请求时还要访问其他对象,情况会变得更糟。\n",
220 | "\n",
221 | "为了避免大量可有可无的参数把视图函数弄得一团糟,Flask 使用 **上下文** 临时把某些对象变为 **全局可访问** 。 视图函数中使用上下文:\n",
222 | "\n",
223 | "```python\n",
224 | "from flask import request\n",
225 | "\n",
226 | "@app.route('/')\n",
227 | "def index():\n",
228 | " user_agent = request.headers.get('User-Agent')\n",
229 | " return 'Your browser is %s
' % user_agent\n", 230 | "```\n", 231 | "\n", 232 | "上面这个视图函数把request
当作全局变量使用。 Flask 使用上下文让特定的变量在一个线程中全局可访问,与此同时却不会干扰其他线程。1\n",
233 | "\n",
234 | "Flask 中有两种上下文: **程序上下文** 和 **请求上下文** 。\n",
235 | "\n",
236 | "| 变量名 | 上下文 | 说明 |\n",
237 | "|---------------|--------|-----------------------------|\n",
238 | "| `current_app` | 程序上下文 | 当前激活程序的程序实例 |\n",
239 | "| `g` | 程序上下文 | 处理请求时用作临时存储的对象,每次请求都会重设这个变量 |\n",
240 | "| `request` | 请求上下文 | 请求对象,封装了客户端发出的 HTTP 请求中的内容 |\n",
241 | "| `session` | 请求上下文 | 用户会话,用于存储请求之间需要“记住”的值的词典 |\n",
242 | "\n",
243 | "Flask 在分发请求之前激活(或推送)程序和请求上下文,请求处理完成后再将其删除。 程序上下文被推送后,就可以在线程中使用current_app
和g
变量。 类似地,请求上下文被推送后,就可以使用request
和session
变量。 如果使用这些变量时没有激活程序上下文或请求上下文,就会导致错误。\n",
244 | "\n",
245 | "下面的Python shell会话演示了程序上下文的使用方法:\n",
246 | "\n",
247 | "```python\n",
248 | ">>> from hello import app\n",
249 | ">>> from flask import current_app\n",
250 | ">>> current_app.name\n",
251 | "Traceback (most recent call last):\n",
252 | "...\n",
253 | "RuntimeError: Working outside of application context.\n",
254 | "...\n",
255 | ">>> app_ctx = app.app_context()\n",
256 | ">>> app_ctx.push()\n",
257 | ">>> current_app.name\n",
258 | "'hello'\n",
259 | ">>> app_ctx.pop()\n",
260 | "```\n",
261 | "\n",
262 | "没激活程序上下文之前就调用current_app.name
会导致错误,推送完上下文之后才可以调用。\n",
263 | "\n",
264 | "**注意:** 调用app.app_context()
可获得一个程序上下文。\n",
265 | "\n",
266 | "### 请求调度\n",
267 | "\n",
268 | "程序收到客户端发来的请求时,要找到处理该请求的视图函数。\n",
269 | "\n",
270 | "**Flask 会在程序的 URL 映射中查找请求的 URL。** URL 映射是 URL 和视图函数之间的对应关系。 Flask 使用app.route
装饰器或者非装饰器形式的app.add_url_rule()
生成映射。\n",
271 | "\n",
272 | "在 Python shell 中检查为hello.py
生成的映射:\n",
273 | "\n",
274 | "```python\n",
275 | "(flaskr_env3) $ python\n",
276 | ">>> from hello import app\n",
277 | ">>> app.url_map\n",
278 | "Map([/
和/user/
路由在程序中使用app.route
装饰器定义。\n",
284 | "- **`/static/g
。** 例如,before_request
处理程序可以从数据库中加载已登录用户,并将其保存到g.user
中。 随后调用视图函数时,视图函数再使用g.user
获取用户。\n",
301 | "\n",
302 | "\n",
303 | "### 响应\n",
304 | "\n",
305 | "Flask 调用视图函数后,会将其返回值作为响应的内容。大多数情况下,响应就是一个简 单的字符串,作为 HTML 页面回送给客户端。\n",
306 | "\n",
307 | "HTTP 响应中一个很重要的部分是 **状态码** , Flask 默认设为 **200** ,这个代码表明请求已经被成功处理。\n",
308 | "\n",
309 | "如果视图函数返回的响应需要使用不同的状态码,那么可以把数字代码作为第二个返回 值,添加到响应文本之后。\n",
310 | "\n",
311 | "```python\n",
312 | "@app.route('/')\n",
313 | "def index():\n",
314 | " return 'make_response
函数 接受1-3个参数(和视图函数的返回值一样),并返回一个Response
对象。\n",
320 | "\n",
321 | "下例中创建了一个响应对象,然后设置了cookie:\n",
322 | "\n",
323 | "```python\n",
324 | "from flask import make_response\n",
325 | "\n",
326 | "@app.route('/')\n",
327 | "def index():\n",
328 | " response = make_response('Location
首部提供。 重定向响应可以使用 3 个值形式的返回值生成,也可在Response
对象中设定。 Flask 还提供了redirect()
辅助函数,用于生成这种响应:\n",
336 | "\n",
337 | "```python\n",
338 | "from flask import redirect\n",
339 | "\n",
340 | "@app.route('/')\n",
341 | "def index():\n",
342 | " return redirect('http://www.example.com')\n",
343 | "```\n",
344 | "\n",
345 | "还有一种特殊的响应由abort
函数生成,用于处理错误。 下例中,如果 URL 中动态参数id
对应的用户不存在,就返回状态码 **404** :\n",
346 | "\n",
347 | "```python\n",
348 | "from flask import abort\n",
349 | "\n",
350 | "@app.route('/user/abort
不会把控制权交还给调用它的函数,而是抛出异常把控制权交给 Web 服务器。\n",
359 | "\n",
360 | "## 脚注\n",
361 | "\n",
362 | "1 多线程 Web 服务器会创建一个线程池,再从线程池中选择一个线程用于处理接收到的请求。\n"
363 | ]
364 | }
365 | ],
366 | "metadata": {
367 | "kernelspec": {
368 | "display_name": "Python 3",
369 | "name": "python3"
370 | },
371 | "language_info": {
372 | "codemirror_mode": {
373 | "name": "ipython",
374 | "version": 3
375 | },
376 | "file_extension": ".py",
377 | "mimetype": "text/x-python",
378 | "name": "python",
379 | "nbconvert_exporter": "python",
380 | "pygments_lexer": "ipython3",
381 | "version": "3.6.2"
382 | },
383 | "name": "2-flask-intro.ipynb",
384 | "toc": {
385 | "colors": {
386 | "hover_highlight": "#ddd",
387 | "running_highlight": "#FF0000",
388 | "selected_highlight": "#ccc"
389 | },
390 | "moveMenuLeft": true,
391 | "nav_menu": {
392 | "height": "282px",
393 | "width": "252px"
394 | },
395 | "navigate_menu": true,
396 | "number_sections": false,
397 | "sideBar": true,
398 | "threshold": 4,
399 | "toc_cell": false,
400 | "toc_section_display": "block",
401 | "toc_window_display": false,
402 | "widenNotebook": false
403 | }
404 | },
405 | "nbformat": 4,
406 | "nbformat_minor": 2
407 | }
408 |
--------------------------------------------------------------------------------
/ch03/3-jinja2-templates.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {
6 | "ein.tags": "worksheet-0",
7 | "slideshow": {
8 | "slide_type": "-"
9 | }
10 | },
11 | "source": [
12 | "# 模板\n",
13 | "把业务逻辑和表现逻辑混在一起会导致代码难以理解和维护。为了提升程序的可维护性,通常把表现逻辑移到 **模板** 中。\n",
14 | "\n",
15 | "**模板** 是一个包含响应文本的文件,其中包含用占位变量表示的动态部分, 其具体值只在请求的上下文中才能知道。使用真实值替换变量,再返回最终得到的响应字符串, 这一过程称为 **渲染** 。为了渲染模板,Flask 使用了一个名为 **[Jinja2](http://jinja.pocoo.org/)** 的强大模板引擎。\n",
16 | "\n",
17 | "\n",
18 | "## Jinja2模板引擎\n",
19 | "\n",
20 | "\n",
21 | "### 基本使用\n",
22 | "\n",
23 | "Jinja2通过`Template`类创建并渲染模板:"
24 | ]
25 | },
26 | {
27 | "cell_type": "code",
28 | "execution_count": null,
29 | "metadata": {
30 | "autoscroll": false,
31 | "collapsed": false,
32 | "ein.tags": "worksheet-0",
33 | "slideshow": {
34 | "slide_type": "-"
35 | }
36 | },
37 | "outputs": [],
38 | "source": [
39 | "from jinja2 import Template\n",
40 | "template = Template('Hello {{ name }}!')\n",
41 | "print(template.render(name='Xiao Ming'))"
42 | ]
43 | },
44 | {
45 | "cell_type": "markdown",
46 | "metadata": {
47 | "ein.tags": "worksheet-0",
48 | "slideshow": {
49 | "slide_type": "-"
50 | }
51 | },
52 | "source": [
53 | "其背后通过`Environment`实例来存储配置和全局对象,从文件系统或其他位置加载模板:\n",
54 | "\n",
55 | "```django\n",
56 | "{# templates/user.html #}\n",
57 | "A value from a dictionary: {{ mydict['key'] }}.
\n", 154 | "A value from a list: {{ mylist[3] }}.
\n", 155 | "A value from a list, with a variable index: {{ mylist[myintvar] }}.
\n", 156 | "A value from an object's method: {{ myobj.somemethod() }}.
\n", 157 | "```\n", 158 | "\n", 159 | "可以使用 **过滤器** 修改变量,过滤器名添加在变量名之后,中间使用竖线(`|`)分隔。\n", 160 | "\n", 161 | "```django\n", 162 | "{# 以首字母大写形式显示变量 name 的值 #}\n", 163 | "Hello, {{ name|capitalize }}\n", 164 | "```\n", 165 | "\n", 166 | "下标列举了 Jinja2 提供的部分常用过滤器1:\n", 167 | "\n", 168 | "| 过滤器名 | 说明 |\n", 169 | "|--------------|-----------------------|\n", 170 | "| `safe` | 渲染值时不转义 |\n", 171 | "| `capitalize` | 把值的首字母转换成大写,其他字母转换成小写 |\n", 172 | "| `lower` | 把值转换成小写形式 |\n", 173 | "| `upper` | 把值转换成大写形式 |\n", 174 | "| `title` | 把值中每个单词的首字母都转换成大写 |\n", 175 | "| `trim` | 把值的首尾空格去掉 |\n", 176 | "| `striptags` | 渲染之前把值中所有的HTML标签都删掉 |\n", 177 | "\n", 178 | "**默认情况下,出于安全考虑,Jinja2 会转义所有变量。** 例如,如果一个变量的值为`'{{ a }} {{ b }} {{ c }}
\n", 320 | "\"\"\").render())\n", 321 | "```\n", 322 | "\n", 323 | "\n", 324 | "\n", 325 | "\n", 326 | "1 0 1
\n", 327 | "\n", 328 | "\n", 329 | "## 使用 Flask-Bootstrap\n", 330 | "\n", 331 | "Bootstrap是非常流行的前端开发框架。\n", 332 | "\n", 333 | "要在程序中集成Bootstrap,需要对模板进行修改,加入 Bootstrap 层叠样式表(CSS) 和 JavaScript 文件的引用。但是,更简单的办法是直接使用Flask扩展 [Flask-Bootstrap](http://pythonhosted.org/Flask-Bootstrap/) 。\n", 334 | "\n", 335 | "\n", 336 | "### 安装 Flask-Bootstrap\n", 337 | "\n", 338 | "```sh\n", 339 | "(flaskr_env3) $ pip install flask-bootstrap\n", 340 | "```\n", 341 | "\n", 342 | "Flask 扩展一般都在创建程序实例时初始化:\n", 343 | "\n", 344 | "```python\n", 345 | "# hello.py\n", 346 | "from flask_bootstrap import Bootstrap\n", 347 | "\n", 348 | "# ...\n", 349 | "bootstrap = Bootstrap(app)\n", 350 | "```\n", 351 | "\n", 352 | "导入`Bootstrap`,然后把程序实例传入构造方法进行初始化。\n", 353 | "\n", 354 | "初始化 Flask-Bootstrap 之后,就可以在程序中使用一个包含所有 Bootstrap 文件的基模板。 这个模板利用 Jinja2 的模板继承机制,让程序扩展一个具有基本页面结构的基模板, 其中就有用来引入 Bootstrap 的元素。\n", 355 | "\n", 356 | "```django\n", 357 | "{# templates/base.html #}\n", 358 | "\n", 359 | "{% extends \"bootstrap/base.html\" %}\n", 360 | "\n", 361 | "{% block title %}Flaskr{% endblock %}\n", 362 | "\n", 363 | "{% block navbar %}\n", 364 | " \n", 383 | "{% endblock %}\n", 384 | "\n", 385 | "{% block content %}\n", 386 | "Pleased to meet you!
\n", 320 | " {% else %}\n", 321 | "Happy to see you again!
\n", 322 | " {% endif %}\n", 323 | "\n", 91 | " {% if user.name %}{{ user.name }}{% endif %}\n", 92 | " {% if user.location %}\n", 93 | " From {{ user.location }}\n", 94 | " {% endif %}\n", 95 | "
\n", 96 | " {% endif %}\n", 97 | " {% if current_user.is_administrator() %}\n", 98 | " \n", 99 | " {% endif %}\n", 100 | " {% if user.about_me %}{{ user.about_me }}
{% endif %}\n", 101 | "Member since {{ moment(user.member_since).format('L') }}.\n", 102 | " Last seen {{ moment(user.last_seen).fromNow() }}.
\n", 103 | "