在刚开始接触 django 的时候, 我们尝试着从各种入门文档中创建一个自己的 django 项目, 需要在 mysite.urls.py 中配置 URL. 这是 django url 匹配处理机制的一小部分.

URL 调度器详解

django url 匹配处理机制主要由一下模块实现: django.conf.urls 和 django.core.urlresolver.py. 有需要摘取上一节中的代码:

  1. # BaseHandler.get_response() 的定义
  2. # 处理请求的函数, 并返回 response
  3. def get_response(self, request):
  4. "Returns an HttpResponse object for the given HttpRequest"
  5. 根据请求, 得到响应
  6.  
  7. try:
  8. 为该线程提供默认的 url 处理器
  9. # Setup default url resolver for this thread, this code is outside
  10. # the try/except so we don't get a spurious "unbound local
  11. # variable" exception in the event an exception is raised before
  12. # resolver is set
  13.  
  14. #ROOT_URLCONF = 'mysite.urls'
  15. urlconf = settings.ROOT_URLCONF
  16.  
  17. # set_urlconf() 会设置 url 配置即 settings.ROOT_URLCONF
  18. # 会设置一个线程共享变量, 存储 urlconf
  19. urlresolvers.set_urlconf(urlconf)
  20.  
  21. # 实例化 RegexURLResolver, 暂且将其理解为一个 url 的匹配处理器, 下节展开
  22. resolver = urlresolvers.RegexURLResolver(r'^/', urlconf)
  23.  
  24. try:
  25. response = None
  26.  
  27. # Apply request middleware 调用请求中间件
  28. ......
  29.  
  30. # 如果没有结果
  31. if response is None:
  32. # 尝试 request 中是否有 urlconf, 一般没有, 可以忽略此段代码!!!
  33. if hasattr(request, 'urlconf'):
  34. # Reset url resolver with a custom urlconf. 自定义的 urlconf
  35. urlconf = request.urlconf
  36. urlresolvers.set_urlconf(urlconf)
  37. resolver = urlresolvers.RegexURLResolver(r'^/', urlconf)
  38. # 调用 RegexURLResolver.resolve(), 可以理解为启动匹配的函数; 返回 ResolverMatch 实例
  39. resolver_match = resolver.resolve(request.path_info)
  40.  
  41. # resolver_match 对象中存储了有用的信息, 譬如 callback 就是我们在 views.py 中定义的函数.
  42. callback, callback_args, callback_kwargs = resolver_match
  43.  
  44. # 将返回的 resolver_match 挂钩到 request
  45. request.resolver_match = resolver_match
  46.  
  47. # Apply view middleware 调用视图中间件
  48. ......

可以简单的理解为 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. # url 里面可以用 incude 函数
  2. def include(arg, namespace=None, app_name=None):
  3. if isinstance(arg, tuple):
  4. # callable returning a namespace hint
  5. if namespace:
  6. raise ImproperlyConfigured('Cannot override the namespace for a dynamic module that provides a namespace')
  7.  
  8. # 获取 urlconf 模块文件, 应用名, 命名空间
  9. urlconf_module, app_name, namespace = arg
  10. else:
  11. # No namespace hint - use manually provided namespace
  12. urlconf_module = arg
  13.  
  14. if isinstance(urlconf_module, six.string_types):
  15. # 尝试导入模块
  16. urlconf_module = import_module(urlconf_module)
  17.  
  18. # 在 urlconf_module 中导入 urlpatterns
  19. # 在 urlconf_module 中肯定会有 urlpatterns 这个变量
  20. patterns = getattr(urlconf_module, 'urlpatterns', urlconf_module)
  21.  
  22. # Make sure we can iterate through the patterns (without this, some
  23. # testcases will break).
  24. if isinstance(patterns, (list, tuple)):
  25. for url_pattern in patterns:
  26. # Test if the LocaleRegexURLResolver is used within the include;
  27. # this should throw an error since this is not allowed!
  28. if isinstance(url_pattern, LocaleRegexURLResolver):
  29. raise ImproperlyConfigured(
  30. 'Using i18n_patterns in an included URLconf is not allowed.')
  31.  
  32. # 返回模块, app 名 ,命名空间
  33. return (urlconf_module, app_name, namespace)
  34.  
  35. def patterns(prefix, *args): 特意留一个 prefix
  36. pattern_list = []
  37. for t in args:
  38. if isinstance(t, (list, tuple)):
  39. t = url(prefix=prefix, *t) 自动转换
  40.  
  41. elif isinstance(t, RegexURLPattern):
  42. t.add_prefix(prefix)
  43.  
  44. pattern_list.append(t)
  45.  
  46. # 返回 RegexURLResolver 或者 RegexURLPattern 对象的列表
  47. return pattern_list
  48.  
  49. # url 函数
  50. def url(regex, view, kwargs=None, name=None, prefix=''):
  51. if isinstance(view, (list,tuple)): 如果是 list 或者 tuple
  52. # For include(...) processing. 处理包含 include(...)
  53. urlconf_module, app_name, namespace = view
  54.  
  55. # 此处返回 RegexURLResolver, 区分下面返回 RegexURLPattern
  56. return RegexURLResolver(regex, urlconf_module, kwargs, app_name=app_name, namespace=namespace)
  57. else:
  58. if isinstance(view, six.string_types):
  59. if not view:
  60. raise ImproperlyConfigured('Empty URL pattern view name not permitted (for pattern %r)' % regex)
  61. if prefix:
  62. view = prefix + '.' + view
  63.  
  64. # 返回 RegexURLPattern 的对象
  65. return RegexURLPattern(regex, view, kwargs, name)
  66. # 从上面可以获知, url 会返回 RegexURLResolver 或者 RegexURLPattern 对象

