装饰器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这个东西下次再写。