Flask框架学习
快速入门
1 from flask import Flask 2 app = Flask(__name__) 3 4 @app.route('/') 5 def hello_world(): 6 return 'Hello World!' 7 8 if __name__ == '__main__': 9 app.run()
把它保存为 hello.py (或是类似的),然后用 Python 解释器来运行。 确保你的应用文件名不是 flask.py ,因为这将与 Flask 本身冲突。
1 $ python hello.py 2 * Running on http://127.0.0.1:5000/
分析:
1. 首先,我们导入了 Flask 类。这个类的实例将会是我们的 WSGI 应用程序。
2. 接下来,我们创建一个该类的实例,第一个参数是应用模块或者包的名称。 如果你使用单一的模块(如本例),你应该使用 __name__ ,因为模块的名称将会因其作为单独应用启动还是作为模块导入而有不同( 也即是 '__main__' 或实际的导入名)。这是必须的,这样 Flask 才知道到哪去找模板、静态文件等等
3. 然后,我们使用 route() 装饰器告诉 Flask 什么样的URL 能触发我们的函数,URL和执行的视图函数(函数 index )的关系保存在app.url_map属性上
4.执行app.run来启动服务器。默认的Flask会监听的地址是127.0.0.1:5000。我们指定host和port参数,就修改了监听地址。 服务启动后,会先判断参数host以及port是否为None,如果为None,就会将host和port修改为默认值。然后会判断debug。然后就会调用werkzeug.serving.run_simple来启动Web服务,默认会使用单进程的werkzeug.serving_BaseWSGIServer来处理客户端的请求。
源码:
注: 这里的Werkzeug其实就是WSGI的实现和应用,从中可以发现,Flask是基于Werkzeug开发的。
Debug调试模式
虽然 run() 方法适用于启动本地的开发服务器,但是你每次修改代码后都要手动重启它。这样并不够优雅,而且 Flask 可以做到更好。如果你启用了调试支持,服务器会在代码修改后自动重新载入,并在发生错误时提供一个相当有用的调试器。
有两种途径来启用调试模式。一种是直接在应用对象上设置:
1 app.debug = True 2 app.run()
另一种是作为 run 方法的一个参数传入:
1 app.run(debug=True)
路由
1 @app.route('/hello') 2 def hello(): 3 return "Hello World!" 4 5 @app.route('/') # 定义路由(views),可以理解为定义页面的url 6 def index(): 7 return "Index Page" # 渲染页面
可以构造含有动态部分的 URL,也可以在一个函数上附着多个规则。
变量规则
要给 URL 添加变量部分,你可以把这些特殊的字段标记为 <variable_name> , 这个部分将会作为命名参数传递到你的函数。规则可以用 <converter:variable_name> 指定一个可选的转换器
1 # 动态的URL,更多规则 2 @app.route('/user/<username>') 3 def show_user_profile(username): 4 return "User %s" % username 5 6 @app.route('/post/<int:post_id>') 7 def show_post(post_id): 8 return "Post %s" % post_id
转换器有下面几种:
唯一 URL / 重定向行为
Flask 的 URL 规则基于 Werkzeug 的路由模块。这个模块背后的思想是基于 Apache 以及更早的 HTTP 服务器主张的先例,保证优雅且唯一的 URL。
以这两个规则为例:
1 @app.route('/projects/') 2 def projects(): 3 return "The project page" 4 5 @app.route('/about') 6 def about(): 7 return 'The about page'
虽然它们看起来着实相似,但它们结尾斜线的使用在 URL 定义 中不同。 第一种情况中,指向 projects 的规范 URL 尾端有一个斜线。这种感觉很像在文件系统中的文件夹。访问一个结尾不带斜线的 URL 会被 Flask 重定向到带斜线的规范 URL 去。
然而,第二种情况的 URL 结尾不带斜线,类似 UNIX-like 系统下的文件的路径名。访问结尾带斜线的 URL 会产生一个 404 “Not Found” 错误。
这个行为使得在遗忘尾斜线时,允许关联的 URL 接任工作,与 Apache 和其它的服务器的行为并无二异。此外,也保证了 URL 的唯一,有助于避免搜索引擎索引同一个页面两次。
构造 URL
如果 Flask 能匹配 URL,那么 Flask 可以生成它们吗?当然可以。你可以用 url_for()来给指定的函数构造 URL。
它接受函数名作为第一个参数,也接受对应 URL 规则的变量部分的命名参数。未知变量部分会添加到 URL 末尾作为查询参数
python的shell进行交互:
1 @app.route('/') # 定义路由(views),可以理解为定义页面的url 2 def index(): 3 return "Index Page" # 渲染页面 4 5 @app.route('/login', methods=['GET', 'POST']) 6 def login(): 7 print('path',request.path) 8 print('headers', request.headers) 9 print('method', request.method) 10 print('url', request.url) 11 print('data', request.data) 12 13 if request.method == 'POST': 14 return "POST" 15 else: 16 return "GET" 17 18 @app.route('/user/<username>') 19 def profile(username): 20 pass 21 22 from flask import url_for 23 24 with app.test_request_context(): 25 print(url_for('index')) 26 print(url_for('login')) 27 print(url_for('login', next='/')) 28 print(url_for('profile', username='John Doe')) 29 # 30 # / 31 # /login 32 # /login?next=%2F 33 # /user/John%20Doe
内部运行:
1 @app.route('/login', methods=['GET', 'POST']) 2 def login(): 3 print(url_for('login', name='hello', age=18, id=10)) #/login?name=hello&age=18&id=10 4 print('path',request.path) 5 print('headers', request.headers) 6 print('method', request.method) 7 print('url', request.url) 8 print('data', request.data) 9 10 if request.method == 'POST': 11 return "POST" 12 else: 13 return "GET"
为什么你要构建 URL 而非在模板中硬编码?这里有三个绝妙的理由:
- 反向构建通常比硬编码的描述性更好。更重要的是,它允许你一次性修改 URL, 而不是到处边找边改。
- URL 构建会转义特殊字符和 Unicode 数据,免去你很多麻烦。
- 如果你的应用不位于 URL 的根路径(比如,在 /myapplication 下,而不是 / ), url_for() 会妥善处理这个问题。
HTTP 方法
HTTP (与 Web 应用会话的协议)有许多不同的访问 URL 方法。默认情况下,路由只回应 GET 请求,但是通过 route() 装饰器传递 methods 参数可以改变这个行为。这里有一些
1 from flask.views import request
1 @app.route('/login', methods=['GET', 'POST']) 2 def login(): 3 print('path',request.path) 4 print('headers', request.headers) 5 print('method', request.method) 6 print('url', request.url) 7 print('data', request.data) 8 9 if request.method == 'POST': 10 return "POST" 11 else: 12 return "GET"
如果存在 GET ,那么也会替你自动地添加 HEAD,无需干预。它会确保遵照 HTTP RFC(描述 HTTP 协议的文档)处理 HEAD 请求,所以你可以完全忽略这部分的 HTTP 规范。同样,自从 Flask 0.6 起, 也实现了 OPTIONS 的自动处理。
HTTP 方法(也经常被叫做“谓词”)告知服务器,客户端想对请求的页面 做 些什么。下面的都是非常常见的方法:
GET浏览器告知服务器:只 获取 页面上的信息并发给我。这是最常用的方法。
HEAD浏览器告诉服务器:欲获取信息,但是只关心 消息头 。应用应像处理 GET 请求一样来处理它,但是不分发实际内容。在 Flask 中你完全无需 人工 干预,底层的 Werkzeug 库已经替你打点好了。
POST浏览器告诉服务器:想在 URL 上 发布 新信息。并且,服务器必须确保 数据已存储且仅存储一次。这是 HTML 表单通常发送数据到服务器的方法。
PUT类似 POST 但是服务器可能触发了存储过程多次,多次覆盖掉旧值。你可 能会问这有什么用,当然这是有原因的。考虑到传输中连接可能会丢失,在 这种 情况下浏览器和服务器之间的系统可能安全地第二次接收请求,而 不破坏其它东西。因为 POST它只触发一次,所以用 POST 是不可能的。
DELETE删除给定位置的信息。
OPTIONS给客户端提供一个敏捷的途径来弄清这个 URL 支持哪些 HTTP 方法。 从 Flask 0.6 开始,实现了自动处理。
静态文件
动态 web 应用也会需要静态文件,通常是 CSS 和 JavaScript 文件。理想状况下, 你已经配置好 Web 服务器来提供静态文件,但是在开发中,Flask 也可以做到。 只要在你的包中或是模块的所在目录中创建一个名为 static 的文件夹,在应用中使用 /static 即可访问。
给静态文件生成 URL ,使用特殊的 'static' 端点名:
1 from flask import render_template 2 3 @app.route('/hello') 4 def hello(): 5 demo_css = url_for('static', filename='css/style.css') 6 return render_template('hello.html', demo_css=demo_css)
两种html导入css的方法:
模板渲染
可以使用 render_template() 方法来渲染模板。你需要做的一切就是将模板名和你想作为关键字的参数传入模板的变量。这里有一个展示如何渲染模板的简例:
1 from flask import render_template 2 3 @app.route('/user/<username>') 4 def profile(username): 5 return render_template('hello.html', username=username)
修改存放模板的文件夹:在 Flask(__name__) 修改代码为 Flask(__name__,template_folder=”存放模板文件夹名称”)
在模板里,你也可以访问 request 、 session 和 g [1] 对象, 以及 get_flashed_messages() 函数。
模板继承让模板用起来相当顺手。如欲了解继承的工作机理,请跳转到 模板继承 模式的文档。最起码,模板继承能使特定元素 (比如页眉、导航栏和页脚)可以出现在所有的页面。
自动转义功能默认是开启的,所以如果 name 包含 HTML ,它将会被自动转义。如果你能信任一个变量,并且你知道它是安全的(例如一个模块把 Wiki 标记转换为 HTML),你可以用 Markup 类或 |safe 过滤器在模板中把它标记为安全的
这里是一个 Markup 类如何使用的简单介绍:
1 >>> from flask import Markup 2 >>> Markup('<strong>Hello %s!</strong>') % '<blink>hacker</blink>' 3 Markup(u'<strong>Hello <blink>hacker</blink>!</strong>') 4 >>> Markup.escape('<blink>hacker</blink>') 5 Markup(u'<blink>hacker</blink>') 6 >>> Markup('<em>Marked up</em> » HTML').striptags() 7 u'Marked up \xbb HTML'
请求对象
当前请求的 HTTP 方法可通过 method 属性来访问。通过:attr:~flask.request.form 属性来访问表单数据( POST 或 PUT 请求提交的数据)。
1 from flask import request
1 @app.route('/login', methods=['POST', 'GET']) 2 def login(): 3 error = None 4 if request.method == 'POST': 5 if valid_login(request.form['username'], 6 request.form['password']): 7 return log_the_user_in(request.form['username']) 8 else: 9 error = 'Invalid username/password' 10 # the code below is executed if the request method 11 # was GET or the credentials were invalid 12 return render_template('login.html', error=error)
当访问 form 属性中的不存在的键会发生什么?会抛出一个特殊的 KeyError 异常。你可以像捕获标准的 KeyError 一样来捕获它。 如果你不这么做,它会显示一个 HTTP 400 Bad Request 错误页面。所以,多数情况下你并不需要干预这个行为。
你可以通过 args 属性来访问 URL 中提交的参数 ( ?key=value )
1 searchword = request.args.get('q', '')
我们推荐用 get 来访问 URL 参数或捕获 KeyError ,因为用户可能会修改 URL,向他们展现一个 400 bad request 页面会影响用户体验。
欲获取请求对象的完整方法和属性清单,请参阅 request 的文档。
文件上传
用 Flask 处理文件上传很简单。只要确保你没忘记在 HTML 表单中设置enctype="multipart/form-data" 属性,不然你的浏览器根本不会发送文件。
已上传的文件存储在内存或是文件系统中一个临时的位置。你可以通过请求对象的 files属性访问它们。每个上传的文件都会存储在这个字典里。它表现近乎为一个标准的 Python file 对象,但它还有一个 save() 方法,这个方法允许你把文件保存到服务器的文件系统上。这里是一个用它保存文件的例子:
1 from flask import request 2 3 @app.route('/upload', methods=['GET', 'POST']) 4 def upload_file(): 5 if request.method == 'POST': 6 f = request.files['the_file'] 7 f.save('/var/www/uploads/uploaded_file.txt')
如果你想知道上传前文件在客户端的文件名是什么,你可以访问 filename 属性。但请记住, 永远不要信任这个值,这个值是可以伪造的。如果你要把文件按客户端提供的文件名存储在服务器上,那么请把它传递给 Werkzeug 提供的 secure_filename() 函数:
1 from flask import request 2 from werkzeug import secure_filename 3 4 @app.route('/upload', methods=['GET', 'POST']) 5 def upload_file(): 6 if request.method == 'POST': 7 f = request.files['the_file'] 8 f.save('/var/www/uploads/' + secure_filename(f.filename))
一些更好的例子,见 上传文件 模式。
Cookies
你可以通过 cookies 属性来访问 Cookies,用响应对象的 set_cookie 方法来设置 Cookies。请求对象的 cookies 属性是一个内容为客户端提交的所有 Cookies 的字典。如果你想使用会话,请不要直接使用 Cookies,请参考 会话 一节
读取 cookies:
1 from flask import request 2 3 @app.route('/') 4 def index(): 5 username = request.cookies.get('username') 6 # use cookies.get(key) instead of cookies[key] to not get a 7 # KeyError if the cookie is missing.
存储 cookies:
1 from flask import make_response 2 3 @app.route('/') 4 def index(): 5 resp = make_response(render_template(...)) 6 resp.set_cookie('username', 'the username') 7 return resp
可注意到的是,Cookies 是设置在响应对象上的。由于通常视图函数只是返回字符串,之后 Flask 将字符串转换为响应对象。如果你要显式地转换,你可以使用 make_response()函数然后再进行修改。
有时候你想设置 Cookie,但响应对象不能存在。这可以利用 延迟请求回调 模式实现。
为此,也可以阅读 关于响应 。
重定向和错误
你可以用 redirect() 函数把用户重定向到其它地方。放弃请求并返回错误代码,用 abort() 函数
1 from flask import abort, redirect, url_for 2 3 @app.route('/') 4 def index(): 5 return redirect(url_for('login')) 6 7 @app.route('/login') 8 def login(): 9 abort(401) 10 this_is_never_executed()
这是一个相当无意义的例子因为用户会从主页重定向到一个不能访问的页面 (401 意味着禁止访问),但是它展示了重定向是如何工作的。
默认情况下,错误代码会显示一个黑白的错误页面。如果你要定制错误页面, 可以使用 errorhandler() 装饰器:
1 from flask import render_template 2 3 @app.errorhandler(404) 4 def page_not_found(error): 5 return render_template('page_not_found.html'), 404
注意 render_template() 调用之后的 404 。这告诉 Flask,该页的错误代码是 404 ,即没有找到。默认为 200,也就是一切正常。
关于响应
视图函数的返回值会被自动转换为一个响应对象。如果返回值是一个字符串, 它被转换为该字符串为主体的、状态码为 200 OK``的 、 MIME 类型是 ``text/html 的响应对象。
Flask 把返回值转换为响应对象的逻辑是这样:
- 如果返回的是一个合法的响应对象,它会从视图直接返回。
- 如果返回的是一个字符串,响应对象会用字符串数据和默认参数创建。
- 如果返回的是一个元组,且元组中的元素可以提供额外的信息。这样的元组必须是 (response, status, headers) 的形式,且至少包含一个元素。 status 值会覆盖状态代码, headers 可以是一个列表或字典,作为额外的消息标头值。
- 如果上述条件均不满足, Flask 会假设返回值是一个合法的 WSGI 应用程序,并转换为一个请求对象。
如果你想在视图里操纵上述步骤结果的响应对象,可以使用 make_response() 函数。
1 @app.errorhandler(404) 2 def not_found(error): 3 return render_template('error.html'), 404
你只需要把返回值表达式传递给 make_response() ,获取结果对象并修改,然后再返回它:
1 @app.errorhandler(404) 2 def not_found(error): 3 resp = make_response(render_template('error.html'), 404) 4 resp.headers['X-Something'] = 'A value' 5 return resp
会话
除请求对象之外,还有一个 session 对象。它允许你在不同请求间存储特定用户的信息。它是在 Cookies 的基础上实现的,并且对 Cookies 进行密钥签名。这意味着用户可以查看你 Cookie 的内容,但却不能修改它,除非用户知道签名的密钥。
要使用会话,你需要设置一个密钥
1 from flask import Flask, session, redirect, url_for, escape, request 2 3 app = Flask(__name__) 4 5 @app.route('/') 6 def index(): 7 if 'username' in session: 8 return 'Logged in as %s' % escape(session['username']) 9 return 'You are not logged in' 10 11 @app.route('/login', methods=['GET', 'POST']) 12 def login(): 13 if request.method == 'POST': 14 session['username'] = request.form['username'] 15 return redirect(url_for('index')) 16 return ''' 17 <form action="" method="post"> 18 <p><input type=text name=username> 19 <p><input type=submit value=Login> 20 </form> 21 ''' 22 23 @app.route('/logout') 24 def logout(): 25 # remove the username from the session if it's there 26 session.pop('username', None) 27 return redirect(url_for('index')) 28 29 # set the secret key. keep this really secret: 30 app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT'
这里提到的 escape() 可以在你模板引擎外做转义
如何生成强壮的密钥
随机的问题在于很难判断什么是真随机。一个密钥应该足够随机。你的操作系统可以基于一个密钥随机生成器来生成漂亮的随机值,这个值可以用来做密钥:】
1 >>> import os 2 >>> os.urandom(24) 3 '\xfd{H\xe5<\x95\xf9\xe3\x96.5\xd1\x01O<!\xd5\xa2\xa0\x9fR"\xa1\xa8'
Flask 会将你放进会话对象的值序列化至 Cookies
日志记录
有时候你会处于这样一种境地,你处理的数据本应该是正确的,但实际上不是。 比如,你会有一些向服务器发送请求的客户端代码,但请求显然是畸形的。这可能是用户篡改了数据,或是客户端代码的粗制滥造。大多数情况下,正常地返回 400 Bad Request 就可以了,但是有时候不能这么做,并且要让代码继续运行。
你可能依然想要记录下,是什么不对劲。这时日志记录就派上了用场。从 Flask 0.3 开始,Flask 就已经预置了日志系统。
这里有一些调用日志记录的例子:
1 app.logger.debug('A value for debugging') 2 app.logger.warning('A warning occurred (%d apples)', 42) 3 app.logger.error('An error occurred')
附带的 logger 是一个标准日志类 Logger ,所以更多信息请查阅 logging 的文档 。