URL 调度器(URL dispatcher)
URL 调度器(URL dispatcher)
在刚开始接触 django 的时候, 我们尝试着从各种入门文档中创建一个自己的 django 项目, 需要在 mysite.urls.py 中配置 URL. 这是 django url 匹配处理机制的一小部分.
URL 调度器详解
django url 匹配处理机制主要由一下模块实现: django.conf.urls 和 django.core.urlresolver.py. 有需要摘取上一节中的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
# BaseHandler.get_response() 的定义 # 处理请求的函数, 并返回 response def get_response( self , request): "Returns an HttpResponse object for the given HttpRequest" 根据请求, 得到响应 try : 为该线程提供默认的 url 处理器 # Setup default url resolver for this thread, this code is outside # the try/except so we don't get a spurious "unbound local # variable" exception in the event an exception is raised before # resolver is set #ROOT_URLCONF = 'mysite.urls' urlconf = settings.ROOT_URLCONF # set_urlconf() 会设置 url 配置即 settings.ROOT_URLCONF # 会设置一个线程共享变量, 存储 urlconf urlresolvers.set_urlconf(urlconf) # 实例化 RegexURLResolver, 暂且将其理解为一个 url 的匹配处理器, 下节展开 resolver = urlresolvers.RegexURLResolver(r '^/' , urlconf) try : response = None # Apply request middleware 调用请求中间件 ...... # 如果没有结果 if response is None : # 尝试 request 中是否有 urlconf, 一般没有, 可以忽略此段代码!!! if hasattr (request, 'urlconf' ): # Reset url resolver with a custom urlconf. 自定义的 urlconf urlconf = request.urlconf urlresolvers.set_urlconf(urlconf) resolver = urlresolvers.RegexURLResolver(r '^/' , urlconf) # 调用 RegexURLResolver.resolve(), 可以理解为启动匹配的函数; 返回 ResolverMatch 实例 resolver_match = resolver.resolve(request.path_info) # resolver_match 对象中存储了有用的信息, 譬如 callback 就是我们在 views.py 中定义的函数. callback, callback_args, callback_kwargs = resolver_match # 将返回的 resolver_match 挂钩到 request request.resolver_match = resolver_match # Apply view middleware 调用视图中间件 ...... |
可以简单的理解为 get_response() 中构造了 RegexURLResolver 对象并调用了 RegexURLResolver.resolve(path) 启动解析. 从上面的代码中, 可以获知 urlconf 默认使用的是 mysite.settings.py 中的 ROOT_URLCONF, 而也确实可以在 mysite.settings.py 中找到对应的设置项, 并且做出修改.
从上, 至少可以知道, 真正发挥匹配作用的是 RegexURLResolver 对象, 并调用 RegexURLResolver.resolve() 启动了解析, 一切从这里开始. 从 urlresolver.py 中抽取主干部分, 可以得到下面的 UML 图:
LocaleRegexProvider 类只为地区化而存在, 他持有 regex 属性, 但在 RegexURLResolver 和 RegexURLPattern 中发挥不同的作用:
- RegexURLResolver: 过滤 url 的前缀, 譬如如果 regex 属性值为 people, 那么能将 people/daoluan/ 过滤为 daoluan/.
- RegexURLPattern: 匹配整个 url.
在展开 ResolverMatch, RegexURLPattern, RegexURLResolver 三个类之前, 暂且将他们理解为:
- ResolverMatch 当匹配成功时会实例化返回
- RegexURLPattern, RegexURLResolver 匹配器, 但有不同.
然后需要先介绍三个函数: url(), include(), patterns(), 三者经常在 urls.py 中用到. 它们在 django.conf.urls 中定义. 摘抄和解析如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
|
# url 里面可以用 incude 函数 def include(arg, namespace = None , app_name = None ): if isinstance (arg, tuple ): # callable returning a namespace hint if namespace: raise ImproperlyConfigured( 'Cannot override the namespace for a dynamic module that provides a namespace' ) # 获取 urlconf 模块文件, 应用名, 命名空间 urlconf_module, app_name, namespace = arg else : # No namespace hint - use manually provided namespace urlconf_module = arg if isinstance (urlconf_module, six.string_types): # 尝试导入模块 urlconf_module = import_module(urlconf_module) # 在 urlconf_module 中导入 urlpatterns # 在 urlconf_module 中肯定会有 urlpatterns 这个变量 patterns = getattr (urlconf_module, 'urlpatterns' , urlconf_module) # Make sure we can iterate through the patterns (without this, some # testcases will break). if isinstance (patterns, ( list , tuple )): for url_pattern in patterns: # Test if the LocaleRegexURLResolver is used within the include; # this should throw an error since this is not allowed! if isinstance (url_pattern, LocaleRegexURLResolver): raise ImproperlyConfigured( 'Using i18n_patterns in an included URLconf is not allowed.' ) # 返回模块, app 名 ,命名空间 return (urlconf_module, app_name, namespace) def patterns(prefix, * args): 特意留一个 prefix pattern_list = [] for t in args: if isinstance (t, ( list , tuple )): t = url(prefix = prefix, * t) 自动转换 elif isinstance (t, RegexURLPattern): t.add_prefix(prefix) pattern_list.append(t) # 返回 RegexURLResolver 或者 RegexURLPattern 对象的列表 return pattern_list # url 函数 def url(regex, view, kwargs = None , name = None , prefix = ''): if isinstance (view, ( list , tuple )): 如果是 list 或者 tuple # For include(...) processing. 处理包含 include(...) urlconf_module, app_name, namespace = view # 此处返回 RegexURLResolver, 区分下面返回 RegexURLPattern return RegexURLResolver(regex, urlconf_module, kwargs, app_name = app_name, namespace = namespace) else : if isinstance (view, six.string_types): if not view: raise ImproperlyConfigured( 'Empty URL pattern view name not permitted (for pattern %r)' % regex) if prefix: view = prefix + '.' + view # 返回 RegexURLPattern 的对象 return RegexURLPattern(regex, view, kwargs, name) # 从上面可以获知, url 会返回 RegexURLResolver 或者 RegexURLPattern 对象 |
可以简单的理解为, url() 根据具体情况返回 RegexURLResolver 或者 RegexURLPattern 对象; patterns() 返回了包含有 RegexURLPattern 和 RegexURLResolver 对象的列表. 当在 urls.py 中出现:
每个 include() 的时候, 最终会产生一个 RegexURLResolver 对象;
否则为 RegexURLPattern 对象.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
|
回到那三个类, 摘取 RegexURLResolver 的主干函数作为讲解: # 最关键的函数 def resolve( self , path): tried = [] # regex 在 RegexURLResolver 中表示前缀 match = self .regex.search(path) if match: # 去除前缀 new_path = path[match.end():] for pattern in self .url_patterns: # 穷举所有的 url pattern # pattern 是 RegexURLPattern 实例 try : """在 RegexURLResolver.resolve() 中的一句: sub_match = pattern.resolve(new_path) 最为关键. 从上面 patterns() 函数的作用知道, pattern 可以是 RegexURLPattern 对象或者 RegexURLResolver 对象. 当为 RegexURLResolver 对象的时候, 就是启动子 url 匹配处理器, 于是又回到了上面. RegexURLPattern 和 RegexURLResolver 都有一个 resolve() 函数, 所以, 下面的一句 resolve() 调用, 可以是调用 RegexURLPattern.resolve() 或者 RegexURLResolver.resolve()""" # 返回 ResolverMatch 实例 sub_match = pattern.resolve(new_path) except Resolver404 as e: # 搜集已经尝试过的匹配器, 在出错的页面中会显示错误信息 sub_tried = e.args[ 0 ].get( 'tried' ) if sub_tried is not None : tried.extend([[pattern] + t for t in sub_tried]) else : tried.append([pattern]) else : # 是否成功匹配 if sub_match: # match.groupdict() # Return a dictionary containing all the named subgroups of the match, # keyed by the subgroup name. # 如果在 urls.py 的正则表达式中使用了变量, match.groupdict() 返回即为变量和值. sub_match_dict = dict (match.groupdict(), * * self .default_kwargs) sub_match_dict.update(sub_match.kwargs) # 返回 ResolverMatch 对象, 如你所知, 得到此对象将可以执行真正的逻辑操作, 即 views.py 内定义的函数. return ResolverMatch(sub_match.func, sub_match.args, sub_match_dict, sub_match.url_name, self .app_name or sub_match.app_name, [ self .namespace] + sub_match.namespaces) tried.append([pattern]) # 如果没有匹配成功的项目, 将异常 raise Resolver404({ 'tried' : tried, 'path' : new_path}) raise Resolver404({ 'path' : path}) # 修饰 urlconf_module, 返回 self._urlconf_module, 即 urlpatterns 变量所在的文件 @property def urlconf_module( self ): try : return self ._urlconf_module except AttributeError: self ._urlconf_module = import_module( self .urlconf_name) return self ._urlconf_module # 返回指定文件中的 urlpatterns 变量 @property def url_patterns( self ): patterns = getattr ( self .urlconf_module, "urlpatterns" , self .urlconf_module) try : iter (patterns) # 是否可以迭代 except TypeError: raise ImproperlyConfigured( "The included urlconf %s doesn't have any patterns in it" % self .urlconf_name) # patterns 实际上是 RegexURLPattern 对象和 RegexURLResolver 对象的集合 return patterns 摘取 RegexURLPattern 的主干函数作为讲解: # 执行正则匹配 def resolve( self , path): match = self .regex.search(path) # 搜索 if match: # If there are any named groups, use those as kwargs, ignoring # non-named groups. Otherwise, pass all non-named arguments as # positional arguments. # match.groupdict() 返回正则表达式中匹配的变量以及其值, 需要了解 python 中正则表达式的使用 kwargs = match.groupdict() if kwargs: args = () else : args = match.groups() # In both cases, pass any extra_kwargs as **kwargs. kwargs.update( self .default_args) # 成功, 返回匹配结果类; 否则返回 None return ResolverMatch( self .callback, args, kwargs, self .name) # 对 callback 进行修饰, 如果 self._callback 不是一个可调用的对象, 则可能还是一个字符串, 需要解析得到可调用的对象 @property def callback( self ): if self ._callback is not None : return self ._callback self ._callback = get_callable( self ._callback_str) return self ._callback ResolverMatch 不贴代码了, 它包装了匹配成功所需要的信息, 如 views.py 中定义的函数. |
下面的具体例子将加深对 RegexURLResolver.reslove() 调用的理解. 假设工程名为 mysite, 并且创建了 app people.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
# mysite.urls.py from django.conf.urls import patterns, include, url urlpatterns = patterns('', url(r "^$" , "mysite.views.index" ), url(r "^about/" , "mysite.views.about" ), url(r "^people/" ,include(people.urls)), url(r "^contact/" , "mysite.views.contact" ), url(r "^update/" , "mysite.views.update" ), ) # people.urls.py from django.conf.urls import patterns, include, url urlpatterns = patterns('', url(r "^daoluan/" , "people.views.daoluan" ), url(r "^sam/" , "people.views.sam" ), url(r "^jenny/" , "people.views.jenny" ), ) # people.views.py def daoluan(request): return HttpResponse( "hello" ) |
URL 调度过程实例
当访问 http://exapmle.com/people/daoluan/ 的时候 URL dispatcher 的调度过程(蓝色部分):
对应上面的例子 url 调度器机制的具体工作过程如下, 从 BaseHandler.get_response() 开始说起:
1. BaseHandler.get_response() 中根据 settings.py 中的 ROOT_URLCONF 设置选项构造 RegexURLResolver 对象, 并调用 RegexURLResolver.resolve("/people/daoluan/") 启动解析, 其中 RegexURLResolver.regex = "^\", 也就是说它会过滤 "\", url 变为 "people/daoluan/";
2. resolve() 中调用 RegexURLResolver.url_patterns(), 加载了所有的匹配信息如下(和图中一样):
- (类型)RegexURLPattern (正则匹配式)[^$]
- RegexURLPattern [^about/]
- RegexURLResolver [^people/]
- RegexURLPattern [^contact/]
- RegexURLPattern [^update/]
语句 for pattern in self.url_patterns: 开始依次匹配. 第一个因为是 RegexURLPattern 对象, 调用 resolve() 为 RegexURLPattern.resolve(): 它直接用 [^$] 去匹配 "people/daoluan/", 结果当然是不匹配.
3. 下一个 pattern 过程同上.
4. 第三个 pattern 因为是 RegexURLResolver 对象, 所以 resolve() 调用的是 RegexURLResolver.resolve(), 而非上面两个例子中的 RegexURLPattern.resolve(). 因为第三个 pattern.regex = "^people/", 所以会将 "people/daoluan/" 过滤为 "daoluan/". pattern.resolve() 中会调用 RegexURLResolver.url_patterns(), 加载了所有的匹配信息如下(和图中一样):
- RegexURLPattern [^daoluan$]
- RegexURLPattern [^sam$]
- RegexURLPattern [^jenny$]
语句 for pattern in self.url_patterns: 开始依次匹配. 第一个就中, 过程和刚开始的过程一样. 因此构造 RegexURLMatch 对象返回. 于是 BaseHandler.get_response() 就顺利得到 RegexURLMatch 对象, 其中记录了有用的信息. 在 BaseHandler.get_response() 中有足够的信息让你知道开发人员在 views.py 中定义的函数是 def daoluan(request): 在什么时候调用的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
# BaseHandler.get_response() 的定义 # 处理请求的函数, 并返回 response def get_response( self , request): ...... # 实例化 RegexURLResolver, 暂且将其理解为一个 url 的匹配处理器, 下节展开 resolver = urlresolvers.RegexURLResolver(r '^/' , urlconf) ...... # 调用 RegexURLResolver.resolve(), 可以理解为启动匹配的函数; 返回 ResolverMatch 实例 resolver_match = resolver.resolve(request.path_info) ...... # resolver_match 对象中存储了有用的信息, 譬如 callback 就是我们在 views.py 中定义的函数. callback, callback_args, callback_kwargs = resolver_match ...... # 这里调用的是真正的处理函数, 我们一般在 view.py 中定义这些函数 response = callback(request, * callback_args, * * callback_kwargs) ...... return response |
总结
从上面知道, url 调度器主要 RegexURLResolver, RegexURLPattern, ResolverMatch 和三个辅助函数 url(), include(), patterns() 完成. 可以发现, url 的调度顺序是根据 urls.py 中的声明顺序决定的, 意即遍历一张表而已, 有没有办法提高查找的效率?
我已经在 github 备份了 Django 源码的注释: Decode-Django, 有兴趣的童鞋 fork 吧.
捣乱 2013-9-15
URL 调度器(URL dispatcher)的更多相关文章
- Django 源码小剖: URL 调度器(URL dispatcher)
在刚开始接触 django 的时候, 我们尝试着从各种入门文档中创建一个自己的 django 项目, 需要在 mysite.urls.py 中配置 URL. 这是 django url 匹配处理机制的 ...
- Django 源码小剖: 更高效的 URL 调度器(URL dispatcher)
效率问题 django 内部的 url 调度机制说白了就是给一张有关匹配信息的表, 这张表中有着 url -> action 的映射, 当请求到来的时候, 一个一个(遍历)去匹配. 中, 则调用 ...
- Django URL调度器
Django处理请求的流程 Django确定要使用的根URLconf模块.通常,这是ROOT_URLCONF设置的值,但如果传入 HttpRequest对象具有urlconf 属性(由中间件设置),则 ...
- 02-URLConf调度器
1.工作原理 django通过urlconf来映射视图函数,只区分路径,不区分http方法 Django确定要使用的根URLconf模块,一般是在settings中的ROOT_URLCONF设置的值. ...
- python爬虫主要就是五个模块:爬虫启动入口模块,URL管理器存放已经爬虫的URL和待爬虫URL列表,html下载器,html解析器,html输出器 同时可以掌握到urllib2的使用、bs4(BeautifulSoup)页面解析器、re正则表达式、urlparse、python基础知识回顾(set集合操作)等相关内容。
本次python爬虫百步百科,里面详细分析了爬虫的步骤,对每一步代码都有详细的注释说明,可通过本案例掌握python爬虫的特点: 1.爬虫调度入口(crawler_main.py) # coding: ...
- python3 爬虫五大模块之二:URL管理器
Python的爬虫框架主要可以分为以下五个部分: 爬虫调度器:用于各个模块之间的通信,可以理解为爬虫的入口与核心(main函数),爬虫的执行策略在此模块进行定义: URL管理器:负责URL的管理,包括 ...
- DjangoRestFramework学习三之认证组件、权限组件、频率组件、url注册器、响应器、分页组件
DjangoRestFramework学习三之认证组件.权限组件.频率组件.url注册器.响应器.分页组件 本节目录 一 认证组件 二 权限组件 三 频率组件 四 URL注册器 五 响应器 六 分 ...
- Atitit atiplat_reader 基于url阅读器的新特性
Atitit atiplat_reader 基于url阅读器的新特性 1.1. feature功能特性1 1.2. note1 1.1. feature功能特性 支持url数据源,实际就是只支持一层连 ...
- day91 DjangoRestFramework学习三之认证组件、权限组件、频率组件、url注册器、响应器、分页组件
DjangoRestFramework学习三之认证组件.权限组件.频率组件.url注册器.响应器.分页组件 本节目录 一 认证组件 二 权限组件 三 频率组件 四 URL注册器 五 响应器 六 分 ...
随机推荐
- EF中的贪婪加载和延迟加载(懒加载)
在上一章中,我们使用了Linq对Entity Framework进行了一个查询,但是通过学习我们却发现了懒加载给我来的性能上的开销是很到的,尤其是在循环中,如果数据量不是很多的情况下还可以接受,如果数 ...
- 乐趣与你rabbitMQ 源代码
RabbitMQ API RabbitMQ Server它提供了丰富的http api. 对于列子 须要HTTP基本身份验证.默认的username/password为guest/guest. 这些返 ...
- 【百度地图API】小学生找哥哥——小学生没钱打车,所以此为公交查询功能
原文:[百度地图API]小学生找哥哥--小学生没钱打车,所以此为公交查询功能 任务描述: 有位在魏公村附近上小学的小朋友,要去北京邮电大学找哥哥.他身上钱很少,只够坐公交的.所以,百度地图API快帮帮 ...
- 突破IP限制动态替换代理ip。
须要导入的两个jar包 实现的javabean <span style="font-size:18px;">package com.jx.po; public clas ...
- Cocos2d-xvision3.0加载失败,和,Vs2012环境搭建
1.安装好VS2012,下载Cocos2d-x3.0 双击击win32 sln运行VS2012 如果加载失败点击程序运行,输入devenv.exe /resetuserdata 回车,然后再进入VS, ...
- Font-Awesome 体验 鼠标进入图标变大
<html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> &l ...
- HDU1342 Lotto 【深搜】
Lotto Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) Total Subm ...
- Spark Standalone模式应用程序开发
作者:过往记忆 | 新浪微博:左手牵右手TEL | 能够转载, 但必须以超链接形式标明文章原始出处和作者信息及版权声明博客地址:http://www.iteblog.com/文章标题:<Spar ...
- 前端开发——移动bug整理
1.ios下jquery的delegate失效问题? 解决方案: $("body").delegate(...) 改为 $(".item").delegate( ...
- SQL SERVER 2005中如何获取日期(一个月的最后一日、上个月第一天、最后一天、一年的第一日等等)
原文:[转]SQL SERVER 2005中如何获取日期(一个月的最后一日.上个月第一天.最后一天.一年的第一日等等) 在网上找到的一篇文章,相当不错哦O(∩_∩)O~ //C#本周第一天 ...