路由
Flask 只有 route()装饰器把视图函数绑定到 url 上面.
- @App.route('/user')
- def hello_user():
- return 'Hello, user!'
另外我们也可以指定动态 url.
通过把 URL 的一部分标记为 <variable_name> 就可以在 URL 中添加变量. 标记的 部分会作为关键字参数传递给函数.
- # 比如获取一个用户详情的 url
- @App.route('/user/<id>/')
- def hello_user(id):
- return 'Hello, userid: {0}!'.format(id)
- <variable_name>
- 默认是字符串, 如果我们需要指定参数类型可以通过:
- <converter:variable_name>
Flask 执行的转换器类型:
唯一 Url
Flask 的 URL 规则基于 Werkzeug 的路由模块, 主张保证优雅且唯一的 URL.
以上述的例子为例如果你访问
http://0.0.0.0:9999/user/2
我们定义的路由是'/user//', 如果访问一个结尾不带 / 的 URL 会被重定向到带斜杠的 URL 上去, 这样可以保持 URL 唯一, 并帮助 搜索引擎避免重复索引同一页面.
构造 URL
使用 url_for()构建 url, 它接受函数名字作为第一个参数, 也接受对应 URL 规则的变量部分的命名参数, 未知的变量部分会添加到 URL 末尾作为查询参数.
- from flask import Flask, url_for
- App = Flask(__name__)
- App.config['DEBUG'] = True
- @App.route('/user/<int:id>')
- def user(id):
- pass
- # test_request_context() 告诉 Flask 正在处理一个请求
- with App.test_request_context():
- print(url_for('user', id=2))
- print(url_for('user', id=3, type='doctor'))
- if __name__ == '__main__':
- App.run(host='0.0.0.0', port='9999')
输出:
- /user/2
- /user/3?type=doctor
为什么不在把 URL 写死在模板中, 而要使用反转函数 url_for() 动态构建?
1. 反转通常比硬编码 URL 的描述性更好.
2. 未来更过 URL, 你可以只在一个地方改变 URL, 而不用到处替换.
3. URL 创建会为你处理特殊字符的转义和 Unicode 数据, 比较直观.
4. 生产的路径总是绝对路径, 可以避免相对路径产生副作用.
5. 如果你的应用是放在 URL 根路径之外的地方(如在 /myapplication 中, 不在 / 中), url_for() 会为你妥善处理.
跳转与重定向
跳转 (301) 多用于旧网址在废弃之前转向新网址保证用户的访问, 有页面被永久性易走的概念.
重定向 (302) 表示页面只是暂时的转移, 不建议经常性使用重定向.
- redirect(location) #默认是 302
- redirect(location, code=301) # 可以指定 code
- from flask import Flask, url_for, render_template, redirect
- App = Flask(__name__)
- App.config['DEBUG'] = True
- @App.route('/')
- def index():
- return redirect(url_for('login'))
- @App.route('/login')
- def login():
- return render_template('login.html')
- if __name__ == '__main__':
- App.run(host='0.0.0.0', port='9999')
HTTP 方法
web 应用使用不同的 HTTP 方法处理 URL, 默认情况下, 一个路由只回应 GET 请求. 可以使用 route() 装饰器的 methods 参数来处理不同的 HTTP 方法:
@App.route('/login', methods=['GET', 'POST'])
简单介绍下常见的 HTTP 方法与使用场景:
- GET: 获取资源, GET 操作应该是幂等的
- HEAD: 想要获取信息, 但是只关心消息头, 应该可以把它当作 GET 来处理, 但是不返回内容. 具有幂等性
- POST: 创建一个资源, 非幂等方法
- PUT: 完整的替换资源或者创建资源, 是幂等方法
- DELETE: 删除资源, 是幂等方法
- OPTIONS: 获取资源所支持的所有 HTTP 方法
- PATCH: 局部更新, 非幂等方法
HTTP 幂等方法, 是指无论调用多少次都不会有不同结果的 HTTP 方法. 不管你调用一次, 还是调用一百次, 一千次, 结果都是相同的.
关于如何理解 RESTful 的幂等性.
在视图函数里面我们可以使用如下方式判断 HTTP 请求方法
- from flask import Flask, request
- request.method # 获取当前请求的方法
静态文件
动态的 Web 应用也需要静态文件, 一般是 CSS 和 JavaScript 文件. 理想情况下你的 服务器已经配置好了为你的提供静态文件的服务. 但是在开发过程中, Flask 也能做好 这项工作. 只要在你的包或模块旁边创建一个名为 static 的文件夹就行了. 静态文件位于应用的 /static 中.
在实例化 Flask 时候如果你查看源码你会发现
- App = Flask(__name__)
- def __init__(
- self,
- import_name,
- static_url_path=None,
- static_folder='static',
- static_host=None,
- host_matching=False,
- subdomain_matching=False,
- template_folder='templates',
- instance_path=None,
- instance_relative_config=False,
- root_path=None
- ):
static 为默认的静态文件的目录.
- # 这样是可以直接访问到静态文件的
- http://0.0.0.0:9999/static/app.css
我们不建议在模板里面直接写死静态文件路径, 应该使用 url_for()生成路径
url_for('static', filename='app.css')
当然我们也可以定制静态文件的真实目录
App = Flask(__name__, static_folder='/tmp')
渲染模板
Flask 默认提供的是 Jinja2 模板引擎.
使用 render_template() 方法可以渲染模板, 你只要提供模板名称和需要 作为参数传递给模板的变量就行了. 下面是一个简单的模板渲染例子:
templates 是 Flask 默认的目录名字.
- @App.route('/')
- def index():
- return render_template('index.html', title='首页')
- <!DOCTYPE HTML>
- <HTML lang="en">
- <head>
- <meta charset="UTF-8">
- <title>
- {{title}}
- </title>
- </head>
- <body>
- </body>
- </HTML>
在模板内部可以和访问 get_flashed_messages() 函数一样访问 request , session 和 g 对象[g 保存的是当前请求的全局变量, 不同的请求会有不同的全局变量].
Jinja2 文档 http://docs.jinkan.org/docs/jinja2/ 这里就不细说了, 后面有空单独写一篇, 现在前后分离, 已经比较少使用模板了.
操作 request 对象
在 Flask 中由全局对象 request 来提供请求信息. 如果你有一些 Python 基础, 那么 可能 会奇怪: 既然这个对象是全局的, 怎么还能保持线程安全?
from flask import request
某些对象在 Flask 中是全局对象, 但不是通常意义下的全局对象. 这些对象实际上是 特定环境下本地对象的代理. 真拗口! 但还是很容易理解的.
从一个 Flask App 读入配置并启动开始, 就进入了 App Context, 在其中我们可以访问配置文件, 打开资源文件, 通过路由规则反向构造 URL. 当一个请求进入开始被处理时, 就进入了 Request Context, 在其中我们可以访问请求携带的信息, 比如 HTTP Method, 表单域等.
request 虽然是全局变量但是只是一个代理, 想深挖细节的可以看看这个文章 Flask 的 Context 机制.
理解 Flask 的 request 上下文.
- 通过使用 method 属性可以操作当前请求方法
- 通过使用 form 属性处理表单数据(在 POST 或者 PUT 请求 中传输的数据), 当 form 属性中不存在这个键时会发生什么? 会引发一个 KeyError .
如果你不像捕捉一个标准错误一样捕捉 KeyError , 那么会显示一个 HTTP 400 Bad Request 错误页面.
- 要操作 URL (如 ?key=value )中提交的参数可以使用 args 属性:
searchword = request.args.get('key', '')
响应
视图函数的返回值会被转为一个响应对象.
比如:
- # 你会发现页面里面没有文字显示, 只有 h3 标签
- @App.route('/')
- def index():
- return '<h3></h3>'
你查看 App.route()源码会发现
- # route 装饰器只是一个语法糖, 实际执行的还是 add_url_rule()
- def decorator(f):
- endpoint = options.pop('endpoint', None)
- self.add_url_rule(rule, endpoint, f, **options)
- return f
- return decorator
转化逻辑如下:
1. 如果返回的是一个合法的响应对象, 它会从视图直接返回
2. 如果返回的是一个字符串, 会用字符串数据和默认参数来创建以字符串为主体, 状态码为 200,MIME 类型的 text.HTML 的 werkzeug.wrappers.Response 响应对象.
3. 如果返回的是一个元组, 且元组的元素可以提供额外的信息, 这样的元组格式为(response, status, headers). 它们会覆盖默认的配置.
4. 如果上述条件都不对, Flask 会假设返回值是一个合法的 WSGI 应用程序, 并通过 Response.force_type(rv, request.environ)转化为请求对象
实例:
- @App.route('/')
- def index():
- return render_template('index.html'), 400
我们也可以显示使用 make_response 方法
- from flask import Flask, url_for, render_template, redirect, request, make_response
- App = Flask(__name__)
- App.config['DEBUG'] = True
- @App.route('/')
- def index():
- response = make_response(render_template('index.html'), 400)
- return response
使用 make_response 方法我们可以设置 cookie, 头信息等.
查看 make_response 方法源码其实也很好理解.
- def make_response(*args):
- # 根据 args 传参情况进行不同处理
- if not args:
- return current_app.response_class()
- if len(args) == 1:
- args = args[0]
- return current_app.make_response(args)
- def make_response(self, rv):
- status = headers = None
- # unpack tuple returns
- # 传入的是元组
- if isinstance(rv, tuple):
- len_rv = len(rv)
- # 格式(response, status, headers)
- if len_rv == 3:
- rv, status, headers = rv
- # decide if a 2-tuple has status or headers
- elif len_rv == 2:
- if isinstance(rv[1], (Headers, dict, tuple, list)):
- rv, headers = rv
- else:
- rv, status = rv
- # other sized tuples are not allowed
- else:
- raise TypeError(
- 'The view function did not return a valid response tuple.'
- 'The tuple must have the form (body, status, headers),'
- '(body, status), or (body, headers).'
- )
- # the body must not be None
- if rv is None:
- raise TypeError(
- 'The view function did not return a valid response. The'
- 'function either returned None or ended without a return'
- 'statement.'
- )
- # make sure the body is an instance of the response class
- if not isinstance(rv, self.response_class):
- if isinstance(rv, (text_type, bytes, bytearray)):
- # let the response class set the status and headers instead of
- # waiting to do it manually, so that the class can handle any
- # special logic
- rv = self.response_class(rv, status=status, headers=headers)
- status = headers = None
- else:
- # evaluate a WSGI callable, or coerce a different response
- # class to the correct type
- try:
- rv = self.response_class.force_type(rv, request.environ)
- except TypeError as e:
- new_error = TypeError(
- '{e}\nThe view function did not return a valid'
- 'response. The return type must be a string, tuple,'
- 'Response instance, or WSGI callable, but it was a'
- '{rv.__class__.__name__}.'.format(e=e, rv=rv)
- )
- reraise(TypeError, new_error, sys.exc_info()[2])
- # prefer the status if it was provided
- if status is not None:
- if isinstance(status, (text_type, bytes, bytearray)):
- rv.status = status
- else:
- rv.status_code = status
- # extend existing headers with provided headers
- if headers:
- rv.headers.extend(headers)
- return rv
来源: https://juejin.im/post/5c446af06fb9a049a62ce364