flask使用

2018/12/31

介绍

Flask 是一个 Python 实现的 Web 开发微框架。

一个最小的应用

from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
  return 'Hello World!'

if __name__ == '__main__':
  app.run()

把它保存为 hello.py (或是类似的),然后用 Python 解释器来运行。 确保你的应用文件名不是 flask.py ,因为这将与 Flask 本身冲突。

$ python hello.py
 * Running on http://127.0.0.1:5000/

现在访问 http://127.0.0.1:5000/ ,你会看见 Hello World 问候。

外部可访问的服务器

app.run(host='0.0.0.0')

调试模式

app.debug = True
app.run()

另一种是作为 run 方法的一个参数传入

app.run(debug=True)

路由

基本的例子

@app.route('/')
def index():
  return 'Index Page'

@app.route('/hello')
def hello():
  return 'Hello World'

变量规则

@app.route('/user/<username>')
def show_user_profile(username):
  # show the user profile for that user
  return 'User %s' % username

@app.route('/post/<int:post_id>')
def show_post(post_id):
  # show the post with the given id, the id is an integer
  return 'Post %d' % post_id

转换器有下面几种

类型 描述
int 接受整数
float 同 int ,但是接受浮点数
path 和默认的相似,但也接受斜线

构造 URL

from flask import Flask, url_for
app = Flask(__name__)
@app.route('/')
def index(): pass

@app.route('/login')
def login(): pass

@app.route('/user/<username>')
def profile(username): pass

with app.test_request_context():
  print url_for('index')
  print url_for('login')
  print url_for('login', next='/')
  print url_for('profile', username='John Doe')

这里用到了 test_request_context() 方法,显示如下

/
/login
/login?next=/
/user/John%20Doe

构建 URL 而非在模板中硬编码的三个绝妙理由

  1. 反向构建通常比硬编码的描述性更好。更重要的是,它允许你一次性修改 URL, 而不是到处边找边改。
  2. URL 构建会转义特殊字符和 Unicode 数据,免去你很多麻烦。
  3. 如果你的应用不位于 URL 的根路径(比如,在 /myapplication 下,而不是 / ), url_for() 会妥善处理这个问题。

HTTP 方法

HTTP (与 Web 应用会话的协议)有许多不同的访问 URL 方法。默认情况下,路由只回应 GET 请求,但是通过 route() 装饰器传递 methods 参数可以改变这个行为。

@app.route('/login', methods=['GET', 'POST'])
def login():
  if request.method == 'POST':
    do_the_login()
  else:
    show_the_login_form()

静态文件

给静态文件生成 URL ,使用特殊的 ‘static’ 端点名

url_for('static', filename='style.css')

这个文件应该存储在文件系统上的 static/style.css。

模板渲染

你可以使用 render_template() 方法来渲染模板。你需要做的一切就是将模板名和你想作为关键字的参数传入模板的变量。这里有一个展示如何渲染模板的简例:

from flask import render_template

@app.route('/hello/')
@app.route('/hello/<name>')
def hello(name=None):
  return render_template('hello.html', name=name)

Flask 会在 templates 文件夹里寻找模板。所以,如果你的应用是个模块,这个文件夹应该与模块同级;如果它是一个包,那么这个文件夹作为包的子目录

  • 情况 1 模块:
/application.py
/templates
  /hello.html
  • 情况 2 包:
/application
  /__init__.py
  /templates
    /hello.html

这里有一个模板实例:

<!doctype html>
<title>Hello from Flask</title>
{\% if name %}
  <h1>Hello !</h1>
{\% else %}
  <h1>Hello World!</h1>
{\% endif %}

在模板里,你也可以访问 request 、 session 和 g [1] 对象, 以及 get_flashed_messages() 函数。

自动转义功能默认是开启的,所以如果 name 包含 HTML ,它将会被自动转义。如果你能信任一个变量,并且你知道它是安全的(例如一个模块把 Wiki 标记转换为 HTML),你可以用 Markup 类或 safe 过滤器在模板中把它标记为安全的。

这里是一个 Markup 类如何使用的简单介绍

