作者:Matt Layman
翻译:老齐
当用户访问网站的时候,其实是通过浏览器向网站发起访问请求,那么,网站是如何处理请求的?本文以Django框架为例进行说明,关于Django更详细的使用方法,请阅读《跟老齐学Python:Django实战(第二版)》。
来自浏览器的HTTP请求包含一个URL,URL描述Django应该生成哪些资源。由于URL可以有多种形式,我们必须把web应用可以处理的URL类型告诉Django,这就是URL配置的目的。在Django文档中,URL配置简称为URLconf。
URLconf在哪里?URLconf位于项目设置文件中的ROOT_URLCONF所设置的模块路径上。如果运行startproject命令,则该以project.url形式命名路由,其中“project”是项目名称。换句话说,URLconf就放在
project/urls.py
中,它与
settings.py
文件在同一个目录。
知道了文件所在的位置,但并没有讲它是如何工作的,下面来深入探索。
执行URLconf
可以将URL配置看作URL路径列表,Django会把这些路径从上到下进行匹配。当Django找到匹配的路径时,HTTP请求将转到与该路径相关联的Python代码块。这个“Python代码块”被称为视图,我们将在稍后对其进行更多的探讨。目前,请相信视图知道如何处理HTTP请求。
我们可以使用一个URLconf示例来实践上述观点。
# project/urls.py
from django.urls import path
from application import views
urlpatterns = [
path("", views.home),
path("about/", views.about),
path("contact/", views.contact),
path("terms/", views.terms),
]
复制代码
这里的内容与我上面的描述相吻合,Django对列表中的URL路径从上到下进行匹配。此列表的关键是
urlpatterns
。Django将把
urlpatterns
变量中的列表视为URLconf。
列表中的顺序也很重要,上述示例没有显示路径之间的任何冲突,但可以创建两个不同的
path
,它们可以匹配同一个URL,在后续内容中,会探究这个问题。
我们可以通过这个例子来看看它在www.itdiffer.com上是如何运行的。对于URLconf中的URL,Django不使用https://协议、或者主域名(www.itdiffer.com)及斜杠匹配路由,其他的都是URLconf所要匹配的。
-
对https://www.itdiffer.com/about/匹配“about/”,并匹配第二个路径。该请求将转到
views.about
视图。 -
对https://www.itdiffer.com/的请求匹配“”,并匹配第一个路径。该请求将路由到
views.home
视图。
补充说明:你可能注意到Django URL以斜杠结尾。事实上,如果你尝试访问像https://www.itdiffer.com/about这样的URL,Django会自动附加斜杠,再将请求重定向到相同的URL,这是因为使用了默认设置
APPEND_SLASH
,这种是Django在设计理念上的选择。
path
的作用
如果我给出的只是上面的例子,你可能会说:“哇,Django太笨了,为什么urlpatterns不是下面这样的字典?”
urlpatterns = {
"": views.home,
"about/": views.about,
"contact/": views.contact,
"terms/": views.terms,
}
复制代码
我不会因为你以上言论而苛责。
原因是
path
的能力更强大,大部分功能都在传给函数的第一个字符串参数中。
path
的字符串部分(例如,“about/”)称为路由。
正如你所看到的,路由可以是一个简单的字符串,它也可以包含一些转换特征,如此,就可以从URL中提取信息,并传给视图,例如这样的一个路径:
path("blog/<int:year>/<slug:slug>/", views.blog_post),
复制代码
此路径中的两个转换器是:
使用尖括号和一些保留关键词,让Django对URL进行解析,每个转换器都有一些预期的规则要遵循。
- int必须与整数匹配。
- slug必须与标称匹配。Slug是一种出现在Django中的报刊行话,因为Django是从堪萨斯州的一家报纸开始的。slug是一个字符串,它可以包含字符、数字、破折号和下划线。
考虑到这些转换器定义,让我们与一些URL进行比较!
- www.itdiffer.com/blog/2020/u… —— 匹配!
- www.itdiffer.com/blog/twenty… —— 不匹配。
- www.itdiffer.com/blog/0/life… —— 匹配!呃,也许不是我们想要的。让我们很快地看一看。
现在我们可以重新审视一下之前的排序问题,以不同的顺序考虑这两条路径:
path("blog/<int:year>/", views.blog_by_year),
path("blog/2020/", views.blog_for_twenty_twenty),
# vs.
path("blog/2020/", views.blog_for_twenty_twenty),
path("blog/<int:year>/", views.blog_by_year),
复制代码
在第一次排序中,转换器将匹配blog/之后的任何整数,包括https://www.itdiffer.com/blog/2020/。这意味着第一次排序将永远不会调用
views.blog_for_twenty_twenty
,因为Django按顺序匹配路径。
相反,在第二次排序中,blog/2020/将正确地路由到
views.blog_for_twenty_twenty
,因为它首先匹配。这意味着这里有一个教训要谨记:
当包含与转换器范围匹配的路径项时,请确保将它们放在更具体的项之后。
视图简介
转换器如何处理这些从路径中捕获的参数?这很难在不触及视图的情况下解释,所以要对视图给予入门介绍,关于视图更详细的内容,还是要阅读《跟老齐学Python:Django实战(第二版)》的深入浅出讲解。
视图的作用是接受请求并返回响应,使用Python的可选类型检查,下面是一个发送Hello World响应的示例。
from django.http import HttpRequest, HttpResponse
def some_view(request: HttpRequest) -> HttpResponse:
return HttpResponse('Hello World')
复制代码
Django将用户的HTTP请求封装在一个容器类中,这就是HttpRequest。同样,我们可以使用HttpResponse,以便让Django将我们的响应数据转换为格式正确的HTTP响应,并将其发送回用户的浏览器。
现在我们可以再看一个转换器。
path("blog/<int:year>/", views.blog_by_year),
复制代码
路由中有了这个转换器,
blog_by_year
会是什么样子?
# application/views.py
from django.http import HttpResponse
def blog_by_year(request, year):
# ... some code to handle the year
data = 'Some data that would be set by code above'
return HttpResponse(data)
复制代码
Django开始在这里展示出一些不错的品质!转换器为我们做了一堆乏味的工作。Django设置的year参数已经是一个整数,因为Django进行了字符串解析和转换。
如果有人提交
/blog/not_a_number/
,Django将返回
Not Found
响应,因为
not_a_number
不是整数。这样做的好处是,我们不必在
blog_by_year
中添加额外的检查逻辑来处理年份看起来不像数字的奇怪情况。这种函数可以节省时间!它使代码更干净,处理更精确。
怎样理解我们之前看到的另一个奇怪的例子
/blog/0/life-in-rome/
呢?这将与前面部分中的模式相匹配,但假设我们希望匹配一个四位数的年份,我们该怎么做到呢?可以使用正则表达式。
路由中的正则表达式
在编程中,正则表达式应用很普遍,常常被比作电锯:功能强大得令人难以置信,但如果不小心的话,也可以砍掉“你的脚”。
正则表达式可以以非常简洁的方式表示复杂的关系和匹配模式,这种简洁性常常给正则表达式带来难以理解的坏名声。但如果小心使用,它们可以是很好的工具。
正则表达式(通常缩写为“regex”)适合于匹配字符串中的复杂模式。这听起来像是要解决前年提到的年份问题!在我们的问题中,只想匹配一个四位数的整数。让我们看看Django的解决方案,然后分解它的含义。