可以简单的理解为, url() 根据具体情况返回 RegexURLResolver 或者 RegexURLPattern 对象; patterns() 返回了包含有 RegexURLPattern 和 RegexURLResolver 对象的列表. 当在 urls.py 中出现:
每个 include() 的时候, 最终会产生一个 RegexURLResolver 对象;
否则为 RegexURLPattern 对象.

  1. 回到那三个类, 摘取 RegexURLResolver 的主干函数作为讲解:
  2. # 最关键的函数
  3. def resolve(self, path):
  4.  
  5. tried = []
  6.  
  7. # regex 在 RegexURLResolver 中表示前缀
  8. match = self.regex.search(path)
  9.  
  10. if match:
  11. # 去除前缀
  12. new_path = path[match.end():]
  13.  
  14. for pattern in self.url_patterns: # 穷举所有的 url pattern
  15. # pattern 是 RegexURLPattern 实例
  16. try:
  17.  
  18. """在 RegexURLResolver.resolve() 中的一句: sub_match = pattern.resolve(new_path) 最为关键.
  19. 从上面 patterns() 函数的作用知道, pattern 可以是 RegexURLPattern 对象或者 RegexURLResolver 对象. 当为 RegexURLResolver 对象的时候, 就是启动子 url 匹配处理器, 于是又回到了上面.
  20.  
  21. RegexURLPattern 和 RegexURLResolver 都有一个 resolve() 函数, 所以, 下面的一句 resolve() 调用, 可以是调用 RegexURLPattern.resolve() 或者 RegexURLResolver.resolve()"""
  22.  
  23. # 返回 ResolverMatch 实例
  24. sub_match = pattern.resolve(new_path)
  25.  
  26. except Resolver404 as e:
  27. # 搜集已经尝试过的匹配器, 在出错的页面中会显示错误信息
  28. sub_tried = e.args[0].get('tried')
  29.  
  30. if sub_tried is not None:
  31. tried.extend([[pattern] + t for t in sub_tried])
  32. else:
  33. tried.append([pattern])
  34. else:
  35. # 是否成功匹配
  36. if sub_match:
  37. # match.groupdict()
  38. # Return a dictionary containing all the named subgroups of the match,
  39. # keyed by the subgroup name.
  40.  
  41. # 如果在 urls.py 的正则表达式中使用了变量, match.groupdict() 返回即为变量和值.
  42. sub_match_dict = dict(match.groupdict(), **self.default_kwargs)
  43.  
  44. sub_match_dict.update(sub_match.kwargs)
  45.  
  46. # 返回 ResolverMatch 对象, 如你所知, 得到此对象将可以执行真正的逻辑操作, 即 views.py 内定义的函数.
  47. return ResolverMatch(sub_match.func,
  48. sub_match.args, sub_match_dict,
  49. sub_match.url_name, self.app_name or sub_match.app_name,
  50. [self.namespace] + sub_match.namespaces)
  51.  
  52. tried.append([pattern])
  53.  
  54. # 如果没有匹配成功的项目, 将异常
  55. raise Resolver404({'tried': tried, 'path': new_path})
  56.  
  57. raise Resolver404({'path' : path})
  58.  
  59. # 修饰 urlconf_module, 返回 self._urlconf_module, 即 urlpatterns 变量所在的文件
  60. @property
  61. def urlconf_module(self):
  62. try:
  63. return self._urlconf_module
  64. except AttributeError:
  65. self._urlconf_module = import_module(self.urlconf_name)
  66. return self._urlconf_module
  67.  
  68. # 返回指定文件中的 urlpatterns 变量
  69. @property
  70. def url_patterns(self):
  71. patterns = getattr(self.urlconf_module, "urlpatterns", self.urlconf_module)
  72. try:
  73. iter(patterns) # 是否可以迭代
  74. except TypeError:
  75. raise ImproperlyConfigured("The included urlconf %s doesn't have any patterns in it" % self.urlconf_name)
  76.  
  77. # patterns 实际上是 RegexURLPattern 对象和 RegexURLResolver 对象的集合
  78. return patterns
  79.  
  80. 摘取 RegexURLPattern 的主干函数作为讲解:
  81.  
  82. # 执行正则匹配
  83. def resolve(self, path):
  84. match = self.regex.search(path) # 搜索
  85. if match:
  86. # If there are any named groups, use those as kwargs, ignoring
  87. # non-named groups. Otherwise, pass all non-named arguments as
  88. # positional arguments.
  89. # match.groupdict() 返回正则表达式中匹配的变量以及其值, 需要了解 python 中正则表达式的使用
  90. kwargs = match.groupdict()
  91. if kwargs:
  92. args = ()
  93. else:
  94. args = match.groups()
  95.  
  96. # In both cases, pass any extra_kwargs as **kwargs.
  97. kwargs.update(self.default_args)
  98.  
  99. # 成功, 返回匹配结果类; 否则返回 None
  100. return ResolverMatch(self.callback, args, kwargs, self.name)
  101.  
  102. # 对 callback 进行修饰, 如果 self._callback 不是一个可调用的对象, 则可能还是一个字符串, 需要解析得到可调用的对象
  103. @property
  104. def callback(self):
  105. if self._callback is not None:
  106. return self._callback
  107.  
  108. self._callback = get_callable(self._callback_str)
  109. return self._callback
  110.  
  111. ResolverMatch 不贴代码了, 它包装了匹配成功所需要的信息, views.py 中定义的函数.

