第三章 路由与异常
路由?是什么
作为前端程序员,只要用过 Vue 或 React,那不会没见过 route、routes、router。
一个 route 是一组对应关系,比如 /user 对应 “用户管理” 页面。
下面是一个常见的 route:
1{2 path: '/user',3 component: User, // User 是一个 UI 组件4}解释为:当浏览器地址栏的路径是 /user 时,渲染 User 组件。
多个 route 变成一个 route 数组,也就是 routes。
routes 里只包含 “对应关系”,并不会监听地址栏变化、再渲染 xx 组件。 这个功能由 router 承担。
可参考 Vue Router 官方文档的这段代码,注意里面的变量命名:
1import { createMemoryHistory, createRouter } from 'vue-router'2
3import HomeView from './HomeView.vue'4import AboutView from './AboutView.vue'5
6const routes = [7 { path: '/', component: HomeView },8 { path: '/about', component: AboutView },9]10
11const router = createRouter({12 history: createMemoryHistory(),13 routes, // router 里有 routes14})后端的 route 和 router 在概念、使用场景上,和前端是一样的。
handler?是什么
还有一个概念很重要 —— handler,它像前端的监听器:
- 在前端,不同的按钮,有不同的 listener
- 在后端,不同的请求,也有不同 handler(handle: 处理; handler: 处理器,处理者)
后端路由
作为前端程序员,肯定知道怎么区分请求,比如:
GET /user可能是 “获取所有用户列表” 的请求GET /user?id=1可能是 “获取 id 为 1 的用户” 的请求POST /user可能是 “新增用户” 的请求DELETE /user?id=1可能是 “删除 id 为 1 的用户” 的请求
路径相同时,未必是相同的请求。 还要看 method,甚至要看看参数。
1const http = require('http')2
3const routes = [4 { method: 'GET', path: '/user', handle: get_user },5 { method: 'DELETE', path: '/user', handle: delete_user },6 // ...7]8
9const server = http.createServer((req, res) => {10 console.log('收到请求', new Date())11 const method = req.method12 const url = new URL(req.url, 'http://' + req.headers.host)13
14 const route = routes.find(15 route => route.method == method && route.path == url.pathname16 )17
18 route.handle(req, res) // 这里先不考虑 handle 不存在的情况19})20
21server.listen(8080)22console.log('ok')23
24// 封装“响应 json”操作25function respond_json(res, data) {26 res.setHeader('Content-Type', 'application/json')27 res.write(JSON.stringify(data))28 res.end()29}30
31// 用于处理“获取用户”请求32function get_user(req, res) {33 const users = [ // 真实的后端程序里,这一步的数据要从数据库查询34 { name: 'PPz', year: 3 },35 { name: 'CCz', year: 2 },36 ]37 respond_json(res, users)38}39
40// 用于处理“删除用户”请求41function delete_user(req, res) {42 // 删除用户43 // 假装已经删除了44 res.write('ok') // 正式的后端不会只返回 'ok',但本章先只关注 route 和 router45 res.end()46}上面第 3-7 行,是 routes。 即 “xx 路径、yy method” 的请求由哪个函数来处理。
第 14-16 行,实际是起到一个 router 的作用, 正经项目中的 router 尽管比这个复杂,但原理跟这两三行代码一样。
第 18 行,从 “简易 router” 那里得到 handle 后, 执行 handle,并把 req 和 res 对象传过去。
第 24-29 行,封装了一个 “用于响应 json” 的函数。
第 31-38 行,处理 GET /user 请求。
第 40-46 行,处理 DELETE /user 请求。
404 和 500
上面的案例中,为展示最简单的路由,未考虑 “handle 不存在” 和 “handle 执行异常” 这两种情况。
1const http = require('http')2
3const routes = [4 { method: 'GET', path: '/user', handle: get_user },5 { method: 'DELETE', path: '/user', handle: delete_user },6 { method: 'POST', path: '/user', handle: create_user },7 // ...8]9
10const server = http.createServer(async (req, res) => {11 console.log('收到请求', new Date())12 const method = req.method13 const url = new URL(req.url, 'http://' + req.headers.host)14
15 const route = routes.find(16 route => route.method == method && route.path == url.pathname17 )18
19 if (!route.handle) { // handle 不存在时20 res.writeHead(404)21 res.write('未找到资源')22 res.end()23 return24 }25
26 try {27 // route.handle(req, res)28 await route.handle(req, res) // 大部分情况下,handle 都是异步的29 } catch (err) { // handle 发生异常时30 console.error(`处理请求 ${method} ${url.pathname} 时,发生异常`)31 console.error(err)32 res.writeHead(500)33 res.write('服务器内部错误')34 res.end()35 return36 }37})27 collapsed lines
38
39server.listen(8080)40console.log('ok')41
42// 封装“响应 json”操作43function respond_json(res, data) {44 res.setHeader('Content-Type', 'application/json')45 res.write(JSON.stringify(data))46 res.end()47}48
49// 用于处理“获取用户”请求50function get_user(req, res) {51 const users = [ // 真实的后端程序里,这一步的数据要从数据库查询52 { name: 'PPz', year: 3 },53 { name: 'CCz', year: 2 },54 ]55 respond_json(res, users)56}57
58// 用于处理 “删除用户” 请求59function delete_user(req, res) {60 // 删除用户61 // 假装已经删除了62 res.write('ok')63 res.end()64}65
66// 未实现的 “创建用户”67function create_user(req, res) {68 throw Error('现在还不能创建用户')69}404、500 这种码,前端程序员也不陌生,你一定也见过 200。 这种三位数字的码叫 http 状态码,一般地:
- 2xx 代表成功
- 4xx 代表客户端异常,比如 401,403 分别代表认证、权限问题
- 5xx 代表服务端异常
但 http 状态码并不常用, 因为它通常只用于表示通信过程的状态。 换句话说,当出现 4xx 或 5xx 时, 不是因为用户少填了字段或什么, 而是前端或后端的代码出现了 bug。
而且后端业务很复杂时,我想,500-599 这 100 个码可能不够用。 另外,http 状态码不常用还有历史原因: 互联网早期,后端响应的状态码不是 200 时,会被网络提供商拦截……
前端程序员对下面的数据结构一定不陌生:
1{2 code: 0,3 msg: 'success',4 data: {5 // ...6 },7}这里面的 code 和 msg 其实是 json 数据的一部分:
1res.write(JSON.stringify({2 code: 0,3 msg: 'success',4 data: {5 // ...6 },7}))code 为 0 代表正确,非 0 代表错误,这在我头一回见时,觉得很奇怪: 因为往往 0 代表 “假”、“错误”。 但我细想,成功只有一种,错误却可能千奇百怪:用户少填字段、格式不正确……
所以用 0 代表正确,用非 0 们代表各种各样的异常。
或者,你把 code 当成 “错误码” 也行:那么 0 就是 “没有错误”。