裝飾器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 forRule
that change the way it behaves and are passed to theRule
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 realserver_name
from the environment. If you don’t provide it, Werkzeug will useSERVER_NAME
andSERVER_PORT
(orHTTP_HOST
if provided) as usedserver_name
with disabled subdomain feature.If
subdomain
isNone
but an environment and a server name is provided it will calculate the current subdomain automatically.
Example:server_name
is'example.com'
and theSERVER_NAME
in the wsgienviron
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這個東西下次再寫。