URL 调度过程实例

下面的具体例子将加深对 RegexURLResolver.reslove() 调用的理解. 假设工程名为 mysite, 并且创建了 app people.

  1. # mysite.urls.py
  2. from django.conf.urls import patterns, include, url
  3.  
  4. urlpatterns = patterns('',
  5. url(r"^$","mysite.views.index"),
  6. url(r"^about/","mysite.views.about"),
  7. url(r"^people/",include(people.urls)),
  8. url(r"^contact/","mysite.views.contact"),
  9. url(r"^update/","mysite.views.update"),
  10. )
  11.  
  12. # people.urls.py
  13. from django.conf.urls import patterns, include, url
  14.  
  15. urlpatterns = patterns('',
  16. url(r"^daoluan/","people.views.daoluan"),
  17. url(r"^sam/","people.views.sam"),
  18. url(r"^jenny/","people.views.jenny"),
  19. )
  20. # people.views.py
  21. def daoluan(request):
  22. return HttpResponse("hello")

当访问 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: 开始依次匹配. 第一个就中, 过程和刚开始的过程一样. 因此构造 ResolverMatch 对象返回. 于是 BaseHandler.get_response() 就顺利得到 ResolverMatch 对象, 其中记录了有用的信息. 在 BaseHandler.get_response() 中有足够的信息让你知道开发人员在 views.py 中定义的函数是 def daoluan(request): 在什么时候调用的:

  1. # BaseHandler.get_response() 的定义
  2. # 处理请求的函数, 并返回 response
  3. def get_response(self, request):
  4. ......
  5.  
  6. # 实例化 RegexURLResolver, 暂且将其理解为一个 url 的匹配处理器, 下节展开
  7. resolver = urlresolvers.RegexURLResolver(r'^/', urlconf)
  8. ......
  9.  
  10. # 调用 RegexURLResolver.resolve(), 可以理解为启动匹配的函数; 返回 ResolverMatch 实例
  11. resolver_match = resolver.resolve(request.path_info)
  12. ......
  13.  
  14. # resolver_match 对象中存储了有用的信息, 譬如 callback 就是我们在 views.py 中定义的函数.
  15. callback, callback_args, callback_kwargs = resolver_match
  16. ......
  17.  
  18. # 这里调用的是真正的处理函数, 我们一般在 view.py 中定义这些函数
  19. response = callback(request, *callback_args, **callback_kwargs)
  20. ......
  21.  
  22. return response

总结

从上面知道, url 调度器主要 RegexURLResolver, RegexURLPattern, ResolverMatch 和三个辅助函数 url(), include(), patterns() 完成. 可以发现, url 的调度顺序是根据 urls.py 中的声明顺序决定的, 意即遍历一张表而已, 有没有办法提高查找的效率?

我已经在 github 备份了 Django 源码的注释: Decode-Django, 有兴趣的童鞋 fork 吧.

捣乱 2013-9-15

http://daoluan.net

