一、路由系統理解
系統功能:根據用戶訪問的不同url
,執行對應的視圖函數。
web
服務器可以根據用戶訪問的url
地址的不同,返回相應的html
頁面,而html
的頁面渲染由視圖函數處理,這就需要有一個模塊負責分析用戶訪問的url
地址,並根據預先定義的映射規則,將請求分發到不同的視圖函數中進一步處理,負責這個工作的模塊就是web
框架中的路由系統。路由系統的工作總結起來就是:制定路由規則,分析url
,分發請求到響應視圖函數中。
路由系統的路由功能基於路由表,路由表是預先定義好的url
和視圖函數的映射記錄,換句話說,可以理解成將url
和視圖函數做了綁定,映射關系有點類似一個python
字典:
url_to_view_dic = {
'路徑1': view_func_1,
'路徑2': view_func_2,
'路徑n': view_func_n,
...
}
路由表的建立是控制層面,需要在實際業務啟動前就准備完畢,即:先有路由,后有業務。
一旦路由准備完畢,業務的轉發將會完全遵從路由表的指導:
去往路徑1的request --> 被路由器分發到view_func_1函數處理
去往路徑2的request --> 被路由器分發到view_func_2函數處理
去往路徑n的request --> 被路由器分發到view_func_n函數處理
...
二、路由系統功能划分
路由系統的本質功能是:指路,針對一次路由請求,返回下一跳轉發地址。
任何路由系統都將涵蓋至少如下兩個核心功能:
路由器的核心功能:(非常重要!!!!)
1、創建路由表(控制層面) ----> 用戶定義
2、路由分發(轉發層面) ----> django框架自動處理
三、路由表創建
創建工具
django
框架中的工具:re_path
和path
所有的web
請求都將以django
項目目錄下的urls.py
文件作為路由分發主入口,所以如果要完成最簡單的路由功能,只需要在此文件中預先配置好路由表即可。re_path
是django v1
的工具,path
是django v2
的工具,后者兼容前者。
# 項目urls.py文件, 目前兩種工具可以任選使用
re_path(r'home/', views.index)
path('articles/<int:id>', views.show_article)
路由的匹配順序是自上而下,一旦匹配即執行對應視圖函數,便不再繼續匹配。
所以路由表條目的順序很重要,有嚴格要求的路徑應該放前面,寬松要求甚至可以聚合的路徑應該放后面。匹配成功后的視圖函數以如下形式執行:
# 執行接口: view_func(request, *args, **kw)
# 參數是固定的request對象以及由re_path或path捕獲的無名分組/有名分組參數
views.index(request)
views.show_article(request, id)
如下是一張簡單的路由表配置:
# urls.py文件
urlpatterns = [
# 自帶后台管理頁面路由
path('admin/', admin.site.urls),
# 新增
re_path(r'^add/author/$', views.add_author),
re_path(r'^add/book/$', views.add_book),
# 刪除
re_path(r'delete/author/(\d+)', views.delete_author),
re_path(r'delete/book/(\d+)', views.delete_book),
# 修改
re_path(r'edit/author/(\d+)', views.edit_author),
re_path(r'edit/book/(\d+)', views.edit_book),
]
特別注意1,django
路由系統只會針對url
進行匹配,並不會再額外考慮method
或者其他request
中的屬性,這也意味着僅僅只需考慮url即可。(當然,我覺得后續如有需要,可以增加匹配因子,以便做到更精准的匹配)
特別注意2,在瀏覽器中訪問某一個url
,如果路徑結尾沒有添加/
,在django
框架中會被自動添加結尾的/
。在路由表中,匹配路徑的時候要關注/
,即:re_path(r'home/')
,換句話說,可以認為在django
的環境下,路徑pathinfo
是必須有后導/
的。
二級路由
二級路由的意思就是把項目urls文件中的路由整理划分,分布到各自的應用目錄urls
文件中,以此實現:
1、降低項目urls
路由文件中路由數量,由各自應用urls
路由文件承擔
2、解耦整個項目的路由表,出現路由問題的時候可以單獨在二級路由表中處理
3、多級路由以樹形結構執行查詢,在路由數量很大的時候,可以比單路由表有更快的查詢速度
用include
實現二級路由表,二級路由會將在一級路由匹配到的url
截斷后再發送給子路由表繼續匹配。以如下一級路由表為例,如果服務器收到一個http://www.xxx.com:8080/game/user/add/?name=a&pswd=b
的請求,首先會匹配一級路由表中的game/
並將截斷后的user/add/
發送到二級路由表繼續匹配。
re_path(r'game/', include('game_app.urls')),
re_path(r'chat/', include('chat_app.urls')),
re_path(r'vidio/', include('vidio_app,urls'),
路由別名
因為路由url
會被頻繁引用,所以會帶來修改時工作量過大的問題,解決辦法是啟用一個別名來代替url
原始url
,在所有引用的地方使用別名,這樣原始url
不論如何修改,都會被正確指向。當然,這個前提是,別名不能發生修改,否則同樣要變動所有引用此別名的地方,所以別名的定義非常重要。此外,路由別名的作用域是全局,它是一個全局變量,這也意味着使用路由別名也有重名覆蓋的風險。
使用路由別名的目的是獲取原始url
,如果原url
有動態部分,需要在解析的時候傳入對應參數來明確動態部分。
路由別名重名覆蓋風險的解決方法:
1、在全局urls
中定義每一個二級路由的namespace
2、在每一個二級路由urls
中定義app_name
3、在別名定義的時候加上區分前綴如:app01-home
, app02-home
別名的使用場景:
# 在模板中使用:
{% url '別名' *args, **kw %}
# 在視圖函數中使用:
reverse('別名', *args, **kw)
動態路由及重定向
動態路由
所謂的動態路由,其實就是聚合大量同類的url
,並用re
規則執行匹配並獲取動態數據部分。
# re_path:
re_path(r'articles/(?P<id>\d+)'), show_article) ---> show_article(request, id=id)
# path:
path('articles/<int:id>', show_article) ---> show_article(request, id=id)
重定向
return redirect(某一個具體網址,可來自於反向解析的結果)
四、自定義錯誤頁面
固定流程如下:
settings.py
中DEBUG
改為False
,ALLOWD_HOSTS
改為['*']
templates
中新建對應的404.html
, 500.html
等
urls
中定義:
handler404 = views.page_not_found
handler500 = views.server_error
views
中配置對應函數:
def page_not_found(request):
return render(request, '404.html')
def server_error(request):
return render(request, '500.html')
五、圖示路由系統在框架中的定位
每次請求到服務器,執行路由的流程圖
偽代碼實現以上圖示
# 啟動路由分發過程
def route(environ, route_table):
url = environ.url
view_func = None
# 遍歷路由表
for map in route_table:
if url == map[0]:
view_func= map[1]
break
return view_func
# 執行視圖函數處理過程
def start_handle(environ, view_func):
if view_func:
return view_func(environ)
else:
return page_not_found(environ)
# web服務器主循環
def run():
# 循環處理每一次的請求
while True:
# 從tcp中獲取當前客戶端請求的http字節數據
request_bytes = server.recv(1024)
# 根據http協議解析,得到http數據
request_http_data = http_parse(request_bytes.decode('utf-8'))
# web框架進一步處理http數據,封裝成方便使用的environ對象
environ = build_environ(request_http_data)
# 根據當前請求的url,在路由表中找到對應的視圖函數 ---> 路由系統的工作界面
view_func = route(environ, route_table)
# 啟動視圖函數,處理當前請求的具體內容, 返回處理結果
response = start_handle(environ, view_func)
# 按照http協議拆解web框架封裝好的response對象,得到http字符串
response_http_data = http_encapsulation(response)
# 發送http字節數據給客戶端
server.send(response_http_data.encode('utf-8'))
if __name__ == '__main__':
run()
六、路由系統的進階想法
進階考慮:
路由器收到請求request
后,轉發到后端另一台機器上執行,然后使用協程異步,處理其他的reqeust
請求。如果請求得到的響應,再切換回協程,然后執行響應。這樣可以實現入口服務器作為所有請求的承接者,然后轉發到對應的后面不同業務服務器處理各自的業務,可以把業務分離到不同的機器上,而且此時入口服務器也可以處理並發請求。
即:多個服務器上均部署django
,多台服務器之間的django
可以相互通信,這樣可以實現一個類似服務器集群的效果,可以完成負載均衡和備份的效果。