现代 web 应用都使用有意义的 URL ,这样有助于用户记忆,网页会更得到用户的青睐, 提高回头率。
在Flask中可以使用 route() 装饰器来把函数绑定到 URL。
定义一个简单的路由:
@app.route('/')
def hello_world():
return 'Hello World!'
此时我们在浏览器中访问的地址就是:http://127.0.0.1:5000/
我们可以通过把 URL 的一部分标记为 <variable_name> 就可以在 URL 中添加变量。标记的 部分会作为关键字参数传递给函数。
此时函数参数的变量名必须和variable_name名称一致。
@app.route('/user/<username>')
def get_user(username):
return f"接收到的用户名为:{username}"
浏览器访问该地址传入参数username:
其中f’{username}'语法为3.6后的新特性等价于’ {username} '.format(username=username)
默认情况下,服务器只会监听Get请求,我们可以使用postman进行模拟测试:
状态码 405 Method Not Allowed 表明服务器禁止了使用当前 HTTP 方法的请求。也就是说此时服务器是不支持该访问使用post方式的。那么如何制定请求方式呢?
def route(self, rule, **options):
"""A decorator that is used to register a view function for a
given URL rule. This does the same thing as :meth:`add_url_rule`
but is intended for decorator usage::
@app.route('/')
def index():
return 'Hello World'
For more information refer to :ref:`url-route-registrations`.
:param rule: the URL rule as string
:param endpoint: the endpoint for the registered URL rule. Flask
itself assumes the name of the view function as
endpoint
:param options: the options to be forwarded to the underlying
:class:`~werkzeug.routing.Rule` object. A change
to Werkzeug is handling of method options. methods
is a list of methods this rule should be limited
to (``GET``, ``POST`` etc.). By default a rule
just listens for ``GET`` (and implicitly ``HEAD``).
Starting with Flask 0.6, ``OPTIONS`` is implicitly
added and handled by the standard request handling.
"""
def decorator(f):
endpoint = options.pop("endpoint", None)
self.add_url_rule(rule, endpoint, f, **options)
return f
return decorator
上面是flask源码,可以看到我们可以使用数组来制定支持的请求。
可以看到,其中的options参数为werkzeug.routing.Rule对象,那么我们在一起来看看这个对象有哪些属性可以设置:
def __init__(
self,
string,
defaults=None,
subdomain=None,
methods=None,
build_only=False,
endpoint=None,
strict_slashes=None,
merge_slashes=None,
redirect_to=None,
alias=False,
host=None,
websocket=False,
):
这里截取初始化函数的一段代码,可以看到确实有methods属性。看到这里大家好应该知道有哪些东西可以在这里作为参数了。
那么对于开发Restful API规范来说,我们常用的有:
所以我们可以这样来指定请求方法:
@app.route('/user/<username>', methods=['GET', 'POST','PUT','DELETE'])
其中’GET’是不区分大小写的,其内部做了转换处理:
methods = set(item.upper() for item in methods)
其中上述写法为python3中的列表推导式写法,适合用于简单的操作。格式为:
格式:[表达式 for 变量 in 旧列表] or [表达式 for 变量 in 旧列表 if 条件]
列表推导式提供了从序列创建列表的简单途径。通常应用程序将一些操作应用于某个序列的每个元素,用其获得的结果作为生成新列表的元素,或者根据确定的判定条件创建子序列。
每个列表推导式都在 for 之后跟一个表达式,然后有零到多个 for 或 if 子句。返回结果是一个根据表达从其后的 for 和 if 上下文环境中生成出来的列表。如果希望表达式推导出一个元组,就必须使用括号。
此时我们就可以通过post等方法访问:http://127.0.0.1:5000/user/zhangsan
- 注意这里URL中的变量可以理解为正则匹配,变量之间要有明确的区分,这样才会得到期望的结果,如
@app.route('/user/<username><password>/')
可能得到让你预料不到的结果。
转化器
类型 | 描述 |
---|---|
string | (缺省值) 接受任何不包含斜杠的文本 |
int | 接受正整数 |
float | 接受正浮点数 |
path | 类似 string ,但可以包含斜杠 |
uuid | 接受 UUID 字符串 |
正则表达式如果还有不太明白的可参阅官方文档:https://docs.python.org/zh-cn/3/howto/regex.html#regex-howto
一个简单的例子:
@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
如果我们传入非数字类型的参数,此时会出现404错误:
接下来我们看下Flask源码中关于转化器是怎么实现的,其位于\venv\Lib\site-packages\werkzeug\routing.py
首先其内置的转化器有:
#: the default converter mapping for the map.
DEFAULT_CONVERTERS = {
"default": UnicodeConverter,
"string": UnicodeConverter,
"any": AnyConverter,
"path": PathConverter,
"int": IntegerConverter,
"float": FloatConverter,
"uuid": UUIDConverter,
}
我们在看下UnicodeConverter转化器的实现:
class BaseConverter(object):
"""Base class for all converters."""
regex = "[^/]+"
weight = 100
def __init__(self, map):
self.map = map
def to_python(self, value):
return value
def to_url(self, value):
if isinstance(value, (bytes, bytearray)):
return _fast_url_quote(value)
return _fast_url_quote(text_type(value).encode(self.map.charset))
class UnicodeConverter(BaseConverter):
"""This converter is the default converter and accepts any string but
only one path segment. Thus the string can not include a slash.
This is the default validator.
Example::
Rule('/pages/<page>'),
Rule('/<string(length=2):lang_code>')
:param map: the :class:`Map`.
:param minlength: the minimum length of the string. Must be greater
or equal 1.
:param maxlength: the maximum length of the string.
:param length: the exact length of the string.
"""
def __init__(self, map, minlength=1, maxlength=None, length=None):
BaseConverter.__init__(self, map)
if length is not None:
length = "{%d}" % int(length)
else:
if maxlength is None:
maxlength = ""
else:
maxlength = int(maxlength)
length = "{%s,%s}" % (int(minlength), maxlength)
self.regex = "[^/]" + length
class IntegerConverter(NumberConverter):
"""This converter only accepts integer values::
Rule("/page/<int:page>")
By default it only accepts unsigned, positive values. The ``signed``
parameter will enable signed, negative values. ::
Rule("/page/<int(signed=True):page>")
:param map: The :class:`Map`.
:param fixed_digits: The number of fixed digits in the URL. If you
set this to ``4`` for example, the rule will only match if the
URL looks like ``/0001/``. The default is variable length.
:param min: The minimal value.
:param max: The maximal value.
:param signed: Allow signed (negative) values.
.. versionadded:: 0.15
The ``signed`` parameter.
"""
regex = r"\d+"
num_convert = int
详细的代码我们了解一下即可,如果在实际的项目开发中遇到内置的转化器无法满足需求的时候,我们就可以考虑使用自定义的转化器。参照上面的代码可以知道,我们首先需要定义一个自己的转化器类,继承自BaseConverter,最简单的方式可以参考IntegerConverter转化器,只要我们重新指定regex 的正则表达式即可:
# 匹配11位数字的手机号码
class PhoneConverter(BaseConverter):
regex = r'1[85734]\d{9}'
虽然我们自定义了我们的转化器,但是必须的告诉Flask,这是我们的转化器,你的按照我的规则来。此时我们需要将自己的转化器注册到url_map中去。
在Flask类的定义中有url_map_class 属性,其初始值为werkzeug.routing.Map类型:
#: The map object to use for storing the URL rules and routing
#: configuration parameters. Defaults to :class:`werkzeug.routing.Map`.
#:
#: .. versionadded:: 1.1.0
url_map_class = Map
werkzeug.routing.Map类:
...
def __init__(
self,
rules=None,
default_subdomain="",
charset="utf-8",
strict_slashes=True,
merge_slashes=True,
redirect_defaults=True,
converters=None,
sort_parameters=False,
sort_key=None,
encoding_errors="replace",
host_matching=False,
):
...
:param converters: A dict of converters that adds additional converters
to the list of converters. If you redefine one
converter this will override the original one.
大家可以清楚的看到其中有一个converters的属性,这个就是我们需要的。该属性是一个字典,所以添加自定义的转化器也就很简单了。
# 匹配11位数字的手机号码
class PhoneConverter(BaseConverter):
regex = r'1[85734]\d{9}'
app.url_map.converters['phone'] = PhoneConverter
@app.route('/')
def hello_world():
return 'Hello World!'
@app.route('/user/<phone:phone_number>')
def get_user_by_phone(phone_number):
# show the post with the given id, the id is an integer
return 'User phone number is : %s' % phone_number
唯一的 URL / 重定向行为
在Flask中我们需要注意这样的一条规则:是否使用尾部的斜杠。看个示例:
@app.route('/user/<phone:phone_number>')
def get_user_by_phone(phone_number):
# show the post with the given id, the id is an integer
return 'User phone number is : %s' % phone_number
@app.route('/admin/<phone:phone_number>/')
def get_adin_user_by_phone(phone_number):
# show the post with the given id, the id is an integer
return 'Admin User phone number is : %s' % phone_number
这两个URL唯一不同的地方就在于/admin/<phone:phone_number>/
使用了尾部斜杠。在Flask里面访问一个没有斜杠结尾的 URL 时 Flask 会自动进行重定向,帮你在尾部加上一个斜杠。前提是你定义路由的时候在尾部添加了斜杠。
/user/<phone:phone_number>
的 URL 没有尾部斜杠,因此其行为表现与一个文件类似。如果访问这个 URL 时添加了尾部斜杠就会得到一个 404 错误。这样可以保持 URL 唯一,并帮助 搜索引擎避免重复索引同一页面。
URL 构建
url_for() 函数用于构建指定函数的 URL。它把函数名称作为第一个 参数。它可以接受任意个关键字参数,每个关键字参数对应 URL 中的变量。未知变量 将添加到 URL 中作为查询参数。
为什么不在把 URL 写死在模板中,而要使用反转函数 url_for() 动态构建?
- 反转通常比硬编码 URL 的描述性更好。
- 你可以只在一个地方改变 URL ,而不用到处乱找。
- URL 创建会为你处理特殊字符的转义和 Unicode 数据,比较直观。
- 生产的路径总是绝对路径,可以避免相对路径产生副作用。
- 如果你的应用是放在 URL 根路径之外的地方(如在 /myapplication 中,不在 / 中), url_for()
会为你妥善处理。
上示例:
@app.route('/')
def hello_world():
print(url_for('url_for_test'))
print(url_for('get_user_by_phone', phone_number='15928452154'))
print(url_for('get_admin_user_by_phone', phone_number='15928452154'))
return 'Hello World!'
app.route('/user/<phone:phone_number>')
def get_user_by_phone(phone_number):
# show the post with the given id, the id is an integer
return 'User phone number is : %s' % phone_number
@app.route('/admin/<phone:phone_number>/')
def get_admin_user_by_phone(phone_number):
# show the post with the given id, the id is an integer
return 'Admin User phone number is : %s' % phone_number
@app.route('/urlfor/')
def url_for_test():
return "url for test"
运行结果:
如果我们没有给get_user_by_phone指定其对应的参数,那么我们会得到如下错误信息:
也就是说url_for函数在使用时如果函数有参数,那么必须指定对应的参数。如果这个参数之前没有在’url’中定义,那么将变成查询字符串的形式放到’url’中。
print(url_for('get_user_by_phone',phone_number='15928452154',test='test_params'))
另外一个就是字符转义:
print(url_for('get_user_by_phone',phone_number='15928452154',test='test_params',email='liu@34.com'))