Django 源码小剖: URL 调度器(URL dispatcher)的更多相关文章

  1. Django 源码小剖: 更高效的 URL 调度器(URL dispatcher)

    效率问题 django 内部的 url 调度机制说白了就是给一张有关匹配信息的表, 这张表中有着 url -> action 的映射, 当请求到来的时候, 一个一个(遍历)去匹配. 中, 则调用 ...

  2. Django 源码小剖: 响应数据 response 的返回

    响应数据的返回 在 WSGIHandler.__call__(self, environ, start_response) 方法调用了 WSGIHandler.get_response() 方法, 由 ...

  3. Django 源码小剖: 初探 WSGI

    Django 源码小剖: 初探 WSGI python 作为一种脚本语言, 已经逐渐大量用于 web 后台开发中, 而基于 python 的 web 应用程序框架也越来越多, Bottle, Djan ...

  4. Django 源码小剖: Django ORM 查询管理器

    ORM 查询管理器 对于 ORM 定义: 对象关系映射, Object Relational Mapping, ORM, 是一种程序设计技术,用于实现面向对象编程语言里不同类型系统的数据之间的转换.从 ...

  5. Django 源码小剖: Django 对象关系映射(ORM)

    引 从前面已经知道, 一个 request 的到来和一个对应 response 的返回的流程, 数据处理和数据库离不开. 我们也经常在 views.py 的函数定义中与数据库打交道. django O ...

  6. Django 源码小剖: 初探中间件(middleware)

    因为考虑到文章的长度, 所以 BaseHandler 的展开被推迟了. 在 BaseHandler 中隐藏着中间件的信息, 较常见的 SessionMiddleware 就已经默认安装.  BaseH ...

  7. Django 源码小剖: Django 中的 WSGI

    Django 其内部已经自带了一个方便本地测试的小服务器, 所以在刚开始学习 Django 的时候并不需搭建 apache 或者 nginx 服务器. Django 自带的服务器基于 python w ...

  8. Django 源码小剖: 应用程序入口 WSGIHandler

    WSGI 有三个部分, 分别为服务器(server), 应用程序(application) 和中间件(middleware). 已经知道, 服务器方面会调用应用程序来处理请求, 在应用程序中有真正的处 ...

  9. urllib2 源码小剖

    urllib2 源码小剖 2013-08-25 23:38 by 捣乱小子, 272 阅读, 0 评论, 收藏, 编辑 两篇小剖已经完成: urllib 源码小剖 urllib2 源码小剖 urlli ...

随机推荐

  1. Create My MySQL configuration by Percona

    本文地址:http://www.cnblogs.com/yhLinux/p/4013065.html https://tools.percona.com/ Percona是一款在线自动生成MySQL配 ...

  2. FastDFS基本结构(转)

    0.简介 FastDFS是基于互联网应用的开源分布式文件系统,主要用于大中型网站存储资源文件,如图片.文档.音频.视频等.FastDFS采用类似GFS的架构,用纯C语言实现,支持Linux.FreeB ...

  3. 看起来像一个输入框的input,实际上是有两个input

    看起来像一个输入框的input,实际上是有两个input

  4. 打包解决方案后,安装时提示只能在IIS5.1以上运行解决方法

    环境:vs2010 sp1,mvc4,WIN10 生成安装项目后进行安装提示: 此安装程序需要Internet Information Server 5.1或更高版本和Windows XP和更高的安装 ...

  5. 关于RESTFUL API 安全认证方式的一些总结

    常用认证方式 在之前的文章REST API 安全设计指南与使用 AngularJS & NodeJS 实现基于 token 的认证应用两篇文章中,[译]web权限验证方法说明中也详细介绍,一般 ...

  6. [C++] socket - 5 [API事件对象实现线程同步]

    /*API事件对象实现线程同步*/ #include<windows.h> #include<stdio.h> DWORD WINAPI myfun1(LPVOID lpPar ...

  7. Git常用操作命令与图解

    Git 是一个很强大的分布式版本控制系统.它不但适用于管理大型开源软件的源代码,管理私人的文档和源代码也有很多优势. Git常用操作命令: 1) 远程仓库相关命令 检出仓库:$ git clone g ...

  8. JavaScript自定义事件

    很多DOM对象都有原生的事件支持,向div就有click.mouseover等事件,事件机制可以为类的设计带来很大的灵活性,相信.net程序员深有体会.随着web技术发展,使用JavaScript自定 ...

  9. [BTS] Action demo In BizTalk WCF-SAP Adapter

    I use following xml config in BizTalk 2010 WCF-SAP adapter. <BtsActionMapping xmlns:xsi="htt ...

  10. Javascrip的概述

    前言:逻辑思维和思路很重要 ———————————————————————————————————————————————— 一.JavaScript的概述 javascript 具有人机交互性,ja ...