(六)方法与路径共同决定路由
1. 先找路径,再确认可执行的动作
同样的 /api/books,GET 表示读取,POST 表示新增;不能只看到路径相同就调用同一段动作。
# 项目文件:web_project/src/webbook/routing.py
# 先比较路径再比较方法;路径存在但方法不匹配时返回405及Allow。
import re
from .http import Problem
ROUTES = [
(r"/", {"GET": "home"}),
(r"/login", {"GET": "login_page", "POST": "login"}),
(r"/logout", {"POST": "logout"}),
(r"/books", {"POST": "create_form"}),
(r"/api/session", {"GET": "session_info"}),
(r"/api/books", {"GET": "book_list", "POST": "create_json"}),
(r"/api/books/(?P<book_id>[1-9][0-9]*)", {"GET": "book_detail"}),
(r"/api/books/(?P<book_id>[1-9][0-9]*)/finish", {"POST": "finish"}),
(r"/assets/(?P<name>app\.css|app\.js)", {"GET": "asset"}),
]
def route(method, path):
for pattern, methods in ROUTES:
match = re.fullmatch(pattern, path)
if match:
if method not in methods:
raise Problem(
405,
"这个路径不接受该请求方法",
headers=[("Allow", ", ".join(methods))],
)
return methods[method], match.groupdict(), list(methods)
raise Problem(404, "没有找到这个地址")re.fullmatch 要求整个路径符合模式。(?P<book_id>...) 给捕获部分取名,match.groupdict 返回 {"book_id":"1"} 这样的参数字典;handler 是我们给分支取的名字,后面由应用组合对应动作。
模式中的 [1-9] 匹配一个非零数字,[0-9]* 接着匹配零个或多个数字;字符串前的 r 保留反斜杠本身,例如静态文件名中的 \. 表示字面上的句点。
路径存在但方法不支持时返回 405,并用 Allow 列出支持的方法;路径本身没有匹配时返回 404。静态资源也只接受表中列出的文件名,不把任意 URL 直接拼成可读取的本地路径。