Flaskweb开发札记
简介
Flask是使用Python编写的轻量级Web应用框架。它基于Werkzeug WSGI工具包和Jinja2 模板引擎。 Flask使用BSD授权。 Flask基于Python的灵活,为Web开发提供简单的模板。
Flask 有两个主要依赖:路由、调试和 Web 服务器网关接口(Web Server Gateway Interface,WSGI) 子 系 统 由 Werkzeug(http://werkzeug.pocoo.org/) 提 供; 模 板 系 统 由 Jinja2(http://jinja.pocoo.org/)提供。Werkzeug 和 Jinjia2 都是由 Flask 的核心开发者开发而成。
Flask也被称为微框架,因为它核心简单但扩展性好。Flask没有数据库层、表单验证等第三方已经存在的通用功能。Flask的扩展可以像flask 自己实现一样使用这些功能。扩展有ORM(object-relational mappers,对象关系映射)、表单验证、文件上传、各种开放式身份验证技术等。
Flask的作者是Armin Ronacher。本来只是作者的一个愚人节玩笑,不过后来大受欢迎,进而成为一个正式的项目。
Flask和其他框架不同,开发者能完全并有创造力的方式控制应用。当方案非官方支持时,多数框架需要和“和框架斗争”,比如不同的数据库引擎或者用户认证方法,让人头痛。Flask支持所有的关系型数据库和NoSQL 数据库。使用自己开发的数据库引擎或根本用数据库也可以。在Flask中,你可以自主选择应用组件甚至自己开发。Flask 设计时就考虑了扩展性。它强健的核心包含了Web应用的基本功能,其他功能则由生态系统中大量的第三方扩展提供。本书介绍作者使用Flask 开发Web程序的工作流程,注意不是唯一正确方式。大部分软件开发书籍使用短小精悍的示例代码,读者需要自己考虑组合功能。在本书基于博客社交应用由浅入深进行扩展。
Flask功能简介
在Model-View-Controller中,Flask覆盖了C和V。
- 内置开发用服务器和debugger
- 支持Unicode
- WSGI兼容(uWsgi-friendly)
- 集成单元测试(unit testing)
- URL路由
- RESTful请求分发
- secure cookies
- Sessions
- Jinja2模板引擎
- 扩展的文档
- Google App Engine兼容
- 可用扩展增加其他功能
本书源码下载
git clone https://github.com/miguelgrinberg/flasky.git
查看具体实例:
git checkout 1a
如果修改了源码,可以简单重置
git reset --hard
获取最新代码
$ git fetch --all $ git fetch --tags $ git reset --hard origin/master
git fetch 命令用于利用 GitHub 上的远程仓库更新本地仓库的提交历史和标签,但不会改动真正的源文件,随后执行的 git reset 命令才是用于更新文件的操作。
查看程序两个版本之间的区别:
$ git diff 2a 2b
注意1a表示第一章的一个实例。比如第五章5a , 5b。git reset --hard会放弃本地修改,恢复默认配置,这样才能从服务器下载文件。比较差异也可以采用web的方式,比如https://github.com/miguelgrinberg/flasky/compare/2a...2b。
适用python版本:2.7 及 3.3
虚拟环境是Python解释器的拷贝,你可以在上面安装私有python包而不影响全局的Python解释器。虚拟环境可以使用virtualenvwrapper或者virtualenv。使用virtualenvwrapper如下:
sudo pip install virtualenvwrapper echo "source /usr/local/bin/virtualenvwrapper.sh" >> ~/.bashrc mkvirtualenv hello pip install flask deactivate workon hello
virtualenv的安装如下。Python 3.3通过venv模块和pyvenv命令支持虚拟环境,但不支持pip,Python 3.4中没有这个问题。
#pip install virtualenv #pip install flask
快速入门
初始化
Flask应用需要创建应用实例。 Web服务器通过Web Server Gateway Interface (WSGI)协议把从客户端接收到的请求传递给该对象。应用程序实例是Flask类对象,通常创建如下:
from flask import Flask app = Flask(__name__)
路由和视图函数
客户端如Web浏览器发送请求到Web服务器,再转发到Flask应用实例。应用程序实例需要知道每个URL执行哪些代码,因此它保留URL到Python函数的映射。URL和处理函数的关联叫route(路由)。
添加路由最方便的方法是通过应用实例的装饰器app.route,注册函数为路由。比如:
@app.route('/')
def index(): return '<h1>Hello World!</h1>'
装饰器是Python语言的标准功能,可以修改的函数的行为。常用装饰器注册函数为事件处理器。上面的index叫做视图函数。
处理变量
@app.route('/user/<name>') def user(name): return '<h1>Hello, %s!</h1>' % name
启动服务器
if __name__ == '__main__': app.run(debug=True)
此时如果访问不存在的网址,就会返回404。
完整应用
from flask import Flask app = Flask(__name__) @app.route('/') def index(): return '<h1>Hello World!</h1>\n' @app.route('/user/<name>') def user(name): return '<h1>Hello, %s!</h1>\n' % name if __name__ == '__main__': app.run(debug=True, host='0.0.0.0', port=80)
执行服务器端:
# python hello.py * Restarting with stat * Debugger is active! * Debugger pin code: 298-160-090 127.0.0.1 - - [03/Dec/2015 15:58:41] "GET / HTTP/1.1" 200 - 127.0.0.1 - - [03/Dec/2015 15:58:42] "GET /user/Andrew HTTP/1.1" 200 -
执行客户端:
# curl http://localhost/ <h1>Hello World!</h1> [root@localhost ~]# curl http://localhost/user/Andrew <h1>Hello, Andrew!</h1> [root@localhost ~]#
请求响应机制
- 应用和请求上下文
Flask使用上下文临时存储一些对象。比如request对象封装了客户端的HTTP请求。Flask通过context把请求数据放入全局空间。因此可以这样操作。
from flask import Flask from flask import request app = Flask(__name__) @app.route('/') def index(): user_agent = request.headers.get('User-Agent') return '<p>Your browser is %s</p>' % user_agent if __name__ == '__main__': app.run(debug=True,host='0.0.0.0',port=80)
执行结果:
# curl http://localhost/ <p>Your browser is curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.19.1 Basic ECC zlib/1.2.3 libidn/1.18 libssh2/1.4.2</p> # 浏览器执行 Your browser is Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101 Firefox/38.0
注意不同线程看到是不同的request。全局的上下文如下:
push之后方可看到这四个变量。
>>> from hello import app >>> from flask import current_app >>> current_app.name Traceback (most recent call last):...RuntimeError: working outside of application context >>> app_ctx = app.app_context() >>> app_ctx.push() >>> current_app.name 'hello' >>> app_ctx.pop()
- 请求分发
app.route添加网址映射,等效于方法app.add_url_rule()。app.add_url_rule()存储了url映射。
>>> 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>])
- 请求Hook
hook适用于一些通用性的操作。目前有before_first_request、before_request、after_request(有异常不会执行)、teardown_request(有异常也会执行)。hook和视图函数之前可以使用全局变量g传递参数。
- 响应
默认返回状态码200,可以修改:
@app.route('/')
def index(): return '<h1>Bad Request</h1>', 400
执行结果:
$ curl http://127.0.0.1
<h1>Bad Request</h1>
可以添加头的字典作为第3个参数,但是很少使用。另外也可以返回Response对象。make_response()接纳1-3个参数,适用于视图函数定制响应,比如
from flask import make_response @app.route('/')
def index(): response = make_response('<h1>This document carries a cookie!</h1>') response.set_cookie('answer', '42') return response
另有特殊的响应redirect,多用于表单。redirect返回状态码302和URL。也可以直接return 3个值或Response对象,但是redirect更快捷。
from flask import Flask app = Flask(__name__) from flask import redirect @app.route('/')
def index(): return redirect('http://automationtesting.sinaapp.com/') if __name__ == '__main__': app.run(debug=True,host='0.0.0.0',port=80)
执行结果:
$ curl http://127.0.0.1 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"> <title>Redirecting...</title> <h1>Redirecting...</h1> <p>You should be redirected automatically to target URL: <a href="http://automationtesting.sinaapp.com/">http://automationtesting.sinaapp.com/</a>. If not click the link
用浏览器访问通常会直接跳转。
abort也是特殊响应,用于错误处理。它直接产生异常,把控制交给web server。
from flask import abort @app.route('/user/<id>') def get_user(id): user = load_user(id) if not user: abort(404) return '<h1>Hello, %s</h1>' % user.name
上面例子执行会报错,为此我们简化的实例:
from flask import Flask app = Flask(__name__) @app.route('/user/<username>') def get_user(username): if not username: abort(404) return '<h1>Hello, %s</h1>\n' % username if __name__ == '__main__': app.run(debug=True,host='0.0.0.0',port=80)
执行结果:
# curl http://127.0.0.1/user <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"> <title>404 Not Found</title> <h1>Not Found</h1> <p>The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.</p># curl http://127.0.0.1/user/test <h1>Hello, test</h1>
Flask扩展
Flask支持大量的启动配置选项,方法是将它们作为参数传递给app.run()。Flask-Script扩展增加了命令行参数支持。
# pip install flask-script
from flask import Flask from flask.ext.script import Manager app = Flask(__name__) manager = Manager(app) @app.route('/') def index(): return '<h1>Hello World!</h1>' @app.route('/user/<name>') def user(name): return '<h1>Hello, %s!</h1>' % name if __name__ == '__main__': manager.run()
]# python hello.pyusage: hello.py [-?] {shell,runserver} ...positional arguments: {shell,runserver} shell Runs a Python shell inside Flask application context. runserver Runs the Flask development server i.e. app.run()optional arguments: -?, --help show this help message and exit# python hello.py runserver --helpusage: hello.py runserver [-?] [-h HOST] [-p PORT] [--threaded] [--processes PROCESSES] [--passthrough-errors] [-d] [-D] [-r] [-R]Runs the Flask development server i.e. app.run()optional arguments: -?, --help show this help message and exit -h HOST, --host HOST -p PORT, --port PORT --threaded --processes PROCESSES --passthrough-errors -d, --debug enable the Werkzeug debugger (DO NOT use in production code) -D, --no-debug disable the Werkzeug debugger -r, --reload monitor Python files for changes (not 100{'const': True, 'help': 'monitor Python files for changes (not 100% safe for production use)', 'option_strings': ['-r', '--reload'], 'dest': 'use_reloader', 'required': False, 'nargs': 0, 'choices': None, 'default': None, 'prog': 'hello.py runserver', 'container': <argparse._ArgumentGroup object at 0x21dc150>, 'type': None, 'metavar': None}afe for production use) -R, --no-reload do not monitor Python files for changes
#默认是监听localhost
# python hello.py runserver --host 0.0.0.0 -p 80 * Running on http://0.0.0.0:80/
模板
业务(business logic 比如插入数据库)和展示逻辑(presentation logic, 比如生成返回)最好分开,展示逻辑可以放置在模板中。模板是一个包含响应文本的文件,用占位符变量表示动态部分。rendering(渲染):把占位符用实际值代替,并返回最终响应字符串。Flask采用模板引擎Jinja2。
模板引擎Jinja2
简单的模板:templates/index.html
<h1>Hello World!</h1>
带参数的模板:templates/user.html
<h1>Hello, {{ name }}!</h1>
渲染模板
默认Flask查找应用的templates子目录寻找模板,现在我们对上一章的hello.py基于模板进行改写:
from flask import Flask, render_template from flask.ext.script import Manager app = Flask(__name__) manager = Manager(app) @app.route('/') def index(): return render_template('index.html') @app.route('/user/<name>') def user(name): return render_template('user.html', name=name) if __name__ == '__main__': manager.run()
变量
更多变量示例:
<p>A value from a dictionary: {{ mydict['key'] }}.</p> <p>A value froma list: {{ mylist[3] }}.</p> <p>A value froma list, with a variable index: {{ mylist[myintvar] }}.</p> <p>A value froman object's method: {{ myobj.somemethod() }}.</p>
注意上面的列表 {{ mylist[myintvar] }},有名字索引。变量后面还可以添加过滤器,比如首字母转为大写其他的转为小写:Hello, {{ name|capitalize }}。Jinja2中常见的变量过滤器如下:
默认Jinja2因为安全原因转义所有变量,比如变量的值为'<h1>Hello</h1>',Jinja2就会渲染成'<h1>Hello</h1>',这样就不会被浏览器解释。注意不要对用户表单输入等可能不安全的代码使用safe。更多参考:http://jinja.pocoo.org/docs/dev/templates/#builtin-filters。
控制结构
if语句:
{% if user %} Hello, {{ user }}! {% else %} Hello, Stranger! {% endif %}
for语句:
<ul> {% for comment in comments %} <li>{{ comment }}</li> {% endfor %} </ul>
macro
{% macro render_comment(comment) %} <li>{{ comment }}</li> {% endmacro %}
<ul> {% for comment in comments %} {{ render_comment(comment) }} {% endfor %} </ul>
宏也可以放置在文件中:
{% import 'macros.html' as macros %} <ul> {% for comment in comments %} {{ macros.render_comment(comment) }} {% endfor %} </ul>
又如公共的文件可以这样导入:{% include 'common.html' %}
继承
基类:base.html
<html> <head> {% block head %} <title>{% block title %}{% endblock %} - My Application</title> {% endblock %} </head> <body> {% block body %} {% endblock %} </body> </html>
block标签定义继承模板可以修改的部分,上面有head , title和body三个可变部分。下面有个继承的子模板:
{% extends "base.html" %} {% block title %}Index{% endblock %} {% block head %} {{ super() }} <style> </style> {% endblock %} {% block body %} <h1>Hello, World!</h1> {% endblock %}