>>> from flask import Markup
>>> Markup('<strong>Hello %s!</strong>') % '<blink>hacker</blink>'
Markup(u'<strong>Hello &lt;blink&gt;hacker&lt;/blink&gt;!</strong>')
>>> Markup.escape('<blink>hacker</blink>')
Markup(u'&lt;blink&gt;hacker&lt;/blink&gt;')
>>> Markup('<em>Marked up</em> &raquo; HTML').striptags()
u'Marked up \xbb HTML'

在 0.5 版更改: 自动转义不再在所有模板中启用。下列扩展名的模板会触发自动转义:.html、.htm、.xml、.xhtml。从字符串加载的模板会禁用自动转义。

访问请求数据

对于 Web 应用,与客户端发送给服务器的数据交互至关重要。在 Flask 中由全局的 request 对象来提供这些信息。如果你有一定的 Python 经验,你会好奇,为什么这个对象是全局的,为什么 Flask 还能保证线程安全。答案是环境作用域:

环境局部变量

Flask 中的某些对象是全局对象,但却不是通常的那种。这些对象实际上是特定环境的局部对象的代理。虽然很拗口,但实际上很容易理解。

想象一下处理线程的环境。一个请求传入,Web 服务器决定生成一个新线程( 或者别的什么东西,只要这个底层的对象可以胜任并发系统,而不仅仅是线程)。 当 Flask 开始它内部的请求处理时,它认定当前线程是活动的环境,并绑定当前的应用和 WSGI 环境到那个环境上(线程)。它的实现很巧妙,能保证一个应用调用另一个应用时不会出现问题。

所以,这对你来说意味着什么?除非你要做类似单元测试的东西,否则你基本上可以完全无视它。你会发现依赖于一段请求对象的代码,因没有请求对象无法正常运行。解决方案是,自行创建一个请求对象并且把它绑定到环境中。单元测试的最简单的解决方案是:用 test_request_context() 环境管理器。结合 with 声明,绑定一个测试请求,这样你才能与之交互。下面是一个例子

from flask import request

with app.test_request_context('/hello', method='POST'):
  # now you can do something with the request until the
  # end of the with block, such as basic assertions:
  assert request.path == '/hello'
  assert request.method == 'POST'

另一种可能是:传递整个 WSGI 环境给 request_context() 方法:

from flask import request

with app.request_context(environ):
  assert request.method == 'POST'

请求对象

当前请求的 HTTP 方法可通过 method 属性来访问。通过:attr:~flask.request.form 属性来访问表单数据( POST 或 PUT 请求提交的数据)。这里有一个用到上面提到的那两个属性的完整实例:

from flask import request

@app.route('/login', methods=['POST', 'GET'])
def login():
  error = None
  if request.method == 'POST':
    if valid_login(request.form['username'],
             request.form['password']):
      return log_the_user_in(request.form['username'])
    else:
      error = 'Invalid username/password'
  # the code below is executed if the request method
  # was GET or the credentials were invalid
  return render_template('login.html', error=error)

当访问 form 属性中的不存在的键会发生什么?会抛出一个特殊的 KeyError 异常。你可以像捕获标准的 KeyError 一样来捕获它。 如果你不这么做,它会显示一个 HTTP 400 Bad Request 错误页面。所以,多数情况下你并不需要干预这个行为。

你可以通过 args 属性来访问 URL 中提交的参数 ( ?key=value ):

searchword = request.args.get('q', '')

我们推荐用 get 来访问 URL 参数或捕获 KeyError ,因为用户可能会修改 URL,向他们展现一个 400 bad request 页面会影响用户体验。

文件上传

用 Flask 处理文件上传很简单。只要确保你没忘记在 HTML 表单中设置 enctype=”multipart/form-data” 属性,不然你的浏览器根本不会发送文件。

已上传的文件存储在内存或是文件系统中一个临时的位置。你可以通过请求对象的 files 属性访问它们。每个上传的文件都会存储在这个字典里。它表现近乎为一个标准的 Python file 对象,但它还有一个 save() 方法,这个方法允许你把文件保存到服务器的文件系统上。这里是一个用它保存文件的例子:

from flask import request

@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
  if request.method == 'POST':
    f = request.files['the_file']
    f.save('/var/www/uploads/uploaded_file.txt')
  ...

