Flask04:路由与URL

现代 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'))

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值