游客发表
打开浏览器,网站路由输入 `example.com/blog/2024/03/15/hello-world`,网站路由按下回车。网站路由这个地址里藏着的网站路由不只是内容,还有一套决定请求去哪儿的网站路由规则——路由。
路由说白了就是网站路由路径匹配。服务器收到请求,网站路由拿到 URL 路径,网站路由然后在一堆预设规则里找,网站路由看哪条能对上。网站路由对上了,网站路由就交给对应的网站路由处理函数;对不上,返回 404。网站路由就这么简单,网站路由但细节里全是网站路由坑。
我最早接触路由是写 PHP 的时候。那时候用 Apache 的 .htaccess 做重写,把所有请求都指向 index.php,然后在 PHP 里用 $_SERVER['REQUEST_URI'] 拿到路径,自己写一堆 if-else 去判断。比如:
if ($path === '/about') { include 'about.php'; } elseif (preg_match('#^/post/(\d+)$#', $path, $m)) { $id = $m[1]; include 'post.php'; } 能跑,但丑。每加一个页面就得改这个文件,正则写多了自己都记不住哪个是哪个。后来用上了框架,路由配置变成了声明式的,清爽不少。Laravel 里写 Route::get('/post/{ id}', 'PostController@show'),Symfony、Express、Flask 也都差不多这个思路。路径里的 { id} 是占位符,匹配任意值,然后作为参数传给处理函数。
但路由不只是“匹配路径”这么简单。它还得处理 HTTP 方法。同一个路径,GET 和 POST 可能走完全不同的逻辑。RESTful API 里这是标配:GET /users 拿列表,POST /users 创建用户,GET /users/42 拿单个,DELETE /users/42 删掉。路由规则得把方法也考虑进去,否则就乱套了。
还有个容易忽略的东西:路由参数的约束。比如 /user/{ id},你肯定希望 id 是数字,而不是随便什么字符串。很多框架支持正则约束,Laravel 里写 ->where('id', '[0-9]+'),Express 里用 :id(\\d+)。不写约束也行,但到了控制器里还得再校验一遍,不如在路由层就挡掉。
顺序也很关键。路由匹配通常是按注册顺序来的,先匹配到的先赢。如果你把 /user/{ name} 放在 /user/list 前面,那访问 /user/list 的时候,name 会被当成 "list",永远进不了列表页。这种 bug 隐蔽得很,调试半天才发现是顺序问题。
前端路由是另一回事。单页应用里,URL 变了但页面不刷新,靠的是 History API 或者 hash。React Router、Vue Router 这些库把路径映射到组件,用户点链接的时候,JS 拦截默认跳转,pushState 改一下地址栏,然后渲染对应组件。这时候服务器那边通常配一条通配规则,把所有路径都指向 index.html,剩下的交给前端处理。Nginx 里就是 try_files $uri /index.html。
我见过有人把前后端路由搞混的。后端明明配了 /api/users,前端也配了 /users,结果开发环境用代理没问题,一上线就 404。原因是 Nginx 的 location 优先级没搞对,/api 的规则被 / 的规则覆盖了。这种问题不常遇到,遇到一次就够记一辈子。
路由设计得好不好,直接影响 API 的可读性。/getUserById?id=42 和 /users/42 哪个更顺眼,不用多说。REST 不是唯一选择,GraphQL 干脆只有一个端点,路由的概念被弱化了。但只要还在用 HTTP,路径和方法的组合就永远绕不开。
写路由的时候,我习惯先把资源列出来,再想每个资源需要哪些操作,最后拼成路径。这样不容易漏,也不容易设计出 /getUserPostsByDate 这种动词堆砌的怪物。路径应该是名词,方法才是动词。这个原则听起来简单,但真到写的时候,手一滑就忘了。
友情链接