如果你想知道上传前文件在客户端的文件名是什么,你可以访问 filename 属性。但请记住, 永远不要信任这个值,这个值是可以伪造的。如果你要把文件按客户端提供的文件名存储在服务器上,那么请把它传递给 Werkzeug 提供的 secure_filename() 函数:

from flask import request
from werkzeug import secure_filename

@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
  if request.method == 'POST':
    f = request.files['the_file']
    f.save('/var/www/uploads/' + secure_filename(f.filename))
  ...

Cookies

你可以通过 cookies 属性来访问 Cookies,用响应对象的 set_cookie 方法来设置 Cookies。请求对象的 cookies 属性是一个内容为客户端提交的所有 Cookies 的字典。如果你想使用会话,请不要直接使用 Cookies,请参考 会话 一节。在 Flask 中,已经注意处理了一些 Cookies 安全细节。

读取 cookies:

from flask import request

@app.route('/')
def index():
  username = request.cookies.get('username')
  # use cookies.get(key) instead of cookies[key] to not get a
  # KeyError if the cookie is missing.

存储 cookies:

from flask import make_response

@app.route('/')
def index():
  resp = make_response(render_template(...))
  resp.set_cookie('username', 'the username')
  return resp

可注意到的是,Cookies 是设置在响应对象上的。由于通常视图函数只是返回字符串,之后 Flask 将字符串转换为响应对象。如果你要显式地转换,你可以使用 make_response() 函数然后再进行修改。

重定向和错误

可以用 redirect() 函数把用户重定向到其它地方。放弃请求并返回错误代码,用 abort() 函数。

from flask import abort, redirect, url_for

@app.route('/')
def index():
  return redirect(url_for('login')) # 重定向

@app.route('/login')
def login():
  abort(401) # 返回错误码
  this_is_never_executed()

这是一个相当无意义的例子因为用户会从主页重定向到一个不能访问的页面 (401 意味着禁止访问),但是它展示了重定向是如何工作的。

默认情况下,错误代码会显示一个黑白的错误页面。如果你要定制错误页面, 可以使用 errorhandler() 装饰器:

from flask import render_template

@app.errorhandler(404)
def page_not_found(error):
  return render_template('page_not_found.html'), 404

这是一个相当无意义的例子因为用户会从主页重定向到一个不能访问的页面 (401 意味着禁止访问),但是它展示了重定向是如何工作的。

默认情况下,错误代码会显示一个黑白的错误页面。如果你要定制错误页面, 可以使用 errorhandler() 装饰器:

from flask import render_template

@app.errorhandler(404)
def page_not_found(error):
  return render_template('page_not_found.html'), 404

注意 render_template() 调用之后的 404 。这告诉 Flask,该页的错误代码是 404 ,即没有找到。默认为 200,也就是一切正常。

关于响应

视图函数的返回值会被自动转换为一个响应对象。如果返回值是一个字符串, 它被转换为该字符串为主体的、状态码为 200 OK的 、 MIME 类型是 text/html 的响应对象。Flask 把返回值转换为响应对象的逻辑是这样

  1. 如果返回的是一个合法的响应对象,它会从视图直接返回。
  2. 如果返回的是一个字符串,响应对象会用字符串数据和默认参数创建。
  3. 如果返回的是一个元组,且元组中的元素可以提供额外的信息。这样的元组必须是 (response, status, headers) 的形式,且至少包含一个元素。 status 值会覆盖状态代码, headers 可以是一个列表或字典,作为额外的消息标头值。
  4. 如果上述条件均不满足, Flask 会假设返回值是一个合法的 WSGI 应用程序,并转换为一个请求对象。

如果你想在视图里操纵上述步骤结果的响应对象,可以使用 make_response() 函数。

@app.errorhandler(404)
def not_found(error):
  return render_template('error.html'), 404

你只需要把返回值表达式传递给 make_response() ,获取结果对象并修改,然后再返回它

@app.errorhandler(404)
def not_found(error):
  resp = make_response(render_template('error.html'), 404)
  resp.headers['X-Something'] = 'A value'
  return resp

会话

除请求对象之外,还有一个 session 对象。它允许你在不同请求间存储特定用户的信息。它是在 Cookies 的基础上实现的,并且对 Cookies 进行密钥签名。这意味着用户可以查看你 Cookie 的内容,但却不能修改它,除非用户知道签名的密钥。

