Flask 应用中的 URL 处理

本文主要对Flask的URL处理功能进行分析。文章原载于个人技术博客,欢迎你们访问。水平有限,若有不当之处还请指正,谢谢~正则表达式

在文章 一个Flask应用运行过程剖析中,在一个上下文环境中能够处理请求。若是不考虑在处理请求先后作的一些操做,Flask源码中真正处理请求的是dispatch_request()方法。其源码以下:flask

def dispatch_request(self):
    """Does the request dispatching. Matches the URL and returns the return value of the view or error handler. This does not have to be a response object. In order to convert the return value to a proper response object, call :func:`make_response`. """
    try:
        endpoint, values = self.match_request()
        return self.view_functions[endpoint](**values)
    except HTTPException, e:
        handler = self.error_handlers.get(e.code)
        if handler is None:
            return e
        return handler(e)
    except Exception, e:
        handler = self.error_handlers.get(500)
        if self.debug or handler is None:
            raise
        return handler(e)复制代码

从上面的源码中能够看到,dispatch_request()方法作了以下的工做:app

  1. 对请求的URL进行匹配;函数

  2. 若是URL能够匹配,则返回相对应视图函数的结果;url

  3. 若是不能够匹配,则进行错误处理。spa

对于错误的处理,本文暂不作介绍。本文主要对Flask应用的URL模式以及请求处理过程当中的URL匹配进行剖析。debug

Flask应用的url_map

Flask应用实例化的时候,会为应用增添一个url_map属性。这个属性是一个Map类,这个类在werkzeug.routing模块中定义,其主要的功能是为了给应用增长一些URL规则,这些URL规则造成一个Map实例的过程当中会生成对应的正则表达式,能够进行URL匹配。相关的概念和内容能够参考:Werkzeug库——routing模块code

在Flask源码中,它经过两个方法能够很方便地定制应用的URL。这两个方法是:route装饰器和add_url_rule方法。get

1. add_url_rule

def add_url_rule(self, rule, endpoint, **options):
    options['endpoint'] = endpoint
    options.setdefault('methods', ('GET',))
    self.url_map.add(Rule(rule, **options))复制代码

add_url_rule方法很简单,只要向其传递一条URL规则rule和一个endpoint便可。endpoint通常为和这条URL相关的视图函数的名字,这样处理就能够将URL和视图函数关联起来。除此以外,还能够传递一些关键字参数。调用该方法后,会调用Map实例的add方法,它会将URL规则添加进Map实例中。源码

2. route装饰器

为了更加方便、优雅地写应用的URL,Flask实现了一个route装饰器。

def route(self, rule, **options):
    def decorator(f):
        self.add_url_rule(rule, f.__name__, **options)
        self.view_functions[f.__name__] = f
        return f
    return decorator复制代码

route装饰器会装饰一个视图函数。经route装饰的视图函数首先会调用add_url_rule方法,将装饰器中的URL规则添加进Map实例中,视图函数的名字会做为endpoint进行传递。而后在该应用的view_functions中增长endpoint和视图函数的对应关系。这种对应关系能够在请求成功时方便地调用对应的视图函数。

3. 一个简单的例子

咱们用一个简单的例子来讲明以上过程的实现:

>>> from flask import Flask
>>> app = Flask(__name__)
>>> @app.route('/')
    def index():
        return "Hello, World!"
>>> @app.route('/<username>')
    def user(username):
        return "Hello, %s" % username
>>> @app.route('/page/<int:id>')
    def page(id):
        return "This is page %d" % id复制代码

以上代码,咱们建立了一个Flask应用app,而且经过route装饰器的形式为app增长了3条URL规则。

首先: 咱们看一下Flask应用的url_map长啥样:

>>> url_map = app.url_map
>>> url_map

Map([<Rule '/' (HEAD, GET) -> index>,
     <Rule '/static/<filename>' -> static>,
     <Rule '/page/<id>' (HEAD, GET) -> page>,
     <Rule '/<username>' (HEAD, GET) -> user>
    ])复制代码

能够看到,url_map是一个Map实例,这个实例中包含4个Rule实例,分别对应4条URL规则,其中/static/<filename>在Flask应用实例化时会自动添加,其他3条是用户建立的。整个Map类便构成了Flask应用app的URL“地图”,能够用做URL匹配的依据。

接下来: 咱们看一下url_map中的一个属性:_rules_by_endpoint

>>> rules_by_endpoint = url_map._rules_by_endpoint
>>> rules_by_endpoint

{'index': [<Rule '/' (HEAD, GET) -> index>],
 'page': [<Rule '/page/<id>' (HEAD, GET) -> page>],
 'static': [<Rule '/static/<filename>' -> static>],
 'user': [<Rule '/<username>' (HEAD, GET) -> user>]
}复制代码

能够看出,_rules_by_endpoint属性是一个字典,反映了endpoint和URL规则的对应关系。因为用route装饰器建立URL规则时,会将视图函数的名字做为endpoint进行传递,因此以上字典的内容也反映了视图函数和URL规则的对应关系。

再接下来: 咱们看一下Flask应用的view_functions

>>> view_functions = app.view_functions
>>> view_functions

{'index': <function __main__.index>,
 'page': <function __main__.page>,
 'user': <function __main__.user>
}复制代码

在用route装饰器建立URL规则时,它还会作一件事情:self.view_functions[f.__name__] = f。这样作是将函数名和视图函数的对应关系放在Flask应用的view_functions。因为Map实例中存储了函数名和URL规则的对应关系,这样只要在匹配URL规则时,若是匹配成功,只要返回一个函数名,那么即可以在view_functions中运行对应的视图函数。

最后: 咱们看一下URL如何和Map实例中的URL规则进行匹配。咱们以/page/<int:id>这条规则为例:

>>> rule = url_map._rules[2]
>>> rule
<Rule '/page/<id>' (HEAD, GET) -> page>

>>> rule._regex
re.compile(ur'^\|\/page\/(?P<id>\d+)$', re.UNICODE)

>>> rule._regex.pattern
u'^\\|\\/page\\/(?P<id>\\d+)$'复制代码

能够看到,在将一条URL规则的实例Rule添加进Map实例的时候,会为这个Rule生成一个正则表达式的属性_regex。这样当这个Flask应用处理请求时,实际上会将请求中的url和Flask应用中每一条URL规则的正则表达式进行匹配。若是匹配成功,则会返回endpoint和一些参数,返回的endpoint能够用来在view_functions找到对应的视图函数,返回的参数能够传递给视图函数。具体的过程就是:

try:
    # match_request()能够进行URL匹配
    endpoint, values = self.match_request()
    return self.view_functions[endpoint](**values)
    ...复制代码
相关文章
相关标签/搜索