Flaskweb开发札记

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。全局的上下文如下:

Flaskweb开发札记

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中常见的变量过滤器如下:

 Flaskweb开发札记

默认Jinja2因为安全原因转义所有变量,比如变量的值为'<h1>Hello</h1>',Jinja2就会渲染成'&lt;h1&gt;Hello&lt;/h1&gt;',这样就不会被浏览器解释。注意不要对用户表单输入等可能不安全的代码使用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 %}