要使用会话,你需要设置一个密钥。这里介绍会话如何工作:

from flask import Flask, session, redirect, url_for, escape, request

app = Flask(__name__)

@app.route('/')
def index():
  if 'username' in session:
    return 'Logged in as %s' % escape(session['username'])
  return 'You are not logged in'

@app.route('/login', methods=['GET', 'POST'])
def login():
  if request.method == 'POST':
    session['username'] = request.form['username']
    return redirect(url_for('index'))
  return '''
    <form action="" method="post">
      <p><input type=text name=username>
      <p><input type=submit value=Login>
    </form>
  '''

@app.route('/logout')
def logout():
  # remove the username from the session if it's there
  session.pop('username', None)
  return redirect(url_for('index'))

# set the secret key.  keep this really secret:
app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT'

这里提到的 escape() 可以在你模板引擎外做转义(如同本例)。

如何生成强壮的密钥

随机的问题在于很难判断什么是真随机。一个密钥应该足够随机。你的操作系统可以基于一个密钥随机生成器来生成漂亮的随机值,这个值可以用来做密钥:

import os
os.urandom(24)

输出

'\xfd{H\xe5<\x95\xf9\xe3\x96.5\xd1\x01O<!\xd5\xa2\xa0\x9fR"\xa1\xa8'

把这个值复制粘贴进你的代码中,你就有了密钥。

注意

使用基于 cookie 的会话需注意: Flask 会将你放进会话对象的值序列化至 Cookies。如果你发现某些值在请求之间并没有持久存在,然而确实已经启用了 Cookies,但也没有得到明确的错误信息。这时,请检查你的页面响应中的 Cookies 的大小,并与 Web 浏览器所支持的大小对比。

消息闪现

反馈,是良好的应用和用户界面的重要构成。如果用户得不到足够的反馈,他们很可能开始厌恶这个应用。 Flask 提供了消息闪现系统,可以简单地给用户反馈。 消息闪现系统通常会在请求结束时记录信息,并在下一个(且仅在下一个)请求中访问记录的信息。展现这些消息通常结合要模板布局。

使用 flash() 方法可以闪现一条消息。要操作消息本身,请使用 get_flashed_messages() 函数,并且在模板中也可以使用。完整的例子见 消息闪现 部分。

日志记录

有时候你会处于这样一种境地,你处理的数据本应该是正确的,但实际上不是。 比如,你会有一些向服务器发送请求的客户端代码,但请求显然是畸形的。这可能是用户篡改了数据,或是客户端代码的粗制滥造。大多数情况下,正常地返回 400 Bad Request 就可以了,但是有时候不能这么做,并且要让代码继续运行。

你可能依然想要记录下,是什么不对劲。这时日志记录就派上了用场。从 Flask 0.3 开始,Flask 就已经预置了日志系统。

这里有一些调用日志记录的例子:

app.logger.debug('A value for debugging')
app.logger.warning('A warning occurred (%d apples)', 42)
app.logger.error('An error occurred')

附带的 logger 是一个标准日志类 Logger ,所以更多信息请查阅 logging 的文档 。

整合 WSGI 中间件

如果你想给你的应用添加 WSGI 中间件,你可以封装内部 WSGI 应用。例如若是你想用 Werkzeug 包中的某个中间件来应付 lighttpd 中的 bugs ,可以这样做:

from werkzeug.contrib.fixers import LighttpdCGIRootFix
app.wsgi_app = LighttpdCGIRootFix(app.wsgi_app)

部署到 Web 服务器

准备好部署你的 Flask 应用了?你可以立即部署到托管平台来圆满完成快速入门,以下厂商均向小项目提供免费的方案:

  • 在 Heroku 上部署 Flask
  • 在 dotCloud 上部署

托管 Flask 应用的其它选择:

  • 在 Webfaction 上部署 Flask
  • 在 Google App Engine 上部署 Flask
  • 用 Localtunnel 共享你的本地服务器

如果你有自己的主机,并且准备自己托管,请看下面。

部署选择

http://docs.jinkan.org/docs/flask/deploying/index.html#deployment

参考地址

http://docs.jinkan.org/docs/flask/quickstart.html

目录