Flask源碼關於修飾器修飾路由

裝飾器route主要是爲fun套了一層add_url_rule:

def decorator(f):
            endpoint = options.pop('endpoint', None)
            self.add_url_rule(rule, endpoint, f, **options)
            return f
        return decorator

add_url_rule函數主要負責工作是鑑定options,包括endpoint(默認是函數名,應該是後期機制中與url關聯的函數名,在werkzeug中也相對應),methods默認處理爲(‘GET’, ),然後將函數放入view_functions這個dict中,以備映射。最關鍵的部分還是使用了Rule這個類,它是werkzeug中關於路由管理的類,由這個類創建的路由規則將被放入werkzeug.routing中的Map

關於Rule:

A Rule represents one URL pattern. There are some options for Rule that change the way it behaves and are passed to the Rule constructor. Note that besides the rule-string all arguments must be keyword arguments in order to not break the application on Werkzeug upgrades.

關於Map:
根據werkzeug的編程來看對於路由的獲取應該是Map中的bind_to_environ函數起到了作用,該函數文檔解釋如下:

Like :meth:bind but you can pass it an WSGI environment and it will fetch the information from that dictionary. Note that because of limitations in the protocol there is no way to get the current subdomain and real server_name from the environment. If you don’t provide it, Werkzeug will use SERVER_NAME and SERVER_PORT (or HTTP_HOST if provided) as used server_name with disabled subdomain feature.

If subdomain is None but an environment and a server name is provided it will calculate the current subdomain automatically.
Example: server_name is 'example.com' and the SERVER_NAME in the wsgi environ is 'staging.dev.example.com' the calculated subdomain will be 'staging.dev'.

If the object passed as environ has an environ attribute, the value of this attribute is used instead. This allows you to pass request objects. Additionally PATH_INFO added as a default of the :class:MapAdapter so that you don’t have to pass the path info to the match method.

這玩意又調用了bind函數,bind函數最後又返回了一個MapAdapter對象,最後werkzeug中應該調用該對象的match方法…後續不太清楚了,應該會涉及到run中的監聽並調用之前view_functions裏的方法。

下面基於werkzeug來實現一下它:

#!/usr/bin/env python
# encoding: utf-8

from werkzeug.wrappers import Request, Response
from werkzeug.routing import Map, Rule
from werkzeug.exceptions import HTTPException, NotFound

def _endpoint_from_view_func(view_func):
    """
        返回函數名作爲endpoint
    """
    assert view_func is not None, 'excepted view func if endpoint' \
                                'is not provided.'
    return view_func.__name__

class TinyFlask(object):
    """
        造輪子!
    """

    request_class = Request

    def __init__(self):
        self.url_map = Map()
        self.view_functions = {}    #函數與endpoint映射字典


    def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
        """
            添加確定函數與url規則並相映射
        """
        if endpoint is None:
            endpoint = _endpoint_from_view_func(view_func)
        options['endpoint'] = endpoint

        """
            得到http方法
        """
        methods = options.pop('methods', None)

        if methods is None:
            methods = getattr(view_func, 'methods', None) or ('GET', )
        if isinstance(methods, (str, unicode)):
            raise TypeError('Allowed methods have to be iterables of strings, '
                            'for example: @app.route(..., methods=["POST"])')
        methods = set(item.upper() for item in methods)

        #構造url規則
        rule = Rule(rule, methods=methods, **options)

        #向Map中添加該規則
        self.url_map.add(rule)


        if view_func is not None:
            old_func = self.view_functions.get(endpoint)
            if old_func is not None and old_func != view_func:
                raise AssertionError('View function mapping is overwriting an '
                                        'existing endpoint function: %s' % endpoint)
            self.view_functions[endpoint] = view_func

    def route(self, rule, **options):
        """
            裝飾器來確定url規則
        """
        def decorator(fun):
            """
                裝飾器
            """
            endpoint = options.pop('endpoint', None)
            self.add_url_rule(rule, endpoint, fun, **options)
            return fun
        return decorator

    def wsgi_app(self, environ, start_response):
        """
            此處創建WSGI應用
        """
        request = self.request_class(environ)
        urls = self.url_map.bind_to_environ(environ)
        try:
            endpoint, args = urls.match()
        except HTTPException, e:
            return e(environ, start_response)
        start_response('200 OK', [('Content-Type', 'text/plain')])
        return self.view_functions[endpoint](**args)

    def run(self, host='127.0.0.1', port=5000, debug=True, **options):
        from werkzeug.serving import run_simple
        self.debug = bool(debug)
        options.setdefault('use_reloader', self.debug)
        options.setdefault('use_debugger', self.debug)
        run_simple(host, port, self, options)

    def __call__(self, environ, start_response):
        return self.wsgi_app(environ, start_response)

app = TinyFlask()

@app.route('/')
def index():
    return 'Hello, World'

@app.route('/test/<int:year>')
def test(year):
    return 'Test' + str(year)

if __name__ == '__main__':
    app.run()

本來wsgi_app創建WSGI不會這麼簡單,Flask使用了RequestContext類來創建網頁的上下文(裏面包含session等),這裏先偷懶做到這裏,留WSGI這個東西下次